From fba37d66749a92f562b6f8bcc34e7181128f3282 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Wed, 6 Sep 2023 09:04:41 +0100 Subject: [PATCH 001/419] Task create page -File ID; that should not be there --- src/app/tasks/new-tasks/new-tasks.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/tasks/new-tasks/new-tasks.component.html b/src/app/tasks/new-tasks/new-tasks.component.html index 97c3f974..a5298f22 100644 --- a/src/app/tasks/new-tasks/new-tasks.component.html +++ b/src/app/tasks/new-tasks/new-tasks.component.html @@ -339,7 +339,7 @@ Date: Wed, 6 Sep 2023 09:37:00 +0100 Subject: [PATCH 002/419] Task create-File info;add template to allow break line --- .../wrbulk/wrbulk.component.html | 27 ++++++++++++++++--- .../new-preconfigured-tasks.component.html | 27 ++++++++++++++++--- .../tasks/new-tasks/new-tasks.component.html | 27 ++++++++++++++++--- 3 files changed, 72 insertions(+), 9 deletions(-) diff --git a/src/app/tasks/import-supertasks/wrbulk/wrbulk.component.html b/src/app/tasks/import-supertasks/wrbulk/wrbulk.component.html index 4753d4e5..82cab428 100644 --- a/src/app/tasks/import-supertasks/wrbulk/wrbulk.component.html +++ b/src/app/tasks/import-supertasks/wrbulk/wrbulk.component.html @@ -143,12 +143,19 @@ + +
+ Full Name: {{f.filename}} + Line Count: {{f.lineCount}} + Size: {{ f.size | fileSize:false}} +
+
@@ -199,12 +206,19 @@ + +
+ Full Name: {{f.filename}} + Line Count: {{f.lineCount}} + Size: {{ f.size | fileSize:false}} +
+
@@ -255,12 +269,19 @@ + +
+ Full Name: {{f.filename}} + Line Count: {{f.lineCount}} + Size: {{ f.size | fileSize:false}} +
+
diff --git a/src/app/tasks/new-preconfigured-tasks/new-preconfigured-tasks.component.html b/src/app/tasks/new-preconfigured-tasks/new-preconfigured-tasks.component.html index 21b04a94..486a048c 100644 --- a/src/app/tasks/new-preconfigured-tasks/new-preconfigured-tasks.component.html +++ b/src/app/tasks/new-preconfigured-tasks/new-preconfigured-tasks.component.html @@ -185,12 +185,19 @@

New Preconfigured Tasks (Copied From Task ID {{edited + +
+ Full Name: {{f.filename}} + Line Count: {{f.lineCount}} + Size: {{ f.size | fileSize:false}} +
+
@@ -222,12 +229,19 @@

New Preconfigured Tasks (Copied From Task ID {{edited + +
+ Full Name: {{f.filename}} + Line Count: {{f.lineCount}} + Size: {{ f.size | fileSize:false}} +
+
@@ -259,12 +273,19 @@

New Preconfigured Tasks (Copied From Task ID {{edited + +
+ Full Name: {{f.filename}} + Line Count: {{f.lineCount}} + Size: {{ f.size | fileSize:false}} +
+
diff --git a/src/app/tasks/new-tasks/new-tasks.component.html b/src/app/tasks/new-tasks/new-tasks.component.html index a5298f22..ebbb85a4 100644 --- a/src/app/tasks/new-tasks/new-tasks.component.html +++ b/src/app/tasks/new-tasks/new-tasks.component.html @@ -344,12 +344,19 @@ + +
+ Full Name: {{f.filename}} + Line Count: {{f.lineCount}} + Size: {{ f.size | fileSize:false}} +
+
@@ -401,12 +408,19 @@ + +
+ Full Name: {{f.filename}} + Line Count: {{f.lineCount}} + Size: {{ f.size | fileSize:false}} +
+
@@ -458,12 +472,19 @@ + +
+ Full Name: {{f.filename}} + Line Count: {{f.lineCount}} + Size: {{ f.size | fileSize:false}} +
+
From 217deb32f3027bac1abf3124bdc429fa5b1debc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Wed, 6 Sep 2023 09:47:39 +0100 Subject: [PATCH 003/419] File upload, align size,change text,use full space --- .../files/new-files/new-files.component.html | 44 +++++++++---------- 1 file changed, 20 insertions(+), 24 deletions(-) diff --git a/src/app/files/new-files/new-files.component.html b/src/app/files/new-files/new-files.component.html index b9b8becf..56820155 100644 --- a/src/app/files/new-files/new-files.component.html +++ b/src/app/files/new-files/new-files.component.html @@ -1,6 +1,6 @@ - +
    @@ -24,47 +24,43 @@
  - -
-
+
+
-
+
- -
-   -

Uploading File: {{fileStatus.filename}}

- +   +
+

Uploading File: {{fileStatus.filename}}

+ +

Upload completed!

-
;
-
-   +
 
+
- Files are uploaded to the server in small chunks. Once uploading do not refresh the page. + Do not refresh the page while uploading
From 3a27c17406daad165950ee94131837aab85293e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Wed, 6 Sep 2023 12:54:21 +0100 Subject: [PATCH 004/419] Task Overview; Setting priority.Working but value get overwritten by taskwrapper --- src/app/tasks/show-tasks/show-tasks.component.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/app/tasks/show-tasks/show-tasks.component.ts b/src/app/tasks/show-tasks/show-tasks.component.ts index b1afa39a..02eb4d37 100644 --- a/src/app/tasks/show-tasks/show-tasks.component.ts +++ b/src/app/tasks/show-tasks/show-tasks.component.ts @@ -217,7 +217,6 @@ onRefresh(){ this.ngOnInit(); this.rerender(); // rerender datatables } - //Get Tasks and SuperTasks combining the task API and the task wrapper getTasks():void { const params = {'maxResults': this.maxResults, 'expand': 'crackerBinary,crackerBinaryType,hashlist,assignedAgents', 'filter': 'isArchived='+this.isArchived+''}; @@ -229,8 +228,12 @@ getTasks():void { const matchObject = h.values.find(element => element.hashlistId === mainObject.hashlistId ); return { ...mainObject, ...matchObject } }) //Join Supertasks from TaskWrapper with Hashlist info + const addSTinfo = []; //Ass tasktype in tasks + for(let i=0; i < tw.values.length; i++){ + addSTinfo.push( {taskWrapperId: tw.values[i].taskWrapperId, taskType: tw.values[i].taskType}); + } let mergeTasks = tasks.values.map(mainObject => { - const matchObject = tw.values.find(element => element.taskWrapperId === mainObject.taskWrapperId ); + const matchObject = addSTinfo.find(element => element.taskWrapperId === mainObject.taskWrapperId ); return { ...mainObject, ...matchObject } }) // Join Tasks with Taskwrapper information for filtering let filtertasks = mergeTasks.filter(u=> (u.taskType == 0 && u.isArchived === this.isArchived)); //Filter Active Tasks remove subtasks From ac01a2ebd236c0ff6ea42ef30d5235db9c9bbafa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Wed, 6 Sep 2023 15:07:28 +0100 Subject: [PATCH 005/419] Urgent; catch error but not completewithe URL backend --- .../_interceptors/http-res.interceptor.ts | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/src/app/core/_interceptors/http-res.interceptor.ts b/src/app/core/_interceptors/http-res.interceptor.ts index c0af736a..099b4e0c 100644 --- a/src/app/core/_interceptors/http-res.interceptor.ts +++ b/src/app/core/_interceptors/http-res.interceptor.ts @@ -31,15 +31,18 @@ export class HttpResInterceptor implements HttpInterceptor{ errmsg = `Client Side Error: ${err}`; } else { - // console.log(error.error) - // console.log(error.status) - // errmsg = error.error.exception[0].message; - if(error.status === 401 || error.status === 0){ + if(error.status === 0){ + alert(`Unable to Connect to the Server: `+error.message); + } + if(error.status === 401){ errmsg = `${error.error.title}`; } if(error.status === 403){ errmsg = `You don't have permissions. Please contact your Administrator.`; } + if(error.status === 404){ + errmsg = `The requested URL was not found.`; + } // if(error.status !== 404 && error.status !== 403 && error.status !== 401 && error.status >= 300){ // this.router.navigate(['error']); // } @@ -56,4 +59,16 @@ export class HttpResInterceptor implements HttpInterceptor{ } finalize = (): void => this.ls.handleRequest(); + + isNetworkError(errorObject) { + return errorObject.message === "net::ERR_INTERNET_DISCONNECTED" || + errorObject.message === "net::ERR_PROXY_CONNECTION_FAILED" || + errorObject.message === "net::ERR_PROXY_CONNECTION_FAILED" || + errorObject.message === "net::ERR_CONNECTION_TIMED_OUT" || + errorObject.message === "net::ERR_CONNECTION_RESET" || + errorObject.message === "net::ERR_CONNECTION_CLOSE" || + errorObject.message === "net::ERR_UNKNOWN_PROTOCOL" || + errorObject.message === "net::ERR_SLOW_CONNECTION" || + errorObject.message === "net::ERR_NAME_NOT_RESOLVED" ; + } } From 3dff0b67a019244bc774fcbd079b53c8d142a4ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Wed, 6 Sep 2023 15:44:45 +0100 Subject: [PATCH 006/419] Minor - URL configurable using getEndpoint function --- src/app/agents/new-agent/new-agent.component.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/app/agents/new-agent/new-agent.component.ts b/src/app/agents/new-agent/new-agent.component.ts index e4273e37..ad77db1b 100644 --- a/src/app/agents/new-agent/new-agent.component.ts +++ b/src/app/agents/new-agent/new-agent.component.ts @@ -7,6 +7,7 @@ import Swal from 'sweetalert2/dist/sweetalert2.js'; import { Subject } from 'rxjs'; import { UIConfigService } from 'src/app/core/_services/shared/storage.service'; +import { ConfigService } from 'src/app/core/_services/shared/config.service'; import { GlobalService } from 'src/app/core/_services/main.service'; import { PageTitle } from 'src/app/core/_decorators/autotitle'; import { SERV } from '../../core/_services/main.config'; @@ -42,17 +43,22 @@ export class NewAgentComponent implements OnInit, OnDestroy { constructor( private uiService: UIConfigService, - private gs: GlobalService + private gs: GlobalService, + private cs:ConfigService ) { } private maxResults = environment.config.prodApiMaxResults; - pathURL = location.protocol + '//' + location.hostname + ':' + environment.config.agentApiPort; - public agentdownloadURL = this.pathURL + environment.config.agentdownloadURL; - public agentURL = this.pathURL + environment.config.agentURL; + public agentdownloadURL: string; + public agentURL: string; ngOnInit(): void { + const path = this.cs.getEndpoint().replace('/api/v2', ''); + + this.agentdownloadURL = path + environment.config.agentdownloadURL; + this.agentURL = path + '/api' +environment.config.agentURL; + // Generate Voucher this.randomstring = Math.random().toString(36).slice(-8); From 7ff11b6b954359bb160607c0ce9be351471ca595 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Wed, 6 Sep 2023 15:53:56 +0100 Subject: [PATCH 007/419] Minor Part II - URL configurable using getEndpoint function --- src/config/default/app/main.ts | 2 +- src/default/app/main.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/config/default/app/main.ts b/src/config/default/app/main.ts index ee1f0b14..f41db913 100644 --- a/src/config/default/app/main.ts +++ b/src/config/default/app/main.ts @@ -1,7 +1,7 @@ export const DEFAULT_CONFIG = { prodApiEndpoint: 'http://localhost:8080/api/v2', prodApiMaxResults: '3000', - agentURL: '/api/server.php', + agentURL: '/server.php', agentApiPort: '8080', agentdownloadURL: '/agents.php?download=', appName: 'Hashtopolis', diff --git a/src/default/app/main.ts b/src/default/app/main.ts index ee1f0b14..f41db913 100644 --- a/src/default/app/main.ts +++ b/src/default/app/main.ts @@ -1,7 +1,7 @@ export const DEFAULT_CONFIG = { prodApiEndpoint: 'http://localhost:8080/api/v2', prodApiMaxResults: '3000', - agentURL: '/api/server.php', + agentURL: '/server.php', agentApiPort: '8080', agentdownloadURL: '/agents.php?download=', appName: 'Hashtopolis', From 28b4dbb58c6c65dc4313586c16c092c17459668c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Wed, 6 Sep 2023 16:11:18 +0100 Subject: [PATCH 008/419] Minor add brackets --- src/app/tasks/show-tasks/show-tasks.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/tasks/show-tasks/show-tasks.component.html b/src/app/tasks/show-tasks/show-tasks.component.html index 754218fc..e4bdd9e6 100644 --- a/src/app/tasks/show-tasks/show-tasks.component.html +++ b/src/app/tasks/show-tasks/show-tasks.component.html @@ -42,7 +42,7 @@
- +
From c8db85966666ddc4b406ffe3042e69c800533b60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Wed, 6 Sep 2023 21:34:56 +0100 Subject: [PATCH 009/419] All pages-change transparency and overflow menu --- .../notifications.component.html | 2 +- .../notifications/notifications.component.ts | 1 + .../agent-status/agent-status.component.html | 2 +- .../agent-status/agent-status.component.ts | 1 + .../edit-agent/edit-agent.component.html | 2 +- .../agents/edit-agent/edit-agent.component.ts | 1 + .../agents/new-agent/new-agent.component.html | 4 +- .../agents/new-agent/new-agent.component.ts | 1 + .../show-agents/show-agents.component.html | 2 +- .../show-agents/show-agents.component.ts | 1 + .../agent-binaries.component.html | 2 +- .../agent-binaries.component.ts | 1 + .../engine/crackers/crackers.component.html | 2 +- .../engine/crackers/crackers.component.ts | 1 + .../preprocessors.component.html | 2 +- .../preprocessors/preprocessors.component.ts | 1 + .../config/hashtypes/hashtypes.component.html | 2 +- .../config/hashtypes/hashtypes.component.ts | 1 + .../edit-health-checks.component.html | 2 +- .../edit-health-checks.component.ts | 1 + .../health-checks.component.html | 2 +- .../health-checks/health-checks.component.ts | 1 + src/app/config/log/log.component.html | 2 +- src/app/config/log/log.component.ts | 1 + src/app/files/files.component.html | 2 +- src/app/files/files.component.ts | 1 + .../edit-hashlist.component.html | 4 +- .../edit-hashlist/edit-hashlist.component.ts | 43 ++++++++++--------- .../hashlists/hashes/hashes.component.html | 2 +- src/app/hashlists/hashes/hashes.component.ts | 1 + .../hashlist/hashlist.component.html | 2 +- .../hashlists/hashlist/hashlist.component.ts | 1 + .../search-hash/search-hash.component.html | 2 +- .../show-cracks/show-cracks.component.html | 2 +- .../show-cracks/show-cracks.component.ts | 1 + .../superhashlist.component.html | 2 +- .../superhashlist/superhashlist.component.ts | 1 + src/app/projects/projects.component.html | 2 +- src/app/projects/projects.component.ts | 1 + .../cheatsheet/cheatsheet.component.html | 6 +-- src/app/shared/table/table.component.ts | 4 +- src/app/tasks/chunks/chunks.component.html | 2 +- src/app/tasks/chunks/chunks.component.ts | 1 + .../edit-preconfigured-tasks.component.html | 2 +- .../edit-preconfigured-tasks.component.ts | 1 + .../edit-supertasks.component.html | 2 +- .../edit-tasks/edit-tasks.component.html | 6 +-- .../tasks/edit-tasks/edit-tasks.component.ts | 1 + .../wrbulk/wrbulk.component.html | 6 +-- .../wrbulk/wrbulk.component.ts | 1 + .../new-preconfigured-tasks.component.html | 6 +-- .../new-preconfigured-tasks.component.ts | 1 + .../tasks/new-tasks/new-tasks.component.html | 6 +-- .../tasks/new-tasks/new-tasks.component.ts | 1 + .../preconfigured-tasks.component.html | 2 +- .../preconfigured-tasks.component.ts | 1 + .../modal-subtasks.component.html | 2 +- .../modal-subtasks.component.ts | 1 + .../show-tasks/show-tasks.component.html | 2 +- .../tasks/show-tasks/show-tasks.component.ts | 1 + .../modal-pretasks.component.html | 2 +- .../modal-pretasks.component.ts | 1 + .../supertasks/supertasks.component.html | 2 +- .../users/all-users/all-users.component.html | 2 +- .../users/all-users/all-users.component.ts | 1 + ...dit-globalpermissionsgroups.component.html | 2 +- .../globalpermissionsgroups.component.html | 2 +- .../globalpermissionsgroups.component.ts | 1 + src/app/users/groups/groups.component.html | 2 +- src/app/users/groups/groups.component.ts | 1 + src/styles/components/_table.scss | 27 +++++++++++- 71 files changed, 129 insertions(+), 74 deletions(-) diff --git a/src/app/account/notifications/notifications.component.html b/src/app/account/notifications/notifications.component.html index a3914f88..0d29bdc0 100644 --- a/src/app/account/notifications/notifications.component.html +++ b/src/app/account/notifications/notifications.component.html @@ -1,7 +1,7 @@ - +
diff --git a/src/app/account/notifications/notifications.component.ts b/src/app/account/notifications/notifications.component.ts index 38fc97b7..da0fe9da 100644 --- a/src/app/account/notifications/notifications.component.ts +++ b/src/app/account/notifications/notifications.component.ts @@ -47,6 +47,7 @@ export class NotificationsComponent implements OnInit { const self = this; this.dtOptions = { dom: 'Bfrtip', + scrollX: true, pageLength: 10, stateSave: true, select: true, diff --git a/src/app/agents/agent-status/agent-status.component.html b/src/app/agents/agent-status/agent-status.component.html index adbedb7e..ee134ad0 100644 --- a/src/app/agents/agent-status/agent-status.component.html +++ b/src/app/agents/agent-status/agent-status.component.html @@ -407,7 +407,7 @@
ID
+
diff --git a/src/app/agents/agent-status/agent-status.component.ts b/src/app/agents/agent-status/agent-status.component.ts index c49e62b6..3930f52a 100644 --- a/src/app/agents/agent-status/agent-status.component.ts +++ b/src/app/agents/agent-status/agent-status.component.ts @@ -94,6 +94,7 @@ export class AgentStatusComponent implements OnInit { const self = this; this.dtOptions = { dom: 'Bfrtip', + scrollX: true, scrollY: true, bDestroy: true, columnDefs: [ diff --git a/src/app/agents/edit-agent/edit-agent.component.html b/src/app/agents/edit-agent/edit-agent.component.html index f156e6d3..e43cfa85 100644 --- a/src/app/agents/edit-agent/edit-agent.component.html +++ b/src/app/agents/edit-agent/edit-agent.component.html @@ -247,7 +247,7 @@

Agent Detailed Information

-
ID
+
diff --git a/src/app/agents/edit-agent/edit-agent.component.ts b/src/app/agents/edit-agent/edit-agent.component.ts index abc91cd9..03117aeb 100644 --- a/src/app/agents/edit-agent/edit-agent.component.ts +++ b/src/app/agents/edit-agent/edit-agent.component.ts @@ -137,6 +137,7 @@ export class EditAgentComponent implements OnInit { const self = this; this.dtOptions = { dom: 'Bfrtip', + scrollX: true, scrollY: "700px", scrollCollapse: true, paging: false, diff --git a/src/app/agents/new-agent/new-agent.component.html b/src/app/agents/new-agent/new-agent.component.html index e45210b2..4e070852 100644 --- a/src/app/agents/new-agent/new-agent.component.html +++ b/src/app/agents/new-agent/new-agent.component.html @@ -30,7 +30,7 @@ -
ID
+
@@ -93,7 +93,7 @@
-
ID
+
diff --git a/src/app/agents/new-agent/new-agent.component.ts b/src/app/agents/new-agent/new-agent.component.ts index ad77db1b..d90a5913 100644 --- a/src/app/agents/new-agent/new-agent.component.ts +++ b/src/app/agents/new-agent/new-agent.component.ts @@ -79,6 +79,7 @@ export class NewAgentComponent implements OnInit, OnDestroy { this.dtOptions = { dom: 'Bfrtip', + scrollX: true, pageLength: 10, stateSave: true, select: true, diff --git a/src/app/agents/show-agents/show-agents.component.html b/src/app/agents/show-agents/show-agents.component.html index 17735aac..1177114f 100644 --- a/src/app/agents/show-agents/show-agents.component.html +++ b/src/app/agents/show-agents/show-agents.component.html @@ -1,7 +1,7 @@ -
Key
+
diff --git a/src/app/agents/show-agents/show-agents.component.ts b/src/app/agents/show-agents/show-agents.component.ts index cb6fee05..35f8850e 100644 --- a/src/app/agents/show-agents/show-agents.component.ts +++ b/src/app/agents/show-agents/show-agents.component.ts @@ -66,6 +66,7 @@ export class ShowAgentsComponent implements OnInit, OnDestroy { const self = this; this.dtOptions = { dom: 'Bfrtip', + scrollX: true, stateSave: true, destroy: true, select: { diff --git a/src/app/config/engine/agent-binaries/agent-binaries.component.html b/src/app/config/engine/agent-binaries/agent-binaries.component.html index 58700a3e..e3fd212f 100644 --- a/src/app/config/engine/agent-binaries/agent-binaries.component.html +++ b/src/app/config/engine/agent-binaries/agent-binaries.component.html @@ -2,7 +2,7 @@ -
ID
+
diff --git a/src/app/config/engine/agent-binaries/agent-binaries.component.ts b/src/app/config/engine/agent-binaries/agent-binaries.component.ts index eaf2f207..27089fc7 100644 --- a/src/app/config/engine/agent-binaries/agent-binaries.component.ts +++ b/src/app/config/engine/agent-binaries/agent-binaries.component.ts @@ -45,6 +45,7 @@ export class AgentBinariesComponent implements OnInit { }); this.dtOptions = { dom: 'Bfrtip', + scrollX: true, pageLength: 10, stateSave: true, select: true, diff --git a/src/app/config/engine/crackers/crackers.component.html b/src/app/config/engine/crackers/crackers.component.html index ff05df18..5d6bdfd2 100644 --- a/src/app/config/engine/crackers/crackers.component.html +++ b/src/app/config/engine/crackers/crackers.component.html @@ -2,7 +2,7 @@ -
ID
+
diff --git a/src/app/config/engine/crackers/crackers.component.ts b/src/app/config/engine/crackers/crackers.component.ts index b0f9633d..ea8a1115 100644 --- a/src/app/config/engine/crackers/crackers.component.ts +++ b/src/app/config/engine/crackers/crackers.component.ts @@ -50,6 +50,7 @@ export class CrackersComponent implements OnInit, OnDestroy { const self = this; this.dtOptions = { dom: 'Bfrtip', + scrollX: true, pageLength: 10, stateSave: true, select: true, diff --git a/src/app/config/engine/preprocessors/preprocessors.component.html b/src/app/config/engine/preprocessors/preprocessors.component.html index 0aa3aef6..8f4a477e 100644 --- a/src/app/config/engine/preprocessors/preprocessors.component.html +++ b/src/app/config/engine/preprocessors/preprocessors.component.html @@ -2,7 +2,7 @@ -
Cracker ID
+
diff --git a/src/app/config/engine/preprocessors/preprocessors.component.ts b/src/app/config/engine/preprocessors/preprocessors.component.ts index 0868e327..bd13b0e6 100644 --- a/src/app/config/engine/preprocessors/preprocessors.component.ts +++ b/src/app/config/engine/preprocessors/preprocessors.component.ts @@ -47,6 +47,7 @@ export class PreprocessorsComponent implements OnInit { const self = this; this.dtOptions = { dom: 'Bfrtip', + scrollX: true, pageLength: 10, stateSave: true, select: true, diff --git a/src/app/config/hashtypes/hashtypes.component.html b/src/app/config/hashtypes/hashtypes.component.html index dcaeeb42..0bb488e4 100644 --- a/src/app/config/hashtypes/hashtypes.component.html +++ b/src/app/config/hashtypes/hashtypes.component.html @@ -1,7 +1,7 @@ -
Preprocessor ID
+
diff --git a/src/app/config/hashtypes/hashtypes.component.ts b/src/app/config/hashtypes/hashtypes.component.ts index 3cf0c67e..8f745862 100644 --- a/src/app/config/hashtypes/hashtypes.component.ts +++ b/src/app/config/hashtypes/hashtypes.component.ts @@ -49,6 +49,7 @@ export class HashtypesComponent implements OnInit { const self = this; this.dtOptions = { dom: 'Bfrtip', + scrollX: true, pageLength: 10, select: true, processing: true, // Error loading diff --git a/src/app/config/health-checks/edit-health-check/edit-health-checks.component.html b/src/app/config/health-checks/edit-health-check/edit-health-checks.component.html index a5db08a3..6c51896d 100644 --- a/src/app/config/health-checks/edit-health-check/edit-health-checks.component.html +++ b/src/app/config/health-checks/edit-health-check/edit-health-checks.component.html @@ -22,7 +22,7 @@ -
Hashtype (Hashcat -m)
+
diff --git a/src/app/config/health-checks/edit-health-check/edit-health-checks.component.ts b/src/app/config/health-checks/edit-health-check/edit-health-checks.component.ts index a1f76f0f..dbf53777 100644 --- a/src/app/config/health-checks/edit-health-check/edit-health-checks.component.ts +++ b/src/app/config/health-checks/edit-health-check/edit-health-checks.component.ts @@ -88,6 +88,7 @@ export class EditHealthChecksComponent implements OnInit { const self = this; this.dtOptions = { dom: 'Bfrtip', + scrollX: true, pageLength: 10, stateSave: true, select: true, diff --git a/src/app/config/health-checks/health-checks.component.html b/src/app/config/health-checks/health-checks.component.html index 0853412a..fe1cb215 100644 --- a/src/app/config/health-checks/health-checks.component.html +++ b/src/app/config/health-checks/health-checks.component.html @@ -1,7 +1,7 @@ -
ID
+
diff --git a/src/app/config/health-checks/health-checks.component.ts b/src/app/config/health-checks/health-checks.component.ts index cf2bf8e0..df2cbfc6 100644 --- a/src/app/config/health-checks/health-checks.component.ts +++ b/src/app/config/health-checks/health-checks.component.ts @@ -78,6 +78,7 @@ export class HealthChecksComponent implements OnInit { const self = this; this.dtOptions = { dom: 'Bfrtip', + scrollX: true, stateSave: true, select: true, buttons: { diff --git a/src/app/config/log/log.component.html b/src/app/config/log/log.component.html index 6ab855a3..4b6a090d 100644 --- a/src/app/config/log/log.component.html +++ b/src/app/config/log/log.component.html @@ -1,7 +1,7 @@ -
ID
+
diff --git a/src/app/config/log/log.component.ts b/src/app/config/log/log.component.ts index cbdd0c96..21aeb8be 100644 --- a/src/app/config/log/log.component.ts +++ b/src/app/config/log/log.component.ts @@ -98,6 +98,7 @@ export class LogComponent implements OnInit { const self = this; this.dtOptions = { dom: 'Bfrtip', + scrollX: true, bStateSave:true, bPaginate: false, bLengthChange: false, diff --git a/src/app/files/files.component.html b/src/app/files/files.component.html index 537865fe..3925cb83 100644 --- a/src/app/files/files.component.html +++ b/src/app/files/files.component.html @@ -11,7 +11,7 @@ -
ID
+
diff --git a/src/app/files/files.component.ts b/src/app/files/files.component.ts index 51e2ce6f..1a01a718 100644 --- a/src/app/files/files.component.ts +++ b/src/app/files/files.component.ts @@ -101,6 +101,7 @@ export class FilesComponent implements OnInit { const self = this; this.dtOptions = { dom: 'Bfrtip', + scrollX: true, scrollY: true, stateSave: true, destroy: true, diff --git a/src/app/hashlists/edit-hashlist/edit-hashlist.component.html b/src/app/hashlists/edit-hashlist/edit-hashlist.component.html index c5691b4a..c83a04dd 100644 --- a/src/app/hashlists/edit-hashlist/edit-hashlist.component.html +++ b/src/app/hashlists/edit-hashlist/edit-hashlist.component.html @@ -120,7 +120,7 @@ -
ID
+
@@ -150,7 +150,7 @@ -
ID
+
diff --git a/src/app/hashlists/edit-hashlist/edit-hashlist.component.ts b/src/app/hashlists/edit-hashlist/edit-hashlist.component.ts index f244b40b..09bd95b2 100644 --- a/src/app/hashlists/edit-hashlist/edit-hashlist.component.ts +++ b/src/app/hashlists/edit-hashlist/edit-hashlist.component.ts @@ -34,7 +34,9 @@ export class EditHashlistComponent implements OnInit { dtElement: DataTableDirective; dtTrigger: Subject = new Subject(); + dtTrigger1: Subject = new Subject(); dtOptions: any = {}; + dtOptions1: any = {}; constructor( private format: StaticArrayPipe, @@ -142,8 +144,19 @@ export class EditHashlistComponent implements OnInit { }), }); + this.dtTrigger1.next(null); + + // Display Tasks Expand tasks + }); } + this.dtOptions1 = { + dom: 'Bfrtip', + scrollX: true, + bStateSave:true, + destroy: true, + buttons:[] + } } // Remove when expand task is working @@ -151,9 +164,7 @@ export class EditHashlistComponent implements OnInit { const params = {'maxResults': this.maxResults, 'expand': 'crackerBinary,crackerBinaryType,hashlist', 'filter': 'isArchived=false'} const taskh = [] this.gs.getAll(SERV.TASKS,params).subscribe((tasks: any) => { - console.log(tasks) for(let i=0; i < tasks.values.length; i++){ - console.log( tasks.values[i].hashlist) let firtprep = tasks.values[i].hashlist; for(let i=0; i < firtprep.length; i++){ const match = firtprep[i].hashlistId == this.editedHashlistIndex; @@ -163,27 +174,17 @@ export class EditHashlistComponent implements OnInit { } } this.alltasks = taskh; - - this.dtOptions[0] = { - dom: 'Bfrtip', - scrollY: "700px", - scrollCollapse: true, - paging: false, - autoWidth: false, - // destroy: true, - buttons: { - dom: { - button: { - className: 'dt-button buttons-collection btn btn-sm-dt btn-outline-gray-600-dt', - } - }, - buttons:[] - } - } - this.dtTrigger.next(null); - }); + + this.dtOptions = { + dom: 'Bfrtip', + scrollX: true, + bStateSave:true, + destroy: true, + buttons:[] + } + } // @HostListener allows us to also guard against browser refresh, close, etc. diff --git a/src/app/hashlists/hashes/hashes.component.html b/src/app/hashlists/hashes/hashes.component.html index 0a02d5b4..517e9ec2 100644 --- a/src/app/hashlists/hashes/hashes.component.html +++ b/src/app/hashlists/hashes/hashes.component.html @@ -65,7 +65,7 @@

Hashes of hashlist +

ID
diff --git a/src/app/hashlists/hashes/hashes.component.ts b/src/app/hashlists/hashes/hashes.component.ts index 06a61413..13b13d40 100644 --- a/src/app/hashlists/hashes/hashes.component.ts +++ b/src/app/hashlists/hashes/hashes.component.ts @@ -66,6 +66,7 @@ export class HashesComponent implements OnInit { this.dtOptions = { dom: 'Bfrtip', + scrollX: true, pageLength: 10, searching: false, buttons: { diff --git a/src/app/hashlists/hashlist/hashlist.component.html b/src/app/hashlists/hashlist/hashlist.component.html index 2c59958f..cdead6ba 100644 --- a/src/app/hashlists/hashlist/hashlist.component.html +++ b/src/app/hashlists/hashlist/hashlist.component.html @@ -1,7 +1,7 @@ -
Matching hashes
+
diff --git a/src/app/hashlists/hashlist/hashlist.component.ts b/src/app/hashlists/hashlist/hashlist.component.ts index 6bae1020..b72efe2c 100644 --- a/src/app/hashlists/hashlist/hashlist.component.ts +++ b/src/app/hashlists/hashlist/hashlist.component.ts @@ -93,6 +93,7 @@ export class HashlistComponent implements OnInit, OnDestroy { const self = this; this.dtOptions = { dom: 'Bfrtip', + scrollX: true, stateSave: true, select: { style: 'multi', diff --git a/src/app/hashlists/search-hash/search-hash.component.html b/src/app/hashlists/search-hash/search-hash.component.html index 3346096b..70ab25e6 100644 --- a/src/app/hashlists/search-hash/search-hash.component.html +++ b/src/app/hashlists/search-hash/search-hash.component.html @@ -24,7 +24,7 @@ -
ID
+
diff --git a/src/app/hashlists/show-cracks/show-cracks.component.html b/src/app/hashlists/show-cracks/show-cracks.component.html index e5ba19b2..902b7f49 100644 --- a/src/app/hashlists/show-cracks/show-cracks.component.html +++ b/src/app/hashlists/show-cracks/show-cracks.component.html @@ -1,7 +1,7 @@ -
Hash
+
diff --git a/src/app/hashlists/show-cracks/show-cracks.component.ts b/src/app/hashlists/show-cracks/show-cracks.component.ts index 38c33600..23674bf4 100644 --- a/src/app/hashlists/show-cracks/show-cracks.component.ts +++ b/src/app/hashlists/show-cracks/show-cracks.component.ts @@ -44,6 +44,7 @@ export class ShowCracksComponent implements OnInit { const self = this; this.dtOptions = { dom: 'Bfrtip', + scrollX: true, pageLength: 10, stateSave: true, select: true, diff --git a/src/app/hashlists/superhashlist/superhashlist.component.html b/src/app/hashlists/superhashlist/superhashlist.component.html index 63879a42..eb780d47 100644 --- a/src/app/hashlists/superhashlist/superhashlist.component.html +++ b/src/app/hashlists/superhashlist/superhashlist.component.html @@ -1,7 +1,7 @@ -
Time found
+
diff --git a/src/app/hashlists/superhashlist/superhashlist.component.ts b/src/app/hashlists/superhashlist/superhashlist.component.ts index ab93fea9..d4e5bc24 100644 --- a/src/app/hashlists/superhashlist/superhashlist.component.ts +++ b/src/app/hashlists/superhashlist/superhashlist.component.ts @@ -47,6 +47,7 @@ export class SuperhashlistComponent implements OnInit { const self = this; this.dtOptions = { dom: 'Bfrtip', + scrollX: true, pageLength: 10, stateSave: true, select: true, diff --git a/src/app/projects/projects.component.html b/src/app/projects/projects.component.html index 847d0d1f..0abdd428 100644 --- a/src/app/projects/projects.component.html +++ b/src/app/projects/projects.component.html @@ -14,7 +14,7 @@

Projects

-
ID
+
diff --git a/src/app/projects/projects.component.ts b/src/app/projects/projects.component.ts index 9937febc..190d7603 100644 --- a/src/app/projects/projects.component.ts +++ b/src/app/projects/projects.component.ts @@ -48,6 +48,7 @@ export class ProjectsComponent implements OnInit { const self = this; this.dtOptions = { dom: 'Bfrtip', + scrollX: true, bStateSave:true, destroy: true, select: { diff --git a/src/app/shared/alert/cheatsheet/cheatsheet.component.html b/src/app/shared/alert/cheatsheet/cheatsheet.component.html index 973226e9..52b16368 100644 --- a/src/app/shared/alert/cheatsheet/cheatsheet.component.html +++ b/src/app/shared/alert/cheatsheet/cheatsheet.component.html @@ -16,7 +16,7 @@ -
ID
+
@@ -34,7 +34,7 @@ -
Value
+
@@ -52,7 +52,7 @@ -
Type
+
diff --git a/src/app/shared/table/table.component.ts b/src/app/shared/table/table.component.ts index cbcce797..a2354aa5 100644 --- a/src/app/shared/table/table.component.ts +++ b/src/app/shared/table/table.component.ts @@ -5,8 +5,8 @@ import { Router } from '@angular/router'; selector: 'app-table', template: `
-
-
+
+
`, diff --git a/src/app/tasks/chunks/chunks.component.html b/src/app/tasks/chunks/chunks.component.html index de054cfc..dde99771 100644 --- a/src/app/tasks/chunks/chunks.component.html +++ b/src/app/tasks/chunks/chunks.component.html @@ -8,7 +8,7 @@
-
Value
+
diff --git a/src/app/tasks/chunks/chunks.component.ts b/src/app/tasks/chunks/chunks.component.ts index 54ec9c8f..6e5c42bd 100644 --- a/src/app/tasks/chunks/chunks.component.ts +++ b/src/app/tasks/chunks/chunks.component.ts @@ -98,6 +98,7 @@ export class ChunksComponent implements OnInit { const self = this; this.dtOptions = { dom: 'Bfrtip', + scrollX: true, pageLength: 10, select: true, processing: true, diff --git a/src/app/tasks/edit-preconfigured-tasks/edit-preconfigured-tasks.component.html b/src/app/tasks/edit-preconfigured-tasks/edit-preconfigured-tasks.component.html index c137a26a..94eaf229 100644 --- a/src/app/tasks/edit-preconfigured-tasks/edit-preconfigured-tasks.component.html +++ b/src/app/tasks/edit-preconfigured-tasks/edit-preconfigured-tasks.component.html @@ -145,7 +145,7 @@
-
ID
+
diff --git a/src/app/tasks/edit-preconfigured-tasks/edit-preconfigured-tasks.component.ts b/src/app/tasks/edit-preconfigured-tasks/edit-preconfigured-tasks.component.ts index 724a047c..825febc3 100644 --- a/src/app/tasks/edit-preconfigured-tasks/edit-preconfigured-tasks.component.ts +++ b/src/app/tasks/edit-preconfigured-tasks/edit-preconfigured-tasks.component.ts @@ -93,6 +93,7 @@ export class EditPreconfiguredTasksComponent implements OnInit{ this.dtOptions = { dom: 'Bfrtip', + scrollX: true, pageLength: 10, stateSave: true, select: true, diff --git a/src/app/tasks/edit-supertasks/edit-supertasks.component.html b/src/app/tasks/edit-supertasks/edit-supertasks.component.html index 637ccf13..599c692d 100644 --- a/src/app/tasks/edit-supertasks/edit-supertasks.component.html +++ b/src/app/tasks/edit-supertasks/edit-supertasks.component.html @@ -107,7 +107,7 @@
Estimate Time

Pretasks

-
ID
+
diff --git a/src/app/tasks/edit-tasks/edit-tasks.component.html b/src/app/tasks/edit-tasks/edit-tasks.component.html index 0f279bd8..4d117a48 100644 --- a/src/app/tasks/edit-tasks/edit-tasks.component.html +++ b/src/app/tasks/edit-tasks/edit-tasks.component.html @@ -301,7 +301,7 @@
-
ID
+
@@ -362,7 +362,7 @@
-
ID
+
@@ -435,7 +435,7 @@
-
ID
+
diff --git a/src/app/tasks/edit-tasks/edit-tasks.component.ts b/src/app/tasks/edit-tasks/edit-tasks.component.ts index 1dab94ac..1fd1c725 100644 --- a/src/app/tasks/edit-tasks/edit-tasks.component.ts +++ b/src/app/tasks/edit-tasks/edit-tasks.component.ts @@ -340,6 +340,7 @@ export class EditTasksComponent implements OnInit,PendingChangesGuard { const self = this; this.dtOptions = { dom: 'Bfrtip', + scrollX: true, scrollY: "700px", scrollCollapse: true, paging: false, diff --git a/src/app/tasks/import-supertasks/wrbulk/wrbulk.component.html b/src/app/tasks/import-supertasks/wrbulk/wrbulk.component.html index 82cab428..ed2cdc43 100644 --- a/src/app/tasks/import-supertasks/wrbulk/wrbulk.component.html +++ b/src/app/tasks/import-supertasks/wrbulk/wrbulk.component.html @@ -104,7 +104,7 @@ WordList -
ID
+
- - - - - - - diff --git a/src/app/files/files-edit/files-edit.component.ts b/src/app/files/files-edit/files-edit.component.ts deleted file mode 100644 index 2c27482a..00000000 --- a/src/app/files/files-edit/files-edit.component.ts +++ /dev/null @@ -1,147 +0,0 @@ -import { FormControl, FormGroup, Validators } from '@angular/forms'; -import { ActivatedRoute, Params } from '@angular/router'; -import { Component, OnInit } from '@angular/core'; -import { Router } from '@angular/router'; - -import { AlertService } from 'src/app/core/_services/shared/alert.service'; -import { GlobalService } from 'src/app/core/_services/main.service'; -import { PageTitle } from 'src/app/core/_decorators/autotitle'; -import { SERV } from '../../core/_services/main.config'; -import { Filetype } from '../../core/_models/file.model'; - -@Component({ - selector: 'app-files-edit', - templateUrl: './files-edit.component.html' -}) -@PageTitle(['Edit File']) -export class FilesEditComponent implements OnInit { - editMode = false; - editedFileIndex: number; - editedFile: any; // Change to Model - - filterType: number; - whichView: string; - - // accessgroup: AccessGroup; //Use models when data structure is reliable - updateForm: FormGroup; - accessgroup: any[]; - allfiles: any[]; - filetype: any[]; - - constructor( - private route: ActivatedRoute, - private alert: AlertService, - private gs: GlobalService, - private router: Router - ) {} - - ngOnInit(): void { - this.route.params.subscribe((params: Params) => { - this.editedFileIndex = +params['id']; - this.editMode = params['id'] != null; - this.initForm(); - }); - - this.updateForm = new FormGroup({ - fileId: new FormControl({ value: '', disabled: true }), - updateData: new FormGroup({ - filename: new FormControl('', [ - Validators.required, - Validators.minLength(1) - ]), - fileType: new FormControl(null), - accessGroupId: new FormControl(null), - isSecret: new FormControl(null) - }) - }); - - this.route.data.subscribe((data) => { - switch (data['kind']) { - case 'wordlist': - this.whichView = 'wordlist-edit'; - break; - - case 'rules': - this.whichView = 'rules-edit'; - break; - - case 'other': - this.whichView = 'other-edit'; - break; - } - - this.filetype = [ - { fileType: 0, fileName: 'Wordlist' }, - { fileType: 1, fileName: 'Rules' }, - { fileType: 2, fileName: 'Other' } - ]; - - this.gs.getAll(SERV.ACCESS_GROUPS).subscribe((agroups: any) => { - this.accessgroup = agroups.values; - }); - - this.gs.get(SERV.FILES, this.editedFileIndex).subscribe((files: any) => { - this.allfiles = files; - }); - }); - } - - onSubmit(): void { - this.gs - .update( - SERV.FILES, - this.editedFileIndex, - this.updateForm.value['updateData'] - ) - .subscribe( - () => { - this.alert.okAlert('File saved!', ''); - this.route.data.subscribe((data) => { - switch (data['kind']) { - case 'wordlist-edit': - this.whichView = 'wordlist'; - break; - - case 'rules-edit': - this.whichView = 'rules'; - break; - - case 'other-edit': - this.whichView = 'other'; - break; - } - this.router.navigate(['../files/' + this.whichView + '']); - }); - }, - (errorMessage) => { - // check error status code is 500, if so, do some action - this.alert.okAlert( - 'File was not updated, please try again!', - '', - 'warning' - ); - this.ngOnInit(); - } - ); - this.updateForm.reset(); // success, we reset form - } - - private initForm() { - if (this.editMode) { - this.gs.get(SERV.FILES, this.editedFileIndex).subscribe((result) => { - this.updateForm = new FormGroup({ - fileId: new FormControl({ value: result['fileId'], disabled: true }), - updateData: new FormGroup({ - filename: new FormControl(result['filename'], Validators.required), - fileType: new FormControl(result['fileType'], Validators.required), - accessGroupId: new FormControl( - result['accessGroupId'], - Validators.required - ), - isSecret: new FormControl(result['isSecret']) - }) - }); - }); - } - } -} diff --git a/src/app/files/new-files/new-files.component.html b/src/app/files/new-files/new-files.component.html index 502ef29a..85f1c884 100644 --- a/src/app/files/new-files/new-files.component.html +++ b/src/app/files/new-files/new-files.component.html @@ -4,25 +4,18 @@
    -
  • -
  • +
  • +
-
+

Upload from your computer

-
- - -
+ + +
 
@@ -66,60 +59,22 @@

Upload from your computer

-

Download via URL

-
-
-
-
- - -
-
-
-
- - -
-
-
-   -
-
- -
- - - - -
-
-
+

Upload to the server using a public/private link

+ + + + + + + + + -
- Files are uploaded to the server using a public/private link (URL). -
+
- diff --git a/src/app/files/new-files/new-files.component.ts b/src/app/files/new-files/new-files.component.ts index c58f4922..264dc677 100644 --- a/src/app/files/new-files/new-files.component.ts +++ b/src/app/files/new-files/new-files.component.ts @@ -1,5 +1,18 @@ -import { faPlus, faUpload, faDownload, faLink, faFileUpload } from '@fortawesome/free-solid-svg-icons'; -import { Component, ElementRef, OnDestroy, OnInit, ViewChild } from '@angular/core'; +import { + faDownload, + faFileUpload, + faLink, + faPlus, + faUpload +} from '@fortawesome/free-solid-svg-icons'; +import { + ChangeDetectorRef, + Component, + ElementRef, + OnDestroy, + OnInit, + ViewChild +} from '@angular/core'; import { FormControl, FormGroup } from '@angular/forms'; import { Subject, Subscription, takeUntil } from 'rxjs'; // import { takeUntil } from 'rxjs/operators'; @@ -16,14 +29,18 @@ import { ActivatedRoute, Router } from '@angular/router'; import { UploadFileTUS } from '../../core/_models/file.model'; import { SERV } from '../../core/_services/main.config'; import { subscribe } from 'diagnostics_channel'; +import { AutoTitleService } from 'src/app/core/_services/shared/autotitle.service'; +import { UnsubscribeService } from 'src/app/core/_services/unsubscribe.service'; +import { transformSelectOptions } from 'src/app/shared/utils/forms'; @Component({ selector: 'app-new-files', templateUrl: './new-files.component.html', providers: [FileSizePipe] }) -// @PageTitle(['New File']) export class NewFilesComponent implements OnInit, OnDestroy { + /** Flag indicating whether data is still loading. */ + isLoading = true; faFileUpload = faFileUpload; faDownload = faDownload; @@ -31,80 +48,143 @@ export class NewFilesComponent implements OnInit, OnDestroy { faLink = faLink; faPlus = faPlus; + filterType: number; + whichView: string; + form: FormGroup; + submitted = false; + + // Lists of Selected inputs + selectAccessgroup: any[]; + private maxResults = environment.config.prodApiMaxResults; - subscriptions: Subscription[] = [] + subscriptions: Subscription[] = []; constructor( + private unsubscribeService: UnsubscribeService, + private changeDetectorRef: ChangeDetectorRef, private uploadService: UploadTUSService, + private titleService: AutoTitleService, private route: ActivatedRoute, private alert: AlertService, private gs: GlobalService, private fs: FileSizePipe, private router: Router ) { - + this.getLocation(); + this.buildForm(); + titleService.set([this.title]); } - accessgroup: any[] - filterType: number; - whichView: string; - createForm: FormGroup; - submitted = false; + // Get Title + public title: string; + public redirect: string; + getLocation() { + this.route.data.subscribe((data) => { + switch (data['kind']) { + case 'wordlist-new': + this.filterType = 0; + this.title = 'New Wordlist'; + this.redirect = 'wordlist'; + break; - ngOnInit(): void { + case 'rule-new': + this.filterType = 1; + this.title = 'New Rule'; + this.redirect = 'rules'; + break; - this.getLocation(); + case 'other-new': + this.filterType = 2; + this.title = 'New Other'; + this.redirect = 'other'; + break; + } + }); + } + /** + * Lifecycle hook called after component initialization. + */ + ngOnInit(): void { this.loadData(); - } - ngOnDestroy() { - this.subs.forEach((s) => s.unsubscribe()); - for (const sub of this.subscriptions) { - sub.unsubscribe() - } - this.ngUnsubscribe.next(false); - this.ngUnsubscribe.complete(); + /** + * Lifecycle hook called before the component is destroyed. + * Unsubscribes from all subscriptions to prevent memory leaks. + */ + ngOnDestroy(): void { + this.unsubscribeService.unsubscribeAll(); } - loadData() { - - const params = { 'maxResults': this.maxResults }; - - this.gs.getAll(SERV.ACCESS_GROUPS, params).subscribe((agroups: any) => { - this.accessgroup = agroups.values; - }); - - this.createForm = new FormGroup({ + /** + * Builds the form for creating a new Hashlist. + */ + buildForm(): void { + this.form = new FormGroup({ filename: new FormControl(''), isSecret: new FormControl(false), fileType: new FormControl(this.filterType), accessGroupId: new FormControl(1), sourceType: new FormControl('import' || ''), - sourceData: new FormControl(''), + sourceData: new FormControl('') }); + //subscribe to changes to handle select salted hashes + // this.form.get('hashTypeId').valueChanges.subscribe((newvalue) => { + // this.handleSelectedItems(newvalue); + // }); + } + + // ngOnDestroy() { + // this.subs.forEach((s) => s.unsubscribe()); + // for (const sub of this.subscriptions) { + // sub.unsubscribe(); + // } + // this.ngUnsubscribe.next(false); + // this.ngUnsubscribe.complete(); + // } + + loadData() { + const fieldAccess = { + fieldMapping: { + name: 'groupName', + _id: '_id' + } + }; + const accedgroupSubscription$ = this.gs + .getAll(SERV.ACCESS_GROUPS) + .subscribe((response: any) => { + const transformedOptions = transformSelectOptions( + response.values, + fieldAccess + ); + this.selectAccessgroup = transformedOptions; + this.isLoading = false; + this.changeDetectorRef.detectChanges(); + }); + this.unsubscribeService.add(accedgroupSubscription$); } /** * Create File * - */ + */ onSubmit(): void { - if (this.createForm.valid && this.submitted === false) { - - let form = this.onPrep(this.createForm.value, false); + if (this.form.valid && this.submitted === false) { + let form = this.onPrep(this.form.value, false); this.submitted = true; if (form.status === false) { - this.subscriptions.push(this.gs.create(SERV.FILES, form.update).subscribe(() => { - form = this.onPrep(this.createForm.value, true); - this.alert.okAlert('New File created!', ''); - this.submitted = false; - this.router.navigate(['/files', this.redirect]); - })); + this.subscriptions.push( + this.gs.create(SERV.FILES, form.update).subscribe(() => { + form = this.onPrep(this.form.value, true); + this.alert.okAlert('New File created!', ''); + this.submitted = false; + this.router.navigate(['/files', this.redirect]); + }) + ); } } } @@ -120,21 +200,22 @@ export class NewFilesComponent implements OnInit, OnDestroy { fname = this.fileName; } const res = { - "update": { - "filename": fname, - "isSecret": obj.isSecret, - "fileType": this.filterType, - "accessGroupId": obj.accessGroupId, - "sourceType": obj.sourceType, - "sourceData": sourcadata - }, "status": status - } + update: { + filename: fname, + isSecret: obj.isSecret, + fileType: this.filterType, + accessGroupId: obj.accessGroupId, + sourceType: obj.sourceType, + sourceData: sourcadata + }, + status: status + }; return res; } souceType(type: string, view: string) { this.viewMode = view; - this.createForm.patchValue({ + this.form.patchValue({ filename: '', accessGroupId: 1, sourceType: type, @@ -142,35 +223,6 @@ export class NewFilesComponent implements OnInit, OnDestroy { }); } - // Get Title - public title: string; - public redirect: string; - getLocation() { - this.route.data.subscribe(data => { - switch (data['kind']) { - - case 'wordlist-new': - this.filterType = 0; - this.title = 'New Wordlist'; - this.redirect = 'wordlist'; - break; - - case 'rule-new': - this.filterType = 1; - this.title = 'New Rule'; - this.redirect = 'rules'; - break; - - case 'other-new': - this.filterType = 2; - this.title = 'New Other'; - this.redirect = 'other'; - break; - - } - }) - } - // Uploading file @ViewChild('file', { static: false }) file: ElementRef; name = '!!!'; @@ -203,20 +255,21 @@ export class NewFilesComponent implements OnInit, OnDestroy { private ngUnsubscribe = new Subject(); onuploadFile(files: FileList) { - let form = this.onPrep(this.createForm.value, false); + const form = this.onPrep(this.form.value, false); const upload: Array = []; for (let i = 0; i < files.length; i++) { upload.push( - this.uploadService.uploadFile( - files[i], files[i].name, SERV.FILES, form.update, ['/files', this.redirect] - ).pipe(takeUntil(this.ngUnsubscribe)) - .subscribe( - (progress) => { - this.uploadProgress = progress; - // console.log(`Upload progress: ${progress}%`); - } - ) - ) + this.uploadService + .uploadFile(files[i], files[i].name, SERV.FILES, form.update, [ + '/files', + this.redirect + ]) + .pipe(takeUntil(this.ngUnsubscribe)) + .subscribe((progress) => { + this.uploadProgress = progress; + // console.log(`Upload progress: ${progress}%`); + }) + ); } // this.reset(); } @@ -224,5 +277,4 @@ export class NewFilesComponent implements OnInit, OnDestroy { reset() { this.file.nativeElement.value = null; } - } diff --git a/src/app/hashlists/hashlists.module.ts b/src/app/hashlists/hashlists.module.ts index 9c281525..b1b1eb34 100644 --- a/src/app/hashlists/hashlists.module.ts +++ b/src/app/hashlists/hashlists.module.ts @@ -30,9 +30,11 @@ import { MatDividerModule } from '@angular/material/divider'; import { MatTooltipModule } from '@angular/material/tooltip'; import { MatButtonModule } from '@angular/material/button'; import { MatCheckboxModule } from '@angular/material/checkbox'; +import { MatCardModule } from '@angular/material/card'; import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; - +import { MatDialogModule } from '@angular/material/dialog'; import { MatChipsModule } from '@angular/material/chips'; +import { MatProgressBarModule } from '@angular/material/progress-bar'; @NgModule({ declarations: [ @@ -54,12 +56,15 @@ import { MatChipsModule } from '@angular/material/chips'; MatSlideToggleModule, MatAutocompleteModule, MatOptionModule, + MatCardModule, MatButtonModule, MatCheckboxModule, MatDividerModule, MatIconModule, MatInputModule, + MatProgressBarModule, MatProgressSpinnerModule, + MatDialogModule, MatChipsModule, MatSelectModule, MatTooltipModule, diff --git a/src/app/hashlists/new-hashlist/new-hashlist.component.html b/src/app/hashlists/new-hashlist/new-hashlist.component.html index 2286f9cb..fce88d7f 100644 --- a/src/app/hashlists/new-hashlist/new-hashlist.component.html +++ b/src/app/hashlists/new-hashlist/new-hashlist.component.html @@ -1,221 +1,115 @@ - - - -
+ + + + + + - - - - - - - - - -
-
- - -
- - - - -
-
+ + + + + + + +
+ + + +
- -
-   - -
- - - - - - -
- -
- -
- -
-
-
-
- -
-
-

Upload completed!

-
-
- - - - - -
- - - - -   + + + +
+ + + + + + +
+ +
+ +
+ + attach_file + Choose File + + +
- +
+
+ +
+ + + +
+ +
+

Upload completed!

+

+ + + + + +
+ + +{{form.value | json}} - - - - diff --git a/src/app/hashlists/new-hashlist/new-hashlist.component.ts b/src/app/hashlists/new-hashlist/new-hashlist.component.ts index adc5be88..8314d4c2 100644 --- a/src/app/hashlists/new-hashlist/new-hashlist.component.ts +++ b/src/app/hashlists/new-hashlist/new-hashlist.component.ts @@ -1,7 +1,20 @@ -import { faMagnifyingGlass, faUpload, faInfoCircle, faFileUpload, faSearchPlus, faLink } from '@fortawesome/free-solid-svg-icons'; -import { Component, OnInit, ChangeDetectionStrategy, ChangeDetectorRef, HostListener, ViewChild, ElementRef } from '@angular/core'; +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + ElementRef, + HostListener, + OnDestroy, + OnInit, + ViewChild +} from '@angular/core'; import { ModalDismissReasons, NgbModal } from '@ng-bootstrap/ng-bootstrap'; -import { FormControl, FormGroup, Validators } from '@angular/forms'; +import { + FormBuilder, + FormControl, + FormGroup, + Validators +} from '@angular/forms'; import { Observable, Subject, Subscription, takeUntil } from 'rxjs'; import { environment } from './../../../environments/environment'; import Swal from 'sweetalert2/dist/sweetalert2.js'; @@ -14,160 +27,159 @@ import { AlertService } from 'src/app/core/_services/shared/alert.service'; import { GlobalService } from 'src/app/core/_services/main.service'; import { FileSizePipe } from 'src/app/core/_pipes/file-size.pipe'; import { PageTitle } from 'src/app/core/_decorators/autotitle'; -import { ShowHideTypeFile } from '../../shared/utils/forms'; +import { extractIds, transformSelectOptions } from '../../shared/utils/forms'; import { validateFileExt } from '../../shared/utils/util'; import { UploadFileTUS } from '../../core/_models/file.model'; import { SERV } from '../../core/_services/main.config'; +import { SelectField } from 'src/app/core/_models/input.model'; +import { AutoTitleService } from 'src/app/core/_services/shared/autotitle.service'; +import { UnsubscribeService } from 'src/app/core/_services/unsubscribe.service'; +import { HashtypeDetectorComponent } from 'src/app/shared/hashtype-detector/hashtype-detector.component'; +import { MatDialog } from '@angular/material/dialog'; +import { + hashSource, + hashcatbrainFormat, + hashlistFormat +} from 'src/app/core/_constants/hashlist.config'; @Component({ selector: 'app-new-hashlist', templateUrl: './new-hashlist.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, providers: [FileSizePipe] }) -@PageTitle(['New Hashlist']) -export class NewHashlistComponent implements OnInit { - /** - * Fa Icons - * - */ - faMagnifyingGlass = faMagnifyingGlass; - faFileUpload = faFileUpload; - faInfoCircle = faInfoCircle; - faSearchPlus = faSearchPlus; - faUpload = faUpload; - faLink = faLink; +export class NewHashlistComponent implements OnInit, OnDestroy { + /** Flag indicating whether data is still loading. */ + isLoadingAccessGroups = true; + isLoadingHashtypes = true; - /** - * Form Settings - * - */ - signupForm: FormGroup; - ShowHideTypeFile = ShowHideTypeFile; - radio = true; - brainenabled: any; - hashcatbrain: string; - subscriptions: Subscription[] = [] + /** Form group for the new SuperHashlist. */ + form: FormGroup; + + // Lists of Selected inputs + selectAccessgroup: any[]; + selectHashtypes: any[]; + selectFormat = hashlistFormat; + selectSource = hashSource; - // accessgroup: AccessGroup; //Use models when data structure is reliable - accessgroup: any[]; + // Lists of Hashtypes hashtypes: any[]; - private maxResults = environment.config.prodApiMaxResults; + + //Hashcat Brain Mode + brainenabled: any; + selectFormatbrain = hashcatbrainFormat; + hashcatbrain: string; constructor( + private unsubscribeService: UnsubscribeService, + private changeDetectorRef: ChangeDetectorRef, private uploadService: UploadTUSService, + private titleService: AutoTitleService, private uiService: UIConfigService, + private formBuilder: FormBuilder, private modalService: NgbModal, private alert: AlertService, private gs: GlobalService, + private dialog: MatDialog, private fs: FileSizePipe, - private router: Router, + private router: Router ) { + this.buildForm(); + titleService.set(['New Hashlist']); } + /** + * Lifecycle hook called after component initialization. + */ ngOnInit(): void { - this.loadData(); - } - ngOnDestroy() { - for (const sub of this.subscriptions) { - sub.unsubscribe() - } - this.ngUnsubscribe.next(false); - this.ngUnsubscribe.complete(); + /** + * Lifecycle hook called before the component is destroyed. + * Unsubscribes from all subscriptions to prevent memory leaks. + */ + ngOnDestroy(): void { + this.unsubscribeService.unsubscribeAll(); } - loadData() { - - this.brainenabled = this.uiService.getUIsettings('hashcatBrainEnable').value; - - const params = { 'maxResults': this.maxResults }; - - this.subscriptions.push(this.gs.getAll(SERV.ACCESS_GROUPS, params).subscribe((agroups: any) => { - this.accessgroup = agroups.values; - })); - - this.signupForm = new FormGroup({ - 'name': new FormControl('', [Validators.required]), - 'hashTypeId': new FormControl('', [Validators.required]), - 'format': new FormControl(null), - 'separator': new FormControl(null || ':'), - 'isSalted': new FormControl(false), - 'isHexSalt': new FormControl(false), - 'accessGroupId': new FormControl(null, [Validators.required]), - 'useBrain': new FormControl(+this.brainenabled === 1 ? true : false), - 'brainFeatures': new FormControl(null || 3), - 'notes': new FormControl(''), - "sourceType": new FormControl('import' || null), - "sourceData": new FormControl(''), - 'hashCount': new FormControl(0), - 'isArchived': new FormControl(false), - 'isSecret': new FormControl(true), + /** + * Builds the form for creating a new Hashlist. + */ + buildForm(): void { + this.brainenabled = + this.uiService.getUIsettings('hashcatBrainEnable').value; + + this.form = this.formBuilder.group({ + name: new FormControl('', [Validators.required]), + hashTypeId: new FormControl('', [Validators.required]), + format: new FormControl('' || 0), + separator: new FormControl(null || ':'), + isSalted: new FormControl(false), + isHexSalt: new FormControl(false), + accessGroupId: new FormControl(null, [Validators.required]), + useBrain: new FormControl(+this.brainenabled === 1 ? true : false), + brainFeatures: new FormControl(null || 3), + notes: new FormControl(''), + sourceType: new FormControl('import' || null), + sourceData: new FormControl(''), + hashCount: new FormControl(0), + isArchived: new FormControl(false), + isSecret: new FormControl(true) }); + //subscribe to changes to handle select salted hashes + this.form.get('hashTypeId').valueChanges.subscribe((newvalue) => { + this.handleSelectedItems(newvalue); + }); } - ngAfterViewInit() { - - const params = { 'maxResults': this.maxResults }; - - this.subscriptions.push(this.gs.getAll(SERV.HASHTYPES, params).subscribe((htypes: any) => { - const self = this; - this.hashtypes = htypes.values; - const prep = htypes.values; - const response = []; - for (let i = 0; i < prep.length; i++) { - const obj = { hashTypeId: prep[i].hashTypeId, descrId: prep[i].hashTypeId + ' ' + prep[i].description }; - response.push(obj) + /** + * Loads data, Access Groups and Hashtypes, for the component. + */ + loadData(): void { + const fieldAccess = { + fieldMapping: { + name: 'groupName', + _id: '_id' } - ($("#hashtype") as any).selectize({ - plugins: ['remove_button'], - valueField: "hashTypeId", - placeholder: "Search hashtype...", - labelField: "descrId", - searchField: ["descrId"], - loadingClass: 'Loading..', - highlight: true, - onChange: function (value) { - self.OnChangeValue(value); - }, - render: { - option: function (item, escape) { - return '
' + escape(item.descrId) + '
'; - }, - }, - onInitialize: function () { - const selectize = this; - selectize.addOption(response); - const selected_items = []; - $.each(response, function (i, obj) { - selected_items.push(obj.id); - }); - selectize.setValue(selected_items); - } + }; + const accedgroupSubscription$ = this.gs + .getAll(SERV.ACCESS_GROUPS) + .subscribe((response: any) => { + const transformedOptions = transformSelectOptions( + response.values, + fieldAccess + ); + this.selectAccessgroup = transformedOptions; + this.isLoadingAccessGroups = false; + this.changeDetectorRef.detectChanges(); }); - })); + this.unsubscribeService.add(accedgroupSubscription$); + const fieldHashtype = { + fieldMapping: { + name: 'description', + _id: '_id' + } + }; + const hashtypesSubscription$ = this.gs + .getAll(SERV.HASHTYPES) + .subscribe((response: any) => { + const transformedOptions = transformSelectOptions( + response.values, + fieldHashtype + ); + this.selectHashtypes = transformedOptions; + this.hashtypes = response.values; + this.isLoadingHashtypes = false; + this.changeDetectorRef.detectChanges(); + }); + this.unsubscribeService.add(hashtypesSubscription$); } - OnChangeValue(value) { - const id = Number(value); - // Map with hashtype and get if its salted or not - const filter = this.hashtypes.filter(u => u._id === id); - const salted = filter[0]['isSalted']; - if (id === 2500 || id === 16800 || id === 16801) { - this.signupForm.patchValue({ - hashTypeId: id, - format: Number(1), - isSalted: salted - }); - } else { - this.signupForm.patchValue({ - hashTypeId: id, - isSalted: salted - }); - } + get sourceType() { + return this.form.get('sourceType').value; } // FILE UPLOAD: TUS File Uload @@ -177,47 +189,28 @@ export class NewHashlistComponent implements OnInit { private ngUnsubscribe = new Subject(); onuploadFile(files: FileList) { - let form = this.handleUpload(this.signupForm.value); - const upload: Array = []; - for (let i = 0; i < files.length; i++) { - upload.push( - this.uploadService.uploadFile( - files[i], files[i].name, SERV.HASHLISTS, form, ['/hashlists/hashlist'] - ).pipe(takeUntil(this.ngUnsubscribe)) - .subscribe( - (progress) => { + if (this.form.valid) { + const newform = this.handlePathName(this.form.value); + const upload: Array = []; + for (let i = 0; i < files.length; i++) { + upload.push( + this.uploadService + .uploadFile(files[i], files[i].name, SERV.HASHLISTS, newform, [ + '/hashlists/hashlist' + ]) + .pipe(takeUntil(this.ngUnsubscribe)) + .subscribe((progress) => { this.uploadProgress = progress; - } - ) - ) + }) + ); + } } } - onuploadCancel(filename: string) { - // this.uploadService.cancelUpload(filename); - } - - /** - * Drop Zone Area - * - */ - fileList: any = []; - invalidFiles: any = []; - - onFilesChange(fileList: Array | DragEvent) { - this.fileList = fileList; - } - - onFileInvalids(fileList: Array | DragEvent) { - this.invalidFiles = fileList; - } - /** * Handle Input and return file size * @param event - */ - - validateFileExt = validateFileExt; + */ selectedFile: ''; fileGroup: number; @@ -229,92 +222,94 @@ export class NewHashlistComponent implements OnInit { this.fileToUpload = event.target.files[0]; this.fileSize = this.fileToUpload.size; this.fileName = this.fileToUpload.name; - $('.fileuploadspan').text(this.fs.transform(this.fileToUpload.size, false)); + $('.fileuploadspan').text( + ' ' + + this.fileName + + ' / Size: ' + + this.fs.transform(this.fileToUpload.size, false) + ); } /** * Create Hashlist * - */ + */ onSubmit(): void { - if (this.signupForm.valid) { - - const res = this.handleUpload(this.signupForm.value); - - this.subscriptions.push(this.gs.create(SERV.HASHLISTS, res).subscribe(() => { - this.alert.okAlert('New HashList created!', ''); - this.router.navigate(['/hashlists/hashlist']); - } - )); + if (this.form.valid) { + this.handleEncode(); + const onSubmitSubscription$ = this.gs + .create(SERV.HASHLISTS, this.form.value) + .subscribe(() => { + this.alert.okAlert('New HashList created!', ''); + this.router.navigate(['/hashlists/hashlist']); + }); + this.unsubscribeService.add(onSubmitSubscription$); } } - handleUpload(arr: any) { - const str = arr.sourceData; - const filereplace = str.replace("C:\\fakepath\\", ""); - let filename = filereplace; - if (arr.sourceType === 'paste') { - filename = Buffer.from(filereplace).toString('base64'); + handleEncode() { + const fileType = this.form.get('sourceType').value; + if (fileType === 'paste') { + const fileSource = this.form.get('sourceType').value; + this.form.patchValue({ + sourceData: Buffer.from(fileSource).toString('base64') + }); } + } + handlePathName(form: any) { + const filePath = this.form.get('sourceData').value; + const fileReplacePath = filePath.replace('C:\\fakepath\\', ''); const res = { - 'name': arr.name, - 'hashTypeId': arr.hashTypeId, - 'format': arr.format, - 'separator': arr.separator, - 'isSalted': arr.isSalted, - 'isHexSalt': arr.isHexSalt, - 'accessGroupId': arr.accessGroupId, - 'useBrain': arr.useBrain, - 'brainFeatures': arr.brainFeatures, - 'notes': arr.notes, - "sourceType": arr.sourceType, - "sourceData": filename, - 'hashCount': arr.hashCount, - 'isArchived': arr.isArchived, - 'isSecret': arr.isSecret, - } + name: form.name, + hashTypeId: form.hashTypeId, + format: form.format, + separator: form.separator, + isSalted: form.isSalted, + isHexSalt: form.isHexSalt, + accessGroupId: form.accessGroupId, + useBrain: form.useBrain, + brainFeatures: form.brainFeatures, + notes: form.notes, + sourceType: form.sourceType, + sourceData: fileReplacePath, + hashCount: form.hashCount, + isArchived: form.isArchived, + isSecret: form.isSecret + }; return res; } - // @HostListener allows us to also guard against browser refresh, close, etc. - @HostListener('window:beforeunload', ['$event']) - unloadNotification($event: any) { - if (!this.canDeactivate()) { - $event.returnValue = "IE and Edge Message"; - } - } - - canDeactivate(): Observable | boolean { - if (this.signupForm.valid) { - return false; - } - return true; - } - - // Open Modal - // Modal Information - closeResult = ''; - open(content) { - this.modalService.open(content, { size: 'xl' }).result.then( - (result) => { - this.closeResult = `Closed with: ${result}`; - }, - (reason) => { - this.closeResult = `Dismissed ${this.getDismissReason(reason)}`; - }, - ); + // Open Modal Hashtype Detector + openHelpDialog(): void { + const dialogRef = this.dialog.open(HashtypeDetectorComponent, { + width: '100%' + }); + dialogRef.afterClosed().subscribe((result) => { + console.log('Dialog closed with result:', result); + }); } - private getDismissReason(reason: any): string { - if (reason === ModalDismissReasons.ESC) { - return 'by pressing ESC'; - } else if (reason === ModalDismissReasons.BACKDROP_CLICK) { - return 'by clicking on a backdrop'; + /** + * Handles changes in the hashTypeId form control and adjusts form values accordingly. + * + * @param {any} hashTypeId - The new value of the hashTypeId form control. + * @returns {void} + */ + handleSelectedItems(hashTypeId: any): void { + const filter = this.hashtypes.filter((u) => u._id === hashTypeId); + const salted = filter.length > 0 ? filter[0]['isSalted'] : false; + + if (hashTypeId === 2500 || hashTypeId === 16800 || hashTypeId === 16801) { + this.form.patchValue({ + format: Number(1), + isSalted: salted + }); } else { - return `with: ${reason}`; + this.form.patchValue({ + isSalted: salted + }); } } - } diff --git a/src/app/shared/components.module.ts b/src/app/shared/components.module.ts index b870a6dc..c84938c4 100644 --- a/src/app/shared/components.module.ts +++ b/src/app/shared/components.module.ts @@ -19,7 +19,11 @@ import { HorizontalNavModule } from './navigation/navigation.module'; import { PageTitleModule } from './page-headers/page-title.module'; import { PaginationModule } from './pagination/pagination.module'; import { DynamicFormModule } from './dynamic-form-builder/dynamicform.module'; +import { MatTableModule } from '@angular/material/table'; +import { MatTooltipModule } from '@angular/material/tooltip'; import { MatDialogModule } from '@angular/material/dialog'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatInputModule } from '@angular/material/input'; import { MatButtonModule } from '@angular/material/button'; import { GridModule } from './grid-containers/grid.module'; import { TableModule } from './table/table-actions.module'; @@ -57,6 +61,10 @@ import { FormsModule } from '@angular/forms'; PageTitleModule, MatButtonModule, MatDialogModule, + MatTableModule, + MatTooltipModule, + MatFormFieldModule, + MatInputModule, ButtonsModule, LottiesModule, MatIconModule, diff --git a/src/app/shared/grid-containers/grid-main.ts b/src/app/shared/grid-containers/grid-main.ts index 7b52bf72..8450fc03 100644 --- a/src/app/shared/grid-containers/grid-main.ts +++ b/src/app/shared/grid-containers/grid-main.ts @@ -3,27 +3,28 @@ import { Component, Input, OnInit } from '@angular/core'; @Component({ selector: 'grid-main', template: ` - - - -`, -host: { - "(window:resize)":"onWindowResize($event)" -} + + + + + + `, + host: { + '(window:resize)': 'onWindowResize($event)' + } }) export class GridMainComponent implements OnInit { - @Input() class: any; @Input() centered?: boolean; isMobile = false; - width:number = window.innerWidth; - height:number = window.innerHeight; - mobileWidth = 760; + width: number = window.innerWidth; + height: number = window.innerHeight; + mobileWidth = 760; constructor() {} - ngOnInit() : void { + ngOnInit(): void { this.isMobile = this.width < this.mobileWidth; } @@ -32,5 +33,4 @@ export class GridMainComponent implements OnInit { this.height = event.target.innerHeight; this.isMobile = this.width < this.mobileWidth; } - } diff --git a/src/app/shared/hashtype-detector/hashtype-detector.component.html b/src/app/shared/hashtype-detector/hashtype-detector.component.html index 5ca06f69..c0cc0a80 100644 --- a/src/app/shared/hashtype-detector/hashtype-detector.component.html +++ b/src/app/shared/hashtype-detector/hashtype-detector.component.html @@ -1,36 +1,40 @@ - -
@@ -167,7 +167,7 @@ Rules - +
+ + +
@@ -230,7 +230,7 @@ Other - +
- - - -
-
- -
+ +
+ +
+ +
+
- - -
- - +
+
+ +
+ +
+ +
+ +
+ +
+ +
+
+
+
- - + +
- + Task Information @@ -156,21 +156,21 @@
+ type="text" + id="chunkSize" + class="form-control" + formControlName="chunkSize" + />
+ type="text" + id="forcePipe" + class="form-control" + formControlName="forcePipe" + />
@@ -178,21 +178,21 @@
+ type="text" + id="skipKeyspace" + class="form-control" + formControlName="skipKeyspace" + />
+ type="text" + id="keyspace" + class="form-control" + formControlName="keyspace" + />
@@ -200,94 +200,135 @@
+ type="text" + id="keyspaceProgress" + class="form-control" + formControlName="keyspaceProgress" + />
-
+
- + -
+
-
- Calculations -
+
Calculations
- -
- + + +
- -
- + + + - +
- -
+ + -
+
- - + +
- + @@ -299,22 +340,27 @@
-
diff --git a/src/app/tasks/import-supertasks/wrbulk/wrbulk.component.ts b/src/app/tasks/import-supertasks/wrbulk/wrbulk.component.ts index 333709cb..66fcb971 100644 --- a/src/app/tasks/import-supertasks/wrbulk/wrbulk.component.ts +++ b/src/app/tasks/import-supertasks/wrbulk/wrbulk.component.ts @@ -49,6 +49,7 @@ export class WrbulkComponent implements OnInit { this.dtOptions = { dom: 'Bfrtip', + scrollX: true, scrollY: "700px", scrollCollapse: true, paging: false, diff --git a/src/app/tasks/new-preconfigured-tasks/new-preconfigured-tasks.component.html b/src/app/tasks/new-preconfigured-tasks/new-preconfigured-tasks.component.html index 486a048c..dddff069 100644 --- a/src/app/tasks/new-preconfigured-tasks/new-preconfigured-tasks.component.html +++ b/src/app/tasks/new-preconfigured-tasks/new-preconfigured-tasks.component.html @@ -167,7 +167,7 @@

New Preconfigured Tasks (Copied From Task ID {{edited WordList - +
@@ -211,7 +211,7 @@

New Preconfigured Tasks (Copied From Task ID {{edited Rules -

File Name
+
@@ -255,7 +255,7 @@

New Preconfigured Tasks (Copied From Task ID {{edited Other -

File Name
+
diff --git a/src/app/tasks/new-preconfigured-tasks/new-preconfigured-tasks.component.ts b/src/app/tasks/new-preconfigured-tasks/new-preconfigured-tasks.component.ts index 127c6ca5..dfd3a3a4 100644 --- a/src/app/tasks/new-preconfigured-tasks/new-preconfigured-tasks.component.ts +++ b/src/app/tasks/new-preconfigured-tasks/new-preconfigured-tasks.component.ts @@ -197,6 +197,7 @@ export class NewPreconfiguredTasksComponent implements OnInit,AfterViewInit { this.dtOptions = { dom: 'Bfrtip', + scrollX: true, scrollY: "1000px", scrollCollapse: true, paging: false, diff --git a/src/app/tasks/new-tasks/new-tasks.component.html b/src/app/tasks/new-tasks/new-tasks.component.html index ebbb85a4..edbc9ff0 100644 --- a/src/app/tasks/new-tasks/new-tasks.component.html +++ b/src/app/tasks/new-tasks/new-tasks.component.html @@ -304,7 +304,7 @@ WordList -
File Name
+
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
- - - - - - - - - - - - - - Delete - - - - - - `, -}) -export class DynamicFormComponent implements OnInit, AfterViewInit, OnDestroy { - /** - * FontAwesome icon for providing additional information in form fields. - */ - faInfoCircle = faInfoCircle; - - /** - * The subtitle to display. - * @type {string} - */ - @Input() subtitle: string; - - /** - * An array of form field metadata that describes the form structure. - */ - @Input() formMetadata: any[] = []; - - /** - * Additional CSS class for labels. - */ - @Input() labelclass?: any; - - /** - * Initial values for form fields (optional). If not provided, an empty object is used as the default. - */ - @Input() formValues: any = {}; - - /** - * The Angular FormGroup that represents the dynamic form. - */ - @Input() form: FormGroup; - - /** - * Define the custom layout array. - */ - @Input() customLayout: any[]; - - /** - * Indicates whether the form is in "create" mode or "update" mode. - * When true, it's in "create" mode, and when false, it's in "update" mode. - */ - @Input() isCreateMode: boolean; - - /** - * A boolean input property that controls the visibility of the "Delete" button. - * Set it to `true` to display the "Delete" button, and `false` to hide it. - * By default, the "Delete" button is displayed (set to `true`). - */ - @Input() showDeleteButton: boolean = true; - - /** - * The text to display on the "Create" or "Update" button. - */ - @Input() buttonText: string; - - /** - * Event emitter for submitting the form. Emits the form values when the form is submitted. - * Parent components can subscribe to this event to handle form submissions. - */ - @Output() formSubmit: EventEmitter = new EventEmitter(); - - /** - * Event emitter for handling the delete action. Emits when the "Delete" action is triggered. - * Parent components can subscribe to this event to perform delete operations. - */ - @Output() deleteAction: EventEmitter = new EventEmitter(); - - /** - * A Subject used for managing the lifecycle and unsubscribing from observables when the component is destroyed. - * The `destroy$` subject is used to signal the component's destruction. - */ - private destroy$: Subject = new Subject(); - - /** - * Constructor for the DynamicFormComponent. - * @param fb - The Angular FormBuilder for creating form controls and groups. - * @param gs - The GlobalService for handling global operations and API requests. - * @param cd - The Angular ChangeDetectorRef for triggering change detection manually. - */ - constructor(private fb: FormBuilder, private gs: GlobalService, private cd: ChangeDetectorRef) {} - - /** - * Initializes the dynamic form by creating form controls and setting their initial values. - * This method is called when the dynamic form component is initialized. - */ - ngOnInit() { - // Check if this.customLayout is iterable and fallback to an empty array if it's not. - const customLayout = Array.isArray(this.customLayout) ? this.customLayout : []; - - // Initialize an object to store the configuration of form rows. - const formRows = []; - // Iterate through customLayout to define the structure of form rows and columns. - for (const row of customLayout) { - // Create a FormGroup for each row. - const formRow = new FormGroup({}); - - // Iterate through the fields in the formMetadata to create and configure form controls. - for (const field of this.formMetadata) { - // Exclude fields marked as titles from form control creation. - if (!field.isTitle) { - // Get the name of the field. - const fieldName = field.name; - - // Determine the validators for the field, defaulting to an empty array if none are provided. - const validators: ValidatorFn[] = field.validators ? field.validators : []; - - // Initialize the initial value for the form control. - let initialValue; - - // Set the initial value for the form control based on the field's type. - if (field.type === 'checkbox') { - // For checkboxes, use the value directly from formValues. - initialValue = this.formValues[fieldName]; - } else { - // For other field types, use formValues[fieldName] or 0 as a default value if not provided. - initialValue = fieldName in this.formValues ? this.formValues[fieldName] : 0; - } - - // In 'create' mode, override the initial value if a default value is specified in the field's metadata. - if (this.isCreateMode && field.defaultValue !== undefined) { - initialValue = field.defaultValue; - } - - // Create a form control with the initial value and any specified validators. - if (!this.isCreateMode && field.disabled) { - // If in 'update' mode and the field is disabled, create a disabled form control. - formRows[fieldName] = { value: initialValue, disabled: true }; - } else { - // Create a form control with the initial value and optional validators. - formRows[fieldName] = new FormControl(initialValue, validators); - } - - // Create a form control with the initial value and any specified validators. - const formControl = new FormControl(formRows); - - // Add the form control to the formRow with fieldName as the key. - formRow.addControl(fieldName, formControl); - } - // Add the formRow (FormGroup for the current row) to the list of rows. - formRows.push(formRow); - } - } - // Create the Angular FormGroup with the configured controls. - this.form = this.fb.group(formRows); - } - - /** - * A subscription to handle dynamic select options data retrieval. - * This subscription is used to fetch and update select field options with dynamic data. - */ - private selectOptionsSubscription: Subscription; - - /** - * Indicates whether the dynamic select options are currently being loaded. - * When true, it represents that options are being fetched; when false, loading is complete. - */ - isLoadingSelect: boolean = true; - - /** - * Angular lifecycle hook: ngAfterViewInit - * Performs initialization and logic for select fields with dynamic options. - */ - ngAfterViewInit() { - // Check if there are any "select" type fields with "selectOptions$" - const selectFields = this.formMetadata.filter( - (field) => field.type === 'selectd' && field.selectOptions$ - ); - - if (selectFields.length > 0) { - // Handle logic for select fields with selectOptions$ after the view is initialized - selectFields.forEach((field) => { - // Fetch the select options dynamically here - this.selectOptionsSubscription = this.gs.getAll(field.selectEndpoint$,{'maxResults': 5000}) - .pipe(takeUntil(this.destroy$)) - .subscribe((options) => { - - // Sometimes fields need to be mapped - const transformedOptions = this.transformSelectOptions(options.values, field); - - // Assign the fetched options to the field's selectOptions$ - field.selectOptions$ = transformedOptions; - - // Update isLoadingSelect to indicate that loading is complete - this.isLoadingSelect = false; - - // Optionally, update the form control value if needed - const control = this.form.get(field.name); - - // Check if there are options available - if (control && options.values && options.values.length > 0 && !this.isCreateMode) { - // Ensure that options.values[0] and options.values[0].value exist before setting the value - const initialSelectedValue = options.values[0]?.value; - - if (initialSelectedValue !== undefined) { - control.setValue(initialSelectedValue); - } - } - - // Trigger change detection to prevent ExpressionChangedAfterItHasBeenCheckedError - this.cd.detectChanges(); - }); - }); - } - } - - /** - * Transforms API response options based on a field mapping configuration. - * - * @param apiOptions - The options received from an API response. - * @param field - The field configuration that contains the mapping between form fields and API fields. - * - * @returns An array of transformed select options to be used in the form. - */ - transformSelectOptions(apiOptions: any[], field: any): any[] { - return apiOptions.map((apiOption: any) => { - const transformedOption: any = {}; - - for (const formField of Object.keys(field.fieldMapping)) { - const apiField = field.fieldMapping[formField]; - - if (Object.prototype.hasOwnProperty.call(apiOption, apiField)) { - transformedOption[formField] = apiOption[apiField]; - } else { - // Handle the case where the API field doesn't exist in the response - transformedOption[formField] = null; // or set a default value - } - } - - return transformedOption; - }); - } - - /** - * Checks if the form is valid. - * @returns {boolean} True if the form is valid, false otherwise. - */ - formIsValid(): boolean { - return this.form.valid; - } - - /** - * Handles the form submission. - * Emits the form values to the parent component if the form is valid. - */ - onSubmit() { - if (this.form.valid) { - // Emit the form values to the parent component - this.formSubmit.emit(this.form.value); - } - } - - /** - * Handles the delete action. - * Emits the delete action to the parent component when the "Delete" button is clicked. - */ - onDelete(){ - this.deleteAction.emit(); - } - - /** - * Angular lifecycle hook: ngOnDestroy - * Unsubscribes from all relevant subscriptions and cleans up resources - */ - ngOnDestroy() { - // Unsubscribe from the selectOptionsSubscription - // this.selectOptionsSubscription.unsubscribe(); - - // Complete and close the destroy$ subject to prevent memory leaks - this.destroy$.next(); - this.destroy$.complete(); - } - -} diff --git a/src/app/shared/form/form.component.html b/src/app/shared/form/form.component.html new file mode 100644 index 00000000..001ce5e6 --- /dev/null +++ b/src/app/shared/form/form.component.html @@ -0,0 +1,12 @@ + + diff --git a/src/app/shared/form/form.component.ts b/src/app/shared/form/form.component.ts index 25f9e173..a9be65bb 100644 --- a/src/app/shared/form/form.component.ts +++ b/src/app/shared/form/form.component.ts @@ -11,15 +11,12 @@ import { Subscription } from 'rxjs'; @Component({ selector: 'app-form', - template: ` - - `, + templateUrl: 'form.component.html' }) /** * Component for managing forms, supporting both create and edit modes. */ export class FormComponent implements OnInit, OnDestroy { - // Metadata Text, titles, subtitles, forms, and API path globalMetadata: any[] = []; apiPath: string; @@ -86,7 +83,10 @@ export class FormComponent implements OnInit, OnDestroy { formValues: any[] = []; // Subscription for managing asynchronous data retrieval - private mySubscription: Subscription; + private subscriptionService: Subscription; + + // Subscription for managing router params + private routeParamsSubscription: Subscription; /** * Constructor for the FormComponent. @@ -97,7 +97,7 @@ export class FormComponent implements OnInit, OnDestroy { * @param alert - The AlertService for displaying alerts. * @param gs - The GlobalService for handling global operations. * @param router - The Angular Router for navigation. - */ + */ constructor( private unsubscribeService: UnsubscribeService, private metadataService: MetadataService, @@ -108,36 +108,42 @@ export class FormComponent implements OnInit, OnDestroy { private router: Router ) { // Subscribe to route data to initialize component data - this.route.data.subscribe((data: { kind: string, path: string, type: string }) => { - const formKind = data.kind; - this.apiPath = data.path; // Get the API path from route data - this.type = data.type; - this.isCreate = (this.type === 'create'? true:false); - // Load metadata and form information - this.globalMetadata = this.metadataService.getInfoMetadata(formKind+'Info')[0]; - this.formMetadata = this.metadataService.getFormMetadata(formKind); - this.title = this.globalMetadata['title']; - this.customform = this.globalMetadata['customform']; - titleService.set([this.title]); - // Load metadata and form information - if(this.type === 'edit'){ - this.getIndex(); - this.loadEdit(); // Load data for editing - }else{ - this.isloaded = true + this.routeParamsSubscription = this.route.data.subscribe( + (data: { kind: string; path: string; type: string }) => { + const formKind = data.kind; + this.apiPath = data.path; // Get the API path from route data + this.type = data.type; + this.isCreate = this.type === 'create' ? true : false; + // Load metadata and form information + this.globalMetadata = this.metadataService.getInfoMetadata( + formKind + 'Info' + )[0]; + this.formMetadata = this.metadataService.getFormMetadata(formKind); + this.title = this.globalMetadata['title']; + this.customform = this.globalMetadata['customform']; + titleService.set([this.title]); + // Load metadata and form information + if (this.type === 'edit') { + this.getIndex(); + this.loadEdit(); // Load data for editing + } else { + this.isloaded = true; + } } - }); + ); // Add this.mySubscription to UnsubscribeService - this.unsubscribeService.add(this.mySubscription); + this.unsubscribeService.add(this.subscriptionService); } /** * Loads data for editing a form. - */ + */ getIndex() { - this.route.params.subscribe((params: Params) => { - this.editedIndex = +params['id']; - }); + this.routeParamsSubscription = this.route.params.subscribe( + (params: Params) => { + this.editedIndex = +params['id']; + } + ); } /** @@ -149,10 +155,15 @@ export class FormComponent implements OnInit, OnDestroy { }); // Fetch data from the API for editing - this.gs.get(this.apiPath, this.editedIndex).subscribe((result) => { - this.formValues = result; - this.isloaded = true; // Data is loaded and ready for form rendering - }); + + const editSubscription = this.gs + .get(this.apiPath, this.editedIndex) + .subscribe((result) => { + this.formValues = result; + this.isloaded = true; // Data is loaded and ready for form rendering + }); + + this.unsubscribeService.add(editSubscription); } /** @@ -170,30 +181,43 @@ export class FormComponent implements OnInit, OnDestroy { * Unsubscribe from all subscriptions to prevent memory leaks. */ ngOnDestroy(): void { + // Unsubscribe from the route params subscription + if (this.routeParamsSubscription) { + this.routeParamsSubscription.unsubscribe(); + this.routeParamsSubscription = null; + } + + // Unsubscribe from other subscriptions this.unsubscribeService.unsubscribeAll(); } /** * Handles the submission of the form. * @param formValues - The values submitted from the form. - */ + */ onFormSubmit(formValues: any) { - if(this.customform){ - this.onCustomForm(formValues); + if (this.customform) { + this.modifyFormValues(formValues); } if (this.type === 'create') { // Create mode: Submit form data for creating a new item - console.log(formValues) - this.mySubscription = this.gs.create(this.apiPath, formValues).subscribe(() => { - this.alert.okAlert(this.globalMetadata['submitok'], ''); - this.router.navigate([this.globalMetadata['submitokredirect']]); - }); + const createSubscription = this.gs + .create(this.apiPath, formValues) + .subscribe(() => { + this.alert.okAlert(this.globalMetadata['submitok'], ''); // Display success alert first + this.router.navigate([this.globalMetadata['submitokredirect']]); // Navigate after alert + }); + + this.unsubscribeService.add(createSubscription); } else { // Update mode: Submit form data for updating an existing item - this.mySubscription = this.gs.update(this.apiPath, this.editedIndex, formValues).subscribe(() => { - this.alert.okAlert(this.globalMetadata['submitok'], ''); - this.router.navigate([this.globalMetadata['submitokredirect']]); - }); + const updateSubscription = this.gs + .update(this.apiPath, this.editedIndex, formValues) + .subscribe(() => { + this.alert.okAlert(this.globalMetadata['submitok'], ''); + this.router.navigate([this.globalMetadata['submitokredirect']]); + }); + this.unsubscribeService.add(updateSubscription); } } @@ -202,7 +226,7 @@ export class FormComponent implements OnInit, OnDestroy { * @param formValues - The form values to be modified. * @returns The modified form values. */ - onCustomForm(formValues: any) { + modifyFormValues(formValues: any) { // Check the formMetadata for fields with 'replacevalue' property this.getIndex(); for (const field of this.formMetadata) { @@ -226,19 +250,26 @@ export class FormComponent implements OnInit, OnDestroy { if (this.globalMetadata['deltitle']) { this.getIndex(); } - this.alert.deleteConfirmation('', this.globalMetadata['deltitle']).then((confirmed) => { - if (confirmed) { - // Deletion - this.gs.delete(this.apiPath, this.editedIndex).subscribe(() => { - // Successful deletion - this.alert.okAlert(this.globalMetadata['delsubmitok'], ''); - this.router.navigate([this.globalMetadata['delsubmitokredirect']]); - }); - } else { - // Handle cancellation - this.alert.okAlert(this.globalMetadata['delsubmitcancel'], ''); - } - }); - } + this.alert + .deleteConfirmation('', this.globalMetadata['deltitle']) + .then((confirmed) => { + if (confirmed) { + // Deletion + const deleteSubscription = this.gs + .delete(this.apiPath, this.editedIndex) + .subscribe(() => { + // Successful deletion + this.alert.okAlert(this.globalMetadata['delsubmitok'], ''); + this.router.navigate([ + this.globalMetadata['delsubmitokredirect'] + ]); + }); + this.unsubscribeService.add(deleteSubscription); + } else { + // Handle cancellation + this.alert.okAlert(this.globalMetadata['delsubmitcancel'], ''); + } + }); + } } diff --git a/src/app/shared/form/formuisettings.component.ts b/src/app/shared/form/formuisettings.component.ts deleted file mode 100644 index a9d37b92..00000000 --- a/src/app/shared/form/formuisettings.component.ts +++ /dev/null @@ -1,139 +0,0 @@ -import { FormControl, FormGroup, Validators } from '@angular/forms'; -import { ActivatedRoute, Params, Router } from '@angular/router'; -import { Component, OnDestroy, OnInit } from '@angular/core'; - -import { AutoTitleService } from 'src/app/core/_services/shared/autotitle.service'; -import { UnsubscribeService } from 'src/app/core/_services/unsubscribe.service'; -import { AlertService } from 'src/app/core/_services/shared/alert.service'; -import { MetadataService } from 'src/app/core/_services/metadata.service'; -import { GlobalService } from 'src/app/core/_services/main.service'; -import { Subscription } from 'rxjs'; -import { CookieService } from 'src/app/core/_services/shared/cookies.service'; - -@Component({ - selector: 'app-form', - template: ` - - `, -}) -/** - * Component for managing forms, supporting both create and edit modes. - */ -export class FormUIsettingsComponent implements OnDestroy { - - // Metadata Text, titles, subtitles, forms, and API path - globalMetadata: any[] = []; - apiPath: string; - - /** - * Title to be displayed in the form. - * @type {string} - */ - title: string; - - /** - * The Angular FormGroup representing the dynamic form. - * This FormGroup contains form controls for all fields in the formMetadata. - */ - form: FormGroup; - - /** - * An array of form field metadata that describes the form structure. - * Each item in the array represents a form field, including its type, label, and other properties. - * @type {any[]} - */ - formMetadata: any[] = []; - - /** - * Initial values for form fields (optional). - * If provided, these values are used to initialize form controls in the dynamic form. - * @type {any[]} - */ - formValues: any[] = []; - - // Subscription for managing asynchronous data retrieval - private mySubscription: Subscription; - - /** - * Constructor for the FormComponent. - * @param unsubscribeService - The UnsubscribeService for managing subscriptions. - * @param metadataService - The MetadataService for accessing form metadata. - * @param titleService - The AutoTitleService for setting titles. - * @param route - The ActivatedRoute for retrieving route data. - * @param alert - The AlertService for displaying alerts. - * @param gs - The GlobalService for handling global operations. - * @param router - The Angular Router for navigation. - */ - constructor( - private unsubscribeService: UnsubscribeService, - private metadataService: MetadataService, - private titleService: AutoTitleService, - private cookieService: CookieService, - private route: ActivatedRoute, - private alert: AlertService, - private gs: GlobalService, - private router: Router - ) { - // Subscribe to route data to initialize component data - this.route.data.subscribe((data: { kind: string, path: string, type: string }) => { - const formKind = data.kind; - this.apiPath = data.path; // Get the API path from route data - // Load metadata and form information - this.globalMetadata = this.metadataService.getInfoMetadata(formKind+'Info')[0]; - this.formMetadata = this.metadataService.getFormMetadata(formKind); - // this.formValues = - this.title = this.globalMetadata['title']; - titleService.set([this.title]); - }); - // Add this.mySubscription to UnsubscribeService - this.unsubscribeService.add(this.mySubscription); - } - - /** - * Angular lifecycle hook: ngOnInit - */ - ngOnInit(): void { - this.loadEdit(); - } - - /** - * Loads data for editing a form. - * This function fetches data from the API for editing and prepares it for rendering in a form. - */ - loadEdit() { - // Fetch data from the API for editing - // Use utils - } - - /** - * Angular lifecycle hook: ngOnDestroy - * Unsubscribe from all subscriptions to prevent memory leaks. - */ - ngOnDestroy(): void { - this.unsubscribeService.unsubscribeAll(); - } - - /** - * Handles the submission of the form. - * @param formValues - The values submitted from the form. - */ - onFormSubmit(formValues: any) { - // Iterate through form values - for (const key in formValues) { - if (Object.prototype.hasOwnProperty.call(formValues, key)) { - const value = formValues[key]; - // Check if the key is 'autorefresh' - if (key === 'autorefresh') { - this.cookieService.setCookie('autorefresh', JSON.stringify({ active: true, value }), 365); - } else { - // Set other form values as individual cookies - this.cookieService.setCookie(key, value, 365); - } - } - } - // Show an alert or notification - this.alert.okAlert('Saved!', ''); - } - -} - diff --git a/src/app/shared/grid-containers/grid-autocol.ts b/src/app/shared/grid-containers/grid-autocol.ts index cdb2b604..8ad375e8 100644 --- a/src/app/shared/grid-containers/grid-autocol.ts +++ b/src/app/shared/grid-containers/grid-autocol.ts @@ -3,9 +3,7 @@ import { Component, ElementRef, Input, OnInit, Renderer2 } from '@angular/core'; @Component({ selector: 'grid-autocol', template: ` -
- -
+
`, host: { '(window:resize)': 'onWindowResize($event)' @@ -37,37 +35,65 @@ export class GridAutoColComponent implements OnInit { cardWidth = 300; // Width of each card (adjust as needed) cardHeight = 80; // Height of each card (adjust as needed) + /** + * Constructor for the component. + * @param {Renderer2} renderer - The Renderer2 for manipulating the DOM. + * @param {ElementRef} el - The ElementRef for accessing the host element. + */ constructor( private renderer: Renderer2, private el: ElementRef ) {} + /** + * Initializes the component properties. + */ ngOnInit(): void { this.isMobile = this.width < this.mobileWidth; } - // Method to handle window resize events + /** + * Handles window resize events to update component properties based on the new dimensions. + * @param {Event} event - The window resize event. + */ onWindowResize(event) { + /** + * Update the component properties with the new dimensions. + * @type {number} width - The updated width of the window. + * @type {number} height - The updated height of the window. + * @type {boolean} isMobile - A boolean indicating whether the window is considered a mobile view. + */ this.width = event.target.innerWidth; this.height = event.target.innerHeight; this.isMobile = this.width < this.mobileWidth; } - // Calculate the number of columns based on available space - getCols() { + /** + * Calculates and returns the number of columns based on the available space. + * @returns {number} - The calculated number of columns. + */ + getCols(): number { return Math.floor( (this.width + this.gutterSize) / (this.cardWidth + this.gutterSize) ); } - // Calculate the number of rows based on available space - getRows() { + /** + * Calculates and returns the number of rows based on the available space. + * @returns {number} - The calculated number of rows. + */ + getRows(): number { return Math.floor( (this.height + this.gutterSize) / (this.cardHeight + this.gutterSize) ); } - // Calculate and return the CSS styles for the grid layout + /** + * Calculates and returns the CSS styles for the grid layout based on specified conditions. + * If the item count is less than 6 or the screen size is below a certain threshold, + * an empty object is returned to disable the component. + * @returns {Object} - The calculated CSS styles for the grid layout. + */ public getStyles() { // If the item count is less than 6, return an empty object to disable the component if (this.itemCount < 6) { @@ -121,11 +147,22 @@ export class GridAutoColComponent implements OnInit { return styles; } - // Remove previously added vertical lines - private removeVerticalLines() { + /** + * Removes previously added vertical lines from the component. + */ + private removeVerticalLines(): void { + /** + * Selects and retrieves all existing vertical lines with the class 'vertical-line'. + * @type {NodeList} existingLines - The list of existing vertical lines. + */ const existingLines = this.el.nativeElement.querySelectorAll('.vertical-line'); - existingLines.forEach((line) => { + + /** + * Iterates through the existing lines and removes each one from the component. + * @param {Element} line - The current vertical line element. + */ + existingLines.forEach((line: Element) => { this.renderer.removeChild(this.el.nativeElement, line); }); } diff --git a/src/app/shared/navigation/horizontalnav.component.html b/src/app/shared/navigation/horizontalnav.component.html new file mode 100644 index 00000000..ff6f8470 --- /dev/null +++ b/src/app/shared/navigation/horizontalnav.component.html @@ -0,0 +1,33 @@ + +
+
+
+
+ +
+
+ + + + {{ item.label }} + + + +
+
+
+
+ + + +
diff --git a/src/app/shared/navigation/horizontalnav.component.ts b/src/app/shared/navigation/horizontalnav.component.ts index b073cc69..34cf82bb 100644 --- a/src/app/shared/navigation/horizontalnav.component.ts +++ b/src/app/shared/navigation/horizontalnav.component.ts @@ -6,41 +6,7 @@ import { Subject } from 'rxjs'; @Component({ selector: 'horizontalnav', - template: ` - -
-
-
-
- -
-
- - - - {{ item.label }} - - - -
-
-
-
- - - -
- ` + templateUrl: 'horizontalnav.component.html' }) export class HorizontalNavComponent implements OnDestroy { @Input() menuItems: HorizontalNav[] = []; diff --git a/src/app/tasks/tasks-routing.module.ts b/src/app/tasks/tasks-routing.module.ts index a185b88d..577e49e3 100644 --- a/src/app/tasks/tasks-routing.module.ts +++ b/src/app/tasks/tasks-routing.module.ts @@ -1,213 +1,257 @@ -import { CheckPerm } from "../core/_guards/permission.guard"; -import { Routes, RouterModule } from '@angular/router'; -import { IsAuth } from "../core/_guards/auth.guard"; -import { NgModule } from "@angular/core"; +import { CheckPerm } from '../core/_guards/permission.guard'; +import { RouterModule, Routes } from '@angular/router'; +import { IsAuth } from '../core/_guards/auth.guard'; +import { NgModule } from '@angular/core'; -import { EditPreconfiguredTasksComponent } from "./edit-preconfigured-tasks/edit-preconfigured-tasks.component"; -import { NewPreconfiguredTasksComponent } from "./new-preconfigured-tasks/new-preconfigured-tasks.component"; -import { PreconfiguredTasksComponent } from "./preconfigured-tasks/preconfigured-tasks.component"; -import { EditSupertasksComponent } from "./edit-supertasks/edit-supertasks.component"; -import { NewSupertasksComponent } from "./new-supertasks/new-supertasks.component"; -import { ApplyHashlistComponent } from "./supertasks/applyhashlist.component"; -import { WrbulkComponent } from "./import-supertasks/wrbulk/wrbulk.component"; -import { PendingChangesGuard } from "../core/_guards/pendingchanges.guard"; -import { MasksComponent } from "./import-supertasks/masks/masks.component"; -import { SupertasksComponent } from "./supertasks/supertasks.component"; -import { ShowTasksComponent } from "./show-tasks/show-tasks.component"; -import { EditTasksComponent } from "./edit-tasks/edit-tasks.component"; -import { NewTasksComponent } from "./new-tasks/new-tasks.component"; -import { ChunksComponent } from "./chunks/chunks.component"; +import { EditPreconfiguredTasksComponent } from './edit-preconfigured-tasks/edit-preconfigured-tasks.component'; +import { NewPreconfiguredTasksComponent } from './new-preconfigured-tasks/new-preconfigured-tasks.component'; +import { PreconfiguredTasksComponent } from './preconfigured-tasks/preconfigured-tasks.component'; +import { EditSupertasksComponent } from './edit-supertasks/edit-supertasks.component'; +import { NewSupertasksComponent } from './new-supertasks/new-supertasks.component'; +import { ApplyHashlistComponent } from './supertasks/applyhashlist.component'; +import { WrbulkComponent } from './import-supertasks/wrbulk/wrbulk.component'; +import { PendingChangesGuard } from '../core/_guards/pendingchanges.guard'; +import { MasksComponent } from './import-supertasks/masks/masks.component'; +import { SupertasksComponent } from './supertasks/supertasks.component'; +import { ShowTasksComponent } from './show-tasks/show-tasks.component'; +import { EditTasksComponent } from './edit-tasks/edit-tasks.component'; +import { NewTasksComponent } from './new-tasks/new-tasks.component'; +import { MyRoute, RouteData } from '../core/_models/routes.model'; +import { ChunksComponent } from './chunks/chunks.component'; -const routes: Routes = [ +const routes: MyRoute[] = [ { path: '', + canActivate: [IsAuth], children: [ - { - path: 'show-tasks', component: ShowTasksComponent, - data: { - kind: 'show-tasks', - breadcrumb: 'Show tasks', - permission: 'Task' - }, - canActivate: [IsAuth,CheckPerm]}, - { - path: 'show-tasks-archived', component: ShowTasksComponent, - data: { - kind: 'show-tasks-archived', - breadcrumb: 'Show Archived Tasks', - permission: 'Task' - }, - canActivate: [IsAuth,CheckPerm]}, - { - path: 'show-tasks/:id/edit', component: EditTasksComponent, - data: { - kind: 'edit-task', - breadcrumb: 'Edit Task', - permission: 'Task' - }, - canActivate: [IsAuth,CheckPerm]}, - { - path: 'show-tasks/:id/edit/show-100-chunks', component: EditTasksComponent, - data: { - kind: 'edit-task-c100', - breadcrumb: 'Edit Task > Show latest 100 chunks', - permission: 'Task' - }, - canActivate: [IsAuth,CheckPerm]}, - { - path: 'show-tasks/:id/edit/show-all-chunks', component: EditTasksComponent, - data: { - kind: 'edit-task-cAll', - breadcrumb: 'Edit Task > Show All chunks', - permission: 'Task' - }, - canActivate: [IsAuth,CheckPerm]}, - { - path: 'new-task', component: NewTasksComponent, - data: { - kind: 'new-task', - breadcrumb: 'New task', - permission: 'Task' - }, - canActivate: [IsAuth,CheckPerm], - canDeactivate: [PendingChangesGuard]}, - { - path: 'new-tasks/:id/copy', component: NewTasksComponent, - data: { - kind: 'copy-task', - breadcrumb: 'Copy Task', - permission: 'Task' - }, - canActivate: [IsAuth,CheckPerm]}, - { - path: 'new-tasks/:id/copypretask', component: NewTasksComponent, - data: { - kind: 'copy-pretask', - breadcrumb: 'Copy Task', - permission: 'Task' - }, - canActivate: [IsAuth,CheckPerm]}, - { - path: 'preconfigured-tasks', component: PreconfiguredTasksComponent, - data: { - kind: 'preconfigured-tasks', - breadcrumb: 'Preconfigured tasks', - permission: 'Pretask' - }, - canActivate: [IsAuth,CheckPerm]}, - { - path: 'new-preconfigured-tasks', component: NewPreconfiguredTasksComponent, - data: { - kind: 'new-preconfigured-tasks', - breadcrumb: 'New Preconfigured tasks', - permission: 'Pretask' - }, - canActivate: [IsAuth,CheckPerm]}, - { - path: 'preconfigured-tasks/:id/edit', component: EditPreconfiguredTasksComponent, - data: { - kind: 'edit-preconfigured-tasks', - breadcrumb: 'Edit Preconfigured tasks', - permission: 'Pretask' - }, - canActivate: [IsAuth,CheckPerm], - canDeactivate: [PendingChangesGuard]}, - { - path: 'preconfigured-tasks/:id/copy', component: NewPreconfiguredTasksComponent, - data: { - kind: 'copy-preconfigured-tasks', - breadcrumb: 'Copy Preconfigured tasks', - permission: 'Pretask' - }, - canActivate: [IsAuth,CheckPerm]}, - { - path: 'preconfigured-tasks/:id/copytask', component: NewPreconfiguredTasksComponent, - data: { - kind: 'copy-tasks', - breadcrumb: 'Copy Task to Preconfigured task', - permission: 'Pretask' - }, - canActivate: [IsAuth,CheckPerm]}, - { - path: 'supertasks', component: SupertasksComponent, - data: { - kind: 'supertasks', - breadcrumb: 'Supertasks', - permission: 'SuperTask' - }, - canActivate: [IsAuth,CheckPerm]}, - { - path: ':id/applyhashlist', component: ApplyHashlistComponent, + { + path: 'show-tasks', + component: ShowTasksComponent, + data: { + kind: 'show-tasks', + breadcrumb: 'Show tasks', + permission: 'Task' + }, + canActivate: [CheckPerm] + }, + { + path: 'show-tasks-archived', + component: ShowTasksComponent, + data: { + kind: 'show-tasks-archived', + breadcrumb: 'Show Archived Tasks', + permission: 'Task' + }, + canActivate: [CheckPerm] + }, + { + path: 'show-tasks/:id/edit', + component: EditTasksComponent, + data: { + kind: 'edit-task', + breadcrumb: 'Edit Task', + permission: 'Task' + }, + canActivate: [CheckPerm] + }, + { + path: 'show-tasks/:id/edit/show-100-chunks', + component: EditTasksComponent, + data: { + kind: 'edit-task-c100', + breadcrumb: 'Edit Task > Show latest 100 chunks', + permission: 'Task' + }, + canActivate: [CheckPerm] + }, + { + path: 'show-tasks/:id/edit/show-all-chunks', + component: EditTasksComponent, + data: { + kind: 'edit-task-cAll', + breadcrumb: 'Edit Task > Show All chunks', + permission: 'Task' + }, + canActivate: [CheckPerm] + }, + { + path: 'new-task', + component: NewTasksComponent, + data: { + kind: 'new-task', + breadcrumb: 'New task', + permission: 'Task' + }, + canActivate: [CheckPerm], + canDeactivate: [PendingChangesGuard] + }, + { + path: 'new-tasks/:id/copy', + component: NewTasksComponent, + data: { + kind: 'copy-task', + breadcrumb: 'Copy Task', + permission: 'Task' + }, + canActivate: [CheckPerm] + }, + { + path: 'new-tasks/:id/copypretask', + component: NewTasksComponent, + data: { + kind: 'copy-pretask', + breadcrumb: 'Copy Task', + permission: 'Task' + }, + canActivate: [CheckPerm] + }, + { + path: 'preconfigured-tasks', + component: PreconfiguredTasksComponent, + data: { + kind: 'preconfigured-tasks', + breadcrumb: 'Preconfigured tasks', + permission: 'Pretask' + }, + canActivate: [CheckPerm] + }, + { + path: 'new-preconfigured-tasks', + component: NewPreconfiguredTasksComponent, + data: { + kind: 'new-preconfigured-tasks', + breadcrumb: 'New Preconfigured tasks', + permission: 'Pretask' + }, + canActivate: [CheckPerm] + }, + { + path: 'preconfigured-tasks/:id/edit', + component: EditPreconfiguredTasksComponent, + data: { + kind: 'edit-preconfigured-tasks', + breadcrumb: 'Edit Preconfigured tasks', + permission: 'Pretask' + }, + canActivate: [CheckPerm], + canDeactivate: [PendingChangesGuard] + }, + { + path: 'preconfigured-tasks/:id/copy', + component: NewPreconfiguredTasksComponent, + data: { + kind: 'copy-preconfigured-tasks', + breadcrumb: 'Copy Preconfigured tasks', + permission: 'Pretask' + }, + canActivate: [CheckPerm] + }, + { + path: 'preconfigured-tasks/:id/copytask', + component: NewPreconfiguredTasksComponent, + data: { + kind: 'copy-tasks', + breadcrumb: 'Copy Task to Preconfigured task', + permission: 'Pretask' + }, + canActivate: [CheckPerm] + }, + { + path: 'supertasks', + component: SupertasksComponent, + data: { + kind: 'supertasks', + breadcrumb: 'Supertasks', + permission: 'SuperTask' + }, + canActivate: [CheckPerm] + }, + { + path: ':id/applyhashlist', + component: ApplyHashlistComponent, data: { kind: 'applyhashlist', breadcrumb: 'Apply hashlist', permission: 'SuperTask' - }, - canActivate: [IsAuth,CheckPerm]}, - { - path: 'new-supertasks', component: NewSupertasksComponent, - data: { - kind: 'new-supertasks', - breadcrumb: 'New Supertasks', - permission: 'SuperTask' - }, - canActivate: [IsAuth,CheckPerm]}, - { - path: ':id/edit', component: EditSupertasksComponent, - data: { - kind: 'edit-supertasks', - breadcrumb: 'Edit Supertasks', - permission: 'SuperTask' - }, - canActivate: [IsAuth,CheckPerm]}, - { - path: 'import-supertasks/masks', component: MasksComponent, - data: { - kind: 'masks', - breadcrumb: 'Import Masks', - permission: 'SuperTask' - }, - canActivate: [IsAuth,CheckPerm]}, - { - path: 'import-supertasks/wrbulk', component: WrbulkComponent, - data: { - kind: 'wrbulk', - breadcrumb: 'Import Wordlist/Rules Bulk', - permission: 'SuperTask' - }, - canActivate: [IsAuth,CheckPerm]}, - { - path: 'chunks', component: ChunksComponent, - data: { - kind: 'chunks', - breadcrumb: 'Chunks', - permission: 'Chunk' - }, - canActivate: [IsAuth,CheckPerm]}, - { - path: 'chunks/:id/view', component: ChunksComponent, - data: { - kind: 'chunks-view', - breadcrumb: 'Chunks > View Chunk', - permission: 'Chunk' - }, - canActivate: [IsAuth,CheckPerm]}, - { - path: 'chunks/show-all-chunks', component: ChunksComponent, - data: { - kind: 'chunks-cAll', - breadcrumb: 'Chunks > Show All chunks', - permission: 'Chunk' - }, - canActivate: [IsAuth,CheckPerm]}, - ] - } - ] - + }, + canActivate: [CheckPerm] + }, + { + path: 'new-supertasks', + component: NewSupertasksComponent, + data: { + kind: 'new-supertasks', + breadcrumb: 'New Supertasks', + permission: 'SuperTask' + }, + canActivate: [CheckPerm] + }, + { + path: ':id/edit', + component: EditSupertasksComponent, + data: { + kind: 'edit-supertasks', + breadcrumb: 'Edit Supertasks', + permission: 'SuperTask' + }, + canActivate: [CheckPerm] + }, + { + path: 'import-supertasks/masks', + component: MasksComponent, + data: { + kind: 'masks', + breadcrumb: 'Import Masks', + permission: 'SuperTask' + }, + canActivate: [CheckPerm] + }, + { + path: 'import-supertasks/wrbulk', + component: WrbulkComponent, + data: { + kind: 'wrbulk', + breadcrumb: 'Import Wordlist/Rules Bulk', + permission: 'SuperTask' + }, + canActivate: [CheckPerm] + }, + { + path: 'chunks', + component: ChunksComponent, + data: { + kind: 'chunks', + breadcrumb: 'Chunks', + permission: 'Chunk' + }, + canActivate: [CheckPerm] + }, + { + path: 'chunks/:id/view', + component: ChunksComponent, + data: { + kind: 'chunks-view', + breadcrumb: 'Chunks > View Chunk', + permission: 'Chunk' + }, + canActivate: [CheckPerm] + }, + { + path: 'chunks/show-all-chunks', + component: ChunksComponent, + data: { + kind: 'chunks-cAll', + breadcrumb: 'Chunks > Show All chunks', + permission: 'Chunk' + }, + canActivate: [CheckPerm] + } + ] + } +]; @NgModule({ imports: [RouterModule.forChild(routes)], exports: [RouterModule] - }) export class TasksRoutingModule {} diff --git a/src/app/users/users-routing.module.ts b/src/app/users/users-routing.module.ts index 1df68edc..f1d27d8b 100644 --- a/src/app/users/users-routing.module.ts +++ b/src/app/users/users-routing.module.ts @@ -1,108 +1,126 @@ -import { CheckPerm } from "../core/_guards/permission.guard"; -import { Routes, RouterModule } from '@angular/router'; -import { IsAuth } from "../core/_guards/auth.guard"; -import { NgModule } from "@angular/core"; +import { CheckPerm } from '../core/_guards/permission.guard'; +import { RouterModule, Routes } from '@angular/router'; +import { IsAuth } from '../core/_guards/auth.guard'; +import { NgModule } from '@angular/core'; -import { EditGlobalpermissionsgroupsComponent } from "./globalpermissionsgroups/edit-globalpermissionsgroups/edit-globalpermissionsgroups.component"; -import { GlobalpermissionsgroupsComponent } from "./globalpermissionsgroups/globalpermissionsgroups.component"; -import { EditUsersComponent } from "./edit-users/edit-users.component"; -import { AllUsersComponent } from "./all-users/all-users.component"; -import { FormComponent } from "../shared/form/form.component"; -import { GroupsComponent } from "./groups/groups.component"; +import { EditGlobalpermissionsgroupsComponent } from './globalpermissionsgroups/edit-globalpermissionsgroups/edit-globalpermissionsgroups.component'; +import { GlobalpermissionsgroupsComponent } from './globalpermissionsgroups/globalpermissionsgroups.component'; +import { EditUsersComponent } from './edit-users/edit-users.component'; +import { AllUsersComponent } from './all-users/all-users.component'; +import { MyRoute, RouteData } from '../core/_models/routes.model'; +import { FormComponent } from '../shared/form/form.component'; +import { GroupsComponent } from './groups/groups.component'; import { SERV } from '../core/_services/main.config'; -const routes: Routes = [ +const routes: MyRoute[] = [ { path: '', canActivate: [IsAuth], children: [ - { - path: '', component: FormComponent, - data: { - kind: 'newuser', - type: 'create', - path: SERV.USERS, - breadcrumb: 'New User', - permission: 'User' - }, - canActivate: [CheckPerm]}, - { - path: ':id/edit', component: EditUsersComponent, - data: { - kind: 'edit', - breadcrumb: 'Edit User', - permission: 'User' - }, - canActivate: [CheckPerm]}, - { - path: 'all-users', component: AllUsersComponent, - data: { - kind: 'all-users', - breadcrumb: 'All Users', - permission: 'User' - }, - canActivate: [CheckPerm]}, - { - path: 'global-permissions-groups', component: GlobalpermissionsgroupsComponent, - data: { - kind: 'globalpermissionsgp', - breadcrumb: 'Global Permissions Groups', - permission: 'RightGroup' - }, - canActivate: [CheckPerm]}, - { - path: 'global-permissions-groups/new', component: FormComponent, - data: { - kind: 'newglobalpermissionsgp', - type: 'create', - path: SERV.ACCESS_PERMISSIONS_GROUPS, - breadcrumb: 'New Global Permissions Groups', - permission: 'RightGroup' - }, - canActivate: [CheckPerm]}, - { - path: 'global-permissions-groups/:id/edit', component: EditGlobalpermissionsgroupsComponent, - data: { - kind: 'edit-gpg', - breadcrumb: 'Edit Global Permissions Group', - permission: 'RightGroup' - }, - canActivate: [CheckPerm]}, - { - path: 'access-groups', component: GroupsComponent, - data: { - kind: 'access-groups', - breadcrumb: 'Access Groups', - permission: 'GroupAccess' - }, - canActivate: [CheckPerm]}, - { - path: 'access-groups/new', component: FormComponent, - data: { - kind: 'newaccessgroups', - type: 'create', - path: SERV.ACCESS_GROUPS, - breadcrumb: 'New Access Group', - permission: 'GroupAccess' - }, - canActivate: [CheckPerm]}, - { - path: 'access-groups/:id/edit', component: FormComponent, - data: { - kind: 'editaccessgroups', - type: 'edit', - path: SERV.ACCESS_GROUPS, - breadcrumb: 'Edit Access Group', - permission: 'GroupAccess' - }, - canActivate: [CheckPerm]}, - ] + { + path: '', + component: FormComponent, + data: { + kind: 'newuser', + type: 'create', + path: SERV.USERS, + breadcrumb: 'New User', + permission: 'User' + }, + canActivate: [CheckPerm] + }, + { + path: ':id/edit', + component: EditUsersComponent, + data: { + kind: 'edit', + breadcrumb: 'Edit User', + permission: 'User' + }, + canActivate: [CheckPerm] + }, + { + path: 'all-users', + component: AllUsersComponent, + data: { + kind: 'all-users', + breadcrumb: 'All Users', + permission: 'User' + }, + canActivate: [CheckPerm] + }, + { + path: 'global-permissions-groups', + component: GlobalpermissionsgroupsComponent, + data: { + kind: 'globalpermissionsgp', + breadcrumb: 'Global Permissions Groups', + permission: 'RightGroup' + }, + canActivate: [CheckPerm] + }, + { + path: 'global-permissions-groups/new', + component: FormComponent, + data: { + kind: 'newglobalpermissionsgp', + type: 'create', + path: SERV.ACCESS_PERMISSIONS_GROUPS, + breadcrumb: 'New Global Permissions Groups', + permission: 'RightGroup' + }, + canActivate: [CheckPerm] + }, + { + path: 'global-permissions-groups/:id/edit', + component: EditGlobalpermissionsgroupsComponent, + data: { + kind: 'edit-gpg', + breadcrumb: 'Edit Global Permissions Group', + permission: 'RightGroup' + }, + canActivate: [CheckPerm] + }, + { + path: 'access-groups', + component: GroupsComponent, + data: { + kind: 'access-groups', + breadcrumb: 'Access Groups', + permission: 'GroupAccess' + }, + canActivate: [CheckPerm] + }, + { + path: 'access-groups/new', + component: FormComponent, + data: { + kind: 'newaccessgroups', + type: 'create', + path: SERV.ACCESS_GROUPS, + breadcrumb: 'New Access Group', + permission: 'GroupAccess' + }, + canActivate: [CheckPerm] + }, + { + path: 'access-groups/:id/edit', + component: FormComponent, + data: { + kind: 'editaccessgroups', + type: 'edit', + path: SERV.ACCESS_GROUPS, + breadcrumb: 'Edit Access Group', + permission: 'GroupAccess' + }, + canActivate: [CheckPerm] } - ] + ] + } +]; @NgModule({ imports: [RouterModule.forChild(routes)], exports: [RouterModule] - }) export class UsersRoutingModule {} diff --git a/src/styles/base/_base.scss b/src/styles/base/_base.scss index bc7f5a72..c6cf39c2 100644 --- a/src/styles/base/_base.scss +++ b/src/styles/base/_base.scss @@ -113,16 +113,3 @@ h2 { } } -// Angular Material - -.btn-toogle-select { - color: #ffffff; - background-color: #1986b1; - border-color: #1986b1; -} - -// .btn-outline-gray-600-select:hover { -// color: #4B5563; -// border-color: #4B5563; -// } - diff --git a/src/styles/base/_form.scss b/src/styles/base/_form.scss index 25c55f11..38adcea9 100644 --- a/src/styles/base/_form.scss +++ b/src/styles/base/_form.scss @@ -80,23 +80,3 @@ } } - -// Angular Material - -.custom-form { - min-width: 150px; - max-width: 500px; - width: 100%; -} - -.matfield-full-width { - width: 100%; -} - -.mat-input-element { - font-size: 14px !important; /* Adjust the font size as needed */ -} - -.mat-form-field { - max-width: 300px !important; /* Adjust the maximum width as needed */ -} diff --git a/src/styles/components/_form.scss b/src/styles/components/_form.scss index cdc077bb..27f63c32 100644 --- a/src/styles/components/_form.scss +++ b/src/styles/components/_form.scss @@ -2,6 +2,17 @@ SECTION FORM ================================== */ +/* + 00- Define SASS variables +*/ + +$hyperlink-color: #2196f3; // Adjust the color to your preference +$alert-color: #ffebee; // Adjust the alert color to your preference + +$btn-text-color: #ffffff; +$btn-bg-color: #1986b1; +$btn-border-color: #1986b1; + /* 01- Highlight validation in red */ @@ -29,234 +40,9 @@ select, input { 01- End */ -/* - 02- Smart Form Style template -*/ - -// move this element to layout -.container-project { - width: 100%; - padding-right: var(--bs-gutter-x, 0.75rem); - padding-left: var(--bs-gutter-x, 0.75rem); - margin-right: auto; - margin-left: auto; -} - -// move this element to card -.card-body { - flex: 1 1 auto; - padding: 1rem 1rem; -} - -// move this element to layout -.stepsContainer { - display:flex; - width:600px; - margin-left:auto; - margin-right:auto; - margin-bottom:20px; -} - -.stepConectorPiece { - height:3px; - background:lightgrey; - flex:1; - margin-top:19px; -} - -.stepCircle { - width:40px; - height:40px; - border-radius:100%; - background:lightgrey; - text-align:center; - line-height:40px; - color:#ffffff; - font-family:helvetica; -} - -// move this element to layout -.container-smart { - display:flex; - flex-direction:column; - // background:#f6f6f6; - width:300px; - margin-left:auto; - margin-right:auto; -} - -.one, -.two, -.three { - flex:1; - padding:5px; -} - -.one { - padding-top:10px; -} - -.three { - padding-bottom:10px; -} - -.four { - flex:1; - position:relative; - height:40px; -} - -.input { - width:90%; - display:block; - margin-left:auto; - margin-right:auto; - position:relative; - height:32px; - line-height:32px; - padding-left:10px; -} - -.submit { - position:absolute; - right:14px; - background:darkblue; - color:#ffffff; - height:26px; - padding-left:10px; - padding-right:10px; - border:none; - font-size:10px; - font-weight:600; - text-transform:uppercase; - cursor:pointer; -} - -.thankYou { - text-align:center; - font-family:helvetica; - font-weight:800; -} - -.customerContactContainer { - display:flex; - height:32px; - line-height:32px; - font-family:helvetica; - font-weight:600; - margin-top:20px; - width:300px; - margin-left:auto; - margin-right:auto; - -} - -.nameTitle { - width:100px; - background:#f6f6f6; - padding-left:10px; - font-size:12px; -} - -.emailTitle { - flex:1; - background:#f6f6f6; - padding-left:10px; - font-size:12px; -} - -.customerListItemsContainer { - display:flex; - height:32px; - border-bottom:1px solid #dddddd; - width:300px; - margin-left:auto; - margin-right:auto; - line-height:32px; - font-family:helvetica; - font-size:12px; -} - -.name { - width:100px; - padding-left:10px; -} - -.email { - flex:1; - padding-left:10px; -} - -/* - 02- End -*/ - -/* - 03- Selectize custom attributes -*/ - -input.ng-invalid.ng-touched { - border: 1.5px solid red; -} - -.form-control-mini { - display: inline-block; - width: 35px !important; -} - -.style_selectize { - padding: 10px 3px; - visibility: visible !important; - position: relative; - padding-left: 15px; - height: 45px; - border-top: 1px solid rgba(0, 0, 0, 0.3); - cursor: pointer; - display: flex; - align-items: center; - transition: 0.2s; -} - -.style_selectize.active .select::after { - border: none; - color: #ffffff !important; - border-left: 2px solid white; - border-top: 2px solid white; -} - -.style_selectize span{ - color: #000; -} - -.selectize-dropdown .selected { - background-color: #949596; - color: #fff !important; -} - -.selectize-dropdown .active { - color: #070707 !important; -} - -.active i { - color: #1411e6; - border: 2px solid rgb(0, 23, 128); -} - -.active span { - color: #1411e6; - // border: 2px solid rgb(0, 23, 128); -} - -.selectize-input::after { - visibility:hidden; -} - -/* - 03- End -*/ /* - 04- File Zone Area + 02- File Zone Area */ .filedrop { display: flex; @@ -336,11 +122,11 @@ progress::-webkit-progress-value { } /* - 04- End + 02- End */ /* - 05- Custom filter box + 03- Custom filter box */ .filterboxglass{ border:none; @@ -361,11 +147,11 @@ progress::-webkit-progress-value { } /* - 05- End + 03- End */ /* - 06- Password input field style and eye hidden/show + 04- Password input field style and eye hidden/show */ .input-icon-container{ @@ -394,11 +180,11 @@ progress::-webkit-progress-value { } /* - 06- End + 04- End */ /* - 07- Password Strength + 05- Password Strength */ .strength { @@ -430,30 +216,83 @@ progress::-webkit-progress-value { } /* - 07- End + 05- End */ -//Angular Material +/* + 06- Login Form +*/ -// Card alert Login -.mat-card-alert { +.login-container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 100vh; +} + +.login-card { + width: 400px; + margin-bottom: 16px; +} + +.card-login-field { width: 100%; - margin: 16px; - background-color: #f0f0f0; + margin-bottom: 16px; } -.alert { - background-color: #c0c0c0; - padding: 16px; - border-radius: 4px; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); - font-weight: bold; +.forgot-password-link { + margin-top: 16px; text-align: center; + + a { + color: $hyperlink-color; + text-decoration: none; + } } -.alert.alert-dismissible { - display: flex; - align-items: center; - justify-content: center; +.mat-card-alert { + background-color: $alert-color; + margin-top: 16px; +} + +/* + 06- End +*/ + +/* + 07- Horizontal Menu Select +*/ +.btn-toggle-select { + color: $btn-text-color; + background-color: $btn-bg-color; + border-color: $btn-border-color; } +/* + 07- End +*/ + +$custom-form-min-width: 150px; +$custom-form-max-width: 500px; + +$mat-input-font-size: 14px; // Adjust the font size as needed +$mat-form-field-max-width: 300px; // Adjust the maximum width as needed + +.custom-form { + min-width: $custom-form-min-width; + max-width: $custom-form-max-width; + width: 100%; +} + +.matfield-full-width { + width: 100%; +} + +.mat-input-element { + font-size: $mat-input-font-size !important; +} + +.mat-form-field { + max-width: $mat-form-field-max-width !important; +} From 3730574d6a0fae3722c03960ae7c204c1e2a11b9 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Mon, 13 Nov 2023 13:59:53 +0100 Subject: [PATCH 251/419] Update style on startpage --- src/app/home/home.component.html | 105 ++++++++++++++++++--------- src/app/home/home.component.scss | 2 +- src/styles/components/_card.scss | 119 ++++++++++++++++++++++++------- 3 files changed, 166 insertions(+), 60 deletions(-) diff --git a/src/app/home/home.component.html b/src/app/home/home.component.html index 84c140f4..029dd9d5 100644 --- a/src/app/home/home.component.html +++ b/src/app/home/home.component.html @@ -1,15 +1,22 @@
+ [ngClass]="{ + 'ro-1': true, + 'ro-m': screenM, + 'ro-l': screenL, + 'ro-s': screenS, + 'ro-xs': screenXS, + 'ro-xl': screenXL + }" + >
- + -

+
dns - Agents -

+
+

Agents

{{ activeAgents }} / {{ totalAgents }} -
check_circle Active / Total Agents @@ -20,12 +27,11 @@

-

+
checklist - Tasks -

+
+

Tasks

{{ totalTasks }} -
check_circle Total Live Tasks @@ -36,12 +42,11 @@

-

+
checklist - Supertasks -

+
+

Supertasks

{{ totalSupertasks }} -
check_circle Total Supertasks @@ -52,12 +57,11 @@

-

+
lock_open - Cracks -

+
+

Cracks

{{ totalCracks }} -
event_available Last 7 days @@ -67,40 +71,77 @@

+ [ngClass]="{ + 'ro-2': true, + 'ro-m': screenM, + 'ro-l': screenL, + 'ro-s': screenS, + 'ro-xs': screenXS, + 'ro-xl': screenXL + }" + >
-
+
Last updated: {{ lastUpdated }} - -
+
+ [ngClass]="{ + 'ro-3': true, + 'ro-m': screenM, + 'ro-l': screenL, + 'ro-s': screenS, + 'ro-xs': screenXS, + 'ro-xl': screenXL + }" + >
- Hashtopolis is available on Github. - To get information about how to use it, please visit the Wiki or Discord. + Hashtopolis is available on + Github. To get information about how to use it, please visit the + Wiki + or + Discord.
-
- - \ No newline at end of file + diff --git a/src/app/home/home.component.scss b/src/app/home/home.component.scss index 34e10eed..3109fcb8 100644 --- a/src/app/home/home.component.scss +++ b/src/app/home/home.component.scss @@ -72,4 +72,4 @@ margin-right: 0; } } -} \ No newline at end of file +} diff --git a/src/styles/components/_card.scss b/src/styles/components/_card.scss index 684c5725..4e440d13 100644 --- a/src/styles/components/_card.scss +++ b/src/styles/components/_card.scss @@ -25,7 +25,6 @@ width: 95%; } - .card-ag { background-color: #fff; border: 1px solid #d4d4d4; @@ -74,12 +73,12 @@ } .highlight { - background-color: #999 + background-color: #999; } hr.break:after { background: none repeat scroll 0% 0% #000; - content: ""; + content: ''; height: 10px; left: 50%; margin: -5px auto auto -5px; @@ -105,17 +104,27 @@ hr.break:after { .card { box-shadow: 0 6px 10px -4px rgba(0, 0, 0, 0.15); - background-color: #FFFFFF; + background-color: #ffffff; color: black; margin-bottom: 20px; position: relative; border: 0 none; - -webkit-transition: transform 300ms cubic-bezier(0.34, 2, 0.6, 1), box-shadow 200ms ease; - -moz-transition: transform 300ms cubic-bezier(0.34, 2, 0.6, 1), box-shadow 200ms ease; - -o-transition: transform 300ms cubic-bezier(0.34, 2, 0.6, 1), box-shadow 200ms ease; - -ms-transition: transform 300ms cubic-bezier(0.34, 2, 0.6, 1), box-shadow 200ms ease; - transition: transform 300ms cubic-bezier(0.34, 2, 0.6, 1), box-shadow 200ms ease; + -webkit-transition: + transform 300ms cubic-bezier(0.34, 2, 0.6, 1), + box-shadow 200ms ease; + -moz-transition: + transform 300ms cubic-bezier(0.34, 2, 0.6, 1), + box-shadow 200ms ease; + -o-transition: + transform 300ms cubic-bezier(0.34, 2, 0.6, 1), + box-shadow 200ms ease; + -ms-transition: + transform 300ms cubic-bezier(0.34, 2, 0.6, 1), + box-shadow 200ms ease; + transition: + transform 300ms cubic-bezier(0.34, 2, 0.6, 1), + box-shadow 200ms ease; .card-body { padding: 15px 15px 10px 15px; @@ -151,9 +160,7 @@ hr.break:after { color: rgb(144, 130, 122); } } - } - } .card-dash { @@ -188,7 +195,6 @@ hr.break:after { margin-bottom: 15px; } } - } .app-echarts { @@ -201,21 +207,39 @@ hr.break:after { 02- Dashboard Card End */ -.mat-card-box { - border: 1px solid $primary-700 !important; -} - .home-grid { margin-top: 50px; - .project-info { - text-align: center; - border-radius: 0; - border: 3px solid $primary-900 - } - .mat-mdc-card { position: relative; + background-color: transparent; + color: $primary-900; + text-align: center; + box-shadow: none; + + .icon-wrapper { + border-radius: 100%; + background-color: $accent-900; + display: inline-block; + padding: 1em; + width: 30px; + height: 30px; + text-align: center; + + .mat-icon { + color: #fff; + position: relative; + font-size: 30px; + width: 30px; + height: 30px; + } + } + + &.project-info { + text-align: center; + background-color: $accent-900; + color: #fff; + } } .mat-mdc-card-content { @@ -223,9 +247,11 @@ hr.break:after { color: $primary-900; font-weight: 200; margin-top: 0; + margin-bottom: 0.5em; font-size: 1.5em; .mat-icon { + color: $primary-400; position: relative; top: 4px; } @@ -234,6 +260,7 @@ hr.break:after { hr { border-width: 1px; border-style: solid; + border-color: $primary-900; } span { @@ -241,14 +268,13 @@ hr.break:after { width: 100%; &.value { - text-align: right; font-size: 2em; - color: $accent-900; + font-weight: 900; } &.label { - color: $primary-900; - font-size: .8em; + color: $primary-500; + font-size: 0.8em; .mat-icon { font-size: 16px; @@ -268,4 +294,43 @@ hr.break:after { background-color: var(--mat-toolbar-container-background-color); color: #fff; } -} \ No newline at end of file + + .home-grid { + .mat-mdc-card { + background-color: transparent; + color: #fff; + + &.project-info { + background-color: $accent-900; + } + + .icon-wrapper { + background-color: $accent-900; + + .mat-icon { + color: #fff; + } + } + } + + .mat-mdc-card-content { + h3 { + color: $primary-300; + + .mat-icon { + color: $primary-900; + } + } + + hr { + border-color: $primary-900; + } + + span { + &.label { + color: $primary-300; + } + } + } + } +} From c3cb0cb1158cfd6b15c25f5bcb12931db5ea973e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Mon, 13 Nov 2023 13:22:24 +0000 Subject: [PATCH 252/419] Documentation, missing change emitter data return --- .../mat-autocomplete.component.html | 10 ++++++ .../mat-autocomplete.component.ts | 32 +++++++++++-------- 2 files changed, 28 insertions(+), 14 deletions(-) create mode 100644 src/app/shared/form/mat-autocomplete/mat-autocomplete.component.html diff --git a/src/app/shared/form/mat-autocomplete/mat-autocomplete.component.html b/src/app/shared/form/mat-autocomplete/mat-autocomplete.component.html new file mode 100644 index 00000000..3ab3c621 --- /dev/null +++ b/src/app/shared/form/mat-autocomplete/mat-autocomplete.component.html @@ -0,0 +1,10 @@ +
+ + + + + {{ option.taskName }} + + + +
diff --git a/src/app/shared/form/mat-autocomplete/mat-autocomplete.component.ts b/src/app/shared/form/mat-autocomplete/mat-autocomplete.component.ts index 9141fb04..561bdd64 100644 --- a/src/app/shared/form/mat-autocomplete/mat-autocomplete.component.ts +++ b/src/app/shared/form/mat-autocomplete/mat-autocomplete.component.ts @@ -1,25 +1,14 @@ -import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; +import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; import { FormControl } from '@angular/forms'; import { Observable } from 'rxjs'; @Component({ selector: 'app-mat-autocomplete', - template: ` -
- - - - - {{ option.taskName }} - - - -
- `, + templateUrl: 'mat-autocomplete.component.html' }) export class MatAutocompleteComponent implements OnInit { @Input() options$: Observable; // An Observable emitting the options - @Input() placeholder: string = 'Select or search'; + @Input() placeholder = 'Select or search'; @Output() optionSelected = new EventEmitter(); selectedOption = new FormControl(); @@ -27,16 +16,31 @@ export class MatAutocompleteComponent implements OnInit { constructor() {} + /** + * Angular lifecycle hook: ngOnInit + * Initializes the component after Angular has initialized its data-bound properties. + * Subscribes to changes in the selected option value and emits the selected option using the 'optionSelected' event. + * Sets up the filteredOptions if an Observable of options ('options$') is provided. + */ ngOnInit(): void { + // Subscribe to changes in the selected option value this.selectedOption.valueChanges.subscribe((option) => { + // Emit the selected option using the 'optionSelected' event this.optionSelected.emit(option); }); + // Check if an Observable of options is provided if (this.options$) { + // If options$ is available, bind the filteredOptions to the selectedOption value changes this.filteredOptions = this.selectedOption.valueChanges; } } + /** + * Custom display function to determine the display text for the selected option. + * @param option - The option for which the display text is generated. + * @returns The display text for the selected option, or an empty string if the option is falsy. + */ displayFn(option: any): string { return option && option.taskName ? option.taskName : ''; } From 898571652a0a62db1154c0706bf63d8f90a9ff6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Mon, 13 Nov 2023 13:34:31 +0000 Subject: [PATCH 253/419] Config Module, remove unused modules/components --- src/app/config/config.module.ts | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/src/app/config/config.module.ts b/src/app/config/config.module.ts index 4e518670..967ad96e 100644 --- a/src/app/config/config.module.ts +++ b/src/app/config/config.module.ts @@ -7,45 +7,27 @@ import { ConfigRoutingModule } from './config-routing.module'; import { CoreComponentsModule } from '../core/_components/core-components.module'; import { CrackersComponent } from './engine/crackers/crackers.component'; import { DataTablesModule } from 'angular-datatables'; -import { EditCrackersComponent } from './engine/crackers/edit-version/edit-crackers.component'; import { EditHealthChecksComponent } from './health-checks/edit-health-check/edit-health-checks.component'; -import { EngineMenuComponent } from './engine/engine-menu'; import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; -import { HashtypeComponent } from './hashtypes/hashtype/hashtype.component'; import { HashtypesComponent } from './hashtypes/hashtypes.component'; import { HealthChecksComponent } from './health-checks/health-checks.component'; import { LogComponent } from './log/log.component'; -import { NewAgentBinariesComponent } from './engine/agent-binaries/agent-binary/new-agent-binaries.component'; -import { NewCrackerComponent } from './engine/crackers/new-cracker/new-cracker.component'; -import { NewCrackersComponent } from './engine/crackers/new-version/new-crackers.component'; import { NewHealthChecksComponent } from './health-checks/new-health-check/new-health-checks.component'; -import { NewPreprocessorComponent } from './engine/preprocessors/preprocessor/new-preprocessor.component'; import { NgModule } from '@angular/core'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { PipesModule } from '../shared/pipes.module'; import { PreprocessorsComponent } from './engine/preprocessors/preprocessors.component'; import { RouterModule } from '@angular/router'; -import { ServerComponent } from './server/server.component'; -import { SettingsMenuComponent } from './server/settings-menu'; @NgModule({ declarations: [ - NewAgentBinariesComponent, EditHealthChecksComponent, - NewPreprocessorComponent, NewHealthChecksComponent, PreprocessorsComponent, AgentBinariesComponent, HealthChecksComponent, - EditCrackersComponent, - SettingsMenuComponent, - NewCrackersComponent, - NewCrackerComponent, - EngineMenuComponent, HashtypesComponent, CrackersComponent, - HashtypeComponent, - ServerComponent, LogComponent ], imports: [ From 6d188b4613b4a6ba866bd6d6cc1087be8685b1d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Mon, 13 Nov 2023 14:58:20 +0000 Subject: [PATCH 254/419] Get bearer token --- .../core/_services/files/files_tus.service.ts | 116 ++++++++++++------ 1 file changed, 81 insertions(+), 35 deletions(-) diff --git a/src/app/core/_services/files/files_tus.service.ts b/src/app/core/_services/files/files_tus.service.ts index cef5aefe..5eaaaea2 100644 --- a/src/app/core/_services/files/files_tus.service.ts +++ b/src/app/core/_services/files/files_tus.service.ts @@ -1,4 +1,4 @@ -import { Injectable, ViewChild, ChangeDetectorRef } from '@angular/core'; +import { ChangeDetectorRef, Injectable, ViewChild } from '@angular/core'; import { environment } from './../../../../environments/environment'; import * as tus from 'tus-js-client'; import { Observable, Subject, of } from 'rxjs'; @@ -6,54 +6,99 @@ import { ConfigService } from '../shared/config.service'; import { UploadFileTUS } from '../../_models/file.model'; import { SERV } from '../../../core/_services/main.config'; -import { HttpEvent, HttpEventType, HttpProgressEvent } from '@angular/common/http'; +import { + HttpEvent, + HttpEventType, + HttpProgressEvent +} from '@angular/common/http'; import { GlobalService } from '../main.service'; import { Router } from '@angular/router'; +import { LocalStorageService } from '../storage/local-storage.service'; +import { UIConfigService } from '../shared/storage.service'; +import { User, UserData } from '../../_models/auth-user.model'; @Injectable({ providedIn: 'root' }) export class UploadTUSService { - + /** + * The API endpoint helper for uploading files using the TUS protocol. + */ private endpoint = '/helper/importFile'; + + /** + * The chunk size used for uploading files. It is obtained from the environment configuration. + */ private chunked = environment.config.chunkSizeTUS; - private userData: { _token: string } = JSON.parse(localStorage.getItem('userData')); + /** + * The storage key for retrieving user data from local storage. + */ + static readonly STORAGE_KEY = 'userData'; + + /** + * The user authentication token used for setting the Authorization header in file uploads. + */ + private _token: string; + + /** + * Represents the ongoing TUS upload. It is initialized as null. + */ private tusUpload: tus.Upload | null = null; + /** + * Constructs an instance of UploadTUSService. + * @param storage - The LocalStorageService used for storing and retrieving user data. + * @param cs - The ConfigService for fetching endpoint configurations. + * @param gs - The GlobalService for handling global functionalities. + * @param router - The Angular Router service for navigation. + */ constructor( + protected storage: LocalStorageService, private cs: ConfigService, private gs: GlobalService, private router: Router - ) { } + ) { + /** + * Retrieves user data from local storage using the STORAGE_KEY and sets the authentication token. + */ + const userData: UserData = this.storage.getItem(UploadTUSService.STORAGE_KEY); + this._token = userData._token; + } /** - * Upload file using TUS protocol - * @param file - File - * @param filename - Name to upload - * @param form - Creation form - * @param redirect - Link to redirect - **/ - + * Upload file using TUS protocol. + * @param file - File to upload. + * @param filename - Name to upload. + * @param path - Path for upload. + * @param form - Creation form. + * @param redirect - Link to redirect. + * @returns Observable representing the upload progress. + */ // public fileStatusArr: UploadFileTUS[] = []; - uploadFile(file: File, filename: string, path, form = null, redirect = null): Observable { - + uploadFile( + file: File, + filename: string, + path, + form = null, + redirect = null + ): Observable { return new Observable((observer) => { - // Chunksize config default let chunkSize = this.chunked; if (Number.isNaN(chunkSize)) { - chunkSize = Infinity + chunkSize = Infinity; } if (!tus.isSupported) { - alert('This browser does not support uploads. Please use a modern browser instead.') + alert( + 'This browser does not support uploads. Please use a modern browser instead.' + ); } - const upload = new tus.Upload(file, { endpoint: this.cs.getEndpoint() + this.endpoint, headers: { - Authorization: `Bearer ${this.userData._token}`, + Authorization: `Bearer ${this._token}`, 'Tus-Resumable': '1.0.0', 'Tus-Extension': 'checksum', 'Tus-Checksum-Algorithm': 'md5,sha1,crc32' @@ -64,7 +109,7 @@ export class UploadTUSService { removeFingerprintOnSuccess: true, metadata: { filename, - filetype: file.type, + filetype: file.type }, onError: async (error) => { const exist = String(error).includes('exists!'); @@ -77,7 +122,7 @@ export class UploadTUSService { }); } } else { - window.alert(`Failed because: ${error}`) + window.alert(`Failed because: ${error}`); } return false; }, @@ -93,29 +138,28 @@ export class UploadTUSService { }); } } - }) + }); this.tusUpload = upload; checkPreviousuploads(upload).catch((error) => { - console.error(error) - }) - - }) + console.error(error); + }); + }); async function checkPreviousuploads(upload) { - let previousUploads = await upload.findPreviousUploads() + let previousUploads = await upload.findPreviousUploads(); // We only want to consider uploads in the last hour. - const limitUpload = Date.now() - 3 * 60 * 60 * 1000 + const limitUpload = Date.now() - 3 * 60 * 60 * 1000; previousUploads = previousUploads .map((upload) => { - console.log('creationtome') - upload.creationTime = new Date(upload.creationTime) - return upload + console.log('creationtome'); + upload.creationTime = new Date(upload.creationTime); + return upload; }) .filter((upload) => upload.status > limitUpload) - .sort((a, b) => b.creationTime - a.creationTime) + .sort((a, b) => b.creationTime - a.creationTime); // if (previousUploads.length === 0) { // upload.start(); @@ -124,11 +168,14 @@ export class UploadTUSService { // File already exist in the import folder, then return progress as 100 upload.start(); - } - } + /** + * Checks if an HTTP event is a progress event. + * @param event - The HTTP event to check. + * @returns True if the event is a progress event, false otherwise. + */ isHttpProgressEvent(event: HttpEvent): event is HttpProgressEvent { return ( event.type === HttpEventType.DownloadProgress || @@ -136,4 +183,3 @@ export class UploadTUSService { ); } } - From 6266e8a2688da6ff7921c77fdb785607ec6ad25a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Mon, 13 Nov 2023 15:03:13 +0000 Subject: [PATCH 255/419] Comment --- src/app/core/_services/files/files_tus.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/core/_services/files/files_tus.service.ts b/src/app/core/_services/files/files_tus.service.ts index 5eaaaea2..946ecfa0 100644 --- a/src/app/core/_services/files/files_tus.service.ts +++ b/src/app/core/_services/files/files_tus.service.ts @@ -85,7 +85,7 @@ export class UploadTUSService { redirect = null ): Observable { return new Observable((observer) => { - // Chunksize config default + // Get Chunksize config default let chunkSize = this.chunked; if (Number.isNaN(chunkSize)) { chunkSize = Infinity; From b6d9fdb9a2c841ed086a1808ab7c0d694d20714c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Mon, 13 Nov 2023 15:21:02 +0000 Subject: [PATCH 256/419] Missing File Edit --- src/app/files/files-routing.module.ts | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/app/files/files-routing.module.ts b/src/app/files/files-routing.module.ts index e3ee723b..42a54338 100644 --- a/src/app/files/files-routing.module.ts +++ b/src/app/files/files-routing.module.ts @@ -6,6 +6,8 @@ import { NgModule } from '@angular/core'; import { FilesEditComponent } from './files-edit/files-edit.component'; import { NewFilesComponent } from './new-files/new-files.component'; import { MyRoute, RouteData } from '../core/_models/routes.model'; +import { FormComponent } from '../shared/form/form.component'; +import { SERV } from '../core/_services/main.config'; import { FilesComponent } from './files.component'; const routes: MyRoute[] = [ @@ -35,9 +37,11 @@ const routes: MyRoute[] = [ }, { path: ':id/wordlist-edit', - component: FilesEditComponent, + component: FormComponent, data: { - kind: 'wordlist-edit', + kind: 'editwordlist', + type: 'edit', + path: SERV.FILES, breadcrumb: 'Wordlist Edit', permission: 'File' }, @@ -65,9 +69,11 @@ const routes: MyRoute[] = [ }, { path: ':id/rules-edit', - component: FilesEditComponent, + component: FormComponent, data: { - kind: 'rules-edit', + kind: 'editrule', + type: 'edit', + path: SERV.FILES, breadcrumb: 'Rules Edit', permission: 'File' }, @@ -95,9 +101,11 @@ const routes: MyRoute[] = [ }, { path: ':id/other-edit', - component: FilesEditComponent, + component: FormComponent, data: { - kind: 'other-edit', + kind: 'editother', + type: 'edit', + path: SERV.FILES, breadcrumb: 'Other Edit', permission: 'File' }, From 2b2100b447055c744dfdc17b8da6fa13259b6925 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Mon, 13 Nov 2023 15:27:24 +0000 Subject: [PATCH 257/419] Conflict scss _card --- src/styles/components/_card.scss | 118 ++++++++++++++++++++++++------- 1 file changed, 91 insertions(+), 27 deletions(-) diff --git a/src/styles/components/_card.scss b/src/styles/components/_card.scss index fa95b66c..0cbdc526 100644 --- a/src/styles/components/_card.scss +++ b/src/styles/components/_card.scss @@ -25,7 +25,6 @@ width: 95%; } - .card-ag { background-color: #fff; border: 1px solid #d4d4d4; @@ -74,12 +73,12 @@ } .highlight { - background-color: #999 + background-color: #999; } hr.break:after { background: none repeat scroll 0% 0% #000; - content: ""; + content: ''; height: 10px; left: 50%; margin: -5px auto auto -5px; @@ -105,17 +104,27 @@ hr.break:after { .card { box-shadow: 0 6px 10px -4px rgba(0, 0, 0, 0.15); - background-color: #FFFFFF; + background-color: #ffffff; color: black; margin-bottom: 20px; position: relative; border: 0 none; - -webkit-transition: transform 300ms cubic-bezier(0.34, 2, 0.6, 1), box-shadow 200ms ease; - -moz-transition: transform 300ms cubic-bezier(0.34, 2, 0.6, 1), box-shadow 200ms ease; - -o-transition: transform 300ms cubic-bezier(0.34, 2, 0.6, 1), box-shadow 200ms ease; - -ms-transition: transform 300ms cubic-bezier(0.34, 2, 0.6, 1), box-shadow 200ms ease; - transition: transform 300ms cubic-bezier(0.34, 2, 0.6, 1), box-shadow 200ms ease; + -webkit-transition: + transform 300ms cubic-bezier(0.34, 2, 0.6, 1), + box-shadow 200ms ease; + -moz-transition: + transform 300ms cubic-bezier(0.34, 2, 0.6, 1), + box-shadow 200ms ease; + -o-transition: + transform 300ms cubic-bezier(0.34, 2, 0.6, 1), + box-shadow 200ms ease; + -ms-transition: + transform 300ms cubic-bezier(0.34, 2, 0.6, 1), + box-shadow 200ms ease; + transition: + transform 300ms cubic-bezier(0.34, 2, 0.6, 1), + box-shadow 200ms ease; .card-body { padding: 15px 15px 10px 15px; @@ -151,9 +160,7 @@ hr.break:after { color: rgb(144, 130, 122); } } - } - } .card-dash { @@ -188,7 +195,6 @@ hr.break:after { margin-bottom: 15px; } } - } .app-echarts { @@ -201,21 +207,39 @@ hr.break:after { 02- Dashboard Card End */ -.mat-card-box { - border: 1px solid $primary-700 !important; -} - .home-grid { margin-top: 50px; - .project-info { - text-align: center; - border-radius: 0; - border: 3px solid $primary-900 - } - .mat-mdc-card { position: relative; + background-color: transparent; + color: $primary-900; + text-align: center; + box-shadow: none; + + .icon-wrapper { + border-radius: 100%; + background-color: $accent-900; + display: inline-block; + padding: 1em; + width: 30px; + height: 30px; + text-align: center; + + .mat-icon { + color: #fff; + position: relative; + font-size: 30px; + width: 30px; + height: 30px; + } + } + + &.project-info { + text-align: center; + background-color: $accent-900; + color: #fff; + } } .mat-mdc-card-content { @@ -223,9 +247,11 @@ hr.break:after { color: $primary-900; font-weight: 200; margin-top: 0; + margin-bottom: 0.5em; font-size: 1.5em; .mat-icon { + color: $primary-400; position: relative; top: 4px; } @@ -234,6 +260,7 @@ hr.break:after { hr { border-width: 1px; border-style: solid; + border-color: $primary-900; } span { @@ -241,14 +268,13 @@ hr.break:after { width: 100%; &.value { - text-align: right; font-size: 2em; - color: $accent-900; + font-weight: 900; } &.label { - color: $primary-900; - font-size: .8em; + color: $primary-500; + font-size: 0.8em; .mat-icon { font-size: 16px; @@ -268,8 +294,46 @@ hr.break:after { background-color: var(--mat-toolbar-container-background-color); color: #fff; } -} + .home-grid { + .mat-mdc-card { + background-color: transparent; + color: #fff; + + &.project-info { + background-color: $accent-900; + } + + .icon-wrapper { + background-color: $accent-900; + + .mat-icon { + color: #fff; + } + } + } + + .mat-mdc-card-content { + h3 { + color: $primary-300; + + .mat-icon { + color: $primary-900; + } + } + + hr { + border-color: $primary-900; + } + + span { + &.label { + color: $primary-300; + } + } + } + } +} // ANGULAR MATERIAL From 363e119a212d3ca3d599d09ec0dc0c3a40007532 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Mon, 13 Nov 2023 16:08:44 +0000 Subject: [PATCH 258/419] Remove old components --- .../files-edit/files-edit.component.html | 59 -------- .../files/files-edit/files-edit.component.ts | 137 ------------------ src/app/files/files-routing.module.ts | 1 - src/app/files/files.module.ts | 31 ++-- 4 files changed, 13 insertions(+), 215 deletions(-) delete mode 100644 src/app/files/files-edit/files-edit.component.html delete mode 100644 src/app/files/files-edit/files-edit.component.ts diff --git a/src/app/files/files-edit/files-edit.component.html b/src/app/files/files-edit/files-edit.component.html deleted file mode 100644 index 685e4d12..00000000 --- a/src/app/files/files-edit/files-edit.component.html +++ /dev/null @@ -1,59 +0,0 @@ - - - -
-
- - - -
- - - - - - - - - - -
diff --git a/src/app/core/_components/tables/ht-table/ht-table.models.ts b/src/app/core/_components/tables/ht-table/ht-table.models.ts index be56ddec..85f334bc 100644 --- a/src/app/core/_components/tables/ht-table/ht-table.models.ts +++ b/src/app/core/_components/tables/ht-table/ht-table.models.ts @@ -6,6 +6,7 @@ export type DataType = | 'hashlists' | 'chunks' | 'hashtypes' + | 'files' | 'superhashlists'; export interface HTTableIcon { diff --git a/src/app/core/_models/access-group.model.ts b/src/app/core/_models/access-group.model.ts index 26c07e52..eae9a6fd 100644 --- a/src/app/core/_models/access-group.model.ts +++ b/src/app/core/_models/access-group.model.ts @@ -1,4 +1,4 @@ export interface AccessGroup { - accessGroupId: number - groupName: string + accessGroupId: number; + groupName: string; } diff --git a/src/app/core/_models/config-ui.model.ts b/src/app/core/_models/config-ui.model.ts index 3ba66174..2a710480 100644 --- a/src/app/core/_models/config-ui.model.ts +++ b/src/app/core/_models/config-ui.model.ts @@ -1,5 +1,6 @@ import { AgentsTableColumnLabel } from '../_components/tables/agents-table/agents-table.constants'; import { ChunksTableColumnLabel } from '../_components/tables/chunks-table/chunks-table.constants'; +import { FilesTableColumnLabel } from '../_components/tables/files-table/files-table.constants'; import { HashlistsTableColumnLabel } from '../_components/tables/hashlists-table/hashlists-table.constants'; import { HashtypesTableColumnLabel } from '../_components/tables/hashtypes-table/hashtypes-table.constants'; import { SuperHashlistsTableColumnLabel } from '../_components/tables/super-hashlists-table/super-hashlists-table.constants'; @@ -66,6 +67,13 @@ export const uiConfigDefault: UIConfig = { HashtypesTableColumnLabel.DESCRIPTION, HashtypesTableColumnLabel.SALTED, HashtypesTableColumnLabel.SLOW_HASH + ], + filesTable: [ + FilesTableColumnLabel.ID, + FilesTableColumnLabel.NAME, + FilesTableColumnLabel.SIZE, + FilesTableColumnLabel.LINE_COUNT, + FilesTableColumnLabel.ACCESS_GROUP ] }, refreshPage: false, diff --git a/src/app/core/_models/file.model.ts b/src/app/core/_models/file.model.ts index abcd6b29..7e416a0c 100644 --- a/src/app/core/_models/file.model.ts +++ b/src/app/core/_models/file.model.ts @@ -1,26 +1,52 @@ -import { AccessGroup } from './access-group.model' +import { AccessGroup } from './access-group.model'; +export enum FileType { + WORDLIST, + RULES, + OTHER +} + +/** + * @deprecated Use File instead + */ export interface Filetype { - fileId: number - filename: string - size: number - isSecret: number - fileType: number - accessGroupId: number - lineCount: number - accessGroup: AccessGroup + fileId: number; + filename: string; + size: number; + isSecret: number; + fileType: number; + accessGroupId: number; + lineCount: number; + accessGroup: AccessGroup; } +/** + * @todo Rename interface + */ export interface UpdateFileType { - fileId: number - filename: string - fileType: number - accessGroupId: number + fileId: number; + filename: string; + fileType: number; + accessGroupId: number; } export interface UploadFileTUS { - filename: string - progress: number - hash: string - uuid: string + filename: string; + progress: number; + hash: string; + uuid: string; +} + +export interface File { + _id: number; + _self: string; + accessGroup: AccessGroup; + accessGroupId?: number; + accessGroupName?: string; + fileId: number; + fileType: FileType; + filename: string; + isSecret: boolean; + lineCount: number; + size: number; } diff --git a/src/app/files/files-edit/files-edit.component.html b/src/app/files/files-edit/files-edit.component.html index 685e4d12..a7d8aef0 100644 --- a/src/app/files/files-edit/files-edit.component.html +++ b/src/app/files/files-edit/files-edit.component.html @@ -3,57 +3,57 @@
- - - -
- - + + - - - - - - - -
+ + + + diff --git a/src/app/files/files.component.html b/src/app/files/files.component.html index c27f1ddb..56af9b36 100644 --- a/src/app/files/files.component.html +++ b/src/app/files/files.component.html @@ -1,48 +1,29 @@ -
-
- -
-
- -
-
- -
-
- -
@@ -368,7 +368,7 @@ Rules - +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+ + + + + + + + + + + + + + Delete + + + + + + `, +}) +export class DynamicFormComponent implements OnInit, AfterViewInit, OnDestroy { + /** + * FontAwesome icon for providing additional information in form fields. + */ + faInfoCircle = faInfoCircle; + + /** + * The subtitle to display. + * @type {string} + */ + @Input() subtitle: string; + + /** + * An array of form field metadata that describes the form structure. + */ + @Input() formMetadata: any[] = []; + + /** + * Additional CSS class for labels. + */ + @Input() labelclass?: any; + + /** + * Initial values for form fields (optional). If not provided, an empty object is used as the default. + */ + @Input() formValues: any = {}; + + /** + * The Angular FormGroup that represents the dynamic form. + */ + @Input() form: FormGroup; + + /** + * Define the custom layout array. + */ + @Input() customLayout: any[]; + + /** + * Indicates whether the form is in "create" mode or "update" mode. + * When true, it's in "create" mode, and when false, it's in "update" mode. + */ + @Input() isCreateMode: boolean; + + /** + * A boolean input property that controls the visibility of the "Delete" button. + * Set it to `true` to display the "Delete" button, and `false` to hide it. + * By default, the "Delete" button is displayed (set to `true`). + */ + @Input() showDeleteButton: boolean = true; + + /** + * The text to display on the "Create" or "Update" button. + */ + @Input() buttonText: string; + + /** + * Event emitter for submitting the form. Emits the form values when the form is submitted. + * Parent components can subscribe to this event to handle form submissions. + */ + @Output() formSubmit: EventEmitter = new EventEmitter(); + + /** + * Event emitter for handling the delete action. Emits when the "Delete" action is triggered. + * Parent components can subscribe to this event to perform delete operations. + */ + @Output() deleteAction: EventEmitter = new EventEmitter(); + + /** + * A Subject used for managing the lifecycle and unsubscribing from observables when the component is destroyed. + * The `destroy$` subject is used to signal the component's destruction. + */ + private destroy$: Subject = new Subject(); + + /** + * Constructor for the DynamicFormComponent. + * @param fb - The Angular FormBuilder for creating form controls and groups. + * @param gs - The GlobalService for handling global operations and API requests. + * @param cd - The Angular ChangeDetectorRef for triggering change detection manually. + */ + constructor(private fb: FormBuilder, private gs: GlobalService, private cd: ChangeDetectorRef) {} + + /** + * Initializes the dynamic form by creating form controls and setting their initial values. + * This method is called when the dynamic form component is initialized. + */ + ngOnInit() { + // Check if this.customLayout is iterable and fallback to an empty array if it's not. + const customLayout = Array.isArray(this.customLayout) ? this.customLayout : []; + + // Initialize an object to store the configuration of form rows. + const formRows = []; + // Iterate through customLayout to define the structure of form rows and columns. + for (const row of customLayout) { + // Create a FormGroup for each row. + const formRow = new FormGroup({}); + + // Iterate through the fields in the formMetadata to create and configure form controls. + for (const field of this.formMetadata) { + // Exclude fields marked as titles from form control creation. + if (!field.isTitle) { + // Get the name of the field. + const fieldName = field.name; + + // Determine the validators for the field, defaulting to an empty array if none are provided. + const validators: ValidatorFn[] = field.validators ? field.validators : []; + + // Initialize the initial value for the form control. + let initialValue; + + // Set the initial value for the form control based on the field's type. + if (field.type === 'checkbox') { + // For checkboxes, use the value directly from formValues. + initialValue = this.formValues[fieldName]; + } else { + // For other field types, use formValues[fieldName] or 0 as a default value if not provided. + initialValue = fieldName in this.formValues ? this.formValues[fieldName] : 0; + } + + // In 'create' mode, override the initial value if a default value is specified in the field's metadata. + if (this.isCreateMode && field.defaultValue !== undefined) { + initialValue = field.defaultValue; + } + + // Create a form control with the initial value and any specified validators. + if (!this.isCreateMode && field.disabled) { + // If in 'update' mode and the field is disabled, create a disabled form control. + formRows[fieldName] = { value: initialValue, disabled: true }; + } else { + // Create a form control with the initial value and optional validators. + formRows[fieldName] = new FormControl(initialValue, validators); + } + + // Create a form control with the initial value and any specified validators. + const formControl = new FormControl(formRows); + + // Add the form control to the formRow with fieldName as the key. + formRow.addControl(fieldName, formControl); + } + // Add the formRow (FormGroup for the current row) to the list of rows. + formRows.push(formRow); + } + } + // Create the Angular FormGroup with the configured controls. + this.form = this.fb.group(formRows); + } + + /** + * A subscription to handle dynamic select options data retrieval. + * This subscription is used to fetch and update select field options with dynamic data. + */ + private selectOptionsSubscription: Subscription; + + /** + * Indicates whether the dynamic select options are currently being loaded. + * When true, it represents that options are being fetched; when false, loading is complete. + */ + isLoadingSelect: boolean = true; + + /** + * Angular lifecycle hook: ngAfterViewInit + * Performs initialization and logic for select fields with dynamic options. + */ + ngAfterViewInit() { + // Check if there are any "select" type fields with "selectOptions$" + const selectFields = this.formMetadata.filter( + (field) => field.type === 'selectd' && field.selectOptions$ + ); + + if (selectFields.length > 0) { + // Handle logic for select fields with selectOptions$ after the view is initialized + selectFields.forEach((field) => { + // Fetch the select options dynamically here + this.selectOptionsSubscription = this.gs.getAll(field.selectEndpoint$,{'maxResults': 5000}) + .pipe(takeUntil(this.destroy$)) + .subscribe((options) => { + + // Sometimes fields need to be mapped + const transformedOptions = this.transformSelectOptions(options.values, field); + + // Assign the fetched options to the field's selectOptions$ + field.selectOptions$ = transformedOptions; + + // Update isLoadingSelect to indicate that loading is complete + this.isLoadingSelect = false; + + // Optionally, update the form control value if needed + const control = this.form.get(field.name); + + // Check if there are options available + if (control && options.values && options.values.length > 0 && !this.isCreateMode) { + // Ensure that options.values[0] and options.values[0].value exist before setting the value + const initialSelectedValue = options.values[0]?.value; + + if (initialSelectedValue !== undefined) { + control.setValue(initialSelectedValue); + } + } + + // Trigger change detection to prevent ExpressionChangedAfterItHasBeenCheckedError + this.cd.detectChanges(); + }); + }); + } + } + + /** + * Transforms API response options based on a field mapping configuration. + * + * @param apiOptions - The options received from an API response. + * @param field - The field configuration that contains the mapping between form fields and API fields. + * + * @returns An array of transformed select options to be used in the form. + */ + transformSelectOptions(apiOptions: any[], field: any): any[] { + return apiOptions.map((apiOption: any) => { + const transformedOption: any = {}; + + for (const formField of Object.keys(field.fieldMapping)) { + const apiField = field.fieldMapping[formField]; + + if (Object.prototype.hasOwnProperty.call(apiOption, apiField)) { + transformedOption[formField] = apiOption[apiField]; + } else { + // Handle the case where the API field doesn't exist in the response + transformedOption[formField] = null; // or set a default value + } + } + + return transformedOption; + }); + } + + /** + * Checks if the form is valid. + * @returns {boolean} True if the form is valid, false otherwise. + */ + formIsValid(): boolean { + return this.form.valid; + } + + /** + * Handles the form submission. + * Emits the form values to the parent component if the form is valid. + */ + onSubmit() { + if (this.form.valid) { + // Emit the form values to the parent component + this.formSubmit.emit(this.form.value); + } + } + + /** + * Handles the delete action. + * Emits the delete action to the parent component when the "Delete" button is clicked. + */ + onDelete(){ + this.deleteAction.emit(); + } + + /** + * Angular lifecycle hook: ngOnDestroy + * Unsubscribes from all relevant subscriptions and cleans up resources + */ + ngOnDestroy() { + // Unsubscribe from the selectOptionsSubscription + // this.selectOptionsSubscription.unsubscribe(); + + // Complete and close the destroy$ subject to prevent memory leaks + this.destroy$.next(); + this.destroy$.complete(); + } + +} diff --git a/src/app/shared/form/form.component.ts b/src/app/shared/form/form.component.ts new file mode 100644 index 00000000..25f9e173 --- /dev/null +++ b/src/app/shared/form/form.component.ts @@ -0,0 +1,244 @@ +import { FormControl, FormGroup, Validators } from '@angular/forms'; +import { ActivatedRoute, Params, Router } from '@angular/router'; +import { Component, OnDestroy, OnInit } from '@angular/core'; + +import { AutoTitleService } from 'src/app/core/_services/shared/autotitle.service'; +import { UnsubscribeService } from 'src/app/core/_services/unsubscribe.service'; +import { AlertService } from 'src/app/core/_services/shared/alert.service'; +import { MetadataService } from 'src/app/core/_services/metadata.service'; +import { GlobalService } from 'src/app/core/_services/main.service'; +import { Subscription } from 'rxjs'; + +@Component({ + selector: 'app-form', + template: ` + + `, +}) +/** + * Component for managing forms, supporting both create and edit modes. + */ +export class FormComponent implements OnInit, OnDestroy { + + // Metadata Text, titles, subtitles, forms, and API path + globalMetadata: any[] = []; + apiPath: string; + + /** + * Indicates the mode of the form: either 'create' or 'edit'. + * This property determines whether the form is in the process of creating a new item or editing an existing one. + * @type {string} + */ + type: string; + + /** + * Flag that indicates whether the data for the form has been loaded and the form is ready for rendering. + * When true, the form is fully loaded and can be displayed; otherwise, it's still being prepared. + * @type {boolean} + */ + isloaded = false; + + /** + * Flag that specifies whether the form is in "create" mode. + * When true, the form is set to create a new item; when false, it's in edit mode for an existing item. + * @type {boolean} + */ + isCreate: boolean; + + /** + * The index of the item being edited in "edit" mode. + * This value is set when editing an existing item and represents the unique identifier of the item. + * @type {number} + */ + editedIndex: number; + + /** + * Title to be displayed in the form. + * @type {string} + */ + title: string; + + /** + * The Angular FormGroup representing the dynamic form. + * This FormGroup contains form controls for all fields in the formMetadata. + */ + form: FormGroup; + + /** + * Indicates if a custom form layout is used. + * Custom forms may have special logic or layouts. + * @type {boolean} + */ + customform: boolean; + + /** + * An array of form field metadata that describes the form structure. + * Each item in the array represents a form field, including its type, label, and other properties. + * @type {any[]} + */ + formMetadata: any[] = []; + + /** + * Initial values for form fields (optional). + * If provided, these values are used to initialize form controls in the dynamic form. + * @type {any[]} + */ + formValues: any[] = []; + + // Subscription for managing asynchronous data retrieval + private mySubscription: Subscription; + + /** + * Constructor for the FormComponent. + * @param unsubscribeService - The UnsubscribeService for managing subscriptions. + * @param metadataService - The MetadataService for accessing form metadata. + * @param titleService - The AutoTitleService for setting titles. + * @param route - The ActivatedRoute for retrieving route data. + * @param alert - The AlertService for displaying alerts. + * @param gs - The GlobalService for handling global operations. + * @param router - The Angular Router for navigation. + */ + constructor( + private unsubscribeService: UnsubscribeService, + private metadataService: MetadataService, + private titleService: AutoTitleService, + private route: ActivatedRoute, + private alert: AlertService, + private gs: GlobalService, + private router: Router + ) { + // Subscribe to route data to initialize component data + this.route.data.subscribe((data: { kind: string, path: string, type: string }) => { + const formKind = data.kind; + this.apiPath = data.path; // Get the API path from route data + this.type = data.type; + this.isCreate = (this.type === 'create'? true:false); + // Load metadata and form information + this.globalMetadata = this.metadataService.getInfoMetadata(formKind+'Info')[0]; + this.formMetadata = this.metadataService.getFormMetadata(formKind); + this.title = this.globalMetadata['title']; + this.customform = this.globalMetadata['customform']; + titleService.set([this.title]); + // Load metadata and form information + if(this.type === 'edit'){ + this.getIndex(); + this.loadEdit(); // Load data for editing + }else{ + this.isloaded = true + } + }); + // Add this.mySubscription to UnsubscribeService + this.unsubscribeService.add(this.mySubscription); + } + + /** + * Loads data for editing a form. + */ + getIndex() { + this.route.params.subscribe((params: Params) => { + this.editedIndex = +params['id']; + }); + } + + /** + * Loads data for editing a form. + */ + loadEdit() { + this.route.params.subscribe((params: Params) => { + this.editedIndex = +params['id']; + }); + + // Fetch data from the API for editing + this.gs.get(this.apiPath, this.editedIndex).subscribe((result) => { + this.formValues = result; + this.isloaded = true; // Data is loaded and ready for form rendering + }); + } + + /** + * Angular lifecycle hook: ngOnInit + */ + ngOnInit(): void { + // If in "edit" mode, load data for editing + if (this.type === 'edit') { + this.loadEdit(); + } + } + + /** + * Angular lifecycle hook: ngOnDestroy + * Unsubscribe from all subscriptions to prevent memory leaks. + */ + ngOnDestroy(): void { + this.unsubscribeService.unsubscribeAll(); + } + + /** + * Handles the submission of the form. + * @param formValues - The values submitted from the form. + */ + onFormSubmit(formValues: any) { + if(this.customform){ + this.onCustomForm(formValues); + } + if (this.type === 'create') { + // Create mode: Submit form data for creating a new item + console.log(formValues) + this.mySubscription = this.gs.create(this.apiPath, formValues).subscribe(() => { + this.alert.okAlert(this.globalMetadata['submitok'], ''); + this.router.navigate([this.globalMetadata['submitokredirect']]); + }); + } else { + // Update mode: Submit form data for updating an existing item + this.mySubscription = this.gs.update(this.apiPath, this.editedIndex, formValues).subscribe(() => { + this.alert.okAlert(this.globalMetadata['submitok'], ''); + this.router.navigate([this.globalMetadata['submitokredirect']]); + }); + } + } + + /** + * Modifies the form values as needed for custom form submission. + * @param formValues - The form values to be modified. + * @returns The modified form values. + */ + onCustomForm(formValues: any) { + // Check the formMetadata for fields with 'replacevalue' property + this.getIndex(); + for (const field of this.formMetadata) { + if (field.replacevalue) { + // Replace the value with the 'editedIndex' + formValues[field.name] = this.editedIndex; + } + // Add custom logic to modify formValues as needed + } + + // Return the modified formValues + return formValues; + } + + /** + * Handles the deletion action when the "Delete" button is clicked. + * Displays a confirmation dialog and, if confirmed, triggers the deletion of the item. + * Emits success alerts and navigates to the appropriate route. + */ + onDeleteAction() { + if (this.globalMetadata['deltitle']) { + this.getIndex(); + } + this.alert.deleteConfirmation('', this.globalMetadata['deltitle']).then((confirmed) => { + if (confirmed) { + // Deletion + this.gs.delete(this.apiPath, this.editedIndex).subscribe(() => { + // Successful deletion + this.alert.okAlert(this.globalMetadata['delsubmitok'], ''); + this.router.navigate([this.globalMetadata['delsubmitokredirect']]); + }); + } else { + // Handle cancellation + this.alert.okAlert(this.globalMetadata['delsubmitcancel'], ''); + } + }); + } + +} diff --git a/src/app/shared/form/formconfig.component.ts b/src/app/shared/form/formconfig.component.ts new file mode 100644 index 00000000..41bb98a5 --- /dev/null +++ b/src/app/shared/form/formconfig.component.ts @@ -0,0 +1,205 @@ +import { FormControl, FormGroup, Validators } from '@angular/forms'; +import { ActivatedRoute, Params, Router } from '@angular/router'; +import { Component, OnDestroy, OnInit } from '@angular/core'; + +import { AutoTitleService } from 'src/app/core/_services/shared/autotitle.service'; +import { UIConfigService } from 'src/app/core/_services/shared/storage.service'; +import { UnsubscribeService } from 'src/app/core/_services/unsubscribe.service'; +import { AlertService } from 'src/app/core/_services/shared/alert.service'; +import { MetadataService } from 'src/app/core/_services/metadata.service'; +import { HorizontalNav } from 'src/app/core/_models/horizontalnav.model'; +import { GlobalService } from 'src/app/core/_services/main.service'; +import { SERV } from '../../core/_services/main.config'; +import { Subscription } from 'rxjs'; + +@Component({ + selector: 'app-form', + template: ` + + + `, +}) +/** + * Component for managing forms, supporting both create and edit modes. + */ +export class FormConfigComponent implements OnInit, OnDestroy { + + // Metadata Text, titles, subtitles, forms, and API path + globalMetadata: any[] = []; + apiPath: string; + + /** + * Flag that indicates whether the data for the form has been loaded and the form is ready for rendering. + * When true, the form is fully loaded and can be displayed; otherwise, it's still being prepared. + * @type {boolean} + */ + isloaded = false; + + /** + * Title to be displayed in the form. + * @type {string} + */ + title: string; + + /** + * The Angular FormGroup representing the dynamic form. + * This FormGroup contains form controls for all fields in the formMetadata. + */ + form: FormGroup; + + /** + * An array of form field metadata that describes the form structure. + * Each item in the array represents a form field, including its type, label, and other properties. + * @type {any[]} + */ + formMetadata: any[] = []; + + /** + * Initial values for form fields (optional). + * If provided, these values are used to initialize form controls in the dynamic form. + * @type {any[]} + */ + formValues: any[] = []; + + /** + * An array of objects containing IDs and corresponding item names. + * This information is used to map form field names to their associated IDs. + * @type {any[]} + */ + formIds: any[] = []; + + // Subscription for managing asynchronous data retrieval + private mySubscription: Subscription; + + /** + * Constructor for the FormComponent. + * @param unsubscribeService - The UnsubscribeService for managing subscriptions. + * @param metadataService - The MetadataService for accessing form metadata. + * @param titleService - The AutoTitleService for setting titles. + * @param route - The ActivatedRoute for retrieving route data. + * @param alert - The AlertService for displaying alerts. + * @param gs - The GlobalService for handling global operations. + * @param router - The Angular Router for navigation. + */ + constructor( + private unsubscribeService: UnsubscribeService, + private metadataService: MetadataService, + private titleService: AutoTitleService, + private uicService: UIConfigService, + private route: ActivatedRoute, + private alert: AlertService, + private gs: GlobalService, + private router: Router + ) { + // Subscribe to route data to initialize component data + this.route.data.subscribe((data: { kind: string, path: string, type: string }) => { + const formKind = data.kind; + this.apiPath = data.path; // Get the API path from route data + // Load metadata and form information + this.globalMetadata = this.metadataService.getInfoMetadata(formKind+'Info')[0]; + this.formMetadata = this.metadataService.getFormMetadata(formKind); + this.title = this.globalMetadata['title']; + titleService.set([this.title]); + }); + // Add this.mySubscription to UnsubscribeService + this.unsubscribeService.add(this.mySubscription); + } + + /** + * Horizontal menu and redirection links. + */ + menuItems: HorizontalNav[] = [ + { label: 'Agent', routeName: 'config/agent' }, + { label: 'Task/Chunk', routeName: 'config/task-chunk' }, + { label: 'Hashes/Cracks/Hashlist', routeName: 'config/hch' }, + { label: 'Notifications', routeName: 'config/notifications' }, + { label: 'General', routeName: 'config/general-settings' }, + ]; + + /** + * Angular lifecycle hook: ngOnInit + */ + ngOnInit(): void { + this.loadEdit(); + } + + /** + * Loads data for editing a form. + * This function fetches data from the API for editing and prepares it for rendering in a form. + */ + loadEdit() { + // Fetch data from the API for editing + this.mySubscription = this.gs.getAll(this.apiPath, { 'maxResults': 500 }).subscribe((result) => { + // Transform the retrieved array of objects into the desired structure for form rendering + this.formValues = result.values.reduce((result, item) => { + result[item.item] = item.value; + return result; + }, {}); + // Maps the item with the id, so can be used for update + this.formIds = result.values.reduce((result, item) => { + result[item.item] = item._id; + return result; + }, {}); + + this.isloaded = true; // Data is loaded and ready for form rendering + }); + } + + /** + * Angular lifecycle hook: ngOnDestroy + * Unsubscribe from all subscriptions to prevent memory leaks. + */ + ngOnDestroy(): void { + this.unsubscribeService.unsubscribeAll(); + } + + /** + * Handles the submission of the form. + * @param form - The form object containing the updated values. + */ + onFormSubmit(form: any) { + const currentFormValues = form; + const initialFormValues = this.formValues; + const changedFields = {}; + + for (const key in currentFormValues) { + if (Object.prototype.hasOwnProperty.call(currentFormValues, key)) { + if (currentFormValues[key] !== initialFormValues[key]) { + // Convert boolean values to 1 (true) or 0 (false) + // const value = currentFormValues[key] === true ? 0 : (currentFormValues[key] === false ? 1 : currentFormValues[key]); + changedFields[key] = currentFormValues[key]; + } + } + } + + const fieldKeys = Object.keys(changedFields); + let index = 0; + + const showAlertsSequentially = () => { + if (index < fieldKeys.length) { + const key = fieldKeys[index]; + const id = this.formIds[key]; + const valueUpdate = changedFields[key]; + const arr = { 'item': key, 'value': String(valueUpdate) }; + + this.mySubscription = this.gs.update(SERV.CONFIGS, id, arr).subscribe((result) => { + this.uicService.onUpdatingCheck(key); + this.alert.okAlert('Saved', key); + + // Delay showing the next alert by 2000 milliseconds (2 seconds) + setTimeout(() => { + index++; + showAlertsSequentially(); + }, 2000); + }); + } + }; + + showAlertsSequentially(); + } + + +} + + + diff --git a/src/app/shared/form/formuisettings.component.ts b/src/app/shared/form/formuisettings.component.ts new file mode 100644 index 00000000..a9d37b92 --- /dev/null +++ b/src/app/shared/form/formuisettings.component.ts @@ -0,0 +1,139 @@ +import { FormControl, FormGroup, Validators } from '@angular/forms'; +import { ActivatedRoute, Params, Router } from '@angular/router'; +import { Component, OnDestroy, OnInit } from '@angular/core'; + +import { AutoTitleService } from 'src/app/core/_services/shared/autotitle.service'; +import { UnsubscribeService } from 'src/app/core/_services/unsubscribe.service'; +import { AlertService } from 'src/app/core/_services/shared/alert.service'; +import { MetadataService } from 'src/app/core/_services/metadata.service'; +import { GlobalService } from 'src/app/core/_services/main.service'; +import { Subscription } from 'rxjs'; +import { CookieService } from 'src/app/core/_services/shared/cookies.service'; + +@Component({ + selector: 'app-form', + template: ` + + `, +}) +/** + * Component for managing forms, supporting both create and edit modes. + */ +export class FormUIsettingsComponent implements OnDestroy { + + // Metadata Text, titles, subtitles, forms, and API path + globalMetadata: any[] = []; + apiPath: string; + + /** + * Title to be displayed in the form. + * @type {string} + */ + title: string; + + /** + * The Angular FormGroup representing the dynamic form. + * This FormGroup contains form controls for all fields in the formMetadata. + */ + form: FormGroup; + + /** + * An array of form field metadata that describes the form structure. + * Each item in the array represents a form field, including its type, label, and other properties. + * @type {any[]} + */ + formMetadata: any[] = []; + + /** + * Initial values for form fields (optional). + * If provided, these values are used to initialize form controls in the dynamic form. + * @type {any[]} + */ + formValues: any[] = []; + + // Subscription for managing asynchronous data retrieval + private mySubscription: Subscription; + + /** + * Constructor for the FormComponent. + * @param unsubscribeService - The UnsubscribeService for managing subscriptions. + * @param metadataService - The MetadataService for accessing form metadata. + * @param titleService - The AutoTitleService for setting titles. + * @param route - The ActivatedRoute for retrieving route data. + * @param alert - The AlertService for displaying alerts. + * @param gs - The GlobalService for handling global operations. + * @param router - The Angular Router for navigation. + */ + constructor( + private unsubscribeService: UnsubscribeService, + private metadataService: MetadataService, + private titleService: AutoTitleService, + private cookieService: CookieService, + private route: ActivatedRoute, + private alert: AlertService, + private gs: GlobalService, + private router: Router + ) { + // Subscribe to route data to initialize component data + this.route.data.subscribe((data: { kind: string, path: string, type: string }) => { + const formKind = data.kind; + this.apiPath = data.path; // Get the API path from route data + // Load metadata and form information + this.globalMetadata = this.metadataService.getInfoMetadata(formKind+'Info')[0]; + this.formMetadata = this.metadataService.getFormMetadata(formKind); + // this.formValues = + this.title = this.globalMetadata['title']; + titleService.set([this.title]); + }); + // Add this.mySubscription to UnsubscribeService + this.unsubscribeService.add(this.mySubscription); + } + + /** + * Angular lifecycle hook: ngOnInit + */ + ngOnInit(): void { + this.loadEdit(); + } + + /** + * Loads data for editing a form. + * This function fetches data from the API for editing and prepares it for rendering in a form. + */ + loadEdit() { + // Fetch data from the API for editing + // Use utils + } + + /** + * Angular lifecycle hook: ngOnDestroy + * Unsubscribe from all subscriptions to prevent memory leaks. + */ + ngOnDestroy(): void { + this.unsubscribeService.unsubscribeAll(); + } + + /** + * Handles the submission of the form. + * @param formValues - The values submitted from the form. + */ + onFormSubmit(formValues: any) { + // Iterate through form values + for (const key in formValues) { + if (Object.prototype.hasOwnProperty.call(formValues, key)) { + const value = formValues[key]; + // Check if the key is 'autorefresh' + if (key === 'autorefresh') { + this.cookieService.setCookie('autorefresh', JSON.stringify({ active: true, value }), 365); + } else { + // Set other form values as individual cookies + this.cookieService.setCookie(key, value, 365); + } + } + } + // Show an alert or notification + this.alert.okAlert('Saved!', ''); + } + +} + diff --git a/src/app/shared/form/mat-autocomplete/mat-autocomplete.component.ts b/src/app/shared/form/mat-autocomplete/mat-autocomplete.component.ts new file mode 100644 index 00000000..9141fb04 --- /dev/null +++ b/src/app/shared/form/mat-autocomplete/mat-autocomplete.component.ts @@ -0,0 +1,43 @@ +import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; +import { FormControl } from '@angular/forms'; +import { Observable } from 'rxjs'; + +@Component({ + selector: 'app-mat-autocomplete', + template: ` +
+ + + + + {{ option.taskName }} + + + +
+ `, +}) +export class MatAutocompleteComponent implements OnInit { + @Input() options$: Observable; // An Observable emitting the options + @Input() placeholder: string = 'Select or search'; + @Output() optionSelected = new EventEmitter(); + + selectedOption = new FormControl(); + filteredOptions: Observable | undefined; + + constructor() {} + + ngOnInit(): void { + this.selectedOption.valueChanges.subscribe((option) => { + this.optionSelected.emit(option); + }); + + if (this.options$) { + this.filteredOptions = this.selectedOption.valueChanges; + } + } + + displayFn(option: any): string { + return option && option.taskName ? option.taskName : ''; + } +} diff --git a/src/app/shared/grid-containers/grid-autocol.ts b/src/app/shared/grid-containers/grid-autocol.ts index 4b8adad3..d62b2c20 100644 --- a/src/app/shared/grid-containers/grid-autocol.ts +++ b/src/app/shared/grid-containers/grid-autocol.ts @@ -3,71 +3,82 @@ import { Component, Input, OnInit, Renderer2, ElementRef } from '@angular/core'; @Component({ selector: 'grid-autocol', template: ` -
- -
-`, -host: { - "(window:resize)":"onWindowResize($event)" -}, -styles: [` -.vertical-line { - position: absolute; - top: 200px; - bottom: 0; - left: 0; - width: 1px; /* Width of the vertical line */ - background-color: gray; /* Line color, you can customize this */ -} -`] +
+ +
+ `, + host: { + "(window:resize)": "onWindowResize($event)", + }, + styles: [` + .vertical-line { + position: absolute; + top: 200px; + bottom: 0; + left: 0; + width: 1px; /* Width of the vertical line */ + background-color: gray; /* Line color, you can customize this */ + } + `] }) export class GridAutoColComponent implements OnInit { - @Input() centered?: boolean; - @Input() verticalLine?: boolean = false; + // Input properties that can be set from the parent component + @Input() itemCount: number; // Number of items to determine if the component should be enabled + @Input() centered?: boolean; // Whether to center the content + @Input() verticalLine?: boolean = false; // Whether to show vertical division lines - isMobile = false; - width:number = window.innerWidth; - height:number = window.innerHeight; - mobileWidth = 760; + isMobile = false; // Indicates if the screen is in mobile mode + width: number = window.innerWidth; // Current window width + height: number = window.innerHeight; // Current window height + mobileWidth = 760; // Width threshold for mobile mode gutterSize = 50; // Separation between columns (adjust as needed) cardWidth = 300; // Width of each card (adjust as needed) - cardHeight = 80; //We take the full height minus the header and footer (adjust as needed) + cardHeight = 80; // Height of each card (adjust as needed) constructor(private renderer: Renderer2, private el: ElementRef) {} - ngOnInit() : void { + ngOnInit(): void { this.isMobile = this.width < this.mobileWidth; } + // Method to handle window resize events onWindowResize(event) { this.width = event.target.innerWidth; this.height = event.target.innerHeight; this.isMobile = this.width < this.mobileWidth; } + // Calculate the number of columns based on available space getCols() { return Math.floor((this.width + this.gutterSize) / (this.cardWidth + this.gutterSize)); } - getRows(){ - return Math.floor((this.height + this.gutterSize) / (this.cardHeight + this.gutterSize)); + // Calculate the number of rows based on available space + getRows() { + return Math.floor((this.height + this.gutterSize) / (this.cardHeight + this.gutterSize)); } + // Calculate and return the CSS styles for the grid layout public getStyles() { + // If the item count is less than 6, return an empty object to disable the component + if (this.itemCount < 6) { + return {}; + } // If the screen is smaller than a specific threshold, return an empty object if (this.width < 767 || this.height < 721) { return {}; } - // Calculate the number of columns based on available space + // Calculate the number of columns and rows based on available space const cols = this.getCols(); - // Calculate the number of rows based on available space const rows = this.getRows(); - // Calculate the width of the division lines + + // Calculate the width of division lines between columns const divisionLineWidth = (this.width - (cols * this.cardWidth)) / (cols - 1); + // Define the CSS styles for the grid const styles = { 'display': 'grid', 'grid-template-columns': `repeat(2, ${this.cardWidth}px))`, @@ -77,21 +88,20 @@ export class GridAutoColComponent implements OnInit { 'position': 'relative', }; - // Set the division line width + // Set the division line width if necessary if (cols >= 2 && cols < 4) { styles['grid-column-gap'] = `${divisionLineWidth}px`; // Add division lines } - //ToDo use render2 to split columns, complete this // Remove previously added vertical lines this.removeVerticalLines(); - // Dynamically add vertical lines for columns using render2 + // Dynamically add vertical lines for columns using Renderer2 if (cols > 2 && this.verticalLine) { for (let i = 1; i < cols; i++) { const lineElement = this.renderer.createElement('div'); this.renderer.addClass(lineElement, 'vertical-line'); - this.renderer.setStyle(lineElement, 'left', `${(i * this.cardWidth + (i - 1) )}px`); + this.renderer.setStyle(lineElement, 'left', `${(i * this.cardWidth + (i - 1))}px`); this.renderer.appendChild(this.el.nativeElement, lineElement); } } @@ -99,12 +109,11 @@ export class GridAutoColComponent implements OnInit { return styles; } + // Remove previously added vertical lines private removeVerticalLines() { const existingLines = this.el.nativeElement.querySelectorAll('.vertical-line'); existingLines.forEach(line => { this.renderer.removeChild(this.el.nativeElement, line); }); } - } - diff --git a/src/app/shared/grid-containers/grid-main.ts b/src/app/shared/grid-containers/grid-main.ts index fd8ff47a..7b52bf72 100644 --- a/src/app/shared/grid-containers/grid-main.ts +++ b/src/app/shared/grid-containers/grid-main.ts @@ -3,14 +3,9 @@ import { Component, Input, OnInit } from '@angular/core'; @Component({ selector: 'grid-main', template: ` -
-
-
-
-
-
-
-
+ + + `, host: { "(window:resize)":"onWindowResize($event)" diff --git a/src/app/shared/grid-containers/grid.module.ts b/src/app/shared/grid-containers/grid.module.ts index 1ef51c32..4980ed72 100644 --- a/src/app/shared/grid-containers/grid.module.ts +++ b/src/app/shared/grid-containers/grid.module.ts @@ -2,6 +2,7 @@ import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; import { GridFormInputComponent } from './grid-formgroup'; import { GridAutoColComponent } from './grid-autocol'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { MatCardModule } from '@angular/material/card'; import { GridMainComponent } from './grid-main'; import { CommonModule } from '@angular/common'; import { FormsModule } from '@angular/forms'; @@ -12,6 +13,7 @@ import { NgModule } from '@angular/core'; FontAwesomeModule, CommonModule, FormsModule, + MatCardModule, NgbModule ], exports: [ diff --git a/src/app/shared/navigation/horizontalnav.component.ts b/src/app/shared/navigation/horizontalnav.component.ts new file mode 100644 index 00000000..e0517824 --- /dev/null +++ b/src/app/shared/navigation/horizontalnav.component.ts @@ -0,0 +1,69 @@ +import { HorizontalNav } from 'src/app/core/_models/horizontalnav.model'; +import { Component, Input, OnDestroy } from '@angular/core'; +import { takeUntil } from 'rxjs/operators'; +import { Router } from '@angular/router'; +import { Subject } from 'rxjs'; + +@Component({ + selector: 'horizontalnav', + template: ` +
+
+
+
+ +
+ `, +}) +export class HorizontalNavComponent implements OnDestroy { + @Input() menuItems: HorizontalNav[] = []; + private unsubscribe$: Subject = new Subject(); + + constructor(private router: Router) {} + + /** + * Returns the CSS class for a button element based on the current route. + * @param routeName The name of the route associated with the button. + * @returns The CSS class, including 'select' if the button's route is active. + */ + getButtonClass(routeName: string): string { + return this.router.url.includes(routeName) ? 'btn btn-sm btn-outline-gray-600 btn-outline-gray-600-select' : 'btn btn-sm btn-outline-gray-600'; + } + + /** + * Navigates to a specified route when a button is clicked. + * @param routeName The name of the route to navigate to. + */ + navigateTo(routeName: string): void { + this.router.navigate([`${routeName}`]); + } + + /** + * Handles keydown events to trigger navigation when Enter or Spacebar is pressed. + * @param event The keyboard event. + * @param routeName The name of the route to navigate to. + */ + handleKeyDown(event: KeyboardEvent, routeName: string): void { + if (event.key === 'Enter' || event.key === ' ') { + this.navigateTo(routeName); + } + } + + ngOnDestroy(): void { + this.unsubscribe$.next(); + this.unsubscribe$.complete(); + } +} diff --git a/src/app/shared/navigation/navigation.module.ts b/src/app/shared/navigation/navigation.module.ts new file mode 100644 index 00000000..923a9ffc --- /dev/null +++ b/src/app/shared/navigation/navigation.module.ts @@ -0,0 +1,10 @@ +import { HorizontalNavComponent } from './horizontalnav.component'; +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; + +@NgModule({ + declarations: [HorizontalNavComponent], + imports: [CommonModule], + exports: [HorizontalNavComponent], +}) +export class HorizontalNavModule {} diff --git a/src/app/shared/page-headers/page-title.module.ts b/src/app/shared/page-headers/page-title.module.ts index a3ba6e96..947925be 100644 --- a/src/app/shared/page-headers/page-title.module.ts +++ b/src/app/shared/page-headers/page-title.module.ts @@ -1,17 +1,19 @@ import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; import { PageSubTitleComponent } from './page-subtitle.component'; import { PageTitleComponent } from './page-title.component'; +import { MatButtonModule } from '@angular/material/button'; +import { MatCardModule } from '@angular/material/card'; +import { MatIconModule } from '@angular/material/icon'; import { CommonModule } from '@angular/common'; import { FormsModule } from '@angular/forms'; import { NgModule } from '@angular/core'; -import { MatButtonModule } from '@angular/material/button'; -import { MatIconModule } from '@angular/material/icon'; @NgModule({ imports: [ FormsModule, CommonModule, FontAwesomeModule, + MatCardModule, MatButtonModule, MatIconModule, ], diff --git a/src/app/users/users-routing.module.ts b/src/app/users/users-routing.module.ts index 37607f27..1df68edc 100644 --- a/src/app/users/users-routing.module.ts +++ b/src/app/users/users-routing.module.ts @@ -4,26 +4,28 @@ import { IsAuth } from "../core/_guards/auth.guard"; import { NgModule } from "@angular/core"; import { EditGlobalpermissionsgroupsComponent } from "./globalpermissionsgroups/edit-globalpermissionsgroups/edit-globalpermissionsgroups.component"; -import { NewGlobalpermissionsgroupsComponent } from "./globalpermissionsgroups/new-globalpermissionsgroups/new-globalpermissionsgroups.component"; import { GlobalpermissionsgroupsComponent } from "./globalpermissionsgroups/globalpermissionsgroups.component"; -import { CUGroupComponent } from "./groups/cu-group/cu-group.component"; import { EditUsersComponent } from "./edit-users/edit-users.component"; import { AllUsersComponent } from "./all-users/all-users.component"; +import { FormComponent } from "../shared/form/form.component"; import { GroupsComponent } from "./groups/groups.component"; -import { UsersComponent } from "./users.component"; +import { SERV } from '../core/_services/main.config'; const routes: Routes = [ { path: '', + canActivate: [IsAuth], children: [ { - path: '', component: UsersComponent, + path: '', component: FormComponent, data: { - kind: 'users', + kind: 'newuser', + type: 'create', + path: SERV.USERS, breadcrumb: 'New User', permission: 'User' }, - canActivate: [IsAuth,CheckPerm]}, + canActivate: [CheckPerm]}, { path: ':id/edit', component: EditUsersComponent, data: { @@ -31,7 +33,7 @@ const routes: Routes = [ breadcrumb: 'Edit User', permission: 'User' }, - canActivate: [IsAuth,CheckPerm]}, + canActivate: [CheckPerm]}, { path: 'all-users', component: AllUsersComponent, data: { @@ -39,7 +41,7 @@ const routes: Routes = [ breadcrumb: 'All Users', permission: 'User' }, - canActivate: [IsAuth,CheckPerm]}, + canActivate: [CheckPerm]}, { path: 'global-permissions-groups', component: GlobalpermissionsgroupsComponent, data: { @@ -47,15 +49,17 @@ const routes: Routes = [ breadcrumb: 'Global Permissions Groups', permission: 'RightGroup' }, - canActivate: [IsAuth,CheckPerm]}, + canActivate: [CheckPerm]}, { - path: 'global-permissions-groups/new', component: NewGlobalpermissionsgroupsComponent, + path: 'global-permissions-groups/new', component: FormComponent, data: { - kind: 'new-globalpermissionsgp', + kind: 'newglobalpermissionsgp', + type: 'create', + path: SERV.ACCESS_PERMISSIONS_GROUPS, breadcrumb: 'New Global Permissions Groups', permission: 'RightGroup' }, - canActivate: [IsAuth,CheckPerm]}, + canActivate: [CheckPerm]}, { path: 'global-permissions-groups/:id/edit', component: EditGlobalpermissionsgroupsComponent, data: { @@ -63,7 +67,7 @@ const routes: Routes = [ breadcrumb: 'Edit Global Permissions Group', permission: 'RightGroup' }, - canActivate: [IsAuth,CheckPerm]}, + canActivate: [CheckPerm]}, { path: 'access-groups', component: GroupsComponent, data: { @@ -71,23 +75,27 @@ const routes: Routes = [ breadcrumb: 'Access Groups', permission: 'GroupAccess' }, - canActivate: [IsAuth,CheckPerm]}, + canActivate: [CheckPerm]}, { - path: 'access-groups/new', component: CUGroupComponent, + path: 'access-groups/new', component: FormComponent, data: { - kind: 'new-access-groups', + kind: 'newaccessgroups', + type: 'create', + path: SERV.ACCESS_GROUPS, breadcrumb: 'New Access Group', permission: 'GroupAccess' }, - canActivate: [IsAuth,CheckPerm]}, + canActivate: [CheckPerm]}, { - path: 'access-groups/:id/edit', component: CUGroupComponent, + path: 'access-groups/:id/edit', component: FormComponent, data: { - kind: 'edit-access-groups', + kind: 'editaccessgroups', + type: 'edit', + path: SERV.ACCESS_GROUPS, breadcrumb: 'Edit Access Group', permission: 'GroupAccess' }, - canActivate: [IsAuth,CheckPerm]}, + canActivate: [CheckPerm]}, ] } ] diff --git a/src/app/users/users.component.html b/src/app/users/users.component.html deleted file mode 100644 index bbb79838..00000000 --- a/src/app/users/users.component.html +++ /dev/null @@ -1,40 +0,0 @@ - - -
-
- - - - User already exist! - - - - - - - - - - - -
- -
- diff --git a/src/app/users/users.component.ts b/src/app/users/users.component.ts deleted file mode 100644 index e2f5f140..00000000 --- a/src/app/users/users.component.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { FormControl, FormGroup, Validators } from '@angular/forms'; -import { ActivatedRoute, Params, Router } from '@angular/router'; -import { Component, OnInit } from '@angular/core'; - -import { AlertService } from '../core/_services/shared/alert.service'; -import { GlobalService } from 'src/app/core/_services/main.service'; -import { PageTitle } from 'src/app/core/_decorators/autotitle'; -import { environment } from 'src/environments/environment'; -import { SERV } from '../core/_services/main.config'; - -@Component({ - selector: 'app-users', - templateUrl: './users.component.html' -}) -@PageTitle(['New User']) -export class UsersComponent implements OnInit { - - createForm: FormGroup; - agp:any; - usedUserNames = []; - - constructor( - private route:ActivatedRoute, - private alert: AlertService, - private gs: GlobalService, - private router: Router - ){} - - private maxResults = environment.config.prodApiMaxResults; - - ngOnInit(): void { - - const params = {'maxResults': this.maxResults}; - this.gs.getAll(SERV.ACCESS_PERMISSIONS_GROUPS,params).subscribe((agp: any) => { - this.agp = agp.values; - }); - - this.gs.getAll(SERV.USERS,params).subscribe((res: any) => { - const arrNames = []; - for(let i=0; i < res.values.length; i++){ - arrNames.push(res.values[i]['name']); - } - this.usedUserNames = arrNames; - }); - - this.createForm = new FormGroup({ - 'name': new FormControl(null, [Validators.required, this.checkUserNameExist.bind(this)]), - 'email': new FormControl(null, [Validators.required, Validators.email]), //Check ValidationService.emailValidator - 'isValid': new FormControl(true), - 'globalPermissionGroupId': new FormControl() - }); - - } - - onSubmit(){ - if (this.createForm.valid) { - - this.gs.create(SERV.USERS,this.createForm.value).subscribe(() => { - this.alert.okAlert('New User created!',''); - this.router.navigate(['users/all-users']); - } - ); - } - } - - // Connect this with the - checkUserNameExist(control: FormControl): {[s: string]: boolean}{ - if(this.usedUserNames.indexOf(control.value) !== -1){ - return {'nameIsUsed': true}; - } - return null as any; - } - - -} diff --git a/src/app/users/users.module.ts b/src/app/users/users.module.ts index 383f73b6..3c4de5b5 100644 --- a/src/app/users/users.module.ts +++ b/src/app/users/users.module.ts @@ -16,7 +16,6 @@ import { EditUsersComponent } from "./edit-users/edit-users.component"; import { AllUsersComponent } from "./all-users/all-users.component"; import { GroupsComponent } from "./groups/groups.component"; import { UsersRoutingModule } from "./users-routing.module"; -import { UsersComponent } from "./users.component"; @NgModule({ declarations:[ @@ -26,7 +25,6 @@ import { UsersComponent } from "./users.component"; EditUsersComponent, AllUsersComponent, GroupsComponent, - UsersComponent, CUGroupComponent ], imports:[ diff --git a/src/styles/components/_button.scss b/src/styles/components/_button.scss index 5653790c..d492b9b7 100644 --- a/src/styles/components/_button.scss +++ b/src/styles/components/_button.scss @@ -240,3 +240,9 @@ /* 04- End */ + +// Angular Material + +.separation-button { + margin-right: 16px; /* Adjust the margin to control the spacing */ +} diff --git a/src/styles/components/_card.scss b/src/styles/components/_card.scss index 684c5725..fa95b66c 100644 --- a/src/styles/components/_card.scss +++ b/src/styles/components/_card.scss @@ -268,4 +268,13 @@ hr.break:after { background-color: var(--mat-toolbar-container-background-color); color: #fff; } -} \ No newline at end of file +} + + +// ANGULAR MATERIAL + +.grid-card { + background-color: #f0f0f0; + padding: 16px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); +} diff --git a/src/styles/components/_form.scss b/src/styles/components/_form.scss index 6eaaaa91..d10056a6 100644 --- a/src/styles/components/_form.scss +++ b/src/styles/components/_form.scss @@ -432,3 +432,4 @@ progress::-webkit-progress-value { /* 07- End */ + diff --git a/src/styles/styles.scss b/src/styles/styles.scss index 0f544026..9e00645d 100644 --- a/src/styles/styles.scss +++ b/src/styles/styles.scss @@ -10,4 +10,4 @@ @import './base/base-dir'; @import './components/components-dir'; @import './layout/layout-dir'; -@import './pages/pages-dir'; \ No newline at end of file +@import './pages/pages-dir'; From 540dff5fd25dff40faaefed1061144cc2ea180bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Thu, 9 Nov 2023 16:38:40 +0000 Subject: [PATCH 202/419] Template Material --- src/app/auth/auth.component.html | 104 +++++++++---------- src/app/auth/auth.module.ts | 15 +++ src/app/shared/buttons/grid-cancel.ts | 4 +- src/app/shared/form/dynamicform.component.ts | 2 +- src/styles/components/_form.scss | 24 +++++ 5 files changed, 94 insertions(+), 55 deletions(-) diff --git a/src/app/auth/auth.component.html b/src/app/auth/auth.component.html index 20805df3..749d9527 100644 --- a/src/app/auth/auth.component.html +++ b/src/app/auth/auth.component.html @@ -1,54 +1,54 @@ -
-
- -
-

Log In

-
Enter your credentials to access.
-
- - - -
-
- -
- - - - -
-
-   -
-
- -
- - - - -
- - -
-
-
-   - + + Login +
Enter your credentials to access.
+ + +

+ + User Name + + + +

+ +

+ + Password + + +

+ +
-
- -
- -
- This is a private closed system. If you need access you need to contact an admin. + +

+ + + - -
-
+ + + +
+
+ This is a private closed system. If you need access, you need to contact an admin. +
+
+
+
+ + + + + + + diff --git a/src/app/auth/auth.module.ts b/src/app/auth/auth.module.ts index f9856e0b..35a2954e 100644 --- a/src/app/auth/auth.module.ts +++ b/src/app/auth/auth.module.ts @@ -4,6 +4,14 @@ import { IsAuth } from "../core/_guards/auth.guard"; import { CommonModule } from "@angular/common"; import { RouterModule } from "@angular/router"; import { FormsModule } from "@angular/forms"; +import { MatCardModule } from '@angular/material/card'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatTooltipModule } from '@angular/material/tooltip'; +import { MatButtonModule } from "@angular/material/button"; +import { MAT_FORM_FIELD_DEFAULT_OPTIONS } from "@angular/material/form-field"; +import { MatIconModule } from "@angular/material/icon"; +import { MatInputModule } from "@angular/material/input"; +import { MatSelectModule } from "@angular/material/select"; import { NgModule } from "@angular/core"; import { ComponentsModule } from "../shared/components.module"; @@ -23,6 +31,13 @@ import { AuthComponent } from "./auth.component"; FontAwesomeModule, ComponentsModule, CommonModule, + MatFormFieldModule, + MatCardModule, + MatTooltipModule, + MatButtonModule, + MatIconModule, + MatInputModule, + MatSelectModule, FormsModule, NgbModule ] diff --git a/src/app/shared/buttons/grid-cancel.ts b/src/app/shared/buttons/grid-cancel.ts index 03c04c8b..95a651bf 100644 --- a/src/app/shared/buttons/grid-cancel.ts +++ b/src/app/shared/buttons/grid-cancel.ts @@ -20,9 +20,9 @@ import { Component } from '@angular/core'; @Component({ selector: 'grid-buttons', template: ` - + - + ` }) export class GridButtonsComponent {} diff --git a/src/app/shared/form/dynamicform.component.ts b/src/app/shared/form/dynamicform.component.ts index bda867d3..8e231cb1 100644 --- a/src/app/shared/form/dynamicform.component.ts +++ b/src/app/shared/form/dynamicform.component.ts @@ -13,8 +13,8 @@ import { ChangeDetectorRef } from '@angular/core'; @Component({ selector: 'app-dynamic-form', template: ` - +
diff --git a/src/styles/components/_form.scss b/src/styles/components/_form.scss index d10056a6..cdc077bb 100644 --- a/src/styles/components/_form.scss +++ b/src/styles/components/_form.scss @@ -433,3 +433,27 @@ progress::-webkit-progress-value { 07- End */ +//Angular Material + +// Card alert Login +.mat-card-alert { + width: 100%; + margin: 16px; + background-color: #f0f0f0; +} + +.alert { + background-color: #c0c0c0; + padding: 16px; + border-radius: 4px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); + font-weight: bold; + text-align: center; +} + +.alert.alert-dismissible { + display: flex; + align-items: center; + justify-content: center; +} + From 92bad5cee44bfdcc9a06b19764e421c40eceb7fb Mon Sep 17 00:00:00 2001 From: itbf9 Date: Thu, 9 Nov 2023 20:03:39 +0100 Subject: [PATCH 203/419] Sort imports on save --- .devcontainer/devcontainer.json | 2 + .eslintrc.json | 22 ++--- .vscode/settings.json | 21 +++++ package-lock.json | 159 ++++++++++++++++++++++++-------- package.json | 3 +- 5 files changed, 150 insertions(+), 57 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index b48a0b5c..265e695c 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -20,6 +20,8 @@ "extensions": [ "Angular.ng-template", "dbaeumer.vscode-eslint", + "esbenp.prettier-vscode", + "amatiasq.sort-imports", "firefox-devtools.vscode-firefox-debug", "github.vscode-pull-request-github", "ms-azuretools.vscode-docker" diff --git a/.eslintrc.json b/.eslintrc.json index 8e037a18..289ca1cb 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,13 +1,9 @@ { "root": true, - "ignorePatterns": [ - "projects/**/*" - ], + "ignorePatterns": ["projects/**/*"], "overrides": [ { - "files": [ - "*.ts" - ], + "files": ["*.ts"], "extends": [ "eslint:recommended", "plugin:@typescript-eslint/recommended", @@ -16,6 +12,7 @@ "plugin:prettier/recommended" ], "rules": { + "prettier/prettier": ["error"], "@angular-eslint/directive-selector": [ "error", { @@ -38,21 +35,14 @@ "ignoreCase": false, "ignoreDeclarationSort": false, "ignoreMemberSort": false, - "memberSyntaxSortOrder": [ - "none", - "all", - "multiple", - "single" - ], + "memberSyntaxSortOrder": ["none", "all", "multiple", "single"], "allowSeparatedGroups": false } ] } }, { - "files": [ - "*.html" - ], + "files": ["*.html"], "extends": [ "plugin:@angular-eslint/template/recommended", "plugin:@angular-eslint/template/accessibility" @@ -60,4 +50,4 @@ "rules": {} } ] -} \ No newline at end of file +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..a253d9c5 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,21 @@ +{ + "eslint.enable": true, + "eslint.validate": [ + "javascript", + "typescript", + "css", + "scss", + "html" + ], + "editor.formatOnSave": true, + "[javascript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[typescript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "prettier.configPath": ".prettierrc.json", + "editor.codeActionsOnSave": { + "source.fixAll.eslint": true + } +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 668b8bb8..aa93e307 100644 --- a/package-lock.json +++ b/package-lock.json @@ -79,6 +79,7 @@ "@angular-eslint/template-parser": "16.0.3", "@angular/cli": "^16.1.4", "@angular/compiler-cli": "^16.1.4", + "@trivago/prettier-plugin-sort-imports": "^4.2.1", "@types/datatables.net": "^1.10.24", "@types/datatables.net-select": "^1.2.7", "@types/jasmine": "~4.3.5", @@ -824,11 +825,11 @@ "dev": true }, "node_modules/@babel/code-frame": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.10.tgz", - "integrity": "sha512-/KKIMG4UEL35WmI9OlvMhurwtytjvXoFcGNrOvyG9zIzA8YmPjVtIZUf7b05+TPO7G7/GEmLHDaoCgACHl9hhA==", + "version": "7.22.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", + "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", "dependencies": { - "@babel/highlight": "^7.22.10", + "@babel/highlight": "^7.22.13", "chalk": "^2.4.2" }, "engines": { @@ -1016,20 +1017,33 @@ } }, "node_modules/@babel/helper-environment-visitor": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz", - "integrity": "sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-function-name": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz", - "integrity": "sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", "dependencies": { - "@babel/template": "^7.22.5", - "@babel/types": "^7.22.5" + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name/node_modules/@babel/template": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", + "dependencies": { + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" }, "engines": { "node": ">=6.9.0" @@ -1185,9 +1199,9 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", - "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", "engines": { "node": ">=6.9.0" } @@ -1228,11 +1242,11 @@ } }, "node_modules/@babel/highlight": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.10.tgz", - "integrity": "sha512-78aUtVcT7MUscr0K5mIEnkwxPE0MaxkR5RxRwuHaQ+JuU5AmTPhY+do2mdzVTnIJJpyBglql2pehuBIWHug+WQ==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", + "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", "dependencies": { - "@babel/helper-validator-identifier": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", "chalk": "^2.4.2", "js-tokens": "^4.0.0" }, @@ -1241,9 +1255,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.10.tgz", - "integrity": "sha512-lNbdGsQb9ekfsnjFGhEiF4hfFqGgfOP3H3d27re3n+CGhNuTSUEQdfWk556sTLNTloczcdM5TYF2LhzmDQKyvQ==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.3.tgz", + "integrity": "sha512-uVsWNvlVsIninV2prNz/3lHCb+5CJ+e+IUBfbjToAHODtfGYLfCFuY4AU7TskI+dAKk+njsPiBjq1gKTvZOBaw==", "bin": { "parser": "bin/babel-parser.js" }, @@ -2515,18 +2529,18 @@ } }, "node_modules/@babel/traverse": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.10.tgz", - "integrity": "sha512-Q/urqV4pRByiNNpb/f5OSv28ZlGJiFiiTh+GAHktbIrkPhPbl90+uW6SmpoLyZqutrg9AEaEf3Q/ZBRHBXgxig==", - "dependencies": { - "@babel/code-frame": "^7.22.10", - "@babel/generator": "^7.22.10", - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-function-name": "^7.22.5", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", + "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", + "dependencies": { + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.0", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", "@babel/helper-hoist-variables": "^7.22.5", "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.22.10", - "@babel/types": "^7.22.10", + "@babel/parser": "^7.23.0", + "@babel/types": "^7.23.0", "debug": "^4.1.0", "globals": "^11.1.0" }, @@ -2535,11 +2549,11 @@ } }, "node_modules/@babel/traverse/node_modules/@babel/generator": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.10.tgz", - "integrity": "sha512-79KIf7YiWjjdZ81JnLujDRApWtl7BxTqWD88+FFdQEIOG8LJ0etDOM7CXuIgGJa55sGOwZVwuEsaLEm0PJ5/+A==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.3.tgz", + "integrity": "sha512-keeZWAV4LU3tW0qRi19HRpabC/ilM0HRBBzf9/k8FFiG4KVpiv0FIy4hHfLfFQZNhziCTPTmd59zoyv6DNISzg==", "dependencies": { - "@babel/types": "^7.22.10", + "@babel/types": "^7.23.3", "@jridgewell/gen-mapping": "^0.3.2", "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" @@ -2549,12 +2563,12 @@ } }, "node_modules/@babel/types": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.10.tgz", - "integrity": "sha512-obaoigiLrlDZ7TUQln/8m4mSqIW2QFeOrCQc9r+xsaHGNoplVNYlRVpsfE8Vj35GEm2ZH4ZhrNYogs/3fj85kg==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.3.tgz", + "integrity": "sha512-OZnvoH2l8PK5eUvEcUyCt/sXgr/h+UWpVuBbOljwcrAgUl6lpchoQ++PHGyQy1AtYnVA6CEq3y5xeEI10brpXw==", "dependencies": { "@babel/helper-string-parser": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" }, "engines": { @@ -5364,6 +5378,65 @@ "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", "dev": true }, + "node_modules/@trivago/prettier-plugin-sort-imports": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@trivago/prettier-plugin-sort-imports/-/prettier-plugin-sort-imports-4.2.1.tgz", + "integrity": "sha512-iuy2MPVURGdxILTchHr15VAioItuYBejKfcTmQFlxIuqA7jeaT6ngr5aUIG6S6U096d6a6lJCgaOwlRrPLlOPg==", + "dev": true, + "dependencies": { + "@babel/generator": "7.17.7", + "@babel/parser": "^7.20.5", + "@babel/traverse": "7.23.2", + "@babel/types": "7.17.0", + "javascript-natural-sort": "0.7.1", + "lodash": "^4.17.21" + }, + "peerDependencies": { + "@vue/compiler-sfc": "3.x", + "prettier": "2.x - 3.x" + }, + "peerDependenciesMeta": { + "@vue/compiler-sfc": { + "optional": true + } + } + }, + "node_modules/@trivago/prettier-plugin-sort-imports/node_modules/@babel/generator": { + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.7.tgz", + "integrity": "sha512-oLcVCTeIFadUoArDTwpluncplrYBmTCCZZgXCbgNGvOBBiSDDK3eWO4b/+eOTli5tKv1lg+a5/NAXg+nTcei1w==", + "dev": true, + "dependencies": { + "@babel/types": "^7.17.0", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@trivago/prettier-plugin-sort-imports/node_modules/@babel/types": { + "version": "7.17.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.17.0.tgz", + "integrity": "sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.16.7", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@trivago/prettier-plugin-sort-imports/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/@tufjs/canonical-json": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@tufjs/canonical-json/-/canonical-json-1.0.0.tgz", @@ -12168,6 +12241,12 @@ "integrity": "sha512-D4bRej8CplwNtNGyTPD++cafJlZUphzZNV+MSAnbD3er4D0NjL4x9V+mu/SI+5129utnCBen23JwEuBZA9vlpQ==", "dev": true }, + "node_modules/javascript-natural-sort": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz", + "integrity": "sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==", + "dev": true + }, "node_modules/jest-worker": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", diff --git a/package.json b/package.json index b254b155..7b0a4adc 100644 --- a/package.json +++ b/package.json @@ -85,6 +85,7 @@ "@angular-eslint/template-parser": "16.0.3", "@angular/cli": "^16.1.4", "@angular/compiler-cli": "^16.1.4", + "@trivago/prettier-plugin-sort-imports": "^4.2.1", "@types/datatables.net": "^1.10.24", "@types/datatables.net-select": "^1.2.7", "@types/jasmine": "~4.3.5", @@ -106,4 +107,4 @@ "puppeteer": "^20.9.0", "typescript": "~5.1.6" } -} \ No newline at end of file +} From 8cb54c45922268586db534823bbcb6d963171b00 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Thu, 9 Nov 2023 20:05:05 +0100 Subject: [PATCH 204/419] Allow multiple routerLinks in tables --- .../agents-table/agents-table.component.ts | 357 ++++++++++-------- .../agents-table/agents-table.constants.ts | 4 +- .../tables/ht-table/ht-table.component.html | 24 +- .../tables/ht-table/ht-table.models.ts | 35 +- .../core/_datasources/agents.datasource.ts | 123 +++--- src/app/core/_models/agent.model.ts | 120 +++--- src/styles/components/_table.scss | 70 ++-- 7 files changed, 395 insertions(+), 338 deletions(-) diff --git a/src/app/core/_components/tables/agents-table/agents-table.component.ts b/src/app/core/_components/tables/agents-table/agents-table.component.ts index e2d4a651..0d2462fd 100644 --- a/src/app/core/_components/tables/agents-table/agents-table.component.ts +++ b/src/app/core/_components/tables/agents-table/agents-table.component.ts @@ -1,53 +1,57 @@ /* eslint-disable @angular-eslint/component-selector */ -import { Component, OnDestroy, OnInit } from "@angular/core"; -import { formatUnixTimestamp } from "src/app/shared/utils/datetime"; -import { SafeHtml } from "@angular/platform-browser"; -import { catchError, forkJoin } from "rxjs"; -import { HTTableColumn } from "../ht-table/ht-table.models"; -import { BaseTableComponent } from "../base-table/base-table.component"; -import { ChunkData } from "src/app/core/_models/chunk.model"; -import { AgentsTableColumnLabel } from "./agents-table.constants"; -import { Agent } from "src/app/core/_models/agent.model"; -import { DialogData } from "../table-dialog/table-dialog.model"; -import { TableDialogComponent } from "../table-dialog/table-dialog.component"; -import { RowActionMenuAction } from "../../menus/row-action-menu/row-action-menu.constants"; -import { BulkActionMenuAction } from "../../menus/bulk-action-menu/bulk-action-menu.constants"; -import { ActionMenuEvent } from "../../menus/action-menu/action-menu.model"; -import { ExportMenuAction } from "../../menus/export-menu/export-menu.constants"; -import { SERV } from "src/app/core/_services/main.config"; -import { AgentsDataSource } from "src/app/core/_datasources/agents.datasource"; -import { Cacheable } from "src/app/core/_decorators/cacheable"; - +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { HTTableColumn, HTTableRouterLink } from '../ht-table/ht-table.models'; +import { catchError, forkJoin } from 'rxjs'; + +import { AccessGroup } from 'src/app/core/_models/access-group.model'; +import { ActionMenuEvent } from '../../menus/action-menu/action-menu.model'; +import { Agent } from 'src/app/core/_models/agent.model'; +import { AgentsDataSource } from 'src/app/core/_datasources/agents.datasource'; +import { AgentsTableColumnLabel } from './agents-table.constants'; +import { BaseTableComponent } from '../base-table/base-table.component'; +import { BulkActionMenuAction } from '../../menus/bulk-action-menu/bulk-action-menu.constants'; +import { Cacheable } from 'src/app/core/_decorators/cacheable'; +import { ChunkData } from 'src/app/core/_models/chunk.model'; +import { DialogData } from '../table-dialog/table-dialog.model'; +import { ExportMenuAction } from '../../menus/export-menu/export-menu.constants'; +import { RowActionMenuAction } from '../../menus/row-action-menu/row-action-menu.constants'; +import { SERV } from 'src/app/core/_services/main.config'; +import { SafeHtml } from '@angular/platform-browser'; +import { TableDialogComponent } from '../table-dialog/table-dialog.component'; +import { formatUnixTimestamp } from 'src/app/shared/utils/datetime'; @Component({ selector: 'agents-table', templateUrl: './agents-table.component.html' }) - -export class AgentsTableComponent extends BaseTableComponent implements OnInit, OnDestroy { - - tableColumns: HTTableColumn[] = [] +export class AgentsTableComponent + extends BaseTableComponent + implements OnInit, OnDestroy +{ + tableColumns: HTTableColumn[] = []; dataSource: AgentsDataSource; - chunkData: { [key: number]: ChunkData } = {} + chunkData: { [key: number]: ChunkData } = {}; ngOnDestroy(): void { for (const sub of this.subscriptions) { - sub.unsubscribe() + sub.unsubscribe(); } } ngOnInit(): void { - this.tableColumns = this.getColumns() + this.tableColumns = this.getColumns(); this.dataSource = new AgentsDataSource(this.gs, this.uiService); this.dataSource.setColumns(this.tableColumns); this.dataSource.loadAll(); } filter(item: Agent, filterValue: string): boolean { - if (item.agentName.toLowerCase().includes(filterValue) || + if ( + item.agentName.toLowerCase().includes(filterValue) || item.clientSignature.toLowerCase().includes(filterValue) || - item.devices.toLowerCase().includes(filterValue)) { - return true + item.devices.toLowerCase().includes(filterValue) + ) { + return true; } return false; @@ -66,12 +70,16 @@ export class AgentsTableComponent extends BaseTableComponent implements OnInit, dataKey: 'status', render: (agent: Agent) => this.renderStatus(agent), isSortable: true, - export: async (agent: Agent) => agent.isActive ? 'Active' : 'Inactive' + export: async (agent: Agent) => (agent.isActive ? 'Active' : 'Inactive') }, { name: AgentsTableColumnLabel.NAME, dataKey: 'agentName', - routerLink: (agent: Agent) => ['/agents', 'show-agents', agent._id, 'edit'], + routerLink: (agent: Agent) => [ + { + routerLink: ['/agents', 'show-agents', agent._id, 'edit'] + } + ], isSortable: true, export: async (agent: Agent) => agent.agentName }, @@ -79,37 +87,55 @@ export class AgentsTableComponent extends BaseTableComponent implements OnInit, name: AgentsTableColumnLabel.USER, dataKey: 'userId', render: (agent: Agent) => this.renderOwner(agent), - routerLink: (agent: Agent) => agent.userId ? ['/users', agent.userId, 'edit'] : [], + routerLink: (agent: Agent) => [ + { + routerLink: agent.userId ? ['/users', agent.userId, 'edit'] : [] + } + ], isSortable: true, - export: async (agent: Agent) => agent.user ? agent.user.name : '' + export: async (agent: Agent) => (agent.user ? agent.user.name : '') }, { name: AgentsTableColumnLabel.CLIENT, dataKey: 'clientSignature', render: (agent: Agent) => this.renderClient(agent), isSortable: true, - export: async (agent: Agent) => agent.clientSignature ? agent.clientSignature : '' + export: async (agent: Agent) => + agent.clientSignature ? agent.clientSignature : '' }, { name: AgentsTableColumnLabel.CURRENT_TASK, - dataKey: 'taskId', - render: (agent: Agent) => this.renderCurrentTask(agent), + dataKey: 'taskName', + routerLink: (agent: Agent) => [ + { + routerLink: agent.task + ? ['/tasks', 'show-tasks', agent.task._id, 'edit'] + : [] + } + ], isSortable: true, - export: async (agent: Agent) => agent.task ? agent.task.taskName : '' + export: async (agent: Agent) => (agent.task ? agent.task.taskName : '') }, { name: AgentsTableColumnLabel.TASK_SPEED, dataKey: 'taskId', async: (agent: Agent) => this.renderCurrentSpeed(agent), isSortable: false, - export: async (agent: Agent) => await this.getSpeed(agent) + '' + export: async (agent: Agent) => (await this.getSpeed(agent)) + '' }, { name: AgentsTableColumnLabel.CURRENT_CHUNK, dataKey: 'chunkId', - render: (agent: Agent) => this.renderCurrentChunk(agent), + routerLink: (agent: Agent) => [ + { + routerLink: agent.chunk + ? ['/tasks', 'chunks', agent.chunk._id, 'view'] + : [] + } + ], isSortable: true, - export: async (agent: Agent) => agent.chunk ? agent.chunk._id + '' : '' + export: async (agent: Agent) => + agent.chunk ? agent.chunk._id + '' : '' }, { name: AgentsTableColumnLabel.GPUS_CPUS, @@ -122,84 +148,75 @@ export class AgentsTableComponent extends BaseTableComponent implements OnInit, dataKey: 'lastTime', render: (agent: Agent) => this.renderLastActivity(agent), isSortable: true, - export: async (agent: Agent) => formatUnixTimestamp(agent.lastTime, this.dateFormat) + export: async (agent: Agent) => + formatUnixTimestamp(agent.lastTime, this.dateFormat) }, { name: AgentsTableColumnLabel.ACCESS_GROUP, dataKey: 'accessGroupId', - render: (agent: Agent) => this.renderAccessGroup(agent), + routerLink: (agent: Agent) => this.getAccessGroupRouterLinks(agent), isSortable: true, - export: async (agent: Agent) => agent.accessGroups.map((item) => item.groupName).join(', ') + export: async (agent: Agent) => + agent.accessGroups.map((item) => item.groupName).join(', ') } - ] + ]; - return tableColumns + return tableColumns; } - openDialog(data: DialogData) { const dialogRef = this.dialog.open(TableDialogComponent, { data: data, - width: '450px', + width: '450px' }); - this.subscriptions.push(dialogRef.afterClosed().subscribe(result => { - if (result && result.action) { - switch (result.action) { - case RowActionMenuAction.DELETE: - this.rowActionDelete(result.data); - break; - case BulkActionMenuAction.ACTIVATE: - this.bulkActionActivate(result.data, true); - break; - case BulkActionMenuAction.DEACTIVATE: - this.bulkActionActivate(result.data, false); - break; - case BulkActionMenuAction.DELETE: - this.bulkActionDelete(result.data); - break; + this.subscriptions.push( + dialogRef.afterClosed().subscribe((result) => { + if (result && result.action) { + switch (result.action) { + case RowActionMenuAction.DELETE: + this.rowActionDelete(result.data); + break; + case BulkActionMenuAction.ACTIVATE: + this.bulkActionActivate(result.data, true); + break; + case BulkActionMenuAction.DEACTIVATE: + this.bulkActionActivate(result.data, false); + break; + case BulkActionMenuAction.DELETE: + this.bulkActionDelete(result.data); + break; + } } - } - })); + }) + ); } // --- Render functions --- - @Cacheable(['_id', 'agentName', 'isTrusted']) renderName(agent: Agent): SafeHtml { - const agentName = agent.agentName?.length > 40 - ? `${agent.agentName.substring(40)}...` - : agent.agentName + const agentName = + agent.agentName?.length > 40 + ? `${agent.agentName.substring(40)}...` + : agent.agentName; const isTrusted = agent.isTrusted ? '' - : '' + : ''; - return this.sanitize(`${agentName}${isTrusted}`) - } - - - @Cacheable(['_id', 'taskId']) - renderCurrentTask(agent: Agent): SafeHtml { - let html = '-' - if (agent.task) { - const taskName = agent.task.taskName?.length > 40 - ? `${agent.task.taskName.substring(40)}...` - : agent.task.taskName - - html = `${taskName}` - } - return this.sanitize(html) + return this.sanitize( + `${agentName}${isTrusted}` + ); } @Cacheable(['_id', 'taskId']) async renderCurrentSpeed(agent: Agent): Promise { - let html = '-' - const speed = await this.getSpeed(agent) + let html = '-'; + const speed = await this.getSpeed(agent); if (speed) { - html = `${speed} H/s` + html = `${speed} H/s`; } - return this.sanitize(html) + return this.sanitize(html); } private async getSpeed(agent: Agent): Promise { @@ -207,90 +224,94 @@ export class AgentsTableComponent extends BaseTableComponent implements OnInit, this.chunkData[agent._id] = await this.dataSource.getChunkData(agent._id); } if (this.chunkData[agent._id].speed) { - return this.chunkData[agent._id].speed - } - - return 0 - } - - @Cacheable(['_id', 'chunk']) - renderCurrentChunk(agent: Agent): SafeHtml { - let html = '-' - if (agent.chunk) { - html = `${agent.chunk._id}` + return this.chunkData[agent._id].speed; } - return this.sanitize(html) + return 0; } @Cacheable(['_id', 'isActive']) renderStatus(agent: Agent): SafeHtml { - let html: string + let html: string; if (agent.isActive) { - html = 'Active' + html = 'Active'; } else { - html = 'Inactive' + html = 'Inactive'; } - return this.sanitize(html) + return this.sanitize(html); } @Cacheable(['_id', 'accessGroupId']) - renderAccessGroup(agent: Agent): SafeHtml { - const links: string[] = [] - - for (const group of agent.accessGroups) { - links.push(`${group.groupName}`) - if (agent.accessGroups.length > 1) { - links.push('
') + getAccessGroupRouterLinks(agent: Agent): HTTableRouterLink[] { + const links: HTTableRouterLink[] = agent.accessGroups.map( + (accessGroup: AccessGroup) => { + return { + routerLink: [ + '/users', + 'access-groups', + accessGroup.accessGroupId, + 'edit' + ], + label: accessGroup.groupName + }; } - } - - return this.sanitize(links.join('\n')) + ); + return links; } @Cacheable(['_id', 'userId']) renderOwner(agent: Agent): SafeHtml { if (agent.user) { - return this.sanitize(agent.user.name) + return this.sanitize(agent.user.name); } - return '' + return ''; } @Cacheable(['_id', 'clientSignature']) renderClient(agent: Agent): SafeHtml { if (agent.clientSignature) { - return agent.clientSignature + return agent.clientSignature; } - return '' + return ''; } - @Cacheable(['_id', 'lastTime']) renderLastActivity(agent: Agent): SafeHtml { - const formattedDate = formatUnixTimestamp(agent.lastTime, this.dateFormat) - const data = `${agent.lastAct} at
${formattedDate}
IP:${agent.lastIp}` - + const formattedDate = formatUnixTimestamp(agent.lastTime, this.dateFormat); + const data = `${agent.lastAct} at
${formattedDate}
IP:${agent.lastIp}`; - return this.sanitize(data) + return this.sanitize(data); } - // --- Action functions --- exportActionClicked(event: ActionMenuEvent): void { switch (event.menuItem.action) { case ExportMenuAction.EXCEL: - this.exportService.toExcel('hashtopolis-agents', this.tableColumns, event.data) + this.exportService.toExcel( + 'hashtopolis-agents', + this.tableColumns, + event.data + ); break; case ExportMenuAction.CSV: - this.exportService.toCsv('hashtopolis-agents', this.tableColumns, event.data) + this.exportService.toCsv( + 'hashtopolis-agents', + this.tableColumns, + event.data + ); break; case ExportMenuAction.COPY: - this.exportService.toClipboard(this.tableColumns, event.data).then(() => { - this.snackBar.open('The selected rows are copied to the clipboard', 'Close') - }) + this.exportService + .toClipboard(this.tableColumns, event.data) + .then(() => { + this.snackBar.open( + 'The selected rows are copied to the clipboard', + 'Close' + ); + }); break; } } @@ -308,7 +329,7 @@ export class AgentsTableComponent extends BaseTableComponent implements OnInit, body: `Are you sure you want to delete ${event.data.agentName}? Note that this action cannot be undone.`, warn: true, action: event.menuItem.action - }) + }); break; } } @@ -322,7 +343,7 @@ export class AgentsTableComponent extends BaseTableComponent implements OnInit, icon: 'info', listAttribute: 'agentName', action: event.menuItem.action - }) + }); break; case BulkActionMenuAction.DEACTIVATE: this.openDialog({ @@ -331,7 +352,7 @@ export class AgentsTableComponent extends BaseTableComponent implements OnInit, icon: 'info', listAttribute: 'agentName', action: event.menuItem.action - }) + }); break; case BulkActionMenuAction.DELETE: this.openDialog({ @@ -342,57 +363,69 @@ export class AgentsTableComponent extends BaseTableComponent implements OnInit, warn: true, listAttribute: 'agentName', action: event.menuItem.action - }) + }); break; } } private bulkActionActivate(agents: Agent[], isActive: boolean): void { const requests = agents.map((agent: Agent) => { - return this.gs.update(SERV.AGENTS, agent._id, { isActive: isActive }) + return this.gs.update(SERV.AGENTS, agent._id, { isActive: isActive }); }); - const action = isActive ? 'activated' : 'deactivated' - - this.subscriptions.push(forkJoin(requests) - .pipe( - catchError((error) => { - console.error('Error during activation:', error); - return []; + const action = isActive ? 'activated' : 'deactivated'; + + this.subscriptions.push( + forkJoin(requests) + .pipe( + catchError((error) => { + console.error('Error during activation:', error); + return []; + }) + ) + .subscribe((results) => { + this.snackBar.open( + `Successfully ${action} ${results.length} agents!`, + 'Close' + ); + this.dataSource.reload(); }) - ) - .subscribe((results) => { - this.snackBar.open(`Successfully ${action} ${results.length} agents!`, 'Close'); - this.dataSource.reload() - })); + ); } private bulkActionDelete(agents: Agent[]): void { const requests = agents.map((agent: Agent) => { - return this.gs.delete(SERV.AGENTS, agent._id) + return this.gs.delete(SERV.AGENTS, agent._id); }); - this.subscriptions.push(forkJoin(requests) - .pipe( - catchError((error) => { - console.error('Error during deletion:', error); - return []; + this.subscriptions.push( + forkJoin(requests) + .pipe( + catchError((error) => { + console.error('Error during deletion:', error); + return []; + }) + ) + .subscribe((results) => { + this.snackBar.open( + `Successfully deleted ${results.length} agents!`, + 'Close' + ); + this.dataSource.reload(); }) - ) - .subscribe((results) => { - this.snackBar.open(`Successfully deleted ${results.length} agents!`, 'Close'); - this.dataSource.reload() - })); + ); } private rowActionDelete(agent: Agent): void { - this.subscriptions.push(this.gs.delete(SERV.AGENTS, agent._id).subscribe(() => { - this.snackBar.open('Successfully deleted agent!', 'Close'); - this.dataSource.reload() - })); + this.subscriptions.push( + this.gs.delete(SERV.AGENTS, agent._id).subscribe(() => { + this.snackBar.open('Successfully deleted agent!', 'Close'); + this.dataSource.reload(); + }) + ); } private rowActionEdit(agent: Agent): void { this.router.navigate(['agents', 'show-agents', agent._id, 'edit']); } -} \ No newline at end of file +} diff --git a/src/app/core/_components/tables/agents-table/agents-table.constants.ts b/src/app/core/_components/tables/agents-table/agents-table.constants.ts index 5c178927..a07713c3 100644 --- a/src/app/core/_components/tables/agents-table/agents-table.constants.ts +++ b/src/app/core/_components/tables/agents-table/agents-table.constants.ts @@ -9,5 +9,5 @@ export const AgentsTableColumnLabel = { ACCESS_GROUP: 'Access Group', CURRENT_TASK: 'Task', CURRENT_CHUNK: 'Chunk', - TASK_SPEED: 'Speed', -} \ No newline at end of file + TASK_SPEED: 'Speed' +}; 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 bd3cc886..4d30752d 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 @@ -102,20 +102,16 @@ - - - - - - - - - - - - {{ element[tableColumn.dataKey] }} - - + + + + {{ link.label }} + + + {{ element[tableColumn.dataKey] }} + + + diff --git a/src/app/core/_components/tables/ht-table/ht-table.models.ts b/src/app/core/_components/tables/ht-table/ht-table.models.ts index 170db967..39d362fc 100644 --- a/src/app/core/_components/tables/ht-table/ht-table.models.ts +++ b/src/app/core/_components/tables/ht-table/ht-table.models.ts @@ -1,21 +1,26 @@ -import { SafeHtml } from "@angular/platform-browser" +import { SafeHtml } from '@angular/platform-browser'; -export type DataType = 'agents' | 'tasks' | 'chunks' +export type DataType = 'agents' | 'tasks' | 'chunks'; export interface HTTableIcon { - name: string - tooltip?: string - cls?: string + name: string; + tooltip?: string; + cls?: string; +} + +export interface HTTableRouterLink { + label?: string; + routerLink: any[]; } export interface HTTableColumn { - name: string - dataKey: string - position?: 'right' | 'left' - isSortable?: boolean - icons?: (data: any) => Promise - render?: (data: any) => SafeHtml - async?: (data: any) => Promise - routerLink?: (data: any) => any[], - export?: (data: any) => Promise -} \ No newline at end of file + name: string; + dataKey: string; + position?: 'right' | 'left'; + isSortable?: boolean; + icons?: (data: any) => Promise; + render?: (data: any) => SafeHtml; + async?: (data: any) => Promise; + routerLink?: (data: any) => HTTableRouterLink[]; + export?: (data: any) => Promise; +} diff --git a/src/app/core/_datasources/agents.datasource.ts b/src/app/core/_datasources/agents.datasource.ts index b12595a7..a7605247 100644 --- a/src/app/core/_datasources/agents.datasource.ts +++ b/src/app/core/_datasources/agents.datasource.ts @@ -1,58 +1,63 @@ -import { catchError, finalize, firstValueFrom, forkJoin, of } from "rxjs"; -import { SERV } from "../_services/main.config"; -import { BaseDataSource } from "./base.datasource"; -import { User } from "../_models/user.model"; -import { environment } from "src/environments/environment"; -import { ListResponseWrapper } from "../_models/response.model"; -import { Agent } from "../_models/agent.model"; -import { Chunk, ChunkData } from "../_models/chunk.model"; -import { Task } from "../_models/task.model"; +import { Chunk, ChunkData } from '../_models/chunk.model'; +import { catchError, finalize, firstValueFrom, forkJoin, of } from 'rxjs'; +import { Agent } from '../_models/agent.model'; +import { BaseDataSource } from './base.datasource'; +import { ListResponseWrapper } from '../_models/response.model'; +import { SERV } from '../_services/main.config'; +import { Task } from '../_models/task.model'; +import { User } from '../_models/user.model'; +import { environment } from 'src/environments/environment'; export class AgentsDataSource extends BaseDataSource { - loadAll(): void { this.loadingSubject.next(true); - const agentParams = { maxResults: 999999, expand: 'accessGroups' } - const params = { maxResults: 999999 } - const agents$ = this.service.getAll(SERV.AGENTS, agentParams) - const users$ = this.service.getAll(SERV.USERS, params) - const agentAssign$ = this.service.getAll(SERV.AGENT_ASSIGN, params) - const tasks$ = this.service.getAll(SERV.TASKS, params) - const chunks$ = this.service.getAll(SERV.CHUNKS, params) - - forkJoin([agents$, users$, agentAssign$, tasks$, chunks$]).pipe( - catchError(() => of([])), - finalize(() => this.loadingSubject.next(false)) - ).subscribe(([a, u, aa, t, c]: [ - ListResponseWrapper, - ListResponseWrapper, - ListResponseWrapper, - ListResponseWrapper, - ListResponseWrapper - ]) => { - - const agents: Agent[] = a.values - const users: User[] = u.values - const assignments: any[] = aa.values - const tasks: Task[] = t.values - const chunks: Chunk[] = c.values - - agents.map((agent: Agent) => { - agent.user = users.find((e: User) => e._id === agent.userId) - agent.taskId = assignments.find(e => e.agentId === agent._id)?.taskId - if (agent.taskId) { - agent.task = tasks.find(e => e._id === agent.taskId) - agent.chunk = chunks.find(e => e.agentId === agent.agentId) + const agentParams = { maxResults: 999999, expand: 'accessGroups' }; + const params = { maxResults: 999999 }; + const agents$ = this.service.getAll(SERV.AGENTS, agentParams); + const users$ = this.service.getAll(SERV.USERS, params); + const agentAssign$ = this.service.getAll(SERV.AGENT_ASSIGN, params); + const tasks$ = this.service.getAll(SERV.TASKS, params); + const chunks$ = this.service.getAll(SERV.CHUNKS, params); + + forkJoin([agents$, users$, agentAssign$, tasks$, chunks$]) + .pipe( + catchError(() => of([])), + finalize(() => this.loadingSubject.next(false)) + ) + .subscribe( + ([a, u, aa, t, c]: [ + ListResponseWrapper, + ListResponseWrapper, + ListResponseWrapper, + ListResponseWrapper, + ListResponseWrapper + ]) => { + const agents: Agent[] = a.values; + const users: User[] = u.values; + const assignments: any[] = aa.values; + const tasks: Task[] = t.values; + const chunks: Chunk[] = c.values; + + agents.map((agent: Agent) => { + agent.user = users.find((e: User) => e._id === agent.userId); + agent.taskId = assignments.find((e) => e.agentId === agent._id) + ?.taskId; + if (agent.taskId) { + agent.task = tasks.find((e) => e._id === agent.taskId); + agent.taskName = agent.task.taskName; + agent.chunk = chunks.find((e) => e.agentId === agent.agentId); + agent.chunkId = agent.chunk._id; + } + + return agent; + }); + + this.setPaginationConfig(this.pageSize, this.currentPage, a.total); + this.setData(agents); } - - return agent; - }) - - this.setPaginationConfig(this.pageSize, this.currentPage, a.total); - this.setData(agents) - }) + ); } async getChunkData(id: number): Promise { @@ -63,22 +68,28 @@ export class AgentsDataSource extends BaseDataSource { const searched: number[] = []; const cracked: number[] = []; const speed: number[] = []; - const now = Date.now() + const now = Date.now(); const params = { maxResults: maxResults, filter: `agentId=${id}` }; - const response: ListResponseWrapper = await firstValueFrom(this.service.getAll(SERV.CHUNKS, params)) + const response: ListResponseWrapper = await firstValueFrom( + this.service.getAll(SERV.CHUNKS, params) + ); for (const chunk of response.values) { if (chunk.progress >= 10000) { dispatched.push(chunk.length); } - cracked.push(chunk.cracked) + cracked.push(chunk.cracked); searched.push(chunk.checkpoint - chunk.skip); - if (now / 1000 - Math.max(chunk.solveTime, chunk.dispatchTime) < chunktime && chunk.progress < 10000) { + if ( + now / 1000 - Math.max(chunk.solveTime, chunk.dispatchTime) < + chunktime && + chunk.progress < 10000 + ) { speed.push(chunk.speed); } } @@ -88,11 +99,11 @@ export class AgentsDataSource extends BaseDataSource { searched: 0, cracked: cracked.reduce((a, i) => a + i, 0), speed: speed.reduce((a, i) => a + i, 0) - } + }; } reload(): void { - this.reset() - this.loadAll() + this.reset(); + this.loadAll(); } -} \ No newline at end of file +} diff --git a/src/app/core/_models/agent.model.ts b/src/app/core/_models/agent.model.ts index bb48b372..54fcc493 100644 --- a/src/app/core/_models/agent.model.ts +++ b/src/app/core/_models/agent.model.ts @@ -1,67 +1,71 @@ -import { AccessGroup } from './access-group.model' -import { User } from './user.model' -import { Task } from './task.model' -import { Chunk } from './chunk.model' +import { AccessGroup } from './access-group.model'; +import { Chunk } from './chunk.model'; +import { Task } from './task.model'; +import { User } from './user.model'; export interface AgentStats { - _id: number - _self: string - agentStatId: number - agentId: number - statType: number - time: number - value: number[] + _id: number; + _self: string; + agentStatId: number; + agentId: number; + statType: number; + time: number; + value: number[]; } export interface Agent { - _id?: number - _self?: string - agentId: number - agentName: string - uid: string - os: number - devices: string - cmdPars: string - ignoreErrors: number - isActive: boolean - isTrusted: boolean - token: string - lastAct: string - lastTime: number - lastIp: string - userId: number - user?: User - cpuOnly: number - clientSignature: string - agentstats?: AgentStats[] - accessGroups?: AccessGroup[] - taskId?: number - task?: Task - chunk?: Chunk + _id?: number; + _self?: string; + agentId: number; + agentName: string; + uid: string; + os: number; + devices: string; + cmdPars: string; + ignoreErrors: number; + isActive: boolean; + isTrusted: boolean; + token: string; + lastAct: string; + lastTime: number; + lastIp: string; + userId: number; + user?: User; + cpuOnly: number; + clientSignature: string; + agentstats?: AgentStats[]; + accessGroups?: AccessGroup[]; + task?: Task; + taskId?: number; + taskName?: string; + chunk?: Chunk; + chunkId?: number; } export interface IAgents { - _expandable?: string - startAt: number - maxResults: number - total: number - isLast: string - values: [{ - agentId: number - agentName: string - uid: string - os: number - devices: string - cmdPars: string - ignoreErrors: string - isActive: string - isTrusted: string - token: string - lastAct: string - lastTime: number - lastIp: string - userId: number - cpuOnly: number - clientSignature: string - }] + _expandable?: string; + startAt: number; + maxResults: number; + total: number; + isLast: string; + values: [ + { + agentId: number; + agentName: string; + uid: string; + os: number; + devices: string; + cmdPars: string; + ignoreErrors: string; + isActive: string; + isTrusted: string; + token: string; + lastAct: string; + lastTime: number; + lastIp: string; + userId: number; + cpuOnly: number; + clientSignature: string; + } + ]; } diff --git a/src/styles/components/_table.scss b/src/styles/components/_table.scss index 906a9962..6c047d63 100644 --- a/src/styles/components/_table.scss +++ b/src/styles/components/_table.scss @@ -7,12 +7,12 @@ 01- Main Style */ .table .thead-light th { - background-color: #F2F4F6; - color: #1F2937; + background-color: #f2f4f6; + color: #1f2937; } .table .thead-light th a { - color: #1F2937; + color: #1f2937; } .th { @@ -37,8 +37,7 @@ } // Overrule datatables color setting -table.dataTable tbody tr.selected>* { - +table.dataTable tbody tr.selected > * { // box-shadow: inset 0 0 0 9999px rgb(89, 143, 105) !important; a { color: rgb(246, 246, 246) !important; @@ -51,7 +50,7 @@ table.dataTable { padding-right: 12px; text-align: left; vertical-align: bottom; - font-size: .975rem; + font-size: 0.975rem; font-weight: 400; font-weight: normal; color: #999; @@ -62,7 +61,7 @@ table.dataTable { padding: 10px 10px; // vertical-align: top; text-align: left; - font-size: .930rem; + font-size: 0.93rem; font-weight: 400; font-weight: normal; } @@ -72,8 +71,8 @@ table.dataTable { } .checkmark { - --bs-badge-padding-x: 0.0em !important; - --bs-badge-padding-y: 0.0em !important; + --bs-badge-padding-x: 0em !important; + --bs-badge-padding-y: 0em !important; line-height: 1 !important; white-space: normal !important; } @@ -88,13 +87,13 @@ table.dataTable { display: none !important; } -.table.dataTable.table-hover>tbody>tr:hover>* { +.table.dataTable.table-hover > tbody > tr:hover > * { background-color: rgb(249, 249, 167); } .btn-actions { vertical-align: center; - font-size: .680rem; + font-size: 0.68rem; font-weight: 400; font-weight: normal; } @@ -104,7 +103,6 @@ div.dt-button-collection div.dt-button, div.dt-button-collection a.dt-button { position: relative; display: block !important; - ; padding: 0.5em 0 !important; margin: 4px 0 2px 0 !important; overflow-x: visible !important; @@ -123,7 +121,7 @@ div.dt-button-background:not(.disabled) { div.dt-button-collection { background-color: #dadada; color: #c71919 !important; - border-color: #4B5563 !important; + border-color: #4b5563 !important; padding: 4px 4px 2px !important; line-height: 1.6em !important; overflow-x: visible !important; @@ -140,18 +138,17 @@ div.dt-button-collection { margin-bottom: 0px !important; } -.table.dataTable.table-hover>tbody>tr:hover>* { +.table.dataTable.table-hover > tbody > tr:hover > * { background-color: rgb(117, 117, 114); } .btn-actions { vertical-align: center; - font-size: .680rem; + font-size: 0.68rem; font-weight: 400; font-weight: normal; } - div.dt-button-collection span { color: #ffffff; } @@ -161,11 +158,15 @@ div.dt-button, a.dt-button, input.dt-button { box-sizing: border-box; - border: 1px solid rgba(0, 0, 0, .3); + border: 1px solid rgba(0, 0, 0, 0.3); line-height: 1.6em; white-space: nowrap; overflow: hidden; - background: linear-gradient(to bottom, rgba(230, 230, 230, .1) 0%, rgba(0, 0, 0, .1) 100%); + background: linear-gradient( + to bottom, + rgba(230, 230, 230, 0.1) 0%, + rgba(0, 0, 0, 0.1) 100% + ); user-select: none; text-decoration: none; outline: none; @@ -178,7 +179,7 @@ input.dt-button { .paginate_button.disabled, .paginate_button.disabled:hover, .paginate_button.disabled:active { - display: none + display: none; } /* @@ -189,14 +190,14 @@ input.dt-button { 02- Pagination */ -.pagination>.active>a, -.pagination>.active>a:focus, -.pagination>.active>a:hover, -.pagination>.active>span, -.pagination>.active>span:focus, -.pagination>.active>span:hover { - background-color: #4B5563; - border-color: #4B5563; +.pagination > .active > a, +.pagination > .active > a:focus, +.pagination > .active > a:hover, +.pagination > .active > span, +.pagination > .active > span:focus, +.pagination > .active > span:hover { + background-color: #4b5563; + border-color: #4b5563; color: #e0e4ea; } @@ -212,7 +213,6 @@ input.dt-button { 02- End */ - table.hashtopolis-table { .mat-mdc-header-cell { background-color: $primary-800; @@ -234,14 +234,22 @@ table.hashtopolis-table { .mat-icon { margin-top: -3px; } - } + a { + display: block; + width: 100%; + border-bottom: 1px dotted $primary-300; + &:last-child { + border-bottom: none; + } + } + } } div.table-actions { display: flex; justify-content: space-between; - margin-bottom: .5rem; + margin-bottom: 0.5rem; .right-aligned { margin-left: auto; @@ -260,4 +268,4 @@ div.table-actions { .mat-mdc-paginator { background-color: var(--mat-toolbar-container-background-color); } -} \ No newline at end of file +} From f6873f1aee840d95608dc825a31cb5a08abf4fa4 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Fri, 10 Nov 2023 08:26:38 +0100 Subject: [PATCH 205/419] Add chunks datasource + update chunk model --- .../core/_datasources/chunks.datasource.ts | 49 ++++++++++++++++++ src/app/core/_models/chunk.model.ts | 50 ++++++++++--------- 2 files changed, 75 insertions(+), 24 deletions(-) create mode 100644 src/app/core/_datasources/chunks.datasource.ts diff --git a/src/app/core/_datasources/chunks.datasource.ts b/src/app/core/_datasources/chunks.datasource.ts new file mode 100644 index 00000000..892dc8f7 --- /dev/null +++ b/src/app/core/_datasources/chunks.datasource.ts @@ -0,0 +1,49 @@ +import { catchError, finalize, forkJoin, of } from 'rxjs'; + +import { Agent } from '../_models/agent.model'; +import { BaseDataSource } from './base.datasource'; +import { Chunk } from '../_models/chunk.model'; +import { ListResponseWrapper } from '../_models/response.model'; +import { SERV } from '../_services/main.config'; + +export class ChunksDataSource extends BaseDataSource { + loadAll(): void { + this.loadingSubject.next(true); + + const agentParams = { maxResults: 999999 }; + const chunkParams = { maxResults: 1000, expand: 'task' }; + const chunks$ = this.service.getAll(SERV.CHUNKS, chunkParams); + const agents$ = this.service.getAll(SERV.AGENTS, agentParams); + + forkJoin([chunks$, agents$]) + .pipe( + catchError(() => of([])), + finalize(() => this.loadingSubject.next(false)) + ) + .subscribe( + ([c, a]: [ListResponseWrapper, ListResponseWrapper]) => { + const assignedChunks: Chunk[] = c.values; + + assignedChunks.map((chunk: Chunk) => { + console.log(chunk._id); + chunk.agent = a.values.find((e: Agent) => e._id === chunk.agentId); + if (chunk.agent) { + chunk.agentName = chunk.agent.agentName; + } + if (chunk.task) { + chunk.taskName = chunk.task.taskName; + } + + return chunk; + }); + + this.setData(assignedChunks); + } + ); + } + + reload(): void { + this.reset(); + this.loadAll(); + } +} diff --git a/src/app/core/_models/chunk.model.ts b/src/app/core/_models/chunk.model.ts index 01ea3950..127aaf19 100644 --- a/src/app/core/_models/chunk.model.ts +++ b/src/app/core/_models/chunk.model.ts @@ -1,29 +1,31 @@ -import { Agent } from './agent.model' - +import { Agent } from './agent.model'; +import { Task } from './task.model'; export interface Chunk { - _id: number - _self: string - chunkId: number - taskId: number - task?: Task - format: string - skip: number - length: number - agentId: number - agent?: Agent - dispatchTime: number - solveTime: number - checkpoint: number - progress: number - state: number - cracked: number - speed: number + _id: number; + _self: string; + chunkId: number; + taskId: number; + taskName?: string; + task?: Task; + format: string; + skip: number; + length: number; + agentId: number; + agent?: Agent; + agentName?: string; + dispatchTime: number; + solveTime: number; + checkpoint: number; + progress: number; + state: number; + cracked: number; + speed: number; } export interface ChunkData { - dispatched: number - searched: number - cracked: number - speed: number -} \ No newline at end of file + dispatched: number; + searched: number; + cracked: number; + speed: number; +} From 460b0274412ba6cc69d8f7b5a375762465ae49d7 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Fri, 10 Nov 2023 08:27:30 +0100 Subject: [PATCH 206/419] Add utility function to format time --- src/app/shared/utils/datetime.ts | 45 ++++++++++++++++++++++++++++---- 1 file changed, 40 insertions(+), 5 deletions(-) diff --git a/src/app/shared/utils/datetime.ts b/src/app/shared/utils/datetime.ts index 7265fa3d..426b720c 100644 --- a/src/app/shared/utils/datetime.ts +++ b/src/app/shared/utils/datetime.ts @@ -11,7 +11,7 @@ export const unixTimestampInPast = (days: number): number => { const inPast = new Date(currentDate.getTime() - days * 24 * 60 * 60 * 1000); return Math.floor(inPast.getTime() / 1000); -} +}; /** * Formats a Unix timestamp into a date-time string using a custom format. @@ -33,11 +33,14 @@ export const unixTimestampInPast = (days: number): number => { * * @returns The formatted date-time string. */ -export function formatUnixTimestamp(unixTimestamp: number, fmt: string): string { +export function formatUnixTimestamp( + unixTimestamp: number, + fmt: string +): string { //return moment.unix(unixTimestamp).format(fmt) const date = new Date(unixTimestamp * 1000); - return formatDate(date, fmt) + return formatDate(date, fmt); } /** @@ -64,7 +67,8 @@ export function formatDate(date: Date, fmt: string): string { //return moment(date).format(fmt) const pad = (value: number) => (value < 10 ? `0${value}` : value.toString()); - return fmt.replace(/yyyy/g, date.getFullYear().toString()) + return fmt + .replace(/yyyy/g, date.getFullYear().toString()) .replace(/yy/g, date.getFullYear().toString().slice(-2)) .replace(/MM/g, pad(date.getMonth() + 1)) .replace(/M/g, (date.getMonth() + 1).toString()) @@ -76,4 +80,35 @@ export function formatDate(date: Date, fmt: string): string { .replace(/m/g, date.getMinutes().toString()) .replace(/ss/g, pad(date.getSeconds())) .replace(/s/g, date.getSeconds().toString()); -} \ No newline at end of file +} + +/** + * Formats a duration in seconds into a string representing days, hours, minutes, and seconds. + * Example output: "3 Days 04:15:30" + * + * @param seconds - The duration in seconds to format. + * @returns A formatted string representing the duration. + */ +export const formatSeconds = (seconds: number) => { + if (seconds < 1) { + return 'N/A'; + } + + const secondsInDay = 60 * 60 * 24; + let formatted = ''; + + if (seconds >= secondsInDay) { + const days = Math.floor(seconds / secondsInDay); + const dayLabel = days === 1 ? ' Day ' : ' Days '; + const daysFormatted = `${days}${dayLabel}`; + + seconds = seconds - days * secondsInDay; // Remaining Time + formatted += daysFormatted; + } + + const date = new Date(seconds * 1000); + const timeString = date.toISOString().slice(11, 19); + + formatted += timeString; + return formatted; +}; From cc6f41b3f81c1a1db489c179824a6c5663373447 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Fri, 10 Nov 2023 08:28:50 +0100 Subject: [PATCH 207/419] format --- src/app/core/_datasources/base.datasource.ts | 71 ++++++++++++-------- 1 file changed, 44 insertions(+), 27 deletions(-) diff --git a/src/app/core/_datasources/base.datasource.ts b/src/app/core/_datasources/base.datasource.ts index 73d6e642..9333c930 100644 --- a/src/app/core/_datasources/base.datasource.ts +++ b/src/app/core/_datasources/base.datasource.ts @@ -1,11 +1,12 @@ -import { CollectionViewer, DataSource } from "@angular/cdk/collections"; -import { BehaviorSubject, Observable } from "rxjs"; -import { GlobalService } from "../_services/main.service"; -import { MatTableDataSourcePaginator } from "@angular/material/table"; -import { MatSort } from "@angular/material/sort"; +import { BehaviorSubject, Observable } from 'rxjs'; +import { CollectionViewer, DataSource } from '@angular/cdk/collections'; + +import { GlobalService } from '../_services/main.service'; +import { HTTableColumn } from '../_components/tables/ht-table/ht-table.models'; +import { MatSort } from '@angular/material/sort'; +import { MatTableDataSourcePaginator } from '@angular/material/table'; import { SelectionModel } from '@angular/cdk/collections'; -import { UIConfigService } from "../_services/shared/storage.service"; -import { HTTableColumn } from "../_components/tables/ht-table/ht-table.models"; +import { UIConfigService } from '../_services/shared/storage.service'; /** * BaseDataSource is an abstract class for implementing data sources @@ -15,15 +16,17 @@ import { HTTableColumn } from "../_components/tables/ht-table/ht-table.models"; * @template T - The type of data that the data source holds. * @template P - The type of paginator, extending MatTableDataSourcePaginator. */ -export abstract class BaseDataSource implements DataSource { - - +export abstract class BaseDataSource< + T, + P extends MatTableDataSourcePaginator = MatTableDataSourcePaginator +> implements DataSource +{ public pageSize = 10; public currentPage = 0; public totalItems = 0; /** - * Copy of the original dataSubject data used for filtering + * Copy of the original dataSubject data used for filtering */ private originalData: T[] = []; @@ -55,20 +58,22 @@ export abstract class BaseDataSource mapping.name === this.sort.active); + const columnMapping = this.columns.find( + (mapping) => mapping.name === this.sort.active + ); if (!columnMapping) { console.error('Column mapping not found for label: ' + this.sort.active); @@ -132,7 +139,7 @@ export abstract class BaseDataSource { return this.compare(a[property], b[property], isAsc); - }) + }); this.dataSubject.next(sortedData); } @@ -147,7 +154,9 @@ export abstract class BaseDataSource filterFn(item, filterValue)); + const filteredData = this.originalData.filter((item) => + filterFn(item, filterValue) + ); this.dataSubject.next(filteredData); } @@ -156,7 +165,11 @@ export abstract class BaseDataSource Date: Fri, 10 Nov 2023 10:42:13 +0100 Subject: [PATCH 208/419] include core components --- src/app/tasks/tasks.module.ts | 57 +++++++++++++++++------------------ 1 file changed, 28 insertions(+), 29 deletions(-) diff --git a/src/app/tasks/tasks.module.ts b/src/app/tasks/tasks.module.ts index 48c58b51..fc31e71a 100644 --- a/src/app/tasks/tasks.module.ts +++ b/src/app/tasks/tasks.module.ts @@ -1,32 +1,33 @@ -import { FontAwesomeModule } from "@fortawesome/angular-fontawesome"; -import { FormsModule, ReactiveFormsModule } from "@angular/forms"; -import { NgbModule } from "@ng-bootstrap/ng-bootstrap"; -import { DataTablesModule } from "angular-datatables"; -import { CommonModule } from "@angular/common"; -import { RouterModule } from "@angular/router"; -import { NgModule } from "@angular/core"; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { ApplyHashlistComponent } from './supertasks/applyhashlist.component'; +import { ChunksComponent } from './chunks/chunks.component'; +import { CommonModule } from '@angular/common'; +import { ComponentsModule } from '../shared/components.module'; +import { CoreComponentsModule } from '../core/_components/core-components.module'; +import { DataTablesModule } from 'angular-datatables'; import { EditPreconfiguredTasksComponent } from './edit-preconfigured-tasks/edit-preconfigured-tasks.component'; -import { NewPreconfiguredTasksComponent } from "./new-preconfigured-tasks/new-preconfigured-tasks.component"; -import { PreconfiguredTasksComponent } from "./preconfigured-tasks/preconfigured-tasks.component"; -import { ModalSubtasksComponent } from './show-tasks/modal-subtasks/modal-subtasks.component'; -import { ModalPretasksComponent } from './supertasks/modal-pretasks/modal-pretasks.component'; import { EditSupertasksComponent } from './edit-supertasks/edit-supertasks.component'; -import { NewSupertasksComponent } from "./new-supertasks/new-supertasks.component"; -import { WrbulkComponent } from './import-supertasks/wrbulk/wrbulk.component'; -import { ApplyHashlistComponent } from "./supertasks/applyhashlist.component"; -import { MasksComponent } from './import-supertasks/masks/masks.component'; -import { SupertasksComponent } from "./supertasks/supertasks.component"; -import { ShowTasksComponent } from "./show-tasks/show-tasks.component"; import { EditTasksComponent } from './edit-tasks/edit-tasks.component'; -import { NewTasksComponent } from "./new-tasks/new-tasks.component"; -import { ComponentsModule } from "../shared/components.module"; -import { TasksRoutingModule } from "./tasks-routing.module"; -import { ChunksComponent } from "./chunks/chunks.component"; -import { PipesModule } from "../shared/pipes.module"; +import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; +import { MasksComponent } from './import-supertasks/masks/masks.component'; +import { ModalPretasksComponent } from './supertasks/modal-pretasks/modal-pretasks.component'; +import { ModalSubtasksComponent } from './show-tasks/modal-subtasks/modal-subtasks.component'; +import { NewPreconfiguredTasksComponent } from './new-preconfigured-tasks/new-preconfigured-tasks.component'; +import { NewSupertasksComponent } from './new-supertasks/new-supertasks.component'; +import { NewTasksComponent } from './new-tasks/new-tasks.component'; +import { NgModule } from '@angular/core'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { PipesModule } from '../shared/pipes.module'; +import { PreconfiguredTasksComponent } from './preconfigured-tasks/preconfigured-tasks.component'; +import { RouterModule } from '@angular/router'; +import { ShowTasksComponent } from './show-tasks/show-tasks.component'; +import { SupertasksComponent } from './supertasks/supertasks.component'; +import { TasksRoutingModule } from './tasks-routing.module'; +import { WrbulkComponent } from './import-supertasks/wrbulk/wrbulk.component'; @NgModule({ - declarations:[ + declarations: [ EditPreconfiguredTasksComponent, NewPreconfiguredTasksComponent, PreconfiguredTasksComponent, @@ -43,21 +44,19 @@ import { PipesModule } from "../shared/pipes.module"; WrbulkComponent, MasksComponent ], - imports:[ + imports: [ ReactiveFormsModule, TasksRoutingModule, FontAwesomeModule, DataTablesModule, ComponentsModule, + CoreComponentsModule, CommonModule, RouterModule, PipesModule, FormsModule, NgbModule - ], - exports: [ - ModalPretasksComponent, - ModalSubtasksComponent - ] + ], + exports: [ModalPretasksComponent, ModalSubtasksComponent] }) export class TasksModule {} From 861d91bf73b820f602ed694efeef2bc9de2e316f Mon Sep 17 00:00:00 2001 From: itbf9 Date: Fri, 10 Nov 2023 10:42:48 +0100 Subject: [PATCH 209/419] Add chunks table --- .../_components/core-components.module.ts | 64 ++--- .../chunks-table/chunks-table.component.html | 7 + .../chunks-table/chunks-table.component.ts | 225 ++++++++++++++++++ .../chunks-table/chunks-table.constants.ts | 14 ++ src/app/core/_constants/chunks.config.ts | 27 +++ src/app/core/_models/config-ui.model.ts | 34 ++- src/app/tasks/chunks/chunks.component.html | 58 +---- src/app/tasks/chunks/chunks.component.ts | 188 +-------------- 8 files changed, 340 insertions(+), 277 deletions(-) create mode 100644 src/app/core/_components/tables/chunks-table/chunks-table.component.html create mode 100644 src/app/core/_components/tables/chunks-table/chunks-table.component.ts create mode 100644 src/app/core/_components/tables/chunks-table/chunks-table.constants.ts create mode 100644 src/app/core/_constants/chunks.config.ts diff --git a/src/app/core/_components/core-components.module.ts b/src/app/core/_components/core-components.module.ts index c3edf4a3..d02d6db2 100644 --- a/src/app/core/_components/core-components.module.ts +++ b/src/app/core/_components/core-components.module.ts @@ -1,34 +1,38 @@ -import { CommonModule } from "@angular/common"; -import { NgModule } from "@angular/core"; -import { ReactiveFormsModule, FormsModule } from "@angular/forms"; -import { RouterModule } from "@angular/router"; -import { ActionMenuComponent } from "./menus/action-menu/action-menu.component"; -import { BaseMenuComponent } from "./menus/base-menu/base-menu.component"; -import { BulkActionMenuComponent } from "./menus/bulk-action-menu/bulk-action-menu.component"; -import { RowActionMenuComponent } from "./menus/row-action-menu/row-action-menu.component"; -import { TableDialogComponent } from "./tables/table-dialog/table-dialog.component"; -import { ColumnSelectionDialogComponent } from "./tables/column-selection-dialog/column-selection-dialog.component"; -import { MatButtonModule } from "@angular/material/button"; -import { MatDialogModule } from "@angular/material/dialog"; -import { MatFormFieldModule } from "@angular/material/form-field"; -import { MatPaginatorModule } from "@angular/material/paginator"; -import { MatSelectModule } from "@angular/material/select"; -import { MatSnackBarModule, MAT_SNACK_BAR_DEFAULT_OPTIONS } from "@angular/material/snack-bar"; -import { MatSortModule } from "@angular/material/sort"; -import { MatTableModule } from "@angular/material/table"; -import { MatTooltipModule } from "@angular/material/tooltip"; -import { MatInputModule } from '@angular/material/input'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { + MAT_SNACK_BAR_DEFAULT_OPTIONS, + MatSnackBarModule +} from '@angular/material/snack-bar'; + +import { ActionMenuComponent } from './menus/action-menu/action-menu.component'; +import { AgentsTableComponent } from './tables/agents-table/agents-table.component'; +import { BaseMenuComponent } from './menus/base-menu/base-menu.component'; +import { BaseTableComponent } from './tables/base-table/base-table.component'; +import { BulkActionMenuComponent } from './menus/bulk-action-menu/bulk-action-menu.component'; +import { ChunksTableComponent } from './tables/chunks-table/chunks-table.component'; +import { ColumnSelectionDialogComponent } from './tables/column-selection-dialog/column-selection-dialog.component'; +import { CommonModule } from '@angular/common'; +import { ExportMenuComponent } from './menus/export-menu/export-menu.component'; +import { HTTableComponent } from './tables/ht-table/ht-table.component'; +import { MatButtonModule } from '@angular/material/button'; import { MatCheckboxModule } from '@angular/material/checkbox'; +import { MatDialogModule } from '@angular/material/dialog'; +import { MatDividerModule } from '@angular/material/divider'; +import { MatFormFieldModule } from '@angular/material/form-field'; import { MatIconModule } from '@angular/material/icon'; +import { MatInputModule } from '@angular/material/input'; +import { MatListModule } from '@angular/material/list'; import { MatMenuModule } from '@angular/material/menu'; +import { MatPaginatorModule } from '@angular/material/paginator'; import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; -import { MatListModule } from '@angular/material/list'; -import { MatDividerModule } from '@angular/material/divider'; -import { ExportMenuComponent } from "./menus/export-menu/export-menu.component"; -import { BaseTableComponent } from "./tables/base-table/base-table.component"; -import { HTTableComponent } from "./tables/ht-table/ht-table.component"; -import { AgentsTableComponent } from "./tables/agents-table/agents-table.component"; - +import { MatSelectModule } from '@angular/material/select'; +import { MatSortModule } from '@angular/material/sort'; +import { MatTableModule } from '@angular/material/table'; +import { MatTooltipModule } from '@angular/material/tooltip'; +import { NgModule } from '@angular/core'; +import { RouterModule } from '@angular/router'; +import { RowActionMenuComponent } from './menus/row-action-menu/row-action-menu.component'; +import { TableDialogComponent } from './tables/table-dialog/table-dialog.component'; @NgModule({ declarations: [ @@ -42,6 +46,7 @@ import { AgentsTableComponent } from "./tables/agents-table/agents-table.compone ExportMenuComponent, ColumnSelectionDialogComponent, AgentsTableComponent, + ChunksTableComponent ], imports: [ ReactiveFormsModule, @@ -64,7 +69,7 @@ import { AgentsTableComponent } from "./tables/agents-table/agents-table.compone MatTooltipModule, MatDividerModule, RouterModule, - FormsModule, + FormsModule ], exports: [ BaseTableComponent, @@ -76,9 +81,10 @@ import { AgentsTableComponent } from "./tables/agents-table/agents-table.compone BulkActionMenuComponent, ExportMenuComponent, AgentsTableComponent, + ChunksTableComponent ], providers: [ { provide: MAT_SNACK_BAR_DEFAULT_OPTIONS, useValue: { duration: 2500 } } ] }) -export class CoreComponentsModule { } \ No newline at end of file +export class CoreComponentsModule {} diff --git a/src/app/core/_components/tables/chunks-table/chunks-table.component.html b/src/app/core/_components/tables/chunks-table/chunks-table.component.html new file mode 100644 index 00000000..400c48b8 --- /dev/null +++ b/src/app/core/_components/tables/chunks-table/chunks-table.component.html @@ -0,0 +1,7 @@ + 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 new file mode 100644 index 00000000..e989eabf --- /dev/null +++ b/src/app/core/_components/tables/chunks-table/chunks-table.component.ts @@ -0,0 +1,225 @@ +/* eslint-disable @angular-eslint/component-selector */ +import { + ChangeDetectionStrategy, + Component, + Input, + OnInit +} from '@angular/core'; +import { + formatSeconds, + formatUnixTimestamp +} from 'src/app/shared/utils/datetime'; + +import { BaseTableComponent } from '../base-table/base-table.component'; +import { Cacheable } from '../../../_decorators/cacheable'; +import { Chunk } from '../../../_models/chunk.model'; +import { ChunksDataSource } from '../../../_datasources/chunks.datasource'; +import { ChunksTableColumnLabel } from './chunks-table.constants'; +import { HTTableColumn } from '../../tables/ht-table/ht-table.models'; +import { SafeHtml } from '@angular/platform-browser'; +import { chunkStates } from '../../../_constants/chunks.config'; + +@Component({ + selector: 'chunks-table', + templateUrl: './chunks-table.component.html', + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class ChunksTableComponent extends BaseTableComponent implements OnInit { + // Input property to specify an agent ID for filtering chunks. + @Input() agentId: number; + + tableColumns: HTTableColumn[] = []; + dataSource: ChunksDataSource; + + ngOnInit(): void { + this.tableColumns = this.getColumns(); + this.dataSource = new ChunksDataSource(this.gs, this.uiService); + this.dataSource.setColumns(this.tableColumns); + this.dataSource.loadAll(); + } + + getColumns(): HTTableColumn[] { + const tableColumns = [ + { + name: ChunksTableColumnLabel.ID, + dataKey: '_id', + isSortable: true + }, + { + name: ChunksTableColumnLabel.START, + dataKey: 'skip', + isSortable: true + }, + { + name: ChunksTableColumnLabel.LENGTH, + dataKey: 'length', + isSortable: true + }, + { + name: ChunksTableColumnLabel.CHECKPOINT, + dataKey: 'checkpoint', + render: (chunk: Chunk) => this.renderCheckpoint(chunk), + isSortable: true + }, + { + name: ChunksTableColumnLabel.PROGRESS, + dataKey: 'progress', + render: (chunk: Chunk) => this.renderProgress(chunk), + isSortable: true + }, + { + name: ChunksTableColumnLabel.TASK, + dataKey: 'taskName', + routerLink: (chunk: Chunk) => [ + { + routerLink: ['/tasks', 'show-tasks', chunk.taskId, 'edit'] + } + ], + isSortable: true + }, + { + name: ChunksTableColumnLabel.AGENT, + dataKey: 'agentName', + render: (chunk: Chunk) => this.renderAgent(chunk), + routerLink: (chunk: Chunk) => [ + { + routerLink: ['/agents', 'show-agents', chunk.agentId, 'edit'] + } + ], + isSortable: true + }, + { + name: ChunksTableColumnLabel.DISPATCH_TIME, + dataKey: 'dispatchTime', + render: (chunk: Chunk) => this.renderDispatchTime(chunk), + isSortable: true + }, + { + name: ChunksTableColumnLabel.LAST_ACTIVITY, + dataKey: 'solveTime', + render: (chunk: Chunk) => this.renderLastActivity(chunk), + isSortable: true + }, + { + name: ChunksTableColumnLabel.TIME_SPENT, + dataKey: 'timeSpent', + render: (chunk: Chunk) => this.renderTimeSpent(chunk), + isSortable: true + }, + { + name: ChunksTableColumnLabel.STATE, + dataKey: 'state', + render: (chunk: Chunk) => this.renderState(chunk), + isSortable: true + }, + { + name: ChunksTableColumnLabel.CRACKED, + dataKey: 'cracked', + routerLink: (chunk: Chunk) => [ + { + routerLink: ['/hashlists', 'hashes', 'tasks', chunk.taskId] + } + ], + isSortable: true + } + ]; + + return tableColumns; + } + + // --- Render functions --- + + @Cacheable(['_id', 'cracked']) + renderCracked(chunk: Chunk): SafeHtml { + let html = `${chunk.cracked}`; + if (chunk.cracked && chunk.cracked > 0) { + html = `${chunk.cracked}`; + } + + return this.sanitize(html); + } + + @Cacheable(['_id', 'state']) + renderState(chunk: Chunk): SafeHtml { + let html = `${chunk.state}`; + if (chunk.state && chunk.state in chunkStates) { + html = `${chunkStates[chunk.state]}`; + } + + return this.sanitize(html); + } + + @Cacheable(['_id', 'solveTime', 'dispatchTime']) + renderTimeSpent(chunk: Chunk): SafeHtml { + const seconds = chunk.solveTime - chunk.dispatchTime; + if (seconds) { + return this.sanitize(formatSeconds(seconds)); + } + + return this.sanitize('0'); + } + + @Cacheable(['_id', 'progress', 'checkpoint', 'skip', 'length']) + renderCheckpoint(chunk: Chunk): SafeHtml { + const percent = chunk.progress + ? (((chunk.checkpoint - chunk.skip) / chunk.length) * 100).toFixed(2) + : 0; + const data = chunk.checkpoint ? `${chunk.checkpoint} (${percent}%)` : '0'; + + return this.sanitize(data); + } + + @Cacheable(['_id', 'progress']) + renderProgress(chunk: Chunk): SafeHtml { + if (chunk.progress === undefined) { + return this.sanitize('N/A'); + } else if (chunk.progress > 0) { + return this.sanitize(`${chunk.progress / 100}%`); + } + + return `${chunk.progress ? chunk.progress : 0}`; + } + + @Cacheable(['_id', 'taskId']) + renderTask(chunk: Chunk): SafeHtml { + if (chunk.task) { + return this.sanitize(chunk.task.taskName); + } + + return this.sanitize(`${chunk.taskId}`); + } + + @Cacheable(['_id', 'agentId']) + renderAgent(chunk: Chunk): SafeHtml { + if (chunk.agent) { + return this.sanitize(chunk.agent.agentName); + } + + return `${chunk.agentId}`; + } + + @Cacheable(['_id', 'dispatchTime']) + renderDispatchTime(chunk: Chunk): SafeHtml { + const formattedDate = formatUnixTimestamp( + chunk.dispatchTime, + this.dateFormat + ); + + return this.sanitize(formattedDate === '' ? 'N/A' : formattedDate); + } + + @Cacheable(['_id', 'solveTime']) + renderLastActivity(chunk: Chunk): SafeHtml { + if (chunk.solveTime === 0) { + return '(No activity)'; + } else if (chunk.solveTime > 0) { + return this.sanitize( + formatUnixTimestamp(chunk.solveTime, this.dateFormat) + ); + } + + return this.sanitize(`${chunk.solveTime}`); + } +} diff --git a/src/app/core/_components/tables/chunks-table/chunks-table.constants.ts b/src/app/core/_components/tables/chunks-table/chunks-table.constants.ts new file mode 100644 index 00000000..35b83307 --- /dev/null +++ b/src/app/core/_components/tables/chunks-table/chunks-table.constants.ts @@ -0,0 +1,14 @@ +export const ChunksTableColumnLabel = { + ID: 'ID', + START: 'Start', + LENGTH: 'Length', + CHECKPOINT: 'Checkpoint', + PROGRESS: 'Progress', + TASK: 'Task', + AGENT: 'Agent', + DISPATCH_TIME: 'Dispatch Time', + LAST_ACTIVITY: 'Last Activity', + TIME_SPENT: 'Time Spent', + STATE: 'State', + CRACKED: 'Cracked' +}; diff --git a/src/app/core/_constants/chunks.config.ts b/src/app/core/_constants/chunks.config.ts new file mode 100644 index 00000000..6c4fe13a --- /dev/null +++ b/src/app/core/_constants/chunks.config.ts @@ -0,0 +1,27 @@ +export const ChunkState = { + NEW: 'New', + INIT: 'Init', + RUNNING: 'Running', + PAUSED: 'Paused', + EXHAUSTED: 'Exhausted', + CRACKED: 'Cracked', + ABORTED: 'Aborted', + QUIT: 'Quit', + BYPASS: 'Bypass', + TRIMMED: 'Trimmed', + ABORTING: 'Aborting...' +}; + +export const chunkStates = [ + ChunkState.NEW, + ChunkState.INIT, + ChunkState.RUNNING, + ChunkState.PAUSED, + ChunkState.EXHAUSTED, + ChunkState.CRACKED, + ChunkState.ABORTED, + ChunkState.QUIT, + ChunkState.BYPASS, + ChunkState.TRIMMED, + ChunkState.ABORTING +]; diff --git a/src/app/core/_models/config-ui.model.ts b/src/app/core/_models/config-ui.model.ts index ba01a699..ec1195a1 100644 --- a/src/app/core/_models/config-ui.model.ts +++ b/src/app/core/_models/config-ui.model.ts @@ -1,19 +1,20 @@ -import { AgentsTableColumnLabel } from "../_components/tables/agents-table/agents-table.constants" +import { AgentsTableColumnLabel } from '../_components/tables/agents-table/agents-table.constants'; +import { ChunksTableColumnLabel } from '../_components/tables/chunks-table/chunks-table.constants'; -export type Layout = 'full' | 'fixed' -export type Theme = 'light' | 'dark' +export type Layout = 'full' | 'fixed'; +export type Theme = 'light' | 'dark'; export interface TableSettings { - [key: string]: string[] + [key: string]: string[]; } export interface UIConfig { - layout: Layout - theme: Theme - tableSettings: TableSettings - timefmt: string - refreshPage: boolean - refreshInterval: number + layout: Layout; + theme: Theme; + tableSettings: TableSettings; + timefmt: string; + refreshPage: boolean; + refreshInterval: number; } export const uiConfigDefault: UIConfig = { @@ -31,7 +32,18 @@ export const uiConfigDefault: UIConfig = { AgentsTableColumnLabel.LAST_ACTIVITY, AgentsTableColumnLabel.ACCESS_GROUP ], + chunksTable: [ + ChunksTableColumnLabel.ID, + ChunksTableColumnLabel.PROGRESS, + ChunksTableColumnLabel.TASK, + ChunksTableColumnLabel.AGENT, + ChunksTableColumnLabel.DISPATCH_TIME, + ChunksTableColumnLabel.LAST_ACTIVITY, + ChunksTableColumnLabel.TIME_SPENT, + ChunksTableColumnLabel.STATE, + ChunksTableColumnLabel.CRACKED + ] }, refreshPage: false, refreshInterval: 10 -} +}; diff --git a/src/app/tasks/chunks/chunks.component.html b/src/app/tasks/chunks/chunks.component.html index 25186e42..1bf8c483 100644 --- a/src/app/tasks/chunks/chunks.component.html +++ b/src/app/tasks/chunks/chunks.component.html @@ -1,58 +1,4 @@ -
-
- -
-
- -
-
- -
@@ -432,7 +432,7 @@ Other - +
- + diff --git a/src/app/hashlists/show-cracks/show-cracks.component.ts b/src/app/hashlists/show-cracks/show-cracks.component.ts index ffd76c13..7ebab750 100644 --- a/src/app/hashlists/show-cracks/show-cracks.component.ts +++ b/src/app/hashlists/show-cracks/show-cracks.component.ts @@ -1,46 +1,112 @@ +import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core'; import { environment } from './../../../environments/environment'; -import { Component, OnInit, ViewChild } from '@angular/core'; import { faPlus} from '@fortawesome/free-solid-svg-icons'; import { DataTableDirective } from 'angular-datatables'; -import Swal from 'sweetalert2/dist/sweetalert2.js'; -import { Subject } from 'rxjs'; +import { Subject, Subscription } from 'rxjs'; -import { UIConfigService } from 'src/app/core/_services/shared/storage.service'; +import { AutoTitleService } from 'src/app/core/_services/shared/autotitle.service'; import { GlobalService } from 'src/app/core/_services/main.service'; -import { PageTitle } from 'src/app/core/_decorators/autotitle'; import { SERV } from '../../core/_services/main.config'; @Component({ selector: 'app-show-cracks', templateUrl: './show-cracks.component.html' }) -@PageTitle(['Show Cracks']) -export class ShowCracksComponent implements OnInit { - +/** + * ShowCracksComponent is a component that manages and displays all groups data. + * + * It uses DataTables to display and interact with the groups data, including exporting, deleting, bulk actions + * and refreshing the table. +*/ +export class ShowCracksComponent implements OnInit, OnDestroy { + + // Font Awesome icons faPlus=faPlus; + // ViewChild reference to the DataTableDirective @ViewChild(DataTableDirective, {static: false}) dtElement: DataTableDirective; dtTrigger: Subject = new Subject(); dtOptions: any = {}; - constructor( - private uiService: UIConfigService, - private gs: GlobalService, - ) { } - + // List of hashes ToDo. Change Interface allhashes: any = []; + + // Subscriptions to unsubscribe on component destruction + subscriptions: Subscription[] = [] + private maxResults = environment.config.prodApiMaxResults; + constructor( + private titleService: AutoTitleService, + private gs: GlobalService, + ) { + titleService.set(['Show Cracks']) + } + + /** + * Initializes DataTable and retrieves groups. + */ ngOnInit(): void { + this.loadCracks(); + this.setupTable(); + } + + /** + * Unsubscribes from active subscriptions and DataTable. + */ + ngOnDestroy(): void { + this.dtTrigger.unsubscribe(); + for (const sub of this.subscriptions) { + sub.unsubscribe(); + } + } + + /** + * Refreshes the data and the DataTable. + */ + onRefresh() { + this.rerender(); + this.ngOnInit(); + } + + /** + * Rerenders the DataTable instance. + * Destroys and recreates the DataTable to reflect changes. + */ + rerender(): void { + this.dtElement.dtInstance.then((dtInstance: DataTables.Api) => { + // Destroy the table first + dtInstance.destroy(); + // Call the dtTrigger to rerender again + setTimeout(() => { + this.dtTrigger['new'].next(); + }); + }); + } + + /** + * Fetches Hashes from the server. + * Subscribes to the API response and updates the Groups list. + */ + loadCracks() { const params = {'maxResults': this.maxResults, 'filter': 'isCracked=1', 'expand':'hashlist,chunk'} - this.gs.getAll(SERV.HASHES,params).subscribe((hashes: any) => { - this.allhashes = hashes.values; + this.gs.getAll(SERV.HASHES,params).subscribe((response: any) => { + this.allhashes = response.values; this.dtTrigger.next(void 0); }); + + } + + /** + * Sets up the DataTable options and buttons. + * Customizes DataTable appearance and behavior. + */ + setupTable(): void { + // DataTables options const self = this; this.dtOptions = { dom: 'Bfrtip', @@ -88,7 +154,7 @@ export class ShowCracksComponent implements OnInit { $(win.document.body).find( 'table' ) .addClass( 'compact' ) .css( 'font-size', 'inherit' ); - } + } }, { extend: 'csvHtml5', @@ -100,7 +166,7 @@ export class ShowCracksComponent implements OnInit { data = "Logs\n\n"+ dt; } return data; - } + } }, 'copy' ] @@ -117,27 +183,5 @@ export class ShowCracksComponent implements OnInit { ], } }; - - } - - onRefresh(){ - this.rerender(); - this.ngOnInit(); - } - - rerender(): void { - this.dtElement.dtInstance.then((dtInstance: DataTables.Api) => { - dtInstance.destroy(); - setTimeout(() => { - this.dtTrigger['new'].next(); - }); - }); } - - - ngOnDestroy(): void { - this.dtTrigger.unsubscribe(); - } - - } diff --git a/src/app/shared/components.module.ts b/src/app/shared/components.module.ts index c8b26d2a..7d04162e 100644 --- a/src/app/shared/components.module.ts +++ b/src/app/shared/components.module.ts @@ -5,7 +5,10 @@ import { NgModule } from "@angular/core"; import { LoadingSpinnerComponent } from '../shared/loading-spinner/loading-spinner.component'; import { HashtypeDetectorComponent } from "./hashtype-detector/hashtype-detector.component"; import { ActiveSpinnerComponent } from './loading-spinner/loading-spinner-active.component'; +import { PassStrenghtComponent } from './password/pass-strenght/pass-strenght.component'; +import { ButtonTruncateTextComponent } from './table/button-truncate-text.component'; import { HexconvertorComponent } from "./utils/hexconvertor/hexconvertor.component"; +import { PassMatchComponent } from './password/pass-match/pass-match.component'; import { CheatsheetComponent } from "./alert/cheatsheet/cheatsheet.component"; import { FilterTextboxModule } from "./filter-textbox/filter-textbox.module"; import { SwitchThemeModule } from "./switch-theme/switch-theme.module"; @@ -20,11 +23,10 @@ import { LottiesModule } from './lottie/lottie.module'; import { GraphsModule } from "./graphs/graphs.module"; import { ColorPickerModule } from 'ngx-color-picker'; import { FormsModule } from "@angular/forms"; -import { PassStrenghtComponent } from './password/pass-strenght/pass-strenght.component'; -import { PassMatchComponent } from './password/pass-match/pass-match.component'; @NgModule({ declarations: [ + ButtonTruncateTextComponent, HashtypeDetectorComponent, LoadingSpinnerComponent, ActiveSpinnerComponent, @@ -51,6 +53,7 @@ import { PassMatchComponent } from './password/pass-match/pass-match.component'; NgbModule ], exports: [ + ButtonTruncateTextComponent, HashtypeDetectorComponent, LoadingSpinnerComponent, ActiveSpinnerComponent, diff --git a/src/app/shared/table/button-truncate-text.component.ts b/src/app/shared/table/button-truncate-text.component.ts new file mode 100644 index 00000000..17657444 --- /dev/null +++ b/src/app/shared/table/button-truncate-text.component.ts @@ -0,0 +1,59 @@ +import { Component, Input } from '@angular/core'; + +/** + * Component for truncating and expanding text with a "More/Less" button. + */ +@Component({ + selector: 'btn-truncate-text', + template: ` +
+
{{ text | slice:0:maxLength }}{{ text.length > maxLength ? '...' : '' }}
+
{{ text }}
+ +
+ `, + styles: [` + .text-container { + position: relative; + max-width: 200px; /* Customize as needed */ + overflow: hidden; + margin: 0; /* Add this to remove margin */ + padding: 0; /* Add this to remove padding */ + } + + .text-container button { + display: inline; + background: none; + border: none; + color: blue; + cursor: pointer; + margin: 0; /* Add this to remove margin */ + padding: 0; /* Add this to remove padding */ + } +`] +}) +export class ButtonTruncateTextComponent { + /** + * The text to be truncated and expanded. + */ + @Input() text: string; + + /** + * The maximum length of the text to display before truncation. + */ + @Input() maxLength: number = 40; // Default maximum length + + /** + * Flag to determine if the text is expanded or truncated. + */ + expanded: boolean = false; + + /** + * Toggles the expansion of the text. + */ + toggleExpansion() { + this.expanded = !this.expanded; + } +} From a602f8c8107c9168f750c3b62c212b6af5485a52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Wed, 25 Oct 2023 17:21:01 +0100 Subject: [PATCH 108/419] Button inject different type, submit or btn attr --- src/app/shared/buttons/button-submit.ts | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/app/shared/buttons/button-submit.ts b/src/app/shared/buttons/button-submit.ts index dd58060d..592b3a9c 100644 --- a/src/app/shared/buttons/button-submit.ts +++ b/src/app/shared/buttons/button-submit.ts @@ -29,7 +29,7 @@ import { Location } from '@angular/common'; @Component({ selector: 'button-submit', template: ` - + ` }) export class ButtonSubmitComponent { @@ -63,13 +63,24 @@ export class ButtonSubmitComponent { /** * Handle the button click based on its type. - */ + */ handleClick(): void { if (this.type === 'cancel') { this.location.back(); // Go back to the previous window } else { - // Handle other button actions as needed + return } } -} + /** + * Get attribute and inject in button. + */ + getTypeAttribute(): string { + if (this.type === 'cancel' || this.type === 'delete') { + return 'button'; + } else { + return 'submit'; + } + } + +} From ae4253dbc8e174d861b8d259bda00ad920e728bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Wed, 25 Oct 2023 17:21:43 +0100 Subject: [PATCH 109/419] Groups decorator not needed, removed --- src/app/users/groups/groups.component.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/app/users/groups/groups.component.ts b/src/app/users/groups/groups.component.ts index 07fbd6d6..289653e5 100644 --- a/src/app/users/groups/groups.component.ts +++ b/src/app/users/groups/groups.component.ts @@ -16,7 +16,6 @@ declare let $:any; selector: 'app-groups', templateUrl: './groups.component.html' }) -@PageTitle(['Show Groups']) /** * GroupsComponent is a component that manages and displays all groups data. * From ab5df2d6703b024e1dd8083494f80b2331792ad8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Wed, 25 Oct 2023 17:22:19 +0100 Subject: [PATCH 110/419] Edit User, Wrong label notification --- src/app/users/edit-users/edit-users.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/users/edit-users/edit-users.component.ts b/src/app/users/edit-users/edit-users.component.ts index 907ca78f..9d3da507 100644 --- a/src/app/users/edit-users/edit-users.component.ts +++ b/src/app/users/edit-users/edit-users.component.ts @@ -93,7 +93,7 @@ export class EditUsersComponent implements OnInit { } onDelete(){ - this.alert.deleteConfirmation('','Hashtypes').then((confirmed) => { + this.alert.deleteConfirmation('','Users').then((confirmed) => { if (confirmed) { // Deletion this.gs.delete(SERV.USERS, this.editedUserIndex).subscribe(() => { From 405f05d9dc126c530bbb18e1100f20c19cb3a795 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Mon, 30 Oct 2023 21:59:33 +0100 Subject: [PATCH 111/419] Add storage service --- .../storage/base-storage.service.spec.ts | 61 ++++++++++++ .../_services/storage/base-storage.service.ts | 70 +++++++++++++ .../storage/cookie-storage.service.spec.ts | 98 +++++++++++++++++++ .../storage/cookie-storage.service.ts | 90 +++++++++++++++++ .../storage/local-storage.service.spec.ts | 96 ++++++++++++++++++ .../storage/local-storage.service.ts | 66 +++++++++++++ 6 files changed, 481 insertions(+) create mode 100644 src/app/core/_services/storage/base-storage.service.spec.ts create mode 100644 src/app/core/_services/storage/base-storage.service.ts create mode 100644 src/app/core/_services/storage/cookie-storage.service.spec.ts create mode 100644 src/app/core/_services/storage/cookie-storage.service.ts create mode 100644 src/app/core/_services/storage/local-storage.service.spec.ts create mode 100644 src/app/core/_services/storage/local-storage.service.ts diff --git a/src/app/core/_services/storage/base-storage.service.spec.ts b/src/app/core/_services/storage/base-storage.service.spec.ts new file mode 100644 index 00000000..c89c9dbe --- /dev/null +++ b/src/app/core/_services/storage/base-storage.service.spec.ts @@ -0,0 +1,61 @@ +import { BaseStorageService } from "./base-storage.service"; + +class MockStorageService extends BaseStorageService { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + getItem(key: string): T | null { + return null + } + // eslint-disable-next-line @typescript-eslint/no-unused-vars + setItem(key: string, value: T, expiresInMs: number): void { + // Do nothing + } + // eslint-disable-next-line @typescript-eslint/no-unused-vars + removeItem(key: string): void { + // Do nothing + } +} + +describe('BaseStorageService', () => { + let service: MockStorageService; + + beforeEach(() => { + service = new MockStorageService(); + }); + + // --- Test Methods --- + + it('should decode a URL-encoded string', () => { + const encodedString = 'Hello%20World'; + const decodedString = service.decode(encodedString); + + expect(decodedString).toBe('Hello World'); + }); + + it('should return the original string if not URL-encoded', () => { + const originalString = 'Hello World'; + const decodedString = service.decode(originalString); + + expect(decodedString).toBe(originalString); + }); + + it('should return true for an expired storage wrapper', () => { + const expiredWrapper = { value: 'expired', expires: Date.now() - 1000 }; // Set expiration in the past + const result = service.hasExpired(expiredWrapper); + + expect(result).toBe(true); + }); + + it('should return false for a valid (not expired) storage wrapper', () => { + const validWrapper = { value: 'valid', expires: Date.now() + 1000 }; // Set expiration in the future + const result = service.hasExpired(validWrapper); + + expect(result).toBe(false); + }); + + it('should return false for a wrapper with no expiration', () => { + const noExpirationWrapper = { value: 'no expiration', expires: null }; + const result = service.hasExpired(noExpirationWrapper); + + expect(result).toBe(false); + }); +}); \ No newline at end of file diff --git a/src/app/core/_services/storage/base-storage.service.ts b/src/app/core/_services/storage/base-storage.service.ts new file mode 100644 index 00000000..59c1ec97 --- /dev/null +++ b/src/app/core/_services/storage/base-storage.service.ts @@ -0,0 +1,70 @@ +import { Injectable } from '@angular/core'; + +export interface StorageWrapper { + value: T + expires: number +} + +/** + * Abstract base service for data storage with expiration. + * Implementations of this service should provide methods for storing, retrieving, + * and removing data with optional expiration. + * + * @template T - The type of data to be stored. + */ +@Injectable({ + providedIn: 'root', +}) +export abstract class BaseStorageService { + + /** + * Retrieves the stored data associated with the specified key. + * + * @param key - The key under which the data is stored. + * @returns The stored data if found, or `null` if not found or expired. + */ + abstract getItem(key: string): T | null; + + /** + * Stores data with an optional expiration time. + * + * @param key - The key under which to store the data. + * @param value - The data to be stored. + * @param expiresInMs - The optional expiration time in milliseconds. + * If provided and greater than 0, the data will expire + * after the specified time has passed. + */ + abstract setItem(key: string, value: T, expiresInMs: number): void; + + /** + * Removes the stored data associated with the specified key. + * + * @param key - The key under which the data is stored. + */ + abstract removeItem(key: string): void; + + /** + * Decodes a string that may be URL-encoded. If the input string is a valid + * URL-encoded string, it is decoded; otherwise, it returns the original string. + * + * @param data - The string to decode, which may be URL-encoded. + * @returns The decoded string if it was URL-encoded, or the original string if not. + */ + decode(data: string): string { + try { + return decodeURIComponent(data); + } catch { + return data; + } + } + + /** + * Checks if the provided storage wrapper has expired based on its expiration timestamp. + * + * @param wrapper - The storage wrapper containing data and an optional expiration timestamp. + * @returns `true` if the wrapper has expired, or `false` if it is still valid or has no expiration timestamp. + */ + hasExpired(wrapper: StorageWrapper): boolean { + return !!(wrapper.expires && wrapper.expires <= Date.now()) + } +} \ No newline at end of file diff --git a/src/app/core/_services/storage/cookie-storage.service.spec.ts b/src/app/core/_services/storage/cookie-storage.service.spec.ts new file mode 100644 index 00000000..b3a89822 --- /dev/null +++ b/src/app/core/_services/storage/cookie-storage.service.spec.ts @@ -0,0 +1,98 @@ +import { TestBed, fakeAsync, tick } from '@angular/core/testing'; +import { CookieStorageService } from './cookie-storage.service'; + + +describe('CookieStorageService', () => { + let service: CookieStorageService; + + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [ + CookieStorageService, + ] + }); + service = TestBed.inject(CookieStorageService); + }); + + // --- Test Methods --- + + it('should be created', () => { + expect(service).toBeTruthy(); + }); + + it('should store and retrieve data in cookies', () => { + const key = 'testKey'; + const value = 'testValue'; + + service.setItem(key, value, 1000); // Set with 1-second expiration + const retrievedValue = service.getItem(key); + + expect(retrievedValue).toBe(value); + }); + + it('should remove data from cookies after expiration', fakeAsync(() => { + const key = 'testKey'; + const value = 'testValue'; + + service.setItem(key, value, 1); // Set with 1-millisecond expiration + + tick(2); + + const retrievedValue = service.getItem(key); + expect(retrievedValue).toBeNull(); + })); + + it('should remove data from cookies when explicitly removed', () => { + const key = 'testKey'; + const value = 'testValue'; + + service.setItem(key, value, 1000); + service.removeItem(key); + + const retrievedValue = service.getItem(key); + expect(retrievedValue).toBeNull(); + }); + + + it('should handle invalid JSON data', () => { + const key = 'invalidKey'; + const invalidValue = 'invalidJSON'; + + service.setItem(key, invalidValue, 1000); + const retrievedValue = service.getItem(key); + + // The stored value is not valid JSON, so it should be retrieved as-is + expect(retrievedValue).toBe(invalidValue); + }); + + it('should handle null values', () => { + const key = 'nullKey'; + + // Store a null value + service.setItem(key, null, 1000); + const retrievedValue = service.getItem(key); + + expect(retrievedValue).toBeNull(); + }); + + it('should handle complex objects', () => { + const key = 'objectKey'; + const complexObject = { name: 'John', age: 30, hobbies: ['reading', 'hiking'] }; + + // Store a complex object + service.setItem(key, complexObject, 1000); + const retrievedValue = service.getItem(key); + + expect(retrievedValue).toEqual(complexObject); + }); + + it('should handle keys with special characters', () => { + const key = 'specialKey@!$%^&*()_+'; + + service.setItem(key, 'specialValue', 1000); + const retrievedValue = service.getItem(key); + + expect(retrievedValue).toBe('specialValue'); + }); + +}); \ No newline at end of file diff --git a/src/app/core/_services/storage/cookie-storage.service.ts b/src/app/core/_services/storage/cookie-storage.service.ts new file mode 100644 index 00000000..791ba387 --- /dev/null +++ b/src/app/core/_services/storage/cookie-storage.service.ts @@ -0,0 +1,90 @@ +import { Injectable } from "@angular/core"; +import { BaseStorageService } from "./base-storage.service"; + +export type SameSite = 'Lax' | 'None' | 'Strict'; + + +/** + * A storage service implementation that uses browser cookies to store and retrieve data + * with optional expiration. + * + * @template T - The type of data to be stored. + */ +@Injectable({ + providedIn: 'root', +}) +export class CookieStorageService extends BaseStorageService { + + static readonly SAME_SITE: SameSite = 'Lax' + + /** + * Retrieves the stored data associated with the specified key from browser cookies. + * + * @param key - The key under which the data is stored. + * @returns The stored data if found, or `null` if not found or expired. + */ + getItem(key: string): T | null { + key = this.decode(key); + const value = this.getCookie(key); + + try { + // Try parsing the value as JSON + return JSON.parse(value) as T; + } catch (e) { + // If parsing fails, return the value as a plain string + return value !== null ? value as T : null; + } + } + + /** + * Stores data with an optional expiration time in browser cookies. + * + * @param key - The key under which to store the data. + * @param value - The data to be stored. + * @param expiresInMs - The optional expiration time in milliseconds. + * If provided and greater than 0, the data will expire + * after the specified time has passed. + */ + setItem(key: string, value: T, expiresInMs: number): void { + key = this.decode(key); + const serializedValue = typeof value === 'string' ? value : JSON.stringify(value); + + // Construct the cookie string with optional attributes + let cookieString = `${key}=${serializedValue};sameSite=${CookieStorageService.SAME_SITE};` + if (expiresInMs) { + const expires = new Date(Date.now() + expiresInMs).toUTCString(); + cookieString += `expires=${expires};`; + } + + document.cookie = cookieString + } + + /** + * Removes the stored data associated with the specified key from browser cookies. + * + * @param key - The key under which the data is stored. + */ + removeItem(key: string): void { + key = this.decode(key); + document.cookie = `${key}=; expires=Thu, 01 Jan 1970 00:00:00 UTC`; + } + + /** + * Retrieves the raw cookie value associated with the specified key from browser cookies. + * + * @param key - The key under which the data is stored. + * @returns The raw cookie value if found, or `null` if not found. + */ + private getCookie(key: string): string | null { + key = this.decode(key); + const cookies = document.cookie.split(';'); + + for (const cookie of cookies) { + const [cookieKey, cookieValue] = cookie.split('=').map((c) => c.trim()); + if (cookieKey === key) { + return cookieValue; + } + } + return null; + } +} \ No newline at end of file diff --git a/src/app/core/_services/storage/local-storage.service.spec.ts b/src/app/core/_services/storage/local-storage.service.spec.ts new file mode 100644 index 00000000..b9167eea --- /dev/null +++ b/src/app/core/_services/storage/local-storage.service.spec.ts @@ -0,0 +1,96 @@ +import { TestBed, fakeAsync, tick } from '@angular/core/testing'; +import { LocalStorageService } from './local-storage.service'; + + +describe('LocalStorageService', () => { + let service: LocalStorageService; + + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [ + LocalStorageService, + ] + }); + service = TestBed.inject(LocalStorageService); + }); + + // --- Test Methods --- + + it('should be created', () => { + expect(service).toBeTruthy(); + }); + + it('should store and retrieve data in local storage', () => { + const key = 'testKey'; + const value = 'testValue'; + + service.setItem(key, value, 1000); + const retrievedValue = service.getItem(key); + + expect(retrievedValue).toBe(value); + }); + + it('should remove data from local storage after expiration', fakeAsync(() => { + const key = 'testKey'; + const value = 'testValue'; + + service.setItem(key, value, 1); // Set with 1-millisecond expiration + + tick(2); + + const retrievedValue = service.getItem(key); + expect(retrievedValue).toBeNull(); + })); + + it('should remove data from local storage when explicitly removed', () => { + const key = 'testKey'; + const value = 'testValue'; + + service.setItem(key, value, 1000); + service.removeItem(key); + + const retrievedValue = service.getItem(key); + expect(retrievedValue).toBeNull(); + }); + + it('should handle invalid JSON data', () => { + const key = 'invalidKey'; + const invalidValue = 'invalidJSON'; + + service.setItem(key, invalidValue, 1000); + const retrievedValue = service.getItem(key); + + // The stored value is not valid JSON, so it should be retrieved as-is + expect(retrievedValue).toBe(invalidValue); + }); + + it('should handle null values', () => { + const key = 'nullKey'; + + // Store a null value + service.setItem(key, null, 1000); + const retrievedValue = service.getItem(key); + + expect(retrievedValue).toBeNull(); + }); + + it('should handle complex objects', () => { + const key = 'objectKey'; + const complexObject = { name: 'John', age: 30, hobbies: ['reading', 'hiking'] }; + + // Store a complex object + service.setItem(key, complexObject, 1000); + const retrievedValue = service.getItem(key); + + expect(retrievedValue).toEqual(complexObject); + }); + + it('should handle keys with special characters', () => { + const key = 'specialKey@!$%^&*()_+'; + + service.setItem(key, 'specialValue', 1000); + const retrievedValue = service.getItem(key); + + expect(retrievedValue).toBe('specialValue'); + }); +}); \ No newline at end of file diff --git a/src/app/core/_services/storage/local-storage.service.ts b/src/app/core/_services/storage/local-storage.service.ts new file mode 100644 index 00000000..80a63163 --- /dev/null +++ b/src/app/core/_services/storage/local-storage.service.ts @@ -0,0 +1,66 @@ +import { Injectable } from "@angular/core"; +import { BaseStorageService, StorageWrapper } from "./base-storage.service"; + +/** + * A storage service implementation that uses browser's local storage to store and retrieve data + * with optional expiration. + * + * @template T - The type of data to be stored. + */ +@Injectable({ + providedIn: 'root', +}) +export class LocalStorageService extends BaseStorageService { + + /** + * Retrieves the stored data associated with the specified key from local storage, + * and checks if it has expired. If the data has expired, it is removed from local storage. + * + * @param key - The key under which the data is stored. + * @returns The stored data if found and not expired, or `null` if not found or expired. + */ + getItem(key: string): T | null { + key = this.decode(key) + const storedValue = localStorage.getItem(key); + if (!storedValue) { + return null; // Data not found. + } + + const storedData: StorageWrapper = JSON.parse(storedValue); + if (this.hasExpired(storedData)) { + // Data has expired, remove it from local storage. + localStorage.removeItem(key); + return null; + } + + return storedData.value; + } + + /** + * Stores data with an optional expiration time in local storage. + * + * @param key - The key under which to store the data. + * @param value - The data to be stored. + * @param expiresInMs - The optional expiration time in milliseconds. + * If provided and greater than 0, the data will expire + * after the specified time has passed. + */ + setItem(key: string, value: T, expiresInMs: number): void { + const storedValue: StorageWrapper = { + expires: expiresInMs ? new Date(Date.now() + expiresInMs).getTime() : 0, + value: value + } + + localStorage.setItem(this.decode(key), JSON.stringify(storedValue)); + } + + /** + * Removes the stored data associated with the specified key from local storage. + * + * @param key - The key under which the data is stored. + */ + removeItem(key: string): void { + key = this.decode(key) + localStorage.removeItem(key); + } +} \ No newline at end of file From 0957144176d9a7aa5f58fa4778798f6867516e76 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Mon, 30 Oct 2023 22:00:15 +0100 Subject: [PATCH 112/419] Add material design --- package-lock.json | 853 +++++++++++++++++++++++++++++++++++++++++++++- package.json | 4 +- 2 files changed, 855 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 26913f94..be937523 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,11 +9,13 @@ "version": "0.0.0", "dependencies": { "@angular/animations": "^16.1.4", + "@angular/cdk": "^16.2.9", "@angular/common": "^16.1.4", "@angular/compiler": "^16.1.4", "@angular/core": "^16.1.4", "@angular/forms": "^16.1.4", "@angular/localize": "^16.1.4", + "@angular/material": "^16.2.9", "@angular/platform-browser": "^16.1.4", "@angular/platform-browser-dynamic": "^16.1.4", "@angular/router": "^16.1.4", @@ -425,6 +427,34 @@ "@angular/core": "16.2.1" } }, + "node_modules/@angular/cdk": { + "version": "16.2.10", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-16.2.10.tgz", + "integrity": "sha512-kOQrPxSMPi66aM9XfwZIjQXhH+q0PkhK4BNMHB9RkvaaQ34ovOrKaGsT7t0+sjlVhiwTiy2mB1Qgz6NlIB0ZZw==", + "dependencies": { + "tslib": "^2.3.0" + }, + "optionalDependencies": { + "parse5": "^7.1.2" + }, + "peerDependencies": { + "@angular/common": "^16.0.0 || ^17.0.0", + "@angular/core": "^16.0.0 || ^17.0.0", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/@angular/cdk/node_modules/parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "optional": true, + "dependencies": { + "entities": "^4.4.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, "node_modules/@angular/cli": { "version": "16.2.0", "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-16.2.0.tgz", @@ -663,6 +693,70 @@ "semver": "bin/semver.js" } }, + "node_modules/@angular/material": { + "version": "16.2.10", + "resolved": "https://registry.npmjs.org/@angular/material/-/material-16.2.10.tgz", + "integrity": "sha512-0XhMwbcxpEESL11mVO8ycwxa+Jlh+8egOSRleD30zFUesqBA5EhtRpH8cqtna03f/xxtRq00Q315igMIMNiOSg==", + "dependencies": { + "@material/animation": "15.0.0-canary.bc9ae6c9c.0", + "@material/auto-init": "15.0.0-canary.bc9ae6c9c.0", + "@material/banner": "15.0.0-canary.bc9ae6c9c.0", + "@material/base": "15.0.0-canary.bc9ae6c9c.0", + "@material/button": "15.0.0-canary.bc9ae6c9c.0", + "@material/card": "15.0.0-canary.bc9ae6c9c.0", + "@material/checkbox": "15.0.0-canary.bc9ae6c9c.0", + "@material/chips": "15.0.0-canary.bc9ae6c9c.0", + "@material/circular-progress": "15.0.0-canary.bc9ae6c9c.0", + "@material/data-table": "15.0.0-canary.bc9ae6c9c.0", + "@material/density": "15.0.0-canary.bc9ae6c9c.0", + "@material/dialog": "15.0.0-canary.bc9ae6c9c.0", + "@material/dom": "15.0.0-canary.bc9ae6c9c.0", + "@material/drawer": "15.0.0-canary.bc9ae6c9c.0", + "@material/elevation": "15.0.0-canary.bc9ae6c9c.0", + "@material/fab": "15.0.0-canary.bc9ae6c9c.0", + "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", + "@material/floating-label": "15.0.0-canary.bc9ae6c9c.0", + "@material/form-field": "15.0.0-canary.bc9ae6c9c.0", + "@material/icon-button": "15.0.0-canary.bc9ae6c9c.0", + "@material/image-list": "15.0.0-canary.bc9ae6c9c.0", + "@material/layout-grid": "15.0.0-canary.bc9ae6c9c.0", + "@material/line-ripple": "15.0.0-canary.bc9ae6c9c.0", + "@material/linear-progress": "15.0.0-canary.bc9ae6c9c.0", + "@material/list": "15.0.0-canary.bc9ae6c9c.0", + "@material/menu": "15.0.0-canary.bc9ae6c9c.0", + "@material/menu-surface": "15.0.0-canary.bc9ae6c9c.0", + "@material/notched-outline": "15.0.0-canary.bc9ae6c9c.0", + "@material/radio": "15.0.0-canary.bc9ae6c9c.0", + "@material/ripple": "15.0.0-canary.bc9ae6c9c.0", + "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", + "@material/segmented-button": "15.0.0-canary.bc9ae6c9c.0", + "@material/select": "15.0.0-canary.bc9ae6c9c.0", + "@material/shape": "15.0.0-canary.bc9ae6c9c.0", + "@material/slider": "15.0.0-canary.bc9ae6c9c.0", + "@material/snackbar": "15.0.0-canary.bc9ae6c9c.0", + "@material/switch": "15.0.0-canary.bc9ae6c9c.0", + "@material/tab": "15.0.0-canary.bc9ae6c9c.0", + "@material/tab-bar": "15.0.0-canary.bc9ae6c9c.0", + "@material/tab-indicator": "15.0.0-canary.bc9ae6c9c.0", + "@material/tab-scroller": "15.0.0-canary.bc9ae6c9c.0", + "@material/textfield": "15.0.0-canary.bc9ae6c9c.0", + "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "@material/tooltip": "15.0.0-canary.bc9ae6c9c.0", + "@material/top-app-bar": "15.0.0-canary.bc9ae6c9c.0", + "@material/touch-target": "15.0.0-canary.bc9ae6c9c.0", + "@material/typography": "15.0.0-canary.bc9ae6c9c.0", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/animations": "^16.0.0 || ^17.0.0", + "@angular/cdk": "16.2.10", + "@angular/common": "^16.0.0 || ^17.0.0", + "@angular/core": "^16.0.0 || ^17.0.0", + "@angular/forms": "^16.0.0 || ^17.0.0", + "@angular/platform-browser": "^16.0.0 || ^17.0.0", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, "node_modules/@angular/platform-browser": { "version": "16.2.1", "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-16.2.1.tgz", @@ -3488,6 +3582,758 @@ "node": ">= 6" } }, + "node_modules/@material/animation": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/animation/-/animation-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-leRf+BcZTfC/iSigLXnYgcHAGvFVQveoJT5+2PIRdyPI/bIG7hhciRgacHRsCKC0sGya81dDblLgdkjSUemYLw==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@material/auto-init": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/auto-init/-/auto-init-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-uxzDq7q3c0Bu1pAsMugc1Ik9ftQYQqZY+5e2ybNplT8gTImJhNt4M2mMiMHbMANk2l3UgICmUyRSomgPBWCPIA==", + "dependencies": { + "@material/base": "15.0.0-canary.bc9ae6c9c.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/banner": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/banner/-/banner-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-SHeVoidCUFVhXANN6MNWxK9SZoTSgpIP8GZB7kAl52BywLxtV+FirTtLXkg/8RUkxZRyRWl7HvQ0ZFZa7QQAyA==", + "dependencies": { + "@material/base": "15.0.0-canary.bc9ae6c9c.0", + "@material/button": "15.0.0-canary.bc9ae6c9c.0", + "@material/dom": "15.0.0-canary.bc9ae6c9c.0", + "@material/elevation": "15.0.0-canary.bc9ae6c9c.0", + "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", + "@material/ripple": "15.0.0-canary.bc9ae6c9c.0", + "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", + "@material/shape": "15.0.0-canary.bc9ae6c9c.0", + "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "@material/tokens": "15.0.0-canary.bc9ae6c9c.0", + "@material/typography": "15.0.0-canary.bc9ae6c9c.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/base": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/base/-/base-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-Fc3vGuOf+duGo22HTRP6dHdc+MUe0VqQfWOuKrn/wXKD62m0QQR2TqJd3rRhCumH557T5QUyheW943M3E+IGfg==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@material/button": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/button/-/button-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-3AQgwrPZCTWHDJvwgKq7Cj+BurQ4wTjDdGL+FEnIGUAjJDskwi1yzx5tW2Wf/NxIi7IoPFyOY3UB41jwMiOrnw==", + "dependencies": { + "@material/density": "15.0.0-canary.bc9ae6c9c.0", + "@material/dom": "15.0.0-canary.bc9ae6c9c.0", + "@material/elevation": "15.0.0-canary.bc9ae6c9c.0", + "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", + "@material/focus-ring": "15.0.0-canary.bc9ae6c9c.0", + "@material/ripple": "15.0.0-canary.bc9ae6c9c.0", + "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", + "@material/shape": "15.0.0-canary.bc9ae6c9c.0", + "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "@material/tokens": "15.0.0-canary.bc9ae6c9c.0", + "@material/touch-target": "15.0.0-canary.bc9ae6c9c.0", + "@material/typography": "15.0.0-canary.bc9ae6c9c.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/card": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/card/-/card-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-nPlhiWvbLmooTnBmV5gmzB0eLWSgLKsSRBYAbIBmO76Okgz1y+fQNLag+lpm/TDaHVsn5fmQJH8e0zIg0rYsQA==", + "dependencies": { + "@material/dom": "15.0.0-canary.bc9ae6c9c.0", + "@material/elevation": "15.0.0-canary.bc9ae6c9c.0", + "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", + "@material/ripple": "15.0.0-canary.bc9ae6c9c.0", + "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", + "@material/shape": "15.0.0-canary.bc9ae6c9c.0", + "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "@material/tokens": "15.0.0-canary.bc9ae6c9c.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/checkbox": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/checkbox/-/checkbox-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-4tpNnO1L0IppoMF3oeQn8F17t2n0WHB0D7mdJK9rhrujen/fLbekkIC82APB3fdGtLGg3qeNqDqPsJm1YnmrwA==", + "dependencies": { + "@material/animation": "15.0.0-canary.bc9ae6c9c.0", + "@material/base": "15.0.0-canary.bc9ae6c9c.0", + "@material/density": "15.0.0-canary.bc9ae6c9c.0", + "@material/dom": "15.0.0-canary.bc9ae6c9c.0", + "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", + "@material/focus-ring": "15.0.0-canary.bc9ae6c9c.0", + "@material/ripple": "15.0.0-canary.bc9ae6c9c.0", + "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", + "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "@material/touch-target": "15.0.0-canary.bc9ae6c9c.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/chips": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/chips/-/chips-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-fqHKvE5bSWK0bXVkf57MWxZtytGqYBZvvHIOs4JI9HPHEhaJy4CpSw562BEtbm3yFxxALoQknvPW2KYzvADnmA==", + "dependencies": { + "@material/animation": "15.0.0-canary.bc9ae6c9c.0", + "@material/base": "15.0.0-canary.bc9ae6c9c.0", + "@material/checkbox": "15.0.0-canary.bc9ae6c9c.0", + "@material/density": "15.0.0-canary.bc9ae6c9c.0", + "@material/dom": "15.0.0-canary.bc9ae6c9c.0", + "@material/elevation": "15.0.0-canary.bc9ae6c9c.0", + "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", + "@material/focus-ring": "15.0.0-canary.bc9ae6c9c.0", + "@material/ripple": "15.0.0-canary.bc9ae6c9c.0", + "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", + "@material/shape": "15.0.0-canary.bc9ae6c9c.0", + "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "@material/tokens": "15.0.0-canary.bc9ae6c9c.0", + "@material/touch-target": "15.0.0-canary.bc9ae6c9c.0", + "@material/typography": "15.0.0-canary.bc9ae6c9c.0", + "safevalues": "^0.3.4", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/circular-progress": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/circular-progress/-/circular-progress-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-Lxe8BGAxQwCQqrLhrYrIP0Uok10h7aYS3RBXP41ph+5GmwJd5zdyE2t93qm2dyThvU6qKuXw9726Dtq/N+wvZQ==", + "dependencies": { + "@material/animation": "15.0.0-canary.bc9ae6c9c.0", + "@material/base": "15.0.0-canary.bc9ae6c9c.0", + "@material/dom": "15.0.0-canary.bc9ae6c9c.0", + "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", + "@material/progress-indicator": "15.0.0-canary.bc9ae6c9c.0", + "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", + "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/data-table": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/data-table/-/data-table-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-j/7qplT9+sUpfe4pyWhPbl01qJA+OoNAG3VMJruBBR461ZBKyTi7ssKH9yksFGZ8eCEPkOsk/+kDxsiZvRWkeQ==", + "dependencies": { + "@material/animation": "15.0.0-canary.bc9ae6c9c.0", + "@material/base": "15.0.0-canary.bc9ae6c9c.0", + "@material/checkbox": "15.0.0-canary.bc9ae6c9c.0", + "@material/density": "15.0.0-canary.bc9ae6c9c.0", + "@material/dom": "15.0.0-canary.bc9ae6c9c.0", + "@material/elevation": "15.0.0-canary.bc9ae6c9c.0", + "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", + "@material/icon-button": "15.0.0-canary.bc9ae6c9c.0", + "@material/linear-progress": "15.0.0-canary.bc9ae6c9c.0", + "@material/list": "15.0.0-canary.bc9ae6c9c.0", + "@material/menu": "15.0.0-canary.bc9ae6c9c.0", + "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", + "@material/select": "15.0.0-canary.bc9ae6c9c.0", + "@material/shape": "15.0.0-canary.bc9ae6c9c.0", + "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "@material/tokens": "15.0.0-canary.bc9ae6c9c.0", + "@material/touch-target": "15.0.0-canary.bc9ae6c9c.0", + "@material/typography": "15.0.0-canary.bc9ae6c9c.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/density": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/density/-/density-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-Zt3u07fXrBWLW06Tl5fgvjicxNQMkFdawLyNTzZ5TvbXfVkErILLePwwGaw8LNcvzqJP6ABLA8jiR+sKNoJQCg==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@material/dialog": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/dialog/-/dialog-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-o+9a/fmwJ9+gY3Z/uhj/PMVJDq7it1NTWKJn2GwAKdB+fDkT4hb9qEdcxMPyvJJ5ups+XiKZo03+tZrD+38c1w==", + "dependencies": { + "@material/animation": "15.0.0-canary.bc9ae6c9c.0", + "@material/base": "15.0.0-canary.bc9ae6c9c.0", + "@material/button": "15.0.0-canary.bc9ae6c9c.0", + "@material/dom": "15.0.0-canary.bc9ae6c9c.0", + "@material/elevation": "15.0.0-canary.bc9ae6c9c.0", + "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", + "@material/icon-button": "15.0.0-canary.bc9ae6c9c.0", + "@material/ripple": "15.0.0-canary.bc9ae6c9c.0", + "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", + "@material/shape": "15.0.0-canary.bc9ae6c9c.0", + "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "@material/tokens": "15.0.0-canary.bc9ae6c9c.0", + "@material/touch-target": "15.0.0-canary.bc9ae6c9c.0", + "@material/typography": "15.0.0-canary.bc9ae6c9c.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/dom": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/dom/-/dom-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-ly78R7aoCJtundSUu0UROU+5pQD5Piae0Y1MkN6bs0724azeazX1KeXFeaf06JOXnlr5/41ol+fSUPowjoqnOg==", + "dependencies": { + "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", + "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/drawer": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/drawer/-/drawer-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-PFL4cEFnt7VTxDsuspFVNhsFDYyumjU0VWfj3PWB7XudsEfQ3lo85D3HCEtTTbRsCainGN8bgYNDNafLBqiigw==", + "dependencies": { + "@material/animation": "15.0.0-canary.bc9ae6c9c.0", + "@material/base": "15.0.0-canary.bc9ae6c9c.0", + "@material/dom": "15.0.0-canary.bc9ae6c9c.0", + "@material/elevation": "15.0.0-canary.bc9ae6c9c.0", + "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", + "@material/list": "15.0.0-canary.bc9ae6c9c.0", + "@material/ripple": "15.0.0-canary.bc9ae6c9c.0", + "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", + "@material/shape": "15.0.0-canary.bc9ae6c9c.0", + "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "@material/typography": "15.0.0-canary.bc9ae6c9c.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/elevation": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/elevation/-/elevation-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-Ro+Pk8jFuap+T0B0shA3xI1hs2b89dNQ2EIPCNjNMp87emHKAzJfhKb7EZGIwv3+gFLlVaLyIVkb94I89KLsyg==", + "dependencies": { + "@material/animation": "15.0.0-canary.bc9ae6c9c.0", + "@material/base": "15.0.0-canary.bc9ae6c9c.0", + "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", + "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", + "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/fab": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/fab/-/fab-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-dvU0KWMRglwJEQwmQtFAmJcAjzg9VFF6Aqj78bJYu/DAIGFJ1VTTTSgoXM/XCm1YyQEZ7kZRvxBO37CH54rSDg==", + "dependencies": { + "@material/animation": "15.0.0-canary.bc9ae6c9c.0", + "@material/dom": "15.0.0-canary.bc9ae6c9c.0", + "@material/elevation": "15.0.0-canary.bc9ae6c9c.0", + "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", + "@material/focus-ring": "15.0.0-canary.bc9ae6c9c.0", + "@material/ripple": "15.0.0-canary.bc9ae6c9c.0", + "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", + "@material/shape": "15.0.0-canary.bc9ae6c9c.0", + "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "@material/tokens": "15.0.0-canary.bc9ae6c9c.0", + "@material/touch-target": "15.0.0-canary.bc9ae6c9c.0", + "@material/typography": "15.0.0-canary.bc9ae6c9c.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/feature-targeting": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/feature-targeting/-/feature-targeting-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-wkDjVcoVEYYaJvun28IXdln/foLgPD7n9ZC9TY76GErGCwTq+HWpU6wBAAk+ePmpRFDayw4vI4wBlaWGxLtysQ==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@material/floating-label": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/floating-label/-/floating-label-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-bUWPtXzZITOD/2mkvLkEPO1ngDWmb74y0Kgbz6llHLOQBtycyJIpuoQJ1q2Ez0NM/tFLwPphhAgRqmL3YQ/Kzw==", + "dependencies": { + "@material/animation": "15.0.0-canary.bc9ae6c9c.0", + "@material/base": "15.0.0-canary.bc9ae6c9c.0", + "@material/dom": "15.0.0-canary.bc9ae6c9c.0", + "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", + "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", + "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "@material/typography": "15.0.0-canary.bc9ae6c9c.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/focus-ring": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/focus-ring/-/focus-ring-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-cZHThVose3GvAlJzpJoBI1iqL6d1/Jj9hXrR+r8Mwtb1hBIUEG3hxfsRd4vGREuzROPlf0OgNf/V+YHoSwgR5w==", + "dependencies": { + "@material/dom": "15.0.0-canary.bc9ae6c9c.0", + "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", + "@material/rtl": "15.0.0-canary.bc9ae6c9c.0" + } + }, + "node_modules/@material/form-field": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/form-field/-/form-field-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-+JFXy5X44Gue1CbZZAQ6YejnI203lebYwL0i6k0ylDpWHEOdD5xkF2PyHR28r9/65Ebcbwbff6q7kI1SGoT7MA==", + "dependencies": { + "@material/base": "15.0.0-canary.bc9ae6c9c.0", + "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", + "@material/ripple": "15.0.0-canary.bc9ae6c9c.0", + "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", + "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "@material/typography": "15.0.0-canary.bc9ae6c9c.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/icon-button": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/icon-button/-/icon-button-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-1a0MHgyIwOs4RzxrVljsqSizGYFlM1zY2AZaLDsgT4G3kzsplTx8HZQ022GpUCjAygW+WLvg4z1qAhQHvsbqlw==", + "dependencies": { + "@material/base": "15.0.0-canary.bc9ae6c9c.0", + "@material/density": "15.0.0-canary.bc9ae6c9c.0", + "@material/dom": "15.0.0-canary.bc9ae6c9c.0", + "@material/elevation": "15.0.0-canary.bc9ae6c9c.0", + "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", + "@material/focus-ring": "15.0.0-canary.bc9ae6c9c.0", + "@material/ripple": "15.0.0-canary.bc9ae6c9c.0", + "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", + "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "@material/touch-target": "15.0.0-canary.bc9ae6c9c.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/image-list": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/image-list/-/image-list-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-WKWmiYap2iu4QdqmeUSliLlN4O2Ueqa0OuVAYHn/TCzmQ2xmnhZ1pvDLbs6TplpOmlki7vFfe+aSt5SU9gwfOQ==", + "dependencies": { + "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", + "@material/shape": "15.0.0-canary.bc9ae6c9c.0", + "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "@material/typography": "15.0.0-canary.bc9ae6c9c.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/layout-grid": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/layout-grid/-/layout-grid-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-5GqmT6oTZhUGWIb+CLD0ZNyDyTiJsr/rm9oRIi3+vCujACwxFkON9tzBlZohdtFS16nuzUusthN6Jt9UrJcN6Q==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@material/line-ripple": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/line-ripple/-/line-ripple-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-8S30WXEuUdgDdBulzUDlPXD6qMzwCX9SxYb5mGDYLwl199cpSGdXHtGgEcCjokvnpLhdZhcT1Dsxeo1g2Evh5Q==", + "dependencies": { + "@material/animation": "15.0.0-canary.bc9ae6c9c.0", + "@material/base": "15.0.0-canary.bc9ae6c9c.0", + "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", + "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/linear-progress": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/linear-progress/-/linear-progress-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-6EJpjrz6aoH2/gXLg9iMe0yF2C42hpQyZoHpmcgTLKeci85ktDvJIjwup8tnk8ULQyFiGiIrhXw2v2RSsiFjvQ==", + "dependencies": { + "@material/animation": "15.0.0-canary.bc9ae6c9c.0", + "@material/base": "15.0.0-canary.bc9ae6c9c.0", + "@material/dom": "15.0.0-canary.bc9ae6c9c.0", + "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", + "@material/progress-indicator": "15.0.0-canary.bc9ae6c9c.0", + "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", + "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/list": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/list/-/list-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-TQ1ppqiCMQj/P7bGD4edbIIv4goczZUoiUAaPq/feb1dflvrFMzYqJ7tQRRCyBL8nRhJoI2x99tk8Q2RXvlGUQ==", + "dependencies": { + "@material/base": "15.0.0-canary.bc9ae6c9c.0", + "@material/density": "15.0.0-canary.bc9ae6c9c.0", + "@material/dom": "15.0.0-canary.bc9ae6c9c.0", + "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", + "@material/ripple": "15.0.0-canary.bc9ae6c9c.0", + "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", + "@material/shape": "15.0.0-canary.bc9ae6c9c.0", + "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "@material/tokens": "15.0.0-canary.bc9ae6c9c.0", + "@material/typography": "15.0.0-canary.bc9ae6c9c.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/menu": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/menu/-/menu-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-IlAh61xzrzxXs38QZlt74UYt8J431zGznSzDtB1Fqs6YFNd11QPKoiRXn1J2Qu/lUxbFV7i8NBKMCKtia0n6/Q==", + "dependencies": { + "@material/base": "15.0.0-canary.bc9ae6c9c.0", + "@material/dom": "15.0.0-canary.bc9ae6c9c.0", + "@material/elevation": "15.0.0-canary.bc9ae6c9c.0", + "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", + "@material/list": "15.0.0-canary.bc9ae6c9c.0", + "@material/menu-surface": "15.0.0-canary.bc9ae6c9c.0", + "@material/ripple": "15.0.0-canary.bc9ae6c9c.0", + "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", + "@material/shape": "15.0.0-canary.bc9ae6c9c.0", + "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "@material/tokens": "15.0.0-canary.bc9ae6c9c.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/menu-surface": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/menu-surface/-/menu-surface-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-dMtSPN+olTWE+08M5qe4ea1IZOhVryYqzK0Gyb2u1G75rSArUxCOB5rr6OC/ST3Mq3RS6zGuYo7srZt4534K9Q==", + "dependencies": { + "@material/animation": "15.0.0-canary.bc9ae6c9c.0", + "@material/base": "15.0.0-canary.bc9ae6c9c.0", + "@material/elevation": "15.0.0-canary.bc9ae6c9c.0", + "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", + "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", + "@material/shape": "15.0.0-canary.bc9ae6c9c.0", + "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/notched-outline": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/notched-outline/-/notched-outline-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-WuurMg44xexkvLTBTnsO0A+qnzFjpcPdvgWBGstBepYozsvSF9zJGdb1x7Zv1MmqbpYh/Ohnuxtb/Y3jOh6irg==", + "dependencies": { + "@material/base": "15.0.0-canary.bc9ae6c9c.0", + "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", + "@material/floating-label": "15.0.0-canary.bc9ae6c9c.0", + "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", + "@material/shape": "15.0.0-canary.bc9ae6c9c.0", + "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/progress-indicator": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/progress-indicator/-/progress-indicator-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-uOnsvqw5F2fkeTnTl4MrYzjI7KCLmmLyZaM0cgLNuLsWVlddQE+SGMl28tENx7DUK3HebWq0FxCP8f25LuDD+w==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@material/radio": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/radio/-/radio-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-ehzOK+U1IxQN+OQjgD2lsnf1t7t7RAwQzeO6Czkiuid29ookYbQynWuLWk7NW8H8ohl7lnmfqTP1xSNkkL/F0g==", + "dependencies": { + "@material/animation": "15.0.0-canary.bc9ae6c9c.0", + "@material/base": "15.0.0-canary.bc9ae6c9c.0", + "@material/density": "15.0.0-canary.bc9ae6c9c.0", + "@material/dom": "15.0.0-canary.bc9ae6c9c.0", + "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", + "@material/focus-ring": "15.0.0-canary.bc9ae6c9c.0", + "@material/ripple": "15.0.0-canary.bc9ae6c9c.0", + "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "@material/touch-target": "15.0.0-canary.bc9ae6c9c.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/ripple": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/ripple/-/ripple-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-JfLW+g3GMVDv4cruQ19+HUxpKVdWCldFlIPw1UYezz2h3WTNDy05S3uP2zUdXzZ01C3dkBFviv4nqZ0GCT16MA==", + "dependencies": { + "@material/animation": "15.0.0-canary.bc9ae6c9c.0", + "@material/base": "15.0.0-canary.bc9ae6c9c.0", + "@material/dom": "15.0.0-canary.bc9ae6c9c.0", + "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", + "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", + "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/rtl": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/rtl/-/rtl-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-SkKLNLFp5QtG7/JEFg9R92qq4MzTcZ5As6sWbH7rRg6ahTHoJEuqE+pOb9Vrtbj84k5gtX+vCYPvCILtSlr2uw==", + "dependencies": { + "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/segmented-button": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/segmented-button/-/segmented-button-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-YDwkCWP9l5mIZJ7pZJZ2hMDxfBlIGVJ+deNzr8O+Z7/xC5LGXbl4R5aPtUVHygvXAXxpf5096ZD+dSXzYzvWlw==", + "dependencies": { + "@material/base": "15.0.0-canary.bc9ae6c9c.0", + "@material/elevation": "15.0.0-canary.bc9ae6c9c.0", + "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", + "@material/ripple": "15.0.0-canary.bc9ae6c9c.0", + "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "@material/touch-target": "15.0.0-canary.bc9ae6c9c.0", + "@material/typography": "15.0.0-canary.bc9ae6c9c.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/select": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/select/-/select-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-unfOWVf7T0sixVG+3k3RTuATfzqvCF6QAzA6J9rlCh/Tq4HuIBNDdV4z19IVu4zwmgWYxY0iSvqWUvdJJYwakQ==", + "dependencies": { + "@material/animation": "15.0.0-canary.bc9ae6c9c.0", + "@material/base": "15.0.0-canary.bc9ae6c9c.0", + "@material/density": "15.0.0-canary.bc9ae6c9c.0", + "@material/dom": "15.0.0-canary.bc9ae6c9c.0", + "@material/elevation": "15.0.0-canary.bc9ae6c9c.0", + "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", + "@material/floating-label": "15.0.0-canary.bc9ae6c9c.0", + "@material/line-ripple": "15.0.0-canary.bc9ae6c9c.0", + "@material/list": "15.0.0-canary.bc9ae6c9c.0", + "@material/menu": "15.0.0-canary.bc9ae6c9c.0", + "@material/menu-surface": "15.0.0-canary.bc9ae6c9c.0", + "@material/notched-outline": "15.0.0-canary.bc9ae6c9c.0", + "@material/ripple": "15.0.0-canary.bc9ae6c9c.0", + "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", + "@material/shape": "15.0.0-canary.bc9ae6c9c.0", + "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "@material/tokens": "15.0.0-canary.bc9ae6c9c.0", + "@material/typography": "15.0.0-canary.bc9ae6c9c.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/shape": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/shape/-/shape-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-Dsvr771ZKC46ODzoixLdGwlLEQLfxfLrtnRojXABoZf5G3o9KtJU+J+5Ld5aa960OAsCzzANuaub4iR88b1guA==", + "dependencies": { + "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", + "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", + "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/slider": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/slider/-/slider-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-3AEu+7PwW4DSNLndue47dh2u7ga4hDJRYmuu7wnJCIWJBnLCkp6C92kNc4Rj5iQY2ftJio5aj1gqryluh5tlYg==", + "dependencies": { + "@material/animation": "15.0.0-canary.bc9ae6c9c.0", + "@material/base": "15.0.0-canary.bc9ae6c9c.0", + "@material/dom": "15.0.0-canary.bc9ae6c9c.0", + "@material/elevation": "15.0.0-canary.bc9ae6c9c.0", + "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", + "@material/ripple": "15.0.0-canary.bc9ae6c9c.0", + "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", + "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "@material/tokens": "15.0.0-canary.bc9ae6c9c.0", + "@material/typography": "15.0.0-canary.bc9ae6c9c.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/snackbar": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/snackbar/-/snackbar-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-TwwQSYxfGK6mc03/rdDamycND6o+1p61WNd7ElZv1F1CLxB4ihRjbCoH7Qo+oVDaP8CTpjeclka+24RLhQq0mA==", + "dependencies": { + "@material/animation": "15.0.0-canary.bc9ae6c9c.0", + "@material/base": "15.0.0-canary.bc9ae6c9c.0", + "@material/button": "15.0.0-canary.bc9ae6c9c.0", + "@material/dom": "15.0.0-canary.bc9ae6c9c.0", + "@material/elevation": "15.0.0-canary.bc9ae6c9c.0", + "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", + "@material/icon-button": "15.0.0-canary.bc9ae6c9c.0", + "@material/ripple": "15.0.0-canary.bc9ae6c9c.0", + "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", + "@material/shape": "15.0.0-canary.bc9ae6c9c.0", + "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "@material/tokens": "15.0.0-canary.bc9ae6c9c.0", + "@material/typography": "15.0.0-canary.bc9ae6c9c.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/switch": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/switch/-/switch-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-OjUjtT0kRz1ASAsOS+dNzwMwvsjmqy5edK57692qmrP6bL4GblFfBDoiNJ6t0AN4OaKcmL5Hy/xNrTdOZW7Qqw==", + "dependencies": { + "@material/animation": "15.0.0-canary.bc9ae6c9c.0", + "@material/base": "15.0.0-canary.bc9ae6c9c.0", + "@material/density": "15.0.0-canary.bc9ae6c9c.0", + "@material/dom": "15.0.0-canary.bc9ae6c9c.0", + "@material/elevation": "15.0.0-canary.bc9ae6c9c.0", + "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", + "@material/focus-ring": "15.0.0-canary.bc9ae6c9c.0", + "@material/ripple": "15.0.0-canary.bc9ae6c9c.0", + "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", + "@material/shape": "15.0.0-canary.bc9ae6c9c.0", + "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "@material/tokens": "15.0.0-canary.bc9ae6c9c.0", + "safevalues": "^0.3.4", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/tab": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/tab/-/tab-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-s/L9otAwn/pZwVQZBRQJmPqYeNbjoEbzbjMpDQf/VBG/6dJ+aP03ilIBEkqo8NVnCoChqcdtVCoDNRtbU+yp6w==", + "dependencies": { + "@material/base": "15.0.0-canary.bc9ae6c9c.0", + "@material/elevation": "15.0.0-canary.bc9ae6c9c.0", + "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", + "@material/focus-ring": "15.0.0-canary.bc9ae6c9c.0", + "@material/ripple": "15.0.0-canary.bc9ae6c9c.0", + "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", + "@material/tab-indicator": "15.0.0-canary.bc9ae6c9c.0", + "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "@material/tokens": "15.0.0-canary.bc9ae6c9c.0", + "@material/typography": "15.0.0-canary.bc9ae6c9c.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/tab-bar": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/tab-bar/-/tab-bar-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-Xmtq0wJGfu5k+zQeFeNsr4bUKv7L+feCmUp/gsapJ655LQKMXOUQZtSv9ZqWOfrCMy55hoF1CzGFV+oN3tyWWQ==", + "dependencies": { + "@material/animation": "15.0.0-canary.bc9ae6c9c.0", + "@material/base": "15.0.0-canary.bc9ae6c9c.0", + "@material/density": "15.0.0-canary.bc9ae6c9c.0", + "@material/elevation": "15.0.0-canary.bc9ae6c9c.0", + "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", + "@material/tab": "15.0.0-canary.bc9ae6c9c.0", + "@material/tab-indicator": "15.0.0-canary.bc9ae6c9c.0", + "@material/tab-scroller": "15.0.0-canary.bc9ae6c9c.0", + "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "@material/tokens": "15.0.0-canary.bc9ae6c9c.0", + "@material/typography": "15.0.0-canary.bc9ae6c9c.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/tab-indicator": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/tab-indicator/-/tab-indicator-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-despCJYi1GrDDq7F2hvLQkObHnSLZPPDxnOzU16zJ6FNYvIdszgfzn2HgAZ6pl5hLOexQ8cla6cAqjTDuaJBhQ==", + "dependencies": { + "@material/animation": "15.0.0-canary.bc9ae6c9c.0", + "@material/base": "15.0.0-canary.bc9ae6c9c.0", + "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", + "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/tab-scroller": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/tab-scroller/-/tab-scroller-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-QWHG/EWxirj4V9u2IHz+OSY9XCWrnNrPnNgEufxAJVUKV/A8ma1DYeFSQqxhX709R8wKGdycJksg0Flkl7Gq7w==", + "dependencies": { + "@material/animation": "15.0.0-canary.bc9ae6c9c.0", + "@material/base": "15.0.0-canary.bc9ae6c9c.0", + "@material/dom": "15.0.0-canary.bc9ae6c9c.0", + "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", + "@material/tab": "15.0.0-canary.bc9ae6c9c.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/textfield": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/textfield/-/textfield-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-R3qRex9kCaZIAK8DuxPnVC42R0OaW7AB7fsFknDKeTeVQvRcbnV8E+iWSdqTiGdsi6QQHifX8idUrXw+O45zPw==", + "dependencies": { + "@material/animation": "15.0.0-canary.bc9ae6c9c.0", + "@material/base": "15.0.0-canary.bc9ae6c9c.0", + "@material/density": "15.0.0-canary.bc9ae6c9c.0", + "@material/dom": "15.0.0-canary.bc9ae6c9c.0", + "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", + "@material/floating-label": "15.0.0-canary.bc9ae6c9c.0", + "@material/line-ripple": "15.0.0-canary.bc9ae6c9c.0", + "@material/notched-outline": "15.0.0-canary.bc9ae6c9c.0", + "@material/ripple": "15.0.0-canary.bc9ae6c9c.0", + "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", + "@material/shape": "15.0.0-canary.bc9ae6c9c.0", + "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "@material/tokens": "15.0.0-canary.bc9ae6c9c.0", + "@material/typography": "15.0.0-canary.bc9ae6c9c.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/theme": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/theme/-/theme-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-CpUwXGE0dbhxQ45Hu9r9wbJtO/MAlv5ER4tBHA9tp/K+SU+lDgurBE2touFMg5INmdfVNtdumxb0nPPLaNQcUg==", + "dependencies": { + "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/tokens": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/tokens/-/tokens-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-nbEuGj05txWz6ZMUanpM47SaAD7soyjKILR+XwDell9Zg3bGhsnexCNXPEz2fD+YgomS+jM5XmIcaJJHg/H93Q==", + "dependencies": { + "@material/elevation": "15.0.0-canary.bc9ae6c9c.0" + } + }, + "node_modules/@material/tooltip": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/tooltip/-/tooltip-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-UzuXp0b9NuWuYLYpPguxrjbJnCmT/Cco8CkjI/6JajxaeA3o2XEBbQfRMTq8PTafuBjCHTc0b0mQY7rtxUp1Gg==", + "dependencies": { + "@material/animation": "15.0.0-canary.bc9ae6c9c.0", + "@material/base": "15.0.0-canary.bc9ae6c9c.0", + "@material/button": "15.0.0-canary.bc9ae6c9c.0", + "@material/dom": "15.0.0-canary.bc9ae6c9c.0", + "@material/elevation": "15.0.0-canary.bc9ae6c9c.0", + "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", + "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", + "@material/shape": "15.0.0-canary.bc9ae6c9c.0", + "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "@material/tokens": "15.0.0-canary.bc9ae6c9c.0", + "@material/typography": "15.0.0-canary.bc9ae6c9c.0", + "safevalues": "^0.3.4", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/top-app-bar": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/top-app-bar/-/top-app-bar-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-vJWjsvqtdSD5+yQ/9vgoBtBSCvPJ5uF/DVssv8Hdhgs1PYaAcODUi77kdi0+sy/TaWyOsTkQixqmwnFS16zesA==", + "dependencies": { + "@material/animation": "15.0.0-canary.bc9ae6c9c.0", + "@material/base": "15.0.0-canary.bc9ae6c9c.0", + "@material/elevation": "15.0.0-canary.bc9ae6c9c.0", + "@material/ripple": "15.0.0-canary.bc9ae6c9c.0", + "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", + "@material/shape": "15.0.0-canary.bc9ae6c9c.0", + "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "@material/typography": "15.0.0-canary.bc9ae6c9c.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/touch-target": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/touch-target/-/touch-target-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-AqYh9fjt+tv4ZE0C6MeYHblS2H+XwLbDl2mtyrK0DOEnCVQk5/l5ImKDfhrUdFWHvS4a5nBM4AA+sa7KaroLoA==", + "dependencies": { + "@material/base": "15.0.0-canary.bc9ae6c9c.0", + "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", + "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", + "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/typography": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/typography/-/typography-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-CKsG1zyv34AKPNyZC8olER2OdPII64iR2SzQjpqh1UUvmIFiMPk23LvQ1OnC5aCB14pOXzmVgvJt31r9eNdZ6Q==", + "dependencies": { + "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", + "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "tslib": "^2.1.0" + } + }, "node_modules/@ng-bootstrap/ng-bootstrap": { "version": "15.1.1", "resolved": "https://registry.npmjs.org/@ng-bootstrap/ng-bootstrap/-/ng-bootstrap-15.1.1.tgz", @@ -7883,7 +8729,7 @@ "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "dev": true, + "devOptional": true, "engines": { "node": ">=0.12" }, @@ -14856,6 +15702,11 @@ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, + "node_modules/safevalues": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/safevalues/-/safevalues-0.3.4.tgz", + "integrity": "sha512-LRneZZRXNgjzwG4bDQdOTSbze3fHm1EAKN/8bePxnlEZiBmkYEDggaHbuvHI9/hoqHbGfsEA7tWS9GhYHZBBsw==" + }, "node_modules/sass": { "version": "1.64.1", "resolved": "https://registry.npmjs.org/sass/-/sass-1.64.1.tgz", diff --git a/package.json b/package.json index 6f8a4348..29c133f9 100644 --- a/package.json +++ b/package.json @@ -14,11 +14,13 @@ "private": true, "dependencies": { "@angular/animations": "^16.1.4", + "@angular/cdk": "^16.2.9", "@angular/common": "^16.1.4", "@angular/compiler": "^16.1.4", "@angular/core": "^16.1.4", "@angular/forms": "^16.1.4", "@angular/localize": "^16.1.4", + "@angular/material": "^16.2.9", "@angular/platform-browser": "^16.1.4", "@angular/platform-browser-dynamic": "^16.1.4", "@angular/router": "^16.1.4", @@ -98,4 +100,4 @@ "puppeteer": "^20.9.0", "typescript": "~5.1.6" } -} +} \ No newline at end of file From f83792bc78d02166314835e34b8738450f1ff928 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Mon, 30 Oct 2023 22:02:28 +0100 Subject: [PATCH 113/419] New action menu base component --- .../action-menu/action-menu.component.html | 17 +++++++++++ .../action-menu/action-menu.component.ts | 30 +++++++++++++++++++ .../menus/action-menu/action-menu.model.ts | 11 +++++++ 3 files changed, 58 insertions(+) create mode 100644 src/app/core/_components/menus/action-menu/action-menu.component.html create mode 100644 src/app/core/_components/menus/action-menu/action-menu.component.ts create mode 100644 src/app/core/_components/menus/action-menu/action-menu.model.ts diff --git a/src/app/core/_components/menus/action-menu/action-menu.component.html b/src/app/core/_components/menus/action-menu/action-menu.component.html new file mode 100644 index 00000000..80d445c4 --- /dev/null +++ b/src/app/core/_components/menus/action-menu/action-menu.component.html @@ -0,0 +1,17 @@ + + + + + + + \ No newline at end of file diff --git a/src/app/core/_components/menus/action-menu/action-menu.component.ts b/src/app/core/_components/menus/action-menu/action-menu.component.ts new file mode 100644 index 00000000..afb781ed --- /dev/null +++ b/src/app/core/_components/menus/action-menu/action-menu.component.ts @@ -0,0 +1,30 @@ +/* eslint-disable @angular-eslint/component-selector */ +import { Component, EventEmitter, Input, Output } from '@angular/core'; +import { ActionMenuEvent, ActionMenuItem } from './action-menu.model'; + +@Component({ + selector: 'action-menu', + templateUrl: './action-menu.component.html' +}) +export class ActionMenuComponent { + + @Input() icon: string; + @Input() label: string; + @Input() disabled = false; + @Input() cls = '' + @Input() data: any; + @Input() actionMenuItems: ActionMenuItem[][] = [] + + @Output() menuItemClicked: EventEmitter> = new EventEmitter>(); + + /** + * Handle the click event when a menu item is selected. + * @param menuItem - The selected menu item. + */ + onMenuItemClick(menuItem: ActionMenuItem): void { + this.menuItemClicked.emit({ + menuItem: menuItem, + data: this.data + }); + } +} \ No newline at end of file diff --git a/src/app/core/_components/menus/action-menu/action-menu.model.ts b/src/app/core/_components/menus/action-menu/action-menu.model.ts new file mode 100644 index 00000000..013dcf10 --- /dev/null +++ b/src/app/core/_components/menus/action-menu/action-menu.model.ts @@ -0,0 +1,11 @@ +export interface ActionMenuEvent { + menuItem: ActionMenuItem + data: T +} + +export interface ActionMenuItem { + label: string + action: string + icon?: string + red?: boolean +} \ No newline at end of file From d86f34f6a319a8ed1a60a4304404fafe56ce990b Mon Sep 17 00:00:00 2001 From: itbf9 Date: Mon, 30 Oct 2023 22:03:23 +0100 Subject: [PATCH 114/419] New row action menu --- .../row-action-menu.component.html | 2 + .../row-action-menu.component.ts | 88 +++++++++++++++++++ .../row-action-menu.constants.ts | 19 ++++ 3 files changed, 109 insertions(+) create mode 100644 src/app/core/_components/menus/row-action-menu/row-action-menu.component.html create mode 100644 src/app/core/_components/menus/row-action-menu/row-action-menu.component.ts create mode 100644 src/app/core/_components/menus/row-action-menu/row-action-menu.constants.ts diff --git a/src/app/core/_components/menus/row-action-menu/row-action-menu.component.html b/src/app/core/_components/menus/row-action-menu/row-action-menu.component.html new file mode 100644 index 00000000..b891203a --- /dev/null +++ b/src/app/core/_components/menus/row-action-menu/row-action-menu.component.html @@ -0,0 +1,2 @@ + \ No newline at end of file diff --git a/src/app/core/_components/menus/row-action-menu/row-action-menu.component.ts b/src/app/core/_components/menus/row-action-menu/row-action-menu.component.ts new file mode 100644 index 00000000..585cc74b --- /dev/null +++ b/src/app/core/_components/menus/row-action-menu/row-action-menu.component.ts @@ -0,0 +1,88 @@ +/* eslint-disable @angular-eslint/component-selector */ +import { Component, OnInit } from "@angular/core"; +import { RowActionMenuAction, RowActionMenuLabel } from "./row-action-menu.constants"; +import { BaseMenuComponent } from "../base-menu/base-menu.component"; + + +@Component({ + selector: 'row-action-menu', + templateUrl: './row-action-menu.component.html' +}) +export class RowActionMenuComponent extends BaseMenuComponent implements OnInit { + + ngOnInit(): void { + if (this.isAgent()) { + this.getAgentMenu(); + } else if (this.isTask()) { + this.getTaskMenu(); + } + } + + /** + * Get the context menu items for an agent data row. + */ + private getAgentMenu(): void { + this.actionMenuItems[0] = [ + { + label: RowActionMenuLabel.EDIT_AGENT, + action: RowActionMenuAction.EDIT, + icon: 'edit' + } + ]; + this.actionMenuItems[1] = [ + { + label: RowActionMenuLabel.DELETE_AGENT, + action: RowActionMenuAction.DELETE, + icon: 'delete', + red: true + } + ]; + } + + /** + * Get the context menu items for a task data row. + */ + private getTaskMenu(): void { + this.actionMenuItems[0] = [ + { + label: RowActionMenuLabel.EDIT_TASK, + action: RowActionMenuAction.EDIT, + icon: 'edit' + }, + ]; + + this.actionMenuItems[1] = [ + { + label: RowActionMenuLabel.DELETE_TASK, + action: RowActionMenuAction.DELETE, + icon: 'delete', + red: true, + } + ]; + + if (this.data.taskType === 0) { + this.actionMenuItems[0].push({ + label: RowActionMenuLabel.COPY_TO_TASK, + action: RowActionMenuAction.COPY_TO_TASK, + icon: 'content_copy' + }); + this.actionMenuItems[0].push({ + label: RowActionMenuLabel.COPY_TO_PRETASK, + action: RowActionMenuAction.COPY_TO_PRETASK, + icon: 'content_copy' + }); + } else if (this.data.taskType === 1) { + this.actionMenuItems[0].push({ + label: RowActionMenuLabel.EDIT_SUBTASKS, + action: RowActionMenuAction.EDIT_SUBTASKS, + icon: 'edit' + }); + } + + this.actionMenuItems[0].push({ + label: RowActionMenuLabel.ARCHIVE_TASK, + action: RowActionMenuAction.ARCHIVE, + icon: 'archive' + }); + } +} \ No newline at end of file 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 new file mode 100644 index 00000000..87fa0f6a --- /dev/null +++ b/src/app/core/_components/menus/row-action-menu/row-action-menu.constants.ts @@ -0,0 +1,19 @@ +export class RowActionMenuLabel { + static readonly EDIT_AGENT = 'Edit'; + static readonly DELETE_AGENT = 'Delete'; + static readonly EDIT_TASK = 'Edit'; + static readonly COPY_TO_TASK = 'Copy to Task'; + static readonly COPY_TO_PRETASK = 'Copy to Pretask'; + static readonly EDIT_SUBTASKS = 'Edit Subtasks'; + static readonly ARCHIVE_TASK = 'Archive'; + static readonly DELETE_TASK = 'Delete'; +} + +export class RowActionMenuAction { + static readonly EDIT = 'edit'; + static readonly DELETE = 'delete'; + static readonly ARCHIVE = 'archive'; + static readonly COPY_TO_TASK = 'copy-to-task'; + static readonly COPY_TO_PRETASK = 'copy-to-pretask'; + static readonly EDIT_SUBTASKS = 'edit-subtasks'; +} \ No newline at end of file From 47c5133c90c5d168dcfa3e4a6db216c4dd601152 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Mon, 30 Oct 2023 22:03:39 +0100 Subject: [PATCH 115/419] New bulk action menu --- .../bulk-action-menu.component.html | 2 + .../bulk-action-menu.component.ts | 65 +++++++++++++++++++ .../bulk-action-menu.constants.ts | 14 ++++ 3 files changed, 81 insertions(+) create mode 100644 src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.component.html create mode 100644 src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.component.ts create mode 100644 src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.constants.ts diff --git a/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.component.html b/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.component.html new file mode 100644 index 00000000..cef10e34 --- /dev/null +++ b/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.component.html @@ -0,0 +1,2 @@ + \ No newline at end of file diff --git a/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.component.ts b/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.component.ts new file mode 100644 index 00000000..84f0c9c2 --- /dev/null +++ b/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.component.ts @@ -0,0 +1,65 @@ +/* eslint-disable @angular-eslint/component-selector */ +import { Component, Input, OnInit, } from '@angular/core'; +import { BaseMenuComponent } from '../base-menu/base-menu.component'; +import { BulkActionMenuAction, BulkActionMenuLabel } from './bulk-action-menu.constants'; +import { DataType } from '../../ht-table/ht-table.models'; + +@Component({ + selector: 'bulk-action-menu', + templateUrl: './bulk-action-menu.component.html' +}) +export class BulkActionMenuComponent extends BaseMenuComponent implements OnInit { + + @Input() dataType: DataType + + ngOnInit(): void { + if (this.dataType === 'agents') { + this.getAgentMenu(); + } else if (this.dataType === 'tasks') { + this.getTaskMenu(); + } + } + + private getTaskMenu(): void { + this.actionMenuItems[0] = [ + { + label: BulkActionMenuLabel.ARCHIVE_TASKS, + action: BulkActionMenuAction.ARCHIVE, + icon: 'archive', + } + ]; + + this.actionMenuItems[1] = [ + { + label: BulkActionMenuLabel.DELETE_TASKS, + action: BulkActionMenuAction.DELETE, + icon: 'delete', + red: true + } + ]; + } + + private getAgentMenu(): void { + this.actionMenuItems[0] = [ + { + label: BulkActionMenuLabel.ACTIVATE_AGENTS, + action: BulkActionMenuAction.ACTIVATE, + icon: 'radio_button_checked', + }, + { + label: BulkActionMenuLabel.DEACTIVATE_AGENTS, + action: BulkActionMenuAction.DEACTIVATE, + icon: 'radio_button_unchecked', + } + ]; + + this.actionMenuItems[1] = [ + { + label: BulkActionMenuLabel.DELETE_AGENTS, + action: BulkActionMenuAction.DELETE, + icon: 'delete', + red: true + } + ]; + } +} \ No newline at end of file diff --git a/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.constants.ts b/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.constants.ts new file mode 100644 index 00000000..b1023d98 --- /dev/null +++ b/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.constants.ts @@ -0,0 +1,14 @@ +export class BulkActionMenuLabel { + static readonly DELETE_AGENTS = 'Delete Agents'; + static readonly ACTIVATE_AGENTS = 'Activate Agents'; + static readonly DEACTIVATE_AGENTS = 'Deactivate Agents'; + static readonly ARCHIVE_TASKS = 'Archive Tasks'; + static readonly DELETE_TASKS = 'Delete Tasks'; +} + +export class BulkActionMenuAction { + static readonly DELETE = 'bulk-delete'; + static readonly ACTIVATE = 'bulk-activate'; + static readonly DEACTIVATE = 'bulk-deactivate'; + static readonly ARCHIVE = 'bulk-archive'; +} \ No newline at end of file From 723346528b3ee4f5b45b56bcaa6824ddb6f1af3a Mon Sep 17 00:00:00 2001 From: itbf9 Date: Mon, 30 Oct 2023 22:04:34 +0100 Subject: [PATCH 116/419] New base menu with common functionality for menu implementations --- .../menus/base-menu/base-menu.component.ts | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 src/app/core/_components/menus/base-menu/base-menu.component.ts diff --git a/src/app/core/_components/menus/base-menu/base-menu.component.ts b/src/app/core/_components/menus/base-menu/base-menu.component.ts new file mode 100644 index 00000000..0c0bef13 --- /dev/null +++ b/src/app/core/_components/menus/base-menu/base-menu.component.ts @@ -0,0 +1,47 @@ +/* eslint-disable @angular-eslint/component-selector */ +import { Component, EventEmitter, Input, Output } from '@angular/core'; +import { ActionMenuEvent, ActionMenuItem } from '../action-menu/action-menu.model'; + +@Component({ + selector: 'base-menu', + template: '' +}) +export class BaseMenuComponent { + + @Input() disabled = false; + @Input() data: any; + + @Output() menuItemClicked: EventEmitter> = new EventEmitter>(); + + + actionMenuItems: ActionMenuItem[][] = []; + + /** + * Check if the data row is of type "Agent." + * @returns `true` if the data row is an agent; otherwise, `false`. + */ + protected isAgent(): boolean { + try { + return this.data['_id'] === this.data['agentId']; + } catch (error) { + return false; + } + } + + /** + * Check if the data row is of type "Task." + * @returns `true` if the data row is a task; otherwise, `false`. + */ + protected isTask(): boolean { + try { + return this.data['_id'] === this.data['taskId']; + } catch (error) { + return false; + } + } + + onMenuItemClick(event: ActionMenuEvent): void { + this.menuItemClicked.emit(event); + } + +} \ No newline at end of file From c10d84cc54002226066a5a2a6626479ca083ebe8 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Mon, 30 Oct 2023 22:07:11 +0100 Subject: [PATCH 117/419] New dialog component used in table implementations --- .../table-dialog/table-dialog.component.html | 16 ++++++ .../table-dialog/table-dialog.component.ts | 53 +++++++++++++++++++ .../table-dialog/table-dialog.model.ts | 11 ++++ 3 files changed, 80 insertions(+) create mode 100644 src/app/core/_components/table-dialog/table-dialog.component.html create mode 100644 src/app/core/_components/table-dialog/table-dialog.component.ts create mode 100644 src/app/core/_components/table-dialog/table-dialog.model.ts diff --git a/src/app/core/_components/table-dialog/table-dialog.component.html b/src/app/core/_components/table-dialog/table-dialog.component.html new file mode 100644 index 00000000..a69d05ac --- /dev/null +++ b/src/app/core/_components/table-dialog/table-dialog.component.html @@ -0,0 +1,16 @@ +

+ {{ data.icon }} + {{ data.title }} +

+
+
    +
  • {{ row[data.listAttribute] }}
  • +
+

{{ data.body }}

+
+
+ + +
\ No newline at end of file diff --git a/src/app/core/_components/table-dialog/table-dialog.component.ts b/src/app/core/_components/table-dialog/table-dialog.component.ts new file mode 100644 index 00000000..2bc61cf2 --- /dev/null +++ b/src/app/core/_components/table-dialog/table-dialog.component.ts @@ -0,0 +1,53 @@ +/* eslint-disable @angular-eslint/component-selector */ +import { Component, Inject } from "@angular/core"; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { DialogData } from "./table-dialog.model"; + +/** + * A reusable Angular Material dialog component for displaying custom messages and actions. + * + * Usage: + * ``` + * + * openDialog() { + * const dialogRef = this.dialog.open(TableDialogComponent, { + * data: { + * title: 'Confirmation', + * body: 'Are you sure you want to continue?', + * action: 'confirm', + * rows: [{ hello: 'world' }] + * }, + * }); + * + * dialogRef.afterClosed().subscribe(result => { + * if (result?.action === 'confirm') { + * // Perform the confirmation action here + * } + * }); + * } + * ``` + * @template T - The type of data contained in the dialog. + */ +@Component({ + selector: 'table-dialog', + templateUrl: 'table-dialog.component.html', +}) +export class TableDialogComponent { + /** + * Creates an instance of TableDialogComponent. + * + * @param dialogRef - Reference to the MatDialogRef for managing the dialog. + * @param data - Data used to configure the dialog's content and behavior. + */ + constructor( + public dialogRef: MatDialogRef>, + @Inject(MAT_DIALOG_DATA) public data: DialogData + ) { } + + /** + * Handles the click event on the "Cancel" button, closing the dialog. + */ + onNoClick(): void { + this.dialogRef.close(); + } +} \ No newline at end of file diff --git a/src/app/core/_components/table-dialog/table-dialog.model.ts b/src/app/core/_components/table-dialog/table-dialog.model.ts new file mode 100644 index 00000000..85f5e558 --- /dev/null +++ b/src/app/core/_components/table-dialog/table-dialog.model.ts @@ -0,0 +1,11 @@ +export interface DialogData { + icon?: string + title: string + body?: string + warn?: boolean + cancelLabel?: string + okLabel?: string + rows: T[], + action: string, + listAttribute?: string +} \ No newline at end of file From cf52dfa2e4a76c18547cf8b7c73c060ab4d04d21 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Mon, 30 Oct 2023 22:07:46 +0100 Subject: [PATCH 118/419] New column selection dialog used in tables --- .../column-selection-dialog.component.html | 12 ++++++++++ .../column-selection-dialog.component.ts | 24 +++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 src/app/core/_components/column-selection-dialog/column-selection-dialog.component.html create mode 100644 src/app/core/_components/column-selection-dialog/column-selection-dialog.component.ts diff --git a/src/app/core/_components/column-selection-dialog/column-selection-dialog.component.html b/src/app/core/_components/column-selection-dialog/column-selection-dialog.component.html new file mode 100644 index 00000000..1dc02de0 --- /dev/null +++ b/src/app/core/_components/column-selection-dialog/column-selection-dialog.component.html @@ -0,0 +1,12 @@ +

Select Columns

+ + + + {{ column }} + + + + + + + \ No newline at end of file diff --git a/src/app/core/_components/column-selection-dialog/column-selection-dialog.component.ts b/src/app/core/_components/column-selection-dialog/column-selection-dialog.component.ts new file mode 100644 index 00000000..5d947417 --- /dev/null +++ b/src/app/core/_components/column-selection-dialog/column-selection-dialog.component.ts @@ -0,0 +1,24 @@ +import { Component, Inject } from '@angular/core'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; + +@Component({ + selector: 'app-column-selection-dialog', + templateUrl: './column-selection-dialog.component.html', +}) +export class ColumnSelectionDialogComponent { + availableColumns: string[]; + selectedColumns: string[]; + + constructor( + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: { availableColumns: string[], selectedColumns: string[] } + ) { + // Initialize selectedColumns with the default columns + this.selectedColumns = [...data.selectedColumns]; + this.availableColumns = [...data.availableColumns]; + } + + closeDialog(): void { + this.dialogRef.close(); + } +} \ No newline at end of file From 9a8df7c0655da6afeada347eb803e88c8e6a3706 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Mon, 30 Oct 2023 22:08:27 +0100 Subject: [PATCH 119/419] New base hashtopolis table --- .../ht-table/ht-table.component.html | 159 +++++++++++ .../ht-table/ht-table.component.ts | 262 ++++++++++++++++++ .../_components/ht-table/ht-table.models.ts | 20 ++ 3 files changed, 441 insertions(+) create mode 100644 src/app/core/_components/ht-table/ht-table.component.html create mode 100644 src/app/core/_components/ht-table/ht-table.component.ts create mode 100644 src/app/core/_components/ht-table/ht-table.models.ts diff --git a/src/app/core/_components/ht-table/ht-table.component.html b/src/app/core/_components/ht-table/ht-table.component.html new file mode 100644 index 00000000..baa8bf87 --- /dev/null +++ b/src/app/core/_components/ht-table/ht-table.component.html @@ -0,0 +1,159 @@ + + +
+ +
+ +
+ +
+
+ +
+ + + +
+ +
+ + + + + + + +
+ +
+ +
+ +
+ +
+
+ + +
diff --git a/src/app/tasks/new-tasks/new-tasks.component.ts b/src/app/tasks/new-tasks/new-tasks.component.ts index 0982a64e..6c3bf00d 100644 --- a/src/app/tasks/new-tasks/new-tasks.component.ts +++ b/src/app/tasks/new-tasks/new-tasks.component.ts @@ -227,6 +227,7 @@ export class NewTasksComponent implements OnInit { this.dtOptions = { dom: 'Bfrtip', + scrollX: true, scrollY: "700px", scrollCollapse: true, paging: false, diff --git a/src/app/tasks/preconfigured-tasks/preconfigured-tasks.component.html b/src/app/tasks/preconfigured-tasks/preconfigured-tasks.component.html index d1c94a3b..eac4b000 100644 --- a/src/app/tasks/preconfigured-tasks/preconfigured-tasks.component.html +++ b/src/app/tasks/preconfigured-tasks/preconfigured-tasks.component.html @@ -1,7 +1,7 @@ - +
diff --git a/src/app/tasks/preconfigured-tasks/preconfigured-tasks.component.ts b/src/app/tasks/preconfigured-tasks/preconfigured-tasks.component.ts index e66ae99d..027f044b 100644 --- a/src/app/tasks/preconfigured-tasks/preconfigured-tasks.component.ts +++ b/src/app/tasks/preconfigured-tasks/preconfigured-tasks.component.ts @@ -52,6 +52,7 @@ export class PreconfiguredTasksComponent implements OnInit { const self = this; this.dtOptions = { dom: 'Bfrtip', + scrollX: true, pageLength: 10, stateSave: true, select: true, 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 8ac86cd2..499dadb6 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 @@ -5,7 +5,7 @@
ID
+
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 14485b31..e65754a1 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 @@ -47,6 +47,7 @@ export class ModalSubtasksComponent { this.dtOptions = { dom: 'Bfrtip', + scrollX: true, destroy: true, buttons:[] }; diff --git a/src/app/tasks/show-tasks/show-tasks.component.html b/src/app/tasks/show-tasks/show-tasks.component.html index e4bdd9e6..6569cda6 100644 --- a/src/app/tasks/show-tasks/show-tasks.component.html +++ b/src/app/tasks/show-tasks/show-tasks.component.html @@ -8,7 +8,7 @@ -
ID
+
diff --git a/src/app/tasks/show-tasks/show-tasks.component.ts b/src/app/tasks/show-tasks/show-tasks.component.ts index 02eb4d37..49e1e9fe 100644 --- a/src/app/tasks/show-tasks/show-tasks.component.ts +++ b/src/app/tasks/show-tasks/show-tasks.component.ts @@ -89,6 +89,7 @@ export class ShowTasksComponent implements OnInit { const self = this; this.dtOptions = { dom: 'Bfrtip', + scrollX: true, bStateSave:true, destroy: true, order: [], // Removes the default order by id. We need it to sort by priority. diff --git a/src/app/tasks/supertasks/modal-pretasks/modal-pretasks.component.html b/src/app/tasks/supertasks/modal-pretasks/modal-pretasks.component.html index e283164c..266c4b7c 100644 --- a/src/app/tasks/supertasks/modal-pretasks/modal-pretasks.component.html +++ b/src/app/tasks/supertasks/modal-pretasks/modal-pretasks.component.html @@ -5,7 +5,7 @@
ID
+
diff --git a/src/app/tasks/supertasks/modal-pretasks/modal-pretasks.component.ts b/src/app/tasks/supertasks/modal-pretasks/modal-pretasks.component.ts index 90678f73..18c484f0 100644 --- a/src/app/tasks/supertasks/modal-pretasks/modal-pretasks.component.ts +++ b/src/app/tasks/supertasks/modal-pretasks/modal-pretasks.component.ts @@ -42,6 +42,7 @@ export class ModalPretasksComponent implements OnInit { this.dtOptions = { dom: 'Bfrtip', + scrollX: true, destroy: true, buttons:[] }; diff --git a/src/app/tasks/supertasks/supertasks.component.html b/src/app/tasks/supertasks/supertasks.component.html index bbdf5101..adf79bd8 100644 --- a/src/app/tasks/supertasks/supertasks.component.html +++ b/src/app/tasks/supertasks/supertasks.component.html @@ -1,7 +1,7 @@ -
ID
+
diff --git a/src/app/users/all-users/all-users.component.html b/src/app/users/all-users/all-users.component.html index 6ba8055c..6ab7b6b2 100644 --- a/src/app/users/all-users/all-users.component.html +++ b/src/app/users/all-users/all-users.component.html @@ -1,7 +1,7 @@ -
ID
+
diff --git a/src/app/users/all-users/all-users.component.ts b/src/app/users/all-users/all-users.component.ts index d153e7d9..7d7dde8c 100644 --- a/src/app/users/all-users/all-users.component.ts +++ b/src/app/users/all-users/all-users.component.ts @@ -69,6 +69,7 @@ export class AllUsersComponent implements OnInit, OnDestroy { const self = this; this.dtOptions = { dom: 'Bfrtip', + scrollX: true, pageLength: 10, stateSave: true, // "stateLoadParams": function (settings, data) { diff --git a/src/app/users/globalpermissionsgroups/edit-globalpermissionsgroups/edit-globalpermissionsgroups.component.html b/src/app/users/globalpermissionsgroups/edit-globalpermissionsgroups/edit-globalpermissionsgroups.component.html index 72035076..44276a49 100644 --- a/src/app/users/globalpermissionsgroups/edit-globalpermissionsgroups/edit-globalpermissionsgroups.component.html +++ b/src/app/users/globalpermissionsgroups/edit-globalpermissionsgroups/edit-globalpermissionsgroups.component.html @@ -9,7 +9,7 @@

Permission Group {{ editedGPG.name | lowercase | titlecase }}<
  -

ID
+
diff --git a/src/app/users/globalpermissionsgroups/globalpermissionsgroups.component.html b/src/app/users/globalpermissionsgroups/globalpermissionsgroups.component.html index 40278ba0..a03d586d 100644 --- a/src/app/users/globalpermissionsgroups/globalpermissionsgroups.component.html +++ b/src/app/users/globalpermissionsgroups/globalpermissionsgroups.component.html @@ -1,7 +1,7 @@ -
ID
+
diff --git a/src/app/users/globalpermissionsgroups/globalpermissionsgroups.component.ts b/src/app/users/globalpermissionsgroups/globalpermissionsgroups.component.ts index 234b21a4..98e59ecd 100644 --- a/src/app/users/globalpermissionsgroups/globalpermissionsgroups.component.ts +++ b/src/app/users/globalpermissionsgroups/globalpermissionsgroups.component.ts @@ -50,6 +50,7 @@ export class GlobalpermissionsgroupsComponent implements OnInit { const self = this; this.dtOptions = { dom: 'Bfrtip', + scrollX: true, pageLength: 10, select: true, processing: true, // Error loading diff --git a/src/app/users/groups/groups.component.html b/src/app/users/groups/groups.component.html index 83e70c76..164caab0 100644 --- a/src/app/users/groups/groups.component.html +++ b/src/app/users/groups/groups.component.html @@ -1,7 +1,7 @@ -
ID
+
diff --git a/src/app/users/groups/groups.component.ts b/src/app/users/groups/groups.component.ts index b765cc0c..972fbc28 100644 --- a/src/app/users/groups/groups.component.ts +++ b/src/app/users/groups/groups.component.ts @@ -55,6 +55,7 @@ export class GroupsComponent implements OnInit { const self = this; this.dtOptions = { dom: 'Bfrtip', + scrollX: true, pageLength: 10, select: true, processing: true, // Error loading diff --git a/src/styles/components/_table.scss b/src/styles/components/_table.scss index 254fa276..19d5bac3 100644 --- a/src/styles/components/_table.scss +++ b/src/styles/components/_table.scss @@ -97,6 +97,7 @@ div.dt-button-collection button.dt-button, div.dt-button-collection div.dt-butto display: block !important;; padding: 0.5em 0 !important; margin: 4px 0 2px 0 !important; + overflow-x:visible !important; } div.dt-button-background:not(.disabled){ @@ -111,14 +112,36 @@ div.dt-button-background:not(.disabled){ div.dt-button-collection { background-color: #dadada; - background-color: rgba(0, 0, 0, 0) !important; - color: #ffffff !important; + color: #c71919 !important; border-color: #4B5563 !important; padding: 4px 4px 2px !important; line-height: 1.6em !important; + overflow-x:visible !important; + filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr="#f0f0f0", EndColorStr="#dadada"); } +.btn-overflow { + overflow-x:visible !important; +} + +.btn-actions{ + margin-top: -3px !important; + margin-bottom: 0px !important; +} + +.table.dataTable.table-hover>tbody>tr:hover>*{ + background-color: rgb(249, 249, 167); +} + +.btn-actions { + vertical-align: center; + font-size: .680rem; + font-weight: 400; + font-weight: normal; +} + + div.dt-button-collection span { color: #ffffff; } From a7051326d78005a4783ef4473295622945d1c82f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Wed, 6 Sep 2023 22:10:30 +0100 Subject: [PATCH 010/419] All pages-Change file size scale --- src/app/core/_pipes/file-size.pipe.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/app/core/_pipes/file-size.pipe.ts b/src/app/core/_pipes/file-size.pipe.ts index fb4fad29..b1d55e88 100644 --- a/src/app/core/_pipes/file-size.pipe.ts +++ b/src/app/core/_pipes/file-size.pipe.ts @@ -25,7 +25,7 @@ const FILE_SIZE_UNITS_LONG = ['Bytes', 'Kilobytes', 'Megabytes', 'Gigabytes', 'P }) export class FileSizePipe implements PipeTransform { - transform(sizeB: number, longForm: boolean, BASE_SIZE = 1024): string { + transform(sizeB: number, longForm: boolean, BASE_SIZE = 1024, THRESHOLD = 1024): string { const units = longForm ? FILE_SIZE_UNITS_LONG : FILE_SIZE_UNITS; @@ -33,10 +33,12 @@ export class FileSizePipe implements PipeTransform { if (sizeB < 1 ) { return result = '0 B'; } - let power = Math.round(Math.log(sizeB) / Math.log(BASE_SIZE)); + const scale = sizeB > THRESHOLD ? (sizeB / THRESHOLD):sizeB; //Change scale. ie. 113.44MB instead of 0.13GB + + let power = (Math.round(Math.log(scale) / Math.log(BASE_SIZE))); power = Math.min(power, units.length - 1); - const size = sizeB / Math.pow(BASE_SIZE, power); // size in new units + const size = (sizeB) / Math.pow(BASE_SIZE, power); // size in new units const formattedSize = Math.round(size * 100) / 100; // keep up to 2 decimals const unit = units[power]; From 18b0ba495e92ee4f62c842d88140b59d17f647c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Wed, 6 Sep 2023 22:33:45 +0100 Subject: [PATCH 011/419] All pages-Change file size scale PartII --- .../files/new-files/new-files.component.ts | 11 +++++---- .../new-hashlist/new-hashlist.component.ts | 9 +++---- src/app/shared/utils/util.ts | 24 ------------------- 3 files changed, 11 insertions(+), 33 deletions(-) diff --git a/src/app/files/new-files/new-files.component.ts b/src/app/files/new-files/new-files.component.ts index 7428bea5..2e48b41d 100644 --- a/src/app/files/new-files/new-files.component.ts +++ b/src/app/files/new-files/new-files.component.ts @@ -6,8 +6,9 @@ import { Observable } from 'rxjs'; import { Buffer } from 'buffer'; import { UploadTUSService } from 'src/app/core/_services/files/files_tus.service'; -import { fileSizeValue, validateFileExt } from '../../shared/utils/util'; +import { validateFileExt } from '../../shared/utils/util'; import { GlobalService } from 'src/app/core/_services/main.service'; +import { FileSizePipe } from 'src/app/core/_pipes/file-size.pipe'; import { environment } from './../../../environments/environment'; import { PageTitle } from 'src/app/core/_decorators/autotitle'; import { UploadFileTUS } from '../../core/_models/files'; @@ -16,7 +17,8 @@ import { ActivatedRoute, Router } from '@angular/router'; @Component({ selector: 'app-new-files', - templateUrl: './new-files.component.html' + templateUrl: './new-files.component.html', + providers: [FileSizePipe] }) @PageTitle(['New File']) export class NewFilesComponent implements OnInit { @@ -33,6 +35,7 @@ export class NewFilesComponent implements OnInit { private uploadService:UploadTUSService, private route:ActivatedRoute, private gs: GlobalService, + private fs:FileSizePipe, private router: Router ) { } @@ -172,8 +175,6 @@ souceType(type: string, view: string){ this.isHovering = event; } - fileSizeValue = fileSizeValue; - validateFileExt = validateFileExt; selectedFile: ''; @@ -186,7 +187,7 @@ souceType(type: string, view: string){ this.fileToUpload = event.target.files[0]; this.fileSize = this.fileToUpload.size; this.fileName = this.fileToUpload.name; - $('.fileuploadspan').text(fileSizeValue(this.fileToUpload.size)); + $('.fileuploadspan').text(this.fs.transform(this.fileToUpload.size,false)); } // To use as Button diff --git a/src/app/hashlists/new-hashlist/new-hashlist.component.ts b/src/app/hashlists/new-hashlist/new-hashlist.component.ts index 0fdaf2e4..227cf5c9 100644 --- a/src/app/hashlists/new-hashlist/new-hashlist.component.ts +++ b/src/app/hashlists/new-hashlist/new-hashlist.component.ts @@ -10,16 +10,18 @@ import { Buffer } from 'buffer'; import { UIConfigService } from 'src/app/core/_services/shared/storage.service'; import { UploadTUSService } from '../../core/_services/files/files_tus.service'; -import { fileSizeValue, validateFileExt } from '../../shared/utils/util'; import { GlobalService } from 'src/app/core/_services/main.service'; +import { FileSizePipe } from 'src/app/core/_pipes/file-size.pipe'; import { PageTitle } from 'src/app/core/_decorators/autotitle'; import { ShowHideTypeFile } from '../../shared/utils/forms'; +import { validateFileExt } from '../../shared/utils/util'; import { UploadFileTUS } from '../../core/_models/files'; import { SERV } from '../../core/_services/main.config'; @Component({ selector: 'app-new-hashlist', templateUrl: './new-hashlist.component.html', + providers: [FileSizePipe], changeDetection: ChangeDetectionStrategy.OnPush }) @PageTitle(['New Hashlist']) @@ -55,6 +57,7 @@ export class NewHashlistComponent implements OnInit { private uiService: UIConfigService, private modalService: NgbModal, private gs: GlobalService, + private fs:FileSizePipe, private router: Router, ) { } @@ -176,8 +179,6 @@ export class NewHashlistComponent implements OnInit { * @param event */ - fileSizeValue = fileSizeValue; - validateFileExt = validateFileExt; fileGroup: number; @@ -189,7 +190,7 @@ export class NewHashlistComponent implements OnInit { this.fileToUpload = event.target.files[0]; this.fileSize = this.fileToUpload.size; this.fileName = this.fileToUpload.name; - $('.fileuploadspan').text('Size: '+fileSizeValue(this.fileToUpload.size)); + $('.fileuploadspan').text('Size: '+this.fs.transform(this.fileToUpload.size,false)); } /** diff --git a/src/app/shared/utils/util.ts b/src/app/shared/utils/util.ts index ec7e34ad..92f960c1 100644 --- a/src/app/shared/utils/util.ts +++ b/src/app/shared/utils/util.ts @@ -5,30 +5,6 @@ * Comments use: https://tsdoc.org/ */ - -/** - * Converts file value to a more readable format - * - * - * @param size - Value you want formatted - * @returns Value in MB, GB, etc.. i.e 133.4 MB - * ``` - * @public - */ - -export function fileSizeValue(size: number): number | string { - const units: string[] = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB']; - const BASE_SIZE = 1024; - let result: number | string = 0; - - if (size < 1 ) { return result = '0 B'; } - - const i = Math.floor(Math.log(size) / Math.log(BASE_SIZE)) - - return `${parseFloat((size / Math.pow(BASE_SIZE, i)).toFixed(2))} ${units[i]}` - -} - /** * Validate the file extension * Notes: This function is not in place but it could be usefule in the section of files From 605dba43138b61b4593032beab364d82af3214e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Thu, 7 Sep 2023 14:48:52 +0100 Subject: [PATCH 012/419] #925 - Dont trown error in request config.json --- src/app/core/_interceptors/http-res.interceptor.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/app/core/_interceptors/http-res.interceptor.ts b/src/app/core/_interceptors/http-res.interceptor.ts index 099b4e0c..a7be2c7a 100644 --- a/src/app/core/_interceptors/http-res.interceptor.ts +++ b/src/app/core/_interceptors/http-res.interceptor.ts @@ -40,7 +40,7 @@ export class HttpResInterceptor implements HttpInterceptor{ if(error.status === 403){ errmsg = `You don't have permissions. Please contact your Administrator.`; } - if(error.status === 404){ + if(error.status === 404 && !req.url.includes('config.json')){ errmsg = `The requested URL was not found.`; } // if(error.status !== 404 && error.status !== 403 && error.status !== 401 && error.status >= 300){ @@ -69,6 +69,7 @@ export class HttpResInterceptor implements HttpInterceptor{ errorObject.message === "net::ERR_CONNECTION_CLOSE" || errorObject.message === "net::ERR_UNKNOWN_PROTOCOL" || errorObject.message === "net::ERR_SLOW_CONNECTION" || + errorObject.message === "net::ERR_FAILED" || errorObject.message === "net::ERR_NAME_NOT_RESOLVED" ; } } From e096edb5bd171d8133a97e8d802431c2eba52415 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Thu, 7 Sep 2023 17:42:36 +0100 Subject: [PATCH 013/419] Password Strength feature. (Not in feedback) --- .../acc-settings/acc-settings.component.html | 24 +-- .../acc-settings/acc-settings.component.ts | 13 +- .../_services/shared/validation.service.ts | 36 ----- src/app/shared/components.module.ts | 6 + .../pass-match/pass-match.component.spec.ts | 21 +++ .../pass-match/pass-match.component.ts | 34 +++++ .../pass-strenght.component.spec.ts | 21 +++ .../pass-strenght/pass-strenght.component.ts | 140 ++++++++++++++++++ .../edit-users/edit-users.component.html | 3 +- .../users/edit-users/edit-users.component.ts | 7 +- src/app/users/users.component.ts | 1 - src/styles/components/_form.scss | 35 +++++ 12 files changed, 289 insertions(+), 52 deletions(-) delete mode 100644 src/app/core/_services/shared/validation.service.ts create mode 100644 src/app/shared/password/pass-match/pass-match.component.spec.ts create mode 100644 src/app/shared/password/pass-match/pass-match.component.ts create mode 100644 src/app/shared/password/pass-strenght/pass-strenght.component.spec.ts create mode 100644 src/app/shared/password/pass-strenght/pass-strenght.component.ts diff --git a/src/app/account/settings/acc-settings/acc-settings.component.html b/src/app/account/settings/acc-settings/acc-settings.component.html index 5b30383d..99d2fa5b 100644 --- a/src/app/account/settings/acc-settings/acc-settings.component.html +++ b/src/app/account/settings/acc-settings/acc-settings.component.html @@ -48,17 +48,19 @@ required > - - - - + + + + + + diff --git a/src/app/account/settings/acc-settings/acc-settings.component.ts b/src/app/account/settings/acc-settings/acc-settings.component.ts index daaac371..69449322 100644 --- a/src/app/account/settings/acc-settings/acc-settings.component.ts +++ b/src/app/account/settings/acc-settings/acc-settings.component.ts @@ -3,7 +3,6 @@ import { ActivatedRoute, Params, Router } from '@angular/router'; import Swal from 'sweetalert2/dist/sweetalert2.js'; import { Component, OnInit } from '@angular/core'; -import { ValidationService } from '../../../core/_services/shared/validation.service'; import { UIConfigService } from 'src/app/core/_services/shared/storage.service'; import { GlobalService } from 'src/app/core/_services/main.service'; import { PageTitle } from 'src/app/core/_decorators/autotitle'; @@ -28,6 +27,8 @@ function passwordMatchValidator(password: string): ValidatorFn { export class AccountSettingsComponent implements OnInit { updateForm: FormGroup; + strongPassword = false; + passMatch = false; constructor( private uiService: UIConfigService, @@ -35,7 +36,7 @@ export class AccountSettingsComponent implements OnInit { private gs: GlobalService, private router: Router ) { - this.formInit(); + this.formInit() } ngOnInit(): void { @@ -79,6 +80,14 @@ export class AccountSettingsComponent implements OnInit { } } + onPasswordStrengthChanged(event: boolean) { + this.strongPassword = event; + } + + onPasswordMatchChanged(event: boolean) { + this.strongPassword = event; + } + private initForm() { this.gs.get(SERV.USERS,this.gs.userId, {'expand':'globalPermissionGroup'}).subscribe((result)=>{ this.updateForm = new FormGroup({ diff --git a/src/app/core/_services/shared/validation.service.ts b/src/app/core/_services/shared/validation.service.ts deleted file mode 100644 index 4cf6597c..00000000 --- a/src/app/core/_services/shared/validation.service.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { AbstractControl } from '@angular/forms'; - -export class ValidationService { - -/* - * Validates email using RFC 2822 - * If fail returns: - * invalidEmailAddress - * -*/ - static emailValidator(control: AbstractControl) { - if (control.value.match(/[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?/)) { - return null; - } else { - return { 'invalidEmailAddress': true }; - } - } - -/* - * Validates password using minimum security parameters - * {6,100} - Assert password is between 6 and 100 characters - * (?!.*\s) - Spaces are not allowed - * If fail returns: - * invalidPassword - * -*/ - static passwordValidator(control: AbstractControl) { - if (control.value.match(/^(?=.*\d)(?=.*[a-zA-Z!@#$%^&*])(?!.*\s).{6,100}$/)) { - return null; - } else { - return { 'invalidPassword': true }; - } - } - - -} diff --git a/src/app/shared/components.module.ts b/src/app/shared/components.module.ts index 1c61ef5d..c8b26d2a 100644 --- a/src/app/shared/components.module.ts +++ b/src/app/shared/components.module.ts @@ -20,6 +20,8 @@ import { LottiesModule } from './lottie/lottie.module'; import { GraphsModule } from "./graphs/graphs.module"; import { ColorPickerModule } from 'ngx-color-picker'; import { FormsModule } from "@angular/forms"; +import { PassStrenghtComponent } from './password/pass-strenght/pass-strenght.component'; +import { PassMatchComponent } from './password/pass-match/pass-match.component'; @NgModule({ declarations: [ @@ -27,7 +29,9 @@ import { FormsModule } from "@angular/forms"; LoadingSpinnerComponent, ActiveSpinnerComponent, HexconvertorComponent, + PassStrenghtComponent, CheatsheetComponent, + PassMatchComponent, TimeoutComponent, AlertComponent ], @@ -51,8 +55,10 @@ import { FormsModule } from "@angular/forms"; LoadingSpinnerComponent, ActiveSpinnerComponent, HexconvertorComponent, + PassStrenghtComponent, FilterTextboxModule, CheatsheetComponent, + PassMatchComponent, SwitchThemeModule, ColorPickerModule, PaginationModule, diff --git a/src/app/shared/password/pass-match/pass-match.component.spec.ts b/src/app/shared/password/pass-match/pass-match.component.spec.ts new file mode 100644 index 00000000..a7bee729 --- /dev/null +++ b/src/app/shared/password/pass-match/pass-match.component.spec.ts @@ -0,0 +1,21 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PassMatchComponent } from './pass-match.component'; + +describe('PassMatchComponent', () => { + let component: PassMatchComponent; + let fixture: ComponentFixture; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [PassMatchComponent] + }); + fixture = TestBed.createComponent(PassMatchComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/shared/password/pass-match/pass-match.component.ts b/src/app/shared/password/pass-match/pass-match.component.ts new file mode 100644 index 00000000..6a565dd3 --- /dev/null +++ b/src/app/shared/password/pass-match/pass-match.component.ts @@ -0,0 +1,34 @@ +import { Component, EventEmitter, Input, OnChanges, Output, SimpleChange, SimpleChanges } from '@angular/core'; + +@Component({ + selector: 'app-pass-match', + template: ` +
+

{{ message }}

+
+`, +}) +export class PassMatchComponent implements OnChanges { + + @Input() public newPassword: string; + @Input() public confirmPassword: string; + + message: string; + + ngOnChanges(changes: { [propName: string]: SimpleChange }): void { + + const confirmpass = changes['confirmPassword'].currentValue; + + if (confirmpass) { + + this.message = (confirmpass === this.newPassword) ? 'Match' : 'No Match'; + + }else{ + + this.message = ''; + + } + + } + +} diff --git a/src/app/shared/password/pass-strenght/pass-strenght.component.spec.ts b/src/app/shared/password/pass-strenght/pass-strenght.component.spec.ts new file mode 100644 index 00000000..f0d27b4e --- /dev/null +++ b/src/app/shared/password/pass-strenght/pass-strenght.component.spec.ts @@ -0,0 +1,21 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PassStrenghtComponent } from './pass-strenght.component'; + +describe('PassStrenghtComponent', () => { + let component: PassStrenghtComponent; + let fixture: ComponentFixture; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [PassStrenghtComponent] + }); + fixture = TestBed.createComponent(PassStrenghtComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/shared/password/pass-strenght/pass-strenght.component.ts b/src/app/shared/password/pass-strenght/pass-strenght.component.ts new file mode 100644 index 00000000..e6b7ee5c --- /dev/null +++ b/src/app/shared/password/pass-strenght/pass-strenght.component.ts @@ -0,0 +1,140 @@ +import { Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChange } from '@angular/core'; + +export const PasswordStrengthVal = { + 1: 'Poor', + 2: 'Not Good', + 3: 'Average', + 4: 'Good' +}; + +export enum PasswordStrengthColors { + '#DDDDDD', + '#8b0000', + '#ff4500', + '#FFA500', + '#9ACD32' +} + +@Component({ + selector: 'app-pass-strenght', + template: ` +
+
    +
  • +
  • +
  • +
  • +
+ +

{{ message }}

+
+`, +}) +export class PassStrenghtComponent implements OnChanges { + + bar0: string; + bar1: string; + bar2: string; + bar3: string; + @Input() public passwordToCheck: string; + @Output() passwordStrength = new EventEmitter(); + + message: string; + messageColor: string; + + checkStrength(password: string) { + + let force = 0; + + // Identify if password contains + const regex = /[$-/:-?{-~!"^_@`\[\]]/g; + const lowerLetters = /[a-z]+/.test(password); + const upperLetters = /[A-Z]+/.test(password); + const numbers = /[0-9]+/.test(password); + const special = regex.test(password); + + // Get boolean + const flags = [lowerLetters, upperLetters, numbers, special]; + + // See how many flags are in the password + let passedMatches = 0; + for (const flag of flags) { + passedMatches += flag === true ? 1 : 0; + } + + // 5 + force += 2 * password.length + (password.length >= 10 ? 1 : 0); + force += passedMatches * 10; + + // 6 + force = password.length <= 6 ? Math.min(force, 10) : force; + + // 7 + force = passedMatches === 1 ? Math.min(force, 10) : force; + force = passedMatches === 2 ? Math.min(force, 20) : force; + force = passedMatches === 3 ? Math.min(force, 30) : force; + force = passedMatches === 4 ? Math.min(force, 40) : force; + return force; + } + + ngOnChanges(changes: { [propName: string]: SimpleChange }): void { + const password = changes['passwordToCheck'].currentValue; + + this.setBarColors(5, PasswordStrengthColors[0]); + + if (password) { + const pwdStrength = this.checkStrength(password); + + pwdStrength === 40 ? this.passwordStrength.emit(true) : this.passwordStrength.emit(false); + + const color = this.getColor(pwdStrength); + this.setBarColors(color.index, color.color); + + switch (pwdStrength) { + case 10: + this.message = 'Poor'; + break; + case 20: + this.message = 'Not Good'; + break; + case 30: + this.message = 'Average'; + break; + case 40: + this.message = 'Good'; + break; + } + } else { + this.message = ''; + } + } + + private getColor(strength: number) { + let index = 0; + + if (strength === 10) { + index = 0; + } else if (strength === 20) { + index = 1; + } else if (strength === 30) { + index = 2; + } else if (strength === 40) { + index = 3; + } else { + index = 4; + } + + this.messageColor = PasswordStrengthColors[index+1]; + + return { + index: index + 1, + color: PasswordStrengthColors[index+1], + }; + } + + private setBarColors(count: number, color: string) { + for (let n = 0; n < count; n++) { + (this as any)['bar' + n] = color; + } + } +} diff --git a/src/app/users/edit-users/edit-users.component.html b/src/app/users/edit-users/edit-users.component.html index e2958bf6..873ff860 100644 --- a/src/app/users/edit-users/edit-users.component.html +++ b/src/app/users/edit-users/edit-users.component.html @@ -91,13 +91,14 @@ +
diff --git a/src/app/users/edit-users/edit-users.component.ts b/src/app/users/edit-users/edit-users.component.ts index 62dfcf57..0c088e3a 100644 --- a/src/app/users/edit-users/edit-users.component.ts +++ b/src/app/users/edit-users/edit-users.component.ts @@ -4,7 +4,6 @@ import { FormControl, FormGroup } from '@angular/forms'; import Swal from 'sweetalert2/dist/sweetalert2.js'; import { Component, OnInit } from '@angular/core'; -import { ValidationService } from '../../core/_services/shared/validation.service'; import { UIConfigService } from 'src/app/core/_services/shared/storage.service'; import { GlobalService } from 'src/app/core/_services/main.service'; import { PageTitle } from 'src/app/core/_decorators/autotitle'; @@ -20,8 +19,10 @@ import { User } from '../user.model'; }) @PageTitle(['Edit User']) export class EditUsersComponent implements OnInit { + editMode = false; editedUserIndex: number; + strongPassword = false; editedUser: any // Change to Model faCalendar=faCalendar; @@ -61,6 +62,10 @@ export class EditUsersComponent implements OnInit { 'password': new FormControl(), }) + onPasswordStrengthChanged(event: boolean) { + this.strongPassword = event; + } + ngOnInit(): void { this.route.params diff --git a/src/app/users/users.component.ts b/src/app/users/users.component.ts index 6e6e27f9..cee147cc 100644 --- a/src/app/users/users.component.ts +++ b/src/app/users/users.component.ts @@ -3,7 +3,6 @@ import { ActivatedRoute, Params, Router } from '@angular/router'; import Swal from 'sweetalert2/dist/sweetalert2.js'; import { Component, OnInit } from '@angular/core'; -import { ValidationService } from '../core/_services/shared/validation.service'; import { GlobalService } from 'src/app/core/_services/main.service'; import { PageTitle } from 'src/app/core/_decorators/autotitle'; import { environment } from 'src/environments/environment'; diff --git a/src/styles/components/_form.scss b/src/styles/components/_form.scss index cc277198..7544c430 100644 --- a/src/styles/components/_form.scss +++ b/src/styles/components/_form.scss @@ -394,3 +394,38 @@ progress::-webkit-progress-value { 06- End */ +/* + 07- Password Strength +*/ + +.strength { + display: flex; + flex-direction: row; +} + +.strengthBar { + display: flex; + flex-grow: 1; + list-style: none; + margin: 0; + padding: 0; + vertical-align: 2px; +} + +.strength-point { + background: #ddd; + border-radius: 2px; + display: inline-block; + height: 5px; + margin-right: 1px; + flex-grow: 1; +} + +.str-margin { + font-weight: bold; + flex-grow: 0.1; +} + +/* + 07- End +*/ From 531a09ecf13a45aee8195b7ced1ce38a6ddbe7e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Thu, 7 Sep 2023 21:34:55 +0100 Subject: [PATCH 014/419] All pages change to old logo --- src/app/layout/header/header.component.html | 14 ++------------ src/assets/img/h2p_mp4.mp4 | Bin 157853 -> 0 bytes src/config/default/app/main.ts | 1 - src/default/app/main.ts | 2 -- 4 files changed, 2 insertions(+), 15 deletions(-) delete mode 100644 src/assets/img/h2p_mp4.mp4 diff --git a/src/app/layout/header/header.component.html b/src/app/layout/header/header.component.html index 57570e68..919b6a02 100644 --- a/src/app/layout/header/header.component.html +++ b/src/app/layout/header/header.component.html @@ -1,16 +1,6 @@
diff --git a/src/app/agents/agent-status/agent-status.component.ts b/src/app/agents/agent-status/agent-status.component.ts index 3930f52a..b2bc7f2d 100644 --- a/src/app/agents/agent-status/agent-status.component.ts +++ b/src/app/agents/agent-status/agent-status.component.ts @@ -95,6 +95,7 @@ export class AgentStatusComponent implements OnInit { this.dtOptions = { dom: 'Bfrtip', scrollX: true, + pageLength: 25, scrollY: true, bDestroy: true, columnDefs: [ diff --git a/src/app/agents/edit-agent/edit-agent.component.ts b/src/app/agents/edit-agent/edit-agent.component.ts index 03117aeb..92a81c4f 100644 --- a/src/app/agents/edit-agent/edit-agent.component.ts +++ b/src/app/agents/edit-agent/edit-agent.component.ts @@ -138,6 +138,7 @@ export class EditAgentComponent implements OnInit { this.dtOptions = { dom: 'Bfrtip', scrollX: true, + pageLength: 25, scrollY: "700px", scrollCollapse: true, paging: false, diff --git a/src/app/agents/new-agent/new-agent.component.ts b/src/app/agents/new-agent/new-agent.component.ts index d90a5913..15c2ab1b 100644 --- a/src/app/agents/new-agent/new-agent.component.ts +++ b/src/app/agents/new-agent/new-agent.component.ts @@ -80,7 +80,7 @@ export class NewAgentComponent implements OnInit, OnDestroy { this.dtOptions = { dom: 'Bfrtip', scrollX: true, - pageLength: 10, + pageLength: 25, stateSave: true, select: true, }; diff --git a/src/app/agents/show-agents/show-agents.component.ts b/src/app/agents/show-agents/show-agents.component.ts index 35f8850e..2431e8f0 100644 --- a/src/app/agents/show-agents/show-agents.component.ts +++ b/src/app/agents/show-agents/show-agents.component.ts @@ -67,6 +67,7 @@ export class ShowAgentsComponent implements OnInit, OnDestroy { this.dtOptions = { dom: 'Bfrtip', scrollX: true, + pageLength: 25, stateSave: true, destroy: true, select: { diff --git a/src/app/config/engine/agent-binaries/agent-binaries.component.ts b/src/app/config/engine/agent-binaries/agent-binaries.component.ts index 27089fc7..6c3346c5 100644 --- a/src/app/config/engine/agent-binaries/agent-binaries.component.ts +++ b/src/app/config/engine/agent-binaries/agent-binaries.component.ts @@ -46,7 +46,7 @@ export class AgentBinariesComponent implements OnInit { this.dtOptions = { dom: 'Bfrtip', scrollX: true, - pageLength: 10, + pageLength: 25, stateSave: true, select: true, buttons: { diff --git a/src/app/config/engine/crackers/crackers.component.ts b/src/app/config/engine/crackers/crackers.component.ts index ea8a1115..12d90649 100644 --- a/src/app/config/engine/crackers/crackers.component.ts +++ b/src/app/config/engine/crackers/crackers.component.ts @@ -51,7 +51,7 @@ export class CrackersComponent implements OnInit, OnDestroy { this.dtOptions = { dom: 'Bfrtip', scrollX: true, - pageLength: 10, + pageLength: 25, stateSave: true, select: true, buttons: { diff --git a/src/app/config/engine/preprocessors/preprocessors.component.ts b/src/app/config/engine/preprocessors/preprocessors.component.ts index bd13b0e6..ddbf50bf 100644 --- a/src/app/config/engine/preprocessors/preprocessors.component.ts +++ b/src/app/config/engine/preprocessors/preprocessors.component.ts @@ -48,7 +48,7 @@ export class PreprocessorsComponent implements OnInit { this.dtOptions = { dom: 'Bfrtip', scrollX: true, - pageLength: 10, + pageLength: 25, stateSave: true, select: true, buttons: { diff --git a/src/app/config/hashtypes/hashtypes.component.ts b/src/app/config/hashtypes/hashtypes.component.ts index 8f745862..a3e7c474 100644 --- a/src/app/config/hashtypes/hashtypes.component.ts +++ b/src/app/config/hashtypes/hashtypes.component.ts @@ -50,7 +50,7 @@ export class HashtypesComponent implements OnInit { this.dtOptions = { dom: 'Bfrtip', scrollX: true, - pageLength: 10, + pageLength: 25, select: true, processing: true, // Error loading deferRender: true, diff --git a/src/app/config/health-checks/edit-health-check/edit-health-checks.component.ts b/src/app/config/health-checks/edit-health-check/edit-health-checks.component.ts index dbf53777..b2fc1279 100644 --- a/src/app/config/health-checks/edit-health-check/edit-health-checks.component.ts +++ b/src/app/config/health-checks/edit-health-check/edit-health-checks.component.ts @@ -89,7 +89,7 @@ export class EditHealthChecksComponent implements OnInit { this.dtOptions = { dom: 'Bfrtip', scrollX: true, - pageLength: 10, + pageLength: 25, stateSave: true, select: true, buttons: { diff --git a/src/app/config/health-checks/health-checks.component.ts b/src/app/config/health-checks/health-checks.component.ts index df2cbfc6..4fa83f04 100644 --- a/src/app/config/health-checks/health-checks.component.ts +++ b/src/app/config/health-checks/health-checks.component.ts @@ -79,6 +79,7 @@ export class HealthChecksComponent implements OnInit { this.dtOptions = { dom: 'Bfrtip', scrollX: true, + pageLength: 25, stateSave: true, select: true, buttons: { diff --git a/src/app/config/log/log.component.ts b/src/app/config/log/log.component.ts index 21aeb8be..7c6aff96 100644 --- a/src/app/config/log/log.component.ts +++ b/src/app/config/log/log.component.ts @@ -99,6 +99,7 @@ export class LogComponent implements OnInit { this.dtOptions = { dom: 'Bfrtip', scrollX: true, + pageLength: 25, bStateSave:true, bPaginate: false, bLengthChange: false, diff --git a/src/app/core/_services/shared/autotitle.service.ts b/src/app/core/_services/shared/autotitle.service.ts index 7a85580c..86c5affa 100644 --- a/src/app/core/_services/shared/autotitle.service.ts +++ b/src/app/core/_services/shared/autotitle.service.ts @@ -13,7 +13,7 @@ export class AutoTitleService { * @param title - inject title and join in array * @returns Nav Location **/ - appTitle = ' - Hashtopolis'; + appTitle = 'Hashtopolis - '; set(title: string | string[]){ @@ -21,7 +21,7 @@ export class AutoTitleService { title = title.join(' '); } - title = title.concat(this.appTitle); + title = this.appTitle.concat(title); this.titleService.setTitle(title); diff --git a/src/app/files/files.component.ts b/src/app/files/files.component.ts index 1a01a718..1777ffd9 100644 --- a/src/app/files/files.component.ts +++ b/src/app/files/files.component.ts @@ -102,6 +102,7 @@ export class FilesComponent implements OnInit { this.dtOptions = { dom: 'Bfrtip', scrollX: true, + pageLength: 25, scrollY: true, stateSave: true, destroy: true, diff --git a/src/app/hashlists/edit-hashlist/edit-hashlist.component.ts b/src/app/hashlists/edit-hashlist/edit-hashlist.component.ts index 09bd95b2..8e51c553 100644 --- a/src/app/hashlists/edit-hashlist/edit-hashlist.component.ts +++ b/src/app/hashlists/edit-hashlist/edit-hashlist.component.ts @@ -180,6 +180,7 @@ export class EditHashlistComponent implements OnInit { this.dtOptions = { dom: 'Bfrtip', scrollX: true, + pageLength: 25, bStateSave:true, destroy: true, buttons:[] diff --git a/src/app/hashlists/hashes/hashes.component.ts b/src/app/hashlists/hashes/hashes.component.ts index 13b13d40..483a1b6a 100644 --- a/src/app/hashlists/hashes/hashes.component.ts +++ b/src/app/hashlists/hashes/hashes.component.ts @@ -67,7 +67,7 @@ export class HashesComponent implements OnInit { this.dtOptions = { dom: 'Bfrtip', scrollX: true, - pageLength: 10, + pageLength: 25, searching: false, buttons: { dom: { diff --git a/src/app/hashlists/hashlist/hashlist.component.ts b/src/app/hashlists/hashlist/hashlist.component.ts index b72efe2c..36a8ac73 100644 --- a/src/app/hashlists/hashlist/hashlist.component.ts +++ b/src/app/hashlists/hashlist/hashlist.component.ts @@ -94,6 +94,7 @@ export class HashlistComponent implements OnInit, OnDestroy { this.dtOptions = { dom: 'Bfrtip', scrollX: true, + pageLength: 25, stateSave: true, select: { style: 'multi', diff --git a/src/app/hashlists/show-cracks/show-cracks.component.ts b/src/app/hashlists/show-cracks/show-cracks.component.ts index 23674bf4..a5ebb3fe 100644 --- a/src/app/hashlists/show-cracks/show-cracks.component.ts +++ b/src/app/hashlists/show-cracks/show-cracks.component.ts @@ -45,7 +45,7 @@ export class ShowCracksComponent implements OnInit { this.dtOptions = { dom: 'Bfrtip', scrollX: true, - pageLength: 10, + pageLength: 25, stateSave: true, select: true, order: [[0, 'desc']], diff --git a/src/app/hashlists/superhashlist/superhashlist.component.ts b/src/app/hashlists/superhashlist/superhashlist.component.ts index d4e5bc24..e23b4dd3 100644 --- a/src/app/hashlists/superhashlist/superhashlist.component.ts +++ b/src/app/hashlists/superhashlist/superhashlist.component.ts @@ -48,7 +48,7 @@ export class SuperhashlistComponent implements OnInit { this.dtOptions = { dom: 'Bfrtip', scrollX: true, - pageLength: 10, + pageLength: 25, stateSave: true, select: true, buttons: { diff --git a/src/app/layout/footer/footer.component.html b/src/app/layout/footer/footer.component.html index e6864909..cc292433 100644 --- a/src/app/layout/footer/footer.component.html +++ b/src/app/layout/footer/footer.component.html @@ -4,11 +4,16 @@

©2016-{{year}} {{ this.footerConfig.copyright }} {{ gitInfo.shortSHA }} + +

+ {{ gitInfo.lastCommitTime }} +
+

diff --git a/src/app/layout/header/header.component.html b/src/app/layout/header/header.component.html index 919b6a02..83755b5d 100644 --- a/src/app/layout/header/header.component.html +++ b/src/app/layout/header/header.component.html @@ -80,7 +80,7 @@ Files
diff --git a/src/app/projects/projects.component.ts b/src/app/projects/projects.component.ts index 190d7603..1721f637 100644 --- a/src/app/projects/projects.component.ts +++ b/src/app/projects/projects.component.ts @@ -49,6 +49,7 @@ export class ProjectsComponent implements OnInit { this.dtOptions = { dom: 'Bfrtip', scrollX: true, + pageLength: 25, bStateSave:true, destroy: true, select: { diff --git a/src/app/tasks/chunks/chunks.component.ts b/src/app/tasks/chunks/chunks.component.ts index 6e5c42bd..8d3c8d2b 100644 --- a/src/app/tasks/chunks/chunks.component.ts +++ b/src/app/tasks/chunks/chunks.component.ts @@ -99,7 +99,7 @@ export class ChunksComponent implements OnInit { this.dtOptions = { dom: 'Bfrtip', scrollX: true, - pageLength: 10, + pageLength: 25, select: true, processing: true, deferRender: true, diff --git a/src/app/tasks/edit-preconfigured-tasks/edit-preconfigured-tasks.component.ts b/src/app/tasks/edit-preconfigured-tasks/edit-preconfigured-tasks.component.ts index 825febc3..ead9a9b6 100644 --- a/src/app/tasks/edit-preconfigured-tasks/edit-preconfigured-tasks.component.ts +++ b/src/app/tasks/edit-preconfigured-tasks/edit-preconfigured-tasks.component.ts @@ -94,7 +94,7 @@ export class EditPreconfiguredTasksComponent implements OnInit{ this.dtOptions = { dom: 'Bfrtip', scrollX: true, - pageLength: 10, + pageLength: 25, stateSave: true, select: true, buttons: [ ] diff --git a/src/app/tasks/edit-tasks/edit-tasks.component.ts b/src/app/tasks/edit-tasks/edit-tasks.component.ts index 1fd1c725..48c66c53 100644 --- a/src/app/tasks/edit-tasks/edit-tasks.component.ts +++ b/src/app/tasks/edit-tasks/edit-tasks.component.ts @@ -341,6 +341,7 @@ export class EditTasksComponent implements OnInit,PendingChangesGuard { this.dtOptions = { dom: 'Bfrtip', scrollX: true, + pageLength: 25, scrollY: "700px", scrollCollapse: true, paging: false, diff --git a/src/app/tasks/import-supertasks/wrbulk/wrbulk.component.ts b/src/app/tasks/import-supertasks/wrbulk/wrbulk.component.ts index 66fcb971..867afc07 100644 --- a/src/app/tasks/import-supertasks/wrbulk/wrbulk.component.ts +++ b/src/app/tasks/import-supertasks/wrbulk/wrbulk.component.ts @@ -50,6 +50,7 @@ export class WrbulkComponent implements OnInit { this.dtOptions = { dom: 'Bfrtip', scrollX: true, + pageLength: 25, scrollY: "700px", scrollCollapse: true, paging: false, diff --git a/src/app/tasks/new-preconfigured-tasks/new-preconfigured-tasks.component.html b/src/app/tasks/new-preconfigured-tasks/new-preconfigured-tasks.component.html index dddff069..125bffb5 100644 --- a/src/app/tasks/new-preconfigured-tasks/new-preconfigured-tasks.component.html +++ b/src/app/tasks/new-preconfigured-tasks/new-preconfigured-tasks.component.html @@ -164,7 +164,7 @@

New Preconfigured Tasks (Copied From Task ID {{edited - WordList + Wordlists

Access Group ID
diff --git a/src/app/tasks/new-preconfigured-tasks/new-preconfigured-tasks.component.ts b/src/app/tasks/new-preconfigured-tasks/new-preconfigured-tasks.component.ts index dfd3a3a4..ee879c07 100644 --- a/src/app/tasks/new-preconfigured-tasks/new-preconfigured-tasks.component.ts +++ b/src/app/tasks/new-preconfigured-tasks/new-preconfigured-tasks.component.ts @@ -198,6 +198,7 @@ export class NewPreconfiguredTasksComponent implements OnInit,AfterViewInit { this.dtOptions = { dom: 'Bfrtip', scrollX: true, + pageLength: 25, scrollY: "1000px", scrollCollapse: true, paging: false, diff --git a/src/app/tasks/new-tasks/new-tasks.component.html b/src/app/tasks/new-tasks/new-tasks.component.html index edbc9ff0..e60ceff8 100644 --- a/src/app/tasks/new-tasks/new-tasks.component.html +++ b/src/app/tasks/new-tasks/new-tasks.component.html @@ -301,7 +301,7 @@ - WordList + Wordlists
diff --git a/src/app/tasks/new-tasks/new-tasks.component.ts b/src/app/tasks/new-tasks/new-tasks.component.ts index 6c3bf00d..d2e47809 100644 --- a/src/app/tasks/new-tasks/new-tasks.component.ts +++ b/src/app/tasks/new-tasks/new-tasks.component.ts @@ -228,6 +228,7 @@ export class NewTasksComponent implements OnInit { this.dtOptions = { dom: 'Bfrtip', scrollX: true, + pageLength: 25, scrollY: "700px", scrollCollapse: true, paging: false, diff --git a/src/app/tasks/preconfigured-tasks/preconfigured-tasks.component.ts b/src/app/tasks/preconfigured-tasks/preconfigured-tasks.component.ts index 027f044b..9e165eb8 100644 --- a/src/app/tasks/preconfigured-tasks/preconfigured-tasks.component.ts +++ b/src/app/tasks/preconfigured-tasks/preconfigured-tasks.component.ts @@ -53,7 +53,7 @@ export class PreconfiguredTasksComponent implements OnInit { this.dtOptions = { dom: 'Bfrtip', scrollX: true, - pageLength: 10, + pageLength: 25, stateSave: true, select: true, buttons: { 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 e65754a1..eb5dc699 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 @@ -48,6 +48,7 @@ export class ModalSubtasksComponent { this.dtOptions = { dom: 'Bfrtip', scrollX: true, + pageLength: 25, destroy: true, buttons:[] }; diff --git a/src/app/tasks/show-tasks/show-tasks.component.ts b/src/app/tasks/show-tasks/show-tasks.component.ts index 49e1e9fe..7e9ca078 100644 --- a/src/app/tasks/show-tasks/show-tasks.component.ts +++ b/src/app/tasks/show-tasks/show-tasks.component.ts @@ -90,6 +90,7 @@ export class ShowTasksComponent implements OnInit { this.dtOptions = { dom: 'Bfrtip', scrollX: true, + pageLength: 25, bStateSave:true, destroy: true, order: [], // Removes the default order by id. We need it to sort by priority. diff --git a/src/app/tasks/supertasks/modal-pretasks/modal-pretasks.component.ts b/src/app/tasks/supertasks/modal-pretasks/modal-pretasks.component.ts index 18c484f0..6090401b 100644 --- a/src/app/tasks/supertasks/modal-pretasks/modal-pretasks.component.ts +++ b/src/app/tasks/supertasks/modal-pretasks/modal-pretasks.component.ts @@ -43,6 +43,7 @@ export class ModalPretasksComponent implements OnInit { this.dtOptions = { dom: 'Bfrtip', scrollX: true, + pageLength: 25, destroy: true, buttons:[] }; diff --git a/src/app/users/all-users/all-users.component.ts b/src/app/users/all-users/all-users.component.ts index 7d7dde8c..6c385999 100644 --- a/src/app/users/all-users/all-users.component.ts +++ b/src/app/users/all-users/all-users.component.ts @@ -70,7 +70,7 @@ export class AllUsersComponent implements OnInit, OnDestroy { this.dtOptions = { dom: 'Bfrtip', scrollX: true, - pageLength: 10, + pageLength: 25, stateSave: true, // "stateLoadParams": function (settings, data) { // return false; diff --git a/src/app/users/globalpermissionsgroups/globalpermissionsgroups.component.ts b/src/app/users/globalpermissionsgroups/globalpermissionsgroups.component.ts index 98e59ecd..d76057ea 100644 --- a/src/app/users/globalpermissionsgroups/globalpermissionsgroups.component.ts +++ b/src/app/users/globalpermissionsgroups/globalpermissionsgroups.component.ts @@ -51,7 +51,7 @@ export class GlobalpermissionsgroupsComponent implements OnInit { this.dtOptions = { dom: 'Bfrtip', scrollX: true, - pageLength: 10, + pageLength: 25, select: true, processing: true, // Error loading deferRender: true, diff --git a/src/app/users/groups/groups.component.ts b/src/app/users/groups/groups.component.ts index 972fbc28..24697c09 100644 --- a/src/app/users/groups/groups.component.ts +++ b/src/app/users/groups/groups.component.ts @@ -56,7 +56,7 @@ export class GroupsComponent implements OnInit { this.dtOptions = { dom: 'Bfrtip', scrollX: true, - pageLength: 10, + pageLength: 25, select: true, processing: true, // Error loading deferRender: true, diff --git a/src/styles/components/_table.scss b/src/styles/components/_table.scss index 19d5bac3..5330cbc2 100644 --- a/src/styles/components/_table.scss +++ b/src/styles/components/_table.scss @@ -45,15 +45,15 @@ table.dataTable tbody tr.selected>* { table.dataTable { th { - padding: 16px 12px; + padding: 12px 8px; padding-right: 12px; text-align: left; vertical-align: bottom; - font-size: .875rem; + font-size: .975rem; font-weight: 400; font-weight: normal; color: #999; - text-transform: uppercase; + // text-transform: uppercase; //Chane table header to uppercase } td { padding: 10px 10px; From 94701f1d4c868624ec198fb95631f0b94436a80b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Thu, 7 Sep 2023 23:01:40 +0100 Subject: [PATCH 017/419] agent overview page-Remove agent binary printed --- src/app/agents/show-agents/show-agents.component.html | 1 - 1 file changed, 1 deletion(-) diff --git a/src/app/agents/show-agents/show-agents.component.html b/src/app/agents/show-agents/show-agents.component.html index 1177114f..19f464df 100644 --- a/src/app/agents/show-agents/show-agents.component.html +++ b/src/app/agents/show-agents/show-agents.component.html @@ -39,7 +39,6 @@ {{ agent.userId}} diff --git a/src/app/agents/new-agent/new-agent.component.ts b/src/app/agents/new-agent/new-agent.component.ts index 5da53ef2..e46d123c 100644 --- a/src/app/agents/new-agent/new-agent.component.ts +++ b/src/app/agents/new-agent/new-agent.component.ts @@ -102,7 +102,7 @@ export class NewAgentComponent implements OnInit, OnDestroy { }); } - onDelete(id: number){ + onDelete(id: number, name: string ){ const swalWithBootstrapButtons = Swal.mixin({ customClass: { confirmButton: 'btn', @@ -111,7 +111,7 @@ export class NewAgentComponent implements OnInit, OnDestroy { buttonsStyling: false }) Swal.fire({ - title: "Are you sure?", + title: 'Remove '+ name +' from your Vouchers?', text: "Once deleted, it can not be recovered!", icon: "warning", reverseButtons: true, diff --git a/src/app/agents/show-agents/show-agents.component.html b/src/app/agents/show-agents/show-agents.component.html index 93be8592..9ac2b8fd 100644 --- a/src/app/agents/show-agents/show-agents.component.html +++ b/src/app/agents/show-agents/show-agents.component.html @@ -68,7 +68,7 @@ Edit - diff --git a/src/app/agents/show-agents/show-agents.component.ts b/src/app/agents/show-agents/show-agents.component.ts index 7f282579..3ca3bc5e 100644 --- a/src/app/agents/show-agents/show-agents.component.ts +++ b/src/app/agents/show-agents/show-agents.component.ts @@ -335,7 +335,7 @@ export class ShowAgentsComponent implements OnInit, OnDestroy { })() } - onDelete(id: number){ + onDelete(id: number, name: string){ const swalWithBootstrapButtons = Swal.mixin({ customClass: { confirmButton: 'btn', @@ -344,8 +344,7 @@ export class ShowAgentsComponent implements OnInit, OnDestroy { buttonsStyling: false }) Swal.fire({ - title: "Are you sure?", - text: "Once deleted, it can not be recovered!", + title: 'Remove '+ name +' from your Agents?', icon: "warning", reverseButtons: true, showCancelButton: true, diff --git a/src/app/config/engine/agent-binaries/agent-binaries.component.html b/src/app/config/engine/agent-binaries/agent-binaries.component.html index e3fd212f..593292b6 100644 --- a/src/app/config/engine/agent-binaries/agent-binaries.component.html +++ b/src/app/config/engine/agent-binaries/agent-binaries.component.html @@ -28,7 +28,7 @@ Edit - diff --git a/src/app/config/engine/agent-binaries/agent-binaries.component.ts b/src/app/config/engine/agent-binaries/agent-binaries.component.ts index 2134ddd8..3e677b0a 100644 --- a/src/app/config/engine/agent-binaries/agent-binaries.component.ts +++ b/src/app/config/engine/agent-binaries/agent-binaries.component.ts @@ -85,7 +85,7 @@ export class AgentBinariesComponent implements OnInit { }); } - onDelete(id: number){ + onDelete(id: number, name: string){ const swalWithBootstrapButtons = Swal.mixin({ customClass: { confirmButton: 'btn', @@ -94,8 +94,7 @@ export class AgentBinariesComponent implements OnInit { buttonsStyling: false }) Swal.fire({ - title: "Are you sure?", - text: "Once deleted, it can not be recovered!", + title: 'Remove '+ name +' from your Binaries?', icon: "warning", reverseButtons: true, showCancelButton: true, diff --git a/src/app/config/engine/crackers/crackers.component.html b/src/app/config/engine/crackers/crackers.component.html index a8150687..6b298d10 100644 --- a/src/app/config/engine/crackers/crackers.component.html +++ b/src/app/config/engine/crackers/crackers.component.html @@ -26,7 +26,7 @@ Add version/binary - diff --git a/src/app/config/engine/crackers/crackers.component.ts b/src/app/config/engine/crackers/crackers.component.ts index 14618595..b858ae00 100644 --- a/src/app/config/engine/crackers/crackers.component.ts +++ b/src/app/config/engine/crackers/crackers.component.ts @@ -129,10 +129,9 @@ export class CrackersComponent implements OnInit, OnDestroy { }); } - onDelete(id: number){ + onDelete(id: number, name: string){ Swal.fire({ - title: "Are you sure?", - text: "Once deleted, you will not be able to recover this cracker!", + title: 'Remove '+ name +' from your crackers?', icon: "warning", buttons: true, dangerMode: true, diff --git a/src/app/config/engine/crackers/edit-version/edit-crackers.component.ts b/src/app/config/engine/crackers/edit-version/edit-crackers.component.ts index 895c4906..3f2c6dbc 100644 --- a/src/app/config/engine/crackers/edit-version/edit-crackers.component.ts +++ b/src/app/config/engine/crackers/edit-version/edit-crackers.component.ts @@ -73,8 +73,7 @@ export class EditCrackersComponent implements OnInit { buttonsStyling: false }) Swal.fire({ - title: "Are you sure?", - text: "Once deleted, it can not be recovered!", + title: 'Remove from your crackers?', icon: "warning", reverseButtons: true, showCancelButton: true, diff --git a/src/app/config/engine/preprocessors/preprocessors.component.html b/src/app/config/engine/preprocessors/preprocessors.component.html index f5712724..5f656228 100644 --- a/src/app/config/engine/preprocessors/preprocessors.component.html +++ b/src/app/config/engine/preprocessors/preprocessors.component.html @@ -20,7 +20,7 @@ Edit - diff --git a/src/app/config/engine/preprocessors/preprocessors.component.ts b/src/app/config/engine/preprocessors/preprocessors.component.ts index d14d669f..5607f06e 100644 --- a/src/app/config/engine/preprocessors/preprocessors.component.ts +++ b/src/app/config/engine/preprocessors/preprocessors.component.ts @@ -129,7 +129,7 @@ export class PreprocessorsComponent implements OnInit { }); } - onDelete(id: number){ + onDelete(id: number, name: string){ const swalWithBootstrapButtons = Swal.mixin({ customClass: { confirmButton: 'btn', @@ -138,8 +138,7 @@ export class PreprocessorsComponent implements OnInit { buttonsStyling: false }) Swal.fire({ - title: "Are you sure?", - text: "Once deleted, it can not be recovered!", + title: 'Remove '+ name +' from your preprocessors?', icon: "warning", reverseButtons: true, showCancelButton: true, diff --git a/src/app/config/hashtypes/hashtypes.component.html b/src/app/config/hashtypes/hashtypes.component.html index 0bb488e4..a455b605 100644 --- a/src/app/config/hashtypes/hashtypes.component.html +++ b/src/app/config/hashtypes/hashtypes.component.html @@ -15,8 +15,8 @@ - - + + @@ -35,7 +35,7 @@ Edit - diff --git a/src/app/users/all-users/all-users.component.ts b/src/app/users/all-users/all-users.component.ts index f4f4ac0e..22bf65c6 100644 --- a/src/app/users/all-users/all-users.component.ts +++ b/src/app/users/all-users/all-users.component.ts @@ -147,7 +147,7 @@ export class AllUsersComponent implements OnInit, OnDestroy { } //ToDo - onDelete(id: number){ + onDelete(id: number, name: string){ const swalWithBootstrapButtons = Swal.mixin({ customClass: { confirmButton: 'btn', @@ -156,8 +156,7 @@ export class AllUsersComponent implements OnInit, OnDestroy { buttonsStyling: false }) Swal.fire({ - title: "Are you sure?", - text: "Once deleted, it can not be recovered!", + title: 'Remove '+ name +' from your users?', icon: "warning", reverseButtons: true, showCancelButton: true, diff --git a/src/app/users/edit-users/edit-users.component.ts b/src/app/users/edit-users/edit-users.component.ts index c8b38ab5..6f319ba8 100644 --- a/src/app/users/edit-users/edit-users.component.ts +++ b/src/app/users/edit-users/edit-users.component.ts @@ -99,8 +99,7 @@ export class EditUsersComponent implements OnInit { buttonsStyling: false }) Swal.fire({ - title: "Are you sure?", - text: "Once deleted, it can not be recovered!", + title: 'Remove from your users?', icon: "warning", reverseButtons: true, showCancelButton: true, diff --git a/src/app/users/globalpermissionsgroups/globalpermissionsgroups.component.html b/src/app/users/globalpermissionsgroups/globalpermissionsgroups.component.html index cc9dfd9d..933b43a0 100644 --- a/src/app/users/globalpermissionsgroups/globalpermissionsgroups.component.html +++ b/src/app/users/globalpermissionsgroups/globalpermissionsgroups.component.html @@ -30,7 +30,7 @@ Edit - diff --git a/src/app/users/globalpermissionsgroups/globalpermissionsgroups.component.ts b/src/app/users/globalpermissionsgroups/globalpermissionsgroups.component.ts index b5a1fcc2..296fb471 100644 --- a/src/app/users/globalpermissionsgroups/globalpermissionsgroups.component.ts +++ b/src/app/users/globalpermissionsgroups/globalpermissionsgroups.component.ts @@ -134,7 +134,7 @@ export class GlobalpermissionsgroupsComponent implements OnInit { }); } - onDelete(id: number){ + onDelete(id: number, name: string){ const swalWithBootstrapButtons = Swal.mixin({ customClass: { confirmButton: 'btn', @@ -143,8 +143,7 @@ export class GlobalpermissionsgroupsComponent implements OnInit { buttonsStyling: false }) Swal.fire({ - title: "Are you sure?", - text: "Once deleted, it can not be recovered!", + title: 'Remove '+ name +' from your global permissions?', icon: "warning", reverseButtons: true, showCancelButton: true, diff --git a/src/app/users/groups/groups.component.html b/src/app/users/groups/groups.component.html index 356cd77a..1a6cc1b6 100644 --- a/src/app/users/groups/groups.component.html +++ b/src/app/users/groups/groups.component.html @@ -19,7 +19,7 @@ Edit - diff --git a/src/app/users/groups/groups.component.ts b/src/app/users/groups/groups.component.ts index 321481ce..04f40e43 100644 --- a/src/app/users/groups/groups.component.ts +++ b/src/app/users/groups/groups.component.ts @@ -139,7 +139,7 @@ export class GroupsComponent implements OnInit { }); } - onDelete(id: number){ + onDelete(id: number, name: string){ const swalWithBootstrapButtons = Swal.mixin({ customClass: { confirmButton: 'btn', @@ -148,8 +148,7 @@ export class GroupsComponent implements OnInit { buttonsStyling: false }) Swal.fire({ - title: "Are you sure?", - text: "Once deleted, it can not be recovered!", + title: 'Remove '+ name +' from your groups?', icon: "warning", reverseButtons: true, showCancelButton: true, From a40cbc7deed40c58e70262aa928abd23dd51a1db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Tue, 26 Sep 2023 13:47:54 +0100 Subject: [PATCH 041/419] Hashlisth detail page - cracker information, one line --- src/app/tasks/edit-tasks/edit-tasks.component.html | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/app/tasks/edit-tasks/edit-tasks.component.html b/src/app/tasks/edit-tasks/edit-tasks.component.html index ef34d33a..93fb1d2d 100644 --- a/src/app/tasks/edit-tasks/edit-tasks.component.html +++ b/src/app/tasks/edit-tasks/edit-tasks.component.html @@ -282,8 +282,7 @@
Cracker Information
- Name: {{ crackerinfo['binaryName'] }}
- Version: {{ crackerinfo['version'] }} + {{ crackerinfo['binaryName'] }} - {{ crackerinfo['version'] }}
From 0cec52c7ef63c0122f819b170dc32868374f2d6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Tue, 26 Sep 2023 13:55:15 +0100 Subject: [PATCH 042/419] Search hash - create button should be search button --- src/app/hashlists/search-hash/search-hash.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/hashlists/search-hash/search-hash.component.html b/src/app/hashlists/search-hash/search-hash.component.html index 70ab25e6..fd7ce27f 100644 --- a/src/app/hashlists/search-hash/search-hash.component.html +++ b/src/app/hashlists/search-hash/search-hash.component.html @@ -18,7 +18,7 @@ Minimum 1 hash required!
- + From 6db9f30d3ff1edb4a1861139f6df8726bd5dc73a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Wed, 27 Sep 2023 06:07:48 +0100 Subject: [PATCH 043/419] #997 - Rework Engine tab --- .../agent-binaries/agent-binaries.component.html | 2 +- .../config/engine/crackers/crackers.component.html | 4 ++-- .../preprocessors/preprocessors.component.html | 4 ++-- src/app/layout/header/header.component.html | 12 +++++++++++- src/app/layout/header/header.component.ts | 3 ++- 5 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/app/config/engine/agent-binaries/agent-binaries.component.html b/src/app/config/engine/agent-binaries/agent-binaries.component.html index 593292b6..47a5e1b6 100644 --- a/src/app/config/engine/agent-binaries/agent-binaries.component.html +++ b/src/app/config/engine/agent-binaries/agent-binaries.component.html @@ -1,5 +1,5 @@ - +
- {{agent.agentId}} --- {{ agent.clientSignature }} Unknown From ae7ec166ecc353259eba445b54eea44ab003b3ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Thu, 7 Sep 2023 23:09:46 +0100 Subject: [PATCH 018/419] Add agent page - Voucher padding --- src/app/agents/new-agent/new-agent.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/agents/new-agent/new-agent.component.html b/src/app/agents/new-agent/new-agent.component.html index 4e070852..100febdf 100644 --- a/src/app/agents/new-agent/new-agent.component.html +++ b/src/app/agents/new-agent/new-agent.component.html @@ -75,7 +75,7 @@
- +
Date: Thu, 7 Sep 2023 23:25:16 +0100 Subject: [PATCH 019/419] Hashlist create page - hashlist detector change Hashcat ID for Hashtype --- .../shared/hashtype-detector/hashtype-detector.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/shared/hashtype-detector/hashtype-detector.component.html b/src/app/shared/hashtype-detector/hashtype-detector.component.html index adbfd16f..5ca06f69 100644 --- a/src/app/shared/hashtype-detector/hashtype-detector.component.html +++ b/src/app/shared/hashtype-detector/hashtype-detector.component.html @@ -14,7 +14,7 @@ - + From 54d7629039c4c58e919b7a18426155dede9ae2bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Thu, 7 Sep 2023 23:40:05 +0100 Subject: [PATCH 020/419] Hashlist detail page -Remove eye and remove font in version/nam --- src/app/tasks/edit-tasks/edit-tasks.component.html | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/app/tasks/edit-tasks/edit-tasks.component.html b/src/app/tasks/edit-tasks/edit-tasks.component.html index 4d117a48..9a8d9f33 100644 --- a/src/app/tasks/edit-tasks/edit-tasks.component.html +++ b/src/app/tasks/edit-tasks/edit-tasks.component.html @@ -281,10 +281,9 @@
Cracker Information -
- Name: {{ crackerinfo['binaryName'] | lowercase | titlecase }}
- Version: {{ crackerinfo['version'] }} + Name: {{ crackerinfo['binaryName'] | lowercase | titlecase }}
+ Version: {{ crackerinfo['version'] }}
From dc73fbd301be44af30a7a1b19d50f23746a43594 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Fri, 8 Sep 2023 00:11:22 +0100 Subject: [PATCH 021/419] Header Username remove capitalize --- src/app/layout/header/header.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/layout/header/header.component.html b/src/app/layout/header/header.component.html index 83755b5d..f44dd114 100644 --- a/src/app/layout/header/header.component.html +++ b/src/app/layout/header/header.component.html @@ -149,7 +149,7 @@

{{ nb.title }}

- + From b389e2c20a5e679501b759786ae34532c5cbdd2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Fri, 8 Sep 2023 10:59:14 +0100 Subject: [PATCH 025/419] Datatables - Style Search and pagination --- src/styles/components/_button.scss | 4 ++++ src/styles/components/_table.scss | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/src/styles/components/_button.scss b/src/styles/components/_button.scss index b97d231e..5e386571 100644 --- a/src/styles/components/_button.scss +++ b/src/styles/components/_button.scss @@ -139,6 +139,10 @@ margin-left:-50px; } +.form-control-sm { + font-size: 1.01rem !important; +} + /* 01- End */ diff --git a/src/styles/components/_table.scss b/src/styles/components/_table.scss index 5330cbc2..45c7b2af 100644 --- a/src/styles/components/_table.scss +++ b/src/styles/components/_table.scss @@ -161,6 +161,11 @@ button.dt-button, div.dt-button, a.dt-button, input.dt-button { text-align: center !important; } +// Remove Previous when is in first and next when in last +.paginate_button.disabled, .paginate_button.disabled:hover, .paginate_button.disabled:active { + display:none +} + /* 01- End */ From 99db78c0b8fb71f0fbf701c2af3f34e32aa1f2a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Fri, 8 Sep 2023 11:23:52 +0100 Subject: [PATCH 026/419] All pages - title WordLists convert to Wordlists --- src/app/files/files.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/files/files.component.html b/src/app/files/files.component.html index 3925cb83..8814b74d 100644 --- a/src/app/files/files.component.html +++ b/src/app/files/files.component.html @@ -1,6 +1,6 @@
- +
From 5e1670e6c1f6a4659e176ff888191509d213fe8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Fri, 8 Sep 2023 11:38:17 +0100 Subject: [PATCH 027/419] Files overview page - line count thousand separator --- src/app/files/files.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/files/files.component.html b/src/app/files/files.component.html index 8814b74d..f444f09a 100644 --- a/src/app/files/files.component.html +++ b/src/app/files/files.component.html @@ -29,7 +29,7 @@
- + diff --git a/src/app/agents/show-agents/show-agents.component.html b/src/app/agents/show-agents/show-agents.component.html index 19f464df..93be8592 100644 --- a/src/app/agents/show-agents/show-agents.component.html +++ b/src/app/agents/show-agents/show-agents.component.html @@ -30,7 +30,7 @@ - + - + - + diff --git a/src/app/hashlists/edit-hashlist/edit-hashlist.component.html b/src/app/hashlists/edit-hashlist/edit-hashlist.component.html index c83a04dd..b2cd0c4b 100644 --- a/src/app/hashlists/edit-hashlist/edit-hashlist.component.html +++ b/src/app/hashlists/edit-hashlist/edit-hashlist.component.html @@ -136,7 +136,7 @@ {{ t.taskId }} diff --git a/src/app/hashlists/hashlist/hashlist.component.html b/src/app/hashlists/hashlist/hashlist.component.html index cdead6ba..8e47ac4b 100644 --- a/src/app/hashlists/hashlist/hashlist.component.html +++ b/src/app/hashlists/hashlist/hashlist.component.html @@ -18,7 +18,7 @@ diff --git a/src/app/tasks/edit-preconfigured-tasks/edit-preconfigured-tasks.component.html b/src/app/tasks/edit-preconfigured-tasks/edit-preconfigured-tasks.component.html index 94eaf229..a7abb408 100644 --- a/src/app/tasks/edit-preconfigured-tasks/edit-preconfigured-tasks.component.html +++ b/src/app/tasks/edit-preconfigured-tasks/edit-preconfigured-tasks.component.html @@ -159,7 +159,7 @@ diff --git a/src/app/tasks/edit-supertasks/edit-supertasks.component.html b/src/app/tasks/edit-supertasks/edit-supertasks.component.html index 599c692d..c47f9844 100644 --- a/src/app/tasks/edit-supertasks/edit-supertasks.component.html +++ b/src/app/tasks/edit-supertasks/edit-supertasks.component.html @@ -125,7 +125,7 @@

Pretasks

diff --git a/src/app/tasks/edit-tasks/edit-tasks.component.html b/src/app/tasks/edit-tasks/edit-tasks.component.html index 9a8d9f33..ef34d33a 100644 --- a/src/app/tasks/edit-tasks/edit-tasks.component.html +++ b/src/app/tasks/edit-tasks/edit-tasks.component.html @@ -282,7 +282,7 @@
Cracker Information
- Name:
{{ crackerinfo['binaryName'] | lowercase | titlecase }}
+ Name: {{ crackerinfo['binaryName'] }}
Version: {{ crackerinfo['version'] }} @@ -312,7 +312,7 @@
- + @@ -385,7 +385,7 @@
@@ -462,7 +462,7 @@
diff --git a/src/app/tasks/preconfigured-tasks/preconfigured-tasks.component.html b/src/app/tasks/preconfigured-tasks/preconfigured-tasks.component.html index eac4b000..a7b85cc4 100644 --- a/src/app/tasks/preconfigured-tasks/preconfigured-tasks.component.html +++ b/src/app/tasks/preconfigured-tasks/preconfigured-tasks.component.html @@ -20,7 +20,7 @@ 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 499dadb6..c8c2fe0e 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 @@ -25,7 +25,7 @@ - + diff --git a/src/app/users/globalpermissionsgroups/edit-globalpermissionsgroups/edit-globalpermissionsgroups.component.html b/src/app/users/globalpermissionsgroups/edit-globalpermissionsgroups/edit-globalpermissionsgroups.component.html index 2f9caa3e..588e2ed0 100644 --- a/src/app/users/globalpermissionsgroups/edit-globalpermissionsgroups/edit-globalpermissionsgroups.component.html +++ b/src/app/users/globalpermissionsgroups/edit-globalpermissionsgroups/edit-globalpermissionsgroups.component.html @@ -1,6 +1,6 @@
-

Global Permission Group {{ editedGPG.name | lowercase | titlecase }}

+

Global Permission Group {{ editedGPG.name }}

diff --git a/src/app/users/globalpermissionsgroups/globalpermissionsgroups.component.html b/src/app/users/globalpermissionsgroups/globalpermissionsgroups.component.html index a03d586d..cc9dfd9d 100644 --- a/src/app/users/globalpermissionsgroups/globalpermissionsgroups.component.html +++ b/src/app/users/globalpermissionsgroups/globalpermissionsgroups.component.html @@ -22,7 +22,7 @@
- + - +
Hashcat IDHashtype Description Example
IDCrackedUsername
{{ f.filename | shortenString:35 | lowercase | titlecase }} {{ f.size | fileSize:false }}{{ f.lineCount }}{{ f.lineCount | number: '2.' }} {{ f.accessGroup.groupName }} From b79528815cab52bf5539727880a394d3d7d880d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Fri, 8 Sep 2023 11:57:31 +0100 Subject: [PATCH 028/419] Cleanup Default_config and remove agentApiport --- src/config/default/app/main.ts | 1 - src/default/app/main.ts | 46 ------ src/default/app/tooltip.ts | 232 ------------------------------ src/default/report_layout/main.ts | 0 4 files changed, 279 deletions(-) delete mode 100644 src/default/app/main.ts delete mode 100644 src/default/app/tooltip.ts delete mode 100644 src/default/report_layout/main.ts diff --git a/src/config/default/app/main.ts b/src/config/default/app/main.ts index 604b417f..5db28795 100644 --- a/src/config/default/app/main.ts +++ b/src/config/default/app/main.ts @@ -2,7 +2,6 @@ export const DEFAULT_CONFIG = { prodApiEndpoint: 'http://localhost:8080/api/v2', prodApiMaxResults: '3000', agentURL: '/server.php', - agentApiPort: '8080', agentdownloadURL: '/agents.php?download=', appName: 'Hashtopolis', favicon: 'assets/img/favicon.ico', diff --git a/src/default/app/main.ts b/src/default/app/main.ts deleted file mode 100644 index 604b417f..00000000 --- a/src/default/app/main.ts +++ /dev/null @@ -1,46 +0,0 @@ -export const DEFAULT_CONFIG = { - prodApiEndpoint: 'http://localhost:8080/api/v2', - prodApiMaxResults: '3000', - agentURL: '/server.php', - agentApiPort: '8080', - agentdownloadURL: '/agents.php?download=', - appName: 'Hashtopolis', - favicon: 'assets/img/favicon.ico', - header: { - brand: { - logo: 'assets/img/logo.png', - logored: 'assets/img/logo_red.png', - name: '', - height: '60', - width: '60', - heightred: '60', - widthred: '60', - }, - }, - footer:{ - copyright: 's3in!c Hashtopolis: 0.14.0', - footer_link_one: { - link: 'https://github.com/hashtopolis', - name: 'Github' - }, - footer_link_two: { - link: 'https://discord.com/channels/419123475538509844/419123475538509846', - name: 'Discord' - }, - footer_link_three: { - link: '', - name: 'Help' - } - }, - agents:{ - statusOrderBy: 'asc', - statusOrderByName: 'agentName', - }, - tasks:{ - priority: 0, - maxAgents: 0, - chunkSize: 0, - }, - // File settings 10 * 1024 *1024 (5.24mb) - chunkSizeTUS: 5242880, -}; diff --git a/src/default/app/tooltip.ts b/src/default/app/tooltip.ts deleted file mode 100644 index 1d7022d1..00000000 --- a/src/default/app/tooltip.ts +++ /dev/null @@ -1,232 +0,0 @@ - -/* - * Tooltip generator - * 0 - Concise - * 1 - Precise - * 2 - Exhaustive - * -*/ -export const DEFAULT_CONFIG_TOOLTIP = { - tasks:{ - 0:{ - chunkTime: 'chunk size Level 0', - statusTimer: 'status timer level 0', - useNewBench: 'Concise infomation', - skipKeyspace: 'skipKeyspace level 0', - isCpuTask: 'Task is assigned only to CPU agents', - isSmall: 'Only one agent is assigned to the task', - preprocessorId: 'Preprocessor Level 0', - staticChunks: 'Static chunk level 0', - forcePipe: 'To apply rules before reject', - }, - 1:{ - chunkTime: 'chunk size Level 1', - statusTimer: 'status timer level 1', - useNewBench: 'Detailed infomation', - skipKeyspace: 'skipKeyspace level 1', - isCpuTask: 'Task is assigned only to CPU agents', - isSmall: 'Only one agent is assigned to the task', - preprocessorId: 'Preprocessor Level 1', - staticChunks: 'Static chunk level 1', - forcePipe: 'To apply rules before reject' - }, - 2:{ - chunkTime: 'chunk size Level 2', - statusTimer: 'status timer level 2', - skipKeyspace: 'skipKeyspace level 2', - useNewBench: 'Exhaustive infomation', - isCpuTask: 'Task is assigned only to CPU agents', - isSmall: 'Only one agent is assigned to the task', - preprocessorId: 'Preprocessor Level 2', - staticChunks: 'Static chunk level 2', - forcePipe: 'To apply rules before reject' - } - }, - config:{ - 0:{ - agent:{ - agenttimeout: 'Level 0', - benchtime: 'Level 0', - statustimer: 'Level 0', - agentDataLifetime: 'Level 0', - hideIpInfo: 'Level 0', - voucherDeletion: 'Level 0', - agentStatLimit: 'Level 0', - agentStatTension: 'Level 0', - agentTempThreshold1: 'Level 0', - agentTempThreshold2: 'Level 0', - agentUtilThreshold1: 'Level 0', - agentUtilThreshold2: 'Level 0', - }, - tc:{ - chunktime: 'Level 0', - disptolerance: 'Level 0', - defaultBenchmark: 'Level 0', - disableTrimming: 'Level 0', - hashlistAlias: 'Level 0', - blacklistChars: 'Level 0', - priority0Start: 'Level 0', - showTaskPerformance: 'Level 0', - ruleSplitSmallTasks: 'Level 0', - ruleSplitAlways: 'Level 0', - ruleSplitDisable: 'Level 0' - }, - hch:{ - maxHashlistSize: 'Level 0', - pagingSize: 'Level 0', - hashesPerPage: 'Level 0', - fieldseparator: 'Level 0', - hashlistImportCheck: 'Level 0', - batchSize: 'Level 0', - plainTextMaxLength: 'Level 0', - hashMaxLength: 'Level 0', - }, - notif:{ - emailSender: 'Level 0', - emailSenderName: 'Level 0', - telegramBotToken: 'Level 0', - notificationsProxyEnable: 'Level 0', - notificationsProxyServer: 'Level 0', - notificationsProxyPort: 'Level 0', - notificationsProxyType: 'Level 0', - }, - gs:{ - hashcatBrainEnable: 'Once enable, new options will show in hashlists', - hashcatBrainHost: 'Level 0', - hashcatBrainPort: 'Level 0', - hashcatBrainPass: 'Level 0', - hcErrorIgnore: 'Level 0', - numLogEntries: 'Level 0', - timefmt: 'Level 0', - maxSessionLength: 'Level 0', - baseHost: 'Level 0', - contactEmail: 'Level 0', - serverLogLevel: 'Level 0', - } - }, - 1:{ - agent:{ - agenttimeout: 'Level 1', - benchtime: 'Level 1', - statustimer: 'Level 1', - agentDataLifetime: 'Level 1', - hideIpInfo: 'Level 1', - voucherDeletion: 'Level 1', - agentStatLimit: 'Level 1', - agentStatTension: 'Level 1', - agentTempThreshold1: 'Level 1', - agentTempThreshold2: 'Level 1', - agentUtilThreshold1: 'Level 1', - agentUtilThreshold2: 'Level 1', - }, - tc:{ - chunktime: 'Level 1', - disptolerance: 'Level 1', - defaultBenchmark: 'Level 1', - disableTrimming: 'Level 1', - hashlistAlias: 'Level 1', - blacklistChars: 'Level 1', - priority0Start: 'Level 1', - showTaskPerformance: 'Level 1', - ruleSplitSmallTasks: 'Level 1', - ruleSplitAlways: 'Level 1', - ruleSplitDisable: 'Level 1' - }, - hch:{ - maxHashlistSize: 'Level 1', - pagingSize: 'Level 1', - hashesPerPage: 'Level 1', - fieldseparator: 'Level 1', - hashlistImportCheck: 'Level 1', - batchSize: 'Level 1', - plainTextMaxLength: 'Level 1', - hashMaxLength: 'Level 1', - }, - notif:{ - emailSender: 'Level 1', - emailSenderName: 'Level 1', - telegramBotToken: 'Level 1', - notificationsProxyEnable: 'Level 1', - notificationsProxyServer: 'Level 1', - notificationsProxyPort: 'Level 1', - notificationsProxyType: 'Level 1', - }, - gs:{ - hashcatBrainEnable: 'Once enable, new options will show in hashlists', - hashcatBrainHost: 'Level 1', - hashcatBrainPort: 'Level 1', - hashcatBrainPass: 'Level 1', - hcErrorIgnore: 'Level 1', - numLogEntries: 'Level 1', - timefmt: 'Level 1', - maxSessionLength: 'Level 1', - baseHost: 'Level 1', - contactEmail: 'Level 1', - serverLogLevel: 'Level 1', - } - }, - 2:{ - agent:{ - agenttimeout: 'Level 2', - benchtime: 'Level 2', - statustimer: 'Level 2', - agentDataLifetime: 'Level 2', - hideIpInfo: 'Level 2', - voucherDeletion: 'Level 2', - agentStatLimit: 'Level 2', - agentStatTension: 'Level 2', - agentTempThreshold1: 'Level 2', - agentTempThreshold2: 'Level 2', - agentUtilThreshold1: 'Level 2', - agentUtilThreshold2: 'Level 2', - }, - tc:{ - chunktime: 'Level 2', - disptolerance: 'Level 2', - defaultBenchmark: 'Level 2', - disableTrimming: 'Level 2', - hashlistAlias: 'Level 2', - blacklistChars: 'Level 2', - priority0Start: 'Level 2', - showTaskPerformance: 'Level 2', - ruleSplitSmallTasks: 'Level 2', - ruleSplitAlways: 'Level 2', - ruleSplitDisable: 'Level 2' - }, - hch:{ - maxHashlistSize: 'Level 2', - pagingSize: 'Level 2', - hashesPerPage: 'Level 2', - fieldseparator: 'Level 2', - hashlistImportCheck: 'Level 2', - batchSize: 'Level 2', - plainTextMaxLength: 'Level 2', - hashMaxLength: 'Level 2', - }, - notif:{ - emailSender: 'Level 2', - emailSenderName: 'Level 2', - telegramBotToken: 'Level 2', - notificationsProxyEnable: 'Level 2', - notificationsProxyServer: 'Level 2', - notificationsProxyPort: 'Level 2', - notificationsProxyType: 'Level 2', - }, - gs:{ - hashcatBrainEnable: 'Once enable, new options will show in hashlists', - hashcatBrainHost: 'Level 2', - hashcatBrainPort: 'Level 2', - hashcatBrainPass: 'Level 2', - hcErrorIgnore: 'Level 2', - numLogEntries: 'Level 2', - timefmt: 'Level 2', - maxSessionLength: 'Level 2', - baseHost: 'Level 2', - contactEmail: 'Level 2', - serverLogLevel: 'Level 2', - } - } - } -}; - - diff --git a/src/default/report_layout/main.ts b/src/default/report_layout/main.ts deleted file mode 100644 index e69de29b..00000000 From 402baaaff9eed81bb8c72ce1d9259fe59673cfb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Fri, 8 Sep 2023 13:33:10 +0100 Subject: [PATCH 029/419] All pages with 250 rows added and All rows --- src/app/account/notifications/notifications.component.ts | 4 ++++ src/app/agents/agent-status/agent-status.component.ts | 4 ++++ src/app/agents/edit-agent/edit-agent.component.ts | 4 ++++ src/app/agents/new-agent/new-agent.component.ts | 4 ++++ src/app/agents/show-agents/show-agents.component.ts | 4 ++++ .../config/engine/agent-binaries/agent-binaries.component.ts | 4 ++++ src/app/config/engine/crackers/crackers.component.ts | 4 ++++ .../config/engine/preprocessors/preprocessors.component.ts | 4 ++++ src/app/config/hashtypes/hashtypes.component.ts | 4 ++++ .../edit-health-check/edit-health-checks.component.ts | 4 ++++ src/app/config/log/log.component.ts | 4 ++++ src/app/files/files.component.ts | 4 ++++ src/app/hashlists/edit-hashlist/edit-hashlist.component.ts | 4 ++++ src/app/hashlists/hashes/hashes.component.ts | 4 ++++ src/app/hashlists/hashlist/hashlist.component.ts | 4 ++++ src/app/hashlists/show-cracks/show-cracks.component.ts | 4 ++++ src/app/hashlists/superhashlist/superhashlist.component.ts | 4 ++++ src/app/projects/projects.component.ts | 4 ++++ src/app/tasks/chunks/chunks.component.ts | 4 ++++ .../edit-preconfigured-tasks.component.ts | 4 ++++ src/app/tasks/edit-tasks/edit-tasks.component.ts | 4 ++++ src/app/tasks/import-supertasks/wrbulk/wrbulk.component.ts | 4 ++++ .../new-preconfigured-tasks.component.ts | 4 ++++ src/app/tasks/new-tasks/new-tasks.component.ts | 4 ++++ .../preconfigured-tasks/preconfigured-tasks.component.ts | 4 ++++ .../show-tasks/modal-subtasks/modal-subtasks.component.ts | 4 ++++ src/app/tasks/show-tasks/show-tasks.component.ts | 4 ++++ .../supertasks/modal-pretasks/modal-pretasks.component.ts | 4 ++++ src/app/users/all-users/all-users.component.ts | 4 ++++ .../globalpermissionsgroups.component.ts | 4 ++++ src/app/users/groups/groups.component.ts | 4 ++++ 31 files changed, 124 insertions(+) diff --git a/src/app/account/notifications/notifications.component.ts b/src/app/account/notifications/notifications.component.ts index daf87b63..1730e0c1 100644 --- a/src/app/account/notifications/notifications.component.ts +++ b/src/app/account/notifications/notifications.component.ts @@ -49,6 +49,10 @@ export class NotificationsComponent implements OnInit { dom: 'Bfrtip', scrollX: true, pageLength: 25, + lengthMenu: [ + [10, 25, 50, 100, 250, -1], + [10, 25, 50, 100, 250, 'All'] + ], stateSave: true, select: true, buttons: { diff --git a/src/app/agents/agent-status/agent-status.component.ts b/src/app/agents/agent-status/agent-status.component.ts index b2bc7f2d..8b51c8c7 100644 --- a/src/app/agents/agent-status/agent-status.component.ts +++ b/src/app/agents/agent-status/agent-status.component.ts @@ -96,6 +96,10 @@ export class AgentStatusComponent implements OnInit { dom: 'Bfrtip', scrollX: true, pageLength: 25, + lengthMenu: [ + [10, 25, 50, 100, 250, -1], + [10, 25, 50, 100, 250, 'All'] + ], scrollY: true, bDestroy: true, columnDefs: [ diff --git a/src/app/agents/edit-agent/edit-agent.component.ts b/src/app/agents/edit-agent/edit-agent.component.ts index 92a81c4f..9b4189b4 100644 --- a/src/app/agents/edit-agent/edit-agent.component.ts +++ b/src/app/agents/edit-agent/edit-agent.component.ts @@ -139,6 +139,10 @@ export class EditAgentComponent implements OnInit { dom: 'Bfrtip', scrollX: true, pageLength: 25, + lengthMenu: [ + [10, 25, 50, 100, 250, -1], + [10, 25, 50, 100, 250, 'All'] + ], scrollY: "700px", scrollCollapse: true, paging: false, diff --git a/src/app/agents/new-agent/new-agent.component.ts b/src/app/agents/new-agent/new-agent.component.ts index 15c2ab1b..6cd0bf04 100644 --- a/src/app/agents/new-agent/new-agent.component.ts +++ b/src/app/agents/new-agent/new-agent.component.ts @@ -81,6 +81,10 @@ export class NewAgentComponent implements OnInit, OnDestroy { dom: 'Bfrtip', scrollX: true, pageLength: 25, + lengthMenu: [ + [10, 25, 50, 100, 250, -1], + [10, 25, 50, 100, 250, 'All'] + ], stateSave: true, select: true, }; diff --git a/src/app/agents/show-agents/show-agents.component.ts b/src/app/agents/show-agents/show-agents.component.ts index 2431e8f0..2733bb58 100644 --- a/src/app/agents/show-agents/show-agents.component.ts +++ b/src/app/agents/show-agents/show-agents.component.ts @@ -68,6 +68,10 @@ export class ShowAgentsComponent implements OnInit, OnDestroy { dom: 'Bfrtip', scrollX: true, pageLength: 25, + lengthMenu: [ + [10, 25, 50, 100, 250, -1], + [10, 25, 50, 100, 250, 'All'] + ], stateSave: true, destroy: true, select: { diff --git a/src/app/config/engine/agent-binaries/agent-binaries.component.ts b/src/app/config/engine/agent-binaries/agent-binaries.component.ts index 6c3346c5..7c618c26 100644 --- a/src/app/config/engine/agent-binaries/agent-binaries.component.ts +++ b/src/app/config/engine/agent-binaries/agent-binaries.component.ts @@ -47,6 +47,10 @@ export class AgentBinariesComponent implements OnInit { dom: 'Bfrtip', scrollX: true, pageLength: 25, + lengthMenu: [ + [10, 25, 50, 100, 250, -1], + [10, 25, 50, 100, 250, 'All'] + ], stateSave: true, select: true, buttons: { diff --git a/src/app/config/engine/crackers/crackers.component.ts b/src/app/config/engine/crackers/crackers.component.ts index 12d90649..1b0b8f80 100644 --- a/src/app/config/engine/crackers/crackers.component.ts +++ b/src/app/config/engine/crackers/crackers.component.ts @@ -52,6 +52,10 @@ export class CrackersComponent implements OnInit, OnDestroy { dom: 'Bfrtip', scrollX: true, pageLength: 25, + lengthMenu: [ + [10, 25, 50, 100, 250, -1], + [10, 25, 50, 100, 250, 'All'] + ], stateSave: true, select: true, buttons: { diff --git a/src/app/config/engine/preprocessors/preprocessors.component.ts b/src/app/config/engine/preprocessors/preprocessors.component.ts index ddbf50bf..3508e1c2 100644 --- a/src/app/config/engine/preprocessors/preprocessors.component.ts +++ b/src/app/config/engine/preprocessors/preprocessors.component.ts @@ -49,6 +49,10 @@ export class PreprocessorsComponent implements OnInit { dom: 'Bfrtip', scrollX: true, pageLength: 25, + lengthMenu: [ + [10, 25, 50, 100, 250, -1], + [10, 25, 50, 100, 250, 'All'] + ], stateSave: true, select: true, buttons: { diff --git a/src/app/config/hashtypes/hashtypes.component.ts b/src/app/config/hashtypes/hashtypes.component.ts index 90344f56..7365e516 100644 --- a/src/app/config/hashtypes/hashtypes.component.ts +++ b/src/app/config/hashtypes/hashtypes.component.ts @@ -51,6 +51,10 @@ export class HashtypesComponent implements OnInit { dom: 'Bfrtip', scrollX: true, pageLength: 25, + lengthMenu: [ + [10, 25, 50, 100, 250, -1], + [10, 25, 50, 100, 250, 'All'] + ], processing: true, // Error loading deferRender: true, destroy:true, diff --git a/src/app/config/health-checks/edit-health-check/edit-health-checks.component.ts b/src/app/config/health-checks/edit-health-check/edit-health-checks.component.ts index b2fc1279..af318d3a 100644 --- a/src/app/config/health-checks/edit-health-check/edit-health-checks.component.ts +++ b/src/app/config/health-checks/edit-health-check/edit-health-checks.component.ts @@ -90,6 +90,10 @@ export class EditHealthChecksComponent implements OnInit { dom: 'Bfrtip', scrollX: true, pageLength: 25, + lengthMenu: [ + [10, 25, 50, 100, 250, -1], + [10, 25, 50, 100, 250, 'All'] + ], stateSave: true, select: true, buttons: { diff --git a/src/app/config/log/log.component.ts b/src/app/config/log/log.component.ts index 7c6aff96..bde831ef 100644 --- a/src/app/config/log/log.component.ts +++ b/src/app/config/log/log.component.ts @@ -100,6 +100,10 @@ export class LogComponent implements OnInit { dom: 'Bfrtip', scrollX: true, pageLength: 25, + lengthMenu: [ + [10, 25, 50, 100, 250, -1], + [10, 25, 50, 100, 250, 'All'] + ], bStateSave:true, bPaginate: false, bLengthChange: false, diff --git a/src/app/files/files.component.ts b/src/app/files/files.component.ts index 1777ffd9..b1f3a626 100644 --- a/src/app/files/files.component.ts +++ b/src/app/files/files.component.ts @@ -103,6 +103,10 @@ export class FilesComponent implements OnInit { dom: 'Bfrtip', scrollX: true, pageLength: 25, + lengthMenu: [ + [10, 25, 50, 100, 250, -1], + [10, 25, 50, 100, 250, 'All'] + ], scrollY: true, stateSave: true, destroy: true, diff --git a/src/app/hashlists/edit-hashlist/edit-hashlist.component.ts b/src/app/hashlists/edit-hashlist/edit-hashlist.component.ts index 8e51c553..9d66a5ea 100644 --- a/src/app/hashlists/edit-hashlist/edit-hashlist.component.ts +++ b/src/app/hashlists/edit-hashlist/edit-hashlist.component.ts @@ -181,6 +181,10 @@ export class EditHashlistComponent implements OnInit { dom: 'Bfrtip', scrollX: true, pageLength: 25, + lengthMenu: [ + [10, 25, 50, 100, 250, -1], + [10, 25, 50, 100, 250, 'All'] + ], bStateSave:true, destroy: true, buttons:[] diff --git a/src/app/hashlists/hashes/hashes.component.ts b/src/app/hashlists/hashes/hashes.component.ts index 483a1b6a..1f9ef1dc 100644 --- a/src/app/hashlists/hashes/hashes.component.ts +++ b/src/app/hashlists/hashes/hashes.component.ts @@ -68,6 +68,10 @@ export class HashesComponent implements OnInit { dom: 'Bfrtip', scrollX: true, pageLength: 25, + lengthMenu: [ + [10, 25, 50, 100, 250, -1], + [10, 25, 50, 100, 250, 'All'] + ], searching: false, buttons: { dom: { diff --git a/src/app/hashlists/hashlist/hashlist.component.ts b/src/app/hashlists/hashlist/hashlist.component.ts index 36a8ac73..55eb8f17 100644 --- a/src/app/hashlists/hashlist/hashlist.component.ts +++ b/src/app/hashlists/hashlist/hashlist.component.ts @@ -95,6 +95,10 @@ export class HashlistComponent implements OnInit, OnDestroy { dom: 'Bfrtip', scrollX: true, pageLength: 25, + lengthMenu: [ + [10, 25, 50, 100, 250, -1], + [10, 25, 50, 100, 250, 'All'] + ], stateSave: true, select: { style: 'multi', diff --git a/src/app/hashlists/show-cracks/show-cracks.component.ts b/src/app/hashlists/show-cracks/show-cracks.component.ts index a5ebb3fe..ffd76c13 100644 --- a/src/app/hashlists/show-cracks/show-cracks.component.ts +++ b/src/app/hashlists/show-cracks/show-cracks.component.ts @@ -46,6 +46,10 @@ export class ShowCracksComponent implements OnInit { dom: 'Bfrtip', scrollX: true, pageLength: 25, + lengthMenu: [ + [10, 25, 50, 100, 250, -1], + [10, 25, 50, 100, 250, 'All'] + ], stateSave: true, select: true, order: [[0, 'desc']], diff --git a/src/app/hashlists/superhashlist/superhashlist.component.ts b/src/app/hashlists/superhashlist/superhashlist.component.ts index e23b4dd3..35f251b9 100644 --- a/src/app/hashlists/superhashlist/superhashlist.component.ts +++ b/src/app/hashlists/superhashlist/superhashlist.component.ts @@ -49,6 +49,10 @@ export class SuperhashlistComponent implements OnInit { dom: 'Bfrtip', scrollX: true, pageLength: 25, + lengthMenu: [ + [10, 25, 50, 100, 250, -1], + [10, 25, 50, 100, 250, 'All'] + ], stateSave: true, select: true, buttons: { diff --git a/src/app/projects/projects.component.ts b/src/app/projects/projects.component.ts index 1721f637..b25e3e44 100644 --- a/src/app/projects/projects.component.ts +++ b/src/app/projects/projects.component.ts @@ -50,6 +50,10 @@ export class ProjectsComponent implements OnInit { dom: 'Bfrtip', scrollX: true, pageLength: 25, + lengthMenu: [ + [10, 25, 50, 100, 250, -1], + [10, 25, 50, 100, 250, 'All'] + ], bStateSave:true, destroy: true, select: { diff --git a/src/app/tasks/chunks/chunks.component.ts b/src/app/tasks/chunks/chunks.component.ts index 8d3c8d2b..f1ab8233 100644 --- a/src/app/tasks/chunks/chunks.component.ts +++ b/src/app/tasks/chunks/chunks.component.ts @@ -100,6 +100,10 @@ export class ChunksComponent implements OnInit { dom: 'Bfrtip', scrollX: true, pageLength: 25, + lengthMenu: [ + [10, 25, 50, 100, 250, -1], + [10, 25, 50, 100, 250, 'All'] + ], select: true, processing: true, deferRender: true, diff --git a/src/app/tasks/edit-preconfigured-tasks/edit-preconfigured-tasks.component.ts b/src/app/tasks/edit-preconfigured-tasks/edit-preconfigured-tasks.component.ts index ead9a9b6..3e0e8fde 100644 --- a/src/app/tasks/edit-preconfigured-tasks/edit-preconfigured-tasks.component.ts +++ b/src/app/tasks/edit-preconfigured-tasks/edit-preconfigured-tasks.component.ts @@ -95,6 +95,10 @@ export class EditPreconfiguredTasksComponent implements OnInit{ dom: 'Bfrtip', scrollX: true, pageLength: 25, + lengthMenu: [ + [10, 25, 50, 100, 250, -1], + [10, 25, 50, 100, 250, 'All'] + ], stateSave: true, select: true, buttons: [ ] diff --git a/src/app/tasks/edit-tasks/edit-tasks.component.ts b/src/app/tasks/edit-tasks/edit-tasks.component.ts index 48c66c53..181c6bc8 100644 --- a/src/app/tasks/edit-tasks/edit-tasks.component.ts +++ b/src/app/tasks/edit-tasks/edit-tasks.component.ts @@ -342,6 +342,10 @@ export class EditTasksComponent implements OnInit,PendingChangesGuard { dom: 'Bfrtip', scrollX: true, pageLength: 25, + lengthMenu: [ + [10, 25, 50, 100, 250, -1], + [10, 25, 50, 100, 250, 'All'] + ], scrollY: "700px", scrollCollapse: true, paging: false, diff --git a/src/app/tasks/import-supertasks/wrbulk/wrbulk.component.ts b/src/app/tasks/import-supertasks/wrbulk/wrbulk.component.ts index 867afc07..8a359152 100644 --- a/src/app/tasks/import-supertasks/wrbulk/wrbulk.component.ts +++ b/src/app/tasks/import-supertasks/wrbulk/wrbulk.component.ts @@ -51,6 +51,10 @@ export class WrbulkComponent implements OnInit { dom: 'Bfrtip', scrollX: true, pageLength: 25, + lengthMenu: [ + [10, 25, 50, 100, 250, -1], + [10, 25, 50, 100, 250, 'All'] + ], scrollY: "700px", scrollCollapse: true, paging: false, diff --git a/src/app/tasks/new-preconfigured-tasks/new-preconfigured-tasks.component.ts b/src/app/tasks/new-preconfigured-tasks/new-preconfigured-tasks.component.ts index ee879c07..265f23e4 100644 --- a/src/app/tasks/new-preconfigured-tasks/new-preconfigured-tasks.component.ts +++ b/src/app/tasks/new-preconfigured-tasks/new-preconfigured-tasks.component.ts @@ -199,6 +199,10 @@ export class NewPreconfiguredTasksComponent implements OnInit,AfterViewInit { dom: 'Bfrtip', scrollX: true, pageLength: 25, + lengthMenu: [ + [10, 25, 50, 100, 250, -1], + [10, 25, 50, 100, 250, 'All'] + ], scrollY: "1000px", scrollCollapse: true, paging: false, diff --git a/src/app/tasks/new-tasks/new-tasks.component.ts b/src/app/tasks/new-tasks/new-tasks.component.ts index d2e47809..d654e22c 100644 --- a/src/app/tasks/new-tasks/new-tasks.component.ts +++ b/src/app/tasks/new-tasks/new-tasks.component.ts @@ -229,6 +229,10 @@ export class NewTasksComponent implements OnInit { dom: 'Bfrtip', scrollX: true, pageLength: 25, + lengthMenu: [ + [10, 25, 50, 100, 250, -1], + [10, 25, 50, 100, 250, 'All'] + ], scrollY: "700px", scrollCollapse: true, paging: false, diff --git a/src/app/tasks/preconfigured-tasks/preconfigured-tasks.component.ts b/src/app/tasks/preconfigured-tasks/preconfigured-tasks.component.ts index 9e165eb8..4a8a428b 100644 --- a/src/app/tasks/preconfigured-tasks/preconfigured-tasks.component.ts +++ b/src/app/tasks/preconfigured-tasks/preconfigured-tasks.component.ts @@ -54,6 +54,10 @@ export class PreconfiguredTasksComponent implements OnInit { dom: 'Bfrtip', scrollX: true, pageLength: 25, + lengthMenu: [ + [10, 25, 50, 100, 250, -1], + [10, 25, 50, 100, 250, 'All'] + ], stateSave: true, select: true, buttons: { 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 eb5dc699..58a059b3 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 @@ -49,6 +49,10 @@ export class ModalSubtasksComponent { dom: 'Bfrtip', scrollX: true, pageLength: 25, + lengthMenu: [ + [10, 25, 50, 100, 250, -1], + [10, 25, 50, 100, 250, 'All'] + ], destroy: true, buttons:[] }; diff --git a/src/app/tasks/show-tasks/show-tasks.component.ts b/src/app/tasks/show-tasks/show-tasks.component.ts index 7e9ca078..8866a378 100644 --- a/src/app/tasks/show-tasks/show-tasks.component.ts +++ b/src/app/tasks/show-tasks/show-tasks.component.ts @@ -91,6 +91,10 @@ export class ShowTasksComponent implements OnInit { dom: 'Bfrtip', scrollX: true, pageLength: 25, + lengthMenu: [ + [10, 25, 50, 100, 250, -1], + [10, 25, 50, 100, 250, 'All'] + ], bStateSave:true, destroy: true, order: [], // Removes the default order by id. We need it to sort by priority. diff --git a/src/app/tasks/supertasks/modal-pretasks/modal-pretasks.component.ts b/src/app/tasks/supertasks/modal-pretasks/modal-pretasks.component.ts index 6090401b..efa6a25b 100644 --- a/src/app/tasks/supertasks/modal-pretasks/modal-pretasks.component.ts +++ b/src/app/tasks/supertasks/modal-pretasks/modal-pretasks.component.ts @@ -44,6 +44,10 @@ export class ModalPretasksComponent implements OnInit { dom: 'Bfrtip', scrollX: true, pageLength: 25, + lengthMenu: [ + [10, 25, 50, 100, 250, -1], + [10, 25, 50, 100, 250, 'All'] + ], destroy: true, buttons:[] }; diff --git a/src/app/users/all-users/all-users.component.ts b/src/app/users/all-users/all-users.component.ts index 6c385999..1549b5c5 100644 --- a/src/app/users/all-users/all-users.component.ts +++ b/src/app/users/all-users/all-users.component.ts @@ -71,6 +71,10 @@ export class AllUsersComponent implements OnInit, OnDestroy { dom: 'Bfrtip', scrollX: true, pageLength: 25, + lengthMenu: [ + [10, 25, 50, 100, 250, -1], + [10, 25, 50, 100, 250, 'All'] + ], stateSave: true, // "stateLoadParams": function (settings, data) { // return false; diff --git a/src/app/users/globalpermissionsgroups/globalpermissionsgroups.component.ts b/src/app/users/globalpermissionsgroups/globalpermissionsgroups.component.ts index d76057ea..fd68e5d2 100644 --- a/src/app/users/globalpermissionsgroups/globalpermissionsgroups.component.ts +++ b/src/app/users/globalpermissionsgroups/globalpermissionsgroups.component.ts @@ -52,6 +52,10 @@ export class GlobalpermissionsgroupsComponent implements OnInit { dom: 'Bfrtip', scrollX: true, pageLength: 25, + lengthMenu: [ + [10, 25, 50, 100, 250, -1], + [10, 25, 50, 100, 250, 'All'] + ], select: true, processing: true, // Error loading deferRender: true, diff --git a/src/app/users/groups/groups.component.ts b/src/app/users/groups/groups.component.ts index 24697c09..b3857a5b 100644 --- a/src/app/users/groups/groups.component.ts +++ b/src/app/users/groups/groups.component.ts @@ -57,6 +57,10 @@ export class GroupsComponent implements OnInit { dom: 'Bfrtip', scrollX: true, pageLength: 25, + lengthMenu: [ + [10, 25, 50, 100, 250, -1], + [10, 25, 50, 100, 250, 'All'] + ], select: true, processing: true, // Error loading deferRender: true, From 7462c03e34135eaeb96d27c9aaa0efe625c3f636 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Fri, 8 Sep 2023 13:53:36 +0100 Subject: [PATCH 030/419] All pages - change yellow hover for gray color used in darkmode --- src/styles/components/_table.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/styles/components/_table.scss b/src/styles/components/_table.scss index 45c7b2af..d81f3faa 100644 --- a/src/styles/components/_table.scss +++ b/src/styles/components/_table.scss @@ -131,7 +131,7 @@ div.dt-button-collection { } .table.dataTable.table-hover>tbody>tr:hover>*{ - background-color: rgb(249, 249, 167); + background-color: rgb(117, 117, 114); } .btn-actions { From 65abf00067fac648a1d705b710413de375db11fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Fri, 8 Sep 2023 14:47:34 +0100 Subject: [PATCH 031/419] All pages- Remove name capitalize --- .../agents/agent-status/agent-status.component.html | 12 ++++++------ src/app/agents/edit-agent/edit-agent.component.html | 2 +- .../agents/show-agents/show-agents.component.html | 2 +- .../config/engine/crackers/crackers.component.html | 2 +- .../preprocessors/preprocessors.component.html | 2 +- .../edit-health-checks.component.html | 2 +- src/app/files/files.component.html | 2 +- .../edit-hashlist/edit-hashlist.component.html | 2 +- src/app/hashlists/hashlist/hashlist.component.html | 2 +- .../hashlists/show-cracks/show-cracks.component.html | 2 +- .../superhashlist/superhashlist.component.html | 2 +- src/app/tasks/chunks/chunks.component.html | 4 ++-- .../edit-preconfigured-tasks.component.html | 2 +- .../edit-supertasks/edit-supertasks.component.html | 2 +- src/app/tasks/edit-tasks/edit-tasks.component.html | 8 ++++---- .../preconfigured-tasks.component.html | 2 +- .../modal-subtasks/modal-subtasks.component.html | 2 +- src/app/tasks/show-tasks/show-tasks.component.html | 8 ++++---- .../edit-globalpermissionsgroups.component.html | 2 +- .../globalpermissionsgroups.component.html | 2 +- src/app/users/groups/groups.component.html | 2 +- 21 files changed, 33 insertions(+), 33 deletions(-) diff --git a/src/app/agents/agent-status/agent-status.component.html b/src/app/agents/agent-status/agent-status.component.html index 35ce67a9..a7803474 100644 --- a/src/app/agents/agent-status/agent-status.component.html +++ b/src/app/agents/agent-status/agent-status.component.html @@ -56,7 +56,7 @@

- {{ a.agentName | shortenString:15 | lowercase | titlecase}} + {{ a.agentName | shortenString:15 }}

- {{ aa.agentName | shortenString:15 | lowercase | titlecase }} + {{ aa.agentName | shortenString:15 }} @@ -440,7 +440,7 @@ - {{ aa.taskName | shortenString:15 | lowercase | titlecase }} + {{ aa.taskName | shortenString:15 }} diff --git a/src/app/agents/edit-agent/edit-agent.component.html b/src/app/agents/edit-agent/edit-agent.component.html index e43cfa85..cb7b50a7 100644 --- a/src/app/agents/edit-agent/edit-agent.component.html +++ b/src/app/agents/edit-agent/edit-agent.component.html @@ -272,7 +272,7 @@

Agent Detailed Information

{{ gc.progress/100 }} % N/A - {{ gc.taskName | shortenString:15 | lowercase | titlecase}} + {{ gc.taskName | shortenString:15 }} {{ gc.dispatchTime | uiDate }} (No activity) - {{ agent.agentName | shortenString:40 | lowercase | titlecase }} + {{ agent.agentName | shortenString:40 }} diff --git a/src/app/config/engine/crackers/crackers.component.html b/src/app/config/engine/crackers/crackers.component.html index 5d6bdfd2..a8150687 100644 --- a/src/app/config/engine/crackers/crackers.component.html +++ b/src/app/config/engine/crackers/crackers.component.html @@ -14,7 +14,7 @@
{{ type.crackerBinaryTypeId }}{{ type.typeName | lowercase | titlecase }}{{ type.typeName }}
{{ ver.version }} diff --git a/src/app/config/engine/preprocessors/preprocessors.component.html b/src/app/config/engine/preprocessors/preprocessors.component.html index 8f4a477e..f5712724 100644 --- a/src/app/config/engine/preprocessors/preprocessors.component.html +++ b/src/app/config/engine/preprocessors/preprocessors.component.html @@ -13,7 +13,7 @@
{{ p.preprocessorId }}{{ p.name | lowercase | titlecase }}{{ p.name }}
{{ ha.healthCheckAgentId }} - {{ ha.agentName | shortenString:15 | lowercase | titlecase }} + {{ ha.agentName | shortenString:15 }} diff --git a/src/app/files/files.component.html b/src/app/files/files.component.html index f444f09a..2f24da17 100644 --- a/src/app/files/files.component.html +++ b/src/app/files/files.component.html @@ -26,7 +26,7 @@
{{ f.fileId }}{{ f.filename | shortenString:35 | lowercase | titlecase }}{{ f.filename | shortenString:35 }} {{ f.size | fileSize:false }} {{ f.lineCount | number: '2.' }} - {{ t.taskName | lowercase | titlecase }} + {{ t.taskName }} {{ t.taskId | tdispatched:t.keyspace | async | percent:'1.2-2'}} {{ t.taskId | tdsearched:t.keyspace | async | percent:'1.2-2'}}
{{ list.hashlistId }} - {{ list.name | shortenString:35 | lowercase | titlecase }} + {{ list.name | shortenString:35 }} diff --git a/src/app/hashlists/show-cracks/show-cracks.component.html b/src/app/hashlists/show-cracks/show-cracks.component.html index 902b7f49..d2e2528a 100644 --- a/src/app/hashlists/show-cracks/show-cracks.component.html +++ b/src/app/hashlists/show-cracks/show-cracks.component.html @@ -21,7 +21,7 @@ {{ hash.plaintext }} {{ hash.hash }} - {{ hash.hashlist.name | shortenString:30 | lowercase | titlecase }} + {{ hash.hashlist.name | shortenString:30 }} {{ hash.chunk['agentId'] }} diff --git a/src/app/hashlists/superhashlist/superhashlist.component.html b/src/app/hashlists/superhashlist/superhashlist.component.html index eb780d47..b1eb2a45 100644 --- a/src/app/hashlists/superhashlist/superhashlist.component.html +++ b/src/app/hashlists/superhashlist/superhashlist.component.html @@ -19,7 +19,7 @@
{{ list.hashlistId }} - {{ list.name | shortenString:35 | lowercase | titlecase }} + {{ list.name | shortenString:35 }} diff --git a/src/app/tasks/chunks/chunks.component.html b/src/app/tasks/chunks/chunks.component.html index dde99771..25186e42 100644 --- a/src/app/tasks/chunks/chunks.component.html +++ b/src/app/tasks/chunks/chunks.component.html @@ -34,10 +34,10 @@ {{ c.progress/100 }} % N/A - {{ c.taskName | shortenString:15 | lowercase | titlecase}} + {{ c.taskName | shortenString:15 }} - {{ c.agentName | shortenString:15 | lowercase | titlecase }} + {{ c.agentName | shortenString:15 }} {{ c.dispatchTime | uiDate }} (No acitivity){{ f.fileId }} - {{ f.filename | shortenString:35 | lowercase | titlecase }} + {{ f.filename | shortenString:35 }} - {{ p.taskName | shortenString:35 | lowercase | titlecase }} + {{ p.taskName | shortenString:35 }} {{ p.attackCmd }}
{{ file.fileId }}{{ file.filename | shortenString:15 | lowercase | titlecase }}{{ file.filename | shortenString:15 }} {{ file.fileType | fileType }} {{ file.size | fileSize:false }}
- {{ a.agentName | shortenString:30 | lowercase | titlecase }} + {{ a.agentName | shortenString:30 }} {{ gc.progress/100 }} % N/A - {{ gc.agentName | shortenString:15 | lowercase | titlecase }} + {{ gc.agentName | shortenString:15 }} {{ gc.dispatchTime | uiDate }} (No acitivity) - {{ ptask.taskName | shortenString:20 | lowercase | titlecase }} + {{ ptask.taskName | shortenString:20 }} {{ ptask.attackCmd | shortenString:25 }}{{ st.taskId }} - {{ st.taskName | shortenString:40 | lowercase | titlecase }} + {{ st.taskName | shortenString:40 }} diff --git a/src/app/tasks/show-tasks/show-tasks.component.html b/src/app/tasks/show-tasks/show-tasks.component.html index 6569cda6..7539954f 100644 --- a/src/app/tasks/show-tasks/show-tasks.component.html +++ b/src/app/tasks/show-tasks/show-tasks.component.html @@ -32,10 +32,10 @@ {{ task.taskWrapperId }} - {{ task.taskName | shortenString:40 | lowercase | titlecase }} + {{ task.taskName | shortenString:40 }} {{ task.taskWrapperName | shortenString:40 | lowercase | titlecase }} {{ task.taskWrapperName | shortenString:40 }}
@@ -61,12 +61,12 @@
{{ task.preprocessorId === 1 ? 'Prince':'-'}} - - {{ task.hashlist[0].name | shortenString:30 | lowercase | titlecase}} + {{ task.hashlist[0].name | shortenString:30 }} - {{ task.name | shortenString:30 | lowercase | titlecase }} + {{ task.name | shortenString:30 }}
{{ a.id }}{{ a.name | lowercase | titlecase }}{{ a.name }} {{ a.user.length }} diff --git a/src/app/users/groups/groups.component.html b/src/app/users/groups/groups.component.html index 164caab0..356cd77a 100644 --- a/src/app/users/groups/groups.component.html +++ b/src/app/users/groups/groups.component.html @@ -12,7 +12,7 @@
{{ a.accessGroupId }}{{ a.groupName | lowercase | titlecase }}{{ a.groupName }}
diff --git a/src/styles/components/_table.scss b/src/styles/components/_table.scss index d81f3faa..d4cc0798 100644 --- a/src/styles/components/_table.scss +++ b/src/styles/components/_table.scss @@ -81,6 +81,10 @@ table.dataTable { margin-bottom: 0px !important; } +.remove-caret::after { + display: none !important; +} + .table.dataTable.table-hover>tbody>tr:hover>*{ background-color: rgb(249, 249, 167); } From 257ecd77b3cc85ff95a6bc9030c99b43a343abd9 Mon Sep 17 00:00:00 2001 From: xbenyx Date: Mon, 25 Sep 2023 10:04:23 +0000 Subject: [PATCH 035/419] Note field to text area --- src/app/hashlists/new-hashlist/new-hashlist.component.html | 5 +++-- src/app/tasks/new-tasks/new-tasks.component.html | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/app/hashlists/new-hashlist/new-hashlist.component.html b/src/app/hashlists/new-hashlist/new-hashlist.component.html index a2f04aec..a0b0df3e 100644 --- a/src/app/hashlists/new-hashlist/new-hashlist.component.html +++ b/src/app/hashlists/new-hashlist/new-hashlist.component.html @@ -1,6 +1,6 @@ - + @@ -12,12 +12,13 @@ > - + +
From f2032bf54ac52ba09d486c13af37307d9a5a09b5 Mon Sep 17 00:00:00 2001 From: xbenyx Date: Mon, 25 Sep 2023 10:06:52 +0000 Subject: [PATCH 036/419] Agent status remove eye icon and make clickable --- .../agents/agent-status/agent-status.component.html | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/app/agents/agent-status/agent-status.component.html b/src/app/agents/agent-status/agent-status.component.html index a7803474..d26d8912 100644 --- a/src/app/agents/agent-status/agent-status.component.html +++ b/src/app/agents/agent-status/agent-status.component.html @@ -53,10 +53,7 @@

- {{ aa.agentName | shortenString:15 }} + {{ aa.agentName | shortenString:15 }} @@ -440,7 +437,7 @@ - {{ aa.taskName | shortenString:15 }} + {{ aa.taskName | shortenString:15 }} From 07e6f47fb237bc75f3f14f0eb3278b9008d1aa82 Mon Sep 17 00:00:00 2001 From: xbenyx Date: Mon, 25 Sep 2023 10:07:43 +0000 Subject: [PATCH 037/419] New Agent, always display instructions --- .../agents/new-agent/new-agent.component.html | 74 +++++++++++-------- 1 file changed, 43 insertions(+), 31 deletions(-) diff --git a/src/app/agents/new-agent/new-agent.component.html b/src/app/agents/new-agent/new-agent.component.html index 100febdf..f2f20cf8 100644 --- a/src/app/agents/new-agent/new-agent.component.html +++ b/src/app/agents/new-agent/new-agent.component.html @@ -1,32 +1,24 @@ - - - - Instructions - - -
    -
  1. In clients, download the file and run it
  2. -
  3. Generate voucher to link with agent (Used vouchers are automatically deleted)
  4. -
  5. - Using the URL, link the agent with the app: - {{ agentURL }} - -
  6. -
-
-
-
+
Instructions
+
    +
  1. In clients, download the file and run it
  2. +
  3. Generate voucher to link with agent (Used vouchers are automatically deleted)
  4. +
  5. + Using the URL, link the agent with the app: + {{ agentURL }} + +
  6. +
@@ -73,7 +65,7 @@
-
+ + +
+ +
+
+ +
+
+ +
+
+ +
+ @@ -122,6 +134,6 @@
-
-
+ From 53d3addbac7d382b274c9feeaf749641286d8b5e Mon Sep 17 00:00:00 2001 From: xbenyx Date: Mon, 25 Sep 2023 10:09:09 +0000 Subject: [PATCH 038/419] All pages, full width --- .../notifications/notification/new-notification.component.html | 2 +- .../account/settings/acc-settings/acc-settings.component.html | 2 +- src/app/account/settings/ui-settings/ui-settings.component.html | 2 +- src/app/auth/auth.component.html | 2 +- src/app/auth/forgot/forgot.component.html | 2 +- .../agent-binary/new-agent-binaries.component.html | 2 +- .../engine/crackers/edit-version/edit-crackers.component.html | 2 +- .../engine/crackers/new-cracker/new-cracker.component.html | 2 +- .../engine/crackers/new-version/new-crackers.component.html | 2 +- .../preprocessors/preprocessor/new-preprocessor.component.html | 2 +- src/app/config/hashtypes/hashtype/hashtype.component.html | 2 +- .../new-health-check/new-health-checks.component.html | 2 +- src/app/files/files-edit/files-edit.component.html | 2 +- .../new-superhashlist/new-superhashlist.component.html | 2 +- src/app/tasks/new-supertasks/new-supertasks.component.html | 2 +- .../new-globalpermissionsgroups.component.html | 2 +- src/app/users/groups/cu-group/cu-group.component.html | 2 +- src/app/users/users.component.html | 2 +- 18 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/app/account/notifications/notification/new-notification.component.html b/src/app/account/notifications/notification/new-notification.component.html index 7ebd14f3..7c295544 100644 --- a/src/app/account/notifications/notification/new-notification.component.html +++ b/src/app/account/notifications/notification/new-notification.component.html @@ -1,4 +1,4 @@ - +
diff --git a/src/app/account/settings/acc-settings/acc-settings.component.html b/src/app/account/settings/acc-settings/acc-settings.component.html index 99d2fa5b..ceb82331 100644 --- a/src/app/account/settings/acc-settings/acc-settings.component.html +++ b/src/app/account/settings/acc-settings/acc-settings.component.html @@ -1,5 +1,5 @@ - +
diff --git a/src/app/account/settings/ui-settings/ui-settings.component.html b/src/app/account/settings/ui-settings/ui-settings.component.html index 0e87dbca..5451d1e3 100644 --- a/src/app/account/settings/ui-settings/ui-settings.component.html +++ b/src/app/account/settings/ui-settings/ui-settings.component.html @@ -1,6 +1,6 @@ - +
diff --git a/src/app/auth/auth.component.html b/src/app/auth/auth.component.html index c404614b..a81d1801 100644 --- a/src/app/auth/auth.component.html +++ b/src/app/auth/auth.component.html @@ -1,6 +1,6 @@
- +

Log In

Enter your credentials to access.
diff --git a/src/app/auth/forgot/forgot.component.html b/src/app/auth/forgot/forgot.component.html index 1baed98a..370df61e 100644 --- a/src/app/auth/forgot/forgot.component.html +++ b/src/app/auth/forgot/forgot.component.html @@ -1,4 +1,4 @@ - +

Forgot password

diff --git a/src/app/config/engine/agent-binaries/agent-binary/new-agent-binaries.component.html b/src/app/config/engine/agent-binaries/agent-binary/new-agent-binaries.component.html index acdf9ea3..0c718c88 100644 --- a/src/app/config/engine/agent-binaries/agent-binary/new-agent-binaries.component.html +++ b/src/app/config/engine/agent-binaries/agent-binary/new-agent-binaries.component.html @@ -1,4 +1,4 @@ - +
diff --git a/src/app/config/engine/crackers/edit-version/edit-crackers.component.html b/src/app/config/engine/crackers/edit-version/edit-crackers.component.html index bb302530..20aa9d67 100644 --- a/src/app/config/engine/crackers/edit-version/edit-crackers.component.html +++ b/src/app/config/engine/crackers/edit-version/edit-crackers.component.html @@ -1,6 +1,6 @@ - +
diff --git a/src/app/config/engine/crackers/new-cracker/new-cracker.component.html b/src/app/config/engine/crackers/new-cracker/new-cracker.component.html index 9b3cfd1e..a2eb4f42 100644 --- a/src/app/config/engine/crackers/new-cracker/new-cracker.component.html +++ b/src/app/config/engine/crackers/new-cracker/new-cracker.component.html @@ -1,6 +1,6 @@ - +
diff --git a/src/app/config/engine/crackers/new-version/new-crackers.component.html b/src/app/config/engine/crackers/new-version/new-crackers.component.html index f06d37b6..5da90080 100644 --- a/src/app/config/engine/crackers/new-version/new-crackers.component.html +++ b/src/app/config/engine/crackers/new-version/new-crackers.component.html @@ -1,5 +1,5 @@ - +
diff --git a/src/app/config/engine/preprocessors/preprocessor/new-preprocessor.component.html b/src/app/config/engine/preprocessors/preprocessor/new-preprocessor.component.html index f7de3a1a..4cbfe614 100644 --- a/src/app/config/engine/preprocessors/preprocessor/new-preprocessor.component.html +++ b/src/app/config/engine/preprocessors/preprocessor/new-preprocessor.component.html @@ -7,7 +7,7 @@
- +
diff --git a/src/app/config/hashtypes/hashtype/hashtype.component.html b/src/app/config/hashtypes/hashtype/hashtype.component.html index de8de9a9..adc30edb 100644 --- a/src/app/config/hashtypes/hashtype/hashtype.component.html +++ b/src/app/config/hashtypes/hashtype/hashtype.component.html @@ -7,7 +7,7 @@
- +
diff --git a/src/app/config/health-checks/new-health-check/new-health-checks.component.html b/src/app/config/health-checks/new-health-check/new-health-checks.component.html index 1c98793e..25c74b6e 100644 --- a/src/app/config/health-checks/new-health-check/new-health-checks.component.html +++ b/src/app/config/health-checks/new-health-check/new-health-checks.component.html @@ -1,6 +1,6 @@ - +
diff --git a/src/app/files/files-edit/files-edit.component.html b/src/app/files/files-edit/files-edit.component.html index 97d87e4f..06d04304 100644 --- a/src/app/files/files-edit/files-edit.component.html +++ b/src/app/files/files-edit/files-edit.component.html @@ -1,6 +1,6 @@ - +
diff --git a/src/app/hashlists/new-superhashlist/new-superhashlist.component.html b/src/app/hashlists/new-superhashlist/new-superhashlist.component.html index e70a0847..12f1f9df 100644 --- a/src/app/hashlists/new-superhashlist/new-superhashlist.component.html +++ b/src/app/hashlists/new-superhashlist/new-superhashlist.component.html @@ -1,6 +1,6 @@ - +
diff --git a/src/app/tasks/new-supertasks/new-supertasks.component.html b/src/app/tasks/new-supertasks/new-supertasks.component.html index 192b85b3..e938a314 100644 --- a/src/app/tasks/new-supertasks/new-supertasks.component.html +++ b/src/app/tasks/new-supertasks/new-supertasks.component.html @@ -1,5 +1,5 @@ - + diff --git a/src/app/users/globalpermissionsgroups/new-globalpermissionsgroups/new-globalpermissionsgroups.component.html b/src/app/users/globalpermissionsgroups/new-globalpermissionsgroups/new-globalpermissionsgroups.component.html index 77a9bd61..cc6a0dc3 100644 --- a/src/app/users/globalpermissionsgroups/new-globalpermissionsgroups/new-globalpermissionsgroups.component.html +++ b/src/app/users/globalpermissionsgroups/new-globalpermissionsgroups/new-globalpermissionsgroups.component.html @@ -1,5 +1,5 @@ - +
diff --git a/src/app/users/groups/cu-group/cu-group.component.html b/src/app/users/groups/cu-group/cu-group.component.html index e308e501..87e46f23 100644 --- a/src/app/users/groups/cu-group/cu-group.component.html +++ b/src/app/users/groups/cu-group/cu-group.component.html @@ -1,5 +1,5 @@ - +
diff --git a/src/app/users/users.component.html b/src/app/users/users.component.html index 68b15bc9..ef21ace4 100644 --- a/src/app/users/users.component.html +++ b/src/app/users/users.component.html @@ -1,5 +1,5 @@ - +
From 5f2e10e9eb90c0fa46a1fbdd3097e6459eeb62d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Tue, 26 Sep 2023 08:07:02 +0100 Subject: [PATCH 039/419] All pages - notification small modal --- .../edit-notification.component.ts | 13 ++++---- .../new-notification.component.ts | 6 ++-- .../notifications/notifications.component.ts | 6 ++-- .../acc-settings/acc-settings.component.ts | 8 ++--- .../agents/edit-agent/edit-agent.component.ts | 16 ++++----- .../agents/new-agent/new-agent.component.ts | 13 ++++---- .../show-agents/show-agents.component.ts | 16 +++++---- .../agent-binaries.component.ts | 6 ++-- .../new-agent-binaries.component.ts | 15 +++++---- .../engine/crackers/crackers.component.ts | 6 ++-- .../edit-version/edit-crackers.component.ts | 14 ++++---- .../new-cracker/new-cracker.component.ts | 7 ++-- .../new-version/new-crackers.component.ts | 7 ++-- .../new-preprocessor.component.ts | 15 +++++---- .../preprocessors/preprocessors.component.ts | 8 ++--- .../hashtypes/hashtype/hashtype.component.ts | 12 ++++--- .../config/hashtypes/hashtypes.component.ts | 6 ++-- .../health-checks/health-checks.component.ts | 6 ++-- .../new-health-checks.component.ts | 7 ++-- .../files/files-edit/files-edit.component.ts | 16 +++++---- src/app/files/files.component.ts | 15 +++++---- .../files/new-files/new-files.component.ts | 7 ++-- .../edit-hashlist/edit-hashlist.component.ts | 8 ++--- .../hashlists/hashlist/hashlist.component.ts | 29 ++++++++-------- .../new-hashlist/new-hashlist.component.ts | 7 ++-- .../new-superhashlist.component.ts | 7 ++-- .../superhashlist/superhashlist.component.ts | 6 ++-- .../edit-preconfigured-tasks.component.ts | 8 ++--- .../edit-supertasks.component.ts | 19 ++++++----- .../tasks/edit-tasks/edit-tasks.component.ts | 33 +++++++++++-------- .../masks/masks.component.ts | 8 ++--- .../wrbulk/wrbulk.component.ts | 1 + .../new-preconfigured-tasks.component.ts | 7 ++-- .../new-supertasks.component.ts | 7 ++-- .../tasks/new-tasks/new-tasks.component.ts | 7 ++-- .../preconfigured-tasks.component.ts | 5 +-- .../modal-subtasks.component.ts | 10 +++--- .../tasks/show-tasks/show-tasks.component.ts | 26 +++++++++------ .../supertasks/applyhashlist.component.ts | 6 ++-- .../tasks/supertasks/supertasks.component.ts | 5 +-- .../users/all-users/all-users.component.ts | 5 +-- .../users/edit-users/edit-users.component.ts | 13 ++++---- .../edit-globalpermissionsgroups.component.ts | 5 +-- .../globalpermissionsgroups.component.ts | 6 ++-- .../new-globalpermissionsgroups.component.ts | 5 +-- .../groups/cu-group/cu-group.component.ts | 13 ++++---- src/app/users/groups/groups.component.ts | 5 +-- src/app/users/users.component.ts | 6 ++-- 48 files changed, 264 insertions(+), 218 deletions(-) diff --git a/src/app/account/notifications/notification/edit-notification.component.ts b/src/app/account/notifications/notification/edit-notification.component.ts index fcf8c2fe..4050d91c 100644 --- a/src/app/account/notifications/notification/edit-notification.component.ts +++ b/src/app/account/notifications/notification/edit-notification.component.ts @@ -3,11 +3,12 @@ import { Component, OnInit } from '@angular/core'; import Swal from 'sweetalert2/dist/sweetalert2.js'; import { ACTIONARRAY, ACTION, NOTIFARRAY } from '../../../core/_constants/notifications.config'; -import { environment } from '../../../../environments/environment'; import { GlobalService } from 'src/app/core/_services/main.service'; +import { environment } from '../../../../environments/environment'; +import { ActivatedRoute, Params, Router } from '@angular/router'; import { PageTitle } from 'src/app/core/_decorators/autotitle'; import { SERV } from '../../../core/_services/main.config'; -import { ActivatedRoute, Params, Router } from '@angular/router'; + @Component({ selector: 'app-edit-notification', @@ -76,12 +77,12 @@ export class EditNotificationComponent implements OnInit { this.gs.update(SERV.NOTIFICATIONS,this.editedIndex,{'isActive':this.createForm.value['isActive']}).subscribe(() => { Swal.fire({ - title: "Success!", - text: "New Notification created!", - icon: "success", + position: 'top-end', + icon: 'success', + title: "Saved", showConfirmButton: false, timer: 1500 - }); + }) this.ngOnInit(); this.router.navigate(['/account/notifications']); } diff --git a/src/app/account/notifications/notification/new-notification.component.ts b/src/app/account/notifications/notification/new-notification.component.ts index 0791138e..5af1d0b1 100644 --- a/src/app/account/notifications/notification/new-notification.component.ts +++ b/src/app/account/notifications/notification/new-notification.component.ts @@ -165,15 +165,15 @@ export class NewNotificationComponent implements OnInit { onSubmit(){ if (this.createForm.valid) { - this.gs.create(SERV.NOTIFICATIONS,this.createForm.value).subscribe(() => { Swal.fire({ + position: 'top-end', + icon: 'success', title: "Success!", text: "New Notification created!", - icon: "success", showConfirmButton: false, timer: 1500 - }); + }) this.ngOnInit(); this.router.navigate(['/account/notifications']); } diff --git a/src/app/account/notifications/notifications.component.ts b/src/app/account/notifications/notifications.component.ts index 1730e0c1..73400b40 100644 --- a/src/app/account/notifications/notifications.component.ts +++ b/src/app/account/notifications/notifications.component.ts @@ -161,11 +161,11 @@ export class NotificationsComponent implements OnInit { if (result.isConfirmed) { this.gs.delete(SERV.NOTIFICATIONS,id).subscribe(() => { Swal.fire({ - title: "Success", - icon: "success", + position: 'top-end', + icon: 'success', showConfirmButton: false, timer: 1500 - }); + }) this.ngOnInit(); this.rerender(); // rerender datatables }); diff --git a/src/app/account/settings/acc-settings/acc-settings.component.ts b/src/app/account/settings/acc-settings/acc-settings.component.ts index 69449322..e463c57c 100644 --- a/src/app/account/settings/acc-settings/acc-settings.component.ts +++ b/src/app/account/settings/acc-settings/acc-settings.component.ts @@ -68,12 +68,12 @@ export class AccountSettingsComponent implements OnInit { if (this.updateForm.valid) { this.gs.create(SERV.USERS,this.updateForm.value).subscribe(() => { Swal.fire({ - title: "Success", - text: "Updated!", - icon: "success", + position: 'top-end', + icon: 'success', + title: 'Saved', showConfirmButton: false, timer: 1500 - }); + }) this.router.navigate(['users/all-users']); } ); diff --git a/src/app/agents/edit-agent/edit-agent.component.ts b/src/app/agents/edit-agent/edit-agent.component.ts index 9b4189b4..3abfa69b 100644 --- a/src/app/agents/edit-agent/edit-agent.component.ts +++ b/src/app/agents/edit-agent/edit-agent.component.ts @@ -137,13 +137,13 @@ export class EditAgentComponent implements OnInit { const self = this; this.dtOptions = { dom: 'Bfrtip', + scrollY: "700px", scrollX: true, - pageLength: 25, lengthMenu: [ - [10, 25, 50, 100, 250, -1], - [10, 25, 50, 100, 250, 'All'] + [10, 25, 50, 100, 250, -1], + [10, 25, 50, 100, 250, 'All'] ], - scrollY: "700px", + pageLength: 25, scrollCollapse: true, paging: false, destroy: true, @@ -192,12 +192,12 @@ export class EditAgentComponent implements OnInit { this.onUpdateAssign(this.updateAssignForm.value); this.gs.update(SERV.AGENTS,this.editedAgentIndex,this.updateForm.value).subscribe(() => { Swal.fire({ - title: "Success", - text: "Agent updated!", - icon: "success", + position: 'top-end', + icon: 'success', + title: "Saved", showConfirmButton: false, timer: 1500 - }); + }) this.updateForm.reset(); // success, we reset form this.router.navigate(['agents/show-agents']); }); diff --git a/src/app/agents/new-agent/new-agent.component.ts b/src/app/agents/new-agent/new-agent.component.ts index 6cd0bf04..5da53ef2 100644 --- a/src/app/agents/new-agent/new-agent.component.ts +++ b/src/app/agents/new-agent/new-agent.component.ts @@ -124,11 +124,11 @@ export class NewAgentComponent implements OnInit, OnDestroy { if (result.isConfirmed) { this.gs.delete(SERV.VOUCHER,id).subscribe(() => { Swal.fire({ - title: "Success", - icon: "success", + position: 'top-end', + icon: 'success', showConfirmButton: false, timer: 1500 - }); + }) this.ngOnInit(); this.rerender(); // rerender datatables }); @@ -150,12 +150,13 @@ export class NewAgentComponent implements OnInit, OnDestroy { this.gs.create(SERV.VOUCHER,this.createForm.value).subscribe(() => { Swal.fire({ - title: "Success", + position: 'top-end', + icon: 'success', + title: "Success!", text: "New Voucher created!", - icon: "success", showConfirmButton: false, timer: 1500 - }); + }) this.ngOnInit(); this.rerender(); // rerender datatables } diff --git a/src/app/agents/show-agents/show-agents.component.ts b/src/app/agents/show-agents/show-agents.component.ts index 2733bb58..7f282579 100644 --- a/src/app/agents/show-agents/show-agents.component.ts +++ b/src/app/agents/show-agents/show-agents.component.ts @@ -229,10 +229,10 @@ export class ShowAgentsComponent implements OnInit, OnDestroy { this.rerender(); // rerender datatables Swal.close(); Swal.fire({ - title: 'Done!', - type: 'success', - timer: 1500, - showConfirmButton: false + position: 'top-end', + icon: 'success', + showConfirmButton: false, + timer: 1500 }) },3000); } @@ -242,6 +242,7 @@ export class ShowAgentsComponent implements OnInit, OnDestroy { const selection = $($(this.dtElement).DataTable.tables()).DataTable().rows({ selected: true } ).data().pluck(0).toArray(); if(selection.length == 0) { Swal.fire({ + position: 'top-end', title: "You haven't selected any Agent", type: 'success', timer: 1500, @@ -300,6 +301,7 @@ export class ShowAgentsComponent implements OnInit, OnDestroy { const selection = $($(this.dtElement).DataTable.tables()).DataTable().rows({ selected: true } ).data().pluck(0).toArray(); if(selection.length == 0) { Swal.fire({ + position: 'top-end', title: "You haven't selected any Agent", type: 'success', timer: 1500, @@ -355,11 +357,11 @@ export class ShowAgentsComponent implements OnInit, OnDestroy { if (result.isConfirmed) { this.gs.delete(SERV.AGENTS,id).subscribe(() => { Swal.fire({ - title: "Success", - icon: "success", + position: 'top-end', + icon: 'success', showConfirmButton: false, timer: 1500 - }); + }) this.ngOnInit(); this.rerender(); // rerender datatables }); diff --git a/src/app/config/engine/agent-binaries/agent-binaries.component.ts b/src/app/config/engine/agent-binaries/agent-binaries.component.ts index 7c618c26..2134ddd8 100644 --- a/src/app/config/engine/agent-binaries/agent-binaries.component.ts +++ b/src/app/config/engine/agent-binaries/agent-binaries.component.ts @@ -107,11 +107,11 @@ export class AgentBinariesComponent implements OnInit { if (result.isConfirmed) { this.gs.delete(SERV.AGENT_BINARY,id).subscribe(() => { Swal.fire({ - title: "Success", - icon: "success", + position: 'top-end', + icon: 'success', showConfirmButton: false, timer: 1500 - }); + }) this.ngOnInit(); this.rerender(); // rerender datatables }); diff --git a/src/app/config/engine/agent-binaries/agent-binary/new-agent-binaries.component.ts b/src/app/config/engine/agent-binaries/agent-binary/new-agent-binaries.component.ts index b26c704e..7a149617 100644 --- a/src/app/config/engine/agent-binaries/agent-binary/new-agent-binaries.component.ts +++ b/src/app/config/engine/agent-binaries/agent-binary/new-agent-binaries.component.ts @@ -85,12 +85,13 @@ export class NewAgentBinariesComponent implements OnInit { .subscribe((prep: any) => { const response = prep; Swal.fire({ - title: "Success", + position: 'top-end', + icon: 'success', + title: "Success!", text: "New Agent Binary created!", - icon: "success", showConfirmButton: false, timer: 1500 - }); + }) this.router.navigate(['config/engine/agent-binaries']); } ); @@ -102,12 +103,12 @@ export class NewAgentBinariesComponent implements OnInit { .subscribe((prep: any) => { const response = prep; Swal.fire({ - title: "Success", - text: "Agent Binary updated!", - icon: "success", + position: 'top-end', + icon: 'success', + title: "Saved", showConfirmButton: false, timer: 1500 - }); + }) this.router.navigate(['config/engine/agent-binaries']); } ); diff --git a/src/app/config/engine/crackers/crackers.component.ts b/src/app/config/engine/crackers/crackers.component.ts index 1b0b8f80..14618595 100644 --- a/src/app/config/engine/crackers/crackers.component.ts +++ b/src/app/config/engine/crackers/crackers.component.ts @@ -142,11 +142,11 @@ export class CrackersComponent implements OnInit, OnDestroy { if (willDelete) { this.gs.delete(SERV.CRACKERS_TYPES,id).subscribe(() => { Swal.fire({ - title: "Success", - icon: "success", + position: 'top-end', + icon: 'success', showConfirmButton: false, timer: 1500 - }); + }) }); } else { Swal.fire("Your Cracker is safe!") diff --git a/src/app/config/engine/crackers/edit-version/edit-crackers.component.ts b/src/app/config/engine/crackers/edit-version/edit-crackers.component.ts index 179da24e..895c4906 100644 --- a/src/app/config/engine/crackers/edit-version/edit-crackers.component.ts +++ b/src/app/config/engine/crackers/edit-version/edit-crackers.component.ts @@ -51,12 +51,12 @@ export class EditCrackersComponent implements OnInit { this.gs.update(SERV.CRACKERS,this.editedCrackervIndex,this.updateForm.value).subscribe(() => { Swal.fire({ - title: "Success", - text: "Agent updated!", - icon: "success", + position: 'top-end', + icon: 'success', + title: "Saved", showConfirmButton: false, timer: 1500 - }); + }) this.updateForm.reset(); // success, we reset form this.router.navigate(['config/engine/crackers']); } @@ -86,11 +86,11 @@ export class EditCrackersComponent implements OnInit { if (result.isConfirmed) { this.gs.delete(SERV.CRACKERS,this.editedCrackervIndex).subscribe(() => { Swal.fire({ - title: "Success", - icon: "success", + position: 'top-end', + icon: 'success', showConfirmButton: false, timer: 1500 - }); + }) this.router.navigate(['config/engine/crackers']); }); } else { diff --git a/src/app/config/engine/crackers/new-cracker/new-cracker.component.ts b/src/app/config/engine/crackers/new-cracker/new-cracker.component.ts index c9b1f372..da99202f 100644 --- a/src/app/config/engine/crackers/new-cracker/new-cracker.component.ts +++ b/src/app/config/engine/crackers/new-cracker/new-cracker.component.ts @@ -35,12 +35,13 @@ export class NewCrackerComponent implements OnInit { this.gs.create(SERV.CRACKERS_TYPES, this.createForm.value).subscribe(() => { Swal.fire({ - title: "Success", + position: 'top-end', + icon: 'success', + title: "Success!", text: "New Cracker created!", - icon: "success", showConfirmButton: false, timer: 1500 - }); + }) this.createForm.reset(); // success, we reset form this.router.navigate(['/config/engine/crackers']); } diff --git a/src/app/config/engine/crackers/new-version/new-crackers.component.ts b/src/app/config/engine/crackers/new-version/new-crackers.component.ts index 67e72885..f686abdf 100644 --- a/src/app/config/engine/crackers/new-version/new-crackers.component.ts +++ b/src/app/config/engine/crackers/new-version/new-crackers.component.ts @@ -52,12 +52,13 @@ export class NewCrackersComponent implements OnInit { this.gs.create(SERV.CRACKERS, this.createForm.value).subscribe(() => { Swal.fire({ - title: "Success", + position: 'top-end', + icon: 'success', + title: "Success!", text: "New Version created!", - icon: "success", showConfirmButton: false, timer: 1500 - }); + }) this.createForm.reset(); // success, we reset form this.router.navigate(['/config/engine/crackers']); } diff --git a/src/app/config/engine/preprocessors/preprocessor/new-preprocessor.component.ts b/src/app/config/engine/preprocessors/preprocessor/new-preprocessor.component.ts index 9b0a14a6..9cd10088 100644 --- a/src/app/config/engine/preprocessors/preprocessor/new-preprocessor.component.ts +++ b/src/app/config/engine/preprocessors/preprocessor/new-preprocessor.component.ts @@ -82,12 +82,13 @@ export class NewPreprocessorComponent implements OnInit { .subscribe((prep: any) => { const response = prep; Swal.fire({ - title: "Success", + position: 'top-end', + icon: 'success', + title: "Success!", text: "New Preprocessor created!", - icon: "success", showConfirmButton: false, timer: 1500 - }); + }) this.router.navigate(['config/engine/preprocessors']); } ); @@ -99,12 +100,12 @@ export class NewPreprocessorComponent implements OnInit { .subscribe((prep: any) => { const response = prep; Swal.fire({ - title: "Success", - text: "New Preprocessor created!", - icon: "success", + position: 'top-end', + icon: 'success', + title: "Saved", showConfirmButton: false, timer: 1500 - }); + }) this.router.navigate(['config/engine/preprocessors']); } ); diff --git a/src/app/config/engine/preprocessors/preprocessors.component.ts b/src/app/config/engine/preprocessors/preprocessors.component.ts index 3508e1c2..d14d669f 100644 --- a/src/app/config/engine/preprocessors/preprocessors.component.ts +++ b/src/app/config/engine/preprocessors/preprocessors.component.ts @@ -48,11 +48,11 @@ export class PreprocessorsComponent implements OnInit { this.dtOptions = { dom: 'Bfrtip', scrollX: true, - pageLength: 25, lengthMenu: [ [10, 25, 50, 100, 250, -1], [10, 25, 50, 100, 250, 'All'] ], + pageLength: 25, stateSave: true, select: true, buttons: { @@ -151,11 +151,11 @@ export class PreprocessorsComponent implements OnInit { if (result.isConfirmed) { this.gs.delete(SERV.PREPROCESSORS,id).subscribe(() => { Swal.fire({ - title: "Success", - icon: "success", + position: 'top-end', + icon: 'success', showConfirmButton: false, timer: 1500 - }); + }) this.ngOnInit(); this.rerender(); // rerender datatables }); diff --git a/src/app/config/hashtypes/hashtype/hashtype.component.ts b/src/app/config/hashtypes/hashtype/hashtype.component.ts index 7eb998b5..50289f2c 100644 --- a/src/app/config/hashtypes/hashtype/hashtype.component.ts +++ b/src/app/config/hashtypes/hashtype/hashtype.component.ts @@ -82,12 +82,13 @@ export class HashtypeComponent implements OnInit { case 'create': this.gs.create(SERV.HASHTYPES,this.Form.value).subscribe(() => { Swal.fire({ + position: 'top-end', + icon: 'success', title: "Success", text: "New Hashtype created!", - icon: "success", showConfirmButton: false, timer: 1500 - }); + }) this.router.navigate(['/config/hashtypes']); } ); @@ -97,11 +98,12 @@ export class HashtypeComponent implements OnInit { const id = +this.route.snapshot.params['id']; this.gs.update(SERV.HASHTYPES,id,this.Form.value).subscribe(() => { Swal.fire({ - title: "Updated!", - icon: "success", + position: 'top-end', + icon: 'success', + title: "Saved", showConfirmButton: false, timer: 1500 - }); + }) this.router.navigate(['/config/hashtypes']); }); break; diff --git a/src/app/config/hashtypes/hashtypes.component.ts b/src/app/config/hashtypes/hashtypes.component.ts index 7365e516..088c5ddb 100644 --- a/src/app/config/hashtypes/hashtypes.component.ts +++ b/src/app/config/hashtypes/hashtypes.component.ts @@ -157,11 +157,11 @@ export class HashtypesComponent implements OnInit { if (result.isConfirmed) { this.gs.delete(SERV.HASHTYPES,id).subscribe(() => { Swal.fire({ - title: "Success", - icon: "success", + position: 'top-end', + icon: 'success', showConfirmButton: false, timer: 1500 - }); + }) this.ngOnInit(); this.rerender(); // rerender datatables }); diff --git a/src/app/config/health-checks/health-checks.component.ts b/src/app/config/health-checks/health-checks.component.ts index 4fa83f04..b5c3c481 100644 --- a/src/app/config/health-checks/health-checks.component.ts +++ b/src/app/config/health-checks/health-checks.component.ts @@ -182,11 +182,11 @@ export class HealthChecksComponent implements OnInit { if (result.isConfirmed) { this.gs.delete(SERV.HEALTH_CHECKS,id).subscribe(() => { Swal.fire({ - title: "Success", - icon: "success", + position: 'top-end', + icon: 'success', showConfirmButton: false, timer: 1500 - }); + }) this.ngOnInit(); this.rerender(); // rerender datatables }); diff --git a/src/app/config/health-checks/new-health-check/new-health-checks.component.ts b/src/app/config/health-checks/new-health-check/new-health-checks.component.ts index 5a089f8c..c54c2147 100644 --- a/src/app/config/health-checks/new-health-check/new-health-checks.component.ts +++ b/src/app/config/health-checks/new-health-check/new-health-checks.component.ts @@ -51,12 +51,13 @@ export class NewHealthChecksComponent implements OnInit { this.gs.create(SERV.HEALTH_CHECKS,this.createForm.value).subscribe(() => { Swal.fire({ - title: "Success", + position: 'top-end', + icon: 'success', + title: "Success!", text: "New Health Check created!", - icon: "success", showConfirmButton: false, timer: 1500 - }); + }) this.router.navigate(['/config/health-checks']); } ); diff --git a/src/app/files/files-edit/files-edit.component.ts b/src/app/files/files-edit/files-edit.component.ts index cee20b25..94fb7ae1 100644 --- a/src/app/files/files-edit/files-edit.component.ts +++ b/src/app/files/files-edit/files-edit.component.ts @@ -89,12 +89,12 @@ export class FilesEditComponent implements OnInit { onSubmit(): void{ this.gs.update(SERV.FILES,this.editedFileIndex,this.updateForm.value['updateData']).subscribe(() => { Swal.fire({ - title: "Great!", - text: "File updated!", - icon: "success", + position: 'top-end', + icon: 'success', + title: "Saved", showConfirmButton: false, timer: 1500 - }); + }) this.route.data.subscribe(data => { switch (data['kind']) { @@ -117,11 +117,13 @@ export class FilesEditComponent implements OnInit { errorMessage => { // check error status code is 500, if so, do some action Swal.fire({ + position: 'top-end', + icon: "warning", title: "Oppss! Error", text: "File was not updated, please try again!", - icon: "warning", - showConfirmButton: true - }); + showConfirmButton: false, + timer: 1500 + }) this.ngOnInit(); } ); diff --git a/src/app/files/files.component.ts b/src/app/files/files.component.ts index b1f3a626..7efd214d 100644 --- a/src/app/files/files.component.ts +++ b/src/app/files/files.component.ts @@ -249,11 +249,11 @@ export class FilesComponent implements OnInit { if (result.isConfirmed) { this.gs.delete(SERV.FILES,id).subscribe(() => { Swal.fire({ - title: "Success", - icon: "success", + position: 'top-end', + icon: 'success', showConfirmButton: false, timer: 1500 - }); + }) this.ngOnInit(); this.rerender(); // rerender datatables }); @@ -328,10 +328,11 @@ export class FilesComponent implements OnInit { this.rerender(); // rerender datatables Swal.close(); Swal.fire({ - title: 'Done!', - type: 'success', - timer: 1500, - showConfirmButton: false + position: 'top-end', + icon: 'success', + title: "Success", + showConfirmButton: false, + timer: 1500 }) },3000); } diff --git a/src/app/files/new-files/new-files.component.ts b/src/app/files/new-files/new-files.component.ts index 2e48b41d..fc80d68f 100644 --- a/src/app/files/new-files/new-files.component.ts +++ b/src/app/files/new-files/new-files.component.ts @@ -91,12 +91,13 @@ export class NewFilesComponent implements OnInit { this.gs.create(SERV.FILES,form).subscribe(() => { Swal.fire({ - title: "Success", + position: 'top-end', + icon: 'success', + title: "Success!", text: "New File created!", - icon: "success", showConfirmButton: false, timer: 1500 - }); + }) this.router.navigate(['/files',this.redirect]); } ); diff --git a/src/app/hashlists/edit-hashlist/edit-hashlist.component.ts b/src/app/hashlists/edit-hashlist/edit-hashlist.component.ts index 9d66a5ea..af82f7d0 100644 --- a/src/app/hashlists/edit-hashlist/edit-hashlist.component.ts +++ b/src/app/hashlists/edit-hashlist/edit-hashlist.component.ts @@ -95,12 +95,12 @@ export class EditHashlistComponent implements OnInit { this.gs.update(SERV.HASHLISTS,this.editedHashlistIndex,this.updateForm.value['updateData']).subscribe(() => { Swal.fire({ - title: "Success", - text: "HashList updated!", - icon: "success", + position: 'top-end', + icon: 'success', + title: "Saved", showConfirmButton: false, timer: 1500 - }); + }) this.updateForm.reset(); // success, we reset form const path = this.type === 3 ? '/hashlists/superhashlist':'/hashlists/hashlist'; this.router.navigate([path]); diff --git a/src/app/hashlists/hashlist/hashlist.component.ts b/src/app/hashlists/hashlist/hashlist.component.ts index 55eb8f17..ba2e2f0b 100644 --- a/src/app/hashlists/hashlist/hashlist.component.ts +++ b/src/app/hashlists/hashlist/hashlist.component.ts @@ -228,12 +228,12 @@ rerender(): void { onArchive(id: number){ this.gs.archive(SERV.HASHLISTS,id).subscribe((list: any) => { Swal.fire({ - title: "Success", - text: "Archived!", - icon: "success", + position: 'top-end', + icon: 'success', + title: "Archived!", showConfirmButton: false, timer: 1500 - }); + }) this.ngOnInit(); this.rerender(); // rerender datatables }); @@ -261,11 +261,11 @@ onDelete(id: number){ if (result.isConfirmed) { this.gs.delete(SERV.HASHLISTS,id).subscribe(() => { Swal.fire({ - title: "Success", - icon: "success", + position: 'top-end', + icon: 'success', showConfirmButton: false, timer: 1500 - }); + }) this.ngOnInit(); this.rerender(); // rerender datatables }); @@ -288,10 +288,11 @@ onSelectedHashlists(){ const selection = $($(this.dtElement).DataTable.tables()).DataTable().rows({ selected: true } ).data().pluck(0).toArray(); if(selection.length == 0) { Swal.fire({ + position: 'top-end', + icon: 'success', title: "You haven't selected any Hashlist", - type: 'success', - timer: 1500, - showConfirmButton: false + showConfirmButton: false, + timer: 1500 }) return; } @@ -339,10 +340,10 @@ onDone(value?: any){ this.rerender(); // rerender datatables Swal.close(); Swal.fire({ - title: 'Done!', - type: 'success', - timer: 1500, - showConfirmButton: false + position: 'top-end', + icon: 'success', + showConfirmButton: false, + timer: 1500 }) },3000); } diff --git a/src/app/hashlists/new-hashlist/new-hashlist.component.ts b/src/app/hashlists/new-hashlist/new-hashlist.component.ts index 227cf5c9..24de5960 100644 --- a/src/app/hashlists/new-hashlist/new-hashlist.component.ts +++ b/src/app/hashlists/new-hashlist/new-hashlist.component.ts @@ -213,12 +213,13 @@ export class NewHashlistComponent implements OnInit { this.gs.create(SERV.HASHLISTS,res).subscribe(() => { Swal.fire({ - title: "Success", + position: 'top-end', + icon: 'success', + title: "Success!", text: "New HashList created!", - icon: "success", showConfirmButton: false, timer: 1500 - }); + }) this.router.navigate(['/hashlists/hashlist']); } ); diff --git a/src/app/hashlists/new-superhashlist/new-superhashlist.component.ts b/src/app/hashlists/new-superhashlist/new-superhashlist.component.ts index 975d5f7e..7ba0a75a 100644 --- a/src/app/hashlists/new-superhashlist/new-superhashlist.component.ts +++ b/src/app/hashlists/new-superhashlist/new-superhashlist.component.ts @@ -90,12 +90,13 @@ export class NewSuperhashlistComponent implements OnInit { console.log(this.createForm.value); this.gs.chelper(SERV.HELPER,'createSuperHashlist',this.createForm.value).subscribe(() => { Swal.fire({ - title: "Success", + position: 'top-end', + icon: 'success', + title: "Success!", text: "New SuperHashList created!", - icon: "success", showConfirmButton: false, timer: 1500 - }); + }) this.createForm.reset(); // success, we reset form this.router.navigate(['hashlists/superhashlist']); } diff --git a/src/app/hashlists/superhashlist/superhashlist.component.ts b/src/app/hashlists/superhashlist/superhashlist.component.ts index 35f251b9..c534ad5a 100644 --- a/src/app/hashlists/superhashlist/superhashlist.component.ts +++ b/src/app/hashlists/superhashlist/superhashlist.component.ts @@ -151,11 +151,11 @@ export class SuperhashlistComponent implements OnInit { if (result.isConfirmed) { this.gs.delete(SERV.HASHLISTS,id).subscribe(() => { Swal.fire({ - title: "Success", - icon: "success", + position: 'top-end', + icon: 'success', showConfirmButton: false, timer: 1500 - }); + }) this.ngOnInit(); this.rerender(); // rerender datatables }); diff --git a/src/app/tasks/edit-preconfigured-tasks/edit-preconfigured-tasks.component.ts b/src/app/tasks/edit-preconfigured-tasks/edit-preconfigured-tasks.component.ts index 3e0e8fde..6f45c5e9 100644 --- a/src/app/tasks/edit-preconfigured-tasks/edit-preconfigured-tasks.component.ts +++ b/src/app/tasks/edit-preconfigured-tasks/edit-preconfigured-tasks.component.ts @@ -117,12 +117,12 @@ export class EditPreconfiguredTasksComponent implements OnInit{ this.gs.update(SERV.PRETASKS,this.editedPretaskIndex,this.updateForm.value['updateData']).subscribe(() => { Swal.fire({ - title: "Success", - text: "Pretask updated!", - icon: "success", + position: 'top-end', + icon: 'success', + title: "Saved", showConfirmButton: false, timer: 1500 - }); + }) this.updateForm.reset(); // success, we reset form this.router.navigate(['tasks/preconfigured-tasks']); } diff --git a/src/app/tasks/edit-supertasks/edit-supertasks.component.ts b/src/app/tasks/edit-supertasks/edit-supertasks.component.ts index 90c995d4..daa10a1b 100644 --- a/src/app/tasks/edit-supertasks/edit-supertasks.component.ts +++ b/src/app/tasks/edit-supertasks/edit-supertasks.component.ts @@ -94,12 +94,12 @@ export class EditSupertasksComponent implements OnInit { this.gs.update(SERV.SUPER_TASKS,this.editedSTIndex,{'pretasks': payload}).subscribe(() => { Swal.fire({ - title: "Success", - text: "Pretask updated!", - icon: "success", + position: 'top-end', + icon: 'success', + title: "Saved", showConfirmButton: false, timer: 1500 - }); + }) this.updateForm.reset(); // success, we reset form this.onRefresh(); // this.router.navigate(['/tasks/supertasks']); @@ -191,11 +191,12 @@ export class EditSupertasksComponent implements OnInit { if (result.isConfirmed) { this.gs.delete(SERV.SUPER_TASKS,id).subscribe(() => { Swal.fire({ + position: 'top-end', + icon: 'success', title: "Success", - icon: "success", showConfirmButton: false, timer: 1500 - }); + }) this.ngOnInit(); }); } else { @@ -218,12 +219,12 @@ export class EditSupertasksComponent implements OnInit { } this.gs.update(SERV.SUPER_TASKS,this.editedSTIndex,{'pretasks': payload}).subscribe((result)=>{ Swal.fire({ + position: 'top-end', + icon: 'success', title: "Success", - text: "Pretask deleted!", - icon: "success", showConfirmButton: false, timer: 1500 - }); + }) this.updateForm.reset(); // success, we reset form this.onRefresh(); }) diff --git a/src/app/tasks/edit-tasks/edit-tasks.component.ts b/src/app/tasks/edit-tasks/edit-tasks.component.ts index 181c6bc8..6a0d68a4 100644 --- a/src/app/tasks/edit-tasks/edit-tasks.component.ts +++ b/src/app/tasks/edit-tasks/edit-tasks.component.ts @@ -122,12 +122,12 @@ export class EditTasksComponent implements OnInit,PendingChangesGuard { if (this.updateForm.valid) { this.gs.update(SERV.TASKS,this.editedTaskIndex,this.updateForm.value['updateData']).subscribe(() => { Swal.fire({ - title: "Success", - text: "Task updated!", - icon: "success", + position: 'top-end', + icon: 'success', + title: "Saved", showConfirmButton: false, timer: 1500 - }); + }) this.updateForm.reset(); // success, we reset form this.router.navigate(['tasks/show-tasks']); } @@ -217,12 +217,13 @@ export class EditTasksComponent implements OnInit,PendingChangesGuard { const payload = {"taskId": this.editedTaskIndex, "agentId":this.createForm.value['agentId']}; this.gs.create(SERV.AGENT_ASSIGN,payload).subscribe(() => { Swal.fire({ + position: 'top-end', + icon: 'success', title: "Success", text: "Agent Assigned!", - icon: "success", showConfirmButton: false, timer: 1500 - }); + }) this.rerender(); // rerender datatables this.ngOnInit(); } @@ -233,11 +234,12 @@ export class EditTasksComponent implements OnInit,PendingChangesGuard { onDelete(id: number){ this.gs.delete(SERV.AGENT_ASSIGN,id).subscribe(() => { Swal.fire({ + position: 'top-end', + icon: 'success', title: "Success", - icon: "success", showConfirmButton: false, timer: 1500 - }); + }) this.rerender(); // rerender datatables this.ngOnInit(); }); @@ -266,11 +268,12 @@ export class EditTasksComponent implements OnInit,PendingChangesGuard { if(cvalue !== Number(formValues[0])){ this.gs.update(SERV.AGENT_ASSIGN,id, {benchmark: +formValues}).subscribe(() => { Swal.fire({ + position: 'top-end', + icon: 'success', title: "Success", - icon: "success", showConfirmButton: false, timer: 1500 - }); + }) this.ngOnInit(); this.rerender(); // rerender datatables }); @@ -477,11 +480,12 @@ export class EditTasksComponent implements OnInit,PendingChangesGuard { let payload = {"taskId":this.editedTaskIndex}; this.gs.chelper(SERV.HELPER,'purgeTask',payload).subscribe(() => { Swal.fire({ + position: 'top-end', + icon: 'success', title: "Success", - icon: "success", showConfirmButton: false, timer: 1500 - }); + }) this.ngOnInit(); this.rerender(); // rerender datatables }); @@ -504,11 +508,12 @@ export class EditTasksComponent implements OnInit,PendingChangesGuard { let payload = {'chunkId': id}; this.gs.chelper(SERV.HELPER,path,payload).subscribe(() => { Swal.fire({ + position: 'top-end', + icon: 'success', title: title, - icon: "success", showConfirmButton: false, timer: 1500 - }); + }) this.ngOnInit(); this.rerender(); }); diff --git a/src/app/tasks/import-supertasks/masks/masks.component.ts b/src/app/tasks/import-supertasks/masks/masks.component.ts index 246b949f..6de418c1 100644 --- a/src/app/tasks/import-supertasks/masks/masks.component.ts +++ b/src/app/tasks/import-supertasks/masks/masks.component.ts @@ -50,14 +50,14 @@ export class MasksComponent implements OnInit { if (this.createForm.valid) { this.gs.create(SERV.TASKS,this.createForm.value).subscribe(() => { - Swal.fire({ - title: "Success", + position: 'top-end', + icon: 'success', + title: "Success!", text: "New Supertask created!", - icon: "success", showConfirmButton: false, timer: 1500 - }); + }) this.createForm.reset(); // success, we reset form this.router.navigate(['tasks/show-tasks']); } diff --git a/src/app/tasks/import-supertasks/wrbulk/wrbulk.component.ts b/src/app/tasks/import-supertasks/wrbulk/wrbulk.component.ts index 8a359152..98ce1a57 100644 --- a/src/app/tasks/import-supertasks/wrbulk/wrbulk.component.ts +++ b/src/app/tasks/import-supertasks/wrbulk/wrbulk.component.ts @@ -179,6 +179,7 @@ export class WrbulkComponent implements OnInit { validateFile(value){ if(value.split('.').pop() == '7zip'){ Swal.fire({ + position: 'top-end', title: "Heads Up!", text: "Hashcat has some issues loading 7z files. Better convert it to a hash file ;)", icon: "warning", diff --git a/src/app/tasks/new-preconfigured-tasks/new-preconfigured-tasks.component.ts b/src/app/tasks/new-preconfigured-tasks/new-preconfigured-tasks.component.ts index 265f23e4..05a92d67 100644 --- a/src/app/tasks/new-preconfigured-tasks/new-preconfigured-tasks.component.ts +++ b/src/app/tasks/new-preconfigured-tasks/new-preconfigured-tasks.component.ts @@ -260,12 +260,13 @@ export class NewPreconfiguredTasksComponent implements OnInit,AfterViewInit { this.gs.create(SERV.PRETASKS,this.createForm.value).subscribe(() => { Swal.fire({ - title: "Success", + position: 'top-end', + icon: 'success', + title: "Success!", text: "New PreTask created!", - icon: "success", showConfirmButton: false, timer: 1500 - }); + }) this.createForm.reset(); // success, we reset form this.router.navigate(['tasks/preconfigured-tasks']); } diff --git a/src/app/tasks/new-supertasks/new-supertasks.component.ts b/src/app/tasks/new-supertasks/new-supertasks.component.ts index 63c08d61..1fd0b5c2 100644 --- a/src/app/tasks/new-supertasks/new-supertasks.component.ts +++ b/src/app/tasks/new-supertasks/new-supertasks.component.ts @@ -92,12 +92,13 @@ export class NewSupertasksComponent implements OnInit { this.gs.create(SERV.SUPER_TASKS,this.createForm.value).subscribe(() => { Swal.fire({ - title: "Success", + position: 'top-end', + icon: 'success', + title: "Success!", text: "New Supertask created!", - icon: "success", showConfirmButton: false, timer: 1500 - }); + }) this.createForm.reset(); // success, we reset form this.router.navigate(['tasks/supertasks']); } diff --git a/src/app/tasks/new-tasks/new-tasks.component.ts b/src/app/tasks/new-tasks/new-tasks.component.ts index d654e22c..eb71bdd9 100644 --- a/src/app/tasks/new-tasks/new-tasks.component.ts +++ b/src/app/tasks/new-tasks/new-tasks.component.ts @@ -410,12 +410,13 @@ export class NewTasksComponent implements OnInit { if (this.createForm.valid) { this.gs.create(SERV.TASKS,this.createForm.value).subscribe(() => { Swal.fire({ - title: "Success", + position: 'top-end', + icon: 'success', + title: "Success!", text: "New Task created!", - icon: "success", showConfirmButton: false, timer: 1500 - }); + }) this.createForm.reset(); this.router.navigate(['tasks/show-tasks']); } diff --git a/src/app/tasks/preconfigured-tasks/preconfigured-tasks.component.ts b/src/app/tasks/preconfigured-tasks/preconfigured-tasks.component.ts index 4a8a428b..69592422 100644 --- a/src/app/tasks/preconfigured-tasks/preconfigured-tasks.component.ts +++ b/src/app/tasks/preconfigured-tasks/preconfigured-tasks.component.ts @@ -172,11 +172,12 @@ export class PreconfiguredTasksComponent implements OnInit { if (result.isConfirmed) { this.gs.delete(SERV.PRETASKS,id).subscribe(() => { Swal.fire({ + position: 'top-end', + icon: 'success', title: "Success", - icon: "success", showConfirmButton: false, timer: 1500 - }); + }) this.ngOnInit(); this.rerender(); // rerender datatables }); 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 58a059b3..94c6a826 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 @@ -96,12 +96,13 @@ export class ModalSubtasksComponent { onArchive(id: number){ this.gs.archive(SERV.TASKS,id).subscribe(() => { Swal.fire({ + position: 'top-end', + icon: 'success', title: "Success", text: "Archived!", - icon: "success", showConfirmButton: false, timer: 1500 - }); + }) this.rerender(); // rerender datatables }); } @@ -109,11 +110,12 @@ export class ModalSubtasksComponent { onDelete(id: number){ this.gs.delete(SERV.TASKS,id).subscribe(() => { Swal.fire({ + position: 'top-end', + icon: 'success', title: "Success", - icon: "success", showConfirmButton: false, timer: 1500 - }); + }) this.rerender(); // rerender datatables }); } diff --git a/src/app/tasks/show-tasks/show-tasks.component.ts b/src/app/tasks/show-tasks/show-tasks.component.ts index 8866a378..fc0c3f9a 100644 --- a/src/app/tasks/show-tasks/show-tasks.component.ts +++ b/src/app/tasks/show-tasks/show-tasks.component.ts @@ -267,12 +267,13 @@ onArchive(id: number, type: number){ const path = type === 0 ? SERV.TASKS : SERV.TASKS_WRAPPER; this.gs.archive(path,id).subscribe(() => { Swal.fire({ + position: 'top-end', + icon: 'success', title: "Success", text: "Archived!", - icon: "success", showConfirmButton: false, timer: 1500 - }); + }) this.ngOnInit(); this.rerender(); // rerender datatables }); @@ -310,11 +311,12 @@ onDelete(id: number, type: number){ const path = type === 0 ? SERV.TASKS : SERV.TASKS_WRAPPER; this.gs.delete(path,id).subscribe(() => { Swal.fire({ + position: 'top-end', + icon: 'success', title: "Success", - icon: "success", showConfirmButton: false, timer: 1500 - }); + }) this.ngOnInit(); this.rerender(); // rerender datatables }); @@ -337,6 +339,7 @@ onSelectedTasks(){ const selection = $($(this.dtElement).DataTable.tables()).DataTable().rows({ selected: true } ).data().pluck(0).toArray(); if(selection.length == 0) { Swal.fire({ + position: 'top-end', title: "You haven't selected any Task", type: 'success', timer: 1500, @@ -394,10 +397,11 @@ onDone(value?: any){ this.rerender(); // rerender datatables Swal.close(); Swal.fire({ - title: 'Done!', - type: 'success', - timer: 1500, - showConfirmButton: false + position: 'top-end', + icon: 'success', + title: "Success", + showConfirmButton: false, + timer: 1500 }) },3000); } @@ -409,6 +413,7 @@ onModalProject(title: string){ const selection = $($(this.dtElement).DataTable.tables()).DataTable().rows({ selected: true } ).data().pluck(0).toArray(); if(selection.length == 0) { Swal.fire({ + position: 'top-end', title: "You haven't selected any Task", type: 'success', timer: 1500, @@ -468,11 +473,12 @@ onModalUpdate(title: string, id: number, cvalue: any, formlabel: boolean, namere const path = type === 0 ? SERV.TASKS : SERV.TASKS_WRAPPER; this.gs.update(path,id, update).subscribe(() => { Swal.fire({ + position: 'top-end', + icon: 'success', title: "Success", - icon: "success", showConfirmButton: false, timer: 1500 - }); + }) this.ngOnInit(); this.rerender(); // rerender datatables }); diff --git a/src/app/tasks/supertasks/applyhashlist.component.ts b/src/app/tasks/supertasks/applyhashlist.component.ts index 694e32dd..588acad8 100644 --- a/src/app/tasks/supertasks/applyhashlist.component.ts +++ b/src/app/tasks/supertasks/applyhashlist.component.ts @@ -133,12 +133,12 @@ export class ApplyHashlistComponent { onSubmit(){ this.gs.chelper(SERV.HELPER,'createSupertask', this.createForm.value).subscribe(() => { Swal.fire({ + position: 'top-end', + icon: 'success', title: "Success", - text: "New Task created!", - icon: "success", showConfirmButton: false, timer: 1500 - }); + }) this.createForm.reset(); this.router.navigate(['tasks/show-tasks']); } diff --git a/src/app/tasks/supertasks/supertasks.component.ts b/src/app/tasks/supertasks/supertasks.component.ts index db627349..3061a471 100644 --- a/src/app/tasks/supertasks/supertasks.component.ts +++ b/src/app/tasks/supertasks/supertasks.component.ts @@ -171,11 +171,12 @@ export class SupertasksComponent implements OnInit { if (result.isConfirmed) { this.gs.delete(SERV.SUPER_TASKS,id).subscribe(() => { Swal.fire({ + position: 'top-end', + icon: 'success', title: "Success", - icon: "success", showConfirmButton: false, timer: 1500 - }); + }) this.ngOnInit(); this.rerender(); // rerender datatables }); diff --git a/src/app/users/all-users/all-users.component.ts b/src/app/users/all-users/all-users.component.ts index 1549b5c5..f4f4ac0e 100644 --- a/src/app/users/all-users/all-users.component.ts +++ b/src/app/users/all-users/all-users.component.ts @@ -169,11 +169,12 @@ export class AllUsersComponent implements OnInit, OnDestroy { if (result.isConfirmed) { this.gs.delete(SERV.USERS,id).subscribe(() => { Swal.fire({ + position: 'top-end', + icon: 'success', title: "Success", - icon: "success", showConfirmButton: false, timer: 1500 - }); + }) this.ngOnInit(); this.rerender(); // rerender datatables }); diff --git a/src/app/users/edit-users/edit-users.component.ts b/src/app/users/edit-users/edit-users.component.ts index 0c088e3a..c8b38ab5 100644 --- a/src/app/users/edit-users/edit-users.component.ts +++ b/src/app/users/edit-users/edit-users.component.ts @@ -112,11 +112,12 @@ export class EditUsersComponent implements OnInit { if (result.isConfirmed) { this.gs.delete(SERV.USERS,this.editedUserIndex).subscribe(() => { Swal.fire({ + position: 'top-end', + icon: 'success', title: "Success", - icon: "success", showConfirmButton: false, timer: 1500 - }); + }) this.router.navigate(['/users/all-users']); }); } else { @@ -138,12 +139,12 @@ export class EditUsersComponent implements OnInit { this.gs.update(SERV.USERS,this.editedUserIndex, this.updateForm.value.updateData).subscribe(() => { Swal.fire({ - title: "Success", - text: "User updated!", - icon: "success", + position: 'top-end', + icon: 'success', + title: "Saved", showConfirmButton: false, timer: 1500 - }); + }) this.updateForm.reset(); // success, we reset form this.router.navigate(['users/all-users']); } diff --git a/src/app/users/globalpermissionsgroups/edit-globalpermissionsgroups/edit-globalpermissionsgroups.component.ts b/src/app/users/globalpermissionsgroups/edit-globalpermissionsgroups/edit-globalpermissionsgroups.component.ts index f46318fd..8f33af8b 100644 --- a/src/app/users/globalpermissionsgroups/edit-globalpermissionsgroups/edit-globalpermissionsgroups.component.ts +++ b/src/app/users/globalpermissionsgroups/edit-globalpermissionsgroups/edit-globalpermissionsgroups.component.ts @@ -139,12 +139,13 @@ export class EditGlobalpermissionsgroupsComponent implements OnInit { if (this.updateForm.valid) { this.gs.update(SERV.ACCESS_PERMISSIONS_GROUPS,this.editedGPGIndex, this.updateForm.value).subscribe(() => { Swal.fire({ + position: 'top-end', + icon: 'success', title: "Success", text: "Permission Updated!", - icon: "success", showConfirmButton: false, timer: 1500 - }); + }) this.updateForm.reset(); this.router.navigate(['/users/global-permissions-groups']); } diff --git a/src/app/users/globalpermissionsgroups/globalpermissionsgroups.component.ts b/src/app/users/globalpermissionsgroups/globalpermissionsgroups.component.ts index fd68e5d2..b5a1fcc2 100644 --- a/src/app/users/globalpermissionsgroups/globalpermissionsgroups.component.ts +++ b/src/app/users/globalpermissionsgroups/globalpermissionsgroups.component.ts @@ -156,11 +156,13 @@ export class GlobalpermissionsgroupsComponent implements OnInit { if (result.isConfirmed) { this.gs.delete(SERV.ACCESS_PERMISSIONS_GROUPS,id).subscribe(() => { Swal.fire({ + position: 'top-end', + icon: 'success', title: "Success", - icon: "success", + text: "Archived!", showConfirmButton: false, timer: 1500 - }); + }) this.ngOnInit(); this.rerender(); // rerender datatables }); diff --git a/src/app/users/globalpermissionsgroups/new-globalpermissionsgroups/new-globalpermissionsgroups.component.ts b/src/app/users/globalpermissionsgroups/new-globalpermissionsgroups/new-globalpermissionsgroups.component.ts index 7116224a..c4a76399 100644 --- a/src/app/users/globalpermissionsgroups/new-globalpermissionsgroups/new-globalpermissionsgroups.component.ts +++ b/src/app/users/globalpermissionsgroups/new-globalpermissionsgroups/new-globalpermissionsgroups.component.ts @@ -36,12 +36,13 @@ export class NewGlobalpermissionsgroupsComponent implements OnInit { this.gs.create(SERV.ACCESS_PERMISSIONS_GROUPS,this.createForm.value).subscribe(() => { Swal.fire({ + position: 'top-end', + icon: 'success', title: "Success", text: "Global Permission Group created!", - icon: "success", showConfirmButton: false, timer: 1500 - }); + }) this.router.navigate(['/users/global-permissions-groups']); } ); diff --git a/src/app/users/groups/cu-group/cu-group.component.ts b/src/app/users/groups/cu-group/cu-group.component.ts index 0219f84e..b903aecb 100644 --- a/src/app/users/groups/cu-group/cu-group.component.ts +++ b/src/app/users/groups/cu-group/cu-group.component.ts @@ -74,12 +74,12 @@ export class CUGroupComponent implements OnInit { case 'create': this.gs.create(SERV.ACCESS_GROUPS,this.Form.value).subscribe(() => { Swal.fire({ + position: 'top-end', + icon: 'success', title: "Success", - text: "New Group created!", - icon: "success", showConfirmButton: false, timer: 1500 - }); + }) this.router.navigate(['/users/access-groups']); } ); @@ -88,11 +88,12 @@ export class CUGroupComponent implements OnInit { case 'edit': this.gs.update(SERV.ACCESS_GROUPS,this.editedIndex,this.Form.value).subscribe(() => { Swal.fire({ - title: "Updated!", - icon: "success", + position: 'top-end', + icon: 'success', + title: "Saved", showConfirmButton: false, timer: 1500 - }); + }) this.router.navigate(['/users/access-groups']); }); break; diff --git a/src/app/users/groups/groups.component.ts b/src/app/users/groups/groups.component.ts index b3857a5b..321481ce 100644 --- a/src/app/users/groups/groups.component.ts +++ b/src/app/users/groups/groups.component.ts @@ -161,11 +161,12 @@ export class GroupsComponent implements OnInit { if (result.isConfirmed) { this.gs.delete(SERV.ACCESS_GROUPS,id).subscribe(() => { Swal.fire({ + position: 'top-end', + icon: 'success', title: "Success", - icon: "success", showConfirmButton: false, timer: 1500 - }); + }) this.ngOnInit(); this.rerender(); // rerender datatables }); diff --git a/src/app/users/users.component.ts b/src/app/users/users.component.ts index cee147cc..d641b0ac 100644 --- a/src/app/users/users.component.ts +++ b/src/app/users/users.component.ts @@ -56,12 +56,12 @@ export class UsersComponent implements OnInit { this.gs.create(SERV.USERS,this.createForm.value).subscribe(() => { Swal.fire({ + position: 'top-end', + icon: 'success', title: "Success", - text: "New User created!", - icon: "success", showConfirmButton: false, timer: 1500 - }); + }) this.router.navigate(['users/all-users']); } ); From ede0cfed2b0fe511c2a8da0faeeca79919f7135d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Tue, 26 Sep 2023 10:20:16 +0100 Subject: [PATCH 040/419] All pages deleting - display what will be deleted --- src/app/account/notifications/notifications.component.html | 2 +- src/app/account/notifications/notifications.component.ts | 5 ++--- src/app/agents/new-agent/new-agent.component.html | 2 +- src/app/agents/new-agent/new-agent.component.ts | 4 ++-- src/app/agents/show-agents/show-agents.component.html | 2 +- src/app/agents/show-agents/show-agents.component.ts | 5 ++--- .../engine/agent-binaries/agent-binaries.component.html | 2 +- .../engine/agent-binaries/agent-binaries.component.ts | 5 ++--- src/app/config/engine/crackers/crackers.component.html | 2 +- src/app/config/engine/crackers/crackers.component.ts | 5 ++--- .../engine/crackers/edit-version/edit-crackers.component.ts | 3 +-- .../engine/preprocessors/preprocessors.component.html | 2 +- .../config/engine/preprocessors/preprocessors.component.ts | 5 ++--- src/app/config/hashtypes/hashtypes.component.html | 6 +++--- src/app/config/hashtypes/hashtypes.component.ts | 5 ++--- src/app/config/health-checks/health-checks.component.html | 2 +- src/app/config/health-checks/health-checks.component.ts | 5 ++--- src/app/files/files.component.html | 2 +- src/app/files/files.component.ts | 5 ++--- src/app/hashlists/hashlist/hashlist.component.html | 2 +- src/app/hashlists/hashlist/hashlist.component.ts | 5 ++--- .../hashlists/superhashlist/superhashlist.component.html | 2 +- src/app/hashlists/superhashlist/superhashlist.component.ts | 5 ++--- src/app/tasks/edit-supertasks/edit-supertasks.component.ts | 3 +-- .../preconfigured-tasks/preconfigured-tasks.component.html | 2 +- .../preconfigured-tasks/preconfigured-tasks.component.ts | 5 ++--- src/app/tasks/show-tasks/show-tasks.component.html | 4 ++-- src/app/tasks/show-tasks/show-tasks.component.ts | 5 ++--- src/app/tasks/supertasks/supertasks.component.html | 2 +- src/app/tasks/supertasks/supertasks.component.ts | 5 ++--- src/app/users/all-users/all-users.component.html | 4 ++-- src/app/users/all-users/all-users.component.ts | 5 ++--- src/app/users/edit-users/edit-users.component.ts | 3 +-- .../globalpermissionsgroups.component.html | 2 +- .../globalpermissionsgroups.component.ts | 5 ++--- src/app/users/groups/groups.component.html | 2 +- src/app/users/groups/groups.component.ts | 5 ++--- 37 files changed, 58 insertions(+), 77 deletions(-) diff --git a/src/app/account/notifications/notifications.component.html b/src/app/account/notifications/notifications.component.html index 0d29bdc0..7bed6eaa 100644 --- a/src/app/account/notifications/notifications.component.html +++ b/src/app/account/notifications/notifications.component.html @@ -34,7 +34,7 @@ Edit - diff --git a/src/app/account/notifications/notifications.component.ts b/src/app/account/notifications/notifications.component.ts index 73400b40..3eaccba4 100644 --- a/src/app/account/notifications/notifications.component.ts +++ b/src/app/account/notifications/notifications.component.ts @@ -139,7 +139,7 @@ export class NotificationsComponent implements OnInit { this.dtTrigger.unsubscribe(); } - onDelete(id: number){ + onDelete(id: number, name: string){ const swalWithBootstrapButtons = Swal.mixin({ customClass: { confirmButton: 'btn', @@ -148,8 +148,7 @@ export class NotificationsComponent implements OnInit { buttonsStyling: false }) Swal.fire({ - title: "Are you sure?", - text: "Once deleted, it can not be recovered!", + title: 'Remove '+ name +' from your notifications?', icon: "warning", reverseButtons: true, showCancelButton: true, diff --git a/src/app/agents/new-agent/new-agent.component.html b/src/app/agents/new-agent/new-agent.component.html index f2f20cf8..3a18545a 100644 --- a/src/app/agents/new-agent/new-agent.component.html +++ b/src/app/agents/new-agent/new-agent.component.html @@ -126,7 +126,7 @@
Instructions
{{ v.time | uiDate }} - +
{{ h.hashTypeId }} {{ h.description }}{{ h.isSalted == 0 ? "Yes" : "No" }}{{ h.isSlowHash == 0 ? "Yes" : "No" }}{{ h.isSalted === 0 ? "Yes" : "No" }}{{ h.isSlowHash === 0 ? "Yes" : "No" }}
@@ -24,7 +24,7 @@ Edit - diff --git a/src/app/config/hashtypes/hashtypes.component.ts b/src/app/config/hashtypes/hashtypes.component.ts index 088c5ddb..55b1c64e 100644 --- a/src/app/config/hashtypes/hashtypes.component.ts +++ b/src/app/config/hashtypes/hashtypes.component.ts @@ -135,7 +135,7 @@ export class HashtypesComponent implements OnInit { }); } - onDelete(id: number){ + onDelete(id: number, name: string){ const swalWithBootstrapButtons = Swal.mixin({ customClass: { confirmButton: 'btn', @@ -144,8 +144,7 @@ export class HashtypesComponent implements OnInit { buttonsStyling: false }) Swal.fire({ - title: "Are you sure?", - text: "Once deleted, it can not be recovered!", + title: 'Remove '+ name +' from your hashtypes?', icon: "warning", reverseButtons: true, showCancelButton: true, diff --git a/src/app/config/health-checks/health-checks.component.html b/src/app/config/health-checks/health-checks.component.html index fe1cb215..8a21010e 100644 --- a/src/app/config/health-checks/health-checks.component.html +++ b/src/app/config/health-checks/health-checks.component.html @@ -23,7 +23,7 @@ Edit - diff --git a/src/app/config/health-checks/health-checks.component.ts b/src/app/config/health-checks/health-checks.component.ts index b5c3c481..2cd0c9e5 100644 --- a/src/app/config/health-checks/health-checks.component.ts +++ b/src/app/config/health-checks/health-checks.component.ts @@ -160,7 +160,7 @@ export class HealthChecksComponent implements OnInit { }); } - onDelete(id: number){ + onDelete(id: number, name: string){ const swalWithBootstrapButtons = Swal.mixin({ customClass: { confirmButton: 'btn', @@ -169,8 +169,7 @@ export class HealthChecksComponent implements OnInit { buttonsStyling: false }) Swal.fire({ - title: "Are you sure?", - text: "Once deleted, it can not be recovered!", + title: 'Remove '+ name +' from your health checks?', icon: "warning", reverseButtons: true, showCancelButton: true, diff --git a/src/app/files/files.component.html b/src/app/files/files.component.html index 2f24da17..c27f1ddb 100644 --- a/src/app/files/files.component.html +++ b/src/app/files/files.component.html @@ -37,7 +37,7 @@ Edit - diff --git a/src/app/files/files.component.ts b/src/app/files/files.component.ts index 7efd214d..1ca3f837 100644 --- a/src/app/files/files.component.ts +++ b/src/app/files/files.component.ts @@ -227,7 +227,7 @@ export class FilesComponent implements OnInit { }); } - deleteFile(id: number){ + deleteFile(id: number, name: string){ const swalWithBootstrapButtons = Swal.mixin({ customClass: { confirmButton: 'btn', @@ -236,8 +236,7 @@ export class FilesComponent implements OnInit { buttonsStyling: false }) Swal.fire({ - title: "Are you sure?", - text: "Once deleted, it can not be recovered!", + title: 'Remove '+ name +' from your files?', icon: "warning", reverseButtons: true, showCancelButton: true, diff --git a/src/app/hashlists/hashlist/hashlist.component.html b/src/app/hashlists/hashlist/hashlist.component.html index 8e47ac4b..8b9025fc 100644 --- a/src/app/hashlists/hashlist/hashlist.component.html +++ b/src/app/hashlists/hashlist/hashlist.component.html @@ -58,7 +58,7 @@ Archive - diff --git a/src/app/hashlists/hashlist/hashlist.component.ts b/src/app/hashlists/hashlist/hashlist.component.ts index ba2e2f0b..fc32ee93 100644 --- a/src/app/hashlists/hashlist/hashlist.component.ts +++ b/src/app/hashlists/hashlist/hashlist.component.ts @@ -239,7 +239,7 @@ onArchive(id: number){ }); } -onDelete(id: number){ +onDelete(id: number, name: string){ const swalWithBootstrapButtons = Swal.mixin({ customClass: { confirmButton: 'btn', @@ -248,8 +248,7 @@ onDelete(id: number){ buttonsStyling: false }) Swal.fire({ - title: "Are you sure?", - text: "Once deleted, it can not be recovered!", + title: 'Remove '+ name +' from your hashlists?', icon: "warning", reverseButtons: true, showCancelButton: true, diff --git a/src/app/hashlists/superhashlist/superhashlist.component.html b/src/app/hashlists/superhashlist/superhashlist.component.html index b1eb2a45..bd426695 100644 --- a/src/app/hashlists/superhashlist/superhashlist.component.html +++ b/src/app/hashlists/superhashlist/superhashlist.component.html @@ -62,7 +62,7 @@ Edit - diff --git a/src/app/hashlists/superhashlist/superhashlist.component.ts b/src/app/hashlists/superhashlist/superhashlist.component.ts index c534ad5a..50eccc54 100644 --- a/src/app/hashlists/superhashlist/superhashlist.component.ts +++ b/src/app/hashlists/superhashlist/superhashlist.component.ts @@ -129,7 +129,7 @@ export class SuperhashlistComponent implements OnInit { }); } - onDelete(id: number){ + onDelete(id: number, name: string){ const swalWithBootstrapButtons = Swal.mixin({ customClass: { confirmButton: 'btn', @@ -138,8 +138,7 @@ export class SuperhashlistComponent implements OnInit { buttonsStyling: false }) Swal.fire({ - title: "Are you sure?", - text: "Once deleted, it can not be recovered!", + title: 'Remove '+ name +' from your superhashlists?', icon: "warning", reverseButtons: true, showCancelButton: true, diff --git a/src/app/tasks/edit-supertasks/edit-supertasks.component.ts b/src/app/tasks/edit-supertasks/edit-supertasks.component.ts index daa10a1b..c3556067 100644 --- a/src/app/tasks/edit-supertasks/edit-supertasks.component.ts +++ b/src/app/tasks/edit-supertasks/edit-supertasks.component.ts @@ -178,8 +178,7 @@ export class EditSupertasksComponent implements OnInit { buttonsStyling: false }) Swal.fire({ - title: "Are you sure?", - text: "Once deleted, it can not be recovered!", + title: 'Remove from your supertasks?', icon: "warning", reverseButtons: true, showCancelButton: true, diff --git a/src/app/tasks/preconfigured-tasks/preconfigured-tasks.component.html b/src/app/tasks/preconfigured-tasks/preconfigured-tasks.component.html index a7b85cc4..4de67970 100644 --- a/src/app/tasks/preconfigured-tasks/preconfigured-tasks.component.html +++ b/src/app/tasks/preconfigured-tasks/preconfigured-tasks.component.html @@ -43,7 +43,7 @@ Copy to task - diff --git a/src/app/tasks/preconfigured-tasks/preconfigured-tasks.component.ts b/src/app/tasks/preconfigured-tasks/preconfigured-tasks.component.ts index 69592422..be2d61bc 100644 --- a/src/app/tasks/preconfigured-tasks/preconfigured-tasks.component.ts +++ b/src/app/tasks/preconfigured-tasks/preconfigured-tasks.component.ts @@ -150,7 +150,7 @@ export class PreconfiguredTasksComponent implements OnInit { }); } - onDelete(id: number){ + onDelete(id: number, name: string){ const swalWithBootstrapButtons = Swal.mixin({ customClass: { confirmButton: 'btn', @@ -159,8 +159,7 @@ export class PreconfiguredTasksComponent implements OnInit { buttonsStyling: false }) Swal.fire({ - title: "Are you sure?", - text: "Once deleted, it can not be recovered!", + title: 'Remove '+ name +' from your pretasks?', icon: "warning", reverseButtons: true, showCancelButton: true, diff --git a/src/app/tasks/show-tasks/show-tasks.component.html b/src/app/tasks/show-tasks/show-tasks.component.html index 7539954f..0a7cd62a 100644 --- a/src/app/tasks/show-tasks/show-tasks.component.html +++ b/src/app/tasks/show-tasks/show-tasks.component.html @@ -113,7 +113,7 @@ Archive - @@ -133,7 +133,7 @@ Edit SubTasks - diff --git a/src/app/tasks/show-tasks/show-tasks.component.ts b/src/app/tasks/show-tasks/show-tasks.component.ts index fc0c3f9a..0842558e 100644 --- a/src/app/tasks/show-tasks/show-tasks.component.ts +++ b/src/app/tasks/show-tasks/show-tasks.component.ts @@ -288,7 +288,7 @@ getSubtasks(name: string, id: number){ }) } -onDelete(id: number, type: number){ +onDelete(id: number, type: number, name: string){ const swalWithBootstrapButtons = Swal.mixin({ customClass: { confirmButton: 'btn', @@ -297,8 +297,7 @@ onDelete(id: number, type: number){ buttonsStyling: false }) Swal.fire({ - title: "Are you sure?", - text: "Once deleted, it can not be recovered!", + title: 'Remove '+ name +' from your tasks?', icon: "warning", reverseButtons: true, showCancelButton: true, diff --git a/src/app/tasks/supertasks/supertasks.component.html b/src/app/tasks/supertasks/supertasks.component.html index adf79bd8..16329571 100644 --- a/src/app/tasks/supertasks/supertasks.component.html +++ b/src/app/tasks/supertasks/supertasks.component.html @@ -30,7 +30,7 @@ Apply Hashlist - diff --git a/src/app/tasks/supertasks/supertasks.component.ts b/src/app/tasks/supertasks/supertasks.component.ts index 3061a471..1f6ad039 100644 --- a/src/app/tasks/supertasks/supertasks.component.ts +++ b/src/app/tasks/supertasks/supertasks.component.ts @@ -149,7 +149,7 @@ export class SupertasksComponent implements OnInit { }); } - onDelete(id: number){ + onDelete(id: number, name: string){ const swalWithBootstrapButtons = Swal.mixin({ customClass: { confirmButton: 'btn', @@ -158,8 +158,7 @@ export class SupertasksComponent implements OnInit { buttonsStyling: false }) Swal.fire({ - title: "Are you sure?", - text: "Once deleted, it can not be recovered!", + title: 'Remove '+ name +' from your supertasks?', icon: "warning", reverseButtons: true, showCancelButton: true, diff --git a/src/app/users/all-users/all-users.component.html b/src/app/users/all-users/all-users.component.html index 6ab7b6b2..e043854e 100644 --- a/src/app/users/all-users/all-users.component.html +++ b/src/app/users/all-users/all-users.component.html @@ -23,7 +23,7 @@
{{ user.lastLoginDate | uiDate }} {{ user.email }} - + {{ user.isValid === true ? "Valid" : "Invalid/Not activated" }}
diff --git a/src/app/config/engine/crackers/crackers.component.html b/src/app/config/engine/crackers/crackers.component.html index 6b298d10..15189033 100644 --- a/src/app/config/engine/crackers/crackers.component.html +++ b/src/app/config/engine/crackers/crackers.component.html @@ -1,11 +1,11 @@ - +
- + diff --git a/src/app/config/engine/preprocessors/preprocessors.component.html b/src/app/config/engine/preprocessors/preprocessors.component.html index 5f656228..c2eec031 100644 --- a/src/app/config/engine/preprocessors/preprocessors.component.html +++ b/src/app/config/engine/preprocessors/preprocessors.component.html @@ -1,11 +1,11 @@ - +
Cracker IDID Name Available Versions Actions
- + diff --git a/src/app/layout/header/header.component.html b/src/app/layout/header/header.component.html index f44dd114..c4f3ce87 100644 --- a/src/app/layout/header/header.component.html +++ b/src/app/layout/header/header.component.html @@ -85,6 +85,17 @@ Other + - + - +
- + + + +
@@ -182,7 +185,10 @@
Hashcat Brain Enabled
 

Upload completed!


- + + + + diff --git a/src/app/hashlists/new-superhashlist/new-superhashlist.component.html b/src/app/hashlists/new-superhashlist/new-superhashlist.component.html index 12f1f9df..c4086827 100644 --- a/src/app/hashlists/new-superhashlist/new-superhashlist.component.html +++ b/src/app/hashlists/new-superhashlist/new-superhashlist.component.html @@ -30,6 +30,9 @@ - + + + + diff --git a/src/app/shared/buttons/button-submit.ts b/src/app/shared/buttons/button-submit.ts index 6b683af8..dd58060d 100644 --- a/src/app/shared/buttons/button-submit.ts +++ b/src/app/shared/buttons/button-submit.ts @@ -1,12 +1,75 @@ import { Component, Input } from '@angular/core'; +import { Router } from '@angular/router'; +import { Location } from '@angular/common'; +/** + * Component for rendering a submit or cancel button. + * + * This component provides a button element for form submissions or cancel actions with customizable label, + * disabled state, and a dynamic custom CSS class based on the type (cancel or normal). + * + * @selector button-submit + * + * @param name - The label to display on the button. + * @param disabled - A boolean value that determines whether the button is disabled or not. + * @param type - The type of the button (cancel or normal). + * + * @example + * ``` + * + * + * + * + * + * + * + * + * ``` + */ @Component({ selector: 'button-submit', template: ` - + ` }) export class ButtonSubmitComponent { + /** + * The label to display on the button. + */ @Input() name: string; + + /** + * A boolean value that determines whether the button is disabled or not. + */ @Input() disabled: boolean; + + /** + * The type of the button (cancel or normal). + */ + @Input() type: string; + + constructor(private router: Router, private location: Location) {} + + /** + * Get the custom CSS class based on the button type. + */ + getCustomClass(): string { + if (this.type === 'cancel' || this.type === 'delete') { + return "btn-danger shadow-sm btn-sm me-3"; + } else { + return "btn btn-gray-800"; // Default to normal style + } + } + + /** + * Handle the button click based on its type. + */ + handleClick(): void { + if (this.type === 'cancel') { + this.location.back(); // Go back to the previous window + } else { + // Handle other button actions as needed + } + } } + diff --git a/src/app/shared/buttons/buttons.module.ts b/src/app/shared/buttons/buttons.module.ts index f181fdc4..d933d4e4 100644 --- a/src/app/shared/buttons/buttons.module.ts +++ b/src/app/shared/buttons/buttons.module.ts @@ -1,4 +1,5 @@ import { ButtonSubmitComponent } from './button-submit'; +import { GridButtonsComponent } from './grid-cancel'; import { CommonModule } from '@angular/common'; import { FormsModule } from '@angular/forms'; import { NgModule } from '@angular/core'; @@ -9,10 +10,12 @@ import { NgModule } from '@angular/core'; CommonModule ], exports: [ - ButtonSubmitComponent + ButtonSubmitComponent, + GridButtonsComponent ], declarations: [ - ButtonSubmitComponent + ButtonSubmitComponent, + GridButtonsComponent ] }) export class ButtonsModule { } diff --git a/src/app/shared/buttons/grid-cancel.ts b/src/app/shared/buttons/grid-cancel.ts new file mode 100644 index 00000000..efde4c04 --- /dev/null +++ b/src/app/shared/buttons/grid-cancel.ts @@ -0,0 +1,31 @@ +import { Component } from '@angular/core'; + +/** + * Component for displaying a row with buttons in a grid layout. + * + * @example + * + * + * + * + * + * + * @selector grid-buttons + * + * @param content - Reference to the content inside the component. + * + * @remarks + * This component provides a structured layout for displaying buttons inside a grid row. + */ +@Component({ + selector: 'grid-buttons', + template: ` +
+
+
+
+
+ `, +}) +export class GridButtonsComponent {} + diff --git a/src/app/tasks/edit-preconfigured-tasks/edit-preconfigured-tasks.component.html b/src/app/tasks/edit-preconfigured-tasks/edit-preconfigured-tasks.component.html index a7abb408..f814e64b 100644 --- a/src/app/tasks/edit-preconfigured-tasks/edit-preconfigured-tasks.component.html +++ b/src/app/tasks/edit-preconfigured-tasks/edit-preconfigured-tasks.component.html @@ -137,7 +137,9 @@ - + + + diff --git a/src/app/tasks/edit-supertasks/edit-supertasks.component.html b/src/app/tasks/edit-supertasks/edit-supertasks.component.html index c47f9844..46d8078c 100644 --- a/src/app/tasks/edit-supertasks/edit-supertasks.component.html +++ b/src/app/tasks/edit-supertasks/edit-supertasks.component.html @@ -28,8 +28,8 @@ - - + +
Add Pretask
@@ -51,7 +51,7 @@
Add Pretask
- + diff --git a/src/app/tasks/import-supertasks/masks/masks.component.html b/src/app/tasks/import-supertasks/masks/masks.component.html index 8cb07879..96b21e5c 100644 --- a/src/app/tasks/import-supertasks/masks/masks.component.html +++ b/src/app/tasks/import-supertasks/masks/masks.component.html @@ -95,8 +95,10 @@ - - {{ createForm.value | json }} + + + + diff --git a/src/app/tasks/new-preconfigured-tasks/new-preconfigured-tasks.component.html b/src/app/tasks/new-preconfigured-tasks/new-preconfigured-tasks.component.html index 125bffb5..2f7e3203 100644 --- a/src/app/tasks/new-preconfigured-tasks/new-preconfigured-tasks.component.html +++ b/src/app/tasks/new-preconfigured-tasks/new-preconfigured-tasks.component.html @@ -156,7 +156,10 @@

New Preconfigured Tasks (Copied From Task ID {{edited   - + + + + diff --git a/src/app/tasks/new-supertasks/new-supertasks.component.html b/src/app/tasks/new-supertasks/new-supertasks.component.html index e938a314..0a9cbbe3 100644 --- a/src/app/tasks/new-supertasks/new-supertasks.component.html +++ b/src/app/tasks/new-supertasks/new-supertasks.component.html @@ -30,6 +30,9 @@ - + + + + diff --git a/src/app/tasks/new-tasks/new-tasks.component.html b/src/app/tasks/new-tasks/new-tasks.component.html index b081a969..7e98b244 100644 --- a/src/app/tasks/new-tasks/new-tasks.component.html +++ b/src/app/tasks/new-tasks/new-tasks.component.html @@ -294,7 +294,10 @@ Please complete all the form!
- + + + + diff --git a/src/app/tasks/supertasks/applyhashlist.component.html b/src/app/tasks/supertasks/applyhashlist.component.html index 94db641c..6c581dca 100644 --- a/src/app/tasks/supertasks/applyhashlist.component.html +++ b/src/app/tasks/supertasks/applyhashlist.component.html @@ -35,7 +35,10 @@ - + + + + diff --git a/src/app/users/edit-users/edit-users.component.html b/src/app/users/edit-users/edit-users.component.html index 1f9cc2aa..e89c00b1 100644 --- a/src/app/users/edit-users/edit-users.component.html +++ b/src/app/users/edit-users/edit-users.component.html @@ -114,8 +114,10 @@    - - + + + + diff --git a/src/app/users/globalpermissionsgroups/edit-globalpermissionsgroups/edit-globalpermissionsgroups.component.html b/src/app/users/globalpermissionsgroups/edit-globalpermissionsgroups/edit-globalpermissionsgroups.component.html index 7db0d674..f81fe6d8 100644 --- a/src/app/users/globalpermissionsgroups/edit-globalpermissionsgroups/edit-globalpermissionsgroups.component.html +++ b/src/app/users/globalpermissionsgroups/edit-globalpermissionsgroups/edit-globalpermissionsgroups.component.html @@ -807,7 +807,9 @@
Access Groups
- + + + diff --git a/src/app/users/globalpermissionsgroups/new-globalpermissionsgroups/new-globalpermissionsgroups.component.html b/src/app/users/globalpermissionsgroups/new-globalpermissionsgroups/new-globalpermissionsgroups.component.html index cc6a0dc3..0f302b3e 100644 --- a/src/app/users/globalpermissionsgroups/new-globalpermissionsgroups/new-globalpermissionsgroups.component.html +++ b/src/app/users/globalpermissionsgroups/new-globalpermissionsgroups/new-globalpermissionsgroups.component.html @@ -10,7 +10,10 @@ formControlName="name" > - + + + + diff --git a/src/app/users/groups/cu-group/cu-group.component.html b/src/app/users/groups/cu-group/cu-group.component.html index 87e46f23..5766d3d2 100644 --- a/src/app/users/groups/cu-group/cu-group.component.html +++ b/src/app/users/groups/cu-group/cu-group.component.html @@ -12,10 +12,15 @@
- + + + +
- + + +
diff --git a/src/app/users/users.component.html b/src/app/users/users.component.html index ef21ace4..bbb79838 100644 --- a/src/app/users/users.component.html +++ b/src/app/users/users.component.html @@ -30,7 +30,10 @@ - + + + + From d9fb51b98250e772bf442ccf3f075245cfb119ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Wed, 25 Oct 2023 13:45:39 +0100 Subject: [PATCH 105/419] Global Permission Group, fix style and add subcriptions --- ...dit-globalpermissionsgroups.component.html | 47 ++++++++------- .../edit-globalpermissionsgroups.component.ts | 59 +++++++++++++------ 2 files changed, 67 insertions(+), 39 deletions(-) diff --git a/src/app/users/globalpermissionsgroups/edit-globalpermissionsgroups/edit-globalpermissionsgroups.component.html b/src/app/users/globalpermissionsgroups/edit-globalpermissionsgroups/edit-globalpermissionsgroups.component.html index f81fe6d8..521b9d7c 100644 --- a/src/app/users/globalpermissionsgroups/edit-globalpermissionsgroups/edit-globalpermissionsgroups.component.html +++ b/src/app/users/globalpermissionsgroups/edit-globalpermissionsgroups/edit-globalpermissionsgroups.component.html @@ -2,31 +2,33 @@
- -
-   -

Preprocessor IDID Name Actions
-{{ task.taskId | aspeed | async | fileSize:false:1000 }} H/s + {{ task.taskId | aspeed | async | fileSize:false:1000 }} H/s + - + - {{ task.priority }} From f1dbe3e7d94e927a68964d9b68697e5b9ba02aa9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Wed, 27 Sep 2023 08:49:31 +0100 Subject: [PATCH 047/419] New Agent - Voucher button in table --- .../agents/new-agent/new-agent.component.html | 35 ++++++++++--------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/src/app/agents/new-agent/new-agent.component.html b/src/app/agents/new-agent/new-agent.component.html index 3a18545a..18192086 100644 --- a/src/app/agents/new-agent/new-agent.component.html +++ b/src/app/agents/new-agent/new-agent.component.html @@ -86,24 +86,27 @@
Instructions
-
-
-
-
- -
-
- + +
+
+
+
+
+
- -
+
+ +
+
+ +
From cf6db8a019249429d31aef705e7d8d4671aec699 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Wed, 27 Sep 2023 12:24:06 +0100 Subject: [PATCH 048/419] Hashtype - Bulk action delete hashtypes --- .../config/hashtypes/hashtypes.component.ts | 80 +++++++++++++++++-- 1 file changed, 74 insertions(+), 6 deletions(-) diff --git a/src/app/config/hashtypes/hashtypes.component.ts b/src/app/config/hashtypes/hashtypes.component.ts index 55b1c64e..582fd335 100644 --- a/src/app/config/hashtypes/hashtypes.component.ts +++ b/src/app/config/hashtypes/hashtypes.component.ts @@ -9,6 +9,7 @@ import { environment } from './../../../environments/environment'; import { PageTitle } from 'src/app/core/_decorators/autotitle'; import { SERV } from '../../core/_services/main.config'; +declare let $:any; @Component({ selector: 'app-hashtypes', templateUrl: './hashtypes.component.html' @@ -16,17 +17,17 @@ import { SERV } from '../../core/_services/main.config'; @PageTitle(['Show Hashtypes']) export class HashtypesComponent implements OnInit { + faInfoCircle=faInfoCircle; + faCancel=faCancel; faHome=faHomeAlt; - faPlus=faPlus; faTrash=faTrash; + faPlus=faPlus; faEdit=faEdit; faSave=faSave; - faCancel=faCancel; - faInfoCircle=faInfoCircle; private maxResults = environment.config.prodApiMaxResults; - @ViewChild(DataTableDirective, {static: false}) + @ViewChild(DataTableDirective) dtElement: DataTableDirective; dtTrigger: Subject = new Subject(); @@ -36,7 +37,7 @@ export class HashtypesComponent implements OnInit { private gs: GlobalService, ) { } - public htypes: {hashTypeId: number, description: string, isSalted: number, isSlowHash: number}[] = []; + public htypes: any; ngOnInit(): void { @@ -112,7 +113,21 @@ export class HashtypesComponent implements OnInit { }, 'copy' ] - } + }, + { + extend: 'collection', + text: 'Bulk Actions', + className: 'dt-button buttons-collection btn btn-sm-dt btn-outline-gray-600-dt', + buttons: [ + { + text: 'Delete Hashtypes', + autoClose: true, + action: function (e, dt, node, config) { + self.onDeleteBulk(); + } + } + ] + }, ], } }; @@ -135,6 +150,20 @@ export class HashtypesComponent implements OnInit { }); } + onDone(value?: any){ + setTimeout(() => { + this.ngOnInit(); + this.rerender(); // rerender datatables + Swal.close(); + Swal.fire({ + position: 'top-end', + icon: 'success', + showConfirmButton: false, + timer: 1500 + }) + },3000); + } + onDelete(id: number, name: string){ const swalWithBootstrapButtons = Swal.mixin({ customClass: { @@ -175,6 +204,45 @@ export class HashtypesComponent implements OnInit { } }); } + + onSelectedHashtypes(){ + $(".dt-button-background").trigger("click"); + const selection = $($(this.dtElement).DataTable.tables()).DataTable().rows({ selected: true } ).data().pluck(0).toArray(); + if(selection.length == 0) { + Swal.fire({ + position: 'top-end', + title: "You haven't selected any Hashtype", + type: 'success', + timer: 1500, + showConfirmButton: false + }) + return; + } + const selectionnum = selection.map(i=>Number(i)); + + return selectionnum; + } + + onDeleteBulk(){ + const self = this; + const selectionnum = this.onSelectedHashtypes(); + const sellen = selectionnum.length; + const errors = []; + selectionnum.forEach(function (value) { + Swal.fire('Deleting...'+sellen+' Hashtype(s)...Please wait') + Swal.showLoading() + self.gs.delete(SERV.HASHTYPES,value) + .subscribe( + err => { + // console.log('HTTP Error', err) + err = 1; + errors.push(err); + }, + ); + }); + self.onDone(sellen); + } + // Add unsubscribe to detect changes ngOnDestroy(){ this.dtTrigger.unsubscribe(); From 6a23ba1d54f8edf6fcff3b657fedb71ebe9121ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Wed, 27 Sep 2023 13:01:31 +0100 Subject: [PATCH 049/419] Users overview - Bulk action and clickable username --- .../users/all-users/all-users.component.html | 6 +- .../users/all-users/all-users.component.ts | 72 ++++++++++++++++++- 2 files changed, 75 insertions(+), 3 deletions(-) diff --git a/src/app/users/all-users/all-users.component.html b/src/app/users/all-users/all-users.component.html index e043854e..842a23a2 100644 --- a/src/app/users/all-users/all-users.component.html +++ b/src/app/users/all-users/all-users.component.html @@ -18,7 +18,11 @@ - + diff --git a/src/app/users/all-users/all-users.component.ts b/src/app/users/all-users/all-users.component.ts index 22bf65c6..fec2b555 100644 --- a/src/app/users/all-users/all-users.component.ts +++ b/src/app/users/all-users/all-users.component.ts @@ -12,6 +12,8 @@ import { PageTitle } from 'src/app/core/_decorators/autotitle'; import { SERV } from '../../core/_services/main.config'; import { DataTableDirective } from 'angular-datatables'; +declare let $:any; + @Component({ selector: 'app-all-users', templateUrl: './all-users.component.html' @@ -24,7 +26,7 @@ export class AllUsersComponent implements OnInit, OnDestroy { faEdit=faEdit; faPlus=faPlus; - @ViewChild(DataTableDirective, {static: false}) + @ViewChild(DataTableDirective) dtElement: DataTableDirective; dtTrigger: Subject = new Subject(); @@ -131,7 +133,21 @@ export class AllUsersComponent implements OnInit, OnDestroy { }, 'copy' ] - } + }, + { + extend: 'collection', + text: 'Bulk Actions', + className: 'dt-button buttons-collection btn btn-sm-dt btn-outline-gray-600-dt', + buttons: [ + { + text: 'Delete Users', + autoClose: true, + action: function (e, dt, node, config) { + self.onDeleteBulk(); + } + } + ] + }, ], } }; @@ -189,6 +205,58 @@ export class AllUsersComponent implements OnInit, OnDestroy { }); } + onSelectedAgents(){ + $(".dt-button-background").trigger("click"); + const selection = $($(this.dtElement).DataTable.tables()).DataTable().rows({ selected: true } ).data().pluck(0).toArray(); + if(selection.length == 0) { + Swal.fire({ + position: 'top-end', + title: "You haven't selected any User", + type: 'success', + timer: 1500, + showConfirmButton: false + }) + return; + } + const selectionnum = selection.map(i=>Number(i)); + + return selectionnum; + } + + onDeleteBulk(){ + const self = this; + const selectionnum = this.onSelectedAgents(); + const sellen = selectionnum.length; + const errors = []; + selectionnum.forEach(function (value) { + Swal.fire('Deleting...'+sellen+' User(s)...Please wait') + Swal.showLoading() + self.gs.delete(SERV.AGENTS,value) + .subscribe( + err => { + // console.log('HTTP Error', err) + err = 1; + errors.push(err); + }, + ); + }); + self.onDone(sellen); + } + + onDone(value?: any){ + setTimeout(() => { + this.ngOnInit(); + this.rerender(); // rerender datatables + Swal.close(); + Swal.fire({ + position: 'top-end', + icon: 'success', + showConfirmButton: false, + timer: 1500 + }) + },3000); + } + rerender(): void { this.dtElement.dtInstance.then((dtInstance: DataTables.Api) => { dtInstance.destroy(); From c144d9c1be4fd9f234103601cd12f7998ad3aca6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Wed, 27 Sep 2023 19:52:01 +0100 Subject: [PATCH 050/419] User detail page, max width,permission descrip and error setting pass --- src/app/users/all-users/all-users.component.html | 2 +- src/app/users/edit-users/edit-users.component.html | 4 ++-- src/app/users/edit-users/edit-users.component.ts | 5 +++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/app/users/all-users/all-users.component.html b/src/app/users/all-users/all-users.component.html index 842a23a2..5517c2b8 100644 --- a/src/app/users/all-users/all-users.component.html +++ b/src/app/users/all-users/all-users.component.html @@ -27,7 +27,7 @@ diff --git a/src/app/users/edit-users/edit-users.component.html b/src/app/users/edit-users/edit-users.component.html index 873ff860..1f9cc2aa 100644 --- a/src/app/users/edit-users/edit-users.component.html +++ b/src/app/users/edit-users/edit-users.component.html @@ -1,4 +1,4 @@ - +
@@ -73,7 +73,7 @@
- +
- - - + + + - + - +
- +
-   -
+ +
-
  - -

Uploading File: {{fileStatus.filename}}

- -
+
+
+ +
+

Upload completed!

-
;
- -
+
diff --git a/src/app/hashlists/new-hashlist/new-hashlist.component.ts b/src/app/hashlists/new-hashlist/new-hashlist.component.ts index 9459cc00..1ec21a8d 100644 --- a/src/app/hashlists/new-hashlist/new-hashlist.component.ts +++ b/src/app/hashlists/new-hashlist/new-hashlist.component.ts @@ -1,11 +1,11 @@ import { faMagnifyingGlass, faUpload, faInfoCircle, faFileUpload, faSearchPlus, faLink } from '@fortawesome/free-solid-svg-icons'; -import { Component, OnInit, ChangeDetectionStrategy ,ChangeDetectorRef, HostListener } from '@angular/core'; +import { Component, OnInit, ChangeDetectionStrategy ,ChangeDetectorRef, HostListener, ViewChild, ElementRef } from '@angular/core'; import { ModalDismissReasons, NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { FormControl, FormGroup, Validators } from '@angular/forms'; +import { Observable, Subject, Subscription, takeUntil } from 'rxjs'; import { environment } from './../../../environments/environment'; import Swal from 'sweetalert2/dist/sweetalert2.js'; import { Router } from '@angular/router'; -import { Observable, delay } from 'rxjs'; import { Buffer } from 'buffer'; import { UIConfigService } from 'src/app/core/_services/shared/storage.service'; @@ -21,8 +21,7 @@ import { SERV } from '../../core/_services/main.config'; @Component({ selector: 'app-new-hashlist', templateUrl: './new-hashlist.component.html', - providers: [FileSizePipe], - changeDetection: ChangeDetectionStrategy.OnPush + providers: [FileSizePipe] }) @PageTitle(['New Hashlist']) export class NewHashlistComponent implements OnInit { @@ -46,13 +45,13 @@ export class NewHashlistComponent implements OnInit { radio=true; brainenabled:any; hashcatbrain: string; + subscriptions: Subscription[] = [] // accessgroup: AccessGroup; //Use models when data structure is reliable accessgroup: any[] private maxResults = environment.config.prodApiMaxResults; constructor( - private _changeDetectorRef: ChangeDetectorRef, private uploadService:UploadTUSService, private uiService: UIConfigService, private modalService: NgbModal, @@ -64,13 +63,27 @@ export class NewHashlistComponent implements OnInit { ngOnInit(): void { + this.loadData(); + + } + + ngOnDestroy() { + for (const sub of this.subscriptions) { + sub.unsubscribe() + } + this.ngUnsubscribe.next(false); + this.ngUnsubscribe.complete(); + } + + loadData(){ + this.brainenabled = this.uiService.getUIsettings('hashcatBrainEnable').value; const params = {'maxResults': this.maxResults}; - this.gs.getAll(SERV.ACCESS_GROUPS, params).subscribe((agroups: any) => { + this.subscriptions.push(this.gs.getAll(SERV.ACCESS_GROUPS, params).subscribe((agroups: any) => { this.accessgroup = agroups.values; - }); + })); this.signupForm = new FormGroup({ 'name': new FormControl('', [Validators.required]), @@ -94,11 +107,9 @@ export class NewHashlistComponent implements OnInit { ngAfterViewInit() { - this.uploadProgress = this.uploadService.uploadProgress; // TUS upload progress - const params = {'maxResults': this.maxResults}; - this.gs.getAll(SERV.HASHTYPES,params).subscribe((htypes: any) => { + this.subscriptions.push(this.gs.getAll(SERV.HASHTYPES,params).subscribe((htypes: any) => { const self = this; const prep = htypes.values; const response = []; @@ -132,7 +143,7 @@ export class NewHashlistComponent implements OnInit { selectize.setValue(selected_items); } }); - }); + })); } @@ -140,18 +151,29 @@ export class NewHashlistComponent implements OnInit { this.signupForm.patchValue({ hashTypeId: Number(value) }); - this._changeDetectorRef.detectChanges(); + // this._changeDetectorRef.detectChanges(); } // FILE UPLOAD: TUS File Uload - uploadProgress: Observable; + @ViewChild('file', {static: false}) file: ElementRef; + uploadProgress = 0; filenames: string[] = []; + private ngUnsubscribe = new Subject(); onuploadFile(files: FileList) { + let form = this.handleUpload(this.signupForm.value); + const upload: Array = []; for (let i = 0; i < files.length; i++) { - this.filenames.push(files[i].name); - console.log(`Uploading ${files[i].name} with size ${files[i].size} and type ${files[i].type}`); - this.uploadService.uploadFile(files[i], files[i].name); + upload.push( + this.uploadService.uploadFile( + files[i], files[i].name, SERV.HASHLISTS, form, ['/hashlists/hashlist'] + ).pipe(takeUntil(this.ngUnsubscribe)) + .subscribe( + (progress) => { + this.uploadProgress = progress; + } + ) + ) } } @@ -181,6 +203,7 @@ export class NewHashlistComponent implements OnInit { validateFileExt = validateFileExt; + selectedFile: ''; fileGroup: number; fileToUpload: File | null = null; fileSize: any; @@ -190,7 +213,7 @@ export class NewHashlistComponent implements OnInit { this.fileToUpload = event.target.files[0]; this.fileSize = this.fileToUpload.size; this.fileName = this.fileToUpload.name; - $('.fileuploadspan').text('Size: '+this.fs.transform(this.fileToUpload.size,false)); + $('.fileuploadspan').text( this.fs.transform(this.fileToUpload.size,false)); } /** @@ -198,20 +221,12 @@ export class NewHashlistComponent implements OnInit { * */ - submitted = false; - onSubmitFile(): void{ - setTimeout(() => { - this.onSubmit(); - this.submitted = true; - },1000); - } - onSubmit(): void{ if (this.signupForm.valid) { const res = this.handleUpload(this.signupForm.value); - this.gs.create(SERV.HASHLISTS,res).subscribe(() => { + this.subscriptions.push(this.gs.create(SERV.HASHLISTS,res).subscribe(() => { Swal.fire({ position: 'top-end', backdrop: false, @@ -223,7 +238,7 @@ export class NewHashlistComponent implements OnInit { }) this.router.navigate(['/hashlists/hashlist']); } - ); + )); } } From e06a18173a839c5b661aace97978123f67e1d03e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Tue, 10 Oct 2023 11:41:53 +0100 Subject: [PATCH 079/419] Hashlisth create page - on hasytpe selection change format --- .../new-hashlist/new-hashlist.component.ts | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/src/app/hashlists/new-hashlist/new-hashlist.component.ts b/src/app/hashlists/new-hashlist/new-hashlist.component.ts index 1ec21a8d..4aeb1a12 100644 --- a/src/app/hashlists/new-hashlist/new-hashlist.component.ts +++ b/src/app/hashlists/new-hashlist/new-hashlist.component.ts @@ -48,7 +48,8 @@ export class NewHashlistComponent implements OnInit { subscriptions: Subscription[] = [] // accessgroup: AccessGroup; //Use models when data structure is reliable - accessgroup: any[] + accessgroup: any[]; + hashtypes: any[]; private maxResults = environment.config.prodApiMaxResults; constructor( @@ -111,6 +112,7 @@ export class NewHashlistComponent implements OnInit { this.subscriptions.push(this.gs.getAll(SERV.HASHTYPES,params).subscribe((htypes: any) => { const self = this; + this.hashtypes = htypes.values; const prep = htypes.values; const response = []; for(let i=0; i < prep.length; i++){ @@ -148,10 +150,22 @@ export class NewHashlistComponent implements OnInit { } OnChangeValue(value){ - this.signupForm.patchValue({ - hashTypeId: Number(value) - }); - // this._changeDetectorRef.detectChanges(); + const id = Number(value); + // Map with hashtype and get if its salted or not + const filter = this.hashtypes.filter(u=> u._id === id); + const salted = filter[0]['isSalted']; + if(id === 2500 || id === 16800 || id === 16801){ + this.signupForm.patchValue({ + hashTypeId: id, + format: Number(1), + isSalted: salted + }); + }else{ + this.signupForm.patchValue({ + hashTypeId: id, + isSalted: salted + }); + } } // FILE UPLOAD: TUS File Uload From 5d51eda17fcaf888f84517985bb3a13891214b04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Tue, 10 Oct 2023 15:57:27 +0100 Subject: [PATCH 080/419] Agent page, copy text button and remove old code --- package-lock.json | 8 +- package.json | 2 +- .../agents/new-agent/new-agent.component.html | 152 ++++++++---------- .../agents/new-agent/new-agent.component.ts | 73 +++++++-- 4 files changed, 125 insertions(+), 110 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7989c0ca..9b7afea4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -40,7 +40,7 @@ "datatables.net": "^1.13.5", "datatables.net-bs5": "^1.13.5", "datatables.net-buttons": "^2.4.0", - "datatables.net-buttons-dt": "^2.4.0", + "datatables.net-buttons-dt": "^2.4.2", "datatables.net-dt": "^1.13.5", "datatables.net-responsive": "^2.5.0", "datatables.net-responsive-dt": "^2.5.0", @@ -7270,9 +7270,9 @@ } }, "node_modules/datatables.net-buttons-dt": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/datatables.net-buttons-dt/-/datatables.net-buttons-dt-2.4.1.tgz", - "integrity": "sha512-Pt/X+WS/20fOYU8/U8s2y5XF8LeJO+YvMnKkTEFIX1Pd/m680eBVcBjfIxBxRvLekPVD/nkRznXEh2Hx4uV14A==", + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/datatables.net-buttons-dt/-/datatables.net-buttons-dt-2.4.2.tgz", + "integrity": "sha512-Xp0phHLyIREO7WnTVlCXI1RaFJKSt7nZkm+VOVOFzwJ1jeTUZW/lMsp8kXsQpfl0B/PGjx/NlHbxDRQEg9obNw==", "dependencies": { "datatables.net-buttons": ">=2.3.6", "datatables.net-dt": ">=1.13.4", diff --git a/package.json b/package.json index fd733e95..4c9daf6c 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ "datatables.net": "^1.13.5", "datatables.net-bs5": "^1.13.5", "datatables.net-buttons": "^2.4.0", - "datatables.net-buttons-dt": "^2.4.0", + "datatables.net-buttons-dt": "^2.4.2", "datatables.net-dt": "^1.13.5", "datatables.net-responsive": "^2.5.0", "datatables.net-responsive-dt": "^2.5.0", diff --git a/src/app/agents/new-agent/new-agent.component.html b/src/app/agents/new-agent/new-agent.component.html index cdb79da5..a260cfa4 100644 --- a/src/app/agents/new-agent/new-agent.component.html +++ b/src/app/agents/new-agent/new-agent.component.html @@ -8,7 +8,7 @@
Instructions
Using the URL, link the agent with the app: {{ agentURL }} Instructions -
{{ user.id }}{{ user.name }} + + {{ user.name | shortenString:40 }} + + {{ user.registeredSince | uiDate }} {{ user.lastLoginDate | uiDate }} {{ user.email }}{{ user.lastLoginDate | uiDate }} {{ user.email }} - + {{ user.isValid === true ? "Valid" : "Invalid/Not activated" }}
{{ a.id }}{{ a.name }}{{ a.user.length }}{{ a.id }} + + {{ a.name| shortenString:40 }} + + {{ a.user.length }}
{{ a.accessGroupId }}{{ a.groupName }} + + {{ a.groupName | shortenString:40 }} + + + ` }) -export class ButtonSubmitComponent { - +export class ButtonSubmitComponent { @Input() name: string; - + @Input() disabled: boolean; } From dd2c53221342247112ec662f223491e5dc00ecf9 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Fri, 6 Oct 2023 13:36:17 +0200 Subject: [PATCH 061/419] Initial tests for new-notification --- .../edit-notification.component.ts | 84 +++--- .../new-notification.component.html | 89 +++--- .../new-notification.component.spec.ts | 272 +++++++++++++++++- .../new-notification.component.ts | 189 ++++++------ .../notifications.component.html | 84 +++--- .../notifications/notifications.component.ts | 173 +++++------ src/app/spec-helpers/element.spec-helper.ts | 242 ++++++++++++++++ 7 files changed, 813 insertions(+), 320 deletions(-) create mode 100644 src/app/spec-helpers/element.spec-helper.ts diff --git a/src/app/account/notifications/notification/edit-notification.component.ts b/src/app/account/notifications/notification/edit-notification.component.ts index fcf8c2fe..8789f180 100644 --- a/src/app/account/notifications/notification/edit-notification.component.ts +++ b/src/app/account/notifications/notification/edit-notification.component.ts @@ -1,13 +1,14 @@ -import { FormControl, FormGroup, Validators } from '@angular/forms'; +import { FormControl, FormGroup } from '@angular/forms'; import { Component, OnInit } from '@angular/core'; import Swal from 'sweetalert2/dist/sweetalert2.js'; -import { ACTIONARRAY, ACTION, NOTIFARRAY } from '../../../core/_constants/notifications.config'; -import { environment } from '../../../../environments/environment'; +import { ACTIONARRAY, NOTIFARRAY } from '../../../core/_constants/notifications.config'; import { GlobalService } from 'src/app/core/_services/main.service'; +import { environment } from '../../../../environments/environment'; +import { ActivatedRoute, Params, Router } from '@angular/router'; import { PageTitle } from 'src/app/core/_decorators/autotitle'; import { SERV } from '../../../core/_services/main.config'; -import { ActivatedRoute, Params, Router } from '@angular/router'; + @Component({ selector: 'app-edit-notification', @@ -20,12 +21,11 @@ export class EditNotificationComponent implements OnInit { editView = true; constructor( - private route:ActivatedRoute, + private route: ActivatedRoute, private gs: GlobalService, - private router:Router + private router: Router ) { } - Allnotif: any; value: any; active = true; @@ -35,56 +35,60 @@ export class EditNotificationComponent implements OnInit { private maxResults = environment.config.prodApiMaxResults; - createForm = new FormGroup({ - 'action': new FormControl({value: '', disabled: true}), - 'actionFilter': new FormControl({value: '', disabled: true}), - 'notification': new FormControl({value: '', disabled: true}), - 'receiver': new FormControl({value: '', disabled: true}), + form = new FormGroup({ + 'action': new FormControl({ value: '', disabled: true }), + 'actionFilter': new FormControl({ value: '', disabled: true }), + 'notification': new FormControl({ value: '', disabled: true }), + 'receiver': new FormControl({ value: '', disabled: true }), 'isActive': new FormControl(), }); ngOnInit(): void { this.route.params - .subscribe( - (params: Params) => { - this.editedIndex = +params['id']; - this.initForm(); - } - ); + .subscribe( + (params: Params) => { + this.editedIndex = +params['id']; + this.initForm(); + } + ); + + } + changeAction(action: string): void { } private initForm() { - this.gs.get(SERV.NOTIFICATIONS,this.editedIndex).subscribe((result)=>{ - this.createForm = new FormGroup({ - 'action': new FormControl({value: result['action'], disabled: true}), - 'actionFilter': new FormControl({value: result['objectId'], disabled: true}), - 'notification': new FormControl({value: result['notification'], disabled: true}), - 'receiver': new FormControl({value: result['receiver'], disabled: true}), + this.gs.get(SERV.NOTIFICATIONS, this.editedIndex).subscribe((result) => { + this.form = new FormGroup({ + 'action': new FormControl({ value: result['action'], disabled: true }), + 'actionFilter': new FormControl({ value: result['objectId'], disabled: true }), + 'notification': new FormControl({ value: result['notification'], disabled: true }), + 'receiver': new FormControl({ value: result['receiver'], disabled: true }), 'isActive': new FormControl(result['isActive']), }); }); } - onQueryp(val){} - - onSubmit(){ - if (this.createForm.valid) { - - this.gs.update(SERV.NOTIFICATIONS,this.editedIndex,{'isActive':this.createForm.value['isActive']}).subscribe(() => { - Swal.fire({ - title: "Success!", - text: "New Notification created!", - icon: "success", - showConfirmButton: false, - timer: 1500 - }); - this.ngOnInit(); - this.router.navigate(['/account/notifications']); - } + onQueryp(val) { } + + onSubmit() { + if (this.form.valid) { + + this.gs.update(SERV.NOTIFICATIONS, this.editedIndex, { 'isActive': this.form.value['isActive'] }).subscribe(() => { + Swal.fire({ + position: 'top-end', + backdrop: false, + icon: 'success', + title: "Saved", + showConfirmButton: false, + timer: 1500 + }) + this.ngOnInit(); + this.router.navigate(['/account/notifications']); + } ); } } diff --git a/src/app/account/notifications/notification/new-notification.component.html b/src/app/account/notifications/notification/new-notification.component.html index 7ebd14f3..2d240eed 100644 --- a/src/app/account/notifications/notification/new-notification.component.html +++ b/src/app/account/notifications/notification/new-notification.component.html @@ -1,54 +1,39 @@ - + - -
- - - - - - - - - - - - - -
- -
-
- -
+ +
+ + + + + + + + + + + + + +
+ +
+
+ +
-
+
\ No newline at end of file diff --git a/src/app/account/notifications/notification/new-notification.component.spec.ts b/src/app/account/notifications/notification/new-notification.component.spec.ts index 1f9d10ed..dd02d898 100644 --- a/src/app/account/notifications/notification/new-notification.component.spec.ts +++ b/src/app/account/notifications/notification/new-notification.component.spec.ts @@ -1,23 +1,283 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; - import { NewNotificationComponent } from './new-notification.component'; +import { CommonModule } from '@angular/common'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { GlobalService } from 'src/app/core/_services/main.service'; +import { Observable, of } from 'rxjs'; +import { Params, RouterModule } from '@angular/router'; +import { HttpClientModule } from '@angular/common/http'; +import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; +import { DataTablesModule } from 'angular-datatables'; +import { ComponentsModule } from 'src/app/shared/components.module'; +import { PipesModule } from 'src/app/shared/pipes.module'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { findEl, setFieldValue } from 'src/app/spec-helpers/element.spec-helper'; +import { ACTION, NOTIF } from 'src/app/core/_constants/notifications.config'; +import { DebugElement } from '@angular/core'; +import { SERV } from 'src/app/core/_services/main.config'; +import { By } from '@angular/platform-browser'; describe('NewNotificationComponent', () => { let component: NewNotificationComponent; let fixture: ComponentFixture; + const agentValues = [ + { _id: '1', agentName: 'Agent 1' }, + { _id: '2', agentName: 'Agent 2' }, + { _id: '3', agentName: 'Agent 3' }, + { _id: '4', agentName: 'Agent 4' }, + { _id: '5', agentName: 'Agent 5' }, + ] + + const taskValues = [ + { _id: '1', taskName: 'Task 1' }, + { _id: '2', taskName: 'Task 2' }, + { _id: '3', taskName: 'Task 3' }, + { _id: '4', taskName: 'Task 4' }, + { _id: '5', taskName: 'Task 5' }, + ] + + const hashlistValues = [ + { _id: '1', name: 'Hashlist 1' }, + { _id: '2', name: 'Hashlist 2' }, + { _id: '3', name: 'Hashlist 3' }, + { _id: '4', name: 'Hashlist 4' }, + { _id: '5', name: 'Hashlist 5' }, + ] + + const userValues = [ + { _id: '1', name: 'User 1' }, + { _id: '2', name: 'User 2' }, + { _id: '3', name: 'User 3' }, + { _id: '4', name: 'User 4' }, + { _id: '5', name: 'User 5' }, + ] + + const mockService: Partial = { + getAll(_methodUrl: string, _routerParams?: Params): Observable { + return of([]) + } + } + beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [ NewNotificationComponent ] - }) - .compileComponents(); + imports: [ + CommonModule, + FormsModule, + ReactiveFormsModule, + HttpClientModule, + FontAwesomeModule, + DataTablesModule, + ComponentsModule, + RouterModule, + PipesModule, + NgbModule, + ], + declarations: [ + NewNotificationComponent + ], + providers: [ + { + provide: GlobalService, + useValue: mockService + } + ] + }).compileComponents(); fixture = TestBed.createComponent(NewNotificationComponent); component = fixture.componentInstance; fixture.detectChanges(); }); - it('should create', () => { - expect(component).toBeTruthy(); + it('shold not be possible to submit the form when form is empty', () => { + expectButtonToBeDisabled() + }); + + it('shold not be possible to submit the form when trigger action is not selected', () => { + setFieldValue(fixture, 'select-notification', NOTIF.TELEGRAM) + setFieldValue(fixture, 'input-receiver', 'test-receiver') + expectButtonToBeDisabled() + }); + + it('shold not be possible to submit the form when notification is not selected', () => { + spyOn(mockService, 'getAll') + .withArgs(SERV.TASKS, { 'maxResults': component.maxResults }) + .and.returnValue(of({ values: taskValues })) + + setAction(ACTION.NEW_TASK) + setFieldValue(fixture, 'select-action-filter', '1') + setFieldValue(fixture, 'input-receiver', 'test-receiver') + expectButtonToBeDisabled() + }); + + it('shold not be possible to submit the form when receiver is not selected', () => { + spyOn(mockService, 'getAll') + .withArgs(SERV.TASKS, { 'maxResults': component.maxResults }) + .and.returnValue(of({ values: taskValues })) + + setAction(ACTION.NEW_TASK) + setFieldValue(fixture, 'select-action-filter', '1') + setFieldValue(fixture, 'select-notification', NOTIF.EMAIL) + expectButtonToBeDisabled() + }); + + it('shold not possible to submit the form when all fields have data', () => { + spyOn(mockService, 'getAll') + .withArgs(SERV.TASKS, { 'maxResults': component.maxResults }) + .and.returnValue(of({ values: taskValues })) + + setAction(ACTION.NEW_TASK) + setFieldValue(fixture, 'select-action-filter', '1') + setFieldValue(fixture, 'select-notification', NOTIF.EMAIL) + setFieldValue(fixture, 'input-receiver', 'test@mail.com') + fixture.detectChanges(); + + expectButtonToBeEnabled() + }); + + it('should display agent filters when action is AGENT_ERROR', () => { + expectAgentOptionsOnAction(ACTION.AGENT_ERROR) + }); + + it('should display agent filters when action is OWN_AGENT_ERROR ', () => { + expectAgentOptionsOnAction(ACTION.OWN_AGENT_ERROR) + }); + + it('should display agent filters when action is DELETE_AGENT ', () => { + expectAgentOptionsOnAction(ACTION.DELETE_AGENT) + }); + + it('should display task filters when action is NEW_TASK ', () => { + expectTaskOptionsOnAction(ACTION.NEW_TASK) + }); + + it('should display task filters when action is TASK_COMPLETE ', () => { + expectTaskOptionsOnAction(ACTION.TASK_COMPLETE) + }); + + it('should display task filters when action is DELETE_TASK ', () => { + expectTaskOptionsOnAction(ACTION.DELETE_TASK) + }); + + it('should display no filters when action is NEW_HASHLIST ', () => { + expectHiddenOnAction(ACTION.NEW_HASHLIST) + }); + + it('should display hashlist filters when action is DELETE_HASHLIST ', () => { + expectHashlistOptionsOnAction(ACTION.DELETE_HASHLIST) + }); + + it('should display hashlist filters when action is HASHLIST_ALL_CRACKED ', () => { + expectHashlistOptionsOnAction(ACTION.HASHLIST_ALL_CRACKED) + }); + + it('should display hashlist filters when action is HASHLIST_CRACKED_HASH ', () => { + expectHashlistOptionsOnAction(ACTION.HASHLIST_CRACKED_HASH) + }); + + it('should display user filters when action is USER_CREATED ', () => { + expectUserOptionsOnAction(ACTION.USER_CREATED) + }); + + it('should display user filters when action is USER_DELETED ', () => { + expectUserOptionsOnAction(ACTION.USER_DELETED) + }); + + it('should display user filters when action is USER_LOGIN_FAILED ', () => { + expectUserOptionsOnAction(ACTION.USER_LOGIN_FAILED) }); + + it('should display no filters when action is LOG_WARN ', () => { + expectHiddenOnAction(ACTION.LOG_WARN) + }); + + it('should display no filters when action is LOG_FATAL ', () => { + expectHiddenOnAction(ACTION.LOG_FATAL) + }); + + it('should display no filters when action is LOG_ERROR ', () => { + expectHiddenOnAction(ACTION.LOG_ERROR) + }); + + const expectButtonToBeDisabled = (): void => { + const btn: DebugElement = findEl(fixture, 'button-create') + expect(btn.nativeElement.attributes.getNamedItem('ng-reflect-disabled').value).toEqual('true') + } + + const expectButtonToBeEnabled = (): void => { + const btn: DebugElement = findEl(fixture, 'button-create') + expect(btn.nativeElement.attributes.getNamedItem('ng-reflect-disabled').value).toEqual('false') + } + + const expectHiddenOnAction = (action: string): void => { + spyOn(mockService, 'getAll') + setAction(action) + expect(mockService.getAll).not.toHaveBeenCalled() + expect(fixture.debugElement.query(By.css('select-action-filter'))).toBeFalsy() + } + + const expectAgentOptionsOnAction = (action: string): void => { + spyOn(mockService, 'getAll') + .withArgs(SERV.AGENTS, { 'maxResults': component.maxResults }) + .and.returnValue(of({ values: agentValues })) + + setAction(action) + const elem = findEl(fixture, 'select-action-filter') + for (let i = 0; i < 5; i++) { + expextOptionToBe(elem, i + 1, agentValues[i]._id, agentValues[i].agentName) + } + } + + const expectTaskOptionsOnAction = (action: string): void => { + spyOn(mockService, 'getAll') + .withArgs(SERV.TASKS, { 'maxResults': component.maxResults }) + .and.returnValue(of({ values: taskValues })) + + setAction(action) + const elem = findEl(fixture, 'select-action-filter') + for (let i = 0; i < 5; i++) { + expextOptionToBe(elem, i + 1, taskValues[i]._id, taskValues[i].taskName) + } + } + + const expectHashlistOptionsOnAction = (action: string): void => { + spyOn(mockService, 'getAll') + .withArgs(SERV.HASHLISTS, { 'maxResults': component.maxResults }) + .and.returnValue(of({ values: hashlistValues })) + + setAction(action) + const elem = findEl(fixture, 'select-action-filter') + for (let i = 0; i < 5; i++) { + expextOptionToBe(elem, i + 1, hashlistValues[i]._id, hashlistValues[i].name) + } + } + + const expectUserOptionsOnAction = (action: string): void => { + spyOn(mockService, 'getAll') + .withArgs(SERV.USERS, { 'maxResults': component.maxResults }) + .and.returnValue(of({ values: userValues })) + + setAction(action) + const elem = findEl(fixture, 'select-action-filter') + for (let i = 0; i < 5; i++) { + expextOptionToBe(elem, i + 1, userValues[i]._id, userValues[i].name) + } + } + + const setAction = (action: string): void => { + setFieldValue(fixture, 'select-action', action) + fixture.detectChanges(); + } + + const expextOptionToBe = (elem: DebugElement, index: number, value: string, textContent: string): void => { + expect(elem.childNodes[index].nativeNode.value).toEqual(value); + expect(elem.childNodes[index].nativeNode.textContent).toEqual(textContent); + } }); + + + + + + + diff --git a/src/app/account/notifications/notification/new-notification.component.ts b/src/app/account/notifications/notification/new-notification.component.ts index 0791138e..e9107b02 100644 --- a/src/app/account/notifications/notification/new-notification.component.ts +++ b/src/app/account/notifications/notification/new-notification.component.ts @@ -1,186 +1,175 @@ import { FormControl, FormGroup, Validators } from '@angular/forms'; -import { Component, OnInit } from '@angular/core'; +import { Component, OnDestroy, OnInit } from '@angular/core'; import Swal from 'sweetalert2/dist/sweetalert2.js'; import { ACTIONARRAY, ACTION, NOTIFARRAY } from '../../../core/_constants/notifications.config'; import { environment } from '../../../../environments/environment'; import { GlobalService } from 'src/app/core/_services/main.service'; -import { PageTitle } from 'src/app/core/_decorators/autotitle'; import { SERV } from '../../../core/_services/main.config'; -import { ActivatedRoute, Router } from '@angular/router'; +import { Router } from '@angular/router'; +import { Subscription } from 'rxjs'; +import { AutoTitleService } from 'src/app/core/_services/shared/autotitle.service'; @Component({ selector: 'app-new-notification', templateUrl: './new-notification.component.html' }) -@PageTitle(['New Notification']) -export class NewNotificationComponent implements OnInit { +export class NewNotificationComponent implements OnInit, OnDestroy { - constructor( - private route:ActivatedRoute, - private gs: GlobalService, - private router:Router - ) { } - - createForm: FormGroup; - Allnotif: any; + triggerAction: string; + form: FormGroup; value: any; editView = false; - active = true; - + active = false; allowedActions = ACTIONARRAY; - notifications = NOTIFARRAY; + subscriptions: Subscription[] = [] - private maxResults = environment.config.prodApiMaxResults; + maxResults = environment.config.prodApiMaxResults; - ngOnInit(): void { + constructor( + private gs: GlobalService, + private titleService: AutoTitleService, + private router: Router) { + titleService.set(['New Notification']) + } - const qp = this.route.snapshot.queryParams; + ngOnInit(): void { + this.createForm() + } - this.onSubscribe(qp['filter']); + ngOnDestroy(): void { + for (const sub of this.subscriptions) { + sub.unsubscribe() + } + } - this.createForm = new FormGroup({ - 'action': new FormControl('' || qp['filter']), + createForm(): void { + this.form = new FormGroup({ + 'action': new FormControl('', [Validators.required]), 'actionFilter': new FormControl(''), - 'notification': new FormControl('' || 'ChatBot'), - 'receiver': new FormControl(), + 'notification': new FormControl('' || 'ChatBot', [Validators.required]), + 'receiver': new FormControl('', [Validators.required]), 'isActive': new FormControl(true), }); - } - onSubscribe(filter: string){ - - let path = this.checkPath(filter); - - const params = {'maxResults': this.maxResults}; - - if(String(path) !== 'none'){ - this.active = true; - this.gs.getAll(path,params).subscribe((res: any) => { - const value = [] - for(let i=0; i < res.values.length; i++){ - if(path === SERV.AGENTS) { - console.log(res.values.length) - value.push({"id": res.values[i]['_id'], "name": res.values[i]['agentName']}); - } - if(path === SERV.TASKS) { - value.push({"id": res.values[i]['_id'], "name": res.values[i]['taskName']}); - } - if(path === SERV.USERS || path === SERV.HASHLISTS ){ - value.push({"id": res.values[i]['_id'], "name": res.values[i]['name']}); - } - } - this.value = value; - }); - }else{ + changeAction(action: string): void { + if (action) { + const path = this.checkPath(action); + if (path) { + const params = { 'maxResults': this.maxResults }; + + this.active = true; + + this.subscriptions.push(this.gs.getAll(path, params).subscribe((res: any) => { + const value = [] + for (let i = 0; i < res.values.length; i++) { + if (path === SERV.AGENTS) { + value.push({ "id": res.values[i]['_id'], "name": res.values[i]['agentName'] }); + } + if (path === SERV.TASKS) { + value.push({ "id": res.values[i]['_id'], "name": res.values[i]['taskName'] }); + } + if (path === SERV.USERS || path === SERV.HASHLISTS) { + value.push({ "id": res.values[i]['_id'], "name": res.values[i]['name'] }); + } + } + this.value = value; + })); + } else { + this.active = false; + } + } else { this.active = false; } } - onQueryp(name: any){ - this.router.navigate(['/account/notifications/new-notification'], {queryParams: {filter: name}, queryParamsHandling: 'merge'}); - setTimeout(() => { - this.ngOnInit(); - }); - } - - checkPath(filter: string){ + checkPath(filter: string) { + let path: string; - let path; switch (filter) { - case ACTION.AGENT_ERROR: path = SERV.AGENTS; - break; + break; case ACTION.OWN_AGENT_ERROR: path = SERV.AGENTS; - break; + break; case ACTION.DELETE_AGENT: path = SERV.AGENTS; - break; + break; case ACTION.NEW_TASK: path = SERV.TASKS; - break; + break; - case ACTION.TASK_COMPLETE: + case ACTION.TASK_COMPLETE: path = SERV.TASKS; - break; + break; case ACTION.DELETE_TASK: path = SERV.TASKS; - break; + break; case ACTION.NEW_HASHLIST: - path = 'none'; - break; + break; case ACTION.DELETE_HASHLIST: path = SERV.HASHLISTS; - break; + break; case ACTION.HASHLIST_ALL_CRACKED: path = SERV.HASHLISTS; - break; + break; case ACTION.HASHLIST_CRACKED_HASH: path = SERV.HASHLISTS; - break; + break; case ACTION.USER_CREATED: path = SERV.USERS; - break; + break; case ACTION.USER_DELETED: path = SERV.USERS; - break; + break; case ACTION.USER_LOGIN_FAILED: path = SERV.USERS; - break; + break; case ACTION.LOG_WARN: - path = 'none'; - break; + break; case ACTION.LOG_FATAL: - path = 'none'; - break; + break; case ACTION.LOG_ERROR: - path = 'none'; - break; - - default: - path = 'none'; + break; } return path; } - onSubmit(){ - if (this.createForm.valid) { - - this.gs.create(SERV.NOTIFICATIONS,this.createForm.value).subscribe(() => { - Swal.fire({ - title: "Success!", - text: "New Notification created!", - icon: "success", - showConfirmButton: false, - timer: 1500 - }); - this.ngOnInit(); - this.router.navigate(['/account/notifications']); - } - ); + onSubmit() { + if (this.form.valid) { + this.subscriptions.push(this.gs.create(SERV.NOTIFICATIONS, this.form.value).subscribe(() => { + Swal.fire({ + position: 'top-end', + backdrop: false, + icon: 'success', + title: "Success!", + text: "New Notification created!", + showConfirmButton: false, + timer: 1500 + }) + this.router.navigate(['/account/notifications']); + })); } } - } diff --git a/src/app/account/notifications/notifications.component.html b/src/app/account/notifications/notifications.component.html index a3914f88..37ae3678 100644 --- a/src/app/account/notifications/notifications.component.html +++ b/src/app/account/notifications/notifications.component.html @@ -1,45 +1,53 @@ - + - +
- - - - - - - + + + + + + + - - - - - - - - - - + + + + + + + + + + - +
IDStatusTrigger actionApplied ToCalled NotificationReceiverActionIDStatusTrigger actionApplied ToCalled NotificationReceiverAction
{{ n.notificationSettingId }} - - {{ n.isActive === true ? "Active" : "Invalid/Not activated" }} - - {{ n.action }} - {{checkPath(n.action, false)}} {{ n.objectId }} - {{checkPath(n.action, false)}} {{ n.objectId }} - {{ n.notification }}{{ n.receiver }} - - - - - -
{{ n.notificationSettingId }} + + {{ n.isActive === true ? "Active" : "Invalid/Not activated" }} + + {{ n.action }} + {{checkPath(n.action, false)}} {{ n.objectId }} + + {{checkPath(n.action, + false)}} {{ n.objectId }} + {{ n.notification }}{{ n.receiver }} + + + + + +
-
+ \ No newline at end of file diff --git a/src/app/account/notifications/notifications.component.ts b/src/app/account/notifications/notifications.component.ts index 38fc97b7..9cf1d260 100644 --- a/src/app/account/notifications/notifications.component.ts +++ b/src/app/account/notifications/notifications.component.ts @@ -17,12 +17,12 @@ import { SERV } from '../../core/_services/main.config'; @PageTitle(['Notifications']) export class NotificationsComponent implements OnInit { - faTrash=faTrash; - faPlus=faPlus; - faEdit=faEdit; - faEye=faEye; + faTrash = faTrash; + faPlus = faPlus; + faEdit = faEdit; + faEye = faEye; - @ViewChild(DataTableDirective, {static: false}) + @ViewChild(DataTableDirective, { static: false }) dtElement: DataTableDirective; dtTrigger: Subject = new Subject(); @@ -30,24 +30,29 @@ export class NotificationsComponent implements OnInit { constructor( private gs: GlobalService, - ) { } + ) { } - Allnotif: any; + allNotIf: any; private maxResults = environment.config.prodApiMaxResults; ngOnInit(): void { - const params = {'maxResults': this.maxResults}; + const params = { 'maxResults': this.maxResults }; - this.gs.getAll(SERV.NOTIFICATIONS,params).subscribe((notf: any) => { - this.Allnotif = notf.values; + this.gs.getAll(SERV.NOTIFICATIONS, params).subscribe((notf: any) => { + this.allNotIf = notf.values; this.dtTrigger.next(void 0); }); const self = this; this.dtOptions = { dom: 'Bfrtip', - pageLength: 10, + scrollX: true, + pageLength: 25, + lengthMenu: [ + [10, 25, 50, 100, 250, -1], + [10, 25, 50, 100, 250, 'All'] + ], stateSave: true, select: true, buttons: { @@ -56,52 +61,52 @@ export class NotificationsComponent implements OnInit { className: 'dt-button buttons-collection btn btn-sm-dt btn-outline-gray-600-dt', } }, - buttons: [ - { - text: '↻', - autoClose: true, - action: function (e, dt, node, config) { - self.onRefresh(); - } - }, - { - extend: 'collection', - text: 'Export', - buttons: [ - { - extend: 'excelHtml5', - exportOptions: { - columns: [0, 1, 2, 3, 4] + buttons: [ + { + text: '↻', + autoClose: true, + action: function (e, dt, node, config) { + self.onRefresh(); + } + }, + { + extend: 'collection', + text: 'Export', + buttons: [ + { + extend: 'excelHtml5', + exportOptions: { + columns: [0, 1, 2, 3, 4] + }, }, - }, - { - extend: 'print', - exportOptions: { - columns: [0, 1, 2, 3, 4] + { + extend: 'print', + exportOptions: { + columns: [0, 1, 2, 3, 4] + }, + customize: function (win) { + $(win.document.body) + .css('font-size', '10pt') + $(win.document.body).find('table') + .addClass('compact') + .css('font-size', 'inherit'); + } }, - customize: function ( win ) { - $(win.document.body) - .css( 'font-size', '10pt' ) - $(win.document.body).find( 'table' ) - .addClass( 'compact' ) - .css( 'font-size', 'inherit' ); - } - }, - { - extend: 'csvHtml5', - exportOptions: {modifier: {selected: true}}, - select: true, - customize: function (dt, csv) { - let data = ""; - for (let i = 0; i < dt.length; i++) { - data = "Notifications\n\n"+ dt; + { + extend: 'csvHtml5', + exportOptions: { modifier: { selected: true } }, + select: true, + customize: function (dt, csv) { + let data = ""; + for (let i = 0; i < dt.length; i++) { + data = "Notifications\n\n" + dt; + } + return data; } - return data; - } - }, - { - extend: 'copy', - } + }, + { + extend: 'copy', + } ] }, { @@ -114,7 +119,7 @@ export class NotificationsComponent implements OnInit { } - onRefresh(){ + onRefresh() { this.rerender(); this.ngOnInit(); } @@ -134,7 +139,7 @@ export class NotificationsComponent implements OnInit { this.dtTrigger.unsubscribe(); } - onDelete(id: number){ + onDelete(id: number, name: string) { const swalWithBootstrapButtons = Swal.mixin({ customClass: { confirmButton: 'btn', @@ -143,8 +148,7 @@ export class NotificationsComponent implements OnInit { buttonsStyling: false }) Swal.fire({ - title: "Are you sure?", - text: "Once deleted, it can not be recovered!", + title: 'Remove ' + name + ' from your notifications?', icon: "warning", reverseButtons: true, showCancelButton: true, @@ -152,31 +156,32 @@ export class NotificationsComponent implements OnInit { confirmButtonColor: '#C53819', confirmButtonText: 'Yes, delete it!' }) - .then((result) => { - if (result.isConfirmed) { - this.gs.delete(SERV.NOTIFICATIONS,id).subscribe(() => { - Swal.fire({ - title: "Success", - icon: "success", + .then((result) => { + if (result.isConfirmed) { + this.gs.delete(SERV.NOTIFICATIONS, id).subscribe(() => { + Swal.fire({ + position: 'top-end', + backdrop: false, + icon: 'success', + showConfirmButton: false, + timer: 1500 + }) + this.ngOnInit(); + this.rerender(); // rerender datatables + }); + } else { + swalWithBootstrapButtons.fire({ + title: "Cancelled", + text: "Your Notification is safe!", + icon: "error", showConfirmButton: false, timer: 1500 - }); - this.ngOnInit(); - this.rerender(); // rerender datatables - }); - } else { - swalWithBootstrapButtons.fire({ - title: "Cancelled", - text: "Your Notification is safe!", - icon: "error", - showConfirmButton: false, - timer: 1500 - }) - } - }); + }) + } + }); } -checkPath(filter: string, type?: boolean){ + checkPath(filter: string, type?: boolean) { let path; let title; @@ -187,35 +192,35 @@ checkPath(filter: string, type?: boolean){ case ACTION.DELETE_AGENT: title = 'Agent:' path = '/agents/show-agents/'; - break; + break; case ACTION.NEW_TASK: case ACTION.TASK_COMPLETE: case ACTION.DELETE_TASK: title = 'Task:' path = '/tasks/show-tasks/'; - break; + break; case ACTION.DELETE_HASHLIST: case ACTION.HASHLIST_ALL_CRACKED: case ACTION.HASHLIST_CRACKED_HASH: title = 'Hashlist:' path = '/hashlists/hashlist/'; - break; + break; case ACTION.USER_CREATED: case ACTION.USER_DELETED: case ACTION.USER_LOGIN_FAILED: title = 'User:' path = '/users/'; - break; + break; default: title = '' path = 'none'; } - if(type){return path;}else{return title;} + if (type) { return path; } else { return title; } } diff --git a/src/app/spec-helpers/element.spec-helper.ts b/src/app/spec-helpers/element.spec-helper.ts new file mode 100644 index 00000000..55675ad3 --- /dev/null +++ b/src/app/spec-helpers/element.spec-helper.ts @@ -0,0 +1,242 @@ +/* istanbul ignore file */ + +import { DebugElement } from '@angular/core'; +import { ComponentFixture } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; + +/** + * Spec helpers for working with the DOM + */ + +/** + * Returns a selector for the `data-testid` attribute with the given attribute value. + * + * @param testId Test id set by `data-testid` + * + */ +export function testIdSelector(testId: string): string { + return `[data-testid="${testId}"]`; +} + +/** + * Finds a single element inside the Component by the given CSS selector. + * Throws an error if no element was found. + * + * @param fixture Component fixture + * @param selector CSS selector + * + */ +export function queryByCss( + fixture: ComponentFixture, + selector: string, +): DebugElement { + // The return type of DebugElement#query() is declared as DebugElement, + // but the actual return type is DebugElement | null. + // See https://github.com/angular/angular/issues/22449. + const debugElement = fixture.debugElement.query(By.css(selector)); + // Fail on null so the return type is always DebugElement. + if (!debugElement) { + throw new Error(`queryByCss: Element with ${selector} not found`); + } + return debugElement; +} + +/** + * Finds an element inside the Component by the given `data-testid` attribute. + * Throws an error if no element was found. + * + * @param fixture Component fixture + * @param testId Test id set by `data-testid` + * + */ +export function findEl(fixture: ComponentFixture, testId: string): DebugElement { + return queryByCss(fixture, testIdSelector(testId)); +} + +/** + * Finds all elements with the given `data-testid` attribute. + * + * @param fixture Component fixture + * @param testId Test id set by `data-testid` + */ +export function findEls(fixture: ComponentFixture, testId: string): DebugElement[] { + return fixture.debugElement.queryAll(By.css(testIdSelector(testId))); +} + +/** + * Gets the text content of an element with the given `data-testid` attribute. + * + * @param fixture Component fixture + * @param testId Test id set by `data-testid` + */ +export function getText(fixture: ComponentFixture, testId: string): string { + return findEl(fixture, testId).nativeElement.textContent; +} + +/** + * Expects that the element with the given `data-testid` attribute + * has the given text content. + * + * @param fixture Component fixture + * @param testId Test id set by `data-testid` + * @param text Expected text + */ +export function expectText( + fixture: ComponentFixture, + testId: string, + text: string, +): void { + expect(getText(fixture, testId)).toBe(text); +} + +/** + * Expects that the element with the given `data-testid` attribute + * has the given text content. + * + * @param fixture Component fixture + * @param text Expected text + */ +export function expectContainedText(fixture: ComponentFixture, text: string): void { + expect(fixture.nativeElement.textContent).toContain(text); +} + +/** + * Expects that a component has the given text content. + * Both the component text content and the expected text are trimmed for reliability. + * + * @param fixture Component fixture + * @param text Expected text + */ +export function expectContent(fixture: ComponentFixture, text: string): void { + expect(fixture.nativeElement.textContent).toBe(text); +} + +/** + * Dispatches a fake event (synthetic event) at the given element. + * + * @param element Element that is the target of the event + * @param type Event name, e.g. `input` + * @param bubbles Whether the event bubbles up in the DOM tree + */ +export function dispatchFakeEvent( + element: EventTarget, + type: string, + bubbles: boolean = false, +): void { + const event = document.createEvent('Event'); + event.initEvent(type, bubbles, false); + element.dispatchEvent(event); +} + +/** + * Enters text into a form field (`input`, `textarea` or `select` element). + * Triggers appropriate events so Angular takes notice of the change. + * If you listen for the `change` event on `input` or `textarea`, + * you need to trigger it separately. + * + * @param element Form field + * @param value Form field value + */ +export function setFieldElementValue( + element: HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement, + value: string, +): void { + element.value = value; + // Dispatch an `input` or `change` fake event + // so Angular form bindings take notice of the change. + const isSelect = element instanceof HTMLSelectElement; + dispatchFakeEvent(element, isSelect ? 'change' : 'input', isSelect ? false : true); +} + +/** + * Sets the value of a form field with the given `data-testid` attribute. + * + * @param fixture Component fixture + * @param testId Test id set by `data-testid` + * @param value Form field value + */ +export function setFieldValue( + fixture: ComponentFixture, + testId: string, + value: string, +): void { + setFieldElementValue(findEl(fixture, testId).nativeElement, value); +} + +/** + * Checks or unchecks a checkbox or radio button. + * Triggers appropriate events so Angular takes notice of the change. + * + * @param fixture Component fixture + * @param testId Test id set by `data-testid` + * @param checked Whether the checkbox or radio should be checked + */ +export function checkField( + fixture: ComponentFixture, + testId: string, + checked: boolean, +): void { + const { nativeElement } = findEl(fixture, testId); + nativeElement.checked = checked; + // Dispatch a `change` fake event so Angular form bindings take notice of the change. + dispatchFakeEvent(nativeElement, 'change'); +} + +/** + * Makes a fake click event that provides the most important properties. + * Sets the button to left. + * The event can be passed to DebugElement#triggerEventHandler. + * + * @param target Element that is the target of the click event + */ +export function makeClickEvent(target: EventTarget): Partial { + return { + preventDefault(): void { }, + stopPropagation(): void { }, + stopImmediatePropagation(): void { }, + type: 'click', + target, + currentTarget: target, + bubbles: true, + cancelable: true, + button: 0 + }; +} + +/** + * Emulates a left click on the element with the given `data-testid` attribute. + * + * @param fixture Component fixture + * @param testId Test id set by `data-testid` + */ +export function click(fixture: ComponentFixture, testId: string): void { + const element = findEl(fixture, testId); + const event = makeClickEvent(element.nativeElement); + element.triggerEventHandler('click', event); +} + +/** + * Finds a nested Component by its selector, e.g. `app-example`. + * Throws an error if no element was found. + * Use this only for shallow component testing. + * When finding other elements, use `findEl` / `findEls` and `data-testid` attributes. + * + * @param fixture Fixture of the parent Component + * @param selector Element selector, e.g. `app-example` + */ +export function findComponent( + fixture: ComponentFixture, + selector: string, +): DebugElement { + return queryByCss(fixture, selector); +} + +/** + * Finds all nested Components by its selector, e.g. `app-example`. + */ +export function findComponents( + fixture: ComponentFixture, + selector: string, +): DebugElement[] { + return fixture.debugElement.queryAll(By.css(selector)); +} From 83328103d1c63a801fb867f705dea959628f2213 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Fri, 6 Oct 2023 14:14:45 +0200 Subject: [PATCH 062/419] Add puppeteer to handle chrome browser --- karma.conf.js | 8 ++------ package-lock.json | 2 +- package.json | 4 ++-- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/karma.conf.js b/karma.conf.js index fba1c543..e003a24a 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -1,5 +1,7 @@ // Karma configuration file, see link for more information // https://karma-runner.github.io/1.0/config/configuration-file.html +process.env.CHROME_BIN = require('puppeteer').executablePath() + module.exports = function (config) { config.set({ @@ -41,11 +43,5 @@ module.exports = function (config) { browsers: ['ChromeHeadless'], singleRun: false, restartOnFileChange: true, - customLaunchers: { - ChromeHeadlessCustom: { - base: 'ChromeHeadless', - flags: ['--no-sandbox', '--disable-gpu'] - } - }, }); }; diff --git a/package-lock.json b/package-lock.json index fc6a13e6..ab07cd41 100644 --- a/package-lock.json +++ b/package-lock.json @@ -90,7 +90,7 @@ "karma-jasmine": "~5.1.0", "karma-jasmine-html-reporter": "~2.1.0", "nodemon": "^3.0.1", - "puppeteer": "^20.8.0", + "puppeteer": "^20.9.0", "typescript": "~5.1.6" } }, diff --git a/package.json b/package.json index 1d7ba4c3..e98f6c1d 100644 --- a/package.json +++ b/package.json @@ -95,7 +95,7 @@ "karma-jasmine": "~5.1.0", "karma-jasmine-html-reporter": "~2.1.0", "nodemon": "^3.0.1", - "puppeteer": "^20.8.0", + "puppeteer": "^20.9.0", "typescript": "~5.1.6" } -} \ No newline at end of file +} From becca53227a84d4571827ec4e9189c8007f1b4bd Mon Sep 17 00:00:00 2001 From: itbf9 Date: Sat, 7 Oct 2023 10:54:18 +0200 Subject: [PATCH 063/419] Use different subheaders and btnlabel for create vs edit. Disable submit button if nothing has changed or form is invalid --- .../edit-notification.component.ts | 82 ++++++++++--------- .../new-notification.component.html | 4 +- .../new-notification.component.ts | 14 +++- .../notifications/notifications.component.ts | 40 +++++---- 4 files changed, 73 insertions(+), 67 deletions(-) diff --git a/src/app/account/notifications/notification/edit-notification.component.ts b/src/app/account/notifications/notification/edit-notification.component.ts index 8789f180..d78f0397 100644 --- a/src/app/account/notifications/notification/edit-notification.component.ts +++ b/src/app/account/notifications/notification/edit-notification.component.ts @@ -1,39 +1,40 @@ +import { Subscription } from 'rxjs'; import { FormControl, FormGroup } from '@angular/forms'; -import { Component, OnInit } from '@angular/core'; +import { Component, OnDestroy, OnInit } from '@angular/core'; import Swal from 'sweetalert2/dist/sweetalert2.js'; import { ACTIONARRAY, NOTIFARRAY } from '../../../core/_constants/notifications.config'; import { GlobalService } from 'src/app/core/_services/main.service'; -import { environment } from '../../../../environments/environment'; import { ActivatedRoute, Params, Router } from '@angular/router'; -import { PageTitle } from 'src/app/core/_decorators/autotitle'; import { SERV } from '../../../core/_services/main.config'; +import { AutoTitleService } from 'src/app/core/_services/shared/autotitle.service'; @Component({ selector: 'app-edit-notification', templateUrl: './new-notification.component.html' }) -@PageTitle(['Edit Notification']) -export class EditNotificationComponent implements OnInit { +export class EditNotificationComponent implements OnInit, OnDestroy { editedIndex: number; editView = true; - - constructor( - private route: ActivatedRoute, - private gs: GlobalService, - private router: Router - ) { } - + subscriptions: Subscription[] = [] value: any; active = true; - allowedActions = ACTIONARRAY; - notifications = NOTIFARRAY; + submitLabel = 'Save Changes' + subTitle = 'Edit Notification' + oldValue: boolean - private maxResults = environment.config.prodApiMaxResults; + constructor( + private route: ActivatedRoute, + private gs: GlobalService, + private titleService: AutoTitleService, + private router: Router + ) { + titleService.set(['Edit Notification']) + } form = new FormGroup({ 'action': new FormControl({ value: '', disabled: true }), @@ -44,40 +45,46 @@ export class EditNotificationComponent implements OnInit { }); ngOnInit(): void { - - this.route.params - .subscribe( - (params: Params) => { - this.editedIndex = +params['id']; - this.initForm(); - } - ); - + this.route.params.subscribe((params: Params) => { + this.editedIndex = +params['id']; + this.createForm(); + }); } - changeAction(action: string): void { + ngOnDestroy(): void { + for (const sub of this.subscriptions) { + sub.unsubscribe() + } } - private initForm() { + // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function + changeAction(_action: string): void { } + + formIsValid(): boolean { + const oldVal = !this.oldValue ? false : this.oldValue + const newVal = !this.form.value.isActive ? false : this.form.value.isActive + + return oldVal !== newVal + } - this.gs.get(SERV.NOTIFICATIONS, this.editedIndex).subscribe((result) => { + private createForm() { + this.subscriptions.push(this.gs.get(SERV.NOTIFICATIONS, this.editedIndex).subscribe((result) => { + const isActive = result['isActive'] + this.oldValue = isActive this.form = new FormGroup({ 'action': new FormControl({ value: result['action'], disabled: true }), 'actionFilter': new FormControl({ value: result['objectId'], disabled: true }), 'notification': new FormControl({ value: result['notification'], disabled: true }), 'receiver': new FormControl({ value: result['receiver'], disabled: true }), - 'isActive': new FormControl(result['isActive']), + 'isActive': new FormControl(isActive), }); - }); + })); } - onQueryp(val) { } - onSubmit() { if (this.form.valid) { - - this.gs.update(SERV.NOTIFICATIONS, this.editedIndex, { 'isActive': this.form.value['isActive'] }).subscribe(() => { + this.subscriptions.push(this.gs.update(SERV.NOTIFICATIONS, this.editedIndex, { 'isActive': this.form.value['isActive'] }).subscribe(() => { Swal.fire({ position: 'top-end', backdrop: false, @@ -86,13 +93,8 @@ export class EditNotificationComponent implements OnInit { showConfirmButton: false, timer: 1500 }) - this.ngOnInit(); this.router.navigate(['/account/notifications']); - } - ); + })); } } - -} - - +} \ No newline at end of file diff --git a/src/app/account/notifications/notification/new-notification.component.html b/src/app/account/notifications/notification/new-notification.component.html index 2d240eed..9d07bafb 100644 --- a/src/app/account/notifications/notification/new-notification.component.html +++ b/src/app/account/notifications/notification/new-notification.component.html @@ -1,5 +1,5 @@ - +
@@ -33,7 +33,7 @@ data-testid="input-is-active">
- +
\ No newline at end of file diff --git a/src/app/account/notifications/notification/new-notification.component.ts b/src/app/account/notifications/notification/new-notification.component.ts index e9107b02..7c6f7715 100644 --- a/src/app/account/notifications/notification/new-notification.component.ts +++ b/src/app/account/notifications/notification/new-notification.component.ts @@ -24,13 +24,16 @@ export class NewNotificationComponent implements OnInit, OnDestroy { allowedActions = ACTIONARRAY; notifications = NOTIFARRAY; subscriptions: Subscription[] = [] + submitLabel = 'Save Notification' + subTitle = 'Create Notification' maxResults = environment.config.prodApiMaxResults; constructor( private gs: GlobalService, private titleService: AutoTitleService, - private router: Router) { + private router: Router + ) { titleService.set(['New Notification']) } @@ -86,6 +89,11 @@ export class NewNotificationComponent implements OnInit, OnDestroy { } + + formIsValid(): boolean { + return this.form.valid + } + checkPath(filter: string) { let path: string; @@ -170,6 +178,4 @@ export class NewNotificationComponent implements OnInit, OnDestroy { })); } } -} - - +} \ No newline at end of file diff --git a/src/app/account/notifications/notifications.component.ts b/src/app/account/notifications/notifications.component.ts index 9cf1d260..26a0f1a7 100644 --- a/src/app/account/notifications/notifications.component.ts +++ b/src/app/account/notifications/notifications.component.ts @@ -1,6 +1,6 @@ import { faTrash, faPlus, faEye, faEdit } from '@fortawesome/free-solid-svg-icons'; import { environment } from './../../../environments/environment'; -import { Component, OnInit, ViewChild } from '@angular/core'; +import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core'; import { DataTableDirective } from 'angular-datatables'; import Swal from 'sweetalert2/dist/sweetalert2.js'; import { Subject } from 'rxjs'; @@ -15,7 +15,7 @@ import { SERV } from '../../core/_services/main.config'; templateUrl: './notifications.component.html' }) @PageTitle(['Notifications']) -export class NotificationsComponent implements OnInit { +export class NotificationsComponent implements OnInit, OnDestroy { faTrash = faTrash; faPlus = faPlus; @@ -28,9 +28,7 @@ export class NotificationsComponent implements OnInit { dtTrigger: Subject = new Subject(); dtOptions: any = {}; - constructor( - private gs: GlobalService, - ) { } + constructor(private gs: GlobalService) { } allNotIf: any; @@ -130,7 +128,9 @@ export class NotificationsComponent implements OnInit { dtInstance.destroy(); // Call the dtTrigger to rerender again setTimeout(() => { - this.dtTrigger['new'].next(); + if (this.dtTrigger['new']) { + this.dtTrigger['new'].next(); + } }); }); } @@ -147,15 +147,16 @@ export class NotificationsComponent implements OnInit { }, buttonsStyling: false }) - Swal.fire({ - title: 'Remove ' + name + ' from your notifications?', - icon: "warning", - reverseButtons: true, - showCancelButton: true, - cancelButtonColor: '#8A8584', - confirmButtonColor: '#C53819', - confirmButtonText: 'Yes, delete it!' - }) + Swal + .fire({ + title: 'Remove ' + name + ' from your notifications?', + icon: "warning", + reverseButtons: true, + showCancelButton: true, + cancelButtonColor: '#8A8584', + confirmButtonColor: '#C53819', + confirmButtonText: 'Yes, delete it!' + }) .then((result) => { if (result.isConfirmed) { this.gs.delete(SERV.NOTIFICATIONS, id).subscribe(() => { @@ -182,11 +183,10 @@ export class NotificationsComponent implements OnInit { } checkPath(filter: string, type?: boolean) { + let path: string; + let title: string; - let path; - let title; switch (filter) { - case ACTION.AGENT_ERROR: case ACTION.OWN_AGENT_ERROR: case ACTION.DELETE_AGENT: @@ -221,7 +221,5 @@ export class NotificationsComponent implements OnInit { } if (type) { return path; } else { return title; } - } - -} +} \ No newline at end of file From 4a6cf348429b217f7c1b30ad2c447fcdbe93bc90 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Sat, 7 Oct 2023 10:54:51 +0200 Subject: [PATCH 064/419] Add tests for edit notification form --- .../edit-notification.component.spec.ts | 151 ++++++++++++++++++ 1 file changed, 151 insertions(+) create mode 100644 src/app/account/notifications/notification/edit-notification.component.spec.ts diff --git a/src/app/account/notifications/notification/edit-notification.component.spec.ts b/src/app/account/notifications/notification/edit-notification.component.spec.ts new file mode 100644 index 00000000..96b305a5 --- /dev/null +++ b/src/app/account/notifications/notification/edit-notification.component.spec.ts @@ -0,0 +1,151 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { CommonModule } from '@angular/common'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { GlobalService } from 'src/app/core/_services/main.service'; +import { Observable, of } from 'rxjs'; +import { ActivatedRoute, Params, Router, RouterModule } from '@angular/router'; +import { HttpClientModule } from '@angular/common/http'; +import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; +import { DataTablesModule } from 'angular-datatables'; +import { ComponentsModule } from 'src/app/shared/components.module'; +import { PipesModule } from 'src/app/shared/pipes.module'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { findEl, setFieldValue } from 'src/app/spec-helpers/element.spec-helper'; +import { ACTION, NOTIF } from 'src/app/core/_constants/notifications.config'; +import { DebugElement } from '@angular/core'; +import { SERV } from 'src/app/core/_services/main.config'; +import { By } from '@angular/platform-browser'; +import { EditNotificationComponent } from './edit-notification.component'; + +describe('EditNotificationComponent', () => { + let component: EditNotificationComponent; + let fixture: ComponentFixture; + + const nodificationResponse = { + "_expandable": "user", + "_id": 1, + "_self": "/api/v2/ui/notifications/1", + "action": "logError", + "isActive": true, + "notification": "ChatBot", + "notificationSettingId": 1, + "objectId": null, + "receiver": "asdasdasd", + "userId": 1 + } + + const mockService: Partial = { + // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any + get(_methodUrl: string, id: number, _routerParams?: Params): Observable { + return of([]) + } + } + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ + CommonModule, + FormsModule, + ReactiveFormsModule, + HttpClientModule, + FontAwesomeModule, + DataTablesModule, + ComponentsModule, + RouterModule, + PipesModule, + NgbModule, + ], + declarations: [ + EditNotificationComponent + ], + providers: [ + { + provide: GlobalService, + useValue: mockService + }, + { + provide: ActivatedRoute, + useValue: { + params: of({ + id: 1, + }), + }, + }, + ] + }).compileComponents(); + + fixture = TestBed.createComponent(EditNotificationComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should not allow action to be edited', () => { + spyOn(mockService, 'get') + .withArgs(SERV.NOTIFICATIONS, 1) + .and.returnValue(of(nodificationResponse)) + + expectFieldToBeDisabled('select-action') + }); + + it('should not allow action-filter to be edited', () => { + spyOn(mockService, 'get') + .withArgs(SERV.NOTIFICATIONS, 1) + .and.returnValue(of(nodificationResponse)) + + expectFieldToBeDisabled('select-action-filter') + }); + + it('should not allow notification to be edited', () => { + spyOn(mockService, 'get') + .withArgs(SERV.NOTIFICATIONS, 1) + .and.returnValue(of(nodificationResponse)) + + expectFieldToBeDisabled('select-notification') + }); + + it('should not allow receiver to be edited', () => { + spyOn(mockService, 'get') + .withArgs(SERV.NOTIFICATIONS, 1) + .and.returnValue(of(nodificationResponse)) + + expectFieldToBeDisabled('input-receiver') + }); + + it('should allow status to be edited', () => { + spyOn(mockService, 'get') + .withArgs(SERV.NOTIFICATIONS, 1) + .and.returnValue(of(nodificationResponse)) + + expectFieldToBeEnabled('input-is-active') + }); + + it('should not allow form to be submitted when nothing has changed', () => { + spyOn(mockService, 'get') + .withArgs(SERV.NOTIFICATIONS, 1) + .and.returnValue(of(nodificationResponse)) + + expectButtonToBeDisabled() + }); + + + const expectFieldToBeDisabled = (testId: string): void => { + const field: DebugElement = findEl(fixture, testId) + expect(field.nativeElement.disabled).toEqual(true) + } + + const expectFieldToBeEnabled = (testId: string): void => { + const field: DebugElement = findEl(fixture, testId) + expect(field.nativeElement.disabled).toEqual(false) + } + + const expectButtonToBeDisabled = (): void => { + const btn: DebugElement = findEl(fixture, 'button-create') + expect(btn.nativeElement.attributes.getNamedItem('ng-reflect-disabled').value).toEqual('true') + } + + const expectButtonToBeEnabled = (): void => { + const btn: DebugElement = findEl(fixture, 'button-create') + expect(btn.nativeElement.attributes.getNamedItem('ng-reflect-disabled').value).toEqual('false') + } + +}); \ No newline at end of file From a97468fc6ce06b88efbe87a98f483856b436bd7b Mon Sep 17 00:00:00 2001 From: itbf9 Date: Sat, 7 Oct 2023 17:44:23 +0200 Subject: [PATCH 065/419] Add notification models --- src/app/core/_models/notifications.ts | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 src/app/core/_models/notifications.ts diff --git a/src/app/core/_models/notifications.ts b/src/app/core/_models/notifications.ts new file mode 100644 index 00000000..9ae59c76 --- /dev/null +++ b/src/app/core/_models/notifications.ts @@ -0,0 +1,20 @@ +export interface NotificationListResponse { + _expandable: string, + startAt: number, + maxResults: number, + total: number, + isLast: boolean, + values: Notification[] +} + +export interface Notification { + _id: number, + _self: string, + action: string, + isActive: boolean, + notification: string, + receiver: string, + userId: number, + notificationSettingId?: number, + objectId?: number, +} \ No newline at end of file From 9c1e83637677e857363a2bbaa5b199a6be40831c Mon Sep 17 00:00:00 2001 From: itbf9 Date: Sat, 7 Oct 2023 17:48:22 +0200 Subject: [PATCH 066/419] refactor notification component + add comments --- .../notifications.component.html | 2 +- .../notifications/notifications.component.ts | 142 ++++++++++++------ 2 files changed, 101 insertions(+), 43 deletions(-) diff --git a/src/app/account/notifications/notifications.component.html b/src/app/account/notifications/notifications.component.html index 37ae3678..99437bc7 100644 --- a/src/app/account/notifications/notifications.component.html +++ b/src/app/account/notifications/notifications.component.html @@ -16,7 +16,7 @@
{{ n.notificationSettingId }} = new Subject(); dtOptions: any = {}; - constructor(private gs: GlobalService) { } + // List of notifications + notifications: Notification[]; - allNotIf: any; + // Subscriptions to unsubscribe on component destruction + subscriptions: Subscription[] = [] + + @ViewChild(DataTableDirective, { static: false }) private maxResults = environment.config.prodApiMaxResults; + constructor(private gs: GlobalService) { } + + /** + * Initializes DataTable and retrieves notifications. + */ ngOnInit(): void { + this.getNotifications(); + this.setupTable(); + } + + /** + * Unsubscribes from active subscriptions. + */ + ngOnDestroy(): void { + this.dtTrigger.unsubscribe(); + for (const sub of this.subscriptions) { + sub.unsubscribe(); + } + } + + /** + * Refreshes the data table by re-rendering it. + * Fetches notifications and sets up the table again. + */ + onRefresh() { + this.rerender(); + this.getNotifications(); + this.setupTable(); + } + + /** + * Rerenders the DataTable instance. + * Destroys and recreates the DataTable to reflect changes. + */ + rerender(): void { + this.dtElement.dtInstance.then((dtInstance: DataTables.Api) => { + // Destroy the table first + dtInstance.destroy(); + // Call the dtTrigger to rerender again + setTimeout(() => { + if (this.dtTrigger['new']) { + this.dtTrigger['new'].next(); + } + }); + }); + } + /** + * Fetches notifications from the server. + * Subscribes to the API response and updates the notifications list. + */ + getNotifications(): void { const params = { 'maxResults': this.maxResults }; - this.gs.getAll(SERV.NOTIFICATIONS, params).subscribe((notf: any) => { - this.allNotIf = notf.values; + this.subscriptions.push(this.gs.getAll(SERV.NOTIFICATIONS, params).subscribe((response: NotificationListResponse) => { + this.notifications = response.values; this.dtTrigger.next(void 0); - }); + })); + } + + /** + * Sets up the DataTable options and buttons. + * Customizes DataTable appearance and behavior. + */ + setupTable(): void { const self = this; this.dtOptions = { dom: 'Bfrtip', @@ -95,9 +162,9 @@ export class NotificationsComponent implements OnInit, OnDestroy { exportOptions: { modifier: { selected: true } }, select: true, customize: function (dt, csv) { - let data = ""; + let data = ''; for (let i = 0; i < dt.length; i++) { - data = "Notifications\n\n" + dt; + data = 'Notifications\n\n' + dt; } return data; } @@ -108,37 +175,21 @@ export class NotificationsComponent implements OnInit, OnDestroy { ] }, { - extend: "pageLength", - className: "btn-sm" + extend: 'pageLength', + className: 'btn-sm' } ], } } - - } - - onRefresh() { - this.rerender(); - this.ngOnInit(); - } - - rerender(): void { - this.dtElement.dtInstance.then((dtInstance: DataTables.Api) => { - // Destroy the table first - dtInstance.destroy(); - // Call the dtTrigger to rerender again - setTimeout(() => { - if (this.dtTrigger['new']) { - this.dtTrigger['new'].next(); - } - }); - }); - } - - ngOnDestroy(): void { - this.dtTrigger.unsubscribe(); } + /** + * Handles notification deletion. + * Displays a confirmation dialog and deletes the notification if confirmed. + * + * @param {number} id - The ID of the notification to delete. + * @param {string} name - The name of the notification. + */ onDelete(id: number, name: string) { const swalWithBootstrapButtons = Swal.mixin({ customClass: { @@ -149,8 +200,8 @@ export class NotificationsComponent implements OnInit, OnDestroy { }) Swal .fire({ - title: 'Remove ' + name + ' from your notifications?', - icon: "warning", + title: `Remove ${name} from your notifications?`, + icon: 'warning', reverseButtons: true, showCancelButton: true, cancelButtonColor: '#8A8584', @@ -172,9 +223,9 @@ export class NotificationsComponent implements OnInit, OnDestroy { }); } else { swalWithBootstrapButtons.fire({ - title: "Cancelled", - text: "Your Notification is safe!", - icon: "error", + title: 'Cancelled', + text: 'Your Notification is safe!', + icon: 'error', showConfirmButton: false, timer: 1500 }) @@ -182,7 +233,14 @@ export class NotificationsComponent implements OnInit, OnDestroy { }); } - checkPath(filter: string, type?: boolean) { + /** + * Determines the path or title based on the provided filter. + * + * @param {string} filter - The filter used to determine the path or title. + * @param {boolean} [type] - If true, returns the path; otherwise, returns the title. + * @returns {string} The path or title based on the filter. + */ + checkPath(filter: string, type?: boolean): string { let path: string; let title: string; From 3e6713ff6e8cbb554762f9cc0ceb6154f474c0c4 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Sat, 7 Oct 2023 17:49:11 +0200 Subject: [PATCH 067/419] no need for type when we have default value --- src/app/spec-helpers/element.spec-helper.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/spec-helpers/element.spec-helper.ts b/src/app/spec-helpers/element.spec-helper.ts index 55675ad3..b020b7e3 100644 --- a/src/app/spec-helpers/element.spec-helper.ts +++ b/src/app/spec-helpers/element.spec-helper.ts @@ -121,7 +121,7 @@ export function expectContent(fixture: ComponentFixture, text: string): vo export function dispatchFakeEvent( element: EventTarget, type: string, - bubbles: boolean = false, + bubbles = false, ): void { const event = document.createEvent('Event'); event.initEvent(type, bubbles, false); From c1453efc113101b4bd4e4680e71038da41fb7c46 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Sat, 7 Oct 2023 17:49:49 +0200 Subject: [PATCH 068/419] Add dockstrings + extend tests --- .../edit-notification.component.spec.ts | 77 ++++++-- .../edit-notification.component.ts | 72 ++++--- .../new-notification.component.html | 2 +- .../new-notification.component.spec.ts | 64 +++++-- .../new-notification.component.ts | 179 ++++++++---------- 5 files changed, 236 insertions(+), 158 deletions(-) diff --git a/src/app/account/notifications/notification/edit-notification.component.spec.ts b/src/app/account/notifications/notification/edit-notification.component.spec.ts index 96b305a5..81029a67 100644 --- a/src/app/account/notifications/notification/edit-notification.component.spec.ts +++ b/src/app/account/notifications/notification/edit-notification.component.spec.ts @@ -3,41 +3,48 @@ import { CommonModule } from '@angular/common'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { GlobalService } from 'src/app/core/_services/main.service'; import { Observable, of } from 'rxjs'; -import { ActivatedRoute, Params, Router, RouterModule } from '@angular/router'; +import { ActivatedRoute, Params, RouterModule } from '@angular/router'; import { HttpClientModule } from '@angular/common/http'; import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; import { DataTablesModule } from 'angular-datatables'; import { ComponentsModule } from 'src/app/shared/components.module'; import { PipesModule } from 'src/app/shared/pipes.module'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; -import { findEl, setFieldValue } from 'src/app/spec-helpers/element.spec-helper'; -import { ACTION, NOTIF } from 'src/app/core/_constants/notifications.config'; +import { checkField, findEl } from 'src/app/spec-helpers/element.spec-helper'; import { DebugElement } from '@angular/core'; import { SERV } from 'src/app/core/_services/main.config'; -import { By } from '@angular/platform-browser'; import { EditNotificationComponent } from './edit-notification.component'; + describe('EditNotificationComponent', () => { let component: EditNotificationComponent; let fixture: ComponentFixture; + // Define a mock response for the notification const nodificationResponse = { - "_expandable": "user", - "_id": 1, - "_self": "/api/v2/ui/notifications/1", - "action": "logError", - "isActive": true, - "notification": "ChatBot", - "notificationSettingId": 1, - "objectId": null, - "receiver": "asdasdasd", - "userId": 1 + '_expandable': 'user', + '_id': 1, + '_self': '/api/v2/ui/notifications/1', + 'action': 'logError', + 'isActive': false, + 'notification': 'ChatBot', + 'notificationSettingId': 1, + 'objectId': null, + 'receiver': 'asdasdasd', + 'userId': 1 } + // Define a partial mock service to simulate service calls. const mockService: Partial = { + // Simulate the 'get' method to return an empty observable. // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any - get(_methodUrl: string, id: number, _routerParams?: Params): Observable { + get(_methodUrl: string, _id: number, _routerParams?: Params): Observable { return of([]) + }, + // Simulate the 'update' method to return an empty observable. + // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any + update(_methodUrl: string, _id: number, _object: any): Observable { + return of({}) } } @@ -79,6 +86,11 @@ describe('EditNotificationComponent', () => { fixture.detectChanges(); }); + + // --- Test Methods --- + + + // Check if the 'action' field is disabled and cannot be edited. it('should not allow action to be edited', () => { spyOn(mockService, 'get') .withArgs(SERV.NOTIFICATIONS, 1) @@ -87,6 +99,7 @@ describe('EditNotificationComponent', () => { expectFieldToBeDisabled('select-action') }); + // Check if the 'action-filter' field is disabled and cannot be edited. it('should not allow action-filter to be edited', () => { spyOn(mockService, 'get') .withArgs(SERV.NOTIFICATIONS, 1) @@ -95,6 +108,7 @@ describe('EditNotificationComponent', () => { expectFieldToBeDisabled('select-action-filter') }); + // Check if the 'notification' field is disabled and cannot be edited. it('should not allow notification to be edited', () => { spyOn(mockService, 'get') .withArgs(SERV.NOTIFICATIONS, 1) @@ -103,6 +117,7 @@ describe('EditNotificationComponent', () => { expectFieldToBeDisabled('select-notification') }); + // Check if the 'receiver' field is disabled and cannot be edited. it('should not allow receiver to be edited', () => { spyOn(mockService, 'get') .withArgs(SERV.NOTIFICATIONS, 1) @@ -111,6 +126,7 @@ describe('EditNotificationComponent', () => { expectFieldToBeDisabled('input-receiver') }); + // Check if the 'status' field is enabled and can be edited. it('should allow status to be edited', () => { spyOn(mockService, 'get') .withArgs(SERV.NOTIFICATIONS, 1) @@ -119,6 +135,7 @@ describe('EditNotificationComponent', () => { expectFieldToBeEnabled('input-is-active') }); + // Check if the form is disabled and cannot be submitted when nothing has changed. it('should not allow form to be submitted when nothing has changed', () => { spyOn(mockService, 'get') .withArgs(SERV.NOTIFICATIONS, 1) @@ -127,6 +144,36 @@ describe('EditNotificationComponent', () => { expectButtonToBeDisabled() }); + // Check if the form is enabled and can be submitted when the status has changed. + it('should allow form to be submitted when status has changed', () => { + spyOn(mockService, 'get') + .withArgs(SERV.NOTIFICATIONS, 1) + .and.returnValue(of(nodificationResponse)) + + checkField(fixture, 'input-is-active', true) + fixture.detectChanges(); + + expectButtonToBeEnabled() + }); + + // Check if the form is submitted when it is valid and button is clicked. + it('should submit the form when it is valid', () => { + const serviceSpy = spyOn(mockService, 'update') + .withArgs(SERV.NOTIFICATIONS, 1, jasmine.any(Object)) + .and.returnValue(of({})); + + checkField(fixture, 'input-is-active', true) + fixture.detectChanges(); + + const submitButton: DebugElement = findEl(fixture, 'button-create'); + submitButton.nativeElement.querySelector('button').click(); + + expect(serviceSpy).toHaveBeenCalledWith(SERV.NOTIFICATIONS, 1, jasmine.any(Object)); + }); + + + // --- Helper functions --- + const expectFieldToBeDisabled = (testId: string): void => { const field: DebugElement = findEl(fixture, testId) diff --git a/src/app/account/notifications/notification/edit-notification.component.ts b/src/app/account/notifications/notification/edit-notification.component.ts index d78f0397..4f551ea4 100644 --- a/src/app/account/notifications/notification/edit-notification.component.ts +++ b/src/app/account/notifications/notification/edit-notification.component.ts @@ -2,12 +2,13 @@ import { Subscription } from 'rxjs'; import { FormControl, FormGroup } from '@angular/forms'; import { Component, OnDestroy, OnInit } from '@angular/core'; import Swal from 'sweetalert2/dist/sweetalert2.js'; - import { ACTIONARRAY, NOTIFARRAY } from '../../../core/_constants/notifications.config'; import { GlobalService } from 'src/app/core/_services/main.service'; import { ActivatedRoute, Params, Router } from '@angular/router'; import { SERV } from '../../../core/_services/main.config'; import { AutoTitleService } from 'src/app/core/_services/shared/autotitle.service'; +import { Filter } from '../notifications.component'; +import { Notification } from 'src/app/core/_models/notifications'; @Component({ @@ -16,16 +17,27 @@ import { AutoTitleService } from 'src/app/core/_services/shared/autotitle.servic }) export class EditNotificationComponent implements OnInit, OnDestroy { + static readonly SUBMITLABEL = 'Save Changes' + static readonly SUBTITLE = 'Edit Notification' + editedIndex: number; editView = true; subscriptions: Subscription[] = [] - value: any; + filters: Filter[]; active = true; allowedActions = ACTIONARRAY; notifications = NOTIFARRAY; - submitLabel = 'Save Changes' - subTitle = 'Edit Notification' oldValue: boolean + subTitle = EditNotificationComponent.SUBTITLE + submitLabel = EditNotificationComponent.SUBMITLABEL + + form = new FormGroup({ + 'action': new FormControl({ value: '', disabled: true }), + 'actionFilter': new FormControl({ value: '', disabled: true }), + 'notification': new FormControl({ value: '', disabled: true }), + 'receiver': new FormControl({ value: '', disabled: true }), + 'isActive': new FormControl(), + }); constructor( private route: ActivatedRoute, @@ -36,14 +48,9 @@ export class EditNotificationComponent implements OnInit, OnDestroy { titleService.set(['Edit Notification']) } - form = new FormGroup({ - 'action': new FormControl({ value: '', disabled: true }), - 'actionFilter': new FormControl({ value: '', disabled: true }), - 'notification': new FormControl({ value: '', disabled: true }), - 'receiver': new FormControl({ value: '', disabled: true }), - 'isActive': new FormControl(), - }); - + /** + * Subscribes to route parameters and initializes the form. + */ ngOnInit(): void { this.route.params.subscribe((params: Params) => { this.editedIndex = +params['id']; @@ -51,6 +58,9 @@ export class EditNotificationComponent implements OnInit, OnDestroy { }); } + /** + * Unsubscribes from all subscriptions to prevent memory leaks. + */ ngOnDestroy(): void { for (const sub of this.subscriptions) { sub.unsubscribe() @@ -60,36 +70,46 @@ export class EditNotificationComponent implements OnInit, OnDestroy { // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function changeAction(_action: string): void { } - formIsValid(): boolean { - const oldVal = !this.oldValue ? false : this.oldValue - const newVal = !this.form.value.isActive ? false : this.form.value.isActive - return oldVal !== newVal + /** + * Checks whether the form is valid for submission. + * + * @returns {boolean} True if there is a change in the 'isActive' value; otherwise, false. + */ + formIsValid(): boolean { + return Boolean(this.oldValue).valueOf() !== Boolean(this.form.value.isActive).valueOf(); } - private createForm() { - this.subscriptions.push(this.gs.get(SERV.NOTIFICATIONS, this.editedIndex).subscribe((result) => { - const isActive = result['isActive'] + /** + * Private method to create the form for editing a notification. + * Subscribes to a service to fetch notification data and populate the form. + */ + private createForm(): void { + this.subscriptions.push(this.gs.get(SERV.NOTIFICATIONS, this.editedIndex).subscribe((result: Notification) => { + const isActive = result.isActive this.oldValue = isActive this.form = new FormGroup({ - 'action': new FormControl({ value: result['action'], disabled: true }), - 'actionFilter': new FormControl({ value: result['objectId'], disabled: true }), - 'notification': new FormControl({ value: result['notification'], disabled: true }), - 'receiver': new FormControl({ value: result['receiver'], disabled: true }), + 'action': new FormControl({ value: result.action, disabled: true }), + 'actionFilter': new FormControl({ value: result.objectId + '', disabled: true }), + 'notification': new FormControl({ value: result.notification, disabled: true }), + 'receiver': new FormControl({ value: result.receiver, disabled: true }), 'isActive': new FormControl(isActive), }); })); - } - onSubmit() { + /** + * Handles the form submission when the user saves changes to a notification. + * Sends an update request to the server and navigates to the notifications page on success. + */ + onSubmit(): void { if (this.form.valid) { this.subscriptions.push(this.gs.update(SERV.NOTIFICATIONS, this.editedIndex, { 'isActive': this.form.value['isActive'] }).subscribe(() => { Swal.fire({ position: 'top-end', backdrop: false, icon: 'success', - title: "Saved", + title: 'Saved', showConfirmButton: false, timer: 1500 }) diff --git a/src/app/account/notifications/notification/new-notification.component.html b/src/app/account/notifications/notification/new-notification.component.html index 9d07bafb..75e0df2a 100644 --- a/src/app/account/notifications/notification/new-notification.component.html +++ b/src/app/account/notifications/notification/new-notification.component.html @@ -15,7 +15,7 @@ data-testid="select-action-filter"> - + diff --git a/src/app/account/notifications/notification/new-notification.component.spec.ts b/src/app/account/notifications/notification/new-notification.component.spec.ts index dd02d898..9676b2f7 100644 --- a/src/app/account/notifications/notification/new-notification.component.spec.ts +++ b/src/app/account/notifications/notification/new-notification.component.spec.ts @@ -21,6 +21,7 @@ describe('NewNotificationComponent', () => { let component: NewNotificationComponent; let fixture: ComponentFixture; + // Define sample data for agent, task, hashlist, and user values. const agentValues = [ { _id: '1', agentName: 'Agent 1' }, { _id: '2', agentName: 'Agent 2' }, @@ -28,7 +29,6 @@ describe('NewNotificationComponent', () => { { _id: '4', agentName: 'Agent 4' }, { _id: '5', agentName: 'Agent 5' }, ] - const taskValues = [ { _id: '1', taskName: 'Task 1' }, { _id: '2', taskName: 'Task 2' }, @@ -36,7 +36,6 @@ describe('NewNotificationComponent', () => { { _id: '4', taskName: 'Task 4' }, { _id: '5', taskName: 'Task 5' }, ] - const hashlistValues = [ { _id: '1', name: 'Hashlist 1' }, { _id: '2', name: 'Hashlist 2' }, @@ -44,7 +43,6 @@ describe('NewNotificationComponent', () => { { _id: '4', name: 'Hashlist 4' }, { _id: '5', name: 'Hashlist 5' }, ] - const userValues = [ { _id: '1', name: 'User 1' }, { _id: '2', name: 'User 2' }, @@ -53,9 +51,17 @@ describe('NewNotificationComponent', () => { { _id: '5', name: 'User 5' }, ] + // Define a partial mock service to simulate service calls. const mockService: Partial = { + // Simulate the 'getAll' method to return an empty observable. + // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any getAll(_methodUrl: string, _routerParams?: Params): Observable { - return of([]) + return of([]); + }, + // Simulate the 'create' method to return an empty observable. + // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any + create(_methodUrl: string, _object: any): Observable { + return of({}); } } @@ -89,27 +95,33 @@ describe('NewNotificationComponent', () => { fixture.detectChanges(); }); + // --- Test Methods --- + + // Test for Empty Form Submission it('shold not be possible to submit the form when form is empty', () => { - expectButtonToBeDisabled() + expectButtonToBeDisabled(); }); + // Test for Form Submission Without Trigger Action it('shold not be possible to submit the form when trigger action is not selected', () => { setFieldValue(fixture, 'select-notification', NOTIF.TELEGRAM) setFieldValue(fixture, 'input-receiver', 'test-receiver') - expectButtonToBeDisabled() + expectButtonToBeDisabled(); }); + // Test for Form Submission Without Notification Selection it('shold not be possible to submit the form when notification is not selected', () => { spyOn(mockService, 'getAll') .withArgs(SERV.TASKS, { 'maxResults': component.maxResults }) - .and.returnValue(of({ values: taskValues })) + .and.returnValue(of({ values: taskValues })); - setAction(ACTION.NEW_TASK) - setFieldValue(fixture, 'select-action-filter', '1') - setFieldValue(fixture, 'input-receiver', 'test-receiver') - expectButtonToBeDisabled() + setAction(ACTION.NEW_TASK); + setFieldValue(fixture, 'select-action-filter', '1'); + setFieldValue(fixture, 'input-receiver', 'test-receiver'); + expectButtonToBeDisabled(); }); + // Test for Form Submission Without Receiver Selection it('shold not be possible to submit the form when receiver is not selected', () => { spyOn(mockService, 'getAll') .withArgs(SERV.TASKS, { 'maxResults': component.maxResults }) @@ -121,7 +133,8 @@ describe('NewNotificationComponent', () => { expectButtonToBeDisabled() }); - it('shold not possible to submit the form when all fields have data', () => { + // Test for Form Submission with All Required Data + it('should be possible to submit the form when all fields have data', () => { spyOn(mockService, 'getAll') .withArgs(SERV.TASKS, { 'maxResults': component.maxResults }) .and.returnValue(of({ values: taskValues })) @@ -135,6 +148,7 @@ describe('NewNotificationComponent', () => { expectButtonToBeEnabled() }); + // Tests for Filter Display it('should display agent filters when action is AGENT_ERROR', () => { expectAgentOptionsOnAction(ACTION.AGENT_ERROR) }); @@ -199,6 +213,32 @@ describe('NewNotificationComponent', () => { expectHiddenOnAction(ACTION.LOG_ERROR) }); + // Test for Form Submission When It Is Valid + it('should submit the form when it is valid', () => { + const serviceSpy = spyOn(mockService, 'create') + .withArgs(SERV.NOTIFICATIONS, jasmine.any(Object)) + .and.returnValue(of({})); + + setValidFormValues(); + fixture.detectChanges(); + + const submitButton: DebugElement = findEl(fixture, 'button-create'); + submitButton.nativeElement.querySelector('button').click(); + + expect(serviceSpy).toHaveBeenCalledWith(SERV.NOTIFICATIONS, jasmine.any(Object)); + }); + + + // --- Helper functions --- + + const setValidFormValues = (): void => { + setAction(ACTION.NEW_TASK); + setFieldValue(fixture, 'select-action-filter', '1'); + setFieldValue(fixture, 'select-notification', NOTIF.EMAIL); + setFieldValue(fixture, 'input-receiver', 'test@mail.com'); + fixture.detectChanges(); + }; + const expectButtonToBeDisabled = (): void => { const btn: DebugElement = findEl(fixture, 'button-create') expect(btn.nativeElement.attributes.getNamedItem('ng-reflect-disabled').value).toEqual('true') diff --git a/src/app/account/notifications/notification/new-notification.component.ts b/src/app/account/notifications/notification/new-notification.component.ts index 7c6f7715..cae9d743 100644 --- a/src/app/account/notifications/notification/new-notification.component.ts +++ b/src/app/account/notifications/notification/new-notification.component.ts @@ -1,14 +1,15 @@ import { FormControl, FormGroup, Validators } from '@angular/forms'; import { Component, OnDestroy, OnInit } from '@angular/core'; +import { Subscription } from 'rxjs'; +import { Router } from '@angular/router'; import Swal from 'sweetalert2/dist/sweetalert2.js'; - import { ACTIONARRAY, ACTION, NOTIFARRAY } from '../../../core/_constants/notifications.config'; import { environment } from '../../../../environments/environment'; import { GlobalService } from 'src/app/core/_services/main.service'; import { SERV } from '../../../core/_services/main.config'; -import { Router } from '@angular/router'; -import { Subscription } from 'rxjs'; import { AutoTitleService } from 'src/app/core/_services/shared/autotitle.service'; +import { Filter } from '../notifications.component'; + @Component({ selector: 'app-new-notification', @@ -16,17 +17,37 @@ import { AutoTitleService } from 'src/app/core/_services/shared/autotitle.servic }) export class NewNotificationComponent implements OnInit, OnDestroy { + static readonly SUBMITLABEL = 'Save Notification' + static readonly SUBTITLE = 'Create Notification' + triggerAction: string; form: FormGroup; - value: any; + filters: Filter[]; editView = false; active = false; allowedActions = ACTIONARRAY; notifications = NOTIFARRAY; subscriptions: Subscription[] = [] - submitLabel = 'Save Notification' - subTitle = 'Create Notification' - + submitLabel = NewNotificationComponent.SUBMITLABEL + subTitle = NewNotificationComponent.SUBTITLE + actionToServiceMap = { + [ACTION.AGENT_ERROR]: SERV.AGENTS, + [ACTION.OWN_AGENT_ERROR]: SERV.AGENTS, + [ACTION.DELETE_AGENT]: SERV.AGENTS, + [ACTION.NEW_TASK]: SERV.TASKS, + [ACTION.TASK_COMPLETE]: SERV.TASKS, + [ACTION.DELETE_TASK]: SERV.TASKS, + [ACTION.NEW_HASHLIST]: null, + [ACTION.DELETE_HASHLIST]: SERV.HASHLISTS, + [ACTION.HASHLIST_ALL_CRACKED]: SERV.HASHLISTS, + [ACTION.HASHLIST_CRACKED_HASH]: SERV.HASHLISTS, + [ACTION.USER_CREATED]: SERV.USERS, + [ACTION.USER_DELETED]: SERV.USERS, + [ACTION.USER_LOGIN_FAILED]: SERV.USERS, + [ACTION.LOG_WARN]: null, + [ACTION.LOG_FATAL]: null, + [ACTION.LOG_ERROR]: null, + }; maxResults = environment.config.prodApiMaxResults; constructor( @@ -37,16 +58,26 @@ export class NewNotificationComponent implements OnInit, OnDestroy { titleService.set(['New Notification']) } + /** + * Initializes the form for creating a new notification. + */ ngOnInit(): void { this.createForm() } + /** + * Unsubscribes from all subscriptions to prevent memory leaks. + */ ngOnDestroy(): void { for (const sub of this.subscriptions) { sub.unsubscribe() } } + /** + * Creates the form for creating a new notification. + * Initializes form controls with default values and validators. + */ createForm(): void { this.form = new FormGroup({ 'action': new FormControl('', [Validators.required]), @@ -57,120 +88,60 @@ export class NewNotificationComponent implements OnInit, OnDestroy { }); } + /** + * Handles the change of action for creating a new notification. + * Updates the available filters based on the selected action. + * + * @param {string} action - The selected action. + */ changeAction(action: string): void { - if (action) { - const path = this.checkPath(action); - if (path) { - const params = { 'maxResults': this.maxResults }; - - this.active = true; - - this.subscriptions.push(this.gs.getAll(path, params).subscribe((res: any) => { - const value = [] - for (let i = 0; i < res.values.length; i++) { - if (path === SERV.AGENTS) { - value.push({ "id": res.values[i]['_id'], "name": res.values[i]['agentName'] }); - } - if (path === SERV.TASKS) { - value.push({ "id": res.values[i]['_id'], "name": res.values[i]['taskName'] }); - } - if (path === SERV.USERS || path === SERV.HASHLISTS) { - value.push({ "id": res.values[i]['_id'], "name": res.values[i]['name'] }); - } + const path = this.actionToServiceMap[action]; + if (path) { + const params = { 'maxResults': this.maxResults }; + this.active = true; + + this.subscriptions.push(this.gs.getAll(path, params).subscribe((res: any) => { + const _filters: Filter[] = [] + for (let i = 0; i < res.values.length; i++) { + if (path === SERV.AGENTS) { + _filters.push({ 'id': res.values[i]['_id'], 'name': res.values[i]['agentName'] }); + } + if (path === SERV.TASKS) { + _filters.push({ 'id': res.values[i]['_id'], 'name': res.values[i]['taskName'] }); + } + if (path === SERV.USERS || path === SERV.HASHLISTS) { + _filters.push({ 'id': res.values[i]['_id'], 'name': res.values[i]['name'] }); } - this.value = value; - })); - } else { - this.active = false; - } + } + this.filters = _filters; + })); } else { this.active = false; } - } - + /** + * Checks if the form for creating a new notification is valid. + * + * @returns {boolean} True if the form is valid; otherwise, false. + */ formIsValid(): boolean { return this.form.valid } - checkPath(filter: string) { - let path: string; - - switch (filter) { - case ACTION.AGENT_ERROR: - path = SERV.AGENTS; - break; - - case ACTION.OWN_AGENT_ERROR: - path = SERV.AGENTS; - break; - - case ACTION.DELETE_AGENT: - path = SERV.AGENTS; - break; - - case ACTION.NEW_TASK: - path = SERV.TASKS; - break; - - case ACTION.TASK_COMPLETE: - path = SERV.TASKS; - break; - - case ACTION.DELETE_TASK: - path = SERV.TASKS; - break; - - case ACTION.NEW_HASHLIST: - break; - - case ACTION.DELETE_HASHLIST: - path = SERV.HASHLISTS; - break; - - case ACTION.HASHLIST_ALL_CRACKED: - path = SERV.HASHLISTS; - break; - - case ACTION.HASHLIST_CRACKED_HASH: - path = SERV.HASHLISTS; - break; - - case ACTION.USER_CREATED: - path = SERV.USERS; - break; - - case ACTION.USER_DELETED: - path = SERV.USERS; - break; - - case ACTION.USER_LOGIN_FAILED: - path = SERV.USERS; - break; - - case ACTION.LOG_WARN: - break; - - case ACTION.LOG_FATAL: - break; - - case ACTION.LOG_ERROR: - break; - - } - return path; - } - - onSubmit() { + /** + * Submits the form to create a new notification. + * Sends a request to the server and navigates on success. + */ + onSubmit(): void { if (this.form.valid) { this.subscriptions.push(this.gs.create(SERV.NOTIFICATIONS, this.form.value).subscribe(() => { Swal.fire({ position: 'top-end', backdrop: false, icon: 'success', - title: "Success!", - text: "New Notification created!", + title: 'Success!', + text: 'New Notification created!', showConfirmButton: false, timer: 1500 }) From 90c9919584ff46346a3643ec255dd76ec45a3641 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Sun, 8 Oct 2023 10:46:56 +0200 Subject: [PATCH 069/419] Add test for component creation --- .../notification/new-notification.component.spec.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/app/account/notifications/notification/new-notification.component.spec.ts b/src/app/account/notifications/notification/new-notification.component.spec.ts index 9676b2f7..e5fff039 100644 --- a/src/app/account/notifications/notification/new-notification.component.spec.ts +++ b/src/app/account/notifications/notification/new-notification.component.spec.ts @@ -97,6 +97,10 @@ describe('NewNotificationComponent', () => { // --- Test Methods --- + it('should create the component', () => { + expect(component).toBeTruthy(); + }); + // Test for Empty Form Submission it('shold not be possible to submit the form when form is empty', () => { expectButtonToBeDisabled(); From 0886389b3350d5aa9259050c00dd02b847a35ecb Mon Sep 17 00:00:00 2001 From: itbf9 Date: Sun, 8 Oct 2023 10:47:50 +0200 Subject: [PATCH 070/419] Refactor notifications for testing --- .../notifications/notifications.component.ts | 48 ++++++++++++------- 1 file changed, 32 insertions(+), 16 deletions(-) diff --git a/src/app/account/notifications/notifications.component.ts b/src/app/account/notifications/notifications.component.ts index 0f6b433e..1eff72aa 100644 --- a/src/app/account/notifications/notifications.component.ts +++ b/src/app/account/notifications/notifications.component.ts @@ -6,10 +6,10 @@ import Swal from 'sweetalert2/dist/sweetalert2.js'; import { Subject, Subscription } from 'rxjs'; import { ACTION } from '../../core/_constants/notifications.config'; import { GlobalService } from 'src/app/core/_services/main.service'; -import { PageTitle } from 'src/app/core/_decorators/autotitle'; import { SERV } from '../../core/_services/main.config'; import { NotificationListResponse } from 'src/app/core/_models/notifications'; import { Notification } from 'src/app/core/_models/notifications'; +import { AutoTitleService } from 'src/app/core/_services/shared/autotitle.service'; export interface Filter { @@ -21,7 +21,6 @@ export interface Filter { selector: 'app-notifications', templateUrl: './notifications.component.html' }) -@PageTitle(['Notifications']) export class NotificationsComponent implements OnInit, OnDestroy { faTrash = faTrash; @@ -29,6 +28,8 @@ export class NotificationsComponent implements OnInit, OnDestroy { faEdit = faEdit; faEye = faEye; + @ViewChild(DataTableDirective, { static: false }) + // DataTable properties dtElement: DataTableDirective; dtTrigger: Subject = new Subject(); @@ -40,11 +41,14 @@ export class NotificationsComponent implements OnInit, OnDestroy { // Subscriptions to unsubscribe on component destruction subscriptions: Subscription[] = [] - @ViewChild(DataTableDirective, { static: false }) - private maxResults = environment.config.prodApiMaxResults; - constructor(private gs: GlobalService) { } + constructor( + private gs: GlobalService, + private titleService: AutoTitleService, + ) { + titleService.set(['Notifications']) + } /** * Initializes DataTable and retrieves notifications. @@ -210,17 +214,7 @@ export class NotificationsComponent implements OnInit, OnDestroy { }) .then((result) => { if (result.isConfirmed) { - this.gs.delete(SERV.NOTIFICATIONS, id).subscribe(() => { - Swal.fire({ - position: 'top-end', - backdrop: false, - icon: 'success', - showConfirmButton: false, - timer: 1500 - }) - this.ngOnInit(); - this.rerender(); // rerender datatables - }); + this.deleteNotification(id) } else { swalWithBootstrapButtons.fire({ title: 'Cancelled', @@ -233,6 +227,28 @@ export class NotificationsComponent implements OnInit, OnDestroy { }); } + /** + * Handles the deletion of a notification. + * Sends an delete request to the backend and displays a success alert and rebuilds + * the datatable on success. + * + * @param {number} id - The ID of the notification to delete. + */ + deleteNotification(id: number): void { + this.subscriptions.push(this.gs.delete(SERV.NOTIFICATIONS, id).subscribe(() => { + Swal.fire({ + position: 'top-end', + backdrop: false, + icon: 'success', + showConfirmButton: false, + timer: 1500 + }) + this.getNotifications(); + this.rerender(); + this.setupTable(); + })); + } + /** * Determines the path or title based on the provided filter. * From 5a4d1370c7508c4d6aae83abfbb01bfc113d3f64 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Sun, 8 Oct 2023 10:48:10 +0200 Subject: [PATCH 071/419] Add tests for notifications component --- .../notifications.component.spec.ts | 182 ++++++++++++++++++ 1 file changed, 182 insertions(+) create mode 100644 src/app/account/notifications/notifications.component.spec.ts diff --git a/src/app/account/notifications/notifications.component.spec.ts b/src/app/account/notifications/notifications.component.spec.ts new file mode 100644 index 00000000..e8a521e1 --- /dev/null +++ b/src/app/account/notifications/notifications.component.spec.ts @@ -0,0 +1,182 @@ +import { ComponentFixture, TestBed, tick, fakeAsync, flush } from '@angular/core/testing'; +import { NotificationsComponent } from './notifications.component'; +import { CommonModule } from '@angular/common'; +import { DataTablesModule } from 'angular-datatables'; +import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; +import { GlobalService } from 'src/app/core/_services/main.service'; +import { Observable, of } from 'rxjs'; +import { SERV } from '../../core/_services/main.config'; +import { NotificationListResponse, Notification } from 'src/app/core/_models/notifications'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { RouterTestingModule } from '@angular/router/testing'; +import Swal from 'sweetalert2/dist/sweetalert2.js'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { HttpClientModule } from '@angular/common/http'; +import { RouterModule } from '@angular/router'; +import { ComponentsModule } from 'src/app/shared/components.module'; +import { PipesModule } from 'src/app/shared/pipes.module'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; + + +describe('NotificationsComponent', () => { + let component: NotificationsComponent; + let fixture: ComponentFixture; + + // Sample notifications data + const notifications: Notification[] = [ + { + _id: 1, + _self: 'http://example.com/notifications/1', + action: 'Action 1', + isActive: true, + notification: 'Notification 1', + receiver: 'Receiver 1', + userId: 1, + notificationSettingId: 101, + objectId: 201, + }, + { + _id: 2, + _self: 'http://example.com/notifications/2', + action: 'Action 2', + isActive: true, + notification: 'Notification 2', + receiver: 'Receiver 2', + userId: 2, + notificationSettingId: 102, + objectId: 202, + }, + { + _id: 3, + _self: 'http://example.com/notifications/3', + action: 'Action 3', + isActive: true, + notification: 'Notification 3', + receiver: 'Receiver 3', + userId: 3, + notificationSettingId: 103, + objectId: 203, + }, + ]; + + // Mock GlobalService with required methods + const mockService: Partial = { + // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any + getAll(_methodUrl: string, params: any): Observable { + if (_methodUrl === SERV.NOTIFICATIONS) { + const response: NotificationListResponse = { + _expandable: '', + startAt: 0, + maxResults: notifications.length, + total: notifications.length, + isLast: true, + values: notifications, + }; + return of(response); + } + return of({} as NotificationListResponse); + }, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + delete(_methodUrl: string, id: number): Observable { + if (_methodUrl === SERV.NOTIFICATIONS) { + const index = notifications.findIndex((n) => n._id === id); + if (index !== -1) { + notifications.splice(index, 1); + } + return of({}); + } + return of({}); + }, + }; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ + CommonModule, + FormsModule, + ReactiveFormsModule, + HttpClientModule, + FontAwesomeModule, + DataTablesModule, + ComponentsModule, + RouterModule, + PipesModule, + NgbModule, + BrowserAnimationsModule, + RouterTestingModule, + ], + declarations: [NotificationsComponent], + providers: [ + { + provide: GlobalService, + useValue: mockService, + }, + Swal + ], + }).compileComponents(); + + fixture = TestBed.createComponent(NotificationsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + + }); + + // Check if the component is created successfully + it('should create the component', () => { + expect(component).toBeTruthy(); + }); + + // Ensure that notifications are fetched and stored in the component + it('should fetch notifications on initialization', () => { + expect(component.notifications).toEqual(notifications); + }); + + // Verify that the table in the component's HTML is populated with the correct number of rows + it('should render the table with notifications', () => { + const tableRows = fixture.nativeElement.querySelectorAll('tbody tr'); + expect(tableRows.length).toBe(notifications.length); + }); + + // Simulate the deletion of a notification and confirm the action + // Expect that the notification deletion was called, Swal fire was triggered, + // and the notification is no longer in the component's list + it('should handle notification deletion', fakeAsync(() => { + const deleteSpy = spyOn(mockService, 'delete').and.callThrough(); + const swalFireSpy = spyOn(Swal, 'fire').and.callFake(() => { + return Promise.resolve({ isConfirmed: true }); + }); + + const notificationToDelete = notifications[0]; + component.onDelete(notificationToDelete._id, notificationToDelete.notification); + + // Trigger the confirmation and flush any asynchronous tasks + Swal.clickConfirm(); + flush(); + + expect(deleteSpy).toHaveBeenCalledWith(SERV.NOTIFICATIONS, notificationToDelete._id); + expect(swalFireSpy).toHaveBeenCalled(); + expect(component.notifications).not.toContain(notificationToDelete); + })); + + // Simulate the deletion of a notification and cancel the action + // Expect that the notification deletion was not called, + // Swal fire was triggered, and the notification remains in the component's list + it('should handle notification deletion cancellation', fakeAsync(() => { + const deleteSpy = spyOn(mockService, 'delete').and.callThrough(); + const swalFireSpy = spyOn(Swal, 'fire').and.callFake(() => { + return Promise.resolve({ isConfirmed: false }); + }); + + const notificationToDelete = notifications[0]; + component.onDelete(notificationToDelete._id, notificationToDelete.notification); + + // Trigger the cancellation and flush any asynchronous tasks + Swal.clickCancel() + flush(); + + expect(deleteSpy).not.toHaveBeenCalled(); + expect(swalFireSpy).toHaveBeenCalled(); + expect(component.notifications).toContain(notificationToDelete); + })); + +}); From b95d1a27dffeb0cf320b54032ab1c8e1d65d0a10 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Sun, 8 Oct 2023 19:44:41 +0200 Subject: [PATCH 072/419] Remove pagetitle decorator + reformat code --- .../acc-settings/acc-settings.component.html | 58 ++++-------------- .../acc-settings/acc-settings.component.ts | 59 +++++++++---------- 2 files changed, 42 insertions(+), 75 deletions(-) diff --git a/src/app/account/settings/acc-settings/acc-settings.component.html b/src/app/account/settings/acc-settings/acc-settings.component.html index ceb82331..720ccd2f 100644 --- a/src/app/account/settings/acc-settings/acc-settings.component.html +++ b/src/app/account/settings/acc-settings/acc-settings.component.html @@ -3,65 +3,33 @@
- +
- +
- + - + - + - + - + - +
- + \ No newline at end of file diff --git a/src/app/account/settings/acc-settings/acc-settings.component.ts b/src/app/account/settings/acc-settings/acc-settings.component.ts index 807989fb..ddfb67d6 100644 --- a/src/app/account/settings/acc-settings/acc-settings.component.ts +++ b/src/app/account/settings/acc-settings/acc-settings.component.ts @@ -1,13 +1,12 @@ -import { AbstractControl, FormControl, FormGroup, ValidatorFn, Validators } from '@angular/forms'; -import { ActivatedRoute, Params, Router } from '@angular/router'; +import { FormControl, FormGroup, ValidatorFn, Validators } from '@angular/forms'; +import { Router } from '@angular/router'; import Swal from 'sweetalert2/dist/sweetalert2.js'; import { Component, OnInit } from '@angular/core'; - import { UIConfigService } from 'src/app/core/_services/shared/storage.service'; import { GlobalService } from 'src/app/core/_services/main.service'; -import { PageTitle } from 'src/app/core/_decorators/autotitle'; import { SERV } from '../../../core/_services/main.config'; import { uiDatePipe } from 'src/app/core/_pipes/date.pipe'; +import { AutoTitleService } from 'src/app/core/_services/shared/autotitle.service'; function passwordMatchValidator(password: string): ValidatorFn { return (control: FormControl) => { @@ -23,7 +22,6 @@ function passwordMatchValidator(password: string): ValidatorFn { templateUrl: './acc-settings.component.html', providers: [uiDatePipe] }) -@PageTitle(['Account Settings']) export class AccountSettingsComponent implements OnInit { updateForm: FormGroup; @@ -32,23 +30,24 @@ export class AccountSettingsComponent implements OnInit { constructor( private uiService: UIConfigService, - private datePipe:uiDatePipe, + private titleService: AutoTitleService, + private datePipe: uiDatePipe, private gs: GlobalService, private router: Router ) { + this.titleService.set(['Account Settings']) this.formInit() } ngOnInit(): void { - this.initForm(); } private formInit() { this.updateForm = new FormGroup({ - 'name': new FormControl({value: '', disabled: true} ), - 'registeredSince': new FormControl({value: '', disabled: true} ), + 'name': new FormControl({ value: '', disabled: true }), + 'registeredSince': new FormControl({ value: '', disabled: true }), 'email': new FormControl(null, [Validators.required, Validators.email]), 'oldpassword': new FormControl(), 'newpassword': new FormControl([ @@ -64,19 +63,19 @@ export class AccountSettingsComponent implements OnInit { }); } - onSubmit(){ + onSubmit() { if (this.updateForm.valid) { - this.gs.create(SERV.USERS,this.updateForm.value).subscribe(() => { - Swal.fire({ - position: 'top-end', - backdrop: false, - icon: 'success', - title: 'Saved', - showConfirmButton: false, - timer: 1500 - }) - this.router.navigate(['users/all-users']); - } + this.gs.create(SERV.USERS, this.updateForm.value).subscribe(() => { + Swal.fire({ + position: 'top-end', + backdrop: false, + icon: 'success', + title: 'Saved', + showConfirmButton: false, + timer: 1500 + }) + this.router.navigate(['users/all-users']); + } ); } } @@ -90,16 +89,16 @@ export class AccountSettingsComponent implements OnInit { } private initForm() { - this.gs.get(SERV.USERS,this.gs.userId, {'expand':'globalPermissionGroup'}).subscribe((result)=>{ - this.updateForm = new FormGroup({ - 'name': new FormControl({value: result.globalPermissionGroup['name'], disabled: true} ), - 'registeredSince': new FormControl({value: this.datePipe.transform(result['registeredSince']), disabled: true} ), - 'email': new FormControl(result['email']), - 'oldpassword': new FormControl(), - 'newpassword': new FormControl(), - 'confirmpass': new FormControl(), + this.gs.get(SERV.USERS, this.gs.userId, { 'expand': 'globalPermissionGroup' }).subscribe((result) => { + this.updateForm = new FormGroup({ + 'name': new FormControl({ value: result.globalPermissionGroup['name'], disabled: true }), + 'registeredSince': new FormControl({ value: this.datePipe.transform(result['registeredSince']), disabled: true }), + 'email': new FormControl(result['email']), + 'oldpassword': new FormControl(), + 'newpassword': new FormControl(), + 'confirmpass': new FormControl(), + }); }); - }); } } From 14f7e1c7c34b4f562783eef64cf66107d1cb213d Mon Sep 17 00:00:00 2001 From: itbf9 Date: Mon, 9 Oct 2023 17:36:11 +0200 Subject: [PATCH 073/419] Add validator for password fields --- src/app/core/_validators/password.validator.ts | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 src/app/core/_validators/password.validator.ts diff --git a/src/app/core/_validators/password.validator.ts b/src/app/core/_validators/password.validator.ts new file mode 100644 index 00000000..947bd391 --- /dev/null +++ b/src/app/core/_validators/password.validator.ts @@ -0,0 +1,9 @@ +import { AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms'; + + +export const passwordMatchValidator: ValidatorFn = (control: AbstractControl): ValidationErrors | null => { + const a = control.get('newpassword').value + const b = control.get('confirmpass').value + + return a === b ? null : { 'mismatch': true }; +} \ No newline at end of file From 44a8538b499b57d500e02356883a3355ea9413e4 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Mon, 9 Oct 2023 17:37:39 +0200 Subject: [PATCH 074/419] Fix code format --- .../shared/grid-containers/grid-formgroup.ts | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/app/shared/grid-containers/grid-formgroup.ts b/src/app/shared/grid-containers/grid-formgroup.ts index 8e3fc2c2..d19414eb 100644 --- a/src/app/shared/grid-containers/grid-formgroup.ts +++ b/src/app/shared/grid-containers/grid-formgroup.ts @@ -1,31 +1,31 @@ -import { faInfoCircle } from '@fortawesome/free-solid-svg-icons'; +import { faInfoCircle } from '@fortawesome/free-solid-svg-icons'; import { Component, Input } from '@angular/core'; import { Router } from '@angular/router'; +import { FormGroup } from '@angular/forms'; @Component({ selector: 'grid-form-input', template: `
- - -
+ + +
` }) -export class GridFormInputComponent { +export class GridFormInputComponent { - faInfoCircle=faInfoCircle; + faInfoCircle = faInfoCircle; @Input() name?: any; @Input() labelclass?: any; From 2148b2531d641fd7ced7de10d971a7402cad04ab Mon Sep 17 00:00:00 2001 From: itbf9 Date: Mon, 9 Oct 2023 17:40:42 +0200 Subject: [PATCH 075/419] Refactor for testing, inject titleservice and unsubscribe on destroy --- .../acc-settings/acc-settings.component.ts | 115 +++++++++++------- 1 file changed, 69 insertions(+), 46 deletions(-) diff --git a/src/app/account/settings/acc-settings/acc-settings.component.ts b/src/app/account/settings/acc-settings/acc-settings.component.ts index ddfb67d6..d5d52d48 100644 --- a/src/app/account/settings/acc-settings/acc-settings.component.ts +++ b/src/app/account/settings/acc-settings/acc-settings.component.ts @@ -1,71 +1,96 @@ -import { FormControl, FormGroup, ValidatorFn, Validators } from '@angular/forms'; +import { FormControl, FormGroup, Validators } from '@angular/forms'; import { Router } from '@angular/router'; import Swal from 'sweetalert2/dist/sweetalert2.js'; -import { Component, OnInit } from '@angular/core'; -import { UIConfigService } from 'src/app/core/_services/shared/storage.service'; +import { Component, OnDestroy, OnInit } from '@angular/core'; import { GlobalService } from 'src/app/core/_services/main.service'; import { SERV } from '../../../core/_services/main.config'; import { uiDatePipe } from 'src/app/core/_pipes/date.pipe'; import { AutoTitleService } from 'src/app/core/_services/shared/autotitle.service'; +import { Subscription } from 'rxjs'; +import { passwordMatchValidator } from 'src/app/core/_validators/password.validator'; -function passwordMatchValidator(password: string): ValidatorFn { - return (control: FormControl) => { - if (!control || !control.parent) { - return null; - } - return control.parent.get(password).value === control.value ? null : { mismatch: true }; - }; -} @Component({ selector: 'app-acc-settings', templateUrl: './acc-settings.component.html', providers: [uiDatePipe] }) -export class AccountSettingsComponent implements OnInit { +export class AccountSettingsComponent implements OnInit, OnDestroy { - updateForm: FormGroup; + static readonly PWD_MIN = 4 + static readonly PWD_MAX = 12 + + form: FormGroup; strongPassword = false; - passMatch = false; + subscriptions: Subscription[] = [] constructor( - private uiService: UIConfigService, private titleService: AutoTitleService, private datePipe: uiDatePipe, private gs: GlobalService, private router: Router ) { this.titleService.set(['Account Settings']) - this.formInit() } + /** + * Initializes the form, loads user settings, and sets up initial data. + */ ngOnInit(): void { - this.initForm(); + this.createForm(); + this.loadUserSettings(); + } + /** + * Unsubscribes from all active subscriptions to prevent memory leaks. + */ + ngOnDestroy(): void { + for (const sub of this.subscriptions) { + sub.unsubscribe() + } } - private formInit() { - this.updateForm = new FormGroup({ - 'name': new FormControl({ value: '', disabled: true }), - 'registeredSince': new FormControl({ value: '', disabled: true }), - 'email': new FormControl(null, [Validators.required, Validators.email]), - 'oldpassword': new FormControl(), - 'newpassword': new FormControl([ + /** + * Creates and configures the Angular FormGroup for managing form controls. + * + * @param {string} name - Account type name. + * @param {string} registeredSince - Account registration date. + * @param {string} email - The user's email. + */ + createForm(name = '', registeredSince = '', email = ''): void { + this.form = new FormGroup({ + 'name': new FormControl({ + value: name, + disabled: true + }), + 'registeredSince': new FormControl({ + value: registeredSince, + disabled: true + }), + 'email': new FormControl(email, [ + Validators.required, + Validators.email + ]), + 'oldpassword': new FormControl(''), + 'newpassword': new FormControl('', [ Validators.required, - Validators.minLength(4), - Validators.maxLength(12) + Validators.minLength(AccountSettingsComponent.PWD_MIN), + Validators.maxLength(AccountSettingsComponent.PWD_MAX), ]), - 'confirmpass': new FormControl([ + 'confirmpass': new FormControl('', [ Validators.required, - Validators.minLength(4), - Validators.maxLength(12) + Validators.minLength(AccountSettingsComponent.PWD_MIN), + Validators.maxLength(AccountSettingsComponent.PWD_MAX) ]), - }); + }, passwordMatchValidator); } + /** + * Handles form submission. Sends the updated account data to the server upon valid form submission. + */ onSubmit() { - if (this.updateForm.valid) { - this.gs.create(SERV.USERS, this.updateForm.value).subscribe(() => { + if (this.form.valid) { + this.subscriptions.push(this.gs.create(SERV.USERS, this.form.value).subscribe(() => { Swal.fire({ position: 'top-end', backdrop: false, @@ -75,8 +100,7 @@ export class AccountSettingsComponent implements OnInit { timer: 1500 }) this.router.navigate(['users/all-users']); - } - ); + })); } } @@ -88,17 +112,16 @@ export class AccountSettingsComponent implements OnInit { this.strongPassword = event; } - private initForm() { - this.gs.get(SERV.USERS, this.gs.userId, { 'expand': 'globalPermissionGroup' }).subscribe((result) => { - this.updateForm = new FormGroup({ - 'name': new FormControl({ value: result.globalPermissionGroup['name'], disabled: true }), - 'registeredSince': new FormControl({ value: this.datePipe.transform(result['registeredSince']), disabled: true }), - 'email': new FormControl(result['email']), - 'oldpassword': new FormControl(), - 'newpassword': new FormControl(), - 'confirmpass': new FormControl(), - }); - }); + /** + * Loads user settings from the server and populates the form with initial data. + */ + private loadUserSettings() { + this.subscriptions.push(this.gs.get(SERV.USERS, this.gs.userId, { 'expand': 'globalPermissionGroup' }).subscribe((result) => { + this.createForm( + result.globalPermissionGroup['name'], + this.datePipe.transform(result['registeredSince']), + result['email'] + ) + })); } - } From 6b681b7568b5654b07ec89ecc7616056f749f5fd Mon Sep 17 00:00:00 2001 From: itbf9 Date: Mon, 9 Oct 2023 17:42:38 +0200 Subject: [PATCH 076/419] Add data-testid for use in tests --- .../acc-settings/acc-settings.component.html | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/app/account/settings/acc-settings/acc-settings.component.html b/src/app/account/settings/acc-settings/acc-settings.component.html index 720ccd2f..b778fd0a 100644 --- a/src/app/account/settings/acc-settings/acc-settings.component.html +++ b/src/app/account/settings/acc-settings/acc-settings.component.html @@ -1,35 +1,36 @@ -
+
- - + +
- +
- + + placeholder="Enter Old Password" data-testid="input-password" required> + placeholder="Enter New Password" data-testid="input-newpassword"> - + placeholder="Confirm Password" data-testid="input-confirmpass" required> - - + +
\ No newline at end of file From 4d5924e1858078cf38c89cdcc3049d2f1cb186c7 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Mon, 9 Oct 2023 17:43:09 +0200 Subject: [PATCH 077/419] Add basic tests for account settings --- .../acc-settings.component.spec.ts | 206 ++++++++++++++++++ 1 file changed, 206 insertions(+) create mode 100644 src/app/account/settings/acc-settings/acc-settings.component.spec.ts diff --git a/src/app/account/settings/acc-settings/acc-settings.component.spec.ts b/src/app/account/settings/acc-settings/acc-settings.component.spec.ts new file mode 100644 index 00000000..38986861 --- /dev/null +++ b/src/app/account/settings/acc-settings/acc-settings.component.spec.ts @@ -0,0 +1,206 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { RouterTestingModule } from '@angular/router/testing'; +import { AccountSettingsComponent } from './acc-settings.component'; +import { CommonModule } from '@angular/common'; +import { HttpClientModule } from '@angular/common/http'; +import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; +import { DataTablesModule } from 'angular-datatables'; +import { ComponentsModule } from 'src/app/shared/components.module'; +import { PipesModule } from 'src/app/shared/pipes.module'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { GlobalService } from 'src/app/core/_services/main.service'; +import { Params } from '@angular/router'; +import { Observable, of } from 'rxjs'; +import { findEl, setFieldValue } from 'src/app/spec-helpers/element.spec-helper'; +import { DebugElement } from '@angular/core'; +import { SERV } from 'src/app/core/_services/main.config'; + +describe('AccountSettingsComponent', () => { + let component: AccountSettingsComponent; + let fixture: ComponentFixture; + + const userSettingsResponse = { + "_expandable": "accessGroups", + "_id": 1, + "_self": "/api/v2/ui/users/1", + "email": "user@test.com", + "globalPermissionGroup": { + "_id": 1, + "_self": "/api/v2/ui/globalpermissiongroups/1", + "id": 1, + "name": "Administrator", + "permissions": {} + }, + "globalPermissionGroupId": 1, + "id": 1, + "isComputedPassword": true, + "isValid": true, + "lastLoginDate": 1695811400, + "name": "admin", + "otp1": "", + "otp2": "", + "otp3": "", + "otp4": "", + "registeredSince": 1695810913, + "sessionLifetime": 3600, + "yubikey": "0" + } + + // Define a partial mock service to simulate service calls. + const mockService: Partial = { + // Simulate the 'get' method to return an empty observable. + // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any + get(_methodUrl: string, _id: number, _routerParams?: Params): Observable { + if (_methodUrl === SERV.USERS) { + return of(userSettingsResponse) + } + return of([]) + }, + // Simulate the 'create' method to return an empty observable. + // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any + create(_methodUrl: string, _object: any): Observable { + return of({}); + }, + + userId: 1 + } + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ + CommonModule, + FormsModule, + ReactiveFormsModule, + HttpClientModule, + FontAwesomeModule, + DataTablesModule, + ComponentsModule, + PipesModule, + NgbModule, + RouterTestingModule, + ], + providers: [ + { + provide: GlobalService, + useValue: mockService + }, + ], + declarations: [AccountSettingsComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(AccountSettingsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + + // --- Test Methods --- + + + it('should create the component', () => { + expect(component).toBeTruthy(); + }); + + it('should initialize the form with default values', () => { + const formValue = component.form.getRawValue(); + + // Ensure that the initial form values are as expected + expect(formValue.name).toBe(userSettingsResponse.globalPermissionGroup.name); + expect(formValue.registeredSince).toBe('27/09/2023 12:35:13'); + expect(formValue.email).toBe(userSettingsResponse.email); + expect(formValue.oldpassword).toBe(''); + expect(formValue.newpassword).toBe(''); + expect(formValue.confirmpass).toBe(''); + }); + + it('should validate email as required', () => { + const emailControl = component.form.get('email'); + + // Set an empty email value + emailControl.setValue(''); + expect(emailControl.hasError('required')).toBe(true); + + setFieldValue(fixture, 'input-email', 'test@example.com'); + expect(emailControl.hasError('required')).toBe(false); + }); + + + it('should validate email format', () => { + const emailControl = component.form.get('email'); + + // Set an invalid email format + setFieldValue(fixture, 'input-email', 'invalid-email'); + expect(emailControl.hasError('email')).toBe(true); + + // Set a valid email format + setFieldValue(fixture, 'input-email', 'test@example.com'); + expect(emailControl.hasError('email')).toBe(false); + }); + + + it('should validate password length', () => { + const newPasswordControl = component.form.get('newpassword'); + const confirmPasswordControl = component.form.get('confirmpass'); + + // Set a password with length less than PWD_MIN + setFieldValue(fixture, 'input-newpassword', '123'); + setFieldValue(fixture, 'input-confirmpass', '123'); + expect(newPasswordControl.hasError('minlength')).toBe(true); + expect(confirmPasswordControl.hasError('minlength')).toBe(true); + + // Set a password with length equal to PWD_MIN + setFieldValue(fixture, 'input-newpassword', '1234'); + setFieldValue(fixture, 'input-confirmpass', '1234'); + expect(newPasswordControl.hasError('minlength')).toBe(false); + expect(confirmPasswordControl.hasError('minlength')).toBe(false); + + // Set a password with length greater than PWD_MAX + setFieldValue(fixture, 'input-newpassword', '1234567890123'); + setFieldValue(fixture, 'input-confirmpass', '1234567890123'); + expect(newPasswordControl.hasError('maxlength')).toBe(true); + expect(confirmPasswordControl.hasError('maxlength')).toBe(true); + + // Set a password with length equal to PWD_MAX + setFieldValue(fixture, 'input-newpassword', '123456789012'); + setFieldValue(fixture, 'input-confirmpass', '123456789012'); + expect(newPasswordControl.hasError('maxlength')).toBe(false); + expect(confirmPasswordControl.hasError('maxlength')).toBe(false); + }); + + it('should validate password match', () => { + // Set different passwords + setFieldValue(fixture, 'input-newpassword', 'password123'); + setFieldValue(fixture, 'input-confirmpass', 'password456'); + + expect(component.form.hasError('mismatch')).toBe(true); + + // Set matching passwords + setFieldValue(fixture, 'input-newpassword', 'password123'); + setFieldValue(fixture, 'input-confirmpass', 'password123'); + + expect(component.form.hasError('mismatch')).toBe(false); + }); + + it('should enable form submission when form is valid', () => { + const btn: DebugElement = findEl(fixture, 'button-submit') + + // Initially, the form should be invalid, and the submit button should be disabled + expect(component.form.valid).toBe(false); + expect(btn.nativeElement.attributes.getNamedItem('ng-reflect-disabled').value).toEqual('true') + + // Set valid values for the form + setFieldValue(fixture, 'input-name', 'admin'); + setFieldValue(fixture, 'input-registered-since', '27/09/2023 12:35:13'); + setFieldValue(fixture, 'input-email', 'test@example.com'); + setFieldValue(fixture, 'input-password', 'summer123'); + setFieldValue(fixture, 'input-newpassword', 'password123'); + setFieldValue(fixture, 'input-confirmpass', 'password123'); + + fixture.detectChanges(); + + // The form should now be valid, and the submit button should be enabled + expect(component.form.valid).toBe(true); + expect(btn.nativeElement.attributes.getNamedItem('ng-reflect-disabled').value).toEqual('false') + }); +}); From 238e4efee70cf3ea34db332b070440db6548c6be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Mon, 9 Oct 2023 21:39:57 +0100 Subject: [PATCH 078/419] Upload multiple files #977 --- package-lock.json | 2 +- package.json | 2 +- .../agents/new-agent/new-agent.component.html | 3 +- .../core/_services/files/files_tus.service.ts | 184 +++++++++++------- .../files/new-files/new-files.component.html | 11 +- .../files/new-files/new-files.component.ts | 140 +++++++------ .../new-hashlist/new-hashlist.component.html | 49 +++-- .../new-hashlist/new-hashlist.component.ts | 69 ++++--- 8 files changed, 270 insertions(+), 190 deletions(-) diff --git a/package-lock.json b/package-lock.json index ab07cd41..7989c0ca 100644 --- a/package-lock.json +++ b/package-lock.json @@ -63,7 +63,7 @@ "rxjs": "~7.8.1", "sweetalert2": "^11.7.12", "tslib": "^2.6.0", - "tus-js-client": "^3.1.0", + "tus-js-client": "^3.1.1", "zone.js": "~0.13.1" }, "devDependencies": { diff --git a/package.json b/package.json index e98f6c1d..fd733e95 100644 --- a/package.json +++ b/package.json @@ -68,7 +68,7 @@ "rxjs": "~7.8.1", "sweetalert2": "^11.7.12", "tslib": "^2.6.0", - "tus-js-client": "^3.1.0", + "tus-js-client": "^3.1.1", "zone.js": "~0.13.1" }, "devDependencies": { diff --git a/src/app/agents/new-agent/new-agent.component.html b/src/app/agents/new-agent/new-agent.component.html index 18192086..cdb79da5 100644 --- a/src/app/agents/new-agent/new-agent.component.html +++ b/src/app/agents/new-agent/new-agent.component.html @@ -8,9 +8,8 @@
Instructions
Using the URL, link the agent with the app: {{ agentURL }} (); - uploadProgress = this.uploadStatus.asObservable(); - - fileStatusArr: UploadFileTUS[] = []; + private tusUpload: tus.Upload | null = null; constructor( private cs: ConfigService, + private gs: GlobalService, + private router: Router ){} -/** - * Upload file using TUS protocol - * @param file - File - * @param filename - Name to upload - * @returns Object -**/ - uploadFile(file: File, filename: string, fileURL = null) { - // Only continue if a file has been selected - if (!file) { - return - } - if (!tus.isSupported) { - alert('This browser does not support uploads. Please use a modern browser instead.') - } - const fileStatus: UploadFileTUS = {filename, progress: 0, hash: '', uuid: ''}; - - this.fileStatusArr.push(fileStatus); - - this.uploadStatus.next(this.fileStatusArr); - - const upload = new tus.Upload(file, { - endpoint: this.cs.getEndpoint() + this.endpoint, - headers: { - Authorization: `Bearer ${this.userData._token}`, - 'Tus-Resumable':'1.0.0', - 'Tus-Extension': 'checksum', - 'Tus-Checksum-Algorithm': 'md5,sha1,crc32' - }, - uploadUrl: fileURL, - retryDelays: [0, 3000, 6000, 9000, 12000], - chunkSize: this.chunked, - metadata: { - filename, - filetype: file.type, - // userId: "1234567" - }, - onError: async (error) => { - if (error) { - if (window.confirm(`Failed because: ${error}\nDo you want to retry?`)) { - upload.start() - return false; - } - } else { - window.alert(`Failed because: ${error}`) - } - return false; - }, - onChunkComplete: (chunkSize, bytesAccepted, bytesTotal) => { - this.fileStatusArr.forEach(value => { - if (value.filename === filename) { - value.progress = Math.floor(bytesAccepted / bytesTotal * 100); - value.uuid = upload.url.split('/').slice(-1)[0]; + /** + * Upload file using TUS protocol + * @param file - File + * @param filename - Name to upload + * @param form - Creation form + * @param redirect - Link to redirect + **/ + + // public fileStatusArr: UploadFileTUS[] = []; + + uploadFile(file: File, filename: string, path, form = null, redirect = null): Observable { + + return new Observable((observer) => { + + // Chunksize config default + let chunkSize = this.chunked; + if (Number.isNaN(chunkSize)) { + chunkSize = Infinity + } + if (!tus.isSupported) { + alert('This browser does not support uploads. Please use a modern browser instead.') + } + + const upload = new tus.Upload(file, { + endpoint: this.cs.getEndpoint() + this.endpoint, + headers: { + Authorization: `Bearer ${this.userData._token}`, + 'Tus-Resumable':'1.0.0', + 'Tus-Extension': 'checksum', + 'Tus-Checksum-Algorithm': 'md5,sha1,crc32' + }, + // uploadUrl: fileURL, //Used for paused uploads + retryDelays: [0, 3000, 6000, 9000, 12000], + chunkSize: chunkSize, + removeFingerprintOnSuccess: true, + metadata: { + filename, + filetype: file.type, + }, + onError: async (error) => { + const exist = String(error).includes('exists!'); + if (exist) { + const progress = 100; + observer.next(progress); + if(form !== null){ + this.gs.create(path,form).subscribe(() => { + this.router.navigate(redirect); + }); + } + } else { + window.alert(`Failed because: ${error}`) } - }); - this.uploadStatus.next(this.fileStatusArr); - }, - onSuccess: async () => { - this.fileStatusArr.forEach(value => { - if (value.filename === filename) { - value.progress = 100; + return false; + }, + onProgress: (bytesUploaded, bytesTotal) => { + const progress = (bytesUploaded / bytesTotal) * 100; + observer.next(progress); + }, + onSuccess: async () => { + observer.complete(); + if(form !== null){ + this.gs.create(path,form).subscribe(() => { + this.router.navigate(redirect); + }); } - }); - this.uploadStatus.next(this.fileStatusArr); - return true; - } - }); - upload.start(); - } + } + }) + + this.tusUpload = upload; + + checkPreviousuploads(upload).catch((error) => { + console.error(error) + }) + + }) + + async function checkPreviousuploads(upload) { + let previousUploads = await upload.findPreviousUploads() + + // We only want to consider uploads in the last hour. + const limitUpload = Date.now() - 3 * 60 * 60 * 1000 + previousUploads = previousUploads + .map((upload) => { + console.log('creationtome') + upload.creationTime = new Date(upload.creationTime) + return upload + }) + .filter((upload) => upload.status > limitUpload) + .sort((a, b) => b.creationTime - a.creationTime) - cancelUpload(filename: string){ + // if (previousUploads.length === 0) { + // upload.start(); + // } + + // File already exist in the import folder, then return progress as 100 + + upload.start(); + + } + + } + isHttpProgressEvent(event: HttpEvent): event is HttpProgressEvent { + return ( + event.type === HttpEventType.DownloadProgress || + event.type === HttpEventType.UploadProgress + ); } } diff --git a/src/app/files/new-files/new-files.component.html b/src/app/files/new-files/new-files.component.html index 56820155..616aed7f 100644 --- a/src/app/files/new-files/new-files.component.html +++ b/src/app/files/new-files/new-files.component.html @@ -39,19 +39,18 @@ -
-   -
-

Uploading File: {{fileStatus.filename}}

+
+
+
-
+

Upload completed!

  diff --git a/src/app/files/new-files/new-files.component.ts b/src/app/files/new-files/new-files.component.ts index c7d580d3..5b659f0d 100644 --- a/src/app/files/new-files/new-files.component.ts +++ b/src/app/files/new-files/new-files.component.ts @@ -1,27 +1,29 @@ import { faPlus, faUpload, faDownload, faLink, faFileUpload} from '@fortawesome/free-solid-svg-icons'; import { FormControl, FormGroup } from '@angular/forms'; import Swal from 'sweetalert2/dist/sweetalert2.js'; -import { Component, OnInit } from '@angular/core'; -import { Observable } from 'rxjs'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, Injectable, OnDestroy, OnInit, ViewChild } from '@angular/core'; +import { Observable, ReplaySubject, Subject, Subscription, map, of, pairwise, startWith, switchMap, take, takeUntil, tap } from 'rxjs'; +// import { takeUntil } from 'rxjs/operators'; import { Buffer } from 'buffer'; import { UploadTUSService } from 'src/app/core/_services/files/files_tus.service'; -import { validateFileExt } from '../../shared/utils/util'; import { GlobalService } from 'src/app/core/_services/main.service'; import { FileSizePipe } from 'src/app/core/_pipes/file-size.pipe'; import { environment } from './../../../environments/environment'; import { PageTitle } from 'src/app/core/_decorators/autotitle'; +import { validateFileExt } from '../../shared/utils/util'; +import { ActivatedRoute, Router } from '@angular/router'; import { UploadFileTUS } from '../../core/_models/files'; import { SERV } from '../../core/_services/main.config'; -import { ActivatedRoute, Router } from '@angular/router'; +import { subscribe } from 'diagnostics_channel'; @Component({ selector: 'app-new-files', templateUrl: './new-files.component.html', providers: [FileSizePipe] }) -@PageTitle(['New File']) -export class NewFilesComponent implements OnInit { +// @PageTitle(['New File']) +export class NewFilesComponent implements OnInit, OnDestroy { faFileUpload=faFileUpload; faDownload=faDownload; @@ -30,6 +32,7 @@ export class NewFilesComponent implements OnInit { faPlus=faPlus; private maxResults = environment.config.prodApiMaxResults; + subscriptions: Subscription[] = [] constructor( private uploadService:UploadTUSService, @@ -37,12 +40,15 @@ export class NewFilesComponent implements OnInit { private gs: GlobalService, private fs:FileSizePipe, private router: Router - ) { } + ) { + + } accessgroup: any[] filterType: number; whichView: string; createForm: FormGroup; + submitted = false; ngOnInit(): void { @@ -50,17 +56,15 @@ export class NewFilesComponent implements OnInit { this.loadData(); - this.createForm = new FormGroup({ - filename: new FormControl(''), - isSecret: new FormControl(false), - fileType: new FormControl(this.filterType), - accessGroupId: new FormControl(1), - sourceType: new FormControl('import' || ''), - sourceData: new FormControl(''), - }); - - this.uploadProgress = this.uploadService.uploadProgress; //Uploading File using tus protocol + } + ngOnDestroy() { + this.subs.forEach((s) => s.unsubscribe()); + for (const sub of this.subscriptions) { + sub.unsubscribe() + } + this.ngUnsubscribe.next(false); + this.ngUnsubscribe.complete(); } loadData(){ @@ -71,41 +75,48 @@ export class NewFilesComponent implements OnInit { this.accessgroup = agroups.values; }); + this.createForm = new FormGroup({ + filename: new FormControl(''), + isSecret: new FormControl(false), + fileType: new FormControl(this.filterType), + accessGroupId: new FormControl(1), + sourceType: new FormControl('import' || ''), + sourceData: new FormControl(''), + }); + } /** * Create File * */ - submitted = false; - onSubmitFile(): void{ - this.onSubmit(); - this.submitted = true; - } - onSubmit(): void{ - - if (this.createForm.valid) { - - const form = this.onPrep(this.createForm.value); - - this.gs.create(SERV.FILES,form).subscribe(() => { - Swal.fire({ - position: 'top-end', - backdrop: false, - icon: 'success', - title: "Success!", - text: "New File created!", - showConfirmButton: false, - timer: 1500 - }) - this.router.navigate(['/files',this.redirect]); + if (this.createForm.valid && this.submitted === false) { + + let form = this.onPrep(this.createForm.value, false); + + this.submitted =true; + + if(form.status === false){ + this.subscriptions.push(this.gs.create(SERV.FILES,form.update).subscribe(() => { + form = this.onPrep(this.createForm.value, true); + Swal.fire({ + position: 'top-end', + backdrop: false, + icon: 'success', + title: "Success!", + text: "New File created!", + showConfirmButton: false, + timer: 1500 + }) + this.submitted = false; + this.router.navigate(['/files',this.redirect]); + })); } - ); } } -onPrep(obj: any){ +onPrep(obj: any, status: boolean){ let sourcadata; let fname; if(obj.sourceType == 'inline'){ @@ -116,12 +127,14 @@ onPrep(obj: any){ fname = this.fileName; } const res = { - "filename": fname, - "isSecret": obj.isSecret, - "fileType": this.filterType, - "accessGroupId": obj.accessGroupId, - "sourceType": obj.sourceType, - "sourceData": sourcadata + "update":{ + "filename": fname, + "isSecret": obj.isSecret, + "fileType": this.filterType, + "accessGroupId": obj.accessGroupId, + "sourceType": obj.sourceType, + "sourceData": sourcadata + },"status": status } return res; } @@ -166,9 +179,10 @@ souceType(type: string, view: string){ } // Uploading file + @ViewChild('file', {static: false}) file: ElementRef; name = '!!!'; viewMode = 'tab1'; - uploadProgress: Observable; + uploadProgress = 0; filenames: string[] = []; isHovering: boolean; @@ -189,17 +203,33 @@ souceType(type: string, view: string){ this.fileToUpload = event.target.files[0]; this.fileSize = this.fileToUpload.size; this.fileName = this.fileToUpload.name; - $('.fileuploadspan').text(this.fs.transform(this.fileToUpload.size,false)); + $('.fileuploadspan').text( this.fs.transform(this.fileToUpload.size,false)); } - // To use as Button - onuploadFile(files: FileList) { - // tslint:disable-next-line:prefer-for-of + private subs: Subscription[] = []; + private ngUnsubscribe = new Subject(); + + onuploadFile(files: FileList) { + let form = this.onPrep(this.createForm.value, false); + const upload: Array = []; for (let i = 0; i < files.length; i++) { - this.filenames.push(files[i].name); - console.log(`Uploading ${files[i].name} with size ${files[i].size} and type ${files[i].type}`); - this.uploadService.uploadFile(files[i], files[i].name); + upload.push( + this.uploadService.uploadFile( + files[i], files[i].name, SERV.FILES, form.update, ['/files',this.redirect] + ).pipe(takeUntil(this.ngUnsubscribe)) + .subscribe( + (progress) => { + this.uploadProgress = progress; + // console.log(`Upload progress: ${progress}%`); + } + ) + ) } + // this.reset(); + } + + reset() { + this.file.nativeElement.value = null; } } diff --git a/src/app/hashlists/new-hashlist/new-hashlist.component.html b/src/app/hashlists/new-hashlist/new-hashlist.component.html index e4f48a6c..731f0f6b 100644 --- a/src/app/hashlists/new-hashlist/new-hashlist.component.html +++ b/src/app/hashlists/new-hashlist/new-hashlist.component.html @@ -1,7 +1,7 @@ -
+ Hashcat Brain Enabled 
+
@@ -29,7 +29,7 @@
Instructions
- + @@ -40,102 +40,78 @@
Instructions
ID Type Operating Systems FilenameDownloadActions
{{ b.operatingSystems }} {{ b.filename }} - + + + +
- - - - - -
-
-
-
-
- -
-
-
- -
+ + + + +
+
+
+
+
+
- -
- - - - - - - - - - - - - - - - -
KeyCreatedAction
{{ v.voucher }} - - - {{ v.time | uiDate }} - - - -
- - +
+
+ +
+
+ +
+ + + + + + + + + + + + + + + + +
KeyCreatedAction
{{ v.voucher }} + + + {{ v.time | uiDate }} + + + +
+
+ diff --git a/src/app/agents/new-agent/new-agent.component.ts b/src/app/agents/new-agent/new-agent.component.ts index f10fbb86..3566db0b 100644 --- a/src/app/agents/new-agent/new-agent.component.ts +++ b/src/app/agents/new-agent/new-agent.component.ts @@ -1,5 +1,5 @@ import { faTrash, faDownload, faInfoCircle, faCopy } from '@fortawesome/free-solid-svg-icons'; -import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core'; +import { Component, OnInit, OnDestroy, ViewChild, AfterViewInit } from '@angular/core'; import { FormControl, FormGroup, Validators } from '@angular/forms'; import { environment } from './../../../environments/environment'; import { DataTableDirective } from 'angular-datatables'; @@ -25,15 +25,12 @@ export class NewAgentComponent implements OnInit, OnDestroy { faTrash=faTrash; faCopy=faCopy; - @ViewChild(DataTableDirective, {static: false}) - dtElement: DataTableDirective; + @ViewChild(DataTableDirective, { static: false }) dtElement: DataTableDirective; dtTrigger: Subject = new Subject(); + dtTrigger1: Subject = new Subject(); dtOptions: any = {}; - - ngOnDestroy(): void { - this.dtTrigger.unsubscribe(); - } + dtOptions1: any = {}; createForm: FormGroup binaries: any = []; @@ -66,29 +63,66 @@ export class NewAgentComponent implements OnInit, OnDestroy { 'voucher': new FormControl(''), }); - const params = {'maxResults': this.maxResults} - - this.gs.getAll(SERV.VOUCHER,params).subscribe((vouchers: any) => { - this.vouchers = vouchers.values; - }); - - this.gs.getAll(SERV.AGENT_BINARY).subscribe((bin: any) => { - this.binaries = bin.values; - this.dtTrigger.next(void 0); - }); + const params = {'maxResults': this.maxResults}; + const self = this; this.dtOptions = { dom: 'Bfrtip', + destroy: true, scrollX: true, + searching: false, + paging: false, + info: false, pageLength: 25, + processing: true, lengthMenu: [ [10, 25, 50, 100, 250, -1], [10, 25, 50, 100, 250, 'All'] ], stateSave: true, select: true, + buttons: { + dom: { + button: { + className: 'dt-button buttons-collection btn btn-sm-dt btn-outline-gray-600-dt', + } + }, + buttons: [ + { + text: '↻', + autoClose: true, + action: function (e, dt, node, config) { + self.onRefresh(); + } + } + ], + } }; + this.gs.getAll(SERV.AGENT_BINARY).subscribe((bin: any) => { + this.binaries = bin.values; + this.dtTrigger.next(void 0); + }); + + this.gs.getAll(SERV.VOUCHER,params).subscribe((vouchers: any) => { + this.vouchers = vouchers.values; + this.dtTrigger1.next(void 0); + }); + + } + + ngOnDestroy(): void { + if (this.dtElement.dtInstance) { + this.dtElement.dtInstance.then((dtInstance: DataTables.Api) => { + // Destroy DataTable when the component is destroyed to avoid memory leaks + dtInstance.destroy(); + }); + } + } + + onRefresh(){ + this.rerender(); + this.ngOnInit(); } rerender(): void { @@ -98,6 +132,7 @@ export class NewAgentComponent implements OnInit, OnDestroy { // Call the dtTrigger to rerender again setTimeout(() => { this.dtTrigger['new'].next(); + // this.dtTrigger1['new'].next(); }); }); } @@ -146,6 +181,10 @@ export class NewAgentComponent implements OnInit, OnDestroy { } + downloadClient(id) { + window.location.href= this.agentdownloadURL+id; + } + onSubmit(){ if (this.createForm.valid) { From 407420db5bf6042822ed51b64dee37115bffe52c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Tue, 10 Oct 2023 17:00:45 +0100 Subject: [PATCH 081/419] Tash create page; order binary type by version instead of id --- .../tasks/new-tasks/new-tasks.component.ts | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/app/tasks/new-tasks/new-tasks.component.ts b/src/app/tasks/new-tasks/new-tasks.component.ts index 36256b27..973d8553 100644 --- a/src/app/tasks/new-tasks/new-tasks.component.ts +++ b/src/app/tasks/new-tasks/new-tasks.component.ts @@ -401,11 +401,37 @@ export class NewTasksComponent implements OnInit { const params = {'filter': 'crackerBinaryTypeId='+id+''}; this.gs.getAll(SERV.CRACKERS,params).subscribe((crackers: any) => { this.crackerversions = crackers.values; + crackers.values.sort(this.compareVersions); const lastItem = this.crackerversions.slice(-1)[0]['crackerBinaryId']; this.createForm.get('crackerBinaryTypeId').patchValue(lastItem); }); } + /** + * Compare software versions instead of using id + */ + + compareVersions(a, b): number { + // Split the version strings into arrays of integers + const versionA = a.version.split('.').map(Number); + const versionB = b.version.split('.').map(Number); + + // Compare each segment of the version numbers + for (let i = 0; i < Math.max(versionA.length, versionB.length); i++) { + const partA = versionA[i] || 0; + const partB = versionB[i] || 0; + + if (partA < partB) { + return -1; + } else if (partA > partB) { + return 1; + } + } + + // If all segments are equal, return 0 + return 0; + } + onSubmit(){ if (this.createForm.valid) { this.gs.create(SERV.TASKS,this.createForm.value).subscribe(() => { From eae61c78013cf268ff3795e439e355ed20d55ff7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Wed, 11 Oct 2023 16:54:59 +0100 Subject: [PATCH 082/419] Urgent - When session timed out redirect using visibility api --- src/app/app.component.ts | 4 +- src/app/app.module.ts | 2 + src/app/core/_services/access/auth.service.ts | 46 +++++++++++++++---- .../_services/access/checktoken.service.ts | 45 ++++++++++++++++++ 4 files changed, 85 insertions(+), 12 deletions(-) create mode 100644 src/app/core/_services/access/checktoken.service.ts diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 0f261736..a15b6817 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -15,6 +15,7 @@ import { AuthService } from './core/_services/access/auth.service'; * Idle watching * **/ +import { CheckTokenService } from './core/_services/access/checktoken.service'; import { TimeoutComponent } from './shared/alert/timeout/timeout.component'; import { ModalDismissReasons, NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { ThemeService } from './core/_services/shared/theme.service'; @@ -59,6 +60,7 @@ export class AppComponent implements OnInit { private configService: ConfigService, private cookieService: CookieService, private uicService:UIConfigService, + private checkt: CheckTokenService, private authService: AuthService, private modalService: NgbModal, private themes: ThemeService, @@ -94,7 +96,6 @@ export class AppComponent implements OnInit { this.idleState = "NOT_IDLE."; this.modalRef.componentInstance.timedOut = false; this.timeoutCountdown = null; - console.log(this.idleState); this.reset(); this.closeModal(); }); @@ -103,7 +104,6 @@ export class AppComponent implements OnInit { this.idleState = 'TIMED_OUT'; this.timedOut = true; this.timeoutCountdown = null; - console.log(this.idleState); this.modalRef.componentInstance.timedOut = true; this.onLogOut(); }); diff --git a/src/app/app.module.ts b/src/app/app.module.ts index bb5bb12b..d9190db0 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -3,6 +3,7 @@ * */ import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { CheckTokenService } from './core/_services/access/checktoken.service'; import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http'; import { AppPreloadingStrategy } from './core/app_preloading_strategy'; import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; @@ -76,6 +77,7 @@ import { AuthModule } from './auth/auth.module'; ], providers: [ Title, + CheckTokenService, { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptorService, diff --git a/src/app/core/_services/access/auth.service.ts b/src/app/core/_services/access/auth.service.ts index 54855b65..bf267109 100644 --- a/src/app/core/_services/access/auth.service.ts +++ b/src/app/core/_services/access/auth.service.ts @@ -24,6 +24,7 @@ export class AuthService { isLogged = this.logged.asObservable(); redirectUrl = ''; private userLoggedIn = new Subject(); + private accessToken: string; private tokenExpiration: any; private endpoint = '/auth'; @@ -32,6 +33,7 @@ export class AuthService { private router: Router, private cs: ConfigService, ){ + this.accessToken = localStorage.getItem('userData'); this.userLoggedIn.next(false); if(this.logged){ this.userId = this.getUserId(this.token); @@ -105,18 +107,41 @@ export class AuthService { this.tokenExpiration = setTimeout(() => { const userData: {_token: string, _expires: string, _username: string} = JSON.parse(localStorage.getItem('userData')); return this.http.post(this.cs.getEndpoint() + this.endpoint + '/refresh', {headers: new HttpHeaders({ Authorization: `Bearer ${userData._token}` })}) - .pipe(catchError(this.handleError), tap(resData => { - this.handleAuthentication(resData.token, +resData.expires, userData._username); - })); + .pipe( + tap((response: any) => { + if (response && response.token) { + this.accessToken = response.token; + const expirationDate = new Date(response.expires * 1000); + const user = new User(response.token, expirationDate, userData._username); + localStorage.setItem('userData', JSON.stringify(user)); + } else { + this.logOut(); + } + }) + ); }, expirationDuration); } - refreshToken() { + refreshToken(): Observable { const userData: {_token: string, _expires: string, _username: string} = JSON.parse(localStorage.getItem('userData')); - return this.http.post(this.cs.getEndpoint() + this.endpoint + '/refresh', {headers: new HttpHeaders({ Authorization: `Bearer ${userData._token}` })}) - .pipe(catchError(this.handleError), tap(resData => { - this.handleAuthentication(resData.token, +resData.expires, userData._username); - })); + return this.http.post(this.cs.getEndpoint() + this.endpoint + '/refresh ', {headers: new HttpHeaders({ Authorization: `Bearer ${userData._token}` })}) + .pipe( + tap((response: any) => { + if (response && response.token) { + this.accessToken = response.token; + const expirationDate = new Date(response.expires * 1000); + const user = new User(response.token, expirationDate, userData._username); + localStorage.setItem('userData', JSON.stringify(user)); + } else { + this.logOut(); + } + }), + catchError((error) => { + // Handle the error here + console.error('An error occurred:', error); + return throwError(error); // Rethrow the error for further handling if needed + }) + ); } logOut(){ @@ -142,12 +167,13 @@ export class AuthService { this.authChanged.emit(status); // Raise changed event } - private handleAuthentication(token: string, expires: number, username: string) { + handleAuthentication(token: string, expires: number, username: string) { + console.log('handling auth') const expirationDate = new Date(expires * 1000); // expires, its epoch time in seconds and returns milliseconds sin Jan 1, 1970. We need to multiple by 1000 const user = new User(token, expirationDate, username); this.user.next(user); this.logged.next(true); - this.autologOut(expires) // Epoch time + this.autologOut(expires); // Epoch time localStorage.setItem('userData', JSON.stringify(user)); this.userId = this.getUserId(token); } diff --git a/src/app/core/_services/access/checktoken.service.ts b/src/app/core/_services/access/checktoken.service.ts new file mode 100644 index 00000000..83999225 --- /dev/null +++ b/src/app/core/_services/access/checktoken.service.ts @@ -0,0 +1,45 @@ +import { AuthService } from "./auth.service"; +import { Injectable,} from "@angular/core"; + +export interface AuthResponseData { + token: string, + expires: string, +} + +@Injectable({providedIn: 'root'}) +export class CheckTokenService { + constructor( + private authService: AuthService, + ) { + // We Listen using visibility api to look change events, tab inactive and active + document.addEventListener('visibilitychange', () => { + if (document.visibilityState === 'visible') { + // When tab is now active; we check token validity and refresh if we have time + this.checkTokenValidity(); + } + }); + } + + checkTokenValidity() { + const userData: { _token: string, _expires: string} = JSON.parse(localStorage.getItem('userData')); + if(!userData){ + return; + } + let tokendate = new Date(userData._expires).getTime(); + let currentDate = new Date().getTime(); + let timeDifference = tokendate - currentDate; + // We should be refreshing but when using refresh token, we get an error "Signature verification failure" + // if(timeDifference > 0 && timeDifference < 600){ + // console.log('trying to refresh token') + // this.authService.refreshToken().subscribe( + // (data) => { + // console.log(data) + // } + // ); + // } + if(timeDifference < 15){ + this.authService.logOut(); + } + } + +} From 9c1530328398e60662202d0f2aca2a2ab3113da3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Wed, 18 Oct 2023 09:17:53 +0100 Subject: [PATCH 083/419] Notification alert, make it smaller and refactor code --- .../edit-notification.component.ts | 28 +++----- .../new-notification.component.ts | 27 +++----- .../notifications/notifications.component.ts | 38 +++++------ .../acc-settings/acc-settings.component.ts | 22 +++--- .../ui-settings/ui-settings.component.ts | 20 ++---- .../agents/edit-agent/edit-agent.component.ts | 16 ++--- .../agents/new-agent/new-agent.component.ts | 30 +++------ .../show-agents/show-agents.component.ts | 47 +++---------- .../agent-binaries.component.ts | 23 ++----- .../new-agent-binaries.component.ts | 26 ++----- .../engine/crackers/crackers.component.ts | 10 +-- .../edit-version/edit-crackers.component.ts | 33 +++------ .../new-cracker/new-cracker.component.ts | 17 ++--- .../new-version/new-crackers.component.ts | 17 ++--- .../new-preprocessor.component.ts | 26 ++----- .../preprocessors/preprocessors.component.ts | 16 ++--- .../hashtypes/hashtype/hashtype.component.ts | 24 ++----- .../config/hashtypes/hashtypes.component.ts | 33 +++------ .../health-checks/health-checks.component.ts | 16 ++--- .../new-health-checks.component.ts | 15 ++--- src/app/config/server/server.component.ts | 22 ++---- .../core/_directives/copy-button.directive.ts | 28 ++------ .../core/_services/shared/alert.service.ts | 37 ++++++++++ .../files/files-edit/files-edit.component.ts | 22 ++---- src/app/files/files.component.ts | 39 +++-------- .../files/new-files/new-files.component.ts | 17 ++--- .../edit-hashlist/edit-hashlist.component.ts | 18 ++--- .../hashlists/hashlist/hashlist.component.ts | 42 +++--------- .../new-hashlist/new-hashlist.component.ts | 12 +--- .../new-superhashlist.component.ts | 17 ++--- .../superhashlist/superhashlist.component.ts | 16 ++--- .../edit-preconfigured-tasks.component.ts | 18 ++--- .../edit-supertasks.component.ts | 26 ++----- .../tasks/edit-tasks/edit-tasks.component.ts | 67 ++++--------------- .../masks/masks.component.ts | 16 ++--- .../wrbulk/wrbulk.component.ts | 11 +-- .../new-preconfigured-tasks.component.ts | 23 ++----- .../new-supertasks.component.ts | 17 ++--- .../tasks/new-tasks/new-tasks.component.ts | 23 ++----- .../preconfigured-tasks.component.ts | 17 ++--- .../modal-subtasks.component.ts | 23 ++----- .../tasks/show-tasks/show-tasks.component.ts | 65 ++++-------------- .../supertasks/applyhashlist.component.ts | 16 ++--- .../tasks/supertasks/supertasks.component.ts | 17 ++--- .../users/all-users/all-users.component.ts | 37 +++------- .../users/edit-users/edit-users.component.ts | 26 ++----- .../edit-globalpermissionsgroups.component.ts | 17 ++--- .../globalpermissionsgroups.component.ts | 35 +++------- .../new-globalpermissionsgroups.component.ts | 13 +--- .../groups/cu-group/cu-group.component.ts | 21 ++---- src/app/users/groups/groups.component.ts | 34 +++------- src/app/users/users.component.ts | 14 ++-- 52 files changed, 378 insertions(+), 912 deletions(-) create mode 100644 src/app/core/_services/shared/alert.service.ts diff --git a/src/app/account/notifications/notification/edit-notification.component.ts b/src/app/account/notifications/notification/edit-notification.component.ts index 4f551ea4..2b63fdc8 100644 --- a/src/app/account/notifications/notification/edit-notification.component.ts +++ b/src/app/account/notifications/notification/edit-notification.component.ts @@ -1,15 +1,15 @@ -import { Subscription } from 'rxjs'; -import { FormControl, FormGroup } from '@angular/forms'; import { Component, OnDestroy, OnInit } from '@angular/core'; -import Swal from 'sweetalert2/dist/sweetalert2.js'; +import { FormControl, FormGroup } from '@angular/forms'; +import { Subscription } from 'rxjs'; + import { ACTIONARRAY, NOTIFARRAY } from '../../../core/_constants/notifications.config'; +import { AutoTitleService } from 'src/app/core/_services/shared/autotitle.service'; +import { AlertService } from 'src/app/core/_services/shared/alert.service'; import { GlobalService } from 'src/app/core/_services/main.service'; import { ActivatedRoute, Params, Router } from '@angular/router'; +import { Notification } from 'src/app/core/_models/notifications'; import { SERV } from '../../../core/_services/main.config'; -import { AutoTitleService } from 'src/app/core/_services/shared/autotitle.service'; import { Filter } from '../notifications.component'; -import { Notification } from 'src/app/core/_models/notifications'; - @Component({ selector: 'app-edit-notification', @@ -40,9 +40,10 @@ export class EditNotificationComponent implements OnInit, OnDestroy { }); constructor( + private titleService: AutoTitleService, private route: ActivatedRoute, + private alert: AlertService, private gs: GlobalService, - private titleService: AutoTitleService, private router: Router ) { titleService.set(['Edit Notification']) @@ -73,7 +74,7 @@ export class EditNotificationComponent implements OnInit, OnDestroy { /** * Checks whether the form is valid for submission. - * + * * @returns {boolean} True if there is a change in the 'isActive' value; otherwise, false. */ formIsValid(): boolean { @@ -105,16 +106,9 @@ export class EditNotificationComponent implements OnInit, OnDestroy { onSubmit(): void { if (this.form.valid) { this.subscriptions.push(this.gs.update(SERV.NOTIFICATIONS, this.editedIndex, { 'isActive': this.form.value['isActive'] }).subscribe(() => { - Swal.fire({ - position: 'top-end', - backdrop: false, - icon: 'success', - title: 'Saved', - showConfirmButton: false, - timer: 1500 - }) + this.alert.okAlert('Notification saved!',''); this.router.navigate(['/account/notifications']); })); } } -} \ No newline at end of file +} diff --git a/src/app/account/notifications/notification/new-notification.component.ts b/src/app/account/notifications/notification/new-notification.component.ts index cae9d743..126e3f25 100644 --- a/src/app/account/notifications/notification/new-notification.component.ts +++ b/src/app/account/notifications/notification/new-notification.component.ts @@ -1,16 +1,16 @@ import { FormControl, FormGroup, Validators } from '@angular/forms'; import { Component, OnDestroy, OnInit } from '@angular/core'; -import { Subscription } from 'rxjs'; import { Router } from '@angular/router'; -import Swal from 'sweetalert2/dist/sweetalert2.js'; +import { Subscription } from 'rxjs'; + import { ACTIONARRAY, ACTION, NOTIFARRAY } from '../../../core/_constants/notifications.config'; +import { AutoTitleService } from 'src/app/core/_services/shared/autotitle.service'; +import { AlertService } from 'src/app/core/_services/shared/alert.service'; import { environment } from '../../../../environments/environment'; import { GlobalService } from 'src/app/core/_services/main.service'; import { SERV } from '../../../core/_services/main.config'; -import { AutoTitleService } from 'src/app/core/_services/shared/autotitle.service'; import { Filter } from '../notifications.component'; - @Component({ selector: 'app-new-notification', templateUrl: './new-notification.component.html' @@ -51,8 +51,9 @@ export class NewNotificationComponent implements OnInit, OnDestroy { maxResults = environment.config.prodApiMaxResults; constructor( - private gs: GlobalService, private titleService: AutoTitleService, + private alert: AlertService, + private gs: GlobalService, private router: Router ) { titleService.set(['New Notification']) @@ -91,7 +92,7 @@ export class NewNotificationComponent implements OnInit, OnDestroy { /** * Handles the change of action for creating a new notification. * Updates the available filters based on the selected action. - * + * * @param {string} action - The selected action. */ changeAction(action: string): void { @@ -122,7 +123,7 @@ export class NewNotificationComponent implements OnInit, OnDestroy { /** * Checks if the form for creating a new notification is valid. - * + * * @returns {boolean} True if the form is valid; otherwise, false. */ formIsValid(): boolean { @@ -136,17 +137,9 @@ export class NewNotificationComponent implements OnInit, OnDestroy { onSubmit(): void { if (this.form.valid) { this.subscriptions.push(this.gs.create(SERV.NOTIFICATIONS, this.form.value).subscribe(() => { - Swal.fire({ - position: 'top-end', - backdrop: false, - icon: 'success', - title: 'Success!', - text: 'New Notification created!', - showConfirmButton: false, - timer: 1500 - }) + this.alert.okAlert('New Notification created!',''); this.router.navigate(['/account/notifications']); })); } } -} \ No newline at end of file +} diff --git a/src/app/account/notifications/notifications.component.ts b/src/app/account/notifications/notifications.component.ts index 1eff72aa..dd9b9e74 100644 --- a/src/app/account/notifications/notifications.component.ts +++ b/src/app/account/notifications/notifications.component.ts @@ -1,16 +1,17 @@ import { faTrash, faPlus, faEye, faEdit } from '@fortawesome/free-solid-svg-icons'; -import { environment } from './../../../environments/environment'; import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core'; +import { environment } from './../../../environments/environment'; import { DataTableDirective } from 'angular-datatables'; import Swal from 'sweetalert2/dist/sweetalert2.js'; import { Subject, Subscription } from 'rxjs'; + +import { AutoTitleService } from 'src/app/core/_services/shared/autotitle.service'; +import { NotificationListResponse } from 'src/app/core/_models/notifications'; +import { AlertService } from 'src/app/core/_services/shared/alert.service'; import { ACTION } from '../../core/_constants/notifications.config'; import { GlobalService } from 'src/app/core/_services/main.service'; -import { SERV } from '../../core/_services/main.config'; -import { NotificationListResponse } from 'src/app/core/_models/notifications'; import { Notification } from 'src/app/core/_models/notifications'; -import { AutoTitleService } from 'src/app/core/_services/shared/autotitle.service'; - +import { SERV } from '../../core/_services/main.config'; export interface Filter { id: number, @@ -44,8 +45,9 @@ export class NotificationsComponent implements OnInit, OnDestroy { private maxResults = environment.config.prodApiMaxResults; constructor( - private gs: GlobalService, private titleService: AutoTitleService, + private alert: AlertService, + private gs: GlobalService ) { titleService.set(['Notifications']) } @@ -208,13 +210,13 @@ export class NotificationsComponent implements OnInit, OnDestroy { icon: 'warning', reverseButtons: true, showCancelButton: true, - cancelButtonColor: '#8A8584', - confirmButtonColor: '#C53819', - confirmButtonText: 'Yes, delete it!' + cancelButtonColor: this.alert.cancelButtonColor, + confirmButtonColor: this.alert.confirmButtonColor, + confirmButtonText: this.alert.delconfirmText }) .then((result) => { if (result.isConfirmed) { - this.deleteNotification(id) + this.deleteNotification(id,name) } else { swalWithBootstrapButtons.fire({ title: 'Cancelled', @@ -229,20 +231,14 @@ export class NotificationsComponent implements OnInit, OnDestroy { /** * Handles the deletion of a notification. - * Sends an delete request to the backend and displays a success alert and rebuilds + * Sends an delete request to the backend and displays a success alert and rebuilds * the datatable on success. - * + * * @param {number} id - The ID of the notification to delete. */ - deleteNotification(id: number): void { + deleteNotification(id: number, name: string): void { this.subscriptions.push(this.gs.delete(SERV.NOTIFICATIONS, id).subscribe(() => { - Swal.fire({ - position: 'top-end', - backdrop: false, - icon: 'success', - showConfirmButton: false, - timer: 1500 - }) + this.alert.okAlert('Deleted '+name+'',''); this.getNotifications(); this.rerender(); this.setupTable(); @@ -296,4 +292,4 @@ export class NotificationsComponent implements OnInit, OnDestroy { } if (type) { return path; } else { return title; } } -} \ No newline at end of file +} diff --git a/src/app/account/settings/acc-settings/acc-settings.component.ts b/src/app/account/settings/acc-settings/acc-settings.component.ts index d5d52d48..0e1f4b92 100644 --- a/src/app/account/settings/acc-settings/acc-settings.component.ts +++ b/src/app/account/settings/acc-settings/acc-settings.component.ts @@ -1,14 +1,14 @@ import { FormControl, FormGroup, Validators } from '@angular/forms'; -import { Router } from '@angular/router'; -import Swal from 'sweetalert2/dist/sweetalert2.js'; import { Component, OnDestroy, OnInit } from '@angular/core'; +import { Router } from '@angular/router'; + +import { passwordMatchValidator } from 'src/app/core/_validators/password.validator'; +import { AutoTitleService } from 'src/app/core/_services/shared/autotitle.service'; +import { AlertService } from 'src/app/core/_services/shared/alert.service'; import { GlobalService } from 'src/app/core/_services/main.service'; import { SERV } from '../../../core/_services/main.config'; import { uiDatePipe } from 'src/app/core/_pipes/date.pipe'; -import { AutoTitleService } from 'src/app/core/_services/shared/autotitle.service'; import { Subscription } from 'rxjs'; -import { passwordMatchValidator } from 'src/app/core/_validators/password.validator'; - @Component({ selector: 'app-acc-settings', @@ -27,6 +27,7 @@ export class AccountSettingsComponent implements OnInit, OnDestroy { constructor( private titleService: AutoTitleService, private datePipe: uiDatePipe, + private alert: AlertService, private gs: GlobalService, private router: Router ) { @@ -52,7 +53,7 @@ export class AccountSettingsComponent implements OnInit, OnDestroy { /** * Creates and configures the Angular FormGroup for managing form controls. - * + * * @param {string} name - Account type name. * @param {string} registeredSince - Account registration date. * @param {string} email - The user's email. @@ -91,14 +92,7 @@ export class AccountSettingsComponent implements OnInit, OnDestroy { onSubmit() { if (this.form.valid) { this.subscriptions.push(this.gs.create(SERV.USERS, this.form.value).subscribe(() => { - Swal.fire({ - position: 'top-end', - backdrop: false, - icon: 'success', - title: 'Saved', - showConfirmButton: false, - timer: 1500 - }) + this.alert.okAlert('User saved!',''); this.router.navigate(['users/all-users']); })); } diff --git a/src/app/account/settings/ui-settings/ui-settings.component.ts b/src/app/account/settings/ui-settings/ui-settings.component.ts index e9f42ef7..6e04b916 100644 --- a/src/app/account/settings/ui-settings/ui-settings.component.ts +++ b/src/app/account/settings/ui-settings/ui-settings.component.ts @@ -1,9 +1,10 @@ import { CookieService } from '../../../core/_services/shared/cookies.service'; import { dateFormat } from '../../../core/_constants/settings.config'; import { FormControl, FormGroup } from '@angular/forms'; -import Swal from 'sweetalert2/dist/sweetalert2.js'; import { Component, OnInit } from '@angular/core'; +import { AlertService } from 'src/app/core/_services/shared/alert.service'; + @Component({ selector: 'app-ui-settings', templateUrl: './ui-settings.component.html' @@ -14,7 +15,8 @@ export class UiSettingsComponent implements OnInit { uiForm: FormGroup; constructor( - private cookieService: CookieService + private cookieService: CookieService, + private alert: AlertService ) { } @@ -40,7 +42,7 @@ export class UiSettingsComponent implements OnInit { setCookieValue(name: string, value: string){ this.cookieService.setCookie(name, value, 365); - this.savedAlert(); + this.alert.okAlert('UI Setting saved!',''); this.ngOnInit(); } @@ -48,16 +50,4 @@ export class UiSettingsComponent implements OnInit { return this.cookieService.getCookie(name); } - savedAlert(){ - Swal.fire({ - position: 'top-end', - backdrop: false, - icon: 'success', - title: 'Saved', - showConfirmButton: false, - timer: 1500 - }) - } - - } diff --git a/src/app/agents/edit-agent/edit-agent.component.ts b/src/app/agents/edit-agent/edit-agent.component.ts index 975e3a30..ac716a59 100644 --- a/src/app/agents/edit-agent/edit-agent.component.ts +++ b/src/app/agents/edit-agent/edit-agent.component.ts @@ -7,7 +7,6 @@ import { Component, OnInit, ViewChild } from '@angular/core'; import { ASC } from '../../core/_constants/agentsc.config'; import { UniversalTransition } from 'echarts/features'; import { DataTableDirective } from 'angular-datatables'; -import Swal from 'sweetalert2/dist/sweetalert2.js'; import { CanvasRenderer } from 'echarts/renderers'; import { LineChart } from 'echarts/charts'; import * as echarts from 'echarts/core'; @@ -18,6 +17,7 @@ import { GlobalService } from 'src/app/core/_services/main.service'; import { environment } from './../../../environments/environment'; import { PageTitle } from 'src/app/core/_decorators/autotitle'; import { SERV } from '../../core/_services/main.config'; +import { AlertService } from 'src/app/core/_services/shared/alert.service'; @Component({ selector: 'app-edit-agent', @@ -45,6 +45,7 @@ export class EditAgentComponent implements OnInit { constructor( private uiService: UIConfigService, private route:ActivatedRoute, + private alert: AlertService, private gs: GlobalService, private router: Router ) { } @@ -191,16 +192,9 @@ export class EditAgentComponent implements OnInit { if (this.updateForm.valid) { this.onUpdateAssign(this.updateAssignForm.value); this.gs.update(SERV.AGENTS,this.editedAgentIndex,this.updateForm.value).subscribe(() => { - Swal.fire({ - position: 'top-end', - backdrop: false, - icon: 'success', - title: "Saved", - showConfirmButton: false, - timer: 1500 - }) - this.updateForm.reset(); // success, we reset form - this.router.navigate(['agents/show-agents']); + this.alert.okAlert('Agent saved!',''); + this.updateForm.reset(); // success, we reset form + this.router.navigate(['agents/show-agents']); }); } } diff --git a/src/app/agents/new-agent/new-agent.component.ts b/src/app/agents/new-agent/new-agent.component.ts index 3566db0b..f22282a0 100644 --- a/src/app/agents/new-agent/new-agent.component.ts +++ b/src/app/agents/new-agent/new-agent.component.ts @@ -8,6 +8,7 @@ import { Subject } from 'rxjs'; import { UIConfigService } from 'src/app/core/_services/shared/storage.service'; import { ConfigService } from 'src/app/core/_services/shared/config.service'; +import { AlertService } from 'src/app/core/_services/shared/alert.service'; import { GlobalService } from 'src/app/core/_services/main.service'; import { PageTitle } from 'src/app/core/_decorators/autotitle'; import { SERV } from '../../core/_services/main.config'; @@ -40,6 +41,7 @@ export class NewAgentComponent implements OnInit, OnDestroy { constructor( private uiService: UIConfigService, + private alert: AlertService, private gs: GlobalService, private cs:ConfigService ) { } @@ -151,20 +153,14 @@ export class NewAgentComponent implements OnInit, OnDestroy { icon: "warning", reverseButtons: true, showCancelButton: true, - cancelButtonColor: '#8A8584', - confirmButtonColor: '#C53819', - confirmButtonText: 'Yes, delete it!' + cancelButtonColor: this.alert.cancelButtonColor, + confirmButtonColor: this.alert.confirmButtonColor, + confirmButtonText: this.alert.delconfirmText }) .then((result) => { if (result.isConfirmed) { this.gs.delete(SERV.VOUCHER,id).subscribe(() => { - Swal.fire({ - position: 'top-end', - backdrop: false, - icon: 'success', - showConfirmButton: false, - timer: 1500 - }) + this.alert.okAlert('Deleted '+name+'',''); this.ngOnInit(); this.rerender(); // rerender datatables }); @@ -189,17 +185,9 @@ export class NewAgentComponent implements OnInit, OnDestroy { if (this.createForm.valid) { this.gs.create(SERV.VOUCHER,this.createForm.value).subscribe(() => { - Swal.fire({ - position: 'top-end', - backdrop: false, - icon: 'success', - title: "Success!", - text: "New Voucher created!", - showConfirmButton: false, - timer: 1500 - }) - this.ngOnInit(); - this.rerender(); // rerender datatables + this.alert.okAlert('New Voucher created!',''); + this.ngOnInit(); + this.rerender(); // rerender datatables } ); } diff --git a/src/app/agents/show-agents/show-agents.component.ts b/src/app/agents/show-agents/show-agents.component.ts index 5fb404c2..b541adcb 100644 --- a/src/app/agents/show-agents/show-agents.component.ts +++ b/src/app/agents/show-agents/show-agents.component.ts @@ -1,4 +1,3 @@ - import { faEdit, faLock, faPauseCircle,faHomeAlt, faPlus, faFileText, faTrash, faCheckCircle, faArrowCircleDown, faMicrochip, faTerminal} from '@fortawesome/free-solid-svg-icons'; import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core'; import { environment } from 'src/environments/environment'; @@ -7,6 +6,7 @@ import Swal from 'sweetalert2/dist/sweetalert2.js'; import {Subject} from 'rxjs'; import { UIConfigService } from 'src/app/core/_services/shared/storage.service'; +import { AlertService } from 'src/app/core/_services/shared/alert.service'; import { GlobalService } from 'src/app/core/_services/main.service'; import { PageTitle } from 'src/app/core/_decorators/autotitle'; import { SERV } from '../../core/_services/main.config'; @@ -50,7 +50,8 @@ export class ShowAgentsComponent implements OnInit, OnDestroy { constructor( private uiService: UIConfigService, - private gs: GlobalService, + private alert: AlertService, + private gs: GlobalService ) { } ngOnInit(): void { @@ -228,13 +229,7 @@ export class ShowAgentsComponent implements OnInit, OnDestroy { this.ngOnInit(); this.rerender(); // rerender datatables Swal.close(); - Swal.fire({ - position: 'top-end', - backdrop: false, - icon: 'success', - showConfirmButton: false, - timer: 1500 - }) + this.alert.okAlert('Completed!',''); },3000); } @@ -242,14 +237,7 @@ export class ShowAgentsComponent implements OnInit, OnDestroy { $(".dt-button-background").trigger("click"); const selection = $($(this.dtElement).DataTable.tables()).DataTable().rows({ selected: true } ).data().pluck(0).toArray(); if(selection.length == 0) { - Swal.fire({ - position: 'top-end', - backdrop: false, - title: "You haven't selected any Agent", - type: 'success', - timer: 1500, - showConfirmButton: false - }) + this.alert.okAlert('You haven not selected any Group',''); return; } const selectionnum = selection.map(i=>Number(i)); @@ -293,7 +281,7 @@ export class ShowAgentsComponent implements OnInit, OnDestroy { }, ); }); - self.onDone(sellen); + self.onDone(sellen); } onModal(title: string){ @@ -302,14 +290,7 @@ export class ShowAgentsComponent implements OnInit, OnDestroy { $(".dt-button-background").trigger("click"); const selection = $($(this.dtElement).DataTable.tables()).DataTable().rows({ selected: true } ).data().pluck(0).toArray(); if(selection.length == 0) { - Swal.fire({ - position: 'top-end', - backdrop: false, - title: "You haven't selected any Agent", - type: 'success', - timer: 1500, - showConfirmButton: false - }) + this.alert.okAlert('You have not selected any Agent',''); return; } @@ -351,20 +332,14 @@ export class ShowAgentsComponent implements OnInit, OnDestroy { icon: "warning", reverseButtons: true, showCancelButton: true, - cancelButtonColor: '#8A8584', - confirmButtonColor: '#C53819', - confirmButtonText: 'Yes, delete it!' + cancelButtonColor: this.alert.cancelButtonColor, + confirmButtonColor: this.alert.confirmButtonColor, + confirmButtonText: this.alert.delconfirmText }) .then((result) => { if (result.isConfirmed) { this.gs.delete(SERV.AGENTS,id).subscribe(() => { - Swal.fire({ - position: 'top-end', - backdrop: false, - icon: 'success', - showConfirmButton: false, - timer: 1500 - }) + this.alert.okAlert('Deleted '+name+'',''); this.ngOnInit(); this.rerender(); // rerender datatables }); diff --git a/src/app/config/engine/agent-binaries/agent-binaries.component.ts b/src/app/config/engine/agent-binaries/agent-binaries.component.ts index 00b0be56..46e9ffce 100644 --- a/src/app/config/engine/agent-binaries/agent-binaries.component.ts +++ b/src/app/config/engine/agent-binaries/agent-binaries.component.ts @@ -4,6 +4,7 @@ import { DataTableDirective } from 'angular-datatables'; import Swal from 'sweetalert2/dist/sweetalert2.js'; import { Subject } from 'rxjs'; +import { AlertService } from 'src/app/core/_services/shared/alert.service'; import { environment } from './../../../../environments/environment'; import { GlobalService } from 'src/app/core/_services/main.service'; import { PageTitle } from 'src/app/core/_decorators/autotitle'; @@ -33,6 +34,7 @@ export class AgentBinariesComponent implements OnInit { private maxResults = environment.config.prodApiMaxResults; constructor( + private alert: AlertService, private gs: GlobalService, ) { } @@ -118,12 +120,7 @@ export class AgentBinariesComponent implements OnInit { } onSubmit(){ - Swal.fire({ - title: "Success", - text: "New Binary created!", - icon: "success", - button: "Close", - }); + this.alert.okAlert('New Binary created!',''); } rerender(): void { @@ -150,20 +147,14 @@ export class AgentBinariesComponent implements OnInit { icon: "warning", reverseButtons: true, showCancelButton: true, - cancelButtonColor: '#8A8584', - confirmButtonColor: '#C53819', - confirmButtonText: 'Yes, delete it!' + cancelButtonColor: this.alert.cancelButtonColor, + confirmButtonColor: this.alert.confirmButtonColor, + confirmButtonText: this.alert.delconfirmText }) .then((result) => { if (result.isConfirmed) { this.gs.delete(SERV.AGENT_BINARY,id).subscribe(() => { - Swal.fire({ - position: 'top-end', - backdrop: false, - icon: 'success', - showConfirmButton: false, - timer: 1500 - }) + this.alert.okAlert('Deleted '+name+'',''); this.ngOnInit(); this.rerender(); // rerender datatables }); diff --git a/src/app/config/engine/agent-binaries/agent-binary/new-agent-binaries.component.ts b/src/app/config/engine/agent-binaries/agent-binary/new-agent-binaries.component.ts index 80f9e264..b5f5bc82 100644 --- a/src/app/config/engine/agent-binaries/agent-binary/new-agent-binaries.component.ts +++ b/src/app/config/engine/agent-binaries/agent-binary/new-agent-binaries.component.ts @@ -1,13 +1,13 @@ import { faHomeAlt, faPlus, faTrash, faEdit } from '@fortawesome/free-solid-svg-icons'; import { FormControl, FormGroup } from '@angular/forms'; import { ActivatedRoute, Params } from '@angular/router'; -import Swal from 'sweetalert2/dist/sweetalert2.js'; import { Component, OnInit } from '@angular/core'; import { Router } from '@angular/router'; import { GlobalService } from 'src/app/core/_services/main.service'; import { PageTitle } from 'src/app/core/_decorators/autotitle'; import { SERV } from '../../../../core/_services/main.config'; +import { AlertService } from 'src/app/core/_services/shared/alert.service'; @Component({ selector: 'app-agent-binaries', @@ -29,6 +29,7 @@ export class NewAgentBinariesComponent implements OnInit { constructor( private route:ActivatedRoute, + private alert: AlertService, private gs: GlobalService, private router:Router ) { } @@ -84,16 +85,8 @@ export class NewAgentBinariesComponent implements OnInit { this.gs.create(SERV.AGENT_BINARY,this.updateForm.value) .subscribe((prep: any) => { const response = prep; - Swal.fire({ - position: 'top-end', - backdrop: false, - icon: 'success', - title: "Success!", - text: "New Agent Binary created!", - showConfirmButton: false, - timer: 1500 - }) - this.router.navigate(['config/engine/agent-binaries']); + this.alert.okAlert('New Agent Binary created!',''); + this.router.navigate(['config/engine/agent-binaries']); } ); break; @@ -103,15 +96,8 @@ export class NewAgentBinariesComponent implements OnInit { this.gs.update(SERV.AGENT_BINARY,id,this.updateForm.value) .subscribe((prep: any) => { const response = prep; - Swal.fire({ - position: 'top-end', - backdrop: false, - icon: 'success', - title: "Saved", - showConfirmButton: false, - timer: 1500 - }) - this.router.navigate(['config/engine/agent-binaries']); + this.alert.okAlert('Agent Binary saved!',''); + this.router.navigate(['config/engine/agent-binaries']); } ); break; diff --git a/src/app/config/engine/crackers/crackers.component.ts b/src/app/config/engine/crackers/crackers.component.ts index bbfff62d..cf6ff3ce 100644 --- a/src/app/config/engine/crackers/crackers.component.ts +++ b/src/app/config/engine/crackers/crackers.component.ts @@ -4,6 +4,7 @@ import { environment } from './../../../../environments/environment'; import Swal from 'sweetalert2/dist/sweetalert2.js'; import { Subject } from 'rxjs'; +import { AlertService } from 'src/app/core/_services/shared/alert.service'; import { GlobalService } from 'src/app/core/_services/main.service'; import { PageTitle } from 'src/app/core/_decorators/autotitle'; import { SERV } from '../../../core/_services/main.config'; @@ -35,6 +36,7 @@ export class CrackersComponent implements OnInit, OnDestroy { crackerType: any = []; constructor( + private alert: AlertService, private gs: GlobalService, ) { } @@ -140,13 +142,7 @@ export class CrackersComponent implements OnInit, OnDestroy { .then((willDelete) => { if (willDelete) { this.gs.delete(SERV.CRACKERS_TYPES,id).subscribe(() => { - Swal.fire({ - position: 'top-end', - backdrop: false, - icon: 'success', - showConfirmButton: false, - timer: 1500 - }) + this.alert.okAlert('Deleted '+name+'',''); }); } else { Swal.fire("Your Cracker is safe!") diff --git a/src/app/config/engine/crackers/edit-version/edit-crackers.component.ts b/src/app/config/engine/crackers/edit-version/edit-crackers.component.ts index ef085435..40d4d2eb 100644 --- a/src/app/config/engine/crackers/edit-version/edit-crackers.component.ts +++ b/src/app/config/engine/crackers/edit-version/edit-crackers.component.ts @@ -1,9 +1,10 @@ -import { Component, OnInit } from '@angular/core'; -import { ActivatedRoute, Params, Router } from '@angular/router'; import { FormControl, FormGroup, Validators } from '@angular/forms'; +import { ActivatedRoute, Params, Router } from '@angular/router'; import { faDownload } from '@fortawesome/free-solid-svg-icons'; import Swal from 'sweetalert2/dist/sweetalert2.js'; +import { Component, OnInit } from '@angular/core'; +import { AlertService } from 'src/app/core/_services/shared/alert.service'; import { GlobalService } from 'src/app/core/_services/main.service'; import { PageTitle } from 'src/app/core/_decorators/autotitle'; import { SERV } from '../../../../core/_services/main.config'; @@ -23,6 +24,7 @@ export class EditCrackersComponent implements OnInit { constructor( private route:ActivatedRoute, + private alert: AlertService, private gs: GlobalService, private router: Router ) { } @@ -50,16 +52,9 @@ export class EditCrackersComponent implements OnInit { if (this.updateForm.valid) { this.gs.update(SERV.CRACKERS,this.editedCrackervIndex,this.updateForm.value).subscribe(() => { - Swal.fire({ - position: 'top-end', - backdrop: false, - icon: 'success', - title: "Saved", - showConfirmButton: false, - timer: 1500 - }) - this.updateForm.reset(); // success, we reset form - this.router.navigate(['config/engine/crackers']); + this.alert.okAlert('Cracker saved!',''); + this.updateForm.reset(); // success, we reset form + this.router.navigate(['config/engine/crackers']); } ); } @@ -78,20 +73,14 @@ export class EditCrackersComponent implements OnInit { icon: "warning", reverseButtons: true, showCancelButton: true, - cancelButtonColor: '#8A8584', - confirmButtonColor: '#C53819', - confirmButtonText: 'Yes, delete it!' + cancelButtonColor: this.alert.cancelButtonColor, + confirmButtonColor: this.alert.confirmButtonColor, + confirmButtonText: this.alert.delconfirmText }) .then((result) => { if (result.isConfirmed) { this.gs.delete(SERV.CRACKERS,this.editedCrackervIndex).subscribe(() => { - Swal.fire({ - position: 'top-end', - backdrop: false, - icon: 'success', - showConfirmButton: false, - timer: 1500 - }) + this.alert.okAlert('Deleted ',''); this.router.navigate(['config/engine/crackers']); }); } else { diff --git a/src/app/config/engine/crackers/new-cracker/new-cracker.component.ts b/src/app/config/engine/crackers/new-cracker/new-cracker.component.ts index 10a4c5b7..69183ace 100644 --- a/src/app/config/engine/crackers/new-cracker/new-cracker.component.ts +++ b/src/app/config/engine/crackers/new-cracker/new-cracker.component.ts @@ -1,8 +1,8 @@ import { PageTitle } from 'src/app/core/_decorators/autotitle'; -import Swal from 'sweetalert2/dist/sweetalert2.js'; import { Component, OnInit } from '@angular/core'; import { Router } from '@angular/router'; +import { AlertService } from 'src/app/core/_services/shared/alert.service'; import { GlobalService } from 'src/app/core/_services/main.service'; import { SERV } from '../../../../core/_services/main.config'; import { FormControl, FormGroup } from '@angular/forms'; @@ -17,6 +17,7 @@ export class NewCrackerComponent implements OnInit { createForm: FormGroup; constructor( + private alert: AlertService, private gs: GlobalService, private router:Router ) { } @@ -34,17 +35,9 @@ export class NewCrackerComponent implements OnInit { if (this.createForm.valid) { this.gs.create(SERV.CRACKERS_TYPES, this.createForm.value).subscribe(() => { - Swal.fire({ - position: 'top-end', - backdrop: false, - icon: 'success', - title: "Success!", - text: "New Cracker created!", - showConfirmButton: false, - timer: 1500 - }) - this.createForm.reset(); // success, we reset form - this.router.navigate(['/config/engine/crackers']); + this.alert.okAlert('New Cracker created!',''); + this.createForm.reset(); // success, we reset form + this.router.navigate(['/config/engine/crackers']); } ); } diff --git a/src/app/config/engine/crackers/new-version/new-crackers.component.ts b/src/app/config/engine/crackers/new-version/new-crackers.component.ts index 4b7954b4..7d3b75a8 100644 --- a/src/app/config/engine/crackers/new-version/new-crackers.component.ts +++ b/src/app/config/engine/crackers/new-version/new-crackers.component.ts @@ -1,13 +1,13 @@ import { faDownload, faInfoCircle } from '@fortawesome/free-solid-svg-icons'; import { FormControl, FormGroup, Validators } from '@angular/forms'; import { ActivatedRoute, Params } from '@angular/router'; -import Swal from 'sweetalert2/dist/sweetalert2.js'; import { Component, OnInit } from '@angular/core'; import { Router } from '@angular/router'; import { GlobalService } from 'src/app/core/_services/main.service'; import { PageTitle } from 'src/app/core/_decorators/autotitle'; import { SERV } from '../../../../core/_services/main.config'; +import { AlertService } from 'src/app/core/_services/shared/alert.service'; @Component({ selector: 'app-new-crackers', @@ -25,6 +25,7 @@ export class NewCrackersComponent implements OnInit { constructor( private route:ActivatedRoute, + private alert: AlertService, private gs: GlobalService, private router: Router ) { } @@ -51,17 +52,9 @@ export class NewCrackersComponent implements OnInit { if (this.createForm.valid) { this.gs.create(SERV.CRACKERS, this.createForm.value).subscribe(() => { - Swal.fire({ - position: 'top-end', - backdrop: false, - icon: 'success', - title: "Success!", - text: "New Version created!", - showConfirmButton: false, - timer: 1500 - }) - this.createForm.reset(); // success, we reset form - this.router.navigate(['/config/engine/crackers']); + this.alert.okAlert('New Version created!',''); + this.createForm.reset(); // success, we reset form + this.router.navigate(['/config/engine/crackers']); } ); } diff --git a/src/app/config/engine/preprocessors/preprocessor/new-preprocessor.component.ts b/src/app/config/engine/preprocessors/preprocessor/new-preprocessor.component.ts index 5604dcca..8d5ebaef 100644 --- a/src/app/config/engine/preprocessors/preprocessor/new-preprocessor.component.ts +++ b/src/app/config/engine/preprocessors/preprocessor/new-preprocessor.component.ts @@ -1,8 +1,8 @@ import { FormControl, FormGroup, Validators } from '@angular/forms'; import { ActivatedRoute, Router, Params } from '@angular/router'; import { Component, OnInit } from '@angular/core'; -import Swal from 'sweetalert2/dist/sweetalert2.js'; +import { AlertService } from 'src/app/core/_services/shared/alert.service'; import { GlobalService } from 'src/app/core/_services/main.service'; import { PageTitle } from 'src/app/core/_decorators/autotitle'; import { SERV } from '../../../../core/_services/main.config'; @@ -20,6 +20,7 @@ export class NewPreprocessorComponent implements OnInit { constructor( private route:ActivatedRoute, + private alert: AlertService, private gs: GlobalService, private router: Router, ) { } @@ -81,16 +82,8 @@ export class NewPreprocessorComponent implements OnInit { this.gs.create(SERV.PREPROCESSORS,this.updateForm.value) .subscribe((prep: any) => { const response = prep; - Swal.fire({ - position: 'top-end', - backdrop: false, - icon: 'success', - title: "Success!", - text: "New Preprocessor created!", - showConfirmButton: false, - timer: 1500 - }) - this.router.navigate(['config/engine/preprocessors']); + this.alert.okAlert('New Preprocessor created!',''); + this.router.navigate(['config/engine/preprocessors']); } ); break; @@ -100,15 +93,8 @@ export class NewPreprocessorComponent implements OnInit { this.gs.update(SERV.PREPROCESSORS,id,this.updateForm.value) .subscribe((prep: any) => { const response = prep; - Swal.fire({ - position: 'top-end', - backdrop: false, - icon: 'success', - title: "Saved", - showConfirmButton: false, - timer: 1500 - }) - this.router.navigate(['config/engine/preprocessors']); + this.alert.okAlert('Preprocessor saved!',''); + this.router.navigate(['config/engine/preprocessors']); } ); break; diff --git a/src/app/config/engine/preprocessors/preprocessors.component.ts b/src/app/config/engine/preprocessors/preprocessors.component.ts index 5e03ff3e..ffcd7631 100644 --- a/src/app/config/engine/preprocessors/preprocessors.component.ts +++ b/src/app/config/engine/preprocessors/preprocessors.component.ts @@ -6,6 +6,7 @@ import Swal from 'sweetalert2/dist/sweetalert2.js'; import { Router } from '@angular/router'; import { Subject } from 'rxjs'; +import { AlertService } from 'src/app/core/_services/shared/alert.service'; import { environment } from './../../../../environments/environment'; import { GlobalService } from 'src/app/core/_services/main.service'; import { PageTitle } from 'src/app/core/_decorators/autotitle'; @@ -32,6 +33,7 @@ export class PreprocessorsComponent implements OnInit { public preproc: {preprocessorId: number, name: string, url: string, binaryName: string, keyspaceCommand: string, skipCommand: string, limitCommand: string}[] = []; constructor( + private alert: AlertService, private gs: GlobalService, ) { } @@ -142,20 +144,14 @@ export class PreprocessorsComponent implements OnInit { icon: "warning", reverseButtons: true, showCancelButton: true, - cancelButtonColor: '#8A8584', - confirmButtonColor: '#C53819', - confirmButtonText: 'Yes, delete it!' + cancelButtonColor: this.alert.cancelButtonColor, + confirmButtonColor: this.alert.confirmButtonColor, + confirmButtonText: this.alert.delconfirmText }) .then((result) => { if (result.isConfirmed) { this.gs.delete(SERV.PREPROCESSORS,id).subscribe(() => { - Swal.fire({ - position: 'top-end', - backdrop: false, - icon: 'success', - showConfirmButton: false, - timer: 1500 - }) + this.alert.okAlert('Deleted '+name+'',''); this.ngOnInit(); this.rerender(); // rerender datatables }); diff --git a/src/app/config/hashtypes/hashtype/hashtype.component.ts b/src/app/config/hashtypes/hashtype/hashtype.component.ts index f7043d5a..f906fe36 100644 --- a/src/app/config/hashtypes/hashtype/hashtype.component.ts +++ b/src/app/config/hashtypes/hashtype/hashtype.component.ts @@ -1,8 +1,8 @@ import { FormControl, FormGroup, Validators } from '@angular/forms'; import { ActivatedRoute, Params, Router } from '@angular/router'; -import Swal from 'sweetalert2/dist/sweetalert2.js'; import { Component, OnInit } from '@angular/core'; +import { AlertService } from 'src/app/core/_services/shared/alert.service'; import { GlobalService } from 'src/app/core/_services/main.service'; import { PageTitle } from 'src/app/core/_decorators/autotitle'; import { SERV } from '../../../core/_services/main.config'; @@ -20,8 +20,9 @@ export class HashtypeComponent implements OnInit { editedIndex: number; constructor( - private gs: GlobalService, private route:ActivatedRoute, + private alert: AlertService, + private gs: GlobalService, private router:Router ) { } @@ -81,15 +82,7 @@ export class HashtypeComponent implements OnInit { case 'create': this.gs.create(SERV.HASHTYPES,this.Form.value).subscribe(() => { - Swal.fire({ - position: 'top-end', - backdrop: false, - icon: 'success', - title: "Success", - text: "New Hashtype created!", - showConfirmButton: false, - timer: 1500 - }) + this.alert.okAlert('New Hashtype created!',''); this.router.navigate(['/config/hashtypes']); } ); @@ -98,14 +91,7 @@ export class HashtypeComponent implements OnInit { case 'edit': const id = +this.route.snapshot.params['id']; this.gs.update(SERV.HASHTYPES,id,this.Form.value).subscribe(() => { - Swal.fire({ - position: 'top-end', - backdrop: false, - icon: 'success', - title: "Saved", - showConfirmButton: false, - timer: 1500 - }) + this.alert.okAlert('Hashtype saved!',''); this.router.navigate(['/config/hashtypes']); }); break; diff --git a/src/app/config/hashtypes/hashtypes.component.ts b/src/app/config/hashtypes/hashtypes.component.ts index 1dca4805..507ccc52 100644 --- a/src/app/config/hashtypes/hashtypes.component.ts +++ b/src/app/config/hashtypes/hashtypes.component.ts @@ -4,6 +4,7 @@ import { DataTableDirective } from 'angular-datatables'; import Swal from 'sweetalert2/dist/sweetalert2.js'; import { Subject } from 'rxjs'; +import { AlertService } from 'src/app/core/_services/shared/alert.service'; import { GlobalService } from 'src/app/core/_services/main.service'; import { environment } from './../../../environments/environment'; import { PageTitle } from 'src/app/core/_decorators/autotitle'; @@ -34,6 +35,7 @@ export class HashtypesComponent implements OnInit { dtOptions: any = {}; constructor( + private alert: AlertService, private gs: GlobalService, ) { } @@ -155,13 +157,7 @@ export class HashtypesComponent implements OnInit { this.ngOnInit(); this.rerender(); // rerender datatables Swal.close(); - Swal.fire({ - position: 'top-end', - backdrop: false, - icon: 'success', - showConfirmButton: false, - timer: 1500 - }) + this.alert.okAlert('Completed',''); },3000); } @@ -178,20 +174,14 @@ export class HashtypesComponent implements OnInit { icon: "warning", reverseButtons: true, showCancelButton: true, - cancelButtonColor: '#8A8584', - confirmButtonColor: '#C53819', - confirmButtonText: 'Yes, delete it!' + cancelButtonColor: this.alert.cancelButtonColor, + confirmButtonColor: this.alert.confirmButtonColor, + confirmButtonText: this.alert.delconfirmText }) .then((result) => { if (result.isConfirmed) { this.gs.delete(SERV.HASHTYPES,id).subscribe(() => { - Swal.fire({ - position: 'top-end', - backdrop: false, - icon: 'success', - showConfirmButton: false, - timer: 1500 - }) + this.alert.okAlert('Deleted '+name+'',''); this.ngOnInit(); this.rerender(); // rerender datatables }); @@ -211,14 +201,7 @@ export class HashtypesComponent implements OnInit { $(".dt-button-background").trigger("click"); const selection = $($(this.dtElement).DataTable.tables()).DataTable().rows({ selected: true } ).data().pluck(0).toArray(); if(selection.length == 0) { - Swal.fire({ - position: 'top-end', - backdrop: false, - title: "You haven't selected any Hashtype", - type: 'success', - timer: 1500, - showConfirmButton: false - }) + this.alert.okAlert('You haven not selected any Hashtype',''); return; } const selectionnum = selection.map(i=>Number(i)); diff --git a/src/app/config/health-checks/health-checks.component.ts b/src/app/config/health-checks/health-checks.component.ts index 2d9d0773..fc54eb84 100644 --- a/src/app/config/health-checks/health-checks.component.ts +++ b/src/app/config/health-checks/health-checks.component.ts @@ -7,6 +7,7 @@ import { Router } from '@angular/router'; import { Subject } from 'rxjs'; import { UIConfigService } from 'src/app/core/_services/shared/storage.service'; +import { AlertService } from 'src/app/core/_services/shared/alert.service'; import { GlobalService } from 'src/app/core/_services/main.service'; import { environment } from './../../../environments/environment'; import { PageTitle } from 'src/app/core/_decorators/autotitle'; @@ -35,6 +36,7 @@ export class HealthChecksComponent implements OnInit { constructor( private uiService: UIConfigService, + private alert: AlertService, private gs: GlobalService, ) { } @@ -173,20 +175,14 @@ export class HealthChecksComponent implements OnInit { icon: "warning", reverseButtons: true, showCancelButton: true, - cancelButtonColor: '#8A8584', - confirmButtonColor: '#C53819', - confirmButtonText: 'Yes, delete it!' + cancelButtonColor: this.alert.cancelButtonColor, + confirmButtonColor: this.alert.confirmButtonColor, + confirmButtonText: this.alert.delconfirmText }) .then((result) => { if (result.isConfirmed) { this.gs.delete(SERV.HEALTH_CHECKS,id).subscribe(() => { - Swal.fire({ - position: 'top-end', - backdrop: false, - icon: 'success', - showConfirmButton: false, - timer: 1500 - }) + this.alert.okAlert('Deleted '+name+'',''); this.ngOnInit(); this.rerender(); // rerender datatables }); diff --git a/src/app/config/health-checks/new-health-check/new-health-checks.component.ts b/src/app/config/health-checks/new-health-check/new-health-checks.component.ts index 2a883088..821969a9 100644 --- a/src/app/config/health-checks/new-health-check/new-health-checks.component.ts +++ b/src/app/config/health-checks/new-health-check/new-health-checks.component.ts @@ -1,8 +1,8 @@ import { FormControl, FormGroup, Validators } from '@angular/forms'; -import Swal from 'sweetalert2/dist/sweetalert2.js'; import { Component, OnInit } from '@angular/core'; import { Router } from '@angular/router'; +import { AlertService } from 'src/app/core/_services/shared/alert.service'; import { GlobalService } from 'src/app/core/_services/main.service'; import { PageTitle } from 'src/app/core/_decorators/autotitle'; import { SERV } from '../../../core/_services/main.config'; @@ -18,6 +18,7 @@ export class NewHealthChecksComponent implements OnInit { createForm: FormGroup; constructor( + private alert: AlertService, private gs: GlobalService, private router:Router ) { } @@ -50,16 +51,8 @@ export class NewHealthChecksComponent implements OnInit { if (this.createForm.valid) { this.gs.create(SERV.HEALTH_CHECKS,this.createForm.value).subscribe(() => { - Swal.fire({ - position: 'top-end', - backdrop: false, - icon: 'success', - title: "Success!", - text: "New Health Check created!", - showConfirmButton: false, - timer: 1500 - }) - this.router.navigate(['/config/health-checks']); + this.alert.okAlert('New Health Check created!',''); + this.router.navigate(['/config/health-checks']); } ); } diff --git a/src/app/config/server/server.component.ts b/src/app/config/server/server.component.ts index d9486ac2..d8b5a252 100644 --- a/src/app/config/server/server.component.ts +++ b/src/app/config/server/server.component.ts @@ -6,10 +6,11 @@ import { Component, OnInit } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; import { Store } from '@ngrx/store'; -import { serverlog, proxytype } from '../../core/_constants/settings.config'; import { UIConfigService } from 'src/app/core/_services/shared/storage.service'; import { TooltipService } from '../../core/_services/shared/tooltip.service'; +import { serverlog, proxytype } from '../../core/_constants/settings.config'; import { CookieService } from '../../core/_services/shared/cookies.service'; +import { AlertService } from 'src/app/core/_services/shared/alert.service'; import { GlobalService } from 'src/app/core/_services/main.service'; import { PageTitle } from 'src/app/core/_decorators/autotitle'; import { SERV } from '../../core/_services/main.config'; @@ -32,6 +33,7 @@ export class ServerComponent implements OnInit { private cookieService: CookieService, private uicService: UIConfigService, private route:ActivatedRoute, + private alert: AlertService, private gs: GlobalService, private store: Store<{configList: {}}> ) { } @@ -325,7 +327,7 @@ export class ServerComponent implements OnInit { this.gs.update(SERV.CONFIGS,indexUpdate, arr).subscribe((result)=>{ this.uicService.onUpdatingCheck(key); if(collap !== true){ - this.savedAlert(); + this.alert.okAlert('Saved',''); this.ngOnInit(); } }); @@ -349,7 +351,7 @@ export class ServerComponent implements OnInit { setAutorefresh(value: string){ this.cookieService.setCookie('autorefresh', JSON.stringify({active:true, value: value}), 365); - this.savedAlert(); + this.alert.okAlert('Saved',''); this.ngOnInit(); } @@ -359,19 +361,9 @@ export class ServerComponent implements OnInit { setTooltipLevel(value: string){ this.cookieService.setCookie('tooltip', value, 365); - this.savedAlert(); + this.alert.okAlert('Saved',''); this.ngOnInit(); } - savedAlert(){ - Swal.fire({ - position: 'top-end', - backdrop: false, - icon: 'success', - title: 'Saved', - showConfirmButton: false, - timer: 1500 - }) - } - } + diff --git a/src/app/core/_directives/copy-button.directive.ts b/src/app/core/_directives/copy-button.directive.ts index d3bbcc90..3c04de37 100644 --- a/src/app/core/_directives/copy-button.directive.ts +++ b/src/app/core/_directives/copy-button.directive.ts @@ -1,11 +1,5 @@ -import Swal from 'sweetalert2/dist/sweetalert2.js'; -import { - Directive, - HostListener, - Output, - Input, - EventEmitter -} from '@angular/core'; +import { Directive,HostListener,Output,Input,EventEmitter } from '@angular/core'; +import { AlertService } from '../_services/shared/alert.service'; /** * Returns copied clipboard string @@ -21,6 +15,10 @@ import { }) export class CopyButtonDirective { + constructor( + private alert: AlertService + ) { } + @Input("copyButton") public payload: string; @@ -46,19 +44,7 @@ export class CopyButtonDirective { document.addEventListener("copy", listener, false); document.execCommand("copy"); document.removeEventListener("copy", listener, false); - this.savedAlert(); - } - - savedAlert(){ - Swal.fire({ - position: 'top-end', - backdrop: false, - toast: true, - icon: 'success', - title: 'Copied', - showConfirmButton: false, - timer: 1500 - }) + this.alert.okAlert('Copied',''); } } diff --git a/src/app/core/_services/shared/alert.service.ts b/src/app/core/_services/shared/alert.service.ts new file mode 100644 index 00000000..d0a96d5c --- /dev/null +++ b/src/app/core/_services/shared/alert.service.ts @@ -0,0 +1,37 @@ +import Swal from 'sweetalert2/dist/sweetalert2.js'; +import { Injectable } from '@angular/core'; + +@Injectable({ + providedIn: 'root' +}) +export class AlertService { + constructor() {} + + cancelButtonColor = '#8A8584'; + confirmButtonColor = '#C53819'; + delconfirmText = 'Yes, delete it!'; + + /** + * Handles notification confirmation. + * Displays a confirmation modal on the top end of the screen using library Sweet Alert + * + * @param {string} title - Title to be displayed + * @param {string} text - Additional text + * @param {string} type - Type of warning, default success + */ + + okAlert(title: string, text: string, type: 'success' | 'error' | 'warning' = 'success') { + Swal.fire({ + title, + text, + icon: type, + position: 'top-end', + backdrop: false, + toast: true, + showConfirmButton: false, + timer: 1500 + }); + } + +} + diff --git a/src/app/files/files-edit/files-edit.component.ts b/src/app/files/files-edit/files-edit.component.ts index 3ec50eb2..ac73eadd 100644 --- a/src/app/files/files-edit/files-edit.component.ts +++ b/src/app/files/files-edit/files-edit.component.ts @@ -1,9 +1,9 @@ import { FormControl, FormGroup, Validators } from '@angular/forms'; import { ActivatedRoute, Params } from '@angular/router'; -import Swal from 'sweetalert2/dist/sweetalert2.js'; import { Component, OnInit } from '@angular/core'; import { Router } from '@angular/router'; +import { AlertService } from 'src/app/core/_services/shared/alert.service'; import { GlobalService } from 'src/app/core/_services/main.service'; import { PageTitle } from 'src/app/core/_decorators/autotitle'; import { SERV } from '../../core/_services/main.config'; @@ -31,6 +31,7 @@ export class FilesEditComponent implements OnInit { constructor( private route:ActivatedRoute, + private alert: AlertService, private gs: GlobalService, private router: Router ) { } @@ -88,14 +89,7 @@ export class FilesEditComponent implements OnInit { onSubmit(): void{ this.gs.update(SERV.FILES,this.editedFileIndex,this.updateForm.value['updateData']).subscribe(() => { - Swal.fire({ - position: 'top-end', - backdrop: false, - icon: 'success', - title: "Saved", - showConfirmButton: false, - timer: 1500 - }) + this.alert.okAlert('File saved!',''); this.route.data.subscribe(data => { switch (data['kind']) { @@ -117,15 +111,7 @@ export class FilesEditComponent implements OnInit { }, errorMessage => { // check error status code is 500, if so, do some action - Swal.fire({ - position: 'top-end', - backdrop: false, - icon: "warning", - title: "Oppss! Error", - text: "File was not updated, please try again!", - showConfirmButton: false, - timer: 1500 - }) + this.alert.okAlert('File was not updated, please try again!','','warning'); this.ngOnInit(); } ); diff --git a/src/app/files/files.component.ts b/src/app/files/files.component.ts index 65a1ba6c..33dbceab 100644 --- a/src/app/files/files.component.ts +++ b/src/app/files/files.component.ts @@ -6,6 +6,7 @@ import Swal from 'sweetalert2/dist/sweetalert2.js'; import { HttpClient } from '@angular/common/http'; import { Subject } from 'rxjs'; +import { AlertService } from '../core/_services/shared/alert.service'; import { GlobalService } from 'src/app/core/_services/main.service'; import { environment } from './../../environments/environment'; import { PageTitle } from '../core/_decorators/autotitle'; @@ -44,6 +45,7 @@ export class FilesComponent implements OnInit { constructor( private route:ActivatedRoute, + private alert: AlertService, private gs: GlobalService, private router:Router ) { } @@ -240,20 +242,14 @@ export class FilesComponent implements OnInit { icon: "warning", reverseButtons: true, showCancelButton: true, - cancelButtonColor: '#8A8584', - confirmButtonColor: '#C53819', - confirmButtonText: 'Yes, delete it!' + cancelButtonColor: this.alert.cancelButtonColor, + confirmButtonColor: this.alert.confirmButtonColor, + confirmButtonText: this.alert.delconfirmText }) .then((result) => { if (result.isConfirmed) { this.gs.delete(SERV.FILES,id).subscribe(() => { - Swal.fire({ - position: 'top-end', - backdrop: false, - icon: 'success', - showConfirmButton: false, - timer: 1500 - }) + this.alert.okAlert('Deleted '+name+'',''); this.ngOnInit(); this.rerender(); // rerender datatables }); @@ -275,12 +271,7 @@ export class FilesComponent implements OnInit { $(".dt-button-background").trigger("click"); const selection = $($(this.dtElement).DataTable.tables()).DataTable().rows({ selected: true } ).data().pluck(0).toArray(); if(selection.length == 0) { - Swal.fire({ - title: "You haven't selected any File", - type: 'success', - timer: 1500, - showConfirmButton: false - }) + this.alert.okAlert('You haven not selected any File',''); return; } const selectionnum = selection.map(i=>Number(i)); @@ -327,14 +318,7 @@ export class FilesComponent implements OnInit { this.ngOnInit(); this.rerender(); // rerender datatables Swal.close(); - Swal.fire({ - position: 'top-end', - backdrop: false, - icon: 'success', - title: "Success", - showConfirmButton: false, - timer: 1500 - }) + this.alert.okAlert('Completed',''); },3000); } @@ -348,12 +332,7 @@ export class FilesComponent implements OnInit { $(".dt-button-background").trigger("click"); const selection = $($(this.dtElement).DataTable.tables()).DataTable().rows({ selected: true } ).data().pluck(0).toArray(); if(selection.length == 0) { - Swal.fire({ - title: "You haven't selected any File", - type: 'success', - timer: 1500, - showConfirmButton: false - }) + this.alert.okAlert('You haven not selected any Group',''); return; } diff --git a/src/app/files/new-files/new-files.component.ts b/src/app/files/new-files/new-files.component.ts index 5b659f0d..aa3be761 100644 --- a/src/app/files/new-files/new-files.component.ts +++ b/src/app/files/new-files/new-files.component.ts @@ -1,12 +1,12 @@ import { faPlus, faUpload, faDownload, faLink, faFileUpload} from '@fortawesome/free-solid-svg-icons'; +import { Component, ElementRef, OnDestroy, OnInit, ViewChild } from '@angular/core'; import { FormControl, FormGroup } from '@angular/forms'; -import Swal from 'sweetalert2/dist/sweetalert2.js'; -import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, Injectable, OnDestroy, OnInit, ViewChild } from '@angular/core'; -import { Observable, ReplaySubject, Subject, Subscription, map, of, pairwise, startWith, switchMap, take, takeUntil, tap } from 'rxjs'; +import { Subject, Subscription, takeUntil } from 'rxjs'; // import { takeUntil } from 'rxjs/operators'; import { Buffer } from 'buffer'; import { UploadTUSService } from 'src/app/core/_services/files/files_tus.service'; +import { AlertService } from 'src/app/core/_services/shared/alert.service'; import { GlobalService } from 'src/app/core/_services/main.service'; import { FileSizePipe } from 'src/app/core/_pipes/file-size.pipe'; import { environment } from './../../../environments/environment'; @@ -37,6 +37,7 @@ export class NewFilesComponent implements OnInit, OnDestroy { constructor( private uploadService:UploadTUSService, private route:ActivatedRoute, + private alert: AlertService, private gs: GlobalService, private fs:FileSizePipe, private router: Router @@ -100,15 +101,7 @@ export class NewFilesComponent implements OnInit, OnDestroy { if(form.status === false){ this.subscriptions.push(this.gs.create(SERV.FILES,form.update).subscribe(() => { form = this.onPrep(this.createForm.value, true); - Swal.fire({ - position: 'top-end', - backdrop: false, - icon: 'success', - title: "Success!", - text: "New File created!", - showConfirmButton: false, - timer: 1500 - }) + this.alert.okAlert('New File created!',''); this.submitted = false; this.router.navigate(['/files',this.redirect]); })); diff --git a/src/app/hashlists/edit-hashlist/edit-hashlist.component.ts b/src/app/hashlists/edit-hashlist/edit-hashlist.component.ts index f528b053..6a7e7949 100644 --- a/src/app/hashlists/edit-hashlist/edit-hashlist.component.ts +++ b/src/app/hashlists/edit-hashlist/edit-hashlist.component.ts @@ -4,13 +4,13 @@ import { Component, HostListener, OnInit, ViewChild} from '@angular/core'; import { ActivatedRoute, Params, Router } from '@angular/router'; import { FormControl, FormGroup } from '@angular/forms'; import { DataTableDirective } from 'angular-datatables'; -import Swal from 'sweetalert2/dist/sweetalert2.js'; import { Observable, Subject } from 'rxjs'; import { GlobalService } from 'src/app/core/_services/main.service'; import { environment } from './../../../environments/environment'; import { PageTitle } from 'src/app/core/_decorators/autotitle'; import { SERV } from '../../core/_services/main.config'; +import { AlertService } from 'src/app/core/_services/shared/alert.service'; @Component({ selector: 'app-edit-hashlist', @@ -41,6 +41,7 @@ export class EditHashlistComponent implements OnInit { constructor( private format: StaticArrayPipe, private route: ActivatedRoute, + private alert: AlertService, private gs: GlobalService, private router: Router ) { } @@ -94,17 +95,10 @@ export class EditHashlistComponent implements OnInit { if (this.updateForm.valid) { this.gs.update(SERV.HASHLISTS,this.editedHashlistIndex,this.updateForm.value['updateData']).subscribe(() => { - Swal.fire({ - position: 'top-end', - backdrop: false, - icon: 'success', - title: "Saved", - showConfirmButton: false, - timer: 1500 - }) - this.updateForm.reset(); // success, we reset form - const path = this.type === 3 ? '/hashlists/superhashlist':'/hashlists/hashlist'; - this.router.navigate([path]); + this.alert.okAlert('Hashlist saved!',''); + this.updateForm.reset(); // success, we reset form + const path = this.type === 3 ? '/hashlists/superhashlist':'/hashlists/hashlist'; + this.router.navigate([path]); } ); } diff --git a/src/app/hashlists/hashlist/hashlist.component.ts b/src/app/hashlists/hashlist/hashlist.component.ts index e9313f72..201b24de 100644 --- a/src/app/hashlists/hashlist/hashlist.component.ts +++ b/src/app/hashlists/hashlist/hashlist.component.ts @@ -5,6 +5,7 @@ import Swal from 'sweetalert2/dist/sweetalert2.js'; import { ActivatedRoute, Router } from '@angular/router'; import { Subject } from 'rxjs'; +import { AlertService } from 'src/app/core/_services/shared/alert.service'; import { GlobalService } from 'src/app/core/_services/main.service'; import { environment } from './../../../environments/environment'; import { PageTitle } from 'src/app/core/_decorators/autotitle'; @@ -57,6 +58,7 @@ export class HashlistComponent implements OnInit, OnDestroy { constructor( private route:ActivatedRoute, + private alert: AlertService, private gs: GlobalService, private router: Router ) { } @@ -227,14 +229,7 @@ rerender(): void { onArchive(id: number){ this.gs.archive(SERV.HASHLISTS,id).subscribe((list: any) => { - Swal.fire({ - position: 'top-end', - backdrop: false, - icon: 'success', - title: "Archived!", - showConfirmButton: false, - timer: 1500 - }) + this.alert.okAlert('Archived!',''); this.ngOnInit(); this.rerender(); // rerender datatables }); @@ -253,20 +248,14 @@ onDelete(id: number, name: string){ icon: "warning", reverseButtons: true, showCancelButton: true, - cancelButtonColor: '#8A8584', - confirmButtonColor: '#C53819', - confirmButtonText: 'Yes, delete it!' + cancelButtonColor: this.alert.cancelButtonColor, + confirmButtonColor: this.alert.confirmButtonColor, + confirmButtonText: this.alert.delconfirmText }) .then((result) => { if (result.isConfirmed) { this.gs.delete(SERV.HASHLISTS,id).subscribe(() => { - Swal.fire({ - position: 'top-end', - backdrop: false, - icon: 'success', - showConfirmButton: false, - timer: 1500 - }) + this.alert.okAlert('Deleted '+name+'',''); this.ngOnInit(); this.rerender(); // rerender datatables }); @@ -288,14 +277,7 @@ onSelectedHashlists(){ $(".dt-button-background").trigger("click"); const selection = $($(this.dtElement).DataTable.tables()).DataTable().rows({ selected: true } ).data().pluck(0).toArray(); if(selection.length == 0) { - Swal.fire({ - position: 'top-end', - backdrop: false, - icon: 'success', - title: "You haven't selected any Hashlist", - showConfirmButton: false, - timer: 1500 - }) + this.alert.okAlert('You haven not selected any Group',''); return; } const selectionnum = selection.map(i=>Number(i)); @@ -341,13 +323,7 @@ onDone(value?: any){ this.ngOnInit(); this.rerender(); // rerender datatables Swal.close(); - Swal.fire({ - position: 'top-end', - backdrop: false, - icon: 'success', - showConfirmButton: false, - timer: 1500 - }) + this.alert.okAlert('Completed',''); },3000); } diff --git a/src/app/hashlists/new-hashlist/new-hashlist.component.ts b/src/app/hashlists/new-hashlist/new-hashlist.component.ts index 4aeb1a12..a6a528a9 100644 --- a/src/app/hashlists/new-hashlist/new-hashlist.component.ts +++ b/src/app/hashlists/new-hashlist/new-hashlist.component.ts @@ -10,6 +10,7 @@ import { Buffer } from 'buffer'; import { UIConfigService } from 'src/app/core/_services/shared/storage.service'; import { UploadTUSService } from '../../core/_services/files/files_tus.service'; +import { AlertService } from 'src/app/core/_services/shared/alert.service'; import { GlobalService } from 'src/app/core/_services/main.service'; import { FileSizePipe } from 'src/app/core/_pipes/file-size.pipe'; import { PageTitle } from 'src/app/core/_decorators/autotitle'; @@ -56,6 +57,7 @@ export class NewHashlistComponent implements OnInit { private uploadService:UploadTUSService, private uiService: UIConfigService, private modalService: NgbModal, + private alert: AlertService, private gs: GlobalService, private fs:FileSizePipe, private router: Router, @@ -241,15 +243,7 @@ export class NewHashlistComponent implements OnInit { const res = this.handleUpload(this.signupForm.value); this.subscriptions.push(this.gs.create(SERV.HASHLISTS,res).subscribe(() => { - Swal.fire({ - position: 'top-end', - backdrop: false, - icon: 'success', - title: "Success!", - text: "New HashList created!", - showConfirmButton: false, - timer: 1500 - }) + this.alert.okAlert('New HashList created!',''); this.router.navigate(['/hashlists/hashlist']); } )); diff --git a/src/app/hashlists/new-superhashlist/new-superhashlist.component.ts b/src/app/hashlists/new-superhashlist/new-superhashlist.component.ts index d58fc086..1339dcd5 100644 --- a/src/app/hashlists/new-superhashlist/new-superhashlist.component.ts +++ b/src/app/hashlists/new-superhashlist/new-superhashlist.component.ts @@ -1,9 +1,9 @@ import { Component, OnInit, ChangeDetectionStrategy ,ChangeDetectorRef } from '@angular/core'; import { faFile, faMagnifyingGlass } from '@fortawesome/free-solid-svg-icons'; import { FormControl, FormGroup, Validators, FormArray } from '@angular/forms'; -import Swal from 'sweetalert2/dist/sweetalert2.js'; import { Router } from '@angular/router'; +import { AlertService } from 'src/app/core/_services/shared/alert.service'; import { GlobalService } from 'src/app/core/_services/main.service'; import { environment } from './../../../environments/environment' import { PageTitle } from 'src/app/core/_decorators/autotitle'; @@ -22,6 +22,7 @@ export class NewSuperhashlistComponent implements OnInit { constructor( private _changeDetectorRef: ChangeDetectorRef, + private alert: AlertService, private gs: GlobalService, private router: Router ) { } @@ -89,17 +90,9 @@ export class NewSuperhashlistComponent implements OnInit { if (this.createForm.valid) { console.log(this.createForm.value); this.gs.chelper(SERV.HELPER,'createSuperHashlist',this.createForm.value).subscribe(() => { - Swal.fire({ - position: 'top-end', - backdrop: false, - icon: 'success', - title: "Success!", - text: "New SuperHashList created!", - showConfirmButton: false, - timer: 1500 - }) - this.createForm.reset(); // success, we reset form - this.router.navigate(['hashlists/superhashlist']); + this.alert.okAlert('New SuperHashList created!',''); + this.createForm.reset(); // success, we reset form + this.router.navigate(['hashlists/superhashlist']); } ); } diff --git a/src/app/hashlists/superhashlist/superhashlist.component.ts b/src/app/hashlists/superhashlist/superhashlist.component.ts index b48e8f87..4bfd6531 100644 --- a/src/app/hashlists/superhashlist/superhashlist.component.ts +++ b/src/app/hashlists/superhashlist/superhashlist.component.ts @@ -4,6 +4,7 @@ import { DataTableDirective } from 'angular-datatables'; import Swal from 'sweetalert2/dist/sweetalert2.js'; import { Subject } from 'rxjs'; +import { AlertService } from 'src/app/core/_services/shared/alert.service'; import { GlobalService } from 'src/app/core/_services/main.service'; import { environment } from './../../../environments/environment'; import { PageTitle } from 'src/app/core/_decorators/autotitle'; @@ -31,6 +32,7 @@ export class SuperhashlistComponent implements OnInit { dtOptions: any = {}; constructor( + private alert: AlertService, private gs: GlobalService, ) { } @@ -142,20 +144,14 @@ export class SuperhashlistComponent implements OnInit { icon: "warning", reverseButtons: true, showCancelButton: true, - cancelButtonColor: '#8A8584', - confirmButtonColor: '#C53819', - confirmButtonText: 'Yes, delete it!' + cancelButtonColor: this.alert.cancelButtonColor, + confirmButtonColor: this.alert.confirmButtonColor, + confirmButtonText: this.alert.delconfirmText }) .then((result) => { if (result.isConfirmed) { this.gs.delete(SERV.HASHLISTS,id).subscribe(() => { - Swal.fire({ - position: 'top-end', - backdrop: false, - icon: 'success', - showConfirmButton: false, - timer: 1500 - }) + this.alert.okAlert('Deleted '+name+'',''); this.ngOnInit(); this.rerender(); // rerender datatables }); diff --git a/src/app/tasks/edit-preconfigured-tasks/edit-preconfigured-tasks.component.ts b/src/app/tasks/edit-preconfigured-tasks/edit-preconfigured-tasks.component.ts index 44327872..e4f23c52 100644 --- a/src/app/tasks/edit-preconfigured-tasks/edit-preconfigured-tasks.component.ts +++ b/src/app/tasks/edit-preconfigured-tasks/edit-preconfigured-tasks.component.ts @@ -2,10 +2,10 @@ import { faHomeAlt, faPlus, faTrash, faInfoCircle, faEye, faLock} from '@fortawe import { Component, OnInit, ViewChild, HostListener } from '@angular/core'; import { FormControl, FormGroup, Validators } from '@angular/forms'; import { ActivatedRoute, Params, Router } from '@angular/router'; -import { Observable, Subject } from 'rxjs'; import { DataTableDirective } from 'angular-datatables'; -import Swal from 'sweetalert2/dist/sweetalert2.js'; +import { Observable, Subject } from 'rxjs'; +import { AlertService } from 'src/app/core/_services/shared/alert.service'; import { GlobalService } from 'src/app/core/_services/main.service'; import { colorpicker } from '../../core/_constants/settings.config'; import { environment } from './../../../environments/environment'; @@ -32,6 +32,7 @@ export class EditPreconfiguredTasksComponent implements OnInit{ constructor( private route:ActivatedRoute, + private alert: AlertService, private gs: GlobalService, private router: Router ) { } @@ -116,16 +117,9 @@ export class EditPreconfiguredTasksComponent implements OnInit{ if (this.updateForm.valid) { this.gs.update(SERV.PRETASKS,this.editedPretaskIndex,this.updateForm.value['updateData']).subscribe(() => { - Swal.fire({ - position: 'top-end', - backdrop: false, - icon: 'success', - title: "Saved", - showConfirmButton: false, - timer: 1500 - }) - this.updateForm.reset(); // success, we reset form - this.router.navigate(['tasks/preconfigured-tasks']); + this.alert.okAlert('PreTask saved!',''); + this.updateForm.reset(); // success, we reset form + this.router.navigate(['tasks/preconfigured-tasks']); } ); } diff --git a/src/app/tasks/edit-supertasks/edit-supertasks.component.ts b/src/app/tasks/edit-supertasks/edit-supertasks.component.ts index 41ef95fe..9fb5247c 100644 --- a/src/app/tasks/edit-supertasks/edit-supertasks.component.ts +++ b/src/app/tasks/edit-supertasks/edit-supertasks.component.ts @@ -6,6 +6,7 @@ import { DataTableDirective } from 'angular-datatables'; import Swal from 'sweetalert2/dist/sweetalert2.js'; import { Subject } from 'rxjs'; +import { AlertService } from 'src/app/core/_services/shared/alert.service'; import { GlobalService } from 'src/app/core/_services/main.service'; import { environment } from './../../../environments/environment'; import { PageTitle } from 'src/app/core/_decorators/autotitle'; @@ -34,6 +35,7 @@ export class EditSupertasksComponent implements OnInit { constructor( private route:ActivatedRoute, + private alert: AlertService, private gs: GlobalService, private router: Router, ) { } @@ -93,14 +95,7 @@ export class EditSupertasksComponent implements OnInit { let payload = concat.concat(this.updateForm.value['pretasks']); this.gs.update(SERV.SUPER_TASKS,this.editedSTIndex,{'pretasks': payload}).subscribe(() => { - Swal.fire({ - position: 'top-end', - backdrop: false, - icon: 'success', - title: "Saved", - showConfirmButton: false, - timer: 1500 - }) + this.alert.okAlert('SuperTask saved!',''); this.updateForm.reset(); // success, we reset form this.onRefresh(); // this.router.navigate(['/tasks/supertasks']); @@ -183,21 +178,14 @@ export class EditSupertasksComponent implements OnInit { icon: "warning", reverseButtons: true, showCancelButton: true, - cancelButtonColor: '#8A8584', - confirmButtonColor: '#C53819', - confirmButtonText: 'Yes, delete it!' + cancelButtonColor: this.alert.cancelButtonColor, + confirmButtonColor: this.alert.confirmButtonColor, + confirmButtonText: this.alert.delconfirmText }) .then((result) => { if (result.isConfirmed) { this.gs.delete(SERV.SUPER_TASKS,id).subscribe(() => { - Swal.fire({ - position: 'top-end', - backdrop: false, - icon: 'success', - title: "Success", - showConfirmButton: false, - timer: 1500 - }) + this.alert.okAlert('Deleted',''); this.ngOnInit(); }); } else { diff --git a/src/app/tasks/edit-tasks/edit-tasks.component.ts b/src/app/tasks/edit-tasks/edit-tasks.component.ts index aad89e5a..4f14020a 100644 --- a/src/app/tasks/edit-tasks/edit-tasks.component.ts +++ b/src/app/tasks/edit-tasks/edit-tasks.component.ts @@ -14,6 +14,7 @@ import * as echarts from 'echarts/core'; import { PendingChangesGuard } from 'src/app/core/_guards/pendingchanges.guard'; import { UIConfigService } from 'src/app/core/_services/shared/storage.service'; +import { AlertService } from 'src/app/core/_services/shared/alert.service'; import { GlobalService } from 'src/app/core/_services/main.service'; import { colorpicker } from '../../core/_constants/settings.config'; import { FileSizePipe } from 'src/app/core/_pipes/file-size.pipe'; @@ -45,6 +46,7 @@ export class EditTasksComponent implements OnInit,PendingChangesGuard { constructor( private uiService:UIConfigService, private route: ActivatedRoute, + private alert: AlertService, private gs: GlobalService, private fs:FileSizePipe, private router: Router @@ -121,14 +123,7 @@ export class EditTasksComponent implements OnInit,PendingChangesGuard { onSubmit(){ if (this.updateForm.valid) { this.gs.update(SERV.TASKS,this.editedTaskIndex,this.updateForm.value['updateData']).subscribe(() => { - Swal.fire({ - position: 'top-end', - backdrop: false, - icon: 'success', - title: "Saved", - showConfirmButton: false, - timer: 1500 - }) + this.alert.okAlert('Task saved!',''); this.updateForm.reset(); // success, we reset form this.router.navigate(['tasks/show-tasks']); } @@ -217,15 +212,7 @@ export class EditTasksComponent implements OnInit,PendingChangesGuard { if (this.createForm.valid) { const payload = {"taskId": this.editedTaskIndex, "agentId":this.createForm.value['agentId']}; this.gs.create(SERV.AGENT_ASSIGN,payload).subscribe(() => { - Swal.fire({ - position: 'top-end', - backdrop: false, - icon: 'success', - title: "Success", - text: "Agent Assigned!", - showConfirmButton: false, - timer: 1500 - }) + this.alert.okAlert('Agent assigned!',''); this.rerender(); // rerender datatables this.ngOnInit(); } @@ -235,14 +222,7 @@ export class EditTasksComponent implements OnInit,PendingChangesGuard { onDelete(id: number){ this.gs.delete(SERV.AGENT_ASSIGN,id).subscribe(() => { - Swal.fire({ - position: 'top-end', - backdrop: false, - icon: 'success', - title: "Success", - showConfirmButton: false, - timer: 1500 - }) + this.alert.okAlert('Deleted',''); this.rerender(); // rerender datatables this.ngOnInit(); }); @@ -257,8 +237,8 @@ export class EditTasksComponent implements OnInit,PendingChangesGuard { '', focusConfirm: false, showCancelButton: true, - cancelButtonColor: '#C53819', - confirmButtonColor: '#8A8584', + cancelButtonColor: this.alert.cancelButtonColor, + confirmButtonColor: this.alert.confirmButtonColor, cancelButton: true, preConfirm: () => { return [ @@ -270,14 +250,7 @@ export class EditTasksComponent implements OnInit,PendingChangesGuard { if (formValues) { if(cvalue !== Number(formValues[0])){ this.gs.update(SERV.AGENT_ASSIGN,id, {benchmark: +formValues}).subscribe(() => { - Swal.fire({ - position: 'top-end', - backdrop: false, - icon: 'success', - title: "Success", - showConfirmButton: false, - timer: 1500 - }) + this.alert.okAlert('Task saved!',''); this.ngOnInit(); this.rerender(); // rerender datatables }); @@ -475,22 +448,15 @@ export class EditTasksComponent implements OnInit,PendingChangesGuard { icon: "warning", reverseButtons: true, showCancelButton: true, - cancelButtonColor: '#8A8584', - confirmButtonColor: '#C53819', - confirmButtonText: 'Yes, delete it!' + cancelButtonColor: this.alert.cancelButtonColor, + confirmButtonColor: this.alert.confirmButtonColor, + confirmButtonText: this.alert.delconfirmText }) .then((result) => { if (result.isConfirmed) { let payload = {"taskId":this.editedTaskIndex}; this.gs.chelper(SERV.HELPER,'purgeTask',payload).subscribe(() => { - Swal.fire({ - position: 'top-end', - backdrop: false, - icon: 'success', - title: "Success", - showConfirmButton: false, - timer: 1500 - }) + this.alert.okAlert('Deleted '+name+'',''); this.ngOnInit(); this.rerender(); // rerender datatables }); @@ -512,14 +478,7 @@ export class EditTasksComponent implements OnInit,PendingChangesGuard { const title = state === 2 ? 'Chunk Abort!' :'Chunk Reset!' ; let payload = {'chunkId': id}; this.gs.chelper(SERV.HELPER,path,payload).subscribe(() => { - Swal.fire({ - position: 'top-end', - backdrop: false, - icon: 'success', - title: title, - showConfirmButton: false, - timer: 1500 - }) + this.alert.okAlert('Resetted!',''); this.ngOnInit(); this.rerender(); }); diff --git a/src/app/tasks/import-supertasks/masks/masks.component.ts b/src/app/tasks/import-supertasks/masks/masks.component.ts index f764daeb..545f2edd 100644 --- a/src/app/tasks/import-supertasks/masks/masks.component.ts +++ b/src/app/tasks/import-supertasks/masks/masks.component.ts @@ -4,6 +4,7 @@ import Swal from 'sweetalert2/dist/sweetalert2.js'; import { Component, OnInit } from '@angular/core'; import { Router } from '@angular/router'; +import { AlertService } from 'src/app/core/_services/shared/alert.service'; import { GlobalService } from 'src/app/core/_services/main.service'; import { PageTitle } from 'src/app/core/_decorators/autotitle'; import { SERV } from '../../../core/_services/main.config'; @@ -21,6 +22,7 @@ export class MasksComponent implements OnInit { crackertype: any; constructor( + private alert: AlertService, private gs: GlobalService, private router: Router, ) { } @@ -50,17 +52,9 @@ export class MasksComponent implements OnInit { if (this.createForm.valid) { this.gs.create(SERV.TASKS,this.createForm.value).subscribe(() => { - Swal.fire({ - position: 'top-end', - backdrop: false, - icon: 'success', - title: "Success!", - text: "New Supertask created!", - showConfirmButton: false, - timer: 1500 - }) - this.createForm.reset(); // success, we reset form - this.router.navigate(['tasks/show-tasks']); + this.alert.okAlert('New SuperTask created!',''); + this.createForm.reset(); // success, we reset form + this.router.navigate(['tasks/show-tasks']); } ); } diff --git a/src/app/tasks/import-supertasks/wrbulk/wrbulk.component.ts b/src/app/tasks/import-supertasks/wrbulk/wrbulk.component.ts index 5f29bc55..ccb9b042 100644 --- a/src/app/tasks/import-supertasks/wrbulk/wrbulk.component.ts +++ b/src/app/tasks/import-supertasks/wrbulk/wrbulk.component.ts @@ -3,11 +3,11 @@ import { ChangeDetectorRef, Component, HostListener, OnInit, ViewChild } from '@ import { environment } from './../../../../environments/environment'; import { faInfoCircle, faLock } from '@fortawesome/free-solid-svg-icons'; import { DataTableDirective } from 'angular-datatables'; -import Swal from 'sweetalert2/dist/sweetalert2.js'; import { Observable, Subject } from 'rxjs'; import { UIConfigService } from 'src/app/core/_services/shared/storage.service'; import { TooltipService } from '../../../core/_services/shared/tooltip.service'; +import { AlertService } from 'src/app/core/_services/shared/alert.service'; import { GlobalService } from 'src/app/core/_services/main.service'; import { PageTitle } from 'src/app/core/_decorators/autotitle'; import { SERV } from '../../../core/_services/main.config'; @@ -37,6 +37,7 @@ export class WrbulkComponent implements OnInit { private _changeDetectorRef: ChangeDetectorRef, private tooltipService: TooltipService, private uiService: UIConfigService, + private alert: AlertService, private gs: GlobalService, private router: Router, ) { } @@ -178,13 +179,7 @@ export class WrbulkComponent implements OnInit { validateFile(value){ if(value.split('.').pop() == '7zip'){ - Swal.fire({ - position: 'top-end', - backdrop: false, - title: "Heads Up!", - text: "Hashcat has some issues loading 7z files. Better convert it to a hash file ;)", - icon: "warning", - }) + this.alert.okAlert('"Hashcat has some issues loading 7z files. Better convert it to a hash file ;)',''); } } diff --git a/src/app/tasks/new-preconfigured-tasks/new-preconfigured-tasks.component.ts b/src/app/tasks/new-preconfigured-tasks/new-preconfigured-tasks.component.ts index 3c7b05fa..02721c8f 100644 --- a/src/app/tasks/new-preconfigured-tasks/new-preconfigured-tasks.component.ts +++ b/src/app/tasks/new-preconfigured-tasks/new-preconfigured-tasks.component.ts @@ -5,10 +5,10 @@ import { faInfoCircle, faLock } from '@fortawesome/free-solid-svg-icons'; import { environment } from './../../../environments/environment'; import { ActivatedRoute, Params, Router } from '@angular/router'; import { DataTableDirective } from 'angular-datatables'; -import Swal from 'sweetalert2/dist/sweetalert2.js'; import { Subject } from 'rxjs'; import { UIConfigService } from 'src/app/core/_services/shared/storage.service'; +import { AlertService } from 'src/app/core/_services/shared/alert.service'; import { GlobalService } from 'src/app/core/_services/main.service'; import { colorpicker } from '../../core/_constants/settings.config'; import { FileTypePipe } from 'src/app/core/_pipes/file-type.pipe'; @@ -35,6 +35,7 @@ export class NewPreconfiguredTasksComponent implements OnInit,AfterViewInit { private modalService: NgbModal, private fileType: FileTypePipe, private route:ActivatedRoute, + private alert: AlertService, private gs: GlobalService, private router: Router ) { } @@ -123,11 +124,7 @@ export class NewPreconfiguredTasksComponent implements OnInit,AfterViewInit { validateFile(value){ if(value.split('.').pop() == '7zip'){ - Swal.fire({ - title: "Heads Up!", - text: "Hashcat has some issues loading 7z files. Better convert it to a hash file ;)", - icon: "warning", - }) + this.alert.okAlert('Hashcat has some issues loading 7z files. Better convert it to a hash file ;)',''); } } @@ -259,17 +256,9 @@ export class NewPreconfiguredTasksComponent implements OnInit,AfterViewInit { if (this.createForm.valid) { this.gs.create(SERV.PRETASKS,this.createForm.value).subscribe(() => { - Swal.fire({ - position: 'top-end', - backdrop: false, - icon: 'success', - title: "Success!", - text: "New PreTask created!", - showConfirmButton: false, - timer: 1500 - }) - this.createForm.reset(); // success, we reset form - this.router.navigate(['tasks/preconfigured-tasks']); + this.alert.okAlert('New PreTask created!',''); + this.createForm.reset(); // success, we reset form + this.router.navigate(['tasks/preconfigured-tasks']); } ); } diff --git a/src/app/tasks/new-supertasks/new-supertasks.component.ts b/src/app/tasks/new-supertasks/new-supertasks.component.ts index 7371c969..fec5c672 100644 --- a/src/app/tasks/new-supertasks/new-supertasks.component.ts +++ b/src/app/tasks/new-supertasks/new-supertasks.component.ts @@ -1,9 +1,9 @@ import { Component, OnInit, ChangeDetectionStrategy ,ChangeDetectorRef } from '@angular/core'; import { FormControl, FormGroup, Validators, FormArray } from '@angular/forms'; import { faFile, faMagnifyingGlass } from '@fortawesome/free-solid-svg-icons'; -import Swal from 'sweetalert2/dist/sweetalert2.js'; import { Router } from '@angular/router'; +import { AlertService } from 'src/app/core/_services/shared/alert.service'; import { GlobalService } from 'src/app/core/_services/main.service'; import { environment } from './../../../environments/environment'; import { PageTitle } from 'src/app/core/_decorators/autotitle'; @@ -22,6 +22,7 @@ export class NewSupertasksComponent implements OnInit { constructor( private _changeDetectorRef: ChangeDetectorRef, + private alert: AlertService, private gs: GlobalService, private router: Router ) { } @@ -91,17 +92,9 @@ export class NewSupertasksComponent implements OnInit { if (this.createForm.valid) { this.gs.create(SERV.SUPER_TASKS,this.createForm.value).subscribe(() => { - Swal.fire({ - position: 'top-end', - backdrop: false, - icon: 'success', - title: "Success!", - text: "New Supertask created!", - showConfirmButton: false, - timer: 1500 - }) - this.createForm.reset(); // success, we reset form - this.router.navigate(['tasks/supertasks']); + this.alert.okAlert('New SuperTask created!',''); + this.createForm.reset(); // success, we reset form + this.router.navigate(['tasks/supertasks']); } ); } diff --git a/src/app/tasks/new-tasks/new-tasks.component.ts b/src/app/tasks/new-tasks/new-tasks.component.ts index 973d8553..229cdb59 100644 --- a/src/app/tasks/new-tasks/new-tasks.component.ts +++ b/src/app/tasks/new-tasks/new-tasks.component.ts @@ -5,12 +5,12 @@ import { ModalDismissReasons, NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { environment } from './../../../environments/environment'; import { ActivatedRoute, Params } from '@angular/router'; import { DataTableDirective } from 'angular-datatables'; -import Swal from 'sweetalert2/dist/sweetalert2.js'; import { Observable, Subject } from 'rxjs'; import { Router } from '@angular/router'; import { UIConfigService } from 'src/app/core/_services/shared/storage.service'; import { TooltipService } from '../../core/_services/shared/tooltip.service'; +import { AlertService } from 'src/app/core/_services/shared/alert.service'; import { colorpicker } from '../../core/_constants/settings.config'; import { GlobalService } from 'src/app/core/_services/main.service'; import { PageTitle } from 'src/app/core/_decorators/autotitle'; @@ -75,6 +75,7 @@ export class NewTasksComponent implements OnInit { private uiService: UIConfigService, private modalService: NgbModal, private route:ActivatedRoute, + private alert: AlertService, private gs: GlobalService, private router: Router ) { } @@ -161,11 +162,7 @@ export class NewTasksComponent implements OnInit { validateFile(value){ if(value.split('.').pop() == '7zip'){ - Swal.fire({ - title: "Heads Up!", - text: "Hashcat has some issues loading 7z files. Better convert it to a hash file ;)", - icon: "warning", - }) + this.alert.okAlert('Hashcat has some issues loading 7z files. Better convert it to a hash file ;)',''); } } @@ -435,17 +432,9 @@ export class NewTasksComponent implements OnInit { onSubmit(){ if (this.createForm.valid) { this.gs.create(SERV.TASKS,this.createForm.value).subscribe(() => { - Swal.fire({ - position: 'top-end', - backdrop: false, - icon: 'success', - title: "Success!", - text: "New Task created!", - showConfirmButton: false, - timer: 1500 - }) - this.createForm.reset(); - this.router.navigate(['tasks/show-tasks']); + this.alert.okAlert('New Task created!',''); + this.createForm.reset(); + this.router.navigate(['tasks/show-tasks']); } ); } diff --git a/src/app/tasks/preconfigured-tasks/preconfigured-tasks.component.ts b/src/app/tasks/preconfigured-tasks/preconfigured-tasks.component.ts index 72865d53..f0ca5274 100644 --- a/src/app/tasks/preconfigured-tasks/preconfigured-tasks.component.ts +++ b/src/app/tasks/preconfigured-tasks/preconfigured-tasks.component.ts @@ -5,6 +5,7 @@ import { DataTableDirective } from 'angular-datatables'; import Swal from 'sweetalert2/dist/sweetalert2.js'; import { Subject } from 'rxjs'; +import { AlertService } from 'src/app/core/_services/shared/alert.service'; import { GlobalService } from 'src/app/core/_services/main.service'; import { PageTitle } from 'src/app/core/_decorators/autotitle'; import { SERV } from '../../core/_services/main.config'; @@ -34,6 +35,7 @@ export class PreconfiguredTasksComponent implements OnInit { dtOptions: any = {}; constructor( + private alert: AlertService, private gs: GlobalService, ) { } @@ -163,21 +165,14 @@ export class PreconfiguredTasksComponent implements OnInit { icon: "warning", reverseButtons: true, showCancelButton: true, - cancelButtonColor: '#8A8584', - confirmButtonColor: '#C53819', - confirmButtonText: 'Yes, delete it!' + cancelButtonColor: this.alert.cancelButtonColor, + confirmButtonColor: this.alert.confirmButtonColor, + confirmButtonText: this.alert.delconfirmText }) .then((result) => { if (result.isConfirmed) { this.gs.delete(SERV.PRETASKS,id).subscribe(() => { - Swal.fire({ - position: 'top-end', - backdrop: false, - icon: 'success', - title: "Success", - showConfirmButton: false, - timer: 1500 - }) + this.alert.okAlert('Deleted '+name+'',''); this.ngOnInit(); this.rerender(); // rerender datatables }); 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 3c7448fe..f00d2d51 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,12 +1,13 @@ +import { faTrash, faEdit, faCopy, faBookmark, faArchive } from '@fortawesome/free-solid-svg-icons'; import { FormArray, FormBuilder, FormControl, FormGroup } from '@angular/forms'; import { Component, Input, OnInit, ViewChild } from '@angular/core'; import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; -import { faTrash, faEdit, faCopy, faBookmark, faArchive } from '@fortawesome/free-solid-svg-icons'; import { DataTableDirective } from 'angular-datatables'; import Swal from 'sweetalert2/dist/sweetalert2.js'; import { Router } from '@angular/router'; import { Subject } from 'rxjs'; +import { AlertService } from 'src/app/core/_services/shared/alert.service'; import { GlobalService } from 'src/app/core/_services/main.service'; import { SERV } from '../../../core/_services/main.config'; @@ -35,6 +36,7 @@ export class ModalSubtasksComponent { constructor( public modal: NgbActiveModal, + private alert: AlertService, private gs: GlobalService, private router: Router, private fb: FormBuilder @@ -95,29 +97,14 @@ export class ModalSubtasksComponent { onArchive(id: number){ this.gs.archive(SERV.TASKS,id).subscribe(() => { - Swal.fire({ - position: 'top-end', - backdrop: false, - icon: 'success', - title: "Success", - text: "Archived!", - showConfirmButton: false, - timer: 1500 - }) + this.alert.okAlert('Archived!',''); this.rerender(); // rerender datatables }); } onDelete(id: number){ this.gs.delete(SERV.TASKS,id).subscribe(() => { - Swal.fire({ - position: 'top-end', - backdrop: false, - icon: 'success', - title: "Success", - showConfirmButton: false, - timer: 1500 - }) + this.alert.okAlert('Deleted ',''); this.rerender(); // rerender datatables }); } diff --git a/src/app/tasks/show-tasks/show-tasks.component.ts b/src/app/tasks/show-tasks/show-tasks.component.ts index fb8c0bb6..a5d37675 100644 --- a/src/app/tasks/show-tasks/show-tasks.component.ts +++ b/src/app/tasks/show-tasks/show-tasks.component.ts @@ -7,10 +7,11 @@ import { DataTableDirective } from 'angular-datatables'; import Swal from 'sweetalert2/dist/sweetalert2.js'; import { Subject, Subscription } from 'rxjs'; +import { ModalSubtasksComponent } from './modal-subtasks/modal-subtasks.component'; +import { AlertService } from 'src/app/core/_services/shared/alert.service'; import { GlobalService } from '../../core/_services/main.service'; import { PageTitle } from 'src/app/core/_decorators/autotitle'; import { SERV } from '../../core/_services/main.config'; -import { ModalSubtasksComponent } from './modal-subtasks/modal-subtasks.component'; declare let $:any; @@ -63,6 +64,7 @@ export class ShowTasksComponent implements OnInit { constructor( private modalService: NgbModal, private route:ActivatedRoute, + private alert: AlertService, private gs: GlobalService, private router: Router ) { } @@ -266,15 +268,7 @@ rerender(): void { onArchive(id: number, type: number){ const path = type === 0 ? SERV.TASKS : SERV.TASKS_WRAPPER; this.gs.archive(path,id).subscribe(() => { - Swal.fire({ - position: 'top-end', - backdrop: false, - icon: 'success', - title: "Success", - text: "Archived!", - showConfirmButton: false, - timer: 1500 - }) + this.alert.okAlert('Archived!',''); this.ngOnInit(); this.rerender(); // rerender datatables }); @@ -302,22 +296,15 @@ onDelete(id: number, type: number, name: string){ icon: "warning", reverseButtons: true, showCancelButton: true, - cancelButtonColor: '#8A8584', - confirmButtonColor: '#C53819', - confirmButtonText: 'Yes, delete it!' + cancelButtonColor: this.alert.cancelButtonColor, + confirmButtonColor: this.alert.confirmButtonColor, + confirmButtonText: this.alert.delconfirmText }) .then((result) => { if (result.isConfirmed) { const path = type === 0 ? SERV.TASKS : SERV.TASKS_WRAPPER; this.gs.delete(path,id).subscribe(() => { - Swal.fire({ - position: 'top-end', - backdrop: false, - icon: 'success', - title: "Success", - showConfirmButton: false, - timer: 1500 - }) + this.alert.okAlert('Deleted '+name+'',''); this.ngOnInit(); this.rerender(); // rerender datatables }); @@ -339,14 +326,7 @@ onSelectedTasks(){ $(".dt-button-background").trigger("click"); const selection = $($(this.dtElement).DataTable.tables()).DataTable().rows({ selected: true } ).data().pluck(0).toArray(); if(selection.length == 0) { - Swal.fire({ - position: 'top-end', - backdrop: false, - title: "You haven't selected any Task", - type: 'success', - timer: 1500, - showConfirmButton: false - }) + this.alert.okAlert('You haven not selected any Group',''); return; } const selectionnum = selection.map(i=>Number(i)); @@ -398,14 +378,7 @@ onDone(value?: any){ this.ngOnInit(); this.rerender(); // rerender datatables Swal.close(); - Swal.fire({ - position: 'top-end', - backdrop: false, - icon: 'success', - title: "Success", - showConfirmButton: false, - timer: 1500 - }) + this.alert.okAlert('Completed',''); },3000); } @@ -415,14 +388,7 @@ onModalProject(title: string){ $(".dt-button-background").trigger("click"); const selection = $($(this.dtElement).DataTable.tables()).DataTable().rows({ selected: true } ).data().pluck(0).toArray(); if(selection.length == 0) { - Swal.fire({ - position: 'top-end', - backdrop: false, - title: "You haven't selected any Task", - type: 'success', - timer: 1500, - showConfirmButton: false - }) + this.alert.okAlert('You haven not selected any Group',''); return; } @@ -476,14 +442,7 @@ onModalUpdate(title: string, id: number, cvalue: any, formlabel: boolean, namere } const path = type === 0 ? SERV.TASKS : SERV.TASKS_WRAPPER; this.gs.update(path,id, update).subscribe(() => { - Swal.fire({ - position: 'top-end', - backdrop: false, - icon: 'success', - title: "Success", - showConfirmButton: false, - timer: 1500 - }) + this.alert.okAlert('Task saved!',''); this.ngOnInit(); this.rerender(); // rerender datatables }); diff --git a/src/app/tasks/supertasks/applyhashlist.component.ts b/src/app/tasks/supertasks/applyhashlist.component.ts index 52f93d56..75e10eb0 100644 --- a/src/app/tasks/supertasks/applyhashlist.component.ts +++ b/src/app/tasks/supertasks/applyhashlist.component.ts @@ -1,8 +1,8 @@ import { ActivatedRoute, Params, Router } from '@angular/router'; import { ChangeDetectorRef, Component } from '@angular/core'; import { DataTableDirective } from 'angular-datatables'; -import Swal from 'sweetalert2/dist/sweetalert2.js'; +import { AlertService } from 'src/app/core/_services/shared/alert.service'; import { GlobalService } from 'src/app/core/_services/main.service'; import { environment } from '../../../environments/environment'; import { PageTitle } from 'src/app/core/_decorators/autotitle'; @@ -26,6 +26,7 @@ export class ApplyHashlistComponent { constructor( private _changeDetectorRef: ChangeDetectorRef, private route:ActivatedRoute, + private alert: AlertService, private gs: GlobalService, private router: Router ) { } @@ -132,16 +133,9 @@ export class ApplyHashlistComponent { onSubmit(){ this.gs.chelper(SERV.HELPER,'createSupertask', this.createForm.value).subscribe(() => { - Swal.fire({ - position: 'top-end', - backdrop: false, - icon: 'success', - title: "Success", - showConfirmButton: false, - timer: 1500 - }) - this.createForm.reset(); - this.router.navigate(['tasks/show-tasks']); + this.alert.okAlert('New SuperTask created!',''); + this.createForm.reset(); + this.router.navigate(['tasks/show-tasks']); } ); } diff --git a/src/app/tasks/supertasks/supertasks.component.ts b/src/app/tasks/supertasks/supertasks.component.ts index 6817f63a..305facbb 100644 --- a/src/app/tasks/supertasks/supertasks.component.ts +++ b/src/app/tasks/supertasks/supertasks.component.ts @@ -6,6 +6,7 @@ import Swal from 'sweetalert2/dist/sweetalert2.js'; import { Subject } from 'rxjs'; import { ModalPretasksComponent } from './modal-pretasks/modal-pretasks.component'; +import { AlertService } from 'src/app/core/_services/shared/alert.service'; import { GlobalService } from 'src/app/core/_services/main.service'; import { environment } from './../../../environments/environment'; import { PageTitle } from 'src/app/core/_decorators/autotitle'; @@ -30,6 +31,7 @@ export class SupertasksComponent implements OnInit { constructor( private modalService: NgbModal, + private alert: AlertService, private gs: GlobalService ) { } @@ -162,21 +164,14 @@ export class SupertasksComponent implements OnInit { icon: "warning", reverseButtons: true, showCancelButton: true, - cancelButtonColor: '#8A8584', - confirmButtonColor: '#C53819', - confirmButtonText: 'Yes, delete it!' + cancelButtonColor: this.alert.cancelButtonColor, + confirmButtonColor: this.alert.confirmButtonColor, + confirmButtonText: this.alert.delconfirmText }) .then((result) => { if (result.isConfirmed) { this.gs.delete(SERV.SUPER_TASKS,id).subscribe(() => { - Swal.fire({ - position: 'top-end', - backdrop: false, - icon: 'success', - title: "Success", - showConfirmButton: false, - timer: 1500 - }) + this.alert.okAlert('Deleted '+name+'',''); this.ngOnInit(); this.rerender(); // rerender datatables }); diff --git a/src/app/users/all-users/all-users.component.ts b/src/app/users/all-users/all-users.component.ts index 372a591a..fefc5874 100644 --- a/src/app/users/all-users/all-users.component.ts +++ b/src/app/users/all-users/all-users.component.ts @@ -6,6 +6,7 @@ import { Router } from '@angular/router'; import { Subject } from 'rxjs'; import { UIConfigService } from '../../core/_services/shared/storage.service'; +import { AlertService } from 'src/app/core/_services/shared/alert.service'; import { GlobalService } from 'src/app/core/_services/main.service'; import { environment } from './../../../environments/environment'; import { PageTitle } from 'src/app/core/_decorators/autotitle'; @@ -54,6 +55,7 @@ export class AllUsersComponent implements OnInit, OnDestroy { constructor( private uiService: UIConfigService, private route:ActivatedRoute, + private alert: AlertService, private gs: GlobalService, private router:Router ) { } @@ -176,21 +178,14 @@ export class AllUsersComponent implements OnInit, OnDestroy { icon: "warning", reverseButtons: true, showCancelButton: true, - cancelButtonColor: '#8A8584', - confirmButtonColor: '#C53819', - confirmButtonText: 'Yes, delete it!' + cancelButtonColor: this.alert.cancelButtonColor, + confirmButtonColor: this.alert.confirmButtonColor, + confirmButtonText: this.alert.delconfirmText }) .then((result) => { if (result.isConfirmed) { this.gs.delete(SERV.USERS,id).subscribe(() => { - Swal.fire({ - position: 'top-end', - backdrop: false, - icon: 'success', - title: "Success", - showConfirmButton: false, - timer: 1500 - }) + this.alert.okAlert('Deleted '+name+'',''); this.ngOnInit(); this.rerender(); // rerender datatables }); @@ -210,14 +205,7 @@ export class AllUsersComponent implements OnInit, OnDestroy { $(".dt-button-background").trigger("click"); const selection = $($(this.dtElement).DataTable.tables()).DataTable().rows({ selected: true } ).data().pluck(0).toArray(); if(selection.length == 0) { - Swal.fire({ - position: 'top-end', - backdrop: false, - title: "You haven't selected any User", - type: 'success', - timer: 1500, - showConfirmButton: false - }) + this.alert.okAlert('You haven not selected any Group',''); return; } const selectionnum = selection.map(i=>Number(i)); @@ -250,13 +238,7 @@ export class AllUsersComponent implements OnInit, OnDestroy { this.ngOnInit(); this.rerender(); // rerender datatables Swal.close(); - Swal.fire({ - position: 'top-end', - backdrop: false, - icon: 'success', - showConfirmButton: false, - timer: 1500 - }) + this.alert.okAlert('Completed',''); },3000); } @@ -270,8 +252,5 @@ export class AllUsersComponent implements OnInit, OnDestroy { } - - - } diff --git a/src/app/users/edit-users/edit-users.component.ts b/src/app/users/edit-users/edit-users.component.ts index 40e6377b..d7e0ed7d 100644 --- a/src/app/users/edit-users/edit-users.component.ts +++ b/src/app/users/edit-users/edit-users.component.ts @@ -5,6 +5,7 @@ import Swal from 'sweetalert2/dist/sweetalert2.js'; import { Component, OnInit } from '@angular/core'; import { UIConfigService } from 'src/app/core/_services/shared/storage.service'; +import { AlertService } from 'src/app/core/_services/shared/alert.service'; import { GlobalService } from 'src/app/core/_services/main.service'; import { PageTitle } from 'src/app/core/_decorators/autotitle'; import { environment } from 'src/environments/environment'; @@ -39,6 +40,7 @@ export class EditUsersComponent implements OnInit { private uiService: UIConfigService, private route:ActivatedRoute, private datePipe:uiDatePipe, + private alert: AlertService, private gs: GlobalService, private router: Router ) { } @@ -103,21 +105,14 @@ export class EditUsersComponent implements OnInit { icon: "warning", reverseButtons: true, showCancelButton: true, - cancelButtonColor: '#8A8584', - confirmButtonColor: '#C53819', - confirmButtonText: 'Yes, delete it!' + cancelButtonColor: this.alert.cancelButtonColor, + confirmButtonColor: this.alert.confirmButtonColor, + confirmButtonText: this.alert.delconfirmText }) .then((result) => { if (result.isConfirmed) { this.gs.delete(SERV.USERS,this.editedUserIndex).subscribe(() => { - Swal.fire({ - position: 'top-end', - backdrop: false, - icon: 'success', - title: "Success", - showConfirmButton: false, - timer: 1500 - }) + this.alert.okAlert('Deleted',''); this.router.navigate(['/users/all-users']); }); } else { @@ -138,14 +133,7 @@ export class EditUsersComponent implements OnInit { this.onUpdatePass(this.updatePassForm.value); this.gs.update(SERV.USERS,this.editedUserIndex, this.updateForm.value.updateData).subscribe(() => { - Swal.fire({ - position: 'top-end', - backdrop: false, - icon: 'success', - title: "Saved", - showConfirmButton: false, - timer: 1500 - }) + this.alert.okAlert('User saved!',''); this.updateForm.reset(); // success, we reset form this.updatePassForm.reset(); this.router.navigate(['users/all-users']); diff --git a/src/app/users/globalpermissionsgroups/edit-globalpermissionsgroups/edit-globalpermissionsgroups.component.ts b/src/app/users/globalpermissionsgroups/edit-globalpermissionsgroups/edit-globalpermissionsgroups.component.ts index dbeda2b3..aff13ce7 100644 --- a/src/app/users/globalpermissionsgroups/edit-globalpermissionsgroups/edit-globalpermissionsgroups.component.ts +++ b/src/app/users/globalpermissionsgroups/edit-globalpermissionsgroups/edit-globalpermissionsgroups.component.ts @@ -3,9 +3,9 @@ import { Component, OnInit, ViewChild } from '@angular/core'; import { faEye } from '@fortawesome/free-solid-svg-icons'; import { FormControl, FormGroup } from '@angular/forms'; import { DataTableDirective } from 'angular-datatables'; -import Swal from 'sweetalert2/dist/sweetalert2.js'; import { Subject } from 'rxjs'; +import { AlertService } from 'src/app/core/_services/shared/alert.service'; import { GlobalService } from 'src/app/core/_services/main.service'; import { PageTitle } from 'src/app/core/_decorators/autotitle'; import { SERV } from '../../../core/_services/main.config'; @@ -25,6 +25,7 @@ export class EditGlobalpermissionsgroupsComponent implements OnInit { constructor( private route:ActivatedRoute, + private alert: AlertService, private gs: GlobalService, private router: Router ) { } @@ -138,17 +139,9 @@ export class EditGlobalpermissionsgroupsComponent implements OnInit { onSubmit(){ if (this.updateForm.valid) { this.gs.update(SERV.ACCESS_PERMISSIONS_GROUPS,this.editedGPGIndex, this.updateForm.value).subscribe(() => { - Swal.fire({ - position: 'top-end', - backdrop: false, - icon: 'success', - title: "Success", - text: "Permission Updated!", - showConfirmButton: false, - timer: 1500 - }) - this.updateForm.reset(); - this.router.navigate(['/users/global-permissions-groups']); + this.alert.okAlert('Global Permission Group saved!',''); + this.updateForm.reset(); + this.router.navigate(['/users/global-permissions-groups']); } ); } diff --git a/src/app/users/globalpermissionsgroups/globalpermissionsgroups.component.ts b/src/app/users/globalpermissionsgroups/globalpermissionsgroups.component.ts index 7d61268b..8e324fdf 100644 --- a/src/app/users/globalpermissionsgroups/globalpermissionsgroups.component.ts +++ b/src/app/users/globalpermissionsgroups/globalpermissionsgroups.component.ts @@ -5,6 +5,7 @@ import Swal from 'sweetalert2/dist/sweetalert2.js'; import { Router } from '@angular/router'; import { Subject } from 'rxjs'; +import { AlertService } from 'src/app/core/_services/shared/alert.service'; import { GlobalService } from 'src/app/core/_services/main.service'; import { PageTitle } from 'src/app/core/_decorators/autotitle'; import { environment } from 'src/environments/environment'; @@ -38,6 +39,7 @@ export class GlobalpermissionsgroupsComponent implements OnInit { public Allgpg: {id: number, name: string , user:[]}[] = []; constructor( + private alert: AlertService, private gs: GlobalService, private router: Router ) { } @@ -163,22 +165,14 @@ export class GlobalpermissionsgroupsComponent implements OnInit { icon: "warning", reverseButtons: true, showCancelButton: true, - cancelButtonColor: '#8A8584', - confirmButtonColor: '#C53819', - confirmButtonText: 'Yes, delete it!' + cancelButtonColor: this.alert.cancelButtonColor, + confirmButtonColor: this.alert.confirmButtonColor, + confirmButtonText: this.alert.delconfirmText }) .then((result) => { if (result.isConfirmed) { this.gs.delete(SERV.ACCESS_PERMISSIONS_GROUPS,id).subscribe(() => { - Swal.fire({ - position: 'top-end', - backdrop: false, - icon: 'success', - title: "Success", - text: "Archived!", - showConfirmButton: false, - timer: 1500 - }) + this.alert.okAlert('Deleted '+name+'',''); this.ngOnInit(); this.rerender(); // rerender datatables }); @@ -198,14 +192,7 @@ export class GlobalpermissionsgroupsComponent implements OnInit { $(".dt-button-background").trigger("click"); const selection = $($(this.dtElement).DataTable.tables()).DataTable().rows({ selected: true } ).data().pluck(0).toArray(); if(selection.length == 0) { - Swal.fire({ - position: 'top-end', - backdrop: false, - title: "You haven't selected any Global Permission Group", - type: 'success', - timer: 1500, - showConfirmButton: false - }) + this.alert.okAlert('You haven not selected any Group',''); return; } const selectionnum = selection.map(i=>Number(i)); @@ -238,13 +225,7 @@ export class GlobalpermissionsgroupsComponent implements OnInit { this.ngOnInit(); this.rerender(); // rerender datatables Swal.close(); - Swal.fire({ - position: 'top-end', - backdrop: false, - icon: 'success', - showConfirmButton: false, - timer: 1500 - }) + this.alert.okAlert('Completed',''); },3000); } diff --git a/src/app/users/globalpermissionsgroups/new-globalpermissionsgroups/new-globalpermissionsgroups.component.ts b/src/app/users/globalpermissionsgroups/new-globalpermissionsgroups/new-globalpermissionsgroups.component.ts index 9e98a91d..706fbaca 100644 --- a/src/app/users/globalpermissionsgroups/new-globalpermissionsgroups/new-globalpermissionsgroups.component.ts +++ b/src/app/users/globalpermissionsgroups/new-globalpermissionsgroups/new-globalpermissionsgroups.component.ts @@ -1,8 +1,8 @@ import { FormControl, FormGroup, Validators } from '@angular/forms'; -import Swal from 'sweetalert2/dist/sweetalert2.js'; import { Component, OnInit } from '@angular/core'; import { Router } from '@angular/router'; +import { AlertService } from 'src/app/core/_services/shared/alert.service'; import { GlobalService } from 'src/app/core/_services/main.service'; import { PageTitle } from 'src/app/core/_decorators/autotitle'; import { SERV } from '../../../core/_services/main.config'; @@ -19,6 +19,7 @@ export class NewGlobalpermissionsgroupsComponent implements OnInit { public isCollapsed = true; constructor( + private alert: AlertService, private gs: GlobalService, private router:Router ) { } @@ -35,15 +36,7 @@ export class NewGlobalpermissionsgroupsComponent implements OnInit { if (this.createForm.valid) { this.gs.create(SERV.ACCESS_PERMISSIONS_GROUPS,this.createForm.value).subscribe(() => { - Swal.fire({ - position: 'top-end', - backdrop: false, - icon: 'success', - title: "Success", - text: "Global Permission Group created!", - showConfirmButton: false, - timer: 1500 - }) + this.alert.okAlert('New Global Permission Group created!',''); this.router.navigate(['/users/global-permissions-groups']); } ); diff --git a/src/app/users/groups/cu-group/cu-group.component.ts b/src/app/users/groups/cu-group/cu-group.component.ts index 4c9ef5cd..e90576bb 100644 --- a/src/app/users/groups/cu-group/cu-group.component.ts +++ b/src/app/users/groups/cu-group/cu-group.component.ts @@ -1,8 +1,8 @@ import { FormControl, FormGroup, Validators } from '@angular/forms'; import { ActivatedRoute, Params, Router } from '@angular/router'; -import Swal from 'sweetalert2/dist/sweetalert2.js'; import { Component, OnInit } from '@angular/core'; +import { AlertService } from 'src/app/core/_services/shared/alert.service'; import { GlobalService } from 'src/app/core/_services/main.service'; import { PageTitle } from 'src/app/core/_decorators/autotitle'; import { SERV } from '../../../core/_services/main.config'; @@ -21,6 +21,7 @@ export class CUGroupComponent implements OnInit { constructor( private route:ActivatedRoute, + private alert: AlertService, private gs: GlobalService, private router:Router ) { } @@ -73,14 +74,7 @@ export class CUGroupComponent implements OnInit { case 'create': this.gs.create(SERV.ACCESS_GROUPS,this.Form.value).subscribe(() => { - Swal.fire({ - position: 'top-end', - backdrop: false, - icon: 'success', - title: "Success", - showConfirmButton: false, - timer: 1500 - }) + this.alert.okAlert('New Access Group created!',''); this.router.navigate(['/users/access-groups']); } ); @@ -88,14 +82,7 @@ export class CUGroupComponent implements OnInit { case 'edit': this.gs.update(SERV.ACCESS_GROUPS,this.editedIndex,this.Form.value).subscribe(() => { - Swal.fire({ - position: 'top-end', - backdrop: false, - icon: 'success', - title: "Saved", - showConfirmButton: false, - timer: 1500 - }) + this.alert.okAlert('Access Group saved!',''); this.router.navigate(['/users/access-groups']); }); break; diff --git a/src/app/users/groups/groups.component.ts b/src/app/users/groups/groups.component.ts index f843c80d..196029c9 100644 --- a/src/app/users/groups/groups.component.ts +++ b/src/app/users/groups/groups.component.ts @@ -5,6 +5,7 @@ import { DataTableDirective } from 'angular-datatables'; import Swal from 'sweetalert2/dist/sweetalert2.js'; import { Subject } from 'rxjs'; +import { AlertService } from 'src/app/core/_services/shared/alert.service'; import { GlobalService } from 'src/app/core/_services/main.service'; import { PageTitle } from 'src/app/core/_decorators/autotitle'; import { SERV } from '../../core/_services/main.config'; @@ -38,6 +39,7 @@ export class GroupsComponent implements OnInit { public agroups: {accessGroupId: number, groupName: string, isEdit: false }[] = []; constructor( + private alert: AlertService, private gs: GlobalService, ) { } @@ -168,21 +170,14 @@ export class GroupsComponent implements OnInit { icon: "warning", reverseButtons: true, showCancelButton: true, - cancelButtonColor: '#8A8584', - confirmButtonColor: '#C53819', - confirmButtonText: 'Yes, delete it!' + cancelButtonColor: this.alert.cancelButtonColor, + confirmButtonColor: this.alert.confirmButtonColor, + confirmButtonText: this.alert.delconfirmText }) .then((result) => { if (result.isConfirmed) { this.gs.delete(SERV.ACCESS_GROUPS,id).subscribe(() => { - Swal.fire({ - position: 'top-end', - backdrop: false, - icon: 'success', - title: "Success", - showConfirmButton: false, - timer: 1500 - }) + this.alert.okAlert('Deleted '+name+'',''); this.ngOnInit(); this.rerender(); // rerender datatables }); @@ -202,14 +197,7 @@ export class GroupsComponent implements OnInit { $(".dt-button-background").trigger("click"); const selection = $($(this.dtElement).DataTable.tables()).DataTable().rows({ selected: true } ).data().pluck(0).toArray(); if(selection.length == 0) { - Swal.fire({ - position: 'top-end', - backdrop: false, - title: "You haven't selected any Group", - type: 'success', - timer: 1500, - showConfirmButton: false - }) + this.alert.okAlert('You haven not selected any Group',''); return; } const selectionnum = selection.map(i=>Number(i)); @@ -242,13 +230,7 @@ export class GroupsComponent implements OnInit { this.ngOnInit(); this.rerender(); // rerender datatables Swal.close(); - Swal.fire({ - position: 'top-end', - backdrop: false, - icon: 'success', - showConfirmButton: false, - timer: 1500 - }) + this.alert.okAlert('Completed',''); },3000); } diff --git a/src/app/users/users.component.ts b/src/app/users/users.component.ts index d8515208..e2f5f140 100644 --- a/src/app/users/users.component.ts +++ b/src/app/users/users.component.ts @@ -1,8 +1,8 @@ import { FormControl, FormGroup, Validators } from '@angular/forms'; import { ActivatedRoute, Params, Router } from '@angular/router'; -import Swal from 'sweetalert2/dist/sweetalert2.js'; import { Component, OnInit } from '@angular/core'; +import { AlertService } from '../core/_services/shared/alert.service'; import { GlobalService } from 'src/app/core/_services/main.service'; import { PageTitle } from 'src/app/core/_decorators/autotitle'; import { environment } from 'src/environments/environment'; @@ -21,6 +21,7 @@ export class UsersComponent implements OnInit { constructor( private route:ActivatedRoute, + private alert: AlertService, private gs: GlobalService, private router: Router ){} @@ -55,15 +56,8 @@ export class UsersComponent implements OnInit { if (this.createForm.valid) { this.gs.create(SERV.USERS,this.createForm.value).subscribe(() => { - Swal.fire({ - position: 'top-end', - backdrop: false, - icon: 'success', - title: "Success", - showConfirmButton: false, - timer: 1500 - }) - this.router.navigate(['users/all-users']); + this.alert.okAlert('New User created!',''); + this.router.navigate(['users/all-users']); } ); } From 2aa9c66fc819904ea511ec617dc50e357de86ba0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Thu, 19 Oct 2023 20:12:54 +0100 Subject: [PATCH 084/419] Notification service and bulk delete/update with progress bar --- package-lock.json | 8 +- package.json | 2 +- .../agents/new-agent/new-agent.component.ts | 45 ++---- .../show-agents/show-agents.component.ts | 105 ++++---------- .../agent-binaries.component.ts | 44 ++---- .../engine/crackers/crackers.component.ts | 28 ++-- .../edit-version/edit-crackers.component.ts | 35 ++--- .../preprocessors/preprocessors.component.ts | 43 +++--- .../config/hashtypes/hashtypes.component.ts | 80 +++-------- .../health-checks/health-checks.component.ts | 45 +++--- src/app/core/_guards/permission.guard.ts | 10 +- .../core/_services/shared/alert.service.ts | 131 +++++++++++++++++- src/app/core/_services/shared/bulk.service.ts | 127 +++++++++++++++++ src/app/files/files.component.ts | 100 ++++--------- .../hashlists/hashlist/hashlist.component.ts | 91 ++++-------- .../superhashlist/superhashlist.component.ts | 45 +++--- .../edit-supertasks.component.ts | 46 ++---- .../preconfigured-tasks.component.ts | 46 ++---- .../tasks/show-tasks/show-tasks.component.ts | 109 +++++---------- .../tasks/supertasks/supertasks.component.ts | 44 ++---- .../users/all-users/all-users.component.ts | 77 +++------- .../users/edit-users/edit-users.component.ts | 37 ++--- .../globalpermissionsgroups.component.ts | 81 ++++------- src/app/users/groups/groups.component.ts | 81 ++++------- src/styles/components/_alert.scss | 26 ++++ 25 files changed, 645 insertions(+), 841 deletions(-) create mode 100644 src/app/core/_services/shared/bulk.service.ts diff --git a/package-lock.json b/package-lock.json index 9b7afea4..26913f94 100644 --- a/package-lock.json +++ b/package-lock.json @@ -61,7 +61,7 @@ "pdfmake": "^0.2.7", "popper.js": "^1.16.1", "rxjs": "~7.8.1", - "sweetalert2": "^11.7.12", + "sweetalert2": "^11.7.32", "tslib": "^2.6.0", "tus-js-client": "^3.1.1", "zone.js": "~0.13.1" @@ -16282,9 +16282,9 @@ } }, "node_modules/sweetalert2": { - "version": "11.7.23", - "resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.7.23.tgz", - "integrity": "sha512-zhc2Jc/coeUFPCM7LyVaUddbSz1Av9/EgQVXxZVPre8cwJrTZXCOEAboCT11StS3vaFTio74cumWustmNzDOEw==", + "version": "11.7.32", + "resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.7.32.tgz", + "integrity": "sha512-44tNNe2oLe7T94mT6dus4hc9G7qg6jZU/K5qZzpNS6e5HGPrSF6Kie6oZ7B5puIJydB34V2h/8f5EhIFivYo4A==", "funding": { "type": "individual", "url": "https://github.com/sponsors/limonte" diff --git a/package.json b/package.json index 4c9daf6c..6f8a4348 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "pdfmake": "^0.2.7", "popper.js": "^1.16.1", "rxjs": "~7.8.1", - "sweetalert2": "^11.7.12", + "sweetalert2": "^11.7.32", "tslib": "^2.6.0", "tus-js-client": "^3.1.1", "zone.js": "~0.13.1" diff --git a/src/app/agents/new-agent/new-agent.component.ts b/src/app/agents/new-agent/new-agent.component.ts index f22282a0..463a733f 100644 --- a/src/app/agents/new-agent/new-agent.component.ts +++ b/src/app/agents/new-agent/new-agent.component.ts @@ -140,41 +140,26 @@ export class NewAgentComponent implements OnInit, OnDestroy { } onDelete(id: number, name: string ){ - const swalWithBootstrapButtons = Swal.mixin({ - customClass: { - confirmButton: 'btn', - cancelButton: 'btn' - }, - buttonsStyling: false - }) - Swal.fire({ - title: 'Remove '+ name +' from your Vouchers?', - text: "Once deleted, it can not be recovered!", - icon: "warning", - reverseButtons: true, - showCancelButton: true, - cancelButtonColor: this.alert.cancelButtonColor, - confirmButtonColor: this.alert.confirmButtonColor, - confirmButtonText: this.alert.delconfirmText - }) - .then((result) => { - if (result.isConfirmed) { - this.gs.delete(SERV.VOUCHER,id).subscribe(() => { - this.alert.okAlert('Deleted '+name+'',''); - this.ngOnInit(); - this.rerender(); // rerender datatables + this.alert.deleteConfirmation(name,'Vouchers').then((confirmed) => { + if (confirmed) { + // Deletion + this.gs.delete(SERV.VOUCHER, id).subscribe(() => { + // Successful deletion + this.alert.okAlert(`Deleted Voucher ${name}`, ''); + this.onRefreshTable(); // Refresh the table }); } else { - swalWithBootstrapButtons.fire({ - title: "Cancelled", - text: "Your Voucher is safe!", - icon: "error", - showConfirmButton: false, - timer: 1500 - }) + // Handle cancellation + this.alert.okAlert(`Your Voucher ${name} is safe!`,''); } }); + } + onRefreshTable(){ + setTimeout(() => { + this.ngOnInit(); + this.rerender(); // rerender datatables + },2000); } downloadClient(id) { diff --git a/src/app/agents/show-agents/show-agents.component.ts b/src/app/agents/show-agents/show-agents.component.ts index b541adcb..c5f92ab5 100644 --- a/src/app/agents/show-agents/show-agents.component.ts +++ b/src/app/agents/show-agents/show-agents.component.ts @@ -224,15 +224,6 @@ export class ShowAgentsComponent implements OnInit, OnDestroy { }); } - onDone(value?: any){ - setTimeout(() => { - this.ngOnInit(); - this.rerender(); // rerender datatables - Swal.close(); - this.alert.okAlert('Completed!',''); - },3000); - } - onSelectedAgents(){ $(".dt-button-background").trigger("click"); const selection = $($(this.dtElement).DataTable.tables()).DataTable().rows({ selected: true } ).data().pluck(0).toArray(); @@ -245,43 +236,16 @@ export class ShowAgentsComponent implements OnInit, OnDestroy { return selectionnum; } - onDeleteBulk(){ - const self = this; - const selectionnum = this.onSelectedAgents(); - const sellen = selectionnum.length; - const errors = []; - selectionnum.forEach(function (value) { - Swal.fire('Deleting...'+sellen+' Agent(s)...Please wait') - Swal.showLoading() - self.gs.delete(SERV.AGENTS,value) - .subscribe( - err => { - // console.log('HTTP Error', err) - err = 1; - errors.push(err); - }, - ); - }); - self.onDone(sellen); + async onDeleteBulk() { + const AgentIds = this.onSelectedAgents(); + this.alert.bulkDeleteAlert(AgentIds,'Agents',SERV.AGENTS); + this.onRefreshTable(); } - onUpdateBulk(value: any){ - const self = this; - const selectionnum = this.onSelectedAgents(); - const sellen = selectionnum.length; - const errors = []; - selectionnum.forEach(function (id) { - Swal.fire('Updating...'+sellen+' Agents...Please wait') - Swal.showLoading() - self.gs.update(SERV.AGENTS,id, value).subscribe( - err => { - // console.log('HTTP Error', err) - err = 1; - errors.push(err); - }, - ); - }); - self.onDone(sellen); + async onUpdateBulk(value: any) { + const AgentIds = this.onSelectedAgents(); + this.alert.bulkUpdateAlert(AgentIds,value,'Agents',SERV.AGENTS); + this.onRefreshTable(); } onModal(title: string){ @@ -320,39 +284,26 @@ export class ShowAgentsComponent implements OnInit, OnDestroy { } onDelete(id: number, name: string){ - const swalWithBootstrapButtons = Swal.mixin({ - customClass: { - confirmButton: 'btn', - cancelButton: 'btn' - }, - buttonsStyling: false - }) - Swal.fire({ - title: 'Remove '+ name +' from your Agents?', - icon: "warning", - reverseButtons: true, - showCancelButton: true, - cancelButtonColor: this.alert.cancelButtonColor, - confirmButtonColor: this.alert.confirmButtonColor, - confirmButtonText: this.alert.delconfirmText - }) - .then((result) => { - if (result.isConfirmed) { - this.gs.delete(SERV.AGENTS,id).subscribe(() => { - this.alert.okAlert('Deleted '+name+'',''); - this.ngOnInit(); - this.rerender(); // rerender datatables - }); - } else { - swalWithBootstrapButtons.fire({ - title: "Cancelled", - text: "Your Agent is safe!", - icon: "error", - showConfirmButton: false, - timer: 1500 - }) - } - }); + this.alert.deleteConfirmation(name,'Agents').then((confirmed) => { + if (confirmed) { + // Deletion + this.gs.delete(SERV.AGENTS, id).subscribe(() => { + // Successful deletion + this.alert.okAlert(`Deleted Agent ${name}`, ''); + this.onRefreshTable(); // Refresh the table + }); + } else { + // Handle cancellation + this.alert.okAlert(`Agent ${name} is safe!`,''); + } + }); + } + + onRefreshTable(){ + setTimeout(() => { + this.ngOnInit(); + this.rerender(); // rerender datatables + },2000); } } diff --git a/src/app/config/engine/agent-binaries/agent-binaries.component.ts b/src/app/config/engine/agent-binaries/agent-binaries.component.ts index 46e9ffce..6ca7218e 100644 --- a/src/app/config/engine/agent-binaries/agent-binaries.component.ts +++ b/src/app/config/engine/agent-binaries/agent-binaries.component.ts @@ -135,40 +135,26 @@ export class AgentBinariesComponent implements OnInit { } onDelete(id: number, name: string){ - const swalWithBootstrapButtons = Swal.mixin({ - customClass: { - confirmButton: 'btn', - cancelButton: 'btn' - }, - buttonsStyling: false - }) - Swal.fire({ - title: 'Remove '+ name +' from your Binaries?', - icon: "warning", - reverseButtons: true, - showCancelButton: true, - cancelButtonColor: this.alert.cancelButtonColor, - confirmButtonColor: this.alert.confirmButtonColor, - confirmButtonText: this.alert.delconfirmText - }) - .then((result) => { - if (result.isConfirmed) { - this.gs.delete(SERV.AGENT_BINARY,id).subscribe(() => { - this.alert.okAlert('Deleted '+name+'',''); - this.ngOnInit(); - this.rerender(); // rerender datatables + this.alert.deleteConfirmation(name,'Binaries').then((confirmed) => { + if (confirmed) { + // Deletion + this.gs.delete(SERV.AGENT_BINARY, id).subscribe(() => { + // Successful deletion + this.alert.okAlert(`Deleted Binary ${name}`, ''); + this.onRefreshTable(); // Refresh the table }); } else { - swalWithBootstrapButtons.fire({ - title: "Cancelled", - text: "Your Binary is safe!", - icon: "error", - showConfirmButton: false, - timer: 1500 - }) + // Handle cancellation + this.alert.okAlert(`Binary ${name} is safe!`,''); } }); } + onRefreshTable(){ + setTimeout(() => { + this.ngOnInit(); + this.rerender(); // rerender datatables + },2000); + } } diff --git a/src/app/config/engine/crackers/crackers.component.ts b/src/app/config/engine/crackers/crackers.component.ts index cf6ff3ce..eddea297 100644 --- a/src/app/config/engine/crackers/crackers.component.ts +++ b/src/app/config/engine/crackers/crackers.component.ts @@ -132,22 +132,26 @@ export class CrackersComponent implements OnInit, OnDestroy { } onDelete(id: number, name: string){ - Swal.fire({ - title: 'Remove '+ name +' from your crackers?', - icon: "warning", - buttons: true, - dangerMode: true, - showCancelButton: true, - }) - .then((willDelete) => { - if (willDelete) { - this.gs.delete(SERV.CRACKERS_TYPES,id).subscribe(() => { - this.alert.okAlert('Deleted '+name+'',''); + this.alert.deleteConfirmation(name,'Crackers').then((confirmed) => { + if (confirmed) { + // Deletion + this.gs.delete(SERV.CRACKERS_TYPES, id).subscribe(() => { + // Successful deletion + this.alert.okAlert(`Deleted Cracker ${name}`, ''); + this.onRefreshTable(); // Refresh the table }); } else { - Swal.fire("Your Cracker is safe!") + // Handle cancellation + this.alert.okAlert(`Cracker ${name} is safe!`,''); } }); } + onRefreshTable(){ + setTimeout(() => { + this.ngOnInit(); + this.rerender(); // rerender datatables + },2000); + } + } diff --git a/src/app/config/engine/crackers/edit-version/edit-crackers.component.ts b/src/app/config/engine/crackers/edit-version/edit-crackers.component.ts index 40d4d2eb..7bdddf0a 100644 --- a/src/app/config/engine/crackers/edit-version/edit-crackers.component.ts +++ b/src/app/config/engine/crackers/edit-version/edit-crackers.component.ts @@ -61,36 +61,17 @@ export class EditCrackersComponent implements OnInit { } onDelete(){ - const swalWithBootstrapButtons = Swal.mixin({ - customClass: { - confirmButton: 'btn', - cancelButton: 'btn' - }, - buttonsStyling: false - }) - Swal.fire({ - title: 'Remove from your crackers?', - icon: "warning", - reverseButtons: true, - showCancelButton: true, - cancelButtonColor: this.alert.cancelButtonColor, - confirmButtonColor: this.alert.confirmButtonColor, - confirmButtonText: this.alert.delconfirmText - }) - .then((result) => { - if (result.isConfirmed) { - this.gs.delete(SERV.CRACKERS,this.editedCrackervIndex).subscribe(() => { - this.alert.okAlert('Deleted ',''); + this.alert.deleteConfirmation('','Crackers').then((confirmed) => { + if (confirmed) { + // Deletion + this.gs.delete(SERV.CRACKERS, this.editedCrackervIndex).subscribe(() => { + // Successful deletion + this.alert.okAlert(`Deleted cracker`, ''); this.router.navigate(['config/engine/crackers']); }); } else { - swalWithBootstrapButtons.fire({ - title: "Cancelled", - text: "Your Cracker is safe!", - icon: "error", - showConfirmButton: false, - timer: 1500 - }) + // Handle cancellation + this.alert.okAlert(`Cracker is safe!`,''); } }); } diff --git a/src/app/config/engine/preprocessors/preprocessors.component.ts b/src/app/config/engine/preprocessors/preprocessors.component.ts index ffcd7631..bd29b71f 100644 --- a/src/app/config/engine/preprocessors/preprocessors.component.ts +++ b/src/app/config/engine/preprocessors/preprocessors.component.ts @@ -132,37 +132,26 @@ export class PreprocessorsComponent implements OnInit { } onDelete(id: number, name: string){ - const swalWithBootstrapButtons = Swal.mixin({ - customClass: { - confirmButton: 'btn', - cancelButton: 'btn' - }, - buttonsStyling: false - }) - Swal.fire({ - title: 'Remove '+ name +' from your preprocessors?', - icon: "warning", - reverseButtons: true, - showCancelButton: true, - cancelButtonColor: this.alert.cancelButtonColor, - confirmButtonColor: this.alert.confirmButtonColor, - confirmButtonText: this.alert.delconfirmText - }) - .then((result) => { - if (result.isConfirmed) { - this.gs.delete(SERV.PREPROCESSORS,id).subscribe(() => { - this.alert.okAlert('Deleted '+name+'',''); - this.ngOnInit(); - this.rerender(); // rerender datatables + this.alert.deleteConfirmation(name,'Preprocessors').then((confirmed) => { + if (confirmed) { + // Deletion + this.gs.delete(SERV.PREPROCESSORS, id).subscribe(() => { + // Successful deletion + this.alert.okAlert(`Deleted Preprocessor ${name}`, ''); + this.onRefreshTable(); // Refresh the table }); } else { - swalWithBootstrapButtons.fire( - 'Cancelled', - 'No worries, your Preprocessor is safe!', - 'error' - ) + // Handle cancellation + this.alert.okAlert(`Preprocessor ${name} is safe!`,''); } }); } + onRefreshTable(){ + setTimeout(() => { + this.ngOnInit(); + this.rerender(); // rerender datatables + },2000); + } + } diff --git a/src/app/config/hashtypes/hashtypes.component.ts b/src/app/config/hashtypes/hashtypes.component.ts index 507ccc52..e32dcdba 100644 --- a/src/app/config/hashtypes/hashtypes.component.ts +++ b/src/app/config/hashtypes/hashtypes.component.ts @@ -5,6 +5,7 @@ import Swal from 'sweetalert2/dist/sweetalert2.js'; import { Subject } from 'rxjs'; import { AlertService } from 'src/app/core/_services/shared/alert.service'; +import { BulkService } from 'src/app/core/_services/shared/bulk.service'; import { GlobalService } from 'src/app/core/_services/main.service'; import { environment } from './../../../environments/environment'; import { PageTitle } from 'src/app/core/_decorators/autotitle'; @@ -36,7 +37,8 @@ export class HashtypesComponent implements OnInit { constructor( private alert: AlertService, - private gs: GlobalService, + private bulk: BulkService, + private gs: GlobalService ) { } public htypes: any; @@ -152,47 +154,18 @@ export class HashtypesComponent implements OnInit { }); } - onDone(value?: any){ - setTimeout(() => { - this.ngOnInit(); - this.rerender(); // rerender datatables - Swal.close(); - this.alert.okAlert('Completed',''); - },3000); - } - onDelete(id: number, name: string){ - const swalWithBootstrapButtons = Swal.mixin({ - customClass: { - confirmButton: 'btn', - cancelButton: 'btn' - }, - buttonsStyling: false - }) - Swal.fire({ - title: 'Remove '+ name +' from your hashtypes?', - icon: "warning", - reverseButtons: true, - showCancelButton: true, - cancelButtonColor: this.alert.cancelButtonColor, - confirmButtonColor: this.alert.confirmButtonColor, - confirmButtonText: this.alert.delconfirmText - }) - .then((result) => { - if (result.isConfirmed) { - this.gs.delete(SERV.HASHTYPES,id).subscribe(() => { - this.alert.okAlert('Deleted '+name+'',''); - this.ngOnInit(); - this.rerender(); // rerender datatables + this.alert.deleteConfirmation(name,'Hashtypes').then((confirmed) => { + if (confirmed) { + // Deletion + this.gs.delete(SERV.HASHTYPES, id).subscribe(() => { + // Successful deletion + this.alert.okAlert(`Deleted Hashtype ${name}`, ''); + this.onRefreshTable(); // Refresh the table }); } else { - swalWithBootstrapButtons.fire({ - title: "Cancelled", - text: "Your Hashtype is safe!", - icon: "error", - showConfirmButton: false, - timer: 1500 - }) + // Handle cancellation + this.alert.okAlert(`Hashtype ${name} is safe!`,''); } }); } @@ -209,24 +182,17 @@ export class HashtypesComponent implements OnInit { return selectionnum; } - onDeleteBulk(){ - const self = this; - const selectionnum = this.onSelectedHashtypes(); - const sellen = selectionnum.length; - const errors = []; - selectionnum.forEach(function (value) { - Swal.fire('Deleting...'+sellen+' Hashtype(s)...Please wait') - Swal.showLoading() - self.gs.delete(SERV.HASHTYPES,value) - .subscribe( - err => { - // console.log('HTTP Error', err) - err = 1; - errors.push(err); - }, - ); - }); - self.onDone(sellen); + async onDeleteBulk() { + const HashtypesIds = this.onSelectedHashtypes(); + this.alert.bulkDeleteAlert(HashtypesIds,'Hashtypes',SERV.HASHTYPES); + this.onRefreshTable(); + } + + onRefreshTable(){ + setTimeout(() => { + this.ngOnInit(); + this.rerender(); // rerender datatables + },2000); } // Add unsubscribe to detect changes diff --git a/src/app/config/health-checks/health-checks.component.ts b/src/app/config/health-checks/health-checks.component.ts index fc54eb84..07e23cf4 100644 --- a/src/app/config/health-checks/health-checks.component.ts +++ b/src/app/config/health-checks/health-checks.component.ts @@ -163,39 +163,26 @@ export class HealthChecksComponent implements OnInit { } onDelete(id: number, name: string){ - const swalWithBootstrapButtons = Swal.mixin({ - customClass: { - confirmButton: 'btn', - cancelButton: 'btn' - }, - buttonsStyling: false - }) - Swal.fire({ - title: 'Remove '+ name +' from your health checks?', - icon: "warning", - reverseButtons: true, - showCancelButton: true, - cancelButtonColor: this.alert.cancelButtonColor, - confirmButtonColor: this.alert.confirmButtonColor, - confirmButtonText: this.alert.delconfirmText - }) - .then((result) => { - if (result.isConfirmed) { - this.gs.delete(SERV.HEALTH_CHECKS,id).subscribe(() => { - this.alert.okAlert('Deleted '+name+'',''); - this.ngOnInit(); - this.rerender(); // rerender datatables + this.alert.deleteConfirmation(name,'Health Checks').then((confirmed) => { + if (confirmed) { + // Deletion + this.gs.delete(SERV.HEALTH_CHECKS, id).subscribe(() => { + // Successful deletion + this.alert.okAlert(`Deleted Health Check ${name}`, ''); + this.onRefreshTable(); // Refresh the table }); } else { - swalWithBootstrapButtons.fire({ - title: "Cancelled", - text: "Your Health Check is safe!", - icon: "error", - showConfirmButton: false, - timer: 1500 - }) + // Handle cancellation + this.alert.okAlert(`Health Check ${name} is safe!`,''); } }); } + onRefreshTable(){ + setTimeout(() => { + this.ngOnInit(); + this.rerender(); // rerender datatables + },2000); + } + } diff --git a/src/app/core/_guards/permission.guard.ts b/src/app/core/_guards/permission.guard.ts index da3565e0..90a23bbf 100644 --- a/src/app/core/_guards/permission.guard.ts +++ b/src/app/core/_guards/permission.guard.ts @@ -5,6 +5,7 @@ import { Injectable, inject } from "@angular/core"; import { map, Observable, take } from "rxjs"; import { GlobalService } from 'src/app/core/_services/main.service'; +import { AlertService } from "../_services/shared/alert.service"; import { SERV } from '../_services/main.config'; @Injectable({ @@ -14,6 +15,7 @@ export class PermissionGuard { isAuthenticated: boolean; constructor( + private alert: AlertService, private gs: GlobalService ){} @@ -27,13 +29,7 @@ export class PermissionGuard { if(hasAccess || typeof hasAccess == 'undefined'){ return true; } - Swal.fire({ - title: "ACCESS DENIED", - text: "Please contact your Administrator.", - icon: "error", - showConfirmButton: false, - timer: 2000 - }) + this.alert.okAlert('ACCESS DENIED','Please contact your Administrator.','error'); return false; })); } diff --git a/src/app/core/_services/shared/alert.service.ts b/src/app/core/_services/shared/alert.service.ts index d0a96d5c..44ee870d 100644 --- a/src/app/core/_services/shared/alert.service.ts +++ b/src/app/core/_services/shared/alert.service.ts @@ -1,11 +1,15 @@ import Swal from 'sweetalert2/dist/sweetalert2.js'; import { Injectable } from '@angular/core'; +import { BulkService } from './bulk.service'; + @Injectable({ providedIn: 'root' }) export class AlertService { - constructor() {} + constructor( + private bulk: BulkService, + ) {} cancelButtonColor = '#8A8584'; confirmButtonColor = '#C53819'; @@ -29,9 +33,132 @@ export class AlertService { backdrop: false, toast: true, showConfirmButton: false, - timer: 1500 + timer: 2000 + }); + } + + /** + * Handles delete confirmation. + * Displays a confirmation modal on the top end of the screen using library Sweet Alert + * + * @param {string} name - Item name + * @param {string} title - Additional text + */ + + deleteConfirmation(name: string, title: string): Promise { + return Swal.fire({ + title: `Remove ${name} from your ${title}?`, + icon: 'warning', + showCancelButton: true, + cancelButtonColor: this.cancelButtonColor, + confirmButtonColor: this.confirmButtonColor, + confirmButtonText: this.delconfirmText + }).then((result) => { + return result.isConfirmed; + }); + } + + /** + * Bulk delete modal with progress bar + * Uses a bulk action service to delete on bulk + * + * @param {array} items - Array of ids to be deleted + * @param {string} text - Text to use in the display i.e Hashtypes + * @param {string} path - API path call to delete the items on the array + */ + + bulkDeleteAlert(items: any[], text: string, path: string ) { + this.bulk.setItems(items); // Items to be deleted + this.bulk.setPath(path); //Path route + Swal.fire({ + title: `Deleting ${items.length} ${text}`, + html: '
', + showCancelButton: false, + showConfirmButton: false, + allowEscapeKey: false, //Dont let user escape modal until its finish + allowOutsideClick: false,//Dont let user close modal until its finish + didOpen: () => { + const progressBar = Swal.getHtmlContainer().querySelector('.progress-bar'); + progressBar.style.width = '0%'; + + this.bulk + .performBulkDelete((percentage) => { + progressBar.style.width = percentage + '%'; + }) + .then((success) => { + if (success) { + this.okAlert(`${items.length} ${text} deleted`,'') + } else { + Swal.update({ + icon: 'error', + title: 'Error Deleting Items', + showConfirmButton: true, + }); + } + }) + .catch((error) => { + Swal.update({ + icon: 'error', + title: 'Error Deleting Items', + text: error.message, + showConfirmButton: true, + }); + }); + }, }); } + /** + * Bulk update modal with progress bar + * Uses a bulk action service to delete on bulk + * + * @param {array} items - Array of ids to be deleted + * @param {any} value - Value to be updated + * @param {string} text - Text to use in the display i.e Hashtypes + * @param {string} path - API path call to delete the items on the array + */ + + bulkUpdateAlert(items: any[], value: any, text: string, path: string ) { + this.bulk.setItems(items); // Items to be deleted + this.bulk.setValue(value); + this.bulk.setPath(path); //Path route + Swal.fire({ + title: `Updating ${items.length} ${text}`, + html: '
', + showCancelButton: false, + showConfirmButton: false, + allowEscapeKey: false, //Dont let user escape modal until its finish + allowOutsideClick: false,//Dont let user close modal until its finish + didOpen: () => { + const progressBar = Swal.getHtmlContainer().querySelector('.progress-bar'); + progressBar.style.width = '0%'; + + this.bulk + .performBulkUpdate((percentage) => { + progressBar.style.width = percentage + '%'; + }) + .then((success) => { + if (success) { + this.okAlert(`${items.length} ${text} updated`,'') + } else { + Swal.update({ + icon: 'error', + title: 'Error Updating Items', + showConfirmButton: true, + }); + } + }) + .catch((error) => { + Swal.update({ + icon: 'error', + title: 'Error Updating Items', + text: error.message, + showConfirmButton: true, + }); + }); + }, + }); + } + } diff --git a/src/app/core/_services/shared/bulk.service.ts b/src/app/core/_services/shared/bulk.service.ts new file mode 100644 index 00000000..8a3868e9 --- /dev/null +++ b/src/app/core/_services/shared/bulk.service.ts @@ -0,0 +1,127 @@ +import { catchError, finalize, map, mergeMap, toArray } from 'rxjs/operators'; +import { from, of, Observable, Subscription, forkJoin } from 'rxjs'; +import { Injectable } from '@angular/core'; + +import { GlobalService } from '../main.service'; + +@Injectable({ + providedIn: 'root' +}) +export class BulkService { + + constructor( + private gs: GlobalService + ) {} + + Items: any[]; + Value: any; + deleteDelay = 0; + path: string; + + setItems(Items: any[]) { + this.Items = Items; + } + + setValue(Value: any[]) { + this.Value = Value; + } + + setPath(path: string) { + this.path = path; + } + + /** + * Handles bulk actions such as update and delete + * Displays a progress bar and one is complete return confirmation of action + * + * @param {number} percentage - Progress value + */ + + async performBulkDelete( + progressCallback: (percentage: number) => void + ): Promise { + const Items = this.Items; + const totalItems = Items.length; + let deletedItems = 0; + + // Create an array to collect the results (true for success, false for failure) + const results: boolean[] = []; + + const itemObservables: Observable[] = []; + + Items.forEach((item) => { + const observable = this.gs.delete(this.path, item).pipe( + map(() => { + deletedItems++; + const progress = (deletedItems / totalItems) * 100; + progressCallback(progress); + return true; // Indicate success for each item + }), + catchError((error) => { + // Handle errors + return of(false); // Indicate failure for each item + }) + ); + + itemObservables.push(observable); + }); + + // Use forkJoin to wait for all item observables to complete + const resultsArray = await forkJoin(itemObservables).toPromise(); + + // Check if any item deletion has failed + const hasFailure = resultsArray.some((result) => result === false); + + return !hasFailure; // Return true if there are no failures + } + + /** + * Handles update bulk + * Displays a progress bar and one is complete return confirmation of action + * + * @param {number} percentage - Progress value + */ + + async performBulkUpdate( + progressCallback: (percentage: number) => void + ): Promise { + const Items = this.Items; + const Value = this.Value; + const totalItems = Items.length; + let deletedItems = 0; + + // Create an array to collect the results (true for success, false for failure) + const results: boolean[] = []; + + const itemObservables: Observable[] = []; + + console.log(this.path) + + Items.forEach((item) => { + const observable = this.gs.update(this.path, item, Value).pipe( + map(() => { + deletedItems++; + const progress = (deletedItems / totalItems) * 100; + progressCallback(progress); + return true; // Indicate success for each item + }), + catchError((error) => { + // Handle errors + return of(false); // Indicate failure for each item + }) + ); + + itemObservables.push(observable); + }); + + // Use forkJoin to wait for all item observables to complete + const resultsArray = await forkJoin(itemObservables).toPromise(); + + // Check if any item deletion has failed + const hasFailure = resultsArray.some((result) => result === false); + + return !hasFailure; // Return true if there are no failures + } + +} + diff --git a/src/app/files/files.component.ts b/src/app/files/files.component.ts index 33dbceab..ca3952ba 100644 --- a/src/app/files/files.component.ts +++ b/src/app/files/files.component.ts @@ -230,39 +230,26 @@ export class FilesComponent implements OnInit { } deleteFile(id: number, name: string){ - const swalWithBootstrapButtons = Swal.mixin({ - customClass: { - confirmButton: 'btn', - cancelButton: 'btn' - }, - buttonsStyling: false - }) - Swal.fire({ - title: 'Remove '+ name +' from your files?', - icon: "warning", - reverseButtons: true, - showCancelButton: true, - cancelButtonColor: this.alert.cancelButtonColor, - confirmButtonColor: this.alert.confirmButtonColor, - confirmButtonText: this.alert.delconfirmText - }) - .then((result) => { - if (result.isConfirmed) { - this.gs.delete(SERV.FILES,id).subscribe(() => { - this.alert.okAlert('Deleted '+name+'',''); - this.ngOnInit(); - this.rerender(); // rerender datatables - }); - } else { - swalWithBootstrapButtons.fire({ - title: "Cancelled", - text: "Your File is safe!", - icon: "error", - showConfirmButton: false, - timer: 1500 - }) - } - }); + this.alert.deleteConfirmation(name,'Files').then((confirmed) => { + if (confirmed) { + // Deletion + this.gs.delete(SERV.FILES, id).subscribe(() => { + // Successful deletion + this.alert.okAlert(`Deleted File ${name}`, ''); + this.onRefreshTable(); // Refresh the table + }); + } else { + // Handle cancellation + this.alert.okAlert(`File ${name} is safe!`,''); + } + }); + } + + onRefreshTable(){ + setTimeout(() => { + this.ngOnInit(); + this.rerender(); // rerender datatables + },2000); } // Bulk Actions @@ -279,47 +266,16 @@ export class FilesComponent implements OnInit { return selectionnum; } - onDeleteBulk(){ - const self = this; - const selectionnum = this.onSelectedFiles(); - const sellen = selectionnum.length; - const errors = []; - selectionnum.forEach(function (value) { - Swal.fire('Deleting...'+sellen+' File(s)...Please wait') - Swal.showLoading() - self.gs.delete(SERV.FILES,value) - .subscribe( - err => { - console.log('HTTP Error', err) - err = 1; - errors.push(err); - }, - ); - }); - self.onDone(sellen); + async onDeleteBulk() { + const FilesIds = this.onSelectedFiles(); + this.alert.bulkDeleteAlert(FilesIds,'Files',SERV.FILES); + this.onRefreshTable(); } - onUpdateBulk(value: any){ - const self = this; - const selectionnum = this.onSelectedFiles(); - const sellen = selectionnum.length; - // let edit = {fileType: value}; - selectionnum.forEach(function (id) { - Swal.fire('Updating...'+sellen+' File(s)...Please wait') - Swal.showLoading() - self.gs.update(SERV.FILES, id, value).subscribe( - ); - }); - self.onDone(sellen); - } - - onDone(value?: any){ - setTimeout(() => { - this.ngOnInit(); - this.rerender(); // rerender datatables - Swal.close(); - this.alert.okAlert('Completed',''); - },3000); + async onUpdateBulk(value: any) { + const FilesIds = this.onSelectedFiles(); + this.alert.bulkUpdateAlert(FilesIds,value,'Files',SERV.FILES); + this.onRefreshTable(); } onEdit(id: number){ diff --git a/src/app/hashlists/hashlist/hashlist.component.ts b/src/app/hashlists/hashlist/hashlist.component.ts index 201b24de..3b86e4b6 100644 --- a/src/app/hashlists/hashlist/hashlist.component.ts +++ b/src/app/hashlists/hashlist/hashlist.component.ts @@ -236,41 +236,28 @@ onArchive(id: number){ } onDelete(id: number, name: string){ - const swalWithBootstrapButtons = Swal.mixin({ - customClass: { - confirmButton: 'btn', - cancelButton: 'btn' - }, - buttonsStyling: false - }) - Swal.fire({ - title: 'Remove '+ name +' from your hashlists?', - icon: "warning", - reverseButtons: true, - showCancelButton: true, - cancelButtonColor: this.alert.cancelButtonColor, - confirmButtonColor: this.alert.confirmButtonColor, - confirmButtonText: this.alert.delconfirmText - }) - .then((result) => { - if (result.isConfirmed) { - this.gs.delete(SERV.HASHLISTS,id).subscribe(() => { - this.alert.okAlert('Deleted '+name+'',''); - this.ngOnInit(); - this.rerender(); // rerender datatables + this.alert.deleteConfirmation(name,'Hashlists').then((confirmed) => { + if (confirmed) { + // Deletion + this.gs.delete(SERV.HASHLISTS, id).subscribe(() => { + // Successful deletion + this.alert.okAlert(`Deleted Hashlist ${name}`, ''); + this.onRefreshTable(); // Refresh the table }); } else { - swalWithBootstrapButtons.fire({ - title: "Cancelled", - text: "Your Hashlist is safe!", - icon: "error", - showConfirmButton: false, - timer: 1500 - }) + // Handle cancellation + this.alert.okAlert(`Hashlist ${name} is safe!`,''); } }); } +onRefreshTable(){ + setTimeout(() => { + this.ngOnInit(); + this.rerender(); // rerender datatables + },2000); +} + // Bulk actions onSelectedHashlists(){ @@ -285,48 +272,18 @@ onSelectedHashlists(){ return selectionnum; } -onDeleteBulk(){ - const self = this; - const selectionnum = $($(this.dtElement).DataTable.tables()).DataTable().rows({ selected: true } ).data().pluck(0).toArray(); - const sellen = selectionnum.length; - const errors = []; - selectionnum.forEach(function (value) { - Swal.fire('Deleting...'+sellen+' Hashlist(s)...Please wait') - Swal.showLoading() - self.gs.delete(SERV.HASHLISTS,value) - .subscribe( - err => { - console.log('HTTP Error', err) - err = 1; - errors.push(err); - }, - ); - }); - self.onDone(sellen); +async onDeleteBulk() { + const HashlistIds = this.onSelectedHashlists(); + this.alert.bulkDeleteAlert(HashlistIds,'Hashlists',SERV.HASHLISTS); + this.onRefreshTable(); } -onUpdateBulk(value: any){ - const self = this; - const selectionnum = this.onSelectedHashlists(); - const sellen = selectionnum.length; - selectionnum.forEach(function (id) { - Swal.fire('Updating...'+sellen+' Hashlist(s)...Please wait') - Swal.showLoading() - self.gs.update(SERV.HASHLISTS,id, value).subscribe( - ); - }); - self.onDone(sellen); +async onUpdateBulk(value: any) { + const HashlistIds = this.onSelectedHashlists(); + this.alert.bulkUpdateAlert(HashlistIds,value,'Hashlists',SERV.HASHLISTS); + this.onRefreshTable(); } -onDone(value?: any){ - setTimeout(() => { - this.ngOnInit(); - this.rerender(); // rerender datatables - Swal.close(); - this.alert.okAlert('Completed',''); - },3000); - } - // Add unsubscribe to detect changes ngOnDestroy(){ this.dtTrigger.unsubscribe(); diff --git a/src/app/hashlists/superhashlist/superhashlist.component.ts b/src/app/hashlists/superhashlist/superhashlist.component.ts index 4bfd6531..f18698a8 100644 --- a/src/app/hashlists/superhashlist/superhashlist.component.ts +++ b/src/app/hashlists/superhashlist/superhashlist.component.ts @@ -132,41 +132,28 @@ export class SuperhashlistComponent implements OnInit { } onDelete(id: number, name: string){ - const swalWithBootstrapButtons = Swal.mixin({ - customClass: { - confirmButton: 'btn', - cancelButton: 'btn' - }, - buttonsStyling: false - }) - Swal.fire({ - title: 'Remove '+ name +' from your superhashlists?', - icon: "warning", - reverseButtons: true, - showCancelButton: true, - cancelButtonColor: this.alert.cancelButtonColor, - confirmButtonColor: this.alert.confirmButtonColor, - confirmButtonText: this.alert.delconfirmText - }) - .then((result) => { - if (result.isConfirmed) { - this.gs.delete(SERV.HASHLISTS,id).subscribe(() => { - this.alert.okAlert('Deleted '+name+'',''); - this.ngOnInit(); - this.rerender(); // rerender datatables + this.alert.deleteConfirmation(name,'Superhashlists').then((confirmed) => { + if (confirmed) { + // Deletion + this.gs.delete(SERV.HASHLISTS, id).subscribe(() => { + // Successful deletion + this.alert.okAlert(`Deleted Superhashlist ${name}`, ''); + this.onRefreshTable(); // Refresh the table }); } else { - swalWithBootstrapButtons.fire({ - title: "Cancelled", - text: "Your SuperHashlist is safe!", - icon: "error", - showConfirmButton: false, - timer: 1500 - }) + // Handle cancellation + this.alert.okAlert(`Superhashlist ${name} is safe!`,''); } }); } + onRefreshTable(){ + setTimeout(() => { + this.ngOnInit(); + this.rerender(); // rerender datatables + },2000); + } + // Add unsubscribe to detect changes ngOnDestroy(): void { this.dtTrigger.unsubscribe(); diff --git a/src/app/tasks/edit-supertasks/edit-supertasks.component.ts b/src/app/tasks/edit-supertasks/edit-supertasks.component.ts index 9fb5247c..3b927598 100644 --- a/src/app/tasks/edit-supertasks/edit-supertasks.component.ts +++ b/src/app/tasks/edit-supertasks/edit-supertasks.component.ts @@ -166,36 +166,17 @@ export class EditSupertasksComponent implements OnInit { onDelete(){ const id = +this.route.snapshot.params['id']; - const swalWithBootstrapButtons = Swal.mixin({ - customClass: { - confirmButton: 'btn', - cancelButton: 'btn' - }, - buttonsStyling: false - }) - Swal.fire({ - title: 'Remove from your supertasks?', - icon: "warning", - reverseButtons: true, - showCancelButton: true, - cancelButtonColor: this.alert.cancelButtonColor, - confirmButtonColor: this.alert.confirmButtonColor, - confirmButtonText: this.alert.delconfirmText - }) - .then((result) => { - if (result.isConfirmed) { - this.gs.delete(SERV.SUPER_TASKS,id).subscribe(() => { - this.alert.okAlert('Deleted',''); - this.ngOnInit(); + this.alert.deleteConfirmation('','Supertasks').then((confirmed) => { + if (confirmed) { + // Deletion + this.gs.delete(SERV.SUPER_TASKS, id).subscribe(() => { + // Successful deletion + this.alert.okAlert(`Deleted Supertask`, ''); + this.router.navigate(['/tasks/supertasks']); }); } else { - swalWithBootstrapButtons.fire({ - title: "Cancelled", - text: "Your SuperTask is safe!", - icon: "error", - showConfirmButton: false, - timer: 1500 - }) + // Handle cancellation + this.alert.okAlert(`Supertask is safe!`,''); } }); } @@ -207,14 +188,7 @@ export class EditSupertasksComponent implements OnInit { payload.push(filter[i].pretaskId); } this.gs.update(SERV.SUPER_TASKS,this.editedSTIndex,{'pretasks': payload}).subscribe((result)=>{ - Swal.fire({ - position: 'top-end', - backdrop: false, - icon: 'success', - title: "Success", - showConfirmButton: false, - timer: 1500 - }) + this.alert.okAlert('Deleted supertask',''); this.updateForm.reset(); // success, we reset form this.onRefresh(); }) diff --git a/src/app/tasks/preconfigured-tasks/preconfigured-tasks.component.ts b/src/app/tasks/preconfigured-tasks/preconfigured-tasks.component.ts index f0ca5274..b85b5342 100644 --- a/src/app/tasks/preconfigured-tasks/preconfigured-tasks.component.ts +++ b/src/app/tasks/preconfigured-tasks/preconfigured-tasks.component.ts @@ -153,42 +153,26 @@ export class PreconfiguredTasksComponent implements OnInit { } onDelete(id: number, name: string){ - const swalWithBootstrapButtons = Swal.mixin({ - customClass: { - confirmButton: 'btn', - cancelButton: 'btn' - }, - buttonsStyling: false - }) - Swal.fire({ - title: 'Remove '+ name +' from your pretasks?', - icon: "warning", - reverseButtons: true, - showCancelButton: true, - cancelButtonColor: this.alert.cancelButtonColor, - confirmButtonColor: this.alert.confirmButtonColor, - confirmButtonText: this.alert.delconfirmText - }) - .then((result) => { - if (result.isConfirmed) { - this.gs.delete(SERV.PRETASKS,id).subscribe(() => { - this.alert.okAlert('Deleted '+name+'',''); - this.ngOnInit(); - this.rerender(); // rerender datatables + this.alert.deleteConfirmation(name,'Pretasks').then((confirmed) => { + if (confirmed) { + // Deletion + this.gs.delete(SERV.PRETASKS, id).subscribe(() => { + // Successful deletion + this.alert.okAlert(`Deleted Pretask ${name}`, ''); + this.onRefreshTable(); // Refresh the table }); } else { - swalWithBootstrapButtons.fire({ - title: "Cancelled", - text: "Your Preconfigured Task is safe!", - icon: "error", - showConfirmButton: false, - timer: 1500 - }) + // Handle cancellation + this.alert.okAlert(`Pretask ${name} is safe!`,''); } }); } - - + onRefreshTable(){ + setTimeout(() => { + this.ngOnInit(); + this.rerender(); // rerender datatables + },2000); + } } diff --git a/src/app/tasks/show-tasks/show-tasks.component.ts b/src/app/tasks/show-tasks/show-tasks.component.ts index a5d37675..6d386b22 100644 --- a/src/app/tasks/show-tasks/show-tasks.component.ts +++ b/src/app/tasks/show-tasks/show-tasks.component.ts @@ -284,40 +284,27 @@ getSubtasks(name: string, id: number){ } onDelete(id: number, type: number, name: string){ - const swalWithBootstrapButtons = Swal.mixin({ - customClass: { - confirmButton: 'btn', - cancelButton: 'btn' - }, - buttonsStyling: false - }) - Swal.fire({ - title: 'Remove '+ name +' from your tasks?', - icon: "warning", - reverseButtons: true, - showCancelButton: true, - cancelButtonColor: this.alert.cancelButtonColor, - confirmButtonColor: this.alert.confirmButtonColor, - confirmButtonText: this.alert.delconfirmText - }) - .then((result) => { - if (result.isConfirmed) { - const path = type === 0 ? SERV.TASKS : SERV.TASKS_WRAPPER; - this.gs.delete(path,id).subscribe(() => { - this.alert.okAlert('Deleted '+name+'',''); - this.ngOnInit(); - this.rerender(); // rerender datatables - }); - } else { - swalWithBootstrapButtons.fire({ - title: "Cancelled", - text: "Your Task is safe!", - icon: "error", - showConfirmButton: false, - timer: 1500 - }) - } - }); + this.alert.deleteConfirmation(name,'Task').then((confirmed) => { + if (confirmed) { + // Deletion + const path = type === 0 ? SERV.TASKS : SERV.TASKS_WRAPPER; //Task or supertask + this.gs.delete(path, id).subscribe(() => { + // Successful deletion + this.alert.okAlert(`Deleted Task ${name}`, ''); + this.onRefreshTable(); // Refresh the table + }); + } else { + // Handle cancellation + this.alert.okAlert(`Task ${name} is safe!`,''); + } + }); +} + +onRefreshTable(){ + setTimeout(() => { + this.ngOnInit(); + this.rerender(); // rerender datatables + },2000); } // Bulk actions @@ -334,54 +321,24 @@ onSelectedTasks(){ return selectionnum; } -onDeleteBulk(){ - const self = this; - const selectionnum = $($(this.dtElement).DataTable.tables()).DataTable().rows({ selected: true } ).data().pluck(0).toArray(); +async onDeleteBulk() { + const TasksIds = $($(this.dtElement).DataTable.tables()).DataTable().rows({ selected: true } ).data().pluck(0).toArray(); const type = String($($(this.dtElement).DataTable.tables()).DataTable().rows({ selected: true } ).data().pluck(3).toArray()); const search = type.includes("SuperTask"); - const sellen = selectionnum.length; - const errors = []; - selectionnum.forEach(function (value) { - Swal.fire('Deleting...'+sellen+' Task(s)...Please wait') - Swal.showLoading() - let path = !search ? SERV.TASKS: SERV.TASKS_WRAPPER; - self.gs.delete(SERV.TASKS,value) - .subscribe( - err => { - console.log('HTTP Error', err) - err = 1; - errors.push(err); - }, - ); - }); - self.onDone(sellen); + let path = !search ? SERV.TASKS: SERV.TASKS_WRAPPER; + this.alert.bulkDeleteAlert(TasksIds,'Hashtypes',path); + this.onRefreshTable(); } -onUpdateBulk(value: any){ - const self = this; - const selectionnum = this.onSelectedTasks(); - const sellen = selectionnum.length; - const type = String($($(this.dtElement).DataTable.tables()).DataTable().rows({ selected: true } ).data().pluck(3).toArray()); - const search = type.includes("SuperTask"); - selectionnum.forEach(function (id) { - Swal.fire('Updating...'+sellen+' Task(s)...Please wait') - Swal.showLoading() - let path = !search ? SERV.TASKS: SERV.TASKS_WRAPPER; - self.gs.update(path, id, value).subscribe( - ); - }); - self.onDone(sellen); +async onUpdateBulk(value: any) { + const FilesIds = this.onSelectedTasks(); + const type = String($($(this.dtElement).DataTable.tables()).DataTable().rows({ selected: true } ).data().pluck(3).toArray()); + const search = type.includes("SuperTask"); + let path = !search ? SERV.TASKS: SERV.TASKS_WRAPPER; + this.alert.bulkUpdateAlert(FilesIds,value,'Files',path); + this.onRefreshTable(); } -onDone(value?: any){ - setTimeout(() => { - this.ngOnInit(); - this.rerender(); // rerender datatables - Swal.close(); - this.alert.okAlert('Completed',''); - },3000); - } - onModalProject(title: string){ (async () => { diff --git a/src/app/tasks/supertasks/supertasks.component.ts b/src/app/tasks/supertasks/supertasks.component.ts index 305facbb..f87d73d9 100644 --- a/src/app/tasks/supertasks/supertasks.component.ts +++ b/src/app/tasks/supertasks/supertasks.component.ts @@ -152,41 +152,27 @@ export class SupertasksComponent implements OnInit { } onDelete(id: number, name: string){ - const swalWithBootstrapButtons = Swal.mixin({ - customClass: { - confirmButton: 'btn', - cancelButton: 'btn' - }, - buttonsStyling: false - }) - Swal.fire({ - title: 'Remove '+ name +' from your supertasks?', - icon: "warning", - reverseButtons: true, - showCancelButton: true, - cancelButtonColor: this.alert.cancelButtonColor, - confirmButtonColor: this.alert.confirmButtonColor, - confirmButtonText: this.alert.delconfirmText - }) - .then((result) => { - if (result.isConfirmed) { - this.gs.delete(SERV.SUPER_TASKS,id).subscribe(() => { - this.alert.okAlert('Deleted '+name+'',''); - this.ngOnInit(); - this.rerender(); // rerender datatables + this.alert.deleteConfirmation(name,'Supertasks').then((confirmed) => { + if (confirmed) { + // Deletion + this.gs.delete(SERV.SUPER_TASKS, id).subscribe(() => { + // Successful deletion + this.alert.okAlert(`Deleted Supertask ${name}`, ''); + this.onRefreshTable(); // Refresh the table }); } else { - swalWithBootstrapButtons.fire({ - title: "Cancelled", - text: "Your SuperTask is safe!", - icon: "error", - showConfirmButton: false, - timer: 1500 - }) + // Handle cancellation + this.alert.okAlert(`Supertask ${name} is safe!`,''); } }); } + onRefreshTable(){ + setTimeout(() => { + this.ngOnInit(); + this.rerender(); // rerender datatables + },2000); + } } diff --git a/src/app/users/all-users/all-users.component.ts b/src/app/users/all-users/all-users.component.ts index fefc5874..5513c4a8 100644 --- a/src/app/users/all-users/all-users.component.ts +++ b/src/app/users/all-users/all-users.component.ts @@ -164,43 +164,29 @@ export class AllUsersComponent implements OnInit, OnDestroy { this.router.navigate(['edit'], {relativeTo: this.route}); } - //ToDo onDelete(id: number, name: string){ - const swalWithBootstrapButtons = Swal.mixin({ - customClass: { - confirmButton: 'btn', - cancelButton: 'btn' - }, - buttonsStyling: false - }) - Swal.fire({ - title: 'Remove '+ name +' from your users?', - icon: "warning", - reverseButtons: true, - showCancelButton: true, - cancelButtonColor: this.alert.cancelButtonColor, - confirmButtonColor: this.alert.confirmButtonColor, - confirmButtonText: this.alert.delconfirmText - }) - .then((result) => { - if (result.isConfirmed) { - this.gs.delete(SERV.USERS,id).subscribe(() => { - this.alert.okAlert('Deleted '+name+'',''); - this.ngOnInit(); - this.rerender(); // rerender datatables + this.alert.deleteConfirmation(name,'Users').then((confirmed) => { + if (confirmed) { + // Deletion + this.gs.delete(SERV.USERS, id).subscribe(() => { + // Successful deletion + this.alert.okAlert(`Deleted User ${name}`, ''); + this.onRefreshTable(); // Refresh the table }); } else { - swalWithBootstrapButtons.fire({ - title: "Cancelled", - text: "Your Hashtype is safe!", - icon: "error", - showConfirmButton: false, - timer: 1500 - }) + // Handle cancellation + this.alert.okAlert(`User ${name} is safe!`,''); } }); } + onRefreshTable(){ + setTimeout(() => { + this.ngOnInit(); + this.rerender(); // rerender datatables + },2000); + } + onSelectedUsers(){ $(".dt-button-background").trigger("click"); const selection = $($(this.dtElement).DataTable.tables()).DataTable().rows({ selected: true } ).data().pluck(0).toArray(); @@ -213,33 +199,10 @@ export class AllUsersComponent implements OnInit, OnDestroy { return selectionnum; } - onDeleteBulk(){ - const self = this; - const selectionnum = this.onSelectedUsers(); - const sellen = selectionnum.length; - const errors = []; - selectionnum.forEach(function (value) { - Swal.fire('Deleting...'+sellen+' User(s)...Please wait') - Swal.showLoading() - self.gs.delete(SERV.AGENTS,value) - .subscribe( - err => { - // console.log('HTTP Error', err) - err = 1; - errors.push(err); - }, - ); - }); - self.onDone(sellen); - } - - onDone(value?: any){ - setTimeout(() => { - this.ngOnInit(); - this.rerender(); // rerender datatables - Swal.close(); - this.alert.okAlert('Completed',''); - },3000); + async onDeleteBulk() { + const UserIds = this.onSelectedUsers(); + this.alert.bulkDeleteAlert(UserIds,'Users',SERV.USERS); + this.onRefreshTable(); } rerender(): void { diff --git a/src/app/users/edit-users/edit-users.component.ts b/src/app/users/edit-users/edit-users.component.ts index d7e0ed7d..907ca78f 100644 --- a/src/app/users/edit-users/edit-users.component.ts +++ b/src/app/users/edit-users/edit-users.component.ts @@ -90,39 +90,20 @@ export class EditUsersComponent implements OnInit { this.agp = agp.values; }); -} + } onDelete(){ - const swalWithBootstrapButtons = Swal.mixin({ - customClass: { - confirmButton: 'btn', - cancelButton: 'btn' - }, - buttonsStyling: false - }) - Swal.fire({ - title: 'Remove from your users?', - icon: "warning", - reverseButtons: true, - showCancelButton: true, - cancelButtonColor: this.alert.cancelButtonColor, - confirmButtonColor: this.alert.confirmButtonColor, - confirmButtonText: this.alert.delconfirmText - }) - .then((result) => { - if (result.isConfirmed) { - this.gs.delete(SERV.USERS,this.editedUserIndex).subscribe(() => { - this.alert.okAlert('Deleted',''); + this.alert.deleteConfirmation('','Hashtypes').then((confirmed) => { + if (confirmed) { + // Deletion + this.gs.delete(SERV.USERS, this.editedUserIndex).subscribe(() => { + // Successful deletion + this.alert.okAlert(`Deleted User`, ''); this.router.navigate(['/users/all-users']); }); } else { - swalWithBootstrapButtons.fire({ - title: "Cancelled", - text: "Your User is safe!", - icon: "error", - showConfirmButton: false, - timer: 1500 - }) + // Handle cancellation + this.alert.okAlert(`User is safe!`,''); } }); } diff --git a/src/app/users/globalpermissionsgroups/globalpermissionsgroups.component.ts b/src/app/users/globalpermissionsgroups/globalpermissionsgroups.component.ts index 8e324fdf..8c0277ec 100644 --- a/src/app/users/globalpermissionsgroups/globalpermissionsgroups.component.ts +++ b/src/app/users/globalpermissionsgroups/globalpermissionsgroups.component.ts @@ -60,10 +60,13 @@ export class GlobalpermissionsgroupsComponent implements OnInit { [10, 25, 50, 100, 250, -1], [10, 25, 50, 100, 250, 'All'] ], - select: true, processing: true, // Error loading deferRender: true, destroy:true, + select: { + style: 'multi', + // selector: 'tr>td:nth-child(1)' //This only allows select the first row + }, buttons: { dom: { button: { @@ -153,41 +156,28 @@ export class GlobalpermissionsgroupsComponent implements OnInit { } onDelete(id: number, name: string){ - const swalWithBootstrapButtons = Swal.mixin({ - customClass: { - confirmButton: 'btn', - cancelButton: 'btn' - }, - buttonsStyling: false - }) - Swal.fire({ - title: 'Remove '+ name +' from your global permissions?', - icon: "warning", - reverseButtons: true, - showCancelButton: true, - cancelButtonColor: this.alert.cancelButtonColor, - confirmButtonColor: this.alert.confirmButtonColor, - confirmButtonText: this.alert.delconfirmText - }) - .then((result) => { - if (result.isConfirmed) { - this.gs.delete(SERV.ACCESS_PERMISSIONS_GROUPS,id).subscribe(() => { - this.alert.okAlert('Deleted '+name+'',''); - this.ngOnInit(); - this.rerender(); // rerender datatables + this.alert.deleteConfirmation(name,'Global permissions').then((confirmed) => { + if (confirmed) { + // Deletion + this.gs.delete(SERV.ACCESS_PERMISSIONS_GROUPS, id).subscribe(() => { + // Successful deletion + this.alert.okAlert(`Deleted Global permission ${name}`, ''); + this.onRefreshTable(); // Refresh the table }); } else { - swalWithBootstrapButtons.fire({ - title: "Cancelled", - text: "Your Global Permission is safe!", - icon: "error", - showConfirmButton: false, - timer: 1500 - }) + // Handle cancellation + this.alert.okAlert(`Global permission ${name} is safe!`,''); } }); } + onRefreshTable(){ + setTimeout(() => { + this.ngOnInit(); + this.rerender(); // rerender datatables + },2000); + } + onSelectedGroups(){ $(".dt-button-background").trigger("click"); const selection = $($(this.dtElement).DataTable.tables()).DataTable().rows({ selected: true } ).data().pluck(0).toArray(); @@ -200,33 +190,10 @@ export class GlobalpermissionsgroupsComponent implements OnInit { return selectionnum; } - onDeleteBulk(){ - const self = this; - const selectionnum = this.onSelectedGroups(); - const sellen = selectionnum.length; - const errors = []; - selectionnum.forEach(function (value) { - Swal.fire('Deleting...'+sellen+' Global Group Permission(s)...Please wait') - Swal.showLoading() - self.gs.delete(SERV.ACCESS_PERMISSIONS_GROUPS,value) - .subscribe( - err => { - // console.log('HTTP Error', err) - err = 1; - errors.push(err); - }, - ); - }); - self.onDone(sellen); - } - - onDone(value?: any){ - setTimeout(() => { - this.ngOnInit(); - this.rerender(); // rerender datatables - Swal.close(); - this.alert.okAlert('Completed',''); - },3000); + async onDeleteBulk() { + const GlobalIds = this.onSelectedGroups(); + this.alert.bulkDeleteAlert(GlobalIds,'Global Group Permissions',SERV.ACCESS_PERMISSIONS_GROUPS); + this.onRefreshTable(); } // Add unsubscribe to detect changes diff --git a/src/app/users/groups/groups.component.ts b/src/app/users/groups/groups.component.ts index 196029c9..15bfa164 100644 --- a/src/app/users/groups/groups.component.ts +++ b/src/app/users/groups/groups.component.ts @@ -65,10 +65,13 @@ export class GroupsComponent implements OnInit { [10, 25, 50, 100, 250, -1], [10, 25, 50, 100, 250, 'All'] ], - select: true, processing: true, // Error loading deferRender: true, destroy:true, + select: { + style: 'multi', + // selector: 'tr>td:nth-child(1)' //This only allows select the first row + }, buttons: { dom: { button: { @@ -158,41 +161,28 @@ export class GroupsComponent implements OnInit { } onDelete(id: number, name: string){ - const swalWithBootstrapButtons = Swal.mixin({ - customClass: { - confirmButton: 'btn', - cancelButton: 'btn' - }, - buttonsStyling: false - }) - Swal.fire({ - title: 'Remove '+ name +' from your groups?', - icon: "warning", - reverseButtons: true, - showCancelButton: true, - cancelButtonColor: this.alert.cancelButtonColor, - confirmButtonColor: this.alert.confirmButtonColor, - confirmButtonText: this.alert.delconfirmText - }) - .then((result) => { - if (result.isConfirmed) { - this.gs.delete(SERV.ACCESS_GROUPS,id).subscribe(() => { - this.alert.okAlert('Deleted '+name+'',''); - this.ngOnInit(); - this.rerender(); // rerender datatables + this.alert.deleteConfirmation(name,'Groups').then((confirmed) => { + if (confirmed) { + // Deletion + this.gs.delete(SERV.ACCESS_GROUPS, id).subscribe(() => { + // Successful deletion + this.alert.okAlert(`Deleted Group ${name}`, ''); + this.onRefreshTable(); // Refresh the table }); } else { - swalWithBootstrapButtons.fire({ - title: "Cancelled", - text: "Your Access Group is safe!", - icon: "error", - showConfirmButton: false, - timer: 1500 - }) + // Handle cancellation + this.alert.okAlert(`Group ${name} is safe!`,''); } }); } + onRefreshTable(){ + setTimeout(() => { + this.ngOnInit(); + this.rerender(); // rerender datatables + },2000); + } + onSelectedGroups(){ $(".dt-button-background").trigger("click"); const selection = $($(this.dtElement).DataTable.tables()).DataTable().rows({ selected: true } ).data().pluck(0).toArray(); @@ -205,33 +195,10 @@ export class GroupsComponent implements OnInit { return selectionnum; } - onDeleteBulk(){ - const self = this; - const selectionnum = this.onSelectedGroups(); - const sellen = selectionnum.length; - const errors = []; - selectionnum.forEach(function (value) { - Swal.fire('Deleting...'+sellen+' Group(s)...Please wait') - Swal.showLoading() - self.gs.delete(SERV.ACCESS_GROUPS,value) - .subscribe( - err => { - // console.log('HTTP Error', err) - err = 1; - errors.push(err); - }, - ); - }); - self.onDone(sellen); - } - - onDone(value?: any){ - setTimeout(() => { - this.ngOnInit(); - this.rerender(); // rerender datatables - Swal.close(); - this.alert.okAlert('Completed',''); - },3000); + async onDeleteBulk() { + const GroupsIds = this.onSelectedGroups(); + this.alert.bulkDeleteAlert(GroupsIds,'Groups',SERV.ACCESS_GROUPS); + this.onRefreshTable(); } // Add unsubscribe to detect changes diff --git a/src/styles/components/_alert.scss b/src/styles/components/_alert.scss index ba62c68e..d4c9bb6a 100644 --- a/src/styles/components/_alert.scss +++ b/src/styles/components/_alert.scss @@ -105,3 +105,29 @@ padding: 0.25rem 0.75rem; position: relative } + +/* + 03- End +*/ + +/* + 04- Progress Bar Sweet Alert Notification +*/ +.progress { + background-color: #eee; + border-radius: 10px; + height: 10px; + width: 100%; +} + +.progress-bar { + background-color: #4CAF50; + height: 100%; + width: 0; + border-radius: 10px; + transition: width 0.5s; +} + +/* + 04- End +*/ From 13dc1bb3eb708c2cbddd5f4b74afaf993592b6d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Mon, 23 Oct 2023 10:33:23 +0100 Subject: [PATCH 085/419] All pages grid auto column, not completed --- .../shared/grid-containers/grid-autocol.ts | 89 ++++++++++++++----- 1 file changed, 68 insertions(+), 21 deletions(-) diff --git a/src/app/shared/grid-containers/grid-autocol.ts b/src/app/shared/grid-containers/grid-autocol.ts index 9b3e66d0..4b8adad3 100644 --- a/src/app/shared/grid-containers/grid-autocol.ts +++ b/src/app/shared/grid-containers/grid-autocol.ts @@ -1,4 +1,4 @@ -import { Component, Input, OnInit } from '@angular/core'; +import { Component, Input, OnInit, Renderer2, ElementRef } from '@angular/core'; @Component({ selector: 'grid-autocol', @@ -9,22 +9,32 @@ import { Component, Input, OnInit } from '@angular/core'; `, host: { "(window:resize)":"onWindowResize($event)" +}, +styles: [` +.vertical-line { + position: absolute; + top: 200px; + bottom: 0; + left: 0; + width: 1px; /* Width of the vertical line */ + background-color: gray; /* Line color, you can customize this */ } +`] }) export class GridAutoColComponent implements OnInit { @Input() centered?: boolean; + @Input() verticalLine?: boolean = false; isMobile = false; width:number = window.innerWidth; height:number = window.innerHeight; mobileWidth = 760; - size1920_1080 = false; - size1366_768 = false; - size1280_720 = false; - size1536_864 = false; + gutterSize = 50; // Separation between columns (adjust as needed) + cardWidth = 300; // Width of each card (adjust as needed) + cardHeight = 80; //We take the full height minus the header and footer (adjust as needed) - constructor() {} + constructor(private renderer: Renderer2, private el: ElementRef) {} ngOnInit() : void { this.isMobile = this.width < this.mobileWidth; @@ -36,28 +46,65 @@ export class GridAutoColComponent implements OnInit { this.isMobile = this.width < this.mobileWidth; } + getCols() { + return Math.floor((this.width + this.gutterSize) / (this.cardWidth + this.gutterSize)); + } + + getRows(){ + return Math.floor((this.height + this.gutterSize) / (this.cardHeight + this.gutterSize)); + } + public getStyles() { - // If screen is Tablet or Phone, use only one column - if(this.width < 767 || this.height < 721){ - return { - '': '', - }; + // If the screen is smaller than a specific threshold, return an empty object + if (this.width < 767 || this.height < 721) { + return {}; } - if(this.width > 767 || this.height > 721){ - const gutterSize = 50; //Separation between columns - const cardWidth = 100; //Padding left and right - const cardHeight = 80; //We take the full height minus the header and footer - var cols = Math.floor((this.width + gutterSize) / (cardWidth + gutterSize)); - var rows = Math.floor((this.height + gutterSize) / (cardHeight + gutterSize)); - } - return { + + // Calculate the number of columns based on available space + const cols = this.getCols(); + // Calculate the number of rows based on available space + const rows = this.getRows(); + // Calculate the width of the division lines + const divisionLineWidth = (this.width - (cols * this.cardWidth)) / (cols - 1); + + const styles = { 'display': 'grid', - 'grid-template-columns': `repeat(${cols}, auto)`, + 'grid-template-columns': `repeat(2, ${this.cardWidth}px))`, 'grid-template-rows': `repeat(${rows}, auto)`, - 'grid-gap': '1px', + 'grid-gap': '10px', // Adjust the gap as needed 'grid-auto-flow': 'column', + 'position': 'relative', }; + + // Set the division line width + if (cols >= 2 && cols < 4) { + styles['grid-column-gap'] = `${divisionLineWidth}px`; // Add division lines + } + + //ToDo use render2 to split columns, complete this + // Remove previously added vertical lines + this.removeVerticalLines(); + + // Dynamically add vertical lines for columns using render2 + if (cols > 2 && this.verticalLine) { + for (let i = 1; i < cols; i++) { + const lineElement = this.renderer.createElement('div'); + this.renderer.addClass(lineElement, 'vertical-line'); + this.renderer.setStyle(lineElement, 'left', `${(i * this.cardWidth + (i - 1) )}px`); + this.renderer.appendChild(this.el.nativeElement, lineElement); + } + } + + return styles; + } + + private removeVerticalLines() { + const existingLines = this.el.nativeElement.querySelectorAll('.vertical-line'); + existingLines.forEach(line => { + this.renderer.removeChild(this.el.nativeElement, line); + }); } } + From cff7363a1c63b90e5473a42bbff3e317a7687c11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Mon, 23 Oct 2023 11:56:41 +0100 Subject: [PATCH 086/419] Pretask overview page, style and bulk actions --- .../preconfigured-tasks.component.html | 4 +- .../preconfigured-tasks.component.ts | 297 ++++++++++++------ 2 files changed, 200 insertions(+), 101 deletions(-) diff --git a/src/app/tasks/preconfigured-tasks/preconfigured-tasks.component.html b/src/app/tasks/preconfigured-tasks/preconfigured-tasks.component.html index 4de67970..dd3b1869 100644 --- a/src/app/tasks/preconfigured-tasks/preconfigured-tasks.component.html +++ b/src/app/tasks/preconfigured-tasks/preconfigured-tasks.component.html @@ -15,9 +15,7 @@
- {{ ptask.pretaskId }} - {{ ptask.pretaskId }} {{ ptask.taskName | shortenString:20 }} diff --git a/src/app/tasks/preconfigured-tasks/preconfigured-tasks.component.ts b/src/app/tasks/preconfigured-tasks/preconfigured-tasks.component.ts index b85b5342..2b708023 100644 --- a/src/app/tasks/preconfigured-tasks/preconfigured-tasks.component.ts +++ b/src/app/tasks/preconfigured-tasks/preconfigured-tasks.component.ts @@ -2,63 +2,131 @@ import { faEdit, faTrash, faLock, faFileImport, faFileExport, faPlus, faHomeAlt, import { environment } from './../../../environments/environment'; import { Component, OnInit, ViewChild } from '@angular/core'; import { DataTableDirective } from 'angular-datatables'; -import Swal from 'sweetalert2/dist/sweetalert2.js'; -import { Subject } from 'rxjs'; +import { Subject, Subscription } from 'rxjs'; +import { AutoTitleService } from 'src/app/core/_services/shared/autotitle.service'; import { AlertService } from 'src/app/core/_services/shared/alert.service'; import { GlobalService } from 'src/app/core/_services/main.service'; -import { PageTitle } from 'src/app/core/_decorators/autotitle'; import { SERV } from '../../core/_services/main.config'; +declare let $:any; + @Component({ selector: 'app-preconfigured-tasks', templateUrl: './preconfigured-tasks.component.html' }) -@PageTitle(['Show Preconfigured Task']) +/** + * PreconfiguredTasksComponent is a component that manages and displays preconfigured tasks data. + * + * It uses DataTables to display and interact with the preconfigured tasks data, including exporting, deleting, bulk actions + * and refreshing the table. + */ export class PreconfiguredTasksComponent implements OnInit { - faFileImport=faFileImport; - faFileExport=faFileExport; - faBookmark=faBookmark; - faArchive=faArchive; - faHome=faHomeAlt; - faTrash=faTrash; - faEdit=faEdit; - faLock=faLock; - faPlus=faPlus; - faCopy=faCopy; - - @ViewChild(DataTableDirective, {static: false}) + // Font Awesome icons + faFileImport = faFileImport; + faFileExport = faFileExport; + faBookmark = faBookmark; + faArchive = faArchive; + faHome = faHomeAlt; + faTrash = faTrash; + faEdit = faEdit; + faLock = faLock; + faPlus = faPlus; + faCopy = faCopy; + + // ViewChild reference to the DataTableDirective + @ViewChild(DataTableDirective) dtElement: DataTableDirective; dtTrigger: Subject = new Subject(); dtOptions: any = {}; + // List of pretasks + allpretasks: any = []; + + // Subscriptions to unsubscribe on component destruction + subscriptions: Subscription[] = [] + + private maxResults = environment.config.prodApiMaxResults; + constructor( + private titleService: AutoTitleService, private alert: AlertService, private gs: GlobalService, - ) { } + ) { + titleService.set(['Show Preconfigured Task']) + } - allpretasks: any = []; - private maxResults = environment.config.prodApiMaxResults + /** + * Initializes DataTable and retrieves pretasks. + */ ngOnInit(): void { + this.getPretasks(); + this.setupTable(); + } - const params = {'maxResults': this.maxResults, 'expand': 'pretaskFiles'} + /** + * Unsubscribes from active subscriptions. + */ + ngOnDestroy(): void { + this.dtTrigger.unsubscribe(); + for (const sub of this.subscriptions) { + sub.unsubscribe(); + } + } + + // Refresh the data and the DataTable + onRefresh() { + this.rerender(); + this.ngOnInit(); + } - this.gs.getAll(SERV.PRETASKS,params).subscribe((pretasks: any) => { - this.allpretasks = pretasks.values; + /** + * Rerenders the DataTable instance. + * Destroys and recreates the DataTable to reflect changes. + */ + rerender(): void { + this.dtElement.dtInstance.then((dtInstance: DataTables.Api) => { + // Destroy the table first + dtInstance.destroy(); + // Call the dtTrigger to rerender again + setTimeout(() => { + if (this.dtTrigger['new']) { + this.dtTrigger['new'].next(); + } + }); + }); + } + + /** + * Fetches Pretasks from the server. + * Subscribes to the API response and updates the Pretasks list. + */ + getPretasks(): void { + // Fetch preconfigured tasks data from the API + const params = { 'maxResults': this.maxResults, 'expand': 'pretaskFiles' }; + this.gs.getAll(SERV.PRETASKS, params).subscribe((response: any) => { + this.allpretasks = response.values; this.dtTrigger.next(void 0); }); + } + /** + * Sets up the DataTable options and buttons. + * Customizes DataTable appearance and behavior. + */ + setupTable(): void { + // DataTables options const self = this; this.dtOptions = { dom: 'Bfrtip', scrollX: true, pageLength: 25, lengthMenu: [ - [10, 25, 50, 100, 250, -1], - [10, 25, 50, 100, 250, 'All'] + [10, 25, 50, 100, 250, -1], + [10, 25, 50, 100, 250, 'All'] ], stateSave: true, select: true, @@ -68,92 +136,98 @@ export class PreconfiguredTasksComponent implements OnInit { className: 'dt-button buttons-collection btn btn-sm-dt btn-outline-gray-600-dt', } }, - buttons: [ - { - text: '↻', - autoClose: true, - action: function (e, dt, node, config) { - self.onRefresh(); - } - }, - { - extend: 'collection', - text: 'Export', - buttons: [ - { - extend: 'excelHtml5', - exportOptions: { - columns: [0, 1, 2, 3, 4, 5] + buttons: [ + { + text: '↻', + autoClose: true, + action: function (e, dt, node, config) { + self.onRefresh(); + } + }, + { + extend: 'collection', + text: 'Export', + buttons: [ + { + extend: 'excelHtml5', + exportOptions: { + columns: [0, 1, 2, 3, 4, 5] + }, }, - }, - { - extend: 'print', - - exportOptions: { - columns: [0, 1, 2, 3, 4, 5] + { + extend: 'print', + exportOptions: { + columns: [0, 1, 2, 3, 4, 5] + }, + customize: function (win) { + $(win.document.body) + .css('font-size', '10pt'); + $(win.document.body).find('table') + .addClass('compact') + .css('font-size', 'inherit'); + } }, - customize: function ( win ) { - $(win.document.body) - .css( 'font-size', '10pt' ) - $(win.document.body).find( 'table' ) - .addClass( 'compact' ) - .css( 'font-size', 'inherit' ); - } - }, - { - extend: 'csvHtml5', - exportOptions: {modifier: {selected: true}}, - select: true, - customize: function (dt, csv) { - let data = ""; - for (let i = 0; i < dt.length; i++) { - data = "Agents\n\n"+ dt; + { + extend: 'csvHtml5', + exportOptions: { modifier: { selected: true } }, + select: true, + customize: function (dt, csv) { + let data = ''; + for (let i = 0; i < dt.length; i++) { + data = 'Agents\n\n' + dt; + } + return data; } - return data; - } - }, - { - extend: 'copy', - } + }, + { + extend: 'copy', + }, ] }, + { + extend: 'collection', + text: 'Bulk Actions', + buttons: [ + { + text: 'Delete PreTask(s)', + autoClose: true, + action: function (e, dt, node, config) { + self.onDeleteBulk(); + } + } + ] + }, { extend: 'colvis', text: 'Column View', - columns: [ 1,2,3,4,5 ], + columns: [1, 2, 3, 4, 5], }, { - extend: "pageLength", - className: "btn-sm" + extend: 'pageLength', + className: 'btn-sm', }, ], } }; - } - onRefresh(){ - this.rerender(); - this.ngOnInit(); - } - - ngOnDestroy(): void { - this.dtTrigger.unsubscribe(); - } - - rerender(): void { - this.dtElement.dtInstance.then((dtInstance: DataTables.Api) => { - // Destroy the table first - dtInstance.destroy(); - // Call the dtTrigger to rerender again - setTimeout(() => { - this.dtTrigger['new'].next(); - }); - }); + // Refresh the table after a delete operation + onRefreshTable() { + setTimeout(() => { + this.ngOnInit(); + this.rerender(); // Rerender the DataTable + }, 2000); } - onDelete(id: number, name: string){ - this.alert.deleteConfirmation(name,'Pretasks').then((confirmed) => { + /** + * Handles pretask deletion. + * Displays a confirmation dialog and deletes the pretask if confirmed. + * + * @param {number} id - The ID of the pretask to delete. + * @param {string} name - The name of the pretask. + */ + onDelete(id: number, name: string) { + this.alert.deleteConfirmation(name, 'Pretasks').then((confirmed) => { if (confirmed) { // Deletion this.gs.delete(SERV.PRETASKS, id).subscribe(() => { @@ -163,16 +237,43 @@ export class PreconfiguredTasksComponent implements OnInit { }); } else { // Handle cancellation - this.alert.okAlert(`Pretask ${name} is safe!`,''); + this.alert.okAlert(`Pretask ${name} is safe!`, ''); } }); } - onRefreshTable(){ - setTimeout(() => { - this.ngOnInit(); - this.rerender(); // rerender datatables - },2000); + /** + * BULK ACTIONS + * + */ + + /** + * Handles pretask selection. + * On multi select grabs the ids to be used for bulk action + * + */ + onSelectedPretasks(){ + $(".dt-button-background").trigger("click"); + const selection = $($(this.dtElement).DataTable.tables()).DataTable().rows({ selected: true } ).data().pluck(0).toArray(); + if(selection.length == 0) { + this.alert.okAlert('You haven not selected any Group',''); + return; + } + const selectionnum = selection.map(i=>Number(i)); + + return selectionnum; + } + + /** + * Handles bulk deletion + * Delete the pretasks showing a progress bar + * + */ + async onDeleteBulk() { + const PretasksIds = this.onSelectedPretasks(); + this.alert.bulkDeleteAlert(PretasksIds,'Pretasks',SERV.PRETASKS); + this.onRefreshTable(); } + } From e790121c916b22da2298c318bab84d55cec1d109 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Mon, 23 Oct 2023 12:11:37 +0100 Subject: [PATCH 087/419] Pretask overview page, missing subscription --- .../preconfigured-tasks.component.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/app/tasks/preconfigured-tasks/preconfigured-tasks.component.ts b/src/app/tasks/preconfigured-tasks/preconfigured-tasks.component.ts index 2b708023..f3a91d47 100644 --- a/src/app/tasks/preconfigured-tasks/preconfigured-tasks.component.ts +++ b/src/app/tasks/preconfigured-tasks/preconfigured-tasks.component.ts @@ -107,10 +107,10 @@ export class PreconfiguredTasksComponent implements OnInit { getPretasks(): void { // Fetch preconfigured tasks data from the API const params = { 'maxResults': this.maxResults, 'expand': 'pretaskFiles' }; - this.gs.getAll(SERV.PRETASKS, params).subscribe((response: any) => { + this.subscriptions.push(this.gs.getAll(SERV.PRETASKS, params).subscribe((response: any) => { this.allpretasks = response.values; this.dtTrigger.next(void 0); - }); + })); } /** @@ -230,11 +230,11 @@ export class PreconfiguredTasksComponent implements OnInit { this.alert.deleteConfirmation(name, 'Pretasks').then((confirmed) => { if (confirmed) { // Deletion - this.gs.delete(SERV.PRETASKS, id).subscribe(() => { - // Successful deletion - this.alert.okAlert(`Deleted Pretask ${name}`, ''); - this.onRefreshTable(); // Refresh the table - }); + this.subscriptions.push(this.gs.delete(SERV.PRETASKS, id).subscribe(() => { + // Successful deletion + this.alert.okAlert(`Deleted Pretask ${name}`, ''); + this.onRefreshTable(); // Refresh the table + })); } else { // Handle cancellation this.alert.okAlert(`Pretask ${name} is safe!`, ''); From dba4f8616754d8d4283087a9d35114c4946e577e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Mon, 23 Oct 2023 14:47:09 +0100 Subject: [PATCH 088/419] All users, add documentation --- .../users/all-users/all-users.component.ts | 286 +++++++++++------- 1 file changed, 175 insertions(+), 111 deletions(-) diff --git a/src/app/users/all-users/all-users.component.ts b/src/app/users/all-users/all-users.component.ts index 5513c4a8..0bf4ddbc 100644 --- a/src/app/users/all-users/all-users.component.ts +++ b/src/app/users/all-users/all-users.component.ts @@ -1,15 +1,13 @@ import { faEdit, faHomeAlt, faPlus, faTrash } from '@fortawesome/free-solid-svg-icons'; import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core'; -import Swal from 'sweetalert2/dist/sweetalert2.js'; import { ActivatedRoute } from '@angular/router'; +import { Subject, Subscription } from 'rxjs'; import { Router } from '@angular/router'; -import { Subject } from 'rxjs'; -import { UIConfigService } from '../../core/_services/shared/storage.service'; +import { AutoTitleService } from 'src/app/core/_services/shared/autotitle.service'; import { AlertService } from 'src/app/core/_services/shared/alert.service'; import { GlobalService } from 'src/app/core/_services/main.service'; import { environment } from './../../../environments/environment'; -import { PageTitle } from 'src/app/core/_decorators/autotitle'; import { SERV } from '../../core/_services/main.config'; import { DataTableDirective } from 'angular-datatables'; @@ -19,24 +17,28 @@ declare let $:any; selector: 'app-all-users', templateUrl: './all-users.component.html' }) -@PageTitle(['Show Users']) -export class AllUsersComponent implements OnInit, OnDestroy { +/** + * AllUsersComponent is a component that manages and displays all users data. + * + * It uses DataTables to display and interact with the users tasks data, including exporting, deleting, bulk actions + * and refreshing the table. + */ +export class AllUsersComponent implements OnInit, OnDestroy { - faHome=faHomeAlt; - faTrash=faTrash; - faEdit=faEdit; - faPlus=faPlus; + // Font Awesome icons + faHome = faHomeAlt; + faTrash = faTrash; + faEdit = faEdit; + faPlus = faPlus; + // ViewChild reference to the DataTableDirective @ViewChild(DataTableDirective) dtElement: DataTableDirective; dtTrigger: Subject = new Subject(); dtOptions: any = {}; - ngOnDestroy(): void { - this.dtTrigger.unsubscribe(); - } - + // List of users ToDo. Change to interface public allusers: { id: number, name: string, @@ -44,7 +46,7 @@ export class AllUsersComponent implements OnInit, OnDestroy { lastLoginDate: number, email: string, isValid: boolean, - sessionLifetime:number, + sessionLifetime: number, rightGroupId: string, globalPermissionGroup: { name: string, @@ -52,37 +54,89 @@ export class AllUsersComponent implements OnInit, OnDestroy { } }[] = []; + // Subscriptions to unsubscribe on component destruction + subscriptions: Subscription[] = [] + + private maxResults = environment.config.prodApiMaxResults; + constructor( - private uiService: UIConfigService, - private route:ActivatedRoute, + private titleService: AutoTitleService, + private route: ActivatedRoute, private alert: AlertService, private gs: GlobalService, - private router:Router - ) { } + private router: Router + ) { + titleService.set(['Show Users']) + } - private maxResults = environment.config.prodApiMaxResults + /** + * Initializes DataTable and retrieves pretasks. + */ ngOnInit(): void { + this.getUsers(); + this.setupTable(); + } - const params = {'maxResults': this.maxResults, 'expand': 'globalPermissionGroup' } - this.gs.getAll(SERV.USERS,params).subscribe((users: any) => { - this.allusers = users.values; - this.dtTrigger.next(void 0); + /** + * Unsubscribes from active subscriptions. + */ + ngOnDestroy(): void { + this.dtTrigger.unsubscribe(); + for (const sub of this.subscriptions) { + sub.unsubscribe(); + } + } + + // Refresh the data and the DataTable + onRefresh() { + this.rerender(); + this.ngOnInit(); + } + + /** + * Rerender the DataTable. + */ + rerender(): void { + this.dtElement.dtInstance.then((dtInstance: DataTables.Api) => { + dtInstance.destroy(); + setTimeout(() => { + this.dtTrigger['new'].next(); + }); }); + } + + /** + * Fetches Users from the server. + * Subscribes to the API response and updates the Users list. + */ + getUsers(): void { + // Set parameters for the API request + const params = { 'maxResults': this.maxResults, 'expand': 'globalPermissionGroup' }; + // Make an API call to get users data + this.subscriptions.push(this.gs.getAll(SERV.USERS, params).subscribe((response: any) => { + this.allusers = response.values; + this.dtTrigger.next(void 0); + })); + } + + /** + * Sets up the DataTable options and buttons. + * Customizes DataTable appearance and behavior. + */ + setupTable(): void { + // DataTables options const self = this; this.dtOptions = { dom: 'Bfrtip', scrollX: true, pageLength: 25, lengthMenu: [ - [10, 25, 50, 100, 250, -1], - [10, 25, 50, 100, 250, 'All'] + [10, 25, 50, 100, 250, -1], + [10, 25, 50, 100, 250, 'All'] ], stateSave: true, - // "stateLoadParams": function (settings, data) { - // return false; - // }, select: true, buttons: { dom: { @@ -90,49 +144,49 @@ export class AllUsersComponent implements OnInit, OnDestroy { className: 'dt-button buttons-collection btn btn-sm-dt btn-outline-gray-600-dt', } }, - buttons: [ - { - text: '↻', - autoClose: true, - action: function (e, dt, node, config) { - self.onRefresh(); - } - }, - { - extend: 'collection', - text: 'Export', - buttons: [ - { - extend: 'excelHtml5', - exportOptions: { - columns: [0, 1] + buttons: [ + { + text: '↻', + autoClose: true, + action: function (e, dt, node, config) { + self.onRefresh(); + } + }, + { + extend: 'collection', + text: 'Export', + buttons: [ + { + extend: 'excelHtml5', + exportOptions: { + columns: [0, 1] + }, }, - }, - { - extend: 'print', - exportOptions: { - columns: [0,1] + { + extend: 'print', + exportOptions: { + columns: [0, 1] + }, + customize: function (win) { + $(win.document.body) + .css('font-size', '10pt'); + $(win.document.body).find('table') + .addClass('compact') + .css('font-size', 'inherit'); + } }, - customize: function ( win ) { - $(win.document.body) - .css( 'font-size', '10pt' ) - $(win.document.body).find( 'table' ) - .addClass( 'compact' ) - .css( 'font-size', 'inherit' ); - } - }, - { - extend: 'csvHtml5', - exportOptions: {modifier: {selected: true}}, - select: true, - customize: function (dt, csv) { - let data = ""; - for (let i = 0; i < dt.length; i++) { - data = "Agents\n\n"+ dt; + { + extend: 'csvHtml5', + exportOptions: { modifier: { selected: true } }, + select: true, + customize: function (dt, csv) { + let data = ''; + for (let i = 0; i < dt.length; i++) { + data = 'Agents\n\n' + dt; + } + return data; } - return data; - } - }, + }, 'copy' ] }, @@ -141,79 +195,89 @@ export class AllUsersComponent implements OnInit, OnDestroy { text: 'Bulk Actions', className: 'dt-button buttons-collection btn btn-sm-dt btn-outline-gray-600-dt', buttons: [ - { - text: 'Delete Users', - autoClose: true, - action: function (e, dt, node, config) { - self.onDeleteBulk(); - } - } - ] - }, + { + text: 'Delete Users', + autoClose: true, + action: function (e, dt, node, config) { + self.onDeleteBulk(); + } + } + ] + }, ], } }; } - onRefresh(){ - this.rerender(); - this.ngOnInit(); + // Refresh the table after a delete operation + onRefreshTable() { + setTimeout(() => { + this.ngOnInit(); + this.rerender(); // Rerender the DataTable + }, 2000); } - editButtonClick(){ - this.router.navigate(['edit'], {relativeTo: this.route}); + /** + * Navigate to the edit page. + */ + editButtonClick() { + this.router.navigate(['edit'], { relativeTo: this.route }); } - onDelete(id: number, name: string){ - this.alert.deleteConfirmation(name,'Users').then((confirmed) => { + /** + * Handles user deletion. + * Displays a confirmation dialog and deletes the user if confirmed. + * + * @param {number} id - The ID of the user to delete. + * @param {string} name - The name of the user. + */ + onDelete(id: number, name: string) { + this.alert.deleteConfirmation(name, 'Users').then((confirmed) => { if (confirmed) { // Deletion - this.gs.delete(SERV.USERS, id).subscribe(() => { + this.subscriptions.push(this.gs.delete(SERV.USERS, id).subscribe(() => { // Successful deletion this.alert.okAlert(`Deleted User ${name}`, ''); this.onRefreshTable(); // Refresh the table - }); + })); } else { // Handle cancellation - this.alert.okAlert(`User ${name} is safe!`,''); + this.alert.okAlert(`User ${name} is safe!`, ''); } }); } - onRefreshTable(){ - setTimeout(() => { - this.ngOnInit(); - this.rerender(); // rerender datatables - },2000); - } + /** + * BULK ACTIONS + * + */ - onSelectedUsers(){ + /** + * Handles pretask selection. + * On multi select grabs the ids to be used for bulk action + * + */ + onSelectedUsers() { $(".dt-button-background").trigger("click"); - const selection = $($(this.dtElement).DataTable.tables()).DataTable().rows({ selected: true } ).data().pluck(0).toArray(); - if(selection.length == 0) { - this.alert.okAlert('You haven not selected any Group',''); + const selection = $($(this.dtElement).DataTable.tables()).DataTable().rows({ selected: true }).data().pluck(0).toArray(); + if (selection.length == 0) { + this.alert.okAlert('You have not selected any Group', ''); return; } - const selectionnum = selection.map(i=>Number(i)); + const selectionnum = selection.map(i => Number(i)); return selectionnum; } + /** + * Handles bulk deletion + * Delete the pretasks showing a progress bar + * + */ async onDeleteBulk() { const UserIds = this.onSelectedUsers(); - this.alert.bulkDeleteAlert(UserIds,'Users',SERV.USERS); + this.alert.bulkDeleteAlert(UserIds, 'Users', SERV.USERS); this.onRefreshTable(); } - rerender(): void { - this.dtElement.dtInstance.then((dtInstance: DataTables.Api) => { - dtInstance.destroy(); - setTimeout(() => { - this.dtTrigger['new'].next(); - }); - }); - } - - } - From 16389867c7fdf023ff76c018b09f9b0c8e3010a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Mon, 23 Oct 2023 14:49:19 +0100 Subject: [PATCH 089/419] Pretask component missing Ondestroy --- .../preconfigured-tasks/preconfigured-tasks.component.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/tasks/preconfigured-tasks/preconfigured-tasks.component.ts b/src/app/tasks/preconfigured-tasks/preconfigured-tasks.component.ts index f3a91d47..f9496c2e 100644 --- a/src/app/tasks/preconfigured-tasks/preconfigured-tasks.component.ts +++ b/src/app/tasks/preconfigured-tasks/preconfigured-tasks.component.ts @@ -1,6 +1,6 @@ import { faEdit, faTrash, faLock, faFileImport, faFileExport, faPlus, faHomeAlt, faArchive, faCopy, faBookmark } from '@fortawesome/free-solid-svg-icons'; +import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core'; import { environment } from './../../../environments/environment'; -import { Component, OnInit, ViewChild } from '@angular/core'; import { DataTableDirective } from 'angular-datatables'; import { Subject, Subscription } from 'rxjs'; @@ -21,7 +21,7 @@ declare let $:any; * It uses DataTables to display and interact with the preconfigured tasks data, including exporting, deleting, bulk actions * and refreshing the table. */ -export class PreconfiguredTasksComponent implements OnInit { +export class PreconfiguredTasksComponent implements OnInit, OnDestroy { // Font Awesome icons faFileImport = faFileImport; From a913b8c2e55bf38a189f079e2b5b69c8d28c3904 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Mon, 23 Oct 2023 15:03:20 +0100 Subject: [PATCH 090/419] Global permission all, documentation --- .../globalpermissionsgroups.component.ts | 179 ++++++++++++------ 1 file changed, 123 insertions(+), 56 deletions(-) diff --git a/src/app/users/globalpermissionsgroups/globalpermissionsgroups.component.ts b/src/app/users/globalpermissionsgroups/globalpermissionsgroups.component.ts index 8c0277ec..1e6b094c 100644 --- a/src/app/users/globalpermissionsgroups/globalpermissionsgroups.component.ts +++ b/src/app/users/globalpermissionsgroups/globalpermissionsgroups.component.ts @@ -1,15 +1,15 @@ import { faHomeAlt, faPlus, faTrash, faEdit, faInfoCircle } from '@fortawesome/free-solid-svg-icons'; -import { Component, OnInit, ViewChild } from '@angular/core'; +import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core'; import { DataTableDirective } from 'angular-datatables'; -import Swal from 'sweetalert2/dist/sweetalert2.js'; +import { Subject, Subscription } from 'rxjs'; import { Router } from '@angular/router'; -import { Subject } from 'rxjs'; import { AlertService } from 'src/app/core/_services/shared/alert.service'; import { GlobalService } from 'src/app/core/_services/main.service'; import { PageTitle } from 'src/app/core/_decorators/autotitle'; import { environment } from 'src/environments/environment'; import { SERV } from '../../core/_services/main.config'; +import { AutoTitleService } from 'src/app/core/_services/shared/autotitle.service'; declare let $:any; @@ -17,40 +17,106 @@ declare let $:any; selector: 'app-globalpermissionsgroups', templateUrl: './globalpermissionsgroups.component.html' }) -@PageTitle(['Show Global Permissions']) -export class GlobalpermissionsgroupsComponent implements OnInit { +/** + * GlobalpermissionsgroupsComponent is a component that manages and displays Global Permissions data. + * + * It uses DataTables to display and interact with the Global Permissions data, including exporting, deleting, bulk actions + * and refreshing the table. + */ +export class GlobalpermissionsgroupsComponent implements OnInit, OnDestroy { - // Form attributtes - faInfoCircle=faInfoCircle; - faHome=faHomeAlt; - faPlus=faPlus; - faEdit=faEdit; - faTrash=faTrash; + // Font Awesome icons + faInfoCircle=faInfoCircle; + faHome=faHomeAlt; + faPlus=faPlus; + faEdit=faEdit; + faTrash=faTrash; - private maxResults = environment.config.prodApiMaxResults; + // ViewChild reference to the DataTableDirective + @ViewChild(DataTableDirective, {static: false}) + dtElement: DataTableDirective; - // Datatable - @ViewChild(DataTableDirective, {static: false}) - dtElement: DataTableDirective; + dtTrigger: Subject = new Subject(); + dtOptions: any = {}; - dtTrigger: Subject = new Subject(); - dtOptions: any = {}; + // List of global permission groups + public Allgpg:any = []; - public Allgpg: {id: number, name: string , user:[]}[] = []; + // Subscriptions to unsubscribe on component destruction + subscriptions: Subscription[] = [] - constructor( - private alert: AlertService, - private gs: GlobalService, - private router: Router - ) { } + private maxResults = environment.config.prodApiMaxResults; - ngOnInit(): void { + constructor( + private titleService: AutoTitleService, + private alert: AlertService, + private gs: GlobalService, + private router: Router + ) { + titleService.set(['Show Global Permissions']) + } + + /** + * Initializes DataTable and retrieves pretasks. + */ + ngOnInit(): void { + this.getGlobalPermissions(); + this.setupTable(); + } + + /** + * Unsubscribes from active subscriptions. + */ + ngOnDestroy(): void { + this.dtTrigger.unsubscribe(); + for (const sub of this.subscriptions) { + sub.unsubscribe(); + } + } + + // Refresh the data and the DataTable + onRefresh() { + this.rerender(); + this.ngOnInit(); + } - const params = {'maxResults': this.maxResults , 'expand': 'user'} - this.gs.getAll(SERV.ACCESS_PERMISSIONS_GROUPS,params).subscribe((gpg: any) => { - this.Allgpg = gpg.values; - this.dtTrigger.next(void 0); + /** + * Rerenders the DataTable instance. + * Destroys and recreates the DataTable to reflect changes. + */ + rerender(): void { + this.dtElement.dtInstance.then((dtInstance: DataTables.Api) => { + // Destroy the table first + dtInstance.destroy(); + // Call the dtTrigger to rerender again + setTimeout(() => { + if (this.dtTrigger['new']) { + this.dtTrigger['new'].next(); + } }); + }); + } + + /** + * Fetches Global Permissions Groups from the server. + * Subscribes to the API response and updates the Global Permissions Groups list. + */ + getGlobalPermissions(): void { + // Set parameters for the API request + const params = {'maxResults': this.maxResults , 'expand': 'user'}; + // Make an API call to get permissions data + this.subscriptions.push(this.gs.getAll(SERV.ACCESS_PERMISSIONS_GROUPS,params).subscribe((response: any) => { + this.Allgpg = response.values; + this.dtTrigger.next(void 0); + })); + } + + /** + * Sets up the DataTable options and buttons. + * Customizes DataTable appearance and behavior. + */ + setupTable(): void { + // DataTables options const self = this; this.dtOptions = { dom: 'Bfrtip', @@ -65,7 +131,6 @@ export class GlobalpermissionsgroupsComponent implements OnInit { destroy:true, select: { style: 'multi', - // selector: 'tr>td:nth-child(1)' //This only allows select the first row }, buttons: { dom: { @@ -139,31 +204,30 @@ export class GlobalpermissionsgroupsComponent implements OnInit { } - onRefresh(){ - this.rerender(); - this.ngOnInit(); - } - - rerender(): void { - this.dtElement.dtInstance.then((dtInstance: DataTables.Api) => { - // Destroy the table first - dtInstance.destroy(); - // Call the dtTrigger to rerender again - setTimeout(() => { - this.dtTrigger['new'].next(); - }); - }); + // Refresh the table after a delete operation + onRefreshTable() { + setTimeout(() => { + this.ngOnInit(); + this.rerender(); // Rerender the DataTable + }, 2000); } + /** + * Handles Global Permission Group deletion. + * Displays a confirmation dialog and deletes the Global Permission Group if confirmed. + * + * @param {number} id - The ID of the Global Permission Group to delete. + * @param {string} name - The name of the Global Permission Group. + */ onDelete(id: number, name: string){ this.alert.deleteConfirmation(name,'Global permissions').then((confirmed) => { if (confirmed) { // Deletion - this.gs.delete(SERV.ACCESS_PERMISSIONS_GROUPS, id).subscribe(() => { + this.subscriptions.push(this.gs.delete(SERV.ACCESS_PERMISSIONS_GROUPS, id).subscribe(() => { // Successful deletion this.alert.okAlert(`Deleted Global permission ${name}`, ''); this.onRefreshTable(); // Refresh the table - }); + })); } else { // Handle cancellation this.alert.okAlert(`Global permission ${name} is safe!`,''); @@ -171,13 +235,16 @@ export class GlobalpermissionsgroupsComponent implements OnInit { }); } - onRefreshTable(){ - setTimeout(() => { - this.ngOnInit(); - this.rerender(); // rerender datatables - },2000); - } + /** + * BULK ACTIONS + * + */ + /** + * Handles Global Permission Group selection. + * On multi select grabs the ids to be used for bulk action + * + */ onSelectedGroups(){ $(".dt-button-background").trigger("click"); const selection = $($(this.dtElement).DataTable.tables()).DataTable().rows({ selected: true } ).data().pluck(0).toArray(); @@ -190,15 +257,15 @@ export class GlobalpermissionsgroupsComponent implements OnInit { return selectionnum; } + /** + * Handles bulk deletion + * Delete the Global Permission Group showing a progress bar + * + */ async onDeleteBulk() { const GlobalIds = this.onSelectedGroups(); this.alert.bulkDeleteAlert(GlobalIds,'Global Group Permissions',SERV.ACCESS_PERMISSIONS_GROUPS); this.onRefreshTable(); } - // Add unsubscribe to detect changes - ngOnDestroy(){ - this.dtTrigger.unsubscribe(); - } - } From b56ca11a813b7393470dafc3550b51e3e4204f8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Mon, 23 Oct 2023 15:17:03 +0100 Subject: [PATCH 091/419] Global group all, documentation --- src/app/users/groups/groups.component.ts | 296 ++++++++++++++--------- 1 file changed, 176 insertions(+), 120 deletions(-) diff --git a/src/app/users/groups/groups.component.ts b/src/app/users/groups/groups.component.ts index 15bfa164..07fbd6d6 100644 --- a/src/app/users/groups/groups.component.ts +++ b/src/app/users/groups/groups.component.ts @@ -1,10 +1,10 @@ import { faHomeAlt, faPlus, faTrash, faEdit, faSave, faCancel } from '@fortawesome/free-solid-svg-icons'; -import { Component, OnInit, ViewChild } from '@angular/core'; +import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core'; import { environment } from 'src/environments/environment'; import { DataTableDirective } from 'angular-datatables'; -import Swal from 'sweetalert2/dist/sweetalert2.js'; -import { Subject } from 'rxjs'; +import { Subject, Subscription } from 'rxjs'; +import { AutoTitleService } from 'src/app/core/_services/shared/autotitle.service'; import { AlertService } from 'src/app/core/_services/shared/alert.service'; import { GlobalService } from 'src/app/core/_services/main.service'; import { PageTitle } from 'src/app/core/_decorators/autotitle'; @@ -17,67 +17,127 @@ declare let $:any; templateUrl: './groups.component.html' }) @PageTitle(['Show Groups']) -export class GroupsComponent implements OnInit { +/** + * GroupsComponent is a component that manages and displays all groups data. + * + * It uses DataTables to display and interact with the groups data, including exporting, deleting, bulk actions + * and refreshing the table. + */ +export class GroupsComponent implements OnInit, OnDestroy { - // Form attributtes - faHome=faHomeAlt; - faPlus=faPlus; - faEdit=faEdit; - faTrash=faTrash; - faSave=faSave; - faCancel=faCancel; + // Font Awesome icons + faHome = faHomeAlt; + faPlus = faPlus; + faEdit = faEdit; + faTrash = faTrash; + faSave = faSave; + faCancel = faCancel; - private maxResults = environment.config.prodApiMaxResults; + // ViewChild reference to the DataTableDirective + @ViewChild(DataTableDirective, { static: false }) + dtElement: DataTableDirective; - // Datatable - @ViewChild(DataTableDirective, {static: false}) - dtElement: DataTableDirective; + dtTrigger: Subject = new Subject(); + dtOptions: any = {}; - dtTrigger: Subject = new Subject(); - dtOptions: any = {}; + // List of groups ToDo. Change Interface + public agroups: { accessGroupId: number, groupName: string, isEdit: false }[] = []; - public agroups: {accessGroupId: number, groupName: string, isEdit: false }[] = []; + // Subscriptions to unsubscribe on component destruction + subscriptions: Subscription[] = [] - constructor( - private alert: AlertService, - private gs: GlobalService, - ) { } + private maxResults = environment.config.prodApiMaxResults; - ngOnInit(): void { + constructor( + private titleService: AutoTitleService, + private alert: AlertService, + private gs: GlobalService, + ) { + titleService.set(['Show Groups']) + } - this.loadAccessGroups(); + /** + * Initializes DataTable and retrieves groups. + */ + ngOnInit(): void { + this.loadAccessGroups(); + this.setupTable(); + } + /** + * Unsubscribes from active subscriptions and DataTable. + */ + ngOnDestroy(): void { + this.dtTrigger.unsubscribe(); + for (const sub of this.subscriptions) { + sub.unsubscribe(); } + } - loadAccessGroups(){ + /** + * Refreshes the data and the DataTable. + */ + onRefresh() { + this.rerender(); + this.ngOnInit(); + } - const params = {'maxResults': this.maxResults} - this.gs.getAll(SERV.ACCESS_GROUPS,params).subscribe((agroups: any) => { - this.agroups = agroups.values; - this.dtTrigger.next(void 0); + /** + * Rerenders the DataTable instance. + * Destroys and recreates the DataTable to reflect changes. + */ + rerender(): void { + this.dtElement.dtInstance.then((dtInstance: DataTables.Api) => { + // Destroy the table first + dtInstance.destroy(); + // Call the dtTrigger to rerender again + setTimeout(() => { + this.dtTrigger['new'].next(); }); - const self = this; - this.dtOptions = { - dom: 'Bfrtip', - scrollX: true, - pageLength: 25, - lengthMenu: [ - [10, 25, 50, 100, 250, -1], - [10, 25, 50, 100, 250, 'All'] - ], - processing: true, // Error loading - deferRender: true, - destroy:true, - select: { - style: 'multi', - // selector: 'tr>td:nth-child(1)' //This only allows select the first row - }, - buttons: { - dom: { - button: { - className: 'dt-button buttons-collection btn btn-sm-dt btn-outline-gray-600-dt', - } - }, + }); + } + + /** + * Fetches Groups from the server. + * Subscribes to the API response and updates the Groups list. + */ + loadAccessGroups() { + // Set parameters for the API request + const params = { 'maxResults': this.maxResults }; + // Make an API call to get groups data + this.subscriptions.push(this.gs.getAll(SERV.ACCESS_GROUPS, params).subscribe((response: any) => { + this.agroups = response.values; + this.dtTrigger.next(void 0); + })); + } + + /** + * Sets up the DataTable options and buttons. + * Customizes DataTable appearance and behavior. + */ + setupTable(): void { + // DataTables options + const self = this; + this.dtOptions = { + dom: 'Bfrtip', + scrollX: true, + pageLength: 25, + lengthMenu: [ + [10, 25, 50, 100, 250, -1], + [10, 25, 50, 100, 250, 'All'] + ], + processing: true, // Error loading + deferRender: true, + destroy: true, + select: { + style: 'multi', + }, + buttons: { + dom: { + button: { + className: 'dt-button buttons-collection btn btn-sm-dt btn-outline-gray-600-dt', + } + }, buttons: [ { text: '↻', @@ -99,111 +159,107 @@ export class GroupsComponent implements OnInit { { extend: 'print', exportOptions: { - columns: [0,1] + columns: [0, 1] }, - customize: function ( win ) { + customize: function (win) { $(win.document.body) - .css( 'font-size', '10pt' ) - $(win.document.body).find( 'table' ) - .addClass( 'compact' ) - .css( 'font-size', 'inherit' ); - } + .css('font-size', '10pt'); + $(win.document.body).find('table') + .addClass('compact') + .css('font-size', 'inherit'); + } }, { extend: 'csvHtml5', - exportOptions: {modifier: {selected: true}}, + exportOptions: { modifier: { selected: true } }, select: true, customize: function (dt, csv) { - let data = ""; + let data = ''; for (let i = 0; i < dt.length; i++) { - data = "Agents\n\n"+ dt; + data = 'Agents\n\n' + dt; } return data; - } + } }, - 'copy' - ] - }, - { - extend: 'collection', - text: 'Bulk Actions', - className: 'dt-button buttons-collection btn btn-sm-dt btn-outline-gray-600-dt', - buttons: [ - { - text: 'Delete Groups', - autoClose: true, - action: function (e, dt, node, config) { - self.onDeleteBulk(); - } - } - ] - } - ], - } - }; - - } - - onRefresh(){ - this.rerender(); - this.ngOnInit(); + 'copy' + ] + }, + { + extend: 'collection', + text: 'Bulk Actions', + className: 'dt-button buttons-collection btn btn-sm-dt btn-outline-gray-600-dt', + buttons: [ + { + text: 'Delete Groups', + autoClose: true, + action: function (e, dt, node, config) { + self.onDeleteBulk(); + } + } + ] + } + ], + } + }; } - rerender(): void { - this.dtElement.dtInstance.then((dtInstance: DataTables.Api) => { - // Destroy the table first - dtInstance.destroy(); - // Call the dtTrigger to rerender again - setTimeout(() => { - this.dtTrigger['new'].next(); - }); - }); + // Refresh the table after a delete operation + onRefreshTable() { + setTimeout(() => { + this.ngOnInit(); + this.rerender(); // Rerender the DataTable + }, 2000); } - onDelete(id: number, name: string){ - this.alert.deleteConfirmation(name,'Groups').then((confirmed) => { + /** + * Handles Group deletion. + * Displays a confirmation dialog and deletes the Group if confirmed. + * + * @param {number} id - The ID of the Group to delete. + * @param {string} name - The name of the Group. + */ + onDelete(id: number, name: string) { + this.alert.deleteConfirmation(name, 'Groups').then((confirmed) => { if (confirmed) { // Deletion - this.gs.delete(SERV.ACCESS_GROUPS, id).subscribe(() => { + this.subscriptions.push(this.gs.delete(SERV.ACCESS_GROUPS, id).subscribe(() => { // Successful deletion this.alert.okAlert(`Deleted Group ${name}`, ''); this.onRefreshTable(); // Refresh the table - }); + })); } else { // Handle cancellation - this.alert.okAlert(`Group ${name} is safe!`,''); + this.alert.okAlert(`Group ${name} is safe!`, ''); } }); } - onRefreshTable(){ - setTimeout(() => { - this.ngOnInit(); - this.rerender(); // rerender datatables - },2000); - } - - onSelectedGroups(){ + /** + * Handles Group selection for bulk actions. + * On multi-select, retrieves the IDs to be used for bulk actions. + * + * @returns An array of selected Group IDs. + */ + onSelectedGroups() { $(".dt-button-background").trigger("click"); - const selection = $($(this.dtElement).DataTable.tables()).DataTable().rows({ selected: true } ).data().pluck(0).toArray(); - if(selection.length == 0) { - this.alert.okAlert('You haven not selected any Group',''); + const selection = $($(this.dtElement).DataTable.tables()).DataTable().rows({ selected: true }).data().pluck(0).toArray(); + if (selection.length == 0) { + this.alert.okAlert('You have not selected any Group', ''); return; } - const selectionnum = selection.map(i=>Number(i)); + const selectionnum = selection.map(i => Number(i)); return selectionnum; } + /** + * Handles bulk deletion of selected Groups. + * Deletes the selected Groups and shows a progress bar. + */ async onDeleteBulk() { const GroupsIds = this.onSelectedGroups(); - this.alert.bulkDeleteAlert(GroupsIds,'Groups',SERV.ACCESS_GROUPS); + this.alert.bulkDeleteAlert(GroupsIds, 'Groups', SERV.ACCESS_GROUPS); this.onRefreshTable(); } - // Add unsubscribe to detect changes - ngOnDestroy(){ - this.dtTrigger.unsubscribe(); - } - } From e8cbc419892e20086612da39be79fd7eecb8399f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Mon, 23 Oct 2023 15:40:24 +0100 Subject: [PATCH 092/419] Hashlist show all, documentation --- .../hashlists/hashlist/hashlist.component.ts | 234 +++++++++++------- 1 file changed, 150 insertions(+), 84 deletions(-) diff --git a/src/app/hashlists/hashlist/hashlist.component.ts b/src/app/hashlists/hashlist/hashlist.component.ts index 3b86e4b6..477fe0a5 100644 --- a/src/app/hashlists/hashlist/hashlist.component.ts +++ b/src/app/hashlists/hashlist/hashlist.component.ts @@ -1,14 +1,13 @@ import { faEdit, faTrash, faLock, faFileImport, faFileExport, faArchive, faPlus, faHomeAlt, faCheckCircle } from '@fortawesome/free-solid-svg-icons'; import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core'; -import { DataTableDirective } from 'angular-datatables'; -import Swal from 'sweetalert2/dist/sweetalert2.js'; import { ActivatedRoute, Router } from '@angular/router'; -import { Subject } from 'rxjs'; +import { DataTableDirective } from 'angular-datatables'; +import { Subject, Subscription } from 'rxjs'; +import { AutoTitleService } from 'src/app/core/_services/shared/autotitle.service'; import { AlertService } from 'src/app/core/_services/shared/alert.service'; import { GlobalService } from 'src/app/core/_services/main.service'; import { environment } from './../../../environments/environment'; -import { PageTitle } from 'src/app/core/_decorators/autotitle'; import { SERV } from '../../core/_services/main.config'; declare let $:any; @@ -17,9 +16,15 @@ declare let $:any; selector: 'app-hashlist', templateUrl: './hashlist.component.html' }) -@PageTitle(['Show Hashlists']) +/** + * HashlistComponent is a component that manages and displays all hashlist data. + * + * It uses DataTables to display and interact with the users hashlist data, including exporting, deleting, bulk actions + * and refreshing the table. + */ export class HashlistComponent implements OnInit, OnDestroy { + // Font Awesome icons faCheckCircle=faCheckCircle; faFileImport=faFileImport; faFileExport=faFileExport; @@ -30,12 +35,14 @@ export class HashlistComponent implements OnInit, OnDestroy { faPlus=faPlus; faEdit=faEdit; + // ViewChild reference to the DataTableDirective @ViewChild(DataTableDirective) dtElement: DataTableDirective; dtTrigger: Subject = new Subject(); dtOptions: any = {}; + // List of hashlists ToDo. Change to interface public allhashlists: { hashlistId: number, name: string, @@ -54,22 +61,69 @@ export class HashlistComponent implements OnInit, OnDestroy { isArchived: string, accessGroup: {accessGroupId: number, groupName: string} hashType: {description: string, hashTypeId: number, isSalted: string, isSlowHash: string} - }[] = []; // Should be in models, Todo when data structure is confirmed + }[] = []; + + // Subscriptions to unsubscribe on component destruction + subscriptions: Subscription[] = [] + + private maxResults = environment.config.prodApiMaxResults; + + // View type and filter options + isArchived: boolean; + whichView: string; constructor( + private titleService: AutoTitleService, private route:ActivatedRoute, private alert: AlertService, private gs: GlobalService, private router: Router - ) { } - - isArchived: boolean; - whichView: string; + ) { + titleService.set(['Show Hashlists']) + } - private maxResults = environment.config.prodApiMaxResults + /** + * Initializes DataTable and retrieves pretasks. + */ ngOnInit(): void { + this.getHashlists(); + this.setupTable(); + } + /** + * Unsubscribes from active subscriptions. + */ + ngOnDestroy(): void { + this.dtTrigger.unsubscribe(); + for (const sub of this.subscriptions) { + sub.unsubscribe(); + } + } + + // Refresh the data and the DataTable + onRefresh() { + this.rerender(); + this.ngOnInit(); + } + + /** + * Rerender the DataTable. + */ + rerender(): void { + this.dtElement.dtInstance.then((dtInstance: DataTables.Api) => { + dtInstance.destroy(); + setTimeout(() => { + this.dtTrigger['new'].next(); + }); + }); + } + + /** + * Fetches Hashlists from the server filtering by live or archived + * Subscribes to the API response and updates the Users list. + */ + getHashlists(): void { this.route.data.subscribe(data => { switch (data['kind']) { @@ -91,7 +145,15 @@ export class HashlistComponent implements OnInit, OnDestroy { this.allhashlists = list.values.filter(u=> u.format != 3); // Exclude superhashlists this.dtTrigger.next(void 0); }); + }) + } + /** + * Sets up the DataTable options and buttons. + * Customizes DataTable appearance and behavior. + */ + setupTable(): void { + // DataTables options const self = this; this.dtOptions = { dom: 'Bfrtip', @@ -205,88 +267,92 @@ export class HashlistComponent implements OnInit, OnDestroy { }, ], } - }; - - }); - -} - -onRefresh(){ - this.ngOnInit(); - this.rerender(); // rerender datatables -} + } + } -rerender(): void { - this.dtElement.dtInstance.then((dtInstance: DataTables.Api) => { - // Destroy the table first - dtInstance.destroy(); - // Call the dtTrigger to rerender again + // Refresh the table after a delete operation + onRefreshTable() { setTimeout(() => { - this.dtTrigger['new'].next(); + this.ngOnInit(); + this.rerender(); // Rerender the DataTable + }, 2000); + } + + /** + * Archives a hashlist with the given ID. + * + * @param {number} id - The ID of the hashlist to archive. + */ + onArchive(id: number){ + this.gs.archive(SERV.HASHLISTS,id).subscribe((list: any) => { + this.alert.okAlert('Archived!',''); + this.ngOnInit(); + this.rerender(); // rerender datatables }); - }); -} + } -onArchive(id: number){ - this.gs.archive(SERV.HASHLISTS,id).subscribe((list: any) => { - this.alert.okAlert('Archived!',''); - this.ngOnInit(); - this.rerender(); // rerender datatables - }); -} + /** + * Handles the deletion of a hashlist. + * Displays a confirmation dialog and deletes the hashlist if confirmed. + * + * @param {number} id - The ID of the hashlist to delete. + * @param {string} name - The name of the hashlist. + */ + onDelete(id: number, name: string){ + this.alert.deleteConfirmation(name,'Hashlists').then((confirmed) => { + if (confirmed) { + // Deletion + this.gs.delete(SERV.HASHLISTS, id).subscribe(() => { + // Successful deletion + this.alert.okAlert(`Deleted Hashlist ${name}`, ''); + this.onRefreshTable(); // Refresh the table + }); + } else { + // Handle cancellation + this.alert.okAlert(`Hashlist ${name} is safe!`,''); + } + }); + } -onDelete(id: number, name: string){ - this.alert.deleteConfirmation(name,'Hashlists').then((confirmed) => { - if (confirmed) { - // Deletion - this.gs.delete(SERV.HASHLISTS, id).subscribe(() => { - // Successful deletion - this.alert.okAlert(`Deleted Hashlist ${name}`, ''); - this.onRefreshTable(); // Refresh the table - }); - } else { - // Handle cancellation - this.alert.okAlert(`Hashlist ${name} is safe!`,''); + // Bulk actions + + /** + * Handles hashlist selection for bulk actions. + * + * @returns {number[]} - An array of selected hashlist IDs. + */ + onSelectedHashlists(){ + $(".dt-button-background").trigger("click"); + const selection = $($(this.dtElement).DataTable.tables()).DataTable().rows({ selected: true } ).data().pluck(0).toArray(); + if(selection.length == 0) { + this.alert.okAlert('You haven not selected any Group',''); + return; } - }); -} - -onRefreshTable(){ - setTimeout(() => { - this.ngOnInit(); - this.rerender(); // rerender datatables - },2000); -} + const selectionnum = selection.map(i=>Number(i)); -// Bulk actions - -onSelectedHashlists(){ - $(".dt-button-background").trigger("click"); - const selection = $($(this.dtElement).DataTable.tables()).DataTable().rows({ selected: true } ).data().pluck(0).toArray(); - if(selection.length == 0) { - this.alert.okAlert('You haven not selected any Group',''); - return; + return selectionnum; } - const selectionnum = selection.map(i=>Number(i)); - - return selectionnum; -} -async onDeleteBulk() { - const HashlistIds = this.onSelectedHashlists(); - this.alert.bulkDeleteAlert(HashlistIds,'Hashlists',SERV.HASHLISTS); - this.onRefreshTable(); -} - -async onUpdateBulk(value: any) { - const HashlistIds = this.onSelectedHashlists(); - this.alert.bulkUpdateAlert(HashlistIds,value,'Hashlists',SERV.HASHLISTS); - this.onRefreshTable(); -} + /** + * Handles bulk deletion + * Delete the hashlists showing a progress bar + * + */ + async onDeleteBulk() { + const HashlistIds = this.onSelectedHashlists(); + this.alert.bulkDeleteAlert(HashlistIds,'Hashlists',SERV.HASHLISTS); + this.onRefreshTable(); + } -// Add unsubscribe to detect changes -ngOnDestroy(){ - this.dtTrigger.unsubscribe(); -} + /** + * Updates the selected hashlists with the given value. + * + * @param {any} value - The value to update the selected hashlists with. + */ + async onUpdateBulk(value: any) { + const HashlistIds = this.onSelectedHashlists(); + this.alert.bulkUpdateAlert(HashlistIds,value,'Hashlists',SERV.HASHLISTS); + this.onRefreshTable(); + } } From 092b0d4c5f63f488502da0f77d70b18ea4e9ea29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Mon, 23 Oct 2023 15:43:49 +0100 Subject: [PATCH 093/419] Hashlist remove library not being used --- .../globalpermissionsgroups.component.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/app/users/globalpermissionsgroups/globalpermissionsgroups.component.ts b/src/app/users/globalpermissionsgroups/globalpermissionsgroups.component.ts index 1e6b094c..57a0430c 100644 --- a/src/app/users/globalpermissionsgroups/globalpermissionsgroups.component.ts +++ b/src/app/users/globalpermissionsgroups/globalpermissionsgroups.component.ts @@ -4,12 +4,11 @@ import { DataTableDirective } from 'angular-datatables'; import { Subject, Subscription } from 'rxjs'; import { Router } from '@angular/router'; +import { AutoTitleService } from 'src/app/core/_services/shared/autotitle.service'; import { AlertService } from 'src/app/core/_services/shared/alert.service'; import { GlobalService } from 'src/app/core/_services/main.service'; -import { PageTitle } from 'src/app/core/_decorators/autotitle'; import { environment } from 'src/environments/environment'; import { SERV } from '../../core/_services/main.config'; -import { AutoTitleService } from 'src/app/core/_services/shared/autotitle.service'; declare let $:any; From 99444040b0bf2244d244387c50bfb09ce5861efe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Mon, 23 Oct 2023 15:45:53 +0100 Subject: [PATCH 094/419] Hashlist table add subscription --- src/app/hashlists/hashlist/hashlist.component.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/app/hashlists/hashlist/hashlist.component.ts b/src/app/hashlists/hashlist/hashlist.component.ts index 477fe0a5..e21d599f 100644 --- a/src/app/hashlists/hashlist/hashlist.component.ts +++ b/src/app/hashlists/hashlist/hashlist.component.ts @@ -141,10 +141,10 @@ export class HashlistComponent implements OnInit, OnDestroy { const params = {'maxResults': this.maxResults, 'expand': 'hashType,accessGroup', 'filter': 'isArchived='+this.isArchived+''} - this.gs.getAll(SERV.HASHLISTS,params).subscribe((list: any) => { + this.subscriptions.push(this.gs.getAll(SERV.HASHLISTS,params).subscribe((list: any) => { this.allhashlists = list.values.filter(u=> u.format != 3); // Exclude superhashlists this.dtTrigger.next(void 0); - }); + })); }) } @@ -302,11 +302,11 @@ export class HashlistComponent implements OnInit, OnDestroy { this.alert.deleteConfirmation(name,'Hashlists').then((confirmed) => { if (confirmed) { // Deletion - this.gs.delete(SERV.HASHLISTS, id).subscribe(() => { + this.subscriptions.push(this.gs.delete(SERV.HASHLISTS, id).subscribe(() => { // Successful deletion this.alert.okAlert(`Deleted Hashlist ${name}`, ''); this.onRefreshTable(); // Refresh the table - }); + })); } else { // Handle cancellation this.alert.okAlert(`Hashlist ${name} is safe!`,''); From 9fdfb523665aa2e0f9d9733af170f4a00a75179e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Mon, 23 Oct 2023 15:59:37 +0100 Subject: [PATCH 095/419] Notifications, global notification and delete bulk action --- .../notifications/notifications.component.ts | 103 +++++++++++------- 1 file changed, 61 insertions(+), 42 deletions(-) diff --git a/src/app/account/notifications/notifications.component.ts b/src/app/account/notifications/notifications.component.ts index dd9b9e74..d2cd13ed 100644 --- a/src/app/account/notifications/notifications.component.ts +++ b/src/app/account/notifications/notifications.component.ts @@ -2,7 +2,6 @@ import { faTrash, faPlus, faEye, faEdit } from '@fortawesome/free-solid-svg-icon import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core'; import { environment } from './../../../environments/environment'; import { DataTableDirective } from 'angular-datatables'; -import Swal from 'sweetalert2/dist/sweetalert2.js'; import { Subject, Subscription } from 'rxjs'; import { AutoTitleService } from 'src/app/core/_services/shared/autotitle.service'; @@ -18,6 +17,8 @@ export interface Filter { name: string } +declare let $:any; + @Component({ selector: 'app-notifications', templateUrl: './notifications.component.html' @@ -180,6 +181,19 @@ export class NotificationsComponent implements OnInit, OnDestroy { } ] }, + { + extend: 'collection', + text: 'Bulk Actions', + buttons: [ + { + text: 'Delete Notification(s)', + autoClose: true, + action: function (e, dt, node, config) { + self.onDeleteBulk(); + } + } + ] + }, { extend: 'pageLength', className: 'btn-sm' @@ -189,6 +203,14 @@ export class NotificationsComponent implements OnInit, OnDestroy { } } + // Refresh the table after a delete operation + onRefreshTable() { + setTimeout(() => { + this.ngOnInit(); + this.rerender(); // Rerender the DataTable + }, 2000); + } + /** * Handles notification deletion. * Displays a confirmation dialog and deletes the notification if confirmed. @@ -197,52 +219,49 @@ export class NotificationsComponent implements OnInit, OnDestroy { * @param {string} name - The name of the notification. */ onDelete(id: number, name: string) { - const swalWithBootstrapButtons = Swal.mixin({ - customClass: { - confirmButton: 'btn', - cancelButton: 'btn' - }, - buttonsStyling: false - }) - Swal - .fire({ - title: `Remove ${name} from your notifications?`, - icon: 'warning', - reverseButtons: true, - showCancelButton: true, - cancelButtonColor: this.alert.cancelButtonColor, - confirmButtonColor: this.alert.confirmButtonColor, - confirmButtonText: this.alert.delconfirmText - }) - .then((result) => { - if (result.isConfirmed) { - this.deleteNotification(id,name) - } else { - swalWithBootstrapButtons.fire({ - title: 'Cancelled', - text: 'Your Notification is safe!', - icon: 'error', - showConfirmButton: false, - timer: 1500 - }) - } - }); + this.alert.deleteConfirmation(name,'Notifications').then((confirmed) => { + if (confirmed) { + // Deletion + this.subscriptions.push(this.gs.delete(SERV.NOTIFICATIONS, id).subscribe(() => { + // Successful deletion + this.alert.okAlert(`Deleted notification ${name}`, ''); + this.onRefreshTable(); // Refresh the table + })); + } else { + // Handle cancellation + this.alert.okAlert(`Notification ${name} is safe!`,''); + } + }); } + // Bulk actions + /** - * Handles the deletion of a notification. - * Sends an delete request to the backend and displays a success alert and rebuilds - * the datatable on success. + * Handles Notifications selection for bulk actions. * - * @param {number} id - The ID of the notification to delete. + * @returns {number[]} - An array of selected hashlist IDs. */ - deleteNotification(id: number, name: string): void { - this.subscriptions.push(this.gs.delete(SERV.NOTIFICATIONS, id).subscribe(() => { - this.alert.okAlert('Deleted '+name+'',''); - this.getNotifications(); - this.rerender(); - this.setupTable(); - })); + onSelectedNotifications(){ + $(".dt-button-background").trigger("click"); + const selection = $($(this.dtElement).DataTable.tables()).DataTable().rows({ selected: true } ).data().pluck(0).toArray(); + if(selection.length == 0) { + this.alert.okAlert('You haven not selected any Notification',''); + return; + } + const selectionnum = selection.map(i=>Number(i)); + + return selectionnum; + } + + /** + * Handles bulk deletion + * Delete the Notifications showing a progress bar + * + */ + async onDeleteBulk() { + const NotifIds = this.onSelectedNotifications(); + this.alert.bulkDeleteAlert(NotifIds,'Notifications',SERV.NOTIFICATIONS); + this.onRefreshTable(); } /** From c0ed19247192df5da14883ecacb8d4484a2dab24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Mon, 23 Oct 2023 20:11:44 +0100 Subject: [PATCH 096/419] Hashes, subscriptions, destroy and documentation --- .../hashlists/hashes/hashes.component.html | 2 +- src/app/hashlists/hashes/hashes.component.ts | 229 ++++++++++++------ 2 files changed, 150 insertions(+), 81 deletions(-) diff --git a/src/app/hashlists/hashes/hashes.component.html b/src/app/hashlists/hashes/hashes.component.html index 517e9ec2..3e620295 100644 --- a/src/app/hashlists/hashes/hashes.component.html +++ b/src/app/hashlists/hashes/hashes.component.html @@ -58,7 +58,7 @@

Hashes of hashlist - + diff --git a/src/app/hashlists/hashes/hashes.component.ts b/src/app/hashlists/hashes/hashes.component.ts index 1f9ef1dc..9b3a46a4 100644 --- a/src/app/hashlists/hashes/hashes.component.ts +++ b/src/app/hashlists/hashes/hashes.component.ts @@ -1,10 +1,11 @@ +import { Component, OnInit,OnDestroy, ViewChild } from '@angular/core'; import { ActivatedRoute, Params, Router } from '@angular/router'; -import { Component, OnInit, ViewChild } from '@angular/core'; import { faCopy } from '@fortawesome/free-solid-svg-icons'; import { DataTableDirective } from 'angular-datatables'; import { FormControl, FormGroup } from '@angular/forms'; -import { Subject } from 'rxjs'; +import { Subject,Subscription } from 'rxjs'; +import { AutoTitleService } from 'src/app/core/_services/shared/autotitle.service'; import { GlobalService } from 'src/app/core/_services/main.service'; import { PageTitle } from 'src/app/core/_decorators/autotitle'; import { SERV } from '../../core/_services/main.config'; @@ -13,29 +14,45 @@ import { SERV } from '../../core/_services/main.config'; selector: 'app-hashes', templateUrl: './hashes.component.html' }) -@PageTitle(['Show Hashes']) -export class HashesComponent implements OnInit { +/** +* The `HashesComponent` is an Angular component responsible for managing and displaying a list of hashes +* with various filtering and display options using the DataTables library. This documentation provides +* an overview of the component's structure and functionality. +*/ +export class HashesComponent implements OnInit,OnDestroy { + + // Component Properties editMode = false; editedIndex: number; edited: any // Change to Model + // Font Awesome icons faCopy=faCopy; - whichView: string; - titleName: any; - + // ViewChild reference to the DataTableDirective @ViewChild(DataTableDirective) dtElement: DataTableDirective; dtTrigger: Subject = new Subject(); dtOptions: any = {}; + // Subscriptions to unsubscribe on component destruction + subscriptions: Subscription[] = [] + + // View type and filter options + whichView: string; + titleName: any; + constructor( + private titleService: AutoTitleService, private route:ActivatedRoute, private gs: GlobalService, private router:Router - ) { } + ) { + titleService.set(['Show Hashes']) + } + // Filtering and Display Properties crackPos: any = true; cracked:any; filtering = ""; @@ -43,6 +60,7 @@ export class HashesComponent implements OnInit { displaying = ""; displayingDescr = ""; + // Filter and Display Options filters: any = [{"name":"cracked", "description":"Cracked"},{"name":"uncracked", "description": "Uncracked"},{"name":"", "description": "All"}]; displays: any = [ {"name":"", "description": "Hashes + Plaintexts"}, @@ -55,8 +73,48 @@ export class HashesComponent implements OnInit { matchHashes:any; + /** + * Initializes DataTable and retrieves hashes.. + */ + ngOnInit(): void { + this.loadHashes(); + this.setupTable(); + } + + /** + * Unsubscribes from active subscriptions. + */ + ngOnDestroy(): void { + this.dtTrigger.unsubscribe(); + for (const sub of this.subscriptions) { + sub.unsubscribe(); + } + } + + // Refresh the data and the DataTable + onRefresh() { + this.rerender(); + this.ngOnInit(); + } + + /** + * Rerender the DataTable. + */ + rerender(): void { + this.dtElement.dtInstance.then((dtInstance: DataTables.Api) => { + dtInstance.destroy(); + setTimeout(() => { + this.dtTrigger['new'].next(); + }); + }); + } + /** + * Fetches Hashes from the server + * Subscribes to the API response and updates the hashes list. + */ + loadHashes(): void { this.route.params .subscribe( (params: Params) => { @@ -64,74 +122,24 @@ export class HashesComponent implements OnInit { } ); - this.dtOptions = { - dom: 'Bfrtip', - scrollX: true, - pageLength: 25, - lengthMenu: [ - [10, 25, 50, 100, 250, -1], - [10, 25, 50, 100, 250, 'All'] - ], - searching: false, - buttons: { - dom: { - button: { - className: 'dt-button buttons-collection btn btn-sm-dt btn-outline-gray-600-dt', - } - }, - buttons: [ - { - extend: 'collection', - text: 'Export', - buttons: [ - { - extend: 'print', - customize: function ( win ) { - $(win.document.title) - .css( 'font-size', '14pt' ) - $(win.document.body) - .css( 'font-size', '10pt' ) - $(win.document.body).find( 'table' ) - .addClass( 'compact' ) - .css( 'font-size', 'inherit' ); - } - }, - { - extend: 'csvHtml5', - exportOptions: {modifier: {selected: true}}, - select: true, - customize: function (dt, csv) { - let data = ""; - for (let i = 0; i < dt.length; i++) { - data = "Hashes Information\n\n"+ dt; - } - return data; - } - } - ] - }, - ], - } - } - this.route.data.subscribe(data => { switch (data['kind']) { case 'chunkshash': this.whichView = 'chunks'; - this.gs.get(SERV.CHUNKS,this.editedIndex).subscribe((result)=>{this.titleName = result['chunkId']}); + this.subscriptions.push(this.gs.get(SERV.CHUNKS,this.editedIndex).subscribe((result)=>{this.titleName = result['chunkId']})); this.initChashes(); break; case 'taskhas': this.whichView = 'tasks'; - this.gs.get(SERV.TASKS,this.editedIndex).subscribe((result)=>{this.titleName = result['taskName']}); + this.subscriptions.push(this.gs.get(SERV.TASKS,this.editedIndex).subscribe((result)=>{this.titleName = result['taskName']})); this.initThashes(); break; case 'hashlisthash': this.whichView = 'hashlists'; - this.gs.get(SERV.HASHLISTS,this.editedIndex).subscribe((result)=>{this.titleName = result['name']}); + this.subscriptions.push(this.gs.get(SERV.HASHLISTS,this.editedIndex).subscribe((result)=>{this.titleName = result['name']})); this.initHhashes(); break; @@ -140,29 +148,102 @@ export class HashesComponent implements OnInit { }); } + /** + * Sets up the DataTable options and buttons. + * Customizes DataTable appearance and behavior. + */ + setupTable(): void { + // DataTables options + this.dtOptions = { + dom: 'Bfrtip', + scrollX: true, + pageLength: 25, + lengthMenu: [ + [10, 25, 50, 100, 250, -1], + [10, 25, 50, 100, 250, 'All'] + ], + searching: false, + buttons: { + dom: { + button: { + className: 'dt-button buttons-collection btn btn-sm-dt btn-outline-gray-600-dt', + } + }, + buttons: [ + { + extend: 'collection', + text: 'Export', + buttons: [ + { + extend: 'print', + customize: function ( win ) { + $(win.document.title) + .css( 'font-size', '14pt' ) + $(win.document.body) + .css( 'font-size', '10pt' ) + $(win.document.body).find( 'table' ) + .addClass( 'compact' ) + .css( 'font-size', 'inherit' ); + } + }, + { + extend: 'csvHtml5', + exportOptions: {modifier: {selected: true}}, + select: true, + customize: function (dt, csv) { + let data = ""; + for (let i = 0; i < dt.length; i++) { + data = "Hashes Information\n\n"+ dt; + } + return data; + } + } + ] + }, + ], + } + } + } + + /** + * Initialize based on Chunk hashes + * + */ private initChashes() { const param = {'filter': 'chunkId='+this.editedIndex+''}; this.getHashes(param); } + /** + * Initialize based on Tasks hashes + * + */ private initThashes() { // This should enough to filter by id // let param = {'filter': 'taskId='+this.editedIndex+''}; this.getHashes(); } + /** + * Initialize based on Hashlists hashes + * + */ private initHhashes() { const param = {'filter': 'hashlistId='+this.editedIndex+''}; this.getHashes(param); } + /** + * Fetch hashes from the server + * + */ async getHashes(param?: any){ const params = {'maxResults': 90000, 'expand':'hashlist,chunk'}; const nwparams = {...params, ...param}; - this.gs.getAll(SERV.HASHES,nwparams).subscribe((hashes: any) => { + this.subscriptions.push(this.gs.getAll(SERV.HASHES,nwparams).subscribe((hashes: any) => { let res = hashes.values; console.log(this.whichView); if(this.whichView === 'tasks'){ @@ -178,10 +259,12 @@ export class HashesComponent implements OnInit { this.matchHashes = res; } this.dtTrigger.next(null); - }); + })); } + // Initialize the form for display and filter options + viewForm: FormGroup; initDisplay(){ @@ -203,6 +286,7 @@ export class HashesComponent implements OnInit { }); } + // Update query parameters and trigger updates onQueryp(name: any, type: number){ let query = {} if(type == 0){ @@ -216,6 +300,7 @@ export class HashesComponent implements OnInit { this.ngOnInit(); } + // Update display or filter options onDisplaying(name: string, type:number){ if(type == 0){ this.displaying = name; @@ -232,11 +317,7 @@ export class HashesComponent implements OnInit { } } - onOK(){ - this.rerender(); - this.ngOnInit(); - } - + // Get the description for filter and display options getDescrip(item: string, type:number){ if(type == 0){ this.displayingDescr = this.displays.find(obj => obj.name === item).description; @@ -249,16 +330,4 @@ export class HashesComponent implements OnInit { } } - rerender(): void { - this.dtElement.dtInstance.then((dtInstance: DataTables.Api) => { - // Destroy the table first - dtInstance.destroy(); - // Call the dtTrigger to rerender again - setTimeout(() => { - this.dtTrigger['new'].next(null); - }); - }); - } - - } From 2cbbbd3e013fdb7fd95f0722eb16fb0dcced760d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Tue, 24 Oct 2023 14:45:47 +0100 Subject: [PATCH 097/419] All pages, fix all breadcrums using a custom directive --- src/app/agents/agents-routing.module.ts | 9 ++-- src/app/app.component.html | 8 +-- .../_directives/nav-startswith.directive.ts | 53 +++++++++++++++++++ src/app/layout/header/header.component.html | 18 +++---- src/app/shared/directives.module.ts | 3 ++ src/styles/layout/_header.scss | 9 ++++ 6 files changed, 83 insertions(+), 17 deletions(-) create mode 100644 src/app/core/_directives/nav-startswith.directive.ts diff --git a/src/app/agents/agents-routing.module.ts b/src/app/agents/agents-routing.module.ts index d0049ae1..e401d91d 100644 --- a/src/app/agents/agents-routing.module.ts +++ b/src/app/agents/agents-routing.module.ts @@ -12,6 +12,7 @@ import { NewAgentComponent } from "./new-agent/new-agent.component"; const routes: Routes = [ { path: '', + canActivate: [IsAuth], children: [ { path: 'agent-status', component: AgentStatusComponent, @@ -20,7 +21,7 @@ const routes: Routes = [ breadcrumb: 'Agent Status', permission: 'Agent' //ToDo this one has Agent read and Agent Stats read }, - canActivate: [IsAuth,CheckPerm] + canActivate: [CheckPerm] }, { path: 'new-agent', component: NewAgentComponent, @@ -29,7 +30,7 @@ const routes: Routes = [ breadcrumb: 'New Agent', permission: 'Agent' }, - canActivate: [IsAuth,CheckPerm]}, + canActivate: [CheckPerm]}, { path: 'show-agents', component: ShowAgentsComponent, data: { @@ -37,7 +38,7 @@ const routes: Routes = [ breadcrumb: 'Show Agent', permission: 'Agent' }, - canActivate: [IsAuth,CheckPerm]}, + canActivate: [CheckPerm]}, { path: 'show-agents/:id/edit', component: EditAgentComponent, data: { @@ -45,7 +46,7 @@ const routes: Routes = [ breadcrumb: 'Edit Agent', permission: 'Agent' }, - canActivate: [IsAuth,CheckPerm]}, + canActivate: [CheckPerm]}, ] }, ]; diff --git a/src/app/app.component.html b/src/app/app.component.html index 61d5723f..263cc20f 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -1,12 +1,12 @@ - +
- - + + - +
diff --git a/src/app/core/_directives/nav-startswith.directive.ts b/src/app/core/_directives/nav-startswith.directive.ts new file mode 100644 index 00000000..1b135a7e --- /dev/null +++ b/src/app/core/_directives/nav-startswith.directive.ts @@ -0,0 +1,53 @@ +import { Directive, ElementRef, Input, OnInit, Renderer2 } from '@angular/core'; +import { Router, NavigationEnd } from '@angular/router'; + +/** + * Directive to add a custom class to an element if the current route starts with a specified path, + * excluding routes containing a specified string. + * + * Usage: + * Add the [startsWithActive] attribute to an HTML element and provide the path as its value. + * You can also provide an optional string to exclude routes containing that string, and a custom class. + * + * Example: + *
+ * Show Agents + * + * + * When the current route starts with the specified path and does not contain the exclusion string, + * the custom class is added to the element. + */ +@Directive({ + selector: '[startsWithActive]' +}) +export class StartsWithActiveDirective implements OnInit { + @Input() startsWithActive: string; + @Input() excludeContaining?: string; + @Input() customClass?: string; + + constructor(private el: ElementRef, private renderer: Renderer2, private router: Router) {} + + ngOnInit(): void { + this.router.events.subscribe((event) => { + if (event instanceof NavigationEnd) { + const currentRoute = this.router.url; + if ( + currentRoute.startsWith(this.startsWithActive) && + (!this.excludeContaining || !currentRoute.includes(this.excludeContaining)) + ) { + if (this.customClass) { + this.renderer.addClass(this.el.nativeElement, this.customClass); + } else { + this.el.nativeElement.classList.add('active'); + } + } else { + if (this.customClass) { + this.renderer.removeClass(this.el.nativeElement, this.customClass); + } else { + this.el.nativeElement.classList.remove('active'); + } + } + } + }); + } +} diff --git a/src/app/layout/header/header.component.html b/src/app/layout/header/header.component.html index 396d7132..6e4f8438 100644 --- a/src/app/layout/header/header.component.html +++ b/src/app/layout/header/header.component.html @@ -36,7 +36,7 @@ - \ No newline at end of file + diff --git a/src/app/account/settings/acc-settings/acc-settings.component.html b/src/app/account/settings/acc-settings/acc-settings.component.html index b778fd0a..4db94fe9 100644 --- a/src/app/account/settings/acc-settings/acc-settings.component.html +++ b/src/app/account/settings/acc-settings/acc-settings.component.html @@ -30,7 +30,9 @@ - + + + - \ No newline at end of file + diff --git a/src/app/account/settings/ui-settings/ui-settings.component.html b/src/app/account/settings/ui-settings/ui-settings.component.html index 5451d1e3..f6a7544b 100644 --- a/src/app/account/settings/ui-settings/ui-settings.component.html +++ b/src/app/account/settings/ui-settings/ui-settings.component.html @@ -15,7 +15,10 @@ - + + + + diff --git a/src/app/agents/edit-agent/edit-agent.component.html b/src/app/agents/edit-agent/edit-agent.component.html index cb7b50a7..a8f5851a 100644 --- a/src/app/agents/edit-agent/edit-agent.component.html +++ b/src/app/agents/edit-agent/edit-agent.component.html @@ -215,7 +215,9 @@

Agent Detailed Information

> - + + + diff --git a/src/app/auth/forgot/forgot.component.html b/src/app/auth/forgot/forgot.component.html index 370df61e..98820604 100644 --- a/src/app/auth/forgot/forgot.component.html +++ b/src/app/auth/forgot/forgot.component.html @@ -25,7 +25,10 @@

Forgot password

required > - + + + + diff --git a/src/app/config/engine/agent-binaries/agent-binary/new-agent-binaries.component.html b/src/app/config/engine/agent-binaries/agent-binary/new-agent-binaries.component.html index 0c718c88..407fec63 100644 --- a/src/app/config/engine/agent-binaries/agent-binary/new-agent-binaries.component.html +++ b/src/app/config/engine/agent-binaries/agent-binary/new-agent-binaries.component.html @@ -54,10 +54,15 @@
- + + + +
- + + +
diff --git a/src/app/config/engine/crackers/edit-version/edit-crackers.component.html b/src/app/config/engine/crackers/edit-version/edit-crackers.component.html index 20aa9d67..76dd1b64 100644 --- a/src/app/config/engine/crackers/edit-version/edit-crackers.component.html +++ b/src/app/config/engine/crackers/edit-version/edit-crackers.component.html @@ -33,8 +33,10 @@
- - + + + +
diff --git a/src/app/config/engine/crackers/new-cracker/new-cracker.component.html b/src/app/config/engine/crackers/new-cracker/new-cracker.component.html index a2eb4f42..98729397 100644 --- a/src/app/config/engine/crackers/new-cracker/new-cracker.component.html +++ b/src/app/config/engine/crackers/new-cracker/new-cracker.component.html @@ -25,7 +25,10 @@ - + + + + diff --git a/src/app/config/engine/crackers/new-version/new-crackers.component.html b/src/app/config/engine/crackers/new-version/new-crackers.component.html index 5da90080..b822df93 100644 --- a/src/app/config/engine/crackers/new-version/new-crackers.component.html +++ b/src/app/config/engine/crackers/new-version/new-crackers.component.html @@ -43,7 +43,10 @@ - + + + + diff --git a/src/app/config/engine/preprocessors/preprocessor/new-preprocessor.component.html b/src/app/config/engine/preprocessors/preprocessor/new-preprocessor.component.html index 4cbfe614..6d2bad9a 100644 --- a/src/app/config/engine/preprocessors/preprocessor/new-preprocessor.component.html +++ b/src/app/config/engine/preprocessors/preprocessor/new-preprocessor.component.html @@ -63,10 +63,15 @@
- + + + +
- + + +
diff --git a/src/app/config/hashtypes/hashtype/hashtype.component.html b/src/app/config/hashtypes/hashtype/hashtype.component.html index adc30edb..27539ed7 100644 --- a/src/app/config/hashtypes/hashtype/hashtype.component.html +++ b/src/app/config/hashtypes/hashtype/hashtype.component.html @@ -59,10 +59,15 @@
- + + + +
- + + +
diff --git a/src/app/config/health-checks/new-health-check/new-health-checks.component.html b/src/app/config/health-checks/new-health-check/new-health-checks.component.html index 25c74b6e..6d6b787a 100644 --- a/src/app/config/health-checks/new-health-check/new-health-checks.component.html +++ b/src/app/config/health-checks/new-health-check/new-health-checks.component.html @@ -48,7 +48,10 @@ //[selected]="first" - + + + + diff --git a/src/app/files/files-edit/files-edit.component.html b/src/app/files/files-edit/files-edit.component.html index 06d04304..685e4d12 100644 --- a/src/app/files/files-edit/files-edit.component.html +++ b/src/app/files/files-edit/files-edit.component.html @@ -51,7 +51,9 @@ - + + + diff --git a/src/app/files/new-files/new-files.component.html b/src/app/files/new-files/new-files.component.html index a4be6265..611715a1 100644 --- a/src/app/files/new-files/new-files.component.html +++ b/src/app/files/new-files/new-files.component.html @@ -54,9 +54,10 @@

Upload completed!

  -
+ + -
+
Do not refresh the page while uploading @@ -108,9 +109,10 @@
-
+ + -
+
Files are uploaded to the server using a public/private link (URL). diff --git a/src/app/hashlists/edit-hashlist/edit-hashlist.component.html b/src/app/hashlists/edit-hashlist/edit-hashlist.component.html index b2cd0c4b..f99eff7a 100644 --- a/src/app/hashlists/edit-hashlist/edit-hashlist.component.html +++ b/src/app/hashlists/edit-hashlist/edit-hashlist.component.html @@ -112,7 +112,9 @@
- + + + diff --git a/src/app/hashlists/new-hashlist/new-hashlist.component.html b/src/app/hashlists/new-hashlist/new-hashlist.component.html index 731f0f6b..431accae 100644 --- a/src/app/hashlists/new-hashlist/new-hashlist.component.html +++ b/src/app/hashlists/new-hashlist/new-hashlist.component.html @@ -149,7 +149,10 @@
Hashcat Brain Enabled
 

- - - - - - - - - - - - -
IDUsername
- {{ g.id }} - - {{ g.name }} -
- + + +
+ + + + + + + + + + + + + +
IDUsername
+ {{ g.id }} + + {{ g.name }} +
+
+
+
@@ -811,5 +813,6 @@
Access Groups
+
diff --git a/src/app/users/globalpermissionsgroups/edit-globalpermissionsgroups/edit-globalpermissionsgroups.component.ts b/src/app/users/globalpermissionsgroups/edit-globalpermissionsgroups/edit-globalpermissionsgroups.component.ts index 4102a016..6e4a25d5 100644 --- a/src/app/users/globalpermissionsgroups/edit-globalpermissionsgroups/edit-globalpermissionsgroups.component.ts +++ b/src/app/users/globalpermissionsgroups/edit-globalpermissionsgroups/edit-globalpermissionsgroups.component.ts @@ -1,41 +1,54 @@ import { ActivatedRoute, Params, Router } from '@angular/router'; -import { Component, OnInit, ViewChild } from '@angular/core'; +import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core'; import { faEye } from '@fortawesome/free-solid-svg-icons'; import { FormControl, FormGroup } from '@angular/forms'; import { DataTableDirective } from 'angular-datatables'; -import { Subject } from 'rxjs'; +import { Subject, Subscription } from 'rxjs'; +import { AutoTitleService } from 'src/app/core/_services/shared/autotitle.service'; import { AlertService } from 'src/app/core/_services/shared/alert.service'; import { GlobalService } from 'src/app/core/_services/main.service'; -import { PageTitle } from 'src/app/core/_decorators/autotitle'; import { SERV } from '../../../core/_services/main.config'; @Component({ selector: 'app-edit-globalpermissionsgroups', templateUrl: './edit-globalpermissionsgroups.component.html' }) -@PageTitle(['Edit Global Permissions']) -export class EditGlobalpermissionsgroupsComponent implements OnInit { +/** + * EditGlobalpermissionsgroupsComponent is a component that manages and edit Global Permissions data. + * +*/ +export class EditGlobalpermissionsgroupsComponent implements OnInit, OnDestroy { + + // Font Awesome icons + faEye=faEye; + + // ViewChild reference to the DataTableDirective + @ViewChild(DataTableDirective) + dtElement: DataTableDirective; + + dtTrigger: Subject = new Subject(); + dtOptions: any = {}; + + // Subscriptions to unsubscribe on component destruction + subscriptions: Subscription[] = [] + + // Filters and forms editMode = false; editedGPGIndex: number; + updateForm: FormGroup; editedGPG: any // Change to Model active= 1; - faEye=faEye; - constructor( + private titleService: AutoTitleService, private route:ActivatedRoute, private alert: AlertService, private gs: GlobalService, private router: Router - ) { } - - @ViewChild(DataTableDirective) - dtElement: DataTableDirective; - - dtTrigger: Subject = new Subject(); - dtOptions: any = {}; - updateForm: FormGroup; + ) { + titleService.set(['Edit Global Permissions']) + } ngOnInit(): void { @@ -136,14 +149,26 @@ export class EditGlobalpermissionsgroupsComponent implements OnInit { } + /** + * Unsubscribes from active subscriptions. + */ + ngOnDestroy(): void { + for (const sub of this.subscriptions) { + sub.unsubscribe(); + } + } + + /** + * OnSubmit save changes + */ onSubmit(){ if (this.updateForm.valid) { - this.gs.update(SERV.ACCESS_PERMISSIONS_GROUPS,this.editedGPGIndex, this.updateForm.value).subscribe(() => { + this.subscriptions.push(this.gs.update(SERV.ACCESS_PERMISSIONS_GROUPS,this.editedGPGIndex, this.updateForm.value).subscribe(() => { this.alert.okAlert('Global Permission Group saved!',''); this.updateForm.reset(); this.router.navigate(['/users/global-permissions-groups']); } - ); + )); } } From 2fe9e5d6daffb7f624f9c447b9d7c100f36bfc90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Wed, 25 Oct 2023 14:24:08 +0100 Subject: [PATCH 106/419] File upload page, upload/download title explaining what it does --- src/app/files/new-files/new-files.component.html | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/app/files/new-files/new-files.component.html b/src/app/files/new-files/new-files.component.html index 611715a1..502ef29a 100644 --- a/src/app/files/new-files/new-files.component.html +++ b/src/app/files/new-files/new-files.component.html @@ -4,13 +4,14 @@
    -
  • +
+

Upload from your computer

@@ -53,7 +54,7 @@

Upload completed!

-
  +
@@ -65,6 +66,7 @@
+

Download via URL

From 0ac6533305391b2833fca6d83659836385738488 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Wed, 25 Oct 2023 17:19:55 +0100 Subject: [PATCH 107/419] Cracks page, table button to truncate hash --- .../show-cracks/show-cracks.component.html | 4 +- .../show-cracks/show-cracks.component.ts | 122 ++++++++++++------ src/app/shared/components.module.ts | 7 +- .../table/button-truncate-text.component.ts | 59 +++++++++ 4 files changed, 150 insertions(+), 42 deletions(-) create mode 100644 src/app/shared/table/button-truncate-text.component.ts diff --git a/src/app/hashlists/show-cracks/show-cracks.component.html b/src/app/hashlists/show-cracks/show-cracks.component.html index d2e2528a..c32230f1 100644 --- a/src/app/hashlists/show-cracks/show-cracks.component.html +++ b/src/app/hashlists/show-cracks/show-cracks.component.html @@ -19,7 +19,9 @@
{{ hash.timeCracked | uiDate }} {{ hash.plaintext }}{{ hash.hash }} + + {{ hash.hashlist.name | shortenString:30 }}
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + {{tableColumn.name}} + + {{tableColumn.name}} + +
+ + + + {{ icon.name }} + + {{ icon.name }} + + + + + + + + + + + + + + + {{ element[tableColumn.dataKey] }} + + + + + +
+
+ + +
+
+ + + {{ element[tableColumn.dataKey] }} + +
+
+
+ + + +
+ +
+ \ No newline at end of file diff --git a/src/app/core/_components/ht-table/ht-table.component.ts b/src/app/core/_components/ht-table/ht-table.component.ts new file mode 100644 index 00000000..4ada3d17 --- /dev/null +++ b/src/app/core/_components/ht-table/ht-table.component.ts @@ -0,0 +1,262 @@ +/* eslint-disable @angular-eslint/component-selector */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { ChangeDetectionStrategy, AfterViewInit, Component, EventEmitter, Input, OnInit, Output, ViewChild, ChangeDetectorRef } from '@angular/core'; +import { MatSort } from '@angular/material/sort'; +import { MatPaginator, PageEvent } from '@angular/material/paginator'; +import { DataType, HTTableColumn } from './ht-table.models'; +import { BaseDataSource } from '../../_datasources/base.datasource'; +import { MatDialog } from '@angular/material/dialog'; +import { ColumnSelectionDialogComponent } from '../column-selection-dialog/column-selection-dialog.component'; +import { LocalStorageService } from '../../_services/storage/local-storage.service'; +import { UIConfig } from '../../_models/config-ui.model'; +import { UISettingsUtilityClass } from 'src/app/shared/utils/config'; +import { ActionMenuEvent } from '../menus/action-menu/action-menu.model'; + +/** + * The `HTTableComponent` is a custom table component that allows you to display tabular data with + * features like sorting, filtering, and row selection. It provides flexibility in selecting columns + * to display and handling various table actions. + * + * Usage: + * ``` + * + * + * + * ``` + * + * - `[name]`: The internal name of the table used when storing user customizations. + * - `[dataSource]`: An instance of a data source that extends `BaseDataSource`. + * - `[isPageable]`: Set to `true` to enable pagination. + * - `[isSortable]`: Set to `true` to enable column sorting. + * - `[isSelectable]`: Set to `true` to enable row selection with checkboxes. + * - `[isFilterable]`: Set to `true` to enable filtering. + * - `[tableColumns]`: An array of `HTTableColumn` configurations for defining columns. + * - `[hasRowAction]`: Set to `true` to enable custom row actions. + * - `[paginationSizes]`: An array of available page sizes. + * - `[defaultPageSize]`: The default page size for pagination. + * - `[filterFn]`: A custom filter function for advanced filtering. + * - `(rowActionClicked)`: Emits an `ActionMenuEvent` when a row action is triggered. + * - `(bulkActionClicked)`: Emits an `ActionMenuEvent` when a bulk action is triggered. + * + * @see `BaseDataSource` for creating a data source. + * @see `HTTableColumn` for defining column configurations. + * + */ +@Component({ + selector: 'ht-table', + templateUrl: './ht-table.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class HTTableComponent implements OnInit, AfterViewInit { + + /** The list of column names to be displayed in the table. */ + displayedColumns: string[]; + + /** The list of all available column names. */ + columnNames: string[]; + + /** Reference to MatPaginator for pagination support. */ + @ViewChild(MatPaginator, { static: false }) matPaginator: MatPaginator; + + /** Reference to MatSort for sorting support. */ + @ViewChild(MatSort, { static: true }) matSort: MatSort; + + /** Name of the table, used when storing user customizations */ + @Input() name: string + + /** Data type displayed in the table, used to load relevant context menus */ + @Input() dataType: DataType + + /** Function used to filter table data. */ + @Input() filterFn: (item: any, filterValue: string) => boolean + + /** The data source for the table. */ + @Input() dataSource: BaseDataSource; + + /** Flag to enable or disable pagination. */ + @Input() isPageable = false; + + /** Flag to enable or disable sorting. */ + @Input() isSortable = false; + + /** Flag to enable or disable selectable rows. */ + @Input() isSelectable = false; + + /** Flag to enable or disable filtering. */ + @Input() isFilterable = false; + + /** The list of table columns and their configurations. */ + @Input() tableColumns: HTTableColumn[] = []; + + /** Flag to enable row action menu */ + @Input() hasRowAction = false; + + /** Pagination sizes to choose from. */ + @Input() paginationSizes: number[] = [3, 25, 50, 100, 250]; + + /** Default page size for pagination. */ + @Input() defaultPageSize = this.paginationSizes[1]; + + /** Event emitter for when the user triggers a row action */ + @Output() rowActionClicked: EventEmitter> = new EventEmitter>(); + + /** Event emitter for when the user triggers a bulk action */ + @Output() bulkActionClicked: EventEmitter> = new EventEmitter>(); + + /** Fetches user customizations */ + private uiSettings: UISettingsUtilityClass + + constructor(public dialog: MatDialog, private cd: ChangeDetectorRef, private storage: LocalStorageService) { } + + ngOnInit(): void { + this.uiSettings = new UISettingsUtilityClass(this.storage) + this.columnNames = this.tableColumns.map((tableColumn: HTTableColumn) => tableColumn.name); + const displayedColumns = this.uiSettings.getTableSettings(this.name) + this.setDisplayedColumns(displayedColumns) + } + + ngAfterViewInit(): void { + // Configure paginator and sorting + this.dataSource.paginator = this.matPaginator; + this.dataSource.sort = this.matSort; + this.dataSource.pageSize = this.defaultPageSize; + this.matSort.sortChange.subscribe(() => { + this.dataSource.sortData(); + }); + } + + /** + * Opens a dialog for selecting table columns to display. + */ + openColumnSelectionDialog(): void { + const dialogRef = this.dialog.open(ColumnSelectionDialogComponent, { + width: '400px', + data: { + availableColumns: this.columnNames, + selectedColumns: this.displayedColumns + } + }); + + dialogRef.afterClosed().subscribe((selectedColumns: string[]) => { + if (selectedColumns) { + this.setDisplayedColumns(selectedColumns) + this.uiSettings.updateTableSettings(this.name, selectedColumns) + this.cd.detectChanges() + } + }); + } + + rowAction(event: ActionMenuEvent): void { + this.rowActionClicked.emit(event) + } + + bulkAction(event: ActionMenuEvent): void { + this.bulkActionClicked.emit(event) + } + + /** + * Sets the displayed columns based on user selection. + * + * @param columnNames - The list of column names to display. + */ + setDisplayedColumns(columnNames: string[]): void { + if (this.hasRowAction) { + // Add action menu if enabled + this.displayedColumns = [...columnNames, 'rowAction']; + } else { + this.displayedColumns = columnNames; + } + + if (this.isSelectable) { + // Add checkbox if enabled + this.displayedColumns = ['select', ...this.displayedColumns]; + } + } + + /** + * Applies a filter to the table based on user input. + */ + applyFilter() { + if (this.filterFn) { + this.dataSource.filterData(this.filterFn) + } + } + + /** + * Checks if a row is selected. + * + * @param row - The row to check. + */ + isSelected(row: any): boolean { + return this.dataSource.isSelected(row); + } + + /** + * Checks if all rows are selected. + */ + isAllSelected(): boolean { + return this.dataSource.isAllSelected(); + } + + /** + * Toggles selection for all rows. + */ + toggleAll(): void { + this.dataSource.toggleAll(); + } + + /** + * Checks if there are selected rows. + */ + hasSelected(): boolean { + return this.dataSource.hasSelected() + } + + /** + * Checks if the selection state is indeterminate. + */ + indeterminate(): boolean { + return this.dataSource.indeterminate(); + } + + /** + * Toggles selection for a specific row. + * + * @param row - The row to toggle. + */ + toggleSelect(row: any): void { + if (this.isSelectable) { + this.dataSource.toggleRow(row); + } + } + + /** + * Reloads the data in the table. + */ + reload(): void { + this.dataSource.reload() + } + + /** + * Handles the page change event, including changes in page size and page index. + * + * @param event - The `PageEvent` object containing information about the new page configuration. + */ + onPageChange(event: PageEvent): void { + this.dataSource.setPaginationConfig(event.pageSize, event.pageIndex, this.dataSource.totalItems); + this.dataSource.reload(); + } +} \ No newline at end of file diff --git a/src/app/core/_components/ht-table/ht-table.models.ts b/src/app/core/_components/ht-table/ht-table.models.ts new file mode 100644 index 00000000..749437a5 --- /dev/null +++ b/src/app/core/_components/ht-table/ht-table.models.ts @@ -0,0 +1,20 @@ +import { SafeHtml } from "@angular/platform-browser" + +export type DataType = 'agents' | 'tasks' | 'chunks' + +export interface HTTableIcon { + name: string + tooltip?: string + cls?: string +} + +export interface HTTableColumn { + name: string + dataKey: string + position?: 'right' | 'left' + isSortable?: boolean + icons?: (data: any) => Promise + render?: (data: any) => SafeHtml + async?: (data: any) => Promise + routerLink?: (data: any) => any[], +} \ No newline at end of file From de1ea552773fa8d015047e991d55a8341847b903 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Mon, 30 Oct 2023 22:09:13 +0100 Subject: [PATCH 120/419] Base table with common functionality used by table implementations --- .../base-table/base-table.component.ts | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 src/app/core/_components/base-table/base-table.component.ts diff --git a/src/app/core/_components/base-table/base-table.component.ts b/src/app/core/_components/base-table/base-table.component.ts new file mode 100644 index 00000000..b43344f0 --- /dev/null +++ b/src/app/core/_components/base-table/base-table.component.ts @@ -0,0 +1,58 @@ +/* eslint-disable @angular-eslint/component-selector */ +import { Component, Renderer2 } from '@angular/core'; +import { GlobalService } from '../../_services/main.service'; +import { Router } from '@angular/router'; +import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; +import { MatSnackBar } from '@angular/material/snack-bar'; +import { MatDialog } from '@angular/material/dialog'; +import { UISettingsUtilityClass } from 'src/app/shared/utils/config'; +import { LocalStorageService } from '../../_services/storage/local-storage.service'; +import { UIConfig, uiConfigDefault } from '../../_models/config-ui.model'; +import { UIConfigService } from '../../_services/shared/storage.service'; +import { Subscription } from 'rxjs'; + +@Component({ + selector: 'base-table', + template: '' +}) +export class BaseTableComponent { + + protected uiSettings: UISettingsUtilityClass + protected dateFormat: string + protected subscriptions: Subscription[] = [] + + constructor( + protected gs: GlobalService, + protected renderer: Renderer2, + protected router: Router, + protected settingsService: LocalStorageService, + protected sanitizer: DomSanitizer, + protected snackBar: MatSnackBar, + protected uiService: UIConfigService, + public dialog: MatDialog, + ) { + this.uiSettings = new UISettingsUtilityClass(settingsService) + this.dateFormat = this.getDateFormat() + } + + /** + * Retrieves the date format for rendering timestamps. + * @todo Change to localstorage + * @returns The date format string. + */ + private getDateFormat(): string { + const fmt = this.uiSettings.getSetting('timefmt') + + return fmt ? fmt : uiConfigDefault.timefmt + } + + /** + * Sanitizes the given HTML string to create a safe HTML value. + * @param html - The HTML string to be sanitized. + * @returns A SafeHtml object that represents the sanitized HTML. + */ + protected sanitize(html: string): SafeHtml { + return this.sanitizer.bypassSecurityTrustHtml(html) + } + +} \ No newline at end of file From 5cb70bc2c158644a647f1955d3ccf30ad671a2c6 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Mon, 30 Oct 2023 22:09:55 +0100 Subject: [PATCH 121/419] New base datasource with common functionality --- src/app/core/_datasources/base.datasource.ts | 276 +++++++++++++++++++ 1 file changed, 276 insertions(+) create mode 100644 src/app/core/_datasources/base.datasource.ts diff --git a/src/app/core/_datasources/base.datasource.ts b/src/app/core/_datasources/base.datasource.ts new file mode 100644 index 00000000..f659c341 --- /dev/null +++ b/src/app/core/_datasources/base.datasource.ts @@ -0,0 +1,276 @@ +import { CollectionViewer, DataSource } from "@angular/cdk/collections"; +import { BehaviorSubject, Observable } from "rxjs"; +import { GlobalService } from "../_services/main.service"; +import { MatTableDataSourcePaginator } from "@angular/material/table"; +import { HTTableColumn } from "../_components/ht-table/ht-table.models"; +import { MatSort } from "@angular/material/sort"; +import { SelectionModel } from '@angular/cdk/collections'; +import { UIConfigService } from "../_services/shared/storage.service"; + +/** + * BaseDataSource is an abstract class for implementing data sources + * for Angular Material tables. It provides common functionality for + * data loading, sorting, filtering, and row selection. + * + * @template T - The type of data that the data source holds. + * @template P - The type of paginator, extending MatTableDataSourcePaginator. + */ +export abstract class BaseDataSource implements DataSource { + + + public pageSize = 10; + public currentPage = 0; + public totalItems = 0; + + /** + * Copy of the original dataSubject data used for filtering + */ + private originalData: T[] = []; + + /** + * BehaviorSubject to track the loading state. + */ + protected loadingSubject = new BehaviorSubject(false); + + /** + * BehaviorSubject to track the data for the table. + */ + protected dataSubject = new BehaviorSubject([]); + + /** + * An array of table columns. + */ + protected columns: HTTableColumn[] = []; + + /** + * Selection model for row selection in the table. + */ + public selection = new SelectionModel(true, []); + + /** + * Observable to track the loading state. + */ + public loading$ = this.loadingSubject.asObservable(); + + /** + * Reference to the paginator, if pagination is enabled. + */ + public paginator: P | null + + /** + * The filter string to be applied to the table data. + */ + public filter: string + + /** + * Reference to MatSort for sorting support. + */ + public sort: MatSort + + constructor(protected service: GlobalService, protected uiService: UIConfigService) { + } + + /** + * Connect the data source to a collection viewer. + * + * @param _collectionViewer - The collection viewer to connect. + * @returns Observable of the data source. + */ + // eslint-disable-next-line @typescript-eslint/no-unused-vars + connect(_collectionViewer: CollectionViewer): Observable { + return this.dataSubject.asObservable(); + } + + /** + * Disconnect the data source from a collection viewer. + * + * @param _collectionViewer - The collection viewer to disconnect. + */ + // eslint-disable-next-line @typescript-eslint/no-unused-vars + disconnect(_collectionViewer: CollectionViewer): void { + this.dataSubject.complete(); + this.loadingSubject.complete(); + } + + /** + * Sets the data for the table. + * + * @param data - The data to be set in the data source. + */ + setData(data: T[]): void { + this.originalData = data; + this.dataSubject.next(data); + } + + /** + * Sets the columns for the table. + * + * @param columns - An array of HTTableColumn for defining table columns. + */ + setColumns(columns: HTTableColumn[]): void { + this.columns = columns; + } + + /** + * Sorts the data based on the sort configuration. + */ + sortData(): void { + if (!this.sort || !this.sort.active || this.sort.direction === '') { + return; + } + const sortDirection = this.sort.direction + const data = this.dataSubject.value.slice(); + const columnMapping = this.columns.find(mapping => mapping.name === this.sort.active); + + if (!columnMapping) { + console.error('Column mapping not found for label: ' + this.sort.active); + return; + } + + const property = columnMapping.dataKey; + const isAsc = sortDirection === 'asc'; + + const sortedData = data.sort((a, b) => { + return this.compare(a[property], b[property], isAsc); + }) + + this.dataSubject.next(sortedData); + } + + /** + * Filters the data based on a filter function. + * + * @param filterFn - A function to filter the data based on filterValue. + */ + filterData(filterFn: (item: T, filterValue: string) => boolean): void { + const filterValue = this.filter.trim().toLowerCase(); + if (!filterValue) { + this.dataSubject.next(this.originalData); + } else { + const filteredData = this.originalData.filter((item) => filterFn(item, filterValue)); + + this.dataSubject.next(filteredData); + } + } + + /** + * Compare function used for sorting. + */ + private compare(a: number | string, b: number | string, isAsc: boolean): number { + return (a < b ? -1 : 1) * (isAsc ? 1 : -1); + } + + /** + * Toggle all rows' selection. + */ + toggleAll() { + if (this.isAllSelected()) { + this.selection.clear(); + } else { + this.dataSubject.value.forEach((row) => { + this.selection.select(row); + }); + } + } + + /** + * Checks whether all rows are selected. + * + * @returns True if all rows are selected + */ + isAllSelected(): boolean { + const numSelected = this.selection.selected.length; + const numRows = this.dataSubject.value.length; + + return !!(numSelected > 0 && numSelected === numRows); + } + + // Select all rows. + selectAll(): void { + this.dataSubject.value.forEach((row) => this.selection.select(row)); + } + + // Select a specific row. + selectRow(row: T): void { + this.selection.select(row); + } + + // Deselect a specific row. + deselectRow(row: T): void { + this.selection.deselect(row); + } + + // Checks if a row is selected. + isSelected(row: T): boolean { + return this.selection.isSelected(row); + } + + // Toggle selection for a specific row. + toggleRow(row: T): void { + if (this.selection.isSelected(row)) { + this.selection.deselect(row); + } else { + this.selection.select(row); + } + } + + /** + * Checks whether the selection model is in an indeterminate state. + * + * @returns True if the selection is in an indeterminate state; otherwise, false. + */ + indeterminate() { + return !!(this.selection.hasValue() && !this.isAllSelected()) + } + + /** + * Checks whether at least one row is selected in the table. + * + * @returns True if at least one row is selected; otherwise, false. + */ + hasSelected(): boolean { + return this.selection.hasValue() + } + + /** + * Sets the pagination configuration for the data source, including page size, current page, and total items. + * + * @param pageSize - The number of items to display per page. + * @param currentPage - The index of the current page. + * @param totalItems - The total number of items in the data source. + */ + setPaginationConfig(pageSize: number, currentPage: number, totalItems: number): void { + this.pageSize = pageSize; + this.currentPage = currentPage; + this.totalItems = totalItems; + } + + /** + * Resets the data source by clearing filters, deselecting all rows, and returning to page 1 (if using pagination). + */ + reset(): void { + // Clear any applied filters + this.filter = ''; + this.dataSubject.next(this.originalData); + + // Deselect all selected rows + this.selection.clear(); + + // Return to page 1 if using pagination + if (this.paginator) { + this.paginator.firstPage(); + } + } + + getFirstRow(): T | undefined { + const data = this.dataSubject.value; + + if (data.length > 0) { + return data[0]; + } else { + return undefined; + } + } + + abstract reload(): void +} \ No newline at end of file From 5104df2f9a9e3f29ca65df1b5faadc6d1ba5b7ff Mon Sep 17 00:00:00 2001 From: itbf9 Date: Tue, 31 Oct 2023 07:44:42 +0100 Subject: [PATCH 122/419] Update ui config model and create helper --- src/app/core/_models/config-ui.model.ts | 22 ++++-- src/app/shared/utils/config.ts | 92 +++++++++++++++++++++++++ 2 files changed, 109 insertions(+), 5 deletions(-) create mode 100644 src/app/shared/utils/config.ts diff --git a/src/app/core/_models/config-ui.model.ts b/src/app/core/_models/config-ui.model.ts index 551a2f2c..7a6463b6 100644 --- a/src/app/core/_models/config-ui.model.ts +++ b/src/app/core/_models/config-ui.model.ts @@ -8,14 +8,26 @@ export class IStorage { private _statimer: string, private _timestamp: number, private _expiresin: number, - ) {} - + ) { } } -export class UIGConfig { - constructor( +export type Layout = 'full' | 'fixed' +export type Theme = 'light' | 'dark' - ) {} +export interface TableSettings { + [key: string]: string[] +} +export interface UIConfig { + layout: Layout + theme: Theme + tableSettings: TableSettings, + timefmt: string, } +export const uiConfigDefault: UIConfig = { + layout: 'fixed', + theme: 'light', + timefmt: 'dd/MM/yyyy h:mm:ss', + tableSettings: {} +} diff --git a/src/app/shared/utils/config.ts b/src/app/shared/utils/config.ts new file mode 100644 index 00000000..e42bbcc6 --- /dev/null +++ b/src/app/shared/utils/config.ts @@ -0,0 +1,92 @@ +import { UIConfig, uiConfigDefault } from "src/app/core/_models/config-ui.model" +import { LocalStorageService } from "src/app/core/_services/storage/local-storage.service" + +/** + * Utility class for managing user interface settings and configurations. + */ +export class UISettingsUtilityClass { + + /** The key used for storing UI configuration in local storage. */ + static readonly KEY = 'ui-config' + + /** The UI configuration object. */ + uiConfig: UIConfig + + /** + * Creates an instance of the UISettingsUtilityClass. + * + * @param storage - The LocalStorageService used for managing UI configuration storage. + */ + constructor(private storage: LocalStorageService) { + this.uiConfig = storage.getItem(UISettingsUtilityClass.KEY) + if (!this.uiConfig) { + this.uiConfig = uiConfigDefault + } + } + + /** + * Updates the table settings for a specific key in the UI configuration. + * + * @param key - The key for the table settings. + * @param columns - An array of column names to set as table settings for the key. + */ + updateTableSettings(key: string, columns: string[]): void { + this.uiConfig.tableSettings[key] = columns + this.storage.setItem(UISettingsUtilityClass.KEY, this.uiConfig, 0) + } + + /** + * Retrieves the table settings for a specific key from the UI configuration. + * + * @param key - The key for the table settings. + * @returns An array of column names as table settings for the key. + */ + getTableSettings(key: string): string[] { + try { + return this.uiConfig.tableSettings[key] + } catch (error) { + console.log(error) + return [] + } + } + + /** + * Retrieves a specific user interface setting from the UI configuration. + * + * @param key - The key for the UI setting. + * @returns The value of the UI setting, or undefined if not found. + */ + getSetting(key: string): T | undefined { + try { + return this.uiConfig[key] + } catch (error) { + console.log(error) + return undefined + } + } + + /** + * Updates multiple user interface settings in the UI configuration. + * + * @param settings - An object containing key-value pairs of settings to update. + * @returns The number of settings that were successfully changed. + */ + updateSettings(settings: { [key: string]: any }): number { + const keys = Object.keys(settings); + let changedValues = 0 + + for (const key of keys) { + if (key in this.uiConfig && this.uiConfig[key] !== settings[key]) { + this.uiConfig[key] = settings[key] + changedValues += 1 + } + } + + if (changedValues > 0) { + this.storage.setItem(UISettingsUtilityClass.KEY, this.uiConfig, 0) + } + + return changedValues + } + +} \ No newline at end of file From 676cca3eea6a2100364b75618f4e1c9e2b075b61 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Tue, 31 Oct 2023 07:45:21 +0100 Subject: [PATCH 123/419] Register new components + load deps --- .../_components/core-components.module.ts | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 src/app/core/_components/core-components.module.ts diff --git a/src/app/core/_components/core-components.module.ts b/src/app/core/_components/core-components.module.ts new file mode 100644 index 00000000..fbfae98e --- /dev/null +++ b/src/app/core/_components/core-components.module.ts @@ -0,0 +1,78 @@ +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; +import { ReactiveFormsModule, FormsModule } from "@angular/forms"; +import { RouterModule } from "@angular/router"; +import { HTTableComponent } from "./ht-table/ht-table.component"; +import { ActionMenuComponent } from "./menus/action-menu/action-menu.component"; +import { BaseMenuComponent } from "./menus/base-menu/base-menu.component"; +import { BulkActionMenuComponent } from "./menus/bulk-action-menu/bulk-action-menu.component"; +import { RowActionMenuComponent } from "./menus/row-action-menu/row-action-menu.component"; +import { TableDialogComponent } from "./table-dialog/table-dialog.component"; +import { BaseTableComponent } from "./base-table/base-table.component"; +import { ColumnSelectionDialogComponent } from "./column-selection-dialog/column-selection-dialog.component"; +import { MatButtonModule } from "@angular/material/button"; +import { MatDialogModule } from "@angular/material/dialog"; +import { MatFormFieldModule } from "@angular/material/form-field"; +import { MatPaginatorModule } from "@angular/material/paginator"; +import { MatSelectModule } from "@angular/material/select"; +import { MatSnackBarModule, MAT_SNACK_BAR_DEFAULT_OPTIONS } from "@angular/material/snack-bar"; +import { MatSortModule } from "@angular/material/sort"; +import { MatTableModule } from "@angular/material/table"; +import { MatTooltipModule } from "@angular/material/tooltip"; +import { MatInputModule } from '@angular/material/input'; +import { MatCheckboxModule } from '@angular/material/checkbox'; +import { MatIconModule } from '@angular/material/icon'; +import { MatMenuModule } from '@angular/material/menu'; +import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; +import { MatListModule } from '@angular/material/list'; +import { MatDividerModule } from '@angular/material/divider'; + + +@NgModule({ + declarations: [ + TableDialogComponent, + BaseTableComponent, + HTTableComponent, + BaseMenuComponent, + ActionMenuComponent, + RowActionMenuComponent, + BulkActionMenuComponent, + ColumnSelectionDialogComponent, + ], + imports: [ + ReactiveFormsModule, + CommonModule, + MatTableModule, + MatFormFieldModule, + MatInputModule, + MatPaginatorModule, + MatSortModule, + MatCheckboxModule, + MatSnackBarModule, + MatIconModule, + MatSelectModule, + MatButtonModule, + MatTableModule, + MatMenuModule, + MatProgressSpinnerModule, + MatListModule, + MatDialogModule, + MatTooltipModule, + MatDividerModule, + RouterModule, + FormsModule, + ], + exports: [ + BaseTableComponent, + HTTableComponent, + ColumnSelectionDialogComponent, + BaseMenuComponent, + ActionMenuComponent, + RowActionMenuComponent, + BulkActionMenuComponent, + ], + providers: [ + { provide: MAT_SNACK_BAR_DEFAULT_OPTIONS, useValue: { duration: 2500 } } + ] +}) +export class CoreComponentsModule { } \ No newline at end of file From a6c9b2c0f272d9f337a0e003c0e3490762e778bf Mon Sep 17 00:00:00 2001 From: itbf9 Date: Tue, 31 Oct 2023 10:49:59 +0100 Subject: [PATCH 124/419] Add access group interface --- src/app/core/_models/access-group.model.ts | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 src/app/core/_models/access-group.model.ts diff --git a/src/app/core/_models/access-group.model.ts b/src/app/core/_models/access-group.model.ts new file mode 100644 index 00000000..26c07e52 --- /dev/null +++ b/src/app/core/_models/access-group.model.ts @@ -0,0 +1,4 @@ +export interface AccessGroup { + accessGroupId: number + groupName: string +} From dd434335216626bbb4c5e3941e69027efe51e420 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Tue, 31 Oct 2023 10:50:50 +0100 Subject: [PATCH 125/419] remove old access group interface --- src/app/core/_models/access-group.ts | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 src/app/core/_models/access-group.ts diff --git a/src/app/core/_models/access-group.ts b/src/app/core/_models/access-group.ts deleted file mode 100644 index eae9a6fd..00000000 --- a/src/app/core/_models/access-group.ts +++ /dev/null @@ -1,4 +0,0 @@ -export interface AccessGroup { - accessGroupId: number; - groupName: string; -} From 0d92c78cbe5c999ac49c2ee947604dc51aba85a3 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Tue, 31 Oct 2023 10:52:20 +0100 Subject: [PATCH 126/419] Rename and update agents interface --- src/app/core/_models/agent.model.ts | 67 +++++++++++++++++++++++++++++ src/app/core/_models/agents.ts | 25 ----------- 2 files changed, 67 insertions(+), 25 deletions(-) create mode 100644 src/app/core/_models/agent.model.ts delete mode 100644 src/app/core/_models/agents.ts diff --git a/src/app/core/_models/agent.model.ts b/src/app/core/_models/agent.model.ts new file mode 100644 index 00000000..bb48b372 --- /dev/null +++ b/src/app/core/_models/agent.model.ts @@ -0,0 +1,67 @@ +import { AccessGroup } from './access-group.model' +import { User } from './user.model' +import { Task } from './task.model' +import { Chunk } from './chunk.model' + +export interface AgentStats { + _id: number + _self: string + agentStatId: number + agentId: number + statType: number + time: number + value: number[] +} + +export interface Agent { + _id?: number + _self?: string + agentId: number + agentName: string + uid: string + os: number + devices: string + cmdPars: string + ignoreErrors: number + isActive: boolean + isTrusted: boolean + token: string + lastAct: string + lastTime: number + lastIp: string + userId: number + user?: User + cpuOnly: number + clientSignature: string + agentstats?: AgentStats[] + accessGroups?: AccessGroup[] + taskId?: number + task?: Task + chunk?: Chunk +} + +export interface IAgents { + _expandable?: string + startAt: number + maxResults: number + total: number + isLast: string + values: [{ + agentId: number + agentName: string + uid: string + os: number + devices: string + cmdPars: string + ignoreErrors: string + isActive: string + isTrusted: string + token: string + lastAct: string + lastTime: number + lastIp: string + userId: number + cpuOnly: number + clientSignature: string + }] +} diff --git a/src/app/core/_models/agents.ts b/src/app/core/_models/agents.ts deleted file mode 100644 index 2344acd4..00000000 --- a/src/app/core/_models/agents.ts +++ /dev/null @@ -1,25 +0,0 @@ -export interface IAgents { - _expandable?: string; - startAt: number; - maxResults: number; - total: number; - isLast: string; - values: [{ - agentId: number; - agentName: string; - uid: string; - os: number; - devices: string; - cmdPars: string; - ignoreErrors: string; - isActive: string; - isTrusted: string; - token: string; - lastAct: string; - lastTime: number; - lastIp: string; - userId: number; - cpuOnly: number; - clientSignature: string; - }] -} From 2136a4dbfa69cd0368cb886f74179eeb32ae307d Mon Sep 17 00:00:00 2001 From: itbf9 Date: Tue, 31 Oct 2023 10:53:36 +0100 Subject: [PATCH 127/419] Rename and update notification interface --- .../edit-notification.component.ts | 4 +- .../notifications.component.spec.ts | 2 +- .../notifications/notifications.component.ts | 38 +++++++++---------- src/app/core/_models/notification.model.ts | 20 ++++++++++ src/app/core/_models/notifications.ts | 20 ---------- 5 files changed, 42 insertions(+), 42 deletions(-) create mode 100644 src/app/core/_models/notification.model.ts delete mode 100644 src/app/core/_models/notifications.ts diff --git a/src/app/account/notifications/notification/edit-notification.component.ts b/src/app/account/notifications/notification/edit-notification.component.ts index 2b63fdc8..4238f91f 100644 --- a/src/app/account/notifications/notification/edit-notification.component.ts +++ b/src/app/account/notifications/notification/edit-notification.component.ts @@ -7,7 +7,7 @@ import { AutoTitleService } from 'src/app/core/_services/shared/autotitle.servic import { AlertService } from 'src/app/core/_services/shared/alert.service'; import { GlobalService } from 'src/app/core/_services/main.service'; import { ActivatedRoute, Params, Router } from '@angular/router'; -import { Notification } from 'src/app/core/_models/notifications'; +import { Notification } from 'src/app/core/_models/notification.model'; import { SERV } from '../../../core/_services/main.config'; import { Filter } from '../notifications.component'; @@ -106,7 +106,7 @@ export class EditNotificationComponent implements OnInit, OnDestroy { onSubmit(): void { if (this.form.valid) { this.subscriptions.push(this.gs.update(SERV.NOTIFICATIONS, this.editedIndex, { 'isActive': this.form.value['isActive'] }).subscribe(() => { - this.alert.okAlert('Notification saved!',''); + this.alert.okAlert('Notification saved!', ''); this.router.navigate(['/account/notifications']); })); } diff --git a/src/app/account/notifications/notifications.component.spec.ts b/src/app/account/notifications/notifications.component.spec.ts index e8a521e1..1df17c25 100644 --- a/src/app/account/notifications/notifications.component.spec.ts +++ b/src/app/account/notifications/notifications.component.spec.ts @@ -6,7 +6,7 @@ import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; import { GlobalService } from 'src/app/core/_services/main.service'; import { Observable, of } from 'rxjs'; import { SERV } from '../../core/_services/main.config'; -import { NotificationListResponse, Notification } from 'src/app/core/_models/notifications'; +import { NotificationListResponse, Notification } from 'src/app/core/_models/notification.model'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { RouterTestingModule } from '@angular/router/testing'; import Swal from 'sweetalert2/dist/sweetalert2.js'; diff --git a/src/app/account/notifications/notifications.component.ts b/src/app/account/notifications/notifications.component.ts index d24b748f..27a8a502 100644 --- a/src/app/account/notifications/notifications.component.ts +++ b/src/app/account/notifications/notifications.component.ts @@ -5,11 +5,11 @@ import { DataTableDirective } from 'angular-datatables'; import { Subject, Subscription } from 'rxjs'; import { AutoTitleService } from 'src/app/core/_services/shared/autotitle.service'; -import { NotificationListResponse } from 'src/app/core/_models/notifications'; +import { NotificationListResponse } from 'src/app/core/_models/notification.model'; import { AlertService } from 'src/app/core/_services/shared/alert.service'; import { ACTION } from '../../core/_constants/notifications.config'; import { GlobalService } from 'src/app/core/_services/main.service'; -import { Notification } from 'src/app/core/_models/notifications'; +import { Notification } from 'src/app/core/_models/notification.model'; import { SERV } from '../../core/_services/main.config'; export interface Filter { @@ -17,7 +17,7 @@ export interface Filter { name: string } -declare let $:any; +declare let $: any; @Component({ selector: 'app-notifications', @@ -185,14 +185,14 @@ export class NotificationsComponent implements OnInit, OnDestroy { extend: 'collection', text: 'Bulk Actions', buttons: [ - { - text: 'Delete Notification(s)', - autoClose: true, - action: function (e, dt, node, config) { - self.onDeleteBulk(); - } - } - ] + { + text: 'Delete Notification(s)', + autoClose: true, + action: function (e, dt, node, config) { + self.onDeleteBulk(); + } + } + ] }, { extend: 'pageLength', @@ -219,7 +219,7 @@ export class NotificationsComponent implements OnInit, OnDestroy { * @param {string} name - The name of the notification. */ onDelete(id: number, name: string) { - this.alert.deleteConfirmation(name,'Notifications').then((confirmed) => { + this.alert.deleteConfirmation(name, 'Notifications').then((confirmed) => { if (confirmed) { // Deletion this.subscriptions.push(this.gs.delete(SERV.NOTIFICATIONS, id).subscribe(() => { @@ -229,7 +229,7 @@ export class NotificationsComponent implements OnInit, OnDestroy { })); } else { // Handle cancellation - this.alert.okAlert(`Notification ${name} is safe!`,''); + this.alert.okAlert(`Notification ${name} is safe!`, ''); } }); } @@ -241,14 +241,14 @@ export class NotificationsComponent implements OnInit, OnDestroy { * * @returns {number[]} - An array of selected hashlist IDs. */ - onSelectedNotifications(){ + onSelectedNotifications() { $(".dt-button-background").trigger("click"); - const selection = $($(this.dtElement).DataTable.tables()).DataTable().rows({ selected: true } ).data().pluck(0).toArray(); - if(selection.length == 0) { - this.alert.okAlert('You have not selected any Notification',''); + const selection = $($(this.dtElement).DataTable.tables()).DataTable().rows({ selected: true }).data().pluck(0).toArray(); + if (selection.length == 0) { + this.alert.okAlert('You have not selected any Notification', ''); return; } - const selectionnum = selection.map(i=>Number(i)); + const selectionnum = selection.map(i => Number(i)); return selectionnum; } @@ -260,7 +260,7 @@ export class NotificationsComponent implements OnInit, OnDestroy { */ async onDeleteBulk() { const NotifIds = this.onSelectedNotifications(); - this.alert.bulkDeleteAlert(NotifIds,'Notifications',SERV.NOTIFICATIONS); + this.alert.bulkDeleteAlert(NotifIds, 'Notifications', SERV.NOTIFICATIONS); this.onRefreshTable(); } diff --git a/src/app/core/_models/notification.model.ts b/src/app/core/_models/notification.model.ts new file mode 100644 index 00000000..bef179a8 --- /dev/null +++ b/src/app/core/_models/notification.model.ts @@ -0,0 +1,20 @@ +export interface NotificationListResponse { + _expandable: string + startAt: number + maxResults: number + total: number + isLast: boolean + values: Notification[] +} + +export interface Notification { + _id: number + _self: string + action: string + isActive: boolean + notification: string + receiver: string + userId: number + notificationSettingId?: number + objectId?: number +} \ No newline at end of file diff --git a/src/app/core/_models/notifications.ts b/src/app/core/_models/notifications.ts deleted file mode 100644 index 9ae59c76..00000000 --- a/src/app/core/_models/notifications.ts +++ /dev/null @@ -1,20 +0,0 @@ -export interface NotificationListResponse { - _expandable: string, - startAt: number, - maxResults: number, - total: number, - isLast: boolean, - values: Notification[] -} - -export interface Notification { - _id: number, - _self: string, - action: string, - isActive: boolean, - notification: string, - receiver: string, - userId: number, - notificationSettingId?: number, - objectId?: number, -} \ No newline at end of file From f4034cececdfb9713cbf81120df820567f782ec1 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Tue, 31 Oct 2023 10:55:02 +0100 Subject: [PATCH 128/419] Rename and update chunk interfae --- src/app/core/_models/chunk.model.ts | 29 +++++++++++++++++++++++++++++ src/app/core/_models/chunk.ts | 16 ---------------- 2 files changed, 29 insertions(+), 16 deletions(-) create mode 100644 src/app/core/_models/chunk.model.ts delete mode 100644 src/app/core/_models/chunk.ts diff --git a/src/app/core/_models/chunk.model.ts b/src/app/core/_models/chunk.model.ts new file mode 100644 index 00000000..01ea3950 --- /dev/null +++ b/src/app/core/_models/chunk.model.ts @@ -0,0 +1,29 @@ +import { Agent } from './agent.model' + + +export interface Chunk { + _id: number + _self: string + chunkId: number + taskId: number + task?: Task + format: string + skip: number + length: number + agentId: number + agent?: Agent + dispatchTime: number + solveTime: number + checkpoint: number + progress: number + state: number + cracked: number + speed: number +} + +export interface ChunkData { + dispatched: number + searched: number + cracked: number + speed: number +} \ No newline at end of file diff --git a/src/app/core/_models/chunk.ts b/src/app/core/_models/chunk.ts deleted file mode 100644 index 86fb15ea..00000000 --- a/src/app/core/_models/chunk.ts +++ /dev/null @@ -1,16 +0,0 @@ -export interface BaseChunk { - chunkId: number, - taskId: number, - format: string, - skip: string, - length: number, - agentId: number, - dispatchTime: number, - solveTime: number, - checkpoINT: number, - progress: number, - state: number, - cracked: number, - speed: number, - } - From 59f9b340d0363a044a8ecac2b3fe597c1280303f Mon Sep 17 00:00:00 2001 From: itbf9 Date: Tue, 31 Oct 2023 10:56:27 +0100 Subject: [PATCH 129/419] New cracker binary interface --- src/app/core/_models/cracker-binary.model.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 src/app/core/_models/cracker-binary.model.ts diff --git a/src/app/core/_models/cracker-binary.model.ts b/src/app/core/_models/cracker-binary.model.ts new file mode 100644 index 00000000..d5a249f0 --- /dev/null +++ b/src/app/core/_models/cracker-binary.model.ts @@ -0,0 +1,17 @@ +export interface CrackerBinary { + _id: number + _self: string + binaryName: string + crackerBinaryId: number + crackerBinaryTypeId: number + downloadUrl: string + version: string +} + +export interface CrackerBinaryType { + crackerBinaryTypeId: number + isChunkingAvailable: boolean + typeName: string + _id: number + _self: string +} \ No newline at end of file From 9dc92ecc9ab0af3c21876d34ef0cd669f69ea775 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Tue, 31 Oct 2023 10:57:40 +0100 Subject: [PATCH 130/419] format auth user class --- src/app/core/_models/auth-user.model.ts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/app/core/_models/auth-user.model.ts b/src/app/core/_models/auth-user.model.ts index 96d19d1e..ea254306 100644 --- a/src/app/core/_models/auth-user.model.ts +++ b/src/app/core/_models/auth-user.model.ts @@ -1,14 +1,14 @@ export class User { - constructor( - private _token: string, - private _expires: Date, - private _username: string - ) {} + constructor( + private _token: string, + private _expires: Date, + private _username: string + ) { } - get token() { - if (!this._expires || new Date() > this._expires) { - return null; - } - return this._token; + get token() { + if (!this._expires || new Date() > this._expires) { + return null } + return this._token } +} From 8246b455da773bd961b078e7ee5832ccf4a99061 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Tue, 31 Oct 2023 10:58:26 +0100 Subject: [PATCH 131/419] Remove unused interface --- src/app/core/_models/config-ui.model.ts | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/src/app/core/_models/config-ui.model.ts b/src/app/core/_models/config-ui.model.ts index 551a2f2c..dc0ad8df 100644 --- a/src/app/core/_models/config-ui.model.ts +++ b/src/app/core/_models/config-ui.model.ts @@ -1,21 +1,5 @@ -export class IStorage { - constructor( - private _timefmt: string, - private _enablebrain: number, - private _halias: string, - private _bchars: string, - private _chunkt: string, - private _statimer: string, - private _timestamp: number, - private _expiresin: number, - ) {} - -} - +/** Not used, remove when merged with https://github.com/hashtopolis/web-ui/pull/22 */ export class UIGConfig { - constructor( - - ) {} } From 7302fb8c5a80a26abb4e45cb6151d233a8d5beff Mon Sep 17 00:00:00 2001 From: itbf9 Date: Tue, 31 Oct 2023 10:59:07 +0100 Subject: [PATCH 132/419] Rename and update file interface --- src/app/core/_models/file.model.ts | 26 ++++++++++++++++++++++++++ src/app/core/_models/files.ts | 27 --------------------------- 2 files changed, 26 insertions(+), 27 deletions(-) create mode 100644 src/app/core/_models/file.model.ts delete mode 100644 src/app/core/_models/files.ts diff --git a/src/app/core/_models/file.model.ts b/src/app/core/_models/file.model.ts new file mode 100644 index 00000000..abcd6b29 --- /dev/null +++ b/src/app/core/_models/file.model.ts @@ -0,0 +1,26 @@ +import { AccessGroup } from './access-group.model' + +export interface Filetype { + fileId: number + filename: string + size: number + isSecret: number + fileType: number + accessGroupId: number + lineCount: number + accessGroup: AccessGroup +} + +export interface UpdateFileType { + fileId: number + filename: string + fileType: number + accessGroupId: number +} + +export interface UploadFileTUS { + filename: string + progress: number + hash: string + uuid: string +} diff --git a/src/app/core/_models/files.ts b/src/app/core/_models/files.ts deleted file mode 100644 index a240b9b4..00000000 --- a/src/app/core/_models/files.ts +++ /dev/null @@ -1,27 +0,0 @@ -export interface Filetype { - fileId: number, - filename: string, - size: number, - isSecret: number, - fileType: number, - accessGroupId: number, - lineCount:number - accessGroup: { - accessGroupId: number, - groupName: string - } -} - -export interface UpdateFileType { - fileId: number, - filename: string, - fileType: number, - accessGroupId: number, -} - -export interface UploadFileTUS { - filename: string; - progress: number; - hash: string; - uuid: string; -} From 3090fee5887f6d3985a199e1042b004df1ba7400 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Tue, 31 Oct 2023 10:59:51 +0100 Subject: [PATCH 133/419] Rename and update hashlist interface --- src/app/core/_models/hashlist.model.ts | 49 ++++++++++++++++++++++++++ src/app/core/_models/hashlist.ts | 27 -------------- 2 files changed, 49 insertions(+), 27 deletions(-) create mode 100644 src/app/core/_models/hashlist.model.ts delete mode 100644 src/app/core/_models/hashlist.ts diff --git a/src/app/core/_models/hashlist.model.ts b/src/app/core/_models/hashlist.model.ts new file mode 100644 index 00000000..be163e7d --- /dev/null +++ b/src/app/core/_models/hashlist.model.ts @@ -0,0 +1,49 @@ +export interface BaseHashlist { + accessGroupId: number + brainFeatures: string + format: string + name: string + hashTypeId: number + isHexSalt: boolean + isSecret: boolean + isSalted: boolean + separator: string + useBrain: boolean + hashCount: number + cracked: number + notes: string + isArchived: boolean + sourceType: string + sourceData: string +} + +export type CreateHashlist = BaseHashlist + +// export interface Hashlist extends BaseHashlist { +// id: number; +// hashCount: number; +// crackedCount: number; +// notes: string; +// } + +export interface Hashlist { + _id: number + _self: string + hashlistId?: number + accessGroupId: number + brainFeatures: string + format: string + name: string + hashTypeId: number + isHexSalt: boolean + isSecret: boolean + isSalted: boolean + separator: string + useBrain: boolean + hashCount: number + cracked: number + notes: string + isArchived: boolean + sourceType: string + sourceData: string +} \ No newline at end of file diff --git a/src/app/core/_models/hashlist.ts b/src/app/core/_models/hashlist.ts deleted file mode 100644 index 67ef91bf..00000000 --- a/src/app/core/_models/hashlist.ts +++ /dev/null @@ -1,27 +0,0 @@ -export interface BaseHashlist { - accessGroupId: number, - brainFeatures: string, - format: string, - name: string, - hashTypeId: number, - isHexSalt: boolean, - isSecret: boolean, - isSalted: boolean, - separator: string, - useBrain: boolean, - hashCount: number, - cracked: number, - notes: string, - isArchived: boolean, - sourceType: string, - sourceData: string -} - -export type CreateHashlist = BaseHashlist - -// export interface Hashlist extends BaseHashlist { -// id: number; -// hashCount: number; -// crackedCount: number; -// notes: string; -// } From 2e1c13dabb66c2dfdecdfb9e8aa3a0ff636382a1 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Tue, 31 Oct 2023 11:00:27 +0100 Subject: [PATCH 134/419] Rename and update hashtype interface --- src/app/core/_models/hashtype.model.ts | 6 ++++++ src/app/core/_models/hashtype.ts | 6 ------ 2 files changed, 6 insertions(+), 6 deletions(-) create mode 100644 src/app/core/_models/hashtype.model.ts delete mode 100644 src/app/core/_models/hashtype.ts diff --git a/src/app/core/_models/hashtype.model.ts b/src/app/core/_models/hashtype.model.ts new file mode 100644 index 00000000..527c6bd2 --- /dev/null +++ b/src/app/core/_models/hashtype.model.ts @@ -0,0 +1,6 @@ +export interface Hashtype { + hashTypeId: number + description: string + isSalted: boolean + isSlowHash: boolean +} diff --git a/src/app/core/_models/hashtype.ts b/src/app/core/_models/hashtype.ts deleted file mode 100644 index 8170c5ab..00000000 --- a/src/app/core/_models/hashtype.ts +++ /dev/null @@ -1,6 +0,0 @@ -export interface Hashtype { - hashTypeId: number; - description: string; - isSalted: boolean; - isSlowHash: boolean; -} From 8af8266317c1f2ed5fcdbe1a98a87a880a92fbed Mon Sep 17 00:00:00 2001 From: itbf9 Date: Tue, 31 Oct 2023 11:01:15 +0100 Subject: [PATCH 135/419] Rename and update healthcheck interface --- src/app/core/_models/healthcheck.model.ts | 22 ++++++++++++++++++++++ src/app/core/_models/healthcheck.ts | 22 ---------------------- 2 files changed, 22 insertions(+), 22 deletions(-) create mode 100644 src/app/core/_models/healthcheck.model.ts delete mode 100644 src/app/core/_models/healthcheck.ts diff --git a/src/app/core/_models/healthcheck.model.ts b/src/app/core/_models/healthcheck.model.ts new file mode 100644 index 00000000..5b3eaa1a --- /dev/null +++ b/src/app/core/_models/healthcheck.model.ts @@ -0,0 +1,22 @@ +export interface HealthCheck { + attackCmd: string + checkType: number + crackerBinaryId: number + expectedCracks: number + hashtypeId: number + healthCheckId: number + status: number + time: number +} + +export interface HealthCheckedAgents { + healthCheckAgentId: number + healthCheckId: number + agentId: number + status: number + cracked: number + numGpus: number + start: number + end: number + errors: string +} diff --git a/src/app/core/_models/healthcheck.ts b/src/app/core/_models/healthcheck.ts deleted file mode 100644 index 448a9d33..00000000 --- a/src/app/core/_models/healthcheck.ts +++ /dev/null @@ -1,22 +0,0 @@ -export interface HealthCheck { - attackCmd: string; - checkType: number; - crackerBinaryId: number; - expectedCracks: number; - hashtypeId: number; - healthCheckId: number; - status: number; - time: number -} - -export interface HealthCheckedAgents { - healthCheckAgentId: number; - healthCheckId: number; - agentId: number; - status: number; - cracked: number; - numGpus: number; - start: number; - end: number; - errors: string; -} From e1036de496d57672c1d8136a83896179036ac869 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Tue, 31 Oct 2023 11:01:41 +0100 Subject: [PATCH 136/419] Remove unused interface --- src/app/core/_models/logs.ts | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/app/core/_models/logs.ts diff --git a/src/app/core/_models/logs.ts b/src/app/core/_models/logs.ts deleted file mode 100644 index e69de29b..00000000 From 001eac6f8a8cda4ef41a1598f8859a3c5490aa66 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Tue, 31 Oct 2023 11:02:19 +0100 Subject: [PATCH 137/419] Rename and update pages results interface --- .../core/_models/{paged-results.ts => paged-results.model.ts} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename src/app/core/_models/{paged-results.ts => paged-results.model.ts} (50%) diff --git a/src/app/core/_models/paged-results.ts b/src/app/core/_models/paged-results.model.ts similarity index 50% rename from src/app/core/_models/paged-results.ts rename to src/app/core/_models/paged-results.model.ts index 962946f8..1d430650 100644 --- a/src/app/core/_models/paged-results.ts +++ b/src/app/core/_models/paged-results.model.ts @@ -1,4 +1,4 @@ export interface IPagedResults { - totalRecords: number; - results: T; + totalRecords: number + results: T } From 050a4abbf484103ef5eb6838e63ec52f78911649 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Tue, 31 Oct 2023 11:03:00 +0100 Subject: [PATCH 138/419] Rename and update preprocessor interface --- src/app/core/_models/preprocessor.model.ts | 9 +++++++++ src/app/core/_models/preprocessor.ts | 9 --------- 2 files changed, 9 insertions(+), 9 deletions(-) create mode 100644 src/app/core/_models/preprocessor.model.ts delete mode 100644 src/app/core/_models/preprocessor.ts diff --git a/src/app/core/_models/preprocessor.model.ts b/src/app/core/_models/preprocessor.model.ts new file mode 100644 index 00000000..3f612173 --- /dev/null +++ b/src/app/core/_models/preprocessor.model.ts @@ -0,0 +1,9 @@ +export interface Preprocessor { + preprocessorId: number + name: string + url: string + binaryName: string + keyspaceCommand: string + skipCommand: string + limitCommand: string +} diff --git a/src/app/core/_models/preprocessor.ts b/src/app/core/_models/preprocessor.ts deleted file mode 100644 index 1a14d717..00000000 --- a/src/app/core/_models/preprocessor.ts +++ /dev/null @@ -1,9 +0,0 @@ -export interface Preprocessor { - preprocessorId: number - name: string; - url: string; - binaryName: string; - keyspaceCommand: string; - skipCommand: string; - limitCommand: string; -} From ec7e36068c57526cd57df0931a166142a75270fb Mon Sep 17 00:00:00 2001 From: itbf9 Date: Tue, 31 Oct 2023 11:03:40 +0100 Subject: [PATCH 139/419] Rename and update pretask interface --- src/app/core/_models/pretask.model.ts | 21 +++++++++++++++++++++ src/app/core/_models/pretask.ts | 21 --------------------- 2 files changed, 21 insertions(+), 21 deletions(-) create mode 100644 src/app/core/_models/pretask.model.ts delete mode 100644 src/app/core/_models/pretask.ts diff --git a/src/app/core/_models/pretask.model.ts b/src/app/core/_models/pretask.model.ts new file mode 100644 index 00000000..4421350f --- /dev/null +++ b/src/app/core/_models/pretask.model.ts @@ -0,0 +1,21 @@ +export class Pretask { + public pretaskId: number + public taskName: string + public attackCmd: string + public chunkTime: number + public statusTimer: number + public color: number + public isSmall: boolean + public isCpuTask: boolean + public useNewBench: boolean + public priority: number + public maxAgents: number + public isMaskImport: boolean + public crackerBinaryTypeId: number + + constructor(pretaskId: number, taskName: string, attackCmd: string) { + this.pretaskId = pretaskId + this.taskName = taskName + this.attackCmd = attackCmd + } +} diff --git a/src/app/core/_models/pretask.ts b/src/app/core/_models/pretask.ts deleted file mode 100644 index c23c4402..00000000 --- a/src/app/core/_models/pretask.ts +++ /dev/null @@ -1,21 +0,0 @@ -export class Pretask { - public pretaskId: number; - public taskName: string; - public attackCmd: string; - public chunkTime: number; - public statusTimer: number; - public color: number; - public isSmall: boolean; - public isCpuTask: boolean; - public useNewBench: boolean; - public priority: number; - public maxAgents: number; - public isMaskImport: boolean; - public crackerBinaryTypeId: number; - - constructor(pretaskId: number, taskName: string, attackCmd: string) { - this.pretaskId = pretaskId; - this.taskName = taskName; - this.attackCmd = attackCmd; - } -} From 72418432bce19cd266cbcee25f7796d3ae3b74d3 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Tue, 31 Oct 2023 11:04:17 +0100 Subject: [PATCH 140/419] Rename and update task interface --- src/app/core/_models/task.model.ts | 54 ++++++++++++++++++++++++++++++ src/app/core/_models/task.ts | 11 ------ 2 files changed, 54 insertions(+), 11 deletions(-) create mode 100644 src/app/core/_models/task.model.ts delete mode 100644 src/app/core/_models/task.ts diff --git a/src/app/core/_models/task.model.ts b/src/app/core/_models/task.model.ts new file mode 100644 index 00000000..e9e1ac4b --- /dev/null +++ b/src/app/core/_models/task.model.ts @@ -0,0 +1,54 @@ +// import { Hashlist } from "./hashlist" + +import { Agent } from "./agent.model" +import { CrackerBinary, CrackerBinaryType } from "./cracker-binary.model" +import { Hashlist } from "./hashlist.model" + +/** + * @deprecated This interface is deprecated and should not be used. + * Use the Task interface instead. + */ +export interface NormalTask { + id: number + name: string + priority: number + maxAgents: number + + hashlistId: number + // hashlist: Hashlist +} + +export interface Task { + _id: number + _self: string + attackCmd: string + chunkSize: number + chunkTime: number + color?: string + crackerBinaryId: number + crackerBinaryTypeId: number + forcePipe: boolean + isArchived: boolean + isCpuTask: boolean + isSmall: boolean + keyspace: number + keyspaceProgress: number + maxAgents: number + notes: string + preprocessorCommand: number + preprocessorId: number + priority: number + skipKeyspace: number + staticChunks: number + statusTimer: number + taskId: number + taskName: string + taskWrapperId: number + taskWrapperName?: string + useNewBench: boolean + assignedAgents?: Agent[] + crackerBinary?: CrackerBinary + crackerBinaryType?: CrackerBinaryType + hashlist?: Hashlist[] + taskType?: number +} \ No newline at end of file diff --git a/src/app/core/_models/task.ts b/src/app/core/_models/task.ts deleted file mode 100644 index d68524cd..00000000 --- a/src/app/core/_models/task.ts +++ /dev/null @@ -1,11 +0,0 @@ -// import { Hashlist } from "./hashlist"; - -export interface NormalTask { - id: number; - name: string; - priority: number; - maxAgents: number; - - hashlistId: number; - // hashlist: Hashlist; -} From 9a90508f904aaabf9e3023cba91de82ebcfc23f7 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Tue, 31 Oct 2023 11:09:00 +0100 Subject: [PATCH 141/419] New user interface --- src/app/core/_models/user.model.ts | 43 +++++++++++++++++++++--------- 1 file changed, 31 insertions(+), 12 deletions(-) diff --git a/src/app/core/_models/user.model.ts b/src/app/core/_models/user.model.ts index ecf0fe87..80a28008 100644 --- a/src/app/core/_models/user.model.ts +++ b/src/app/core/_models/user.model.ts @@ -1,17 +1,36 @@ export interface BaseUser { - userId: number; - username: string; - email: string; - rightGroupId: number; - registered: number; - lastLogin: number; - isValid: number; - sessionLifetime: number; + userId: number + username: string + email: string + rightGroupId: number + registered: number + lastLogin: number + isValid: number + sessionLifetime: number } export interface CreateUser extends BaseUser { - username: string; - email: string; - rightGroupId: number; - isAdmin: number; + username: string + email: string + rightGroupId: number + isAdmin: number } + +export interface User { + _id: number + _self: string + email: string + globalPermissionGroupId: number + id?: number + isComputedPassword: boolean + isValid: boolean + lastLoginDate: number + name: string + otp1: string + otp2: string + otp3: string + otp4: string + registeredSince: number + sessionLifetime: number + yubikey: string +} \ No newline at end of file From 767f74b1f4f08a498da135b8d6077458d1f7c624 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Tue, 31 Oct 2023 11:09:50 +0100 Subject: [PATCH 142/419] Update interface paths --- .../core/_services/files/files_tus.service.ts | 194 +++++++-------- .../core/_services/shared/storage.service.ts | 71 +++--- .../files/files-edit/files-edit.component.ts | 92 +++---- src/app/files/files.component.ts | 226 +++++++++--------- .../files/new-files/new-files.component.ts | 146 +++++------ .../new-hashlist/new-hashlist.component.ts | 126 +++++----- 6 files changed, 426 insertions(+), 429 deletions(-) diff --git a/src/app/core/_services/files/files_tus.service.ts b/src/app/core/_services/files/files_tus.service.ts index cd22ccf2..cef5aefe 100644 --- a/src/app/core/_services/files/files_tus.service.ts +++ b/src/app/core/_services/files/files_tus.service.ts @@ -1,10 +1,10 @@ -import { Injectable, ViewChild, ChangeDetectorRef} from '@angular/core'; +import { Injectable, ViewChild, ChangeDetectorRef } from '@angular/core'; import { environment } from './../../../../environments/environment'; import * as tus from 'tus-js-client'; import { Observable, Subject, of } from 'rxjs'; import { ConfigService } from '../shared/config.service'; -import { UploadFileTUS } from '../../_models/files'; +import { UploadFileTUS } from '../../_models/file.model'; import { SERV } from '../../../core/_services/main.config'; import { HttpEvent, HttpEventType, HttpProgressEvent } from '@angular/common/http'; import { GlobalService } from '../main.service'; @@ -15,125 +15,125 @@ import { Router } from '@angular/router'; }) export class UploadTUSService { - private endpoint = '/helper/importFile'; - private chunked = environment.config.chunkSizeTUS; - private userData: {_token: string} = JSON.parse(localStorage.getItem('userData')); + private endpoint = '/helper/importFile'; + private chunked = environment.config.chunkSizeTUS; + private userData: { _token: string } = JSON.parse(localStorage.getItem('userData')); - private tusUpload: tus.Upload | null = null; + private tusUpload: tus.Upload | null = null; - constructor( - private cs: ConfigService, - private gs: GlobalService, - private router: Router - ){} + constructor( + private cs: ConfigService, + private gs: GlobalService, + private router: Router + ) { } - /** - * Upload file using TUS protocol - * @param file - File - * @param filename - Name to upload - * @param form - Creation form - * @param redirect - Link to redirect - **/ + /** + * Upload file using TUS protocol + * @param file - File + * @param filename - Name to upload + * @param form - Creation form + * @param redirect - Link to redirect + **/ - // public fileStatusArr: UploadFileTUS[] = []; + // public fileStatusArr: UploadFileTUS[] = []; - uploadFile(file: File, filename: string, path, form = null, redirect = null): Observable { + uploadFile(file: File, filename: string, path, form = null, redirect = null): Observable { - return new Observable((observer) => { + return new Observable((observer) => { - // Chunksize config default - let chunkSize = this.chunked; - if (Number.isNaN(chunkSize)) { - chunkSize = Infinity - } - if (!tus.isSupported) { - alert('This browser does not support uploads. Please use a modern browser instead.') - } + // Chunksize config default + let chunkSize = this.chunked; + if (Number.isNaN(chunkSize)) { + chunkSize = Infinity + } + if (!tus.isSupported) { + alert('This browser does not support uploads. Please use a modern browser instead.') + } - const upload = new tus.Upload(file, { - endpoint: this.cs.getEndpoint() + this.endpoint, - headers: { - Authorization: `Bearer ${this.userData._token}`, - 'Tus-Resumable':'1.0.0', - 'Tus-Extension': 'checksum', - 'Tus-Checksum-Algorithm': 'md5,sha1,crc32' - }, - // uploadUrl: fileURL, //Used for paused uploads - retryDelays: [0, 3000, 6000, 9000, 12000], - chunkSize: chunkSize, - removeFingerprintOnSuccess: true, - metadata: { - filename, - filetype: file.type, - }, - onError: async (error) => { - const exist = String(error).includes('exists!'); - if (exist) { - const progress = 100; - observer.next(progress); - if(form !== null){ - this.gs.create(path,form).subscribe(() => { - this.router.navigate(redirect); - }); - } - } else { - window.alert(`Failed because: ${error}`) - } - return false; - }, - onProgress: (bytesUploaded, bytesTotal) => { - const progress = (bytesUploaded / bytesTotal) * 100; + const upload = new tus.Upload(file, { + endpoint: this.cs.getEndpoint() + this.endpoint, + headers: { + Authorization: `Bearer ${this.userData._token}`, + 'Tus-Resumable': '1.0.0', + 'Tus-Extension': 'checksum', + 'Tus-Checksum-Algorithm': 'md5,sha1,crc32' + }, + // uploadUrl: fileURL, //Used for paused uploads + retryDelays: [0, 3000, 6000, 9000, 12000], + chunkSize: chunkSize, + removeFingerprintOnSuccess: true, + metadata: { + filename, + filetype: file.type, + }, + onError: async (error) => { + const exist = String(error).includes('exists!'); + if (exist) { + const progress = 100; observer.next(progress); - }, - onSuccess: async () => { - observer.complete(); - if(form !== null){ - this.gs.create(path,form).subscribe(() => { + if (form !== null) { + this.gs.create(path, form).subscribe(() => { this.router.navigate(redirect); }); } + } else { + window.alert(`Failed because: ${error}`) } - }) - - this.tusUpload = upload; + return false; + }, + onProgress: (bytesUploaded, bytesTotal) => { + const progress = (bytesUploaded / bytesTotal) * 100; + observer.next(progress); + }, + onSuccess: async () => { + observer.complete(); + if (form !== null) { + this.gs.create(path, form).subscribe(() => { + this.router.navigate(redirect); + }); + } + } + }) - checkPreviousuploads(upload).catch((error) => { - console.error(error) - }) + this.tusUpload = upload; + checkPreviousuploads(upload).catch((error) => { + console.error(error) }) - async function checkPreviousuploads(upload) { - let previousUploads = await upload.findPreviousUploads() + }) - // We only want to consider uploads in the last hour. - const limitUpload = Date.now() - 3 * 60 * 60 * 1000 - previousUploads = previousUploads - .map((upload) => { - console.log('creationtome') - upload.creationTime = new Date(upload.creationTime) - return upload - }) - .filter((upload) => upload.status > limitUpload) - .sort((a, b) => b.creationTime - a.creationTime) + async function checkPreviousuploads(upload) { + let previousUploads = await upload.findPreviousUploads() - // if (previousUploads.length === 0) { - // upload.start(); - // } + // We only want to consider uploads in the last hour. + const limitUpload = Date.now() - 3 * 60 * 60 * 1000 + previousUploads = previousUploads + .map((upload) => { + console.log('creationtome') + upload.creationTime = new Date(upload.creationTime) + return upload + }) + .filter((upload) => upload.status > limitUpload) + .sort((a, b) => b.creationTime - a.creationTime) - // File already exist in the import folder, then return progress as 100 + // if (previousUploads.length === 0) { + // upload.start(); + // } - upload.start(); + // File already exist in the import folder, then return progress as 100 - } + upload.start(); } - isHttpProgressEvent(event: HttpEvent): event is HttpProgressEvent { - return ( - event.type === HttpEventType.DownloadProgress || - event.type === HttpEventType.UploadProgress - ); - } } + isHttpProgressEvent(event: HttpEvent): event is HttpProgressEvent { + return ( + event.type === HttpEventType.DownloadProgress || + event.type === HttpEventType.UploadProgress + ); + } +} + diff --git a/src/app/core/_services/shared/storage.service.ts b/src/app/core/_services/shared/storage.service.ts index 5c20f5bf..feec8512 100644 --- a/src/app/core/_services/shared/storage.service.ts +++ b/src/app/core/_services/shared/storage.service.ts @@ -1,10 +1,7 @@ -import { dateFormat } from '../../../core/_constants/settings.config'; import { environment } from '../../../../environments/environment'; import { Injectable } from "@angular/core"; - import { GlobalService } from 'src/app/core/_services/main.service'; import { SERV } from '../../../core/_services/main.config'; -import { IStorage } from '../../_models/config-ui.model'; @Injectable({ providedIn: 'root' @@ -15,76 +12,76 @@ export class UIConfigService { constructor( private gs: GlobalService, - ) {} + ) { } private maxResults = environment.config.prodApiMaxResults; - cachevar= [ + cachevar = [ // {name:'timefmt'}, - {name:'hashcatBrainEnable'}, - {name:'hashlistAlias'}, - {name:'blacklistChars'}, - {name:'chunktime'}, - {name:'agentStatLimit'}, - {name:'agentStatTension'}, - {name:'agentTempThreshold1'}, - {name:'agentTempThreshold2'}, - {name:'agentUtilThreshold1'}, - {name:'agentUtilThreshold2'}, - {name:'statustimer'}, - {name:'agenttimeout'}, - {name:'maxSessionLength'} + { name: 'hashcatBrainEnable' }, + { name: 'hashlistAlias' }, + { name: 'blacklistChars' }, + { name: 'chunktime' }, + { name: 'agentStatLimit' }, + { name: 'agentStatTension' }, + { name: 'agentTempThreshold1' }, + { name: 'agentTempThreshold2' }, + { name: 'agentUtilThreshold1' }, + { name: 'agentUtilThreshold2' }, + { name: 'statustimer' }, + { name: 'agenttimeout' }, + { name: 'maxSessionLength' } ]; - cexprity: number = 72*60*60; // Hours*minutes*Seconds Default: 72 hours + cexprity: number = 72 * 60 * 60; // Hours*minutes*Seconds Default: 72 hours public checkStorage() { - const defaults = JSON.parse(localStorage.getItem('uis')); + const defaults = JSON.parse(localStorage.getItem('uis')); if (defaults) { //Change to !defaults - this.checkExpiry(); - return this.defaultSettings; + this.checkExpiry(); + return this.defaultSettings; } else if (!defaults) { - this.storeDefault(); - this.defaultSettings = true; + this.storeDefault(); + this.defaultSettings = true; } return '' } - public checkExpiry(){ - const timestamp = this.getUIsettings('_timestamp').value || 0; - const expires = this.getUIsettings('_expiresin').value || 0; - if((Date.now() - timestamp) < expires){ + public checkExpiry() { + const timestamp = this.getUIsettings('_timestamp').value || 0; + const expires = this.getUIsettings('_expiresin').value || 0; + if ((Date.now() - timestamp) < expires) { this.storeDefault(); } } - public storeDefault(){ - const params = {'maxResults': this.maxResults} - this.gs.getAll(SERV.CONFIGS,params).subscribe((result)=>{ + public storeDefault() { + const params = { 'maxResults': this.maxResults } + this.gs.getAll(SERV.CONFIGS, params).subscribe((result) => { const post_data = []; this.cachevar.forEach((data) => { const name = data.name; let value = result.values.find(obj => obj.item === data.name).value; - value = {name:name, value: value} + value = { name: name, value: value } post_data.push(value); }); - const timeinfo = [{name: '_timestamp', value: Date.now()},{name: '_expiresin', value: this.cexprity}]; + const timeinfo = [{ name: '_timestamp', value: Date.now() }, { name: '_expiresin', value: this.cexprity }]; - localStorage.setItem('uis', JSON.stringify([].concat(post_data,timeinfo))); + localStorage.setItem('uis', JSON.stringify([].concat(post_data, timeinfo))); }); } - public onUpdatingCheck(name: any){ - if(this.cachevar.some(e => e.name === name)){ + public onUpdatingCheck(name: any) { + if (this.cachevar.some(e => e.name === name)) { this.storeDefault(); } } - public getUIsettings(name?: string){ + public getUIsettings(name?: string) { const uiconfig = JSON.parse(localStorage.getItem('uis')); if (!uiconfig) { return null; diff --git a/src/app/files/files-edit/files-edit.component.ts b/src/app/files/files-edit/files-edit.component.ts index ac73eadd..0c32946d 100644 --- a/src/app/files/files-edit/files-edit.component.ts +++ b/src/app/files/files-edit/files-edit.component.ts @@ -7,7 +7,7 @@ import { AlertService } from 'src/app/core/_services/shared/alert.service'; import { GlobalService } from 'src/app/core/_services/main.service'; import { PageTitle } from 'src/app/core/_decorators/autotitle'; import { SERV } from '../../core/_services/main.config'; -import { Filetype } from '../../core/_models/files'; +import { Filetype } from '../../core/_models/file.model'; @Component({ selector: 'app-files-edit', @@ -30,25 +30,25 @@ export class FilesEditComponent implements OnInit { filetype: any[] constructor( - private route:ActivatedRoute, + private route: ActivatedRoute, private alert: AlertService, private gs: GlobalService, private router: Router - ) { } + ) { } ngOnInit(): void { this.route.params - .subscribe( - (params: Params) => { - this.editedFileIndex = +params['id']; - this.editMode = params['id'] != null; - this.initForm(); - } - ); + .subscribe( + (params: Params) => { + this.editedFileIndex = +params['id']; + this.editMode = params['id'] != null; + this.initForm(); + } + ); this.updateForm = new FormGroup({ - 'fileId': new FormControl({value: '', disabled: true}), + 'fileId': new FormControl({ value: '', disabled: true }), 'updateData': new FormGroup({ 'filename': new FormControl('', [Validators.required, Validators.minLength(1)]), 'fileType': new FormControl(null), @@ -62,76 +62,76 @@ export class FilesEditComponent implements OnInit { case 'wordlist': this.whichView = 'wordlist-edit'; - break; + break; case 'rules': this.whichView = 'rules-edit'; - break; + break; case 'other': this.whichView = 'other-edit'; - break; + break; } - this.filetype = [{fileType: 0, fileName: 'Wordlist'},{fileType: 1, fileName: 'Rules'},{fileType: 2, fileName: 'Other'}]; + this.filetype = [{ fileType: 0, fileName: 'Wordlist' }, { fileType: 1, fileName: 'Rules' }, { fileType: 2, fileName: 'Other' }]; this.gs.getAll(SERV.ACCESS_GROUPS).subscribe((agroups: any) => { this.accessgroup = agroups.values; }); - this.gs.get(SERV.FILES,this.editedFileIndex).subscribe((files: any) => { + this.gs.get(SERV.FILES, this.editedFileIndex).subscribe((files: any) => { this.allfiles = files; }); }); } - onSubmit(): void{ - this.gs.update(SERV.FILES,this.editedFileIndex,this.updateForm.value['updateData']).subscribe(() => { - this.alert.okAlert('File saved!',''); + onSubmit(): void { + this.gs.update(SERV.FILES, this.editedFileIndex, this.updateForm.value['updateData']).subscribe(() => { + this.alert.okAlert('File saved!', ''); this.route.data.subscribe(data => { switch (data['kind']) { case 'wordlist-edit': this.whichView = 'wordlist'; - break; + break; case 'rules-edit': this.whichView = 'rules'; - break; + break; case 'other-edit': this.whichView = 'other'; - break; + break; } - this.router.navigate(['../files/'+this.whichView+'']); + this.router.navigate(['../files/' + this.whichView + '']); }) }, - errorMessage => { - // check error status code is 500, if so, do some action - this.alert.okAlert('File was not updated, please try again!','','warning'); - this.ngOnInit(); - } - ); - this.updateForm.reset(); // success, we reset form -} + errorMessage => { + // check error status code is 500, if so, do some action + this.alert.okAlert('File was not updated, please try again!', '', 'warning'); + this.ngOnInit(); + } + ); + this.updateForm.reset(); // success, we reset form + } -private initForm() { - if (this.editMode) { - this.gs.get(SERV.FILES,this.editedFileIndex).subscribe((result)=>{ - this.updateForm = new FormGroup({ - 'fileId': new FormControl({value: result['fileId'], disabled: true}), - 'updateData': new FormGroup({ - 'filename': new FormControl(result['filename'], Validators.required), - 'fileType': new FormControl(result['fileType'], Validators.required), - 'accessGroupId': new FormControl(result['accessGroupId'], Validators.required), - 'isSecret': new FormControl(result['isSecret']), - }) - }); - }); - } -} + private initForm() { + if (this.editMode) { + this.gs.get(SERV.FILES, this.editedFileIndex).subscribe((result) => { + this.updateForm = new FormGroup({ + 'fileId': new FormControl({ value: result['fileId'], disabled: true }), + 'updateData': new FormGroup({ + 'filename': new FormControl(result['filename'], Validators.required), + 'fileType': new FormControl(result['fileType'], Validators.required), + 'accessGroupId': new FormControl(result['accessGroupId'], Validators.required), + 'isSecret': new FormControl(result['isSecret']), + }) + }); + }); + } + } } diff --git a/src/app/files/files.component.ts b/src/app/files/files.component.ts index 1a933c9a..3d18ce26 100644 --- a/src/app/files/files.component.ts +++ b/src/app/files/files.component.ts @@ -1,4 +1,4 @@ -import { faEdit, faTrash, faPlus, faLock} from '@fortawesome/free-solid-svg-icons'; +import { faEdit, faTrash, faPlus, faLock } from '@fortawesome/free-solid-svg-icons'; import { Component, OnInit, ViewChild } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; import { DataTableDirective } from 'angular-datatables'; @@ -11,9 +11,9 @@ import { GlobalService } from 'src/app/core/_services/main.service'; import { environment } from './../../environments/environment'; import { PageTitle } from '../core/_decorators/autotitle'; import { SERV } from '../core/_services/main.config'; -import { Filetype } from '../core/_models/files'; +import { Filetype } from '../core/_models/file.model'; -declare let $:any; +declare let $: any; @Component({ selector: 'app-files', @@ -22,10 +22,10 @@ declare let $:any; @PageTitle(['Show Files']) export class FilesComponent implements OnInit { - faTrash=faTrash; - faPlus=faPlus; - faLock=faLock; - faEdit=faEdit; + faTrash = faTrash; + faPlus = faPlus; + faLock = faLock; + faEdit = faEdit; public allfiles: { fileId: number, @@ -34,7 +34,7 @@ export class FilesComponent implements OnInit { isSecret: boolean, fileType: number, accessGroupId: number, - lineCount:number + lineCount: number accessGroup: { accessGroupId: number, groupName: string @@ -44,11 +44,11 @@ export class FilesComponent implements OnInit { private maxResults = environment.config.prodApiMaxResults; constructor( - private route:ActivatedRoute, + private route: ActivatedRoute, private alert: AlertService, private gs: GlobalService, - private router:Router - ) { } + private router: Router + ) { } @ViewChild(DataTableDirective) dtElement: DataTableDirective; @@ -56,7 +56,7 @@ export class FilesComponent implements OnInit { dtTrigger: Subject = new Subject(); dtOptions: any = {}; - ngOnDestroy(){ + ngOnDestroy() { this.dtTrigger.unsubscribe(); } @@ -70,7 +70,7 @@ export class FilesComponent implements OnInit { } - loadFiles(){ + loadFiles() { this.route.data.subscribe(data => { switch (data['kind']) { @@ -78,24 +78,24 @@ export class FilesComponent implements OnInit { this.filterType = 0; this.whichView = 'wordlist'; this.navEdit = 'wordlist-edit'; - break; + break; case 'rules': this.filterType = 1; this.whichView = 'rules'; this.navEdit = 'rules-edit'; - break; + break; case 'other': this.filterType = 2; this.whichView = 'other'; this.navEdit = 'rules-edit'; - break; + break; } - const params = {'maxResults': this.maxResults, 'expand': 'accessGroup', 'filter': 'fileType='+this.filterType+''}; + const params = { 'maxResults': this.maxResults, 'expand': 'accessGroup', 'filter': 'fileType=' + this.filterType + '' }; - this.gs.getAll(SERV.FILES,params).subscribe((files: any) => { + this.gs.getAll(SERV.FILES, params).subscribe((files: any) => { this.allfiles = files.values; this.dtTrigger.next(void 0); }); @@ -114,98 +114,98 @@ export class FilesComponent implements OnInit { destroy: true, select: { style: 'multi', - }, + }, order: [[0, 'desc']], buttons: { - dom: { - button: { - className: 'dt-button buttons-collection btn btn-sm-dt btn-outline-gray-600-dt', - } - }, - buttons: [ - { - text: '↻', - autoClose: true, - action: function (e, dt, node, config) { - self.onRefresh(); + dom: { + button: { + className: 'dt-button buttons-collection btn btn-sm-dt btn-outline-gray-600-dt', } }, - { - extend: 'collection', - text: 'Export', - buttons: [ - { - extend: 'excelHtml5', - exportOptions: { - columns: [0, 1, 2, 3, 4, 5] + buttons: [ + { + text: '↻', + autoClose: true, + action: function (e, dt, node, config) { + self.onRefresh(); + } + }, + { + extend: 'collection', + text: 'Export', + buttons: [ + { + extend: 'excelHtml5', + exportOptions: { + columns: [0, 1, 2, 3, 4, 5] + }, }, - }, - { - extend: 'print', - exportOptions: { - columns: [0, 1, 2, 3, 4, 5] + { + extend: 'print', + exportOptions: { + columns: [0, 1, 2, 3, 4, 5] + }, + customize: function (win) { + $(win.document.body) + .css('font-size', '10pt') + $(win.document.body).find('table') + .addClass('compact') + .css('font-size', 'inherit'); + } }, - customize: function ( win ) { - $(win.document.body) - .css( 'font-size', '10pt' ) - $(win.document.body).find( 'table' ) - .addClass( 'compact' ) - .css( 'font-size', 'inherit' ); - } - }, - { - extend: 'csvHtml5', - exportOptions: {modifier: {selected: true}}, - select: true, - customize: function (dt, csv) { - let data = ""; - for (let i = 0; i < dt.length; i++) { - data = "Agents\n\n"+ dt; + { + extend: 'csvHtml5', + exportOptions: { modifier: { selected: true } }, + select: true, + customize: function (dt, csv) { + let data = ""; + for (let i = 0; i < dt.length; i++) { + data = "Agents\n\n" + dt; + } + return data; } - return data; - } - }, - { - extend: 'copy', - } + }, + { + extend: 'copy', + } ] }, { extend: 'collection', text: 'Bulk Actions', buttons: [ - { - text: 'Delete Files', - autoClose: true, - action: function (e, dt, node, config) { - self.onDeleteBulk(); - } - }, - { - text: 'Change Type', - autoClose: true, - action: function ( e, dt, node, config ) { - const title = 'Edit File Type' - self.onModalEditType(title) - } - }, - { - text: 'Line Count (Missing API Call)', - autoClose: true, - action: function ( e, dt, node, config ) { - } - } - ] - }, - { + { + text: 'Delete Files', + autoClose: true, + action: function (e, dt, node, config) { + self.onDeleteBulk(); + } + }, + { + text: 'Change Type', + autoClose: true, + action: function (e, dt, node, config) { + const title = 'Edit File Type' + self.onModalEditType(title) + } + }, + { + text: 'Line Count (Missing API Call)', + autoClose: true, + action: function (e, dt, node, config) { + } + } + ] + }, + { extend: 'colvis', text: 'Column View', columns: [0, 1, 2, 3, 4, 5], }, - { + { extend: "pageLength", className: "btn-sm" - } + } ], } }; @@ -213,7 +213,7 @@ export class FilesComponent implements OnInit { }); } - onRefresh(){ + onRefresh() { this.rerender(); this.ngOnInit(); } @@ -229,8 +229,8 @@ export class FilesComponent implements OnInit { }); } - deleteFile(id: number, name: string){ - this.alert.deleteConfirmation(name,'Files').then((confirmed) => { + deleteFile(id: number, name: string) { + this.alert.deleteConfirmation(name, 'Files').then((confirmed) => { if (confirmed) { // Deletion this.gs.delete(SERV.FILES, id).subscribe(() => { @@ -240,55 +240,55 @@ export class FilesComponent implements OnInit { }); } else { // Handle cancellation - this.alert.okAlert(`File ${name} is safe!`,''); + this.alert.okAlert(`File ${name} is safe!`, ''); } }); } - onRefreshTable(){ + onRefreshTable() { setTimeout(() => { this.ngOnInit(); this.rerender(); // rerender datatables - },2000); + }, 2000); } -// Bulk Actions + // Bulk Actions - onSelectedFiles(){ + onSelectedFiles() { $(".dt-button-background").trigger("click"); - const selection = $($(this.dtElement).DataTable.tables()).DataTable().rows({ selected: true } ).data().pluck(0).toArray(); - if(selection.length == 0) { - this.alert.okAlert('You have not selected any File',''); + const selection = $($(this.dtElement).DataTable.tables()).DataTable().rows({ selected: true }).data().pluck(0).toArray(); + if (selection.length == 0) { + this.alert.okAlert('You have not selected any File', ''); return; } - const selectionnum = selection.map(i=>Number(i)); + const selectionnum = selection.map(i => Number(i)); return selectionnum; } async onDeleteBulk() { const FilesIds = this.onSelectedFiles(); - this.alert.bulkDeleteAlert(FilesIds,'Files',SERV.FILES); + this.alert.bulkDeleteAlert(FilesIds, 'Files', SERV.FILES); this.onRefreshTable(); } async onUpdateBulk(value: any) { const FilesIds = this.onSelectedFiles(); - this.alert.bulkUpdateAlert(FilesIds,value,'Files',SERV.FILES); + this.alert.bulkUpdateAlert(FilesIds, value, 'Files', SERV.FILES); this.onRefreshTable(); } - onEdit(id: number){ - this.router.navigate(['/files',id,this.navEdit]); + onEdit(id: number) { + this.router.navigate(['/files', id, this.navEdit]); } - onModalEditType(title: string){ + onModalEditType(title: string) { (async () => { $(".dt-button-background").trigger("click"); - const selection = $($(this.dtElement).DataTable.tables()).DataTable().rows({ selected: true } ).data().pluck(0).toArray(); - if(selection.length == 0) { - this.alert.okAlert('You have not selected any Group',''); + const selection = $($(this.dtElement).DataTable.tables()).DataTable().rows({ selected: true }).data().pluck(0).toArray(); + if (selection.length == 0) { + this.alert.okAlert('You have not selected any Group', ''); return; } @@ -306,11 +306,11 @@ export class FilesComponent implements OnInit { }) if (formValues) { - const edit = {fileType: +formValues}; + const edit = { fileType: +formValues }; this.onUpdateBulk(edit); } - })() + })() } diff --git a/src/app/files/new-files/new-files.component.ts b/src/app/files/new-files/new-files.component.ts index aa3be761..c58f4922 100644 --- a/src/app/files/new-files/new-files.component.ts +++ b/src/app/files/new-files/new-files.component.ts @@ -1,4 +1,4 @@ -import { faPlus, faUpload, faDownload, faLink, faFileUpload} from '@fortawesome/free-solid-svg-icons'; +import { faPlus, faUpload, faDownload, faLink, faFileUpload } from '@fortawesome/free-solid-svg-icons'; import { Component, ElementRef, OnDestroy, OnInit, ViewChild } from '@angular/core'; import { FormControl, FormGroup } from '@angular/forms'; import { Subject, Subscription, takeUntil } from 'rxjs'; @@ -13,7 +13,7 @@ import { environment } from './../../../environments/environment'; import { PageTitle } from 'src/app/core/_decorators/autotitle'; import { validateFileExt } from '../../shared/utils/util'; import { ActivatedRoute, Router } from '@angular/router'; -import { UploadFileTUS } from '../../core/_models/files'; +import { UploadFileTUS } from '../../core/_models/file.model'; import { SERV } from '../../core/_services/main.config'; import { subscribe } from 'diagnostics_channel'; @@ -25,25 +25,25 @@ import { subscribe } from 'diagnostics_channel'; // @PageTitle(['New File']) export class NewFilesComponent implements OnInit, OnDestroy { - faFileUpload=faFileUpload; - faDownload=faDownload; - faUpload=faUpload; - faLink=faLink; - faPlus=faPlus; + faFileUpload = faFileUpload; + faDownload = faDownload; + faUpload = faUpload; + faLink = faLink; + faPlus = faPlus; private maxResults = environment.config.prodApiMaxResults; subscriptions: Subscription[] = [] constructor( - private uploadService:UploadTUSService, - private route:ActivatedRoute, + private uploadService: UploadTUSService, + private route: ActivatedRoute, private alert: AlertService, private gs: GlobalService, - private fs:FileSizePipe, + private fs: FileSizePipe, private router: Router ) { - } + } accessgroup: any[] filterType: number; @@ -68,11 +68,11 @@ export class NewFilesComponent implements OnInit, OnDestroy { this.ngUnsubscribe.complete(); } - loadData(){ + loadData() { - const params = {'maxResults': this.maxResults}; + const params = { 'maxResults': this.maxResults }; - this.gs.getAll(SERV.ACCESS_GROUPS,params).subscribe((agroups: any) => { + this.gs.getAll(SERV.ACCESS_GROUPS, params).subscribe((agroups: any) => { this.accessgroup = agroups.values; }); @@ -91,61 +91,61 @@ export class NewFilesComponent implements OnInit, OnDestroy { * Create File * */ - onSubmit(): void{ + onSubmit(): void { if (this.createForm.valid && this.submitted === false) { - let form = this.onPrep(this.createForm.value, false); + let form = this.onPrep(this.createForm.value, false); - this.submitted =true; + this.submitted = true; - if(form.status === false){ - this.subscriptions.push(this.gs.create(SERV.FILES,form.update).subscribe(() => { - form = this.onPrep(this.createForm.value, true); - this.alert.okAlert('New File created!',''); - this.submitted = false; - this.router.navigate(['/files',this.redirect]); - })); + if (form.status === false) { + this.subscriptions.push(this.gs.create(SERV.FILES, form.update).subscribe(() => { + form = this.onPrep(this.createForm.value, true); + this.alert.okAlert('New File created!', ''); + this.submitted = false; + this.router.navigate(['/files', this.redirect]); + })); + } } } -} -onPrep(obj: any, status: boolean){ - let sourcadata; - let fname; - if(obj.sourceType == 'inline'){ - fname = obj.filename; - sourcadata = Buffer.from(obj.sourceData).toString('base64'); - }else{ - sourcadata = this.fileName; - fname = this.fileName; - } - const res = { - "update":{ - "filename": fname, - "isSecret": obj.isSecret, - "fileType": this.filterType, - "accessGroupId": obj.accessGroupId, - "sourceType": obj.sourceType, - "sourceData": sourcadata - },"status": status + onPrep(obj: any, status: boolean) { + let sourcadata; + let fname; + if (obj.sourceType == 'inline') { + fname = obj.filename; + sourcadata = Buffer.from(obj.sourceData).toString('base64'); + } else { + sourcadata = this.fileName; + fname = this.fileName; + } + const res = { + "update": { + "filename": fname, + "isSecret": obj.isSecret, + "fileType": this.filterType, + "accessGroupId": obj.accessGroupId, + "sourceType": obj.sourceType, + "sourceData": sourcadata + }, "status": status } return res; -} + } -souceType(type: string, view: string){ - this.viewMode = view; - this.createForm.patchValue({ - filename: '', - accessGroupId: 1, - sourceType:type, - sourceData:'' - }); -} + souceType(type: string, view: string) { + this.viewMode = view; + this.createForm.patchValue({ + filename: '', + accessGroupId: 1, + sourceType: type, + sourceData: '' + }); + } -// Get Title + // Get Title public title: string; public redirect: string; - getLocation(){ + getLocation() { this.route.data.subscribe(data => { switch (data['kind']) { @@ -153,26 +153,26 @@ souceType(type: string, view: string){ this.filterType = 0; this.title = 'New Wordlist'; this.redirect = 'wordlist'; - break; + break; case 'rule-new': this.filterType = 1; this.title = 'New Rule'; this.redirect = 'rules'; - break; + break; case 'other-new': this.filterType = 2; this.title = 'New Other'; this.redirect = 'other'; - break; + break; } }) } -// Uploading file - @ViewChild('file', {static: false}) file: ElementRef; + // Uploading file + @ViewChild('file', { static: false }) file: ElementRef; name = '!!!'; viewMode = 'tab1'; uploadProgress = 0; @@ -196,26 +196,26 @@ souceType(type: string, view: string){ this.fileToUpload = event.target.files[0]; this.fileSize = this.fileToUpload.size; this.fileName = this.fileToUpload.name; - $('.fileuploadspan').text( this.fs.transform(this.fileToUpload.size,false)); + $('.fileuploadspan').text(this.fs.transform(this.fileToUpload.size, false)); } - private subs: Subscription[] = []; - private ngUnsubscribe = new Subject(); + private subs: Subscription[] = []; + private ngUnsubscribe = new Subject(); - onuploadFile(files: FileList) { + onuploadFile(files: FileList) { let form = this.onPrep(this.createForm.value, false); const upload: Array = []; for (let i = 0; i < files.length; i++) { upload.push( - this.uploadService.uploadFile( - files[i], files[i].name, SERV.FILES, form.update, ['/files',this.redirect] + this.uploadService.uploadFile( + files[i], files[i].name, SERV.FILES, form.update, ['/files', this.redirect] ).pipe(takeUntil(this.ngUnsubscribe)) - .subscribe( - (progress) => { - this.uploadProgress = progress; - // console.log(`Upload progress: ${progress}%`); - } - ) + .subscribe( + (progress) => { + this.uploadProgress = progress; + // console.log(`Upload progress: ${progress}%`); + } + ) ) } // this.reset(); diff --git a/src/app/hashlists/new-hashlist/new-hashlist.component.ts b/src/app/hashlists/new-hashlist/new-hashlist.component.ts index a6a528a9..adc5be88 100644 --- a/src/app/hashlists/new-hashlist/new-hashlist.component.ts +++ b/src/app/hashlists/new-hashlist/new-hashlist.component.ts @@ -1,5 +1,5 @@ import { faMagnifyingGlass, faUpload, faInfoCircle, faFileUpload, faSearchPlus, faLink } from '@fortawesome/free-solid-svg-icons'; -import { Component, OnInit, ChangeDetectionStrategy ,ChangeDetectorRef, HostListener, ViewChild, ElementRef } from '@angular/core'; +import { Component, OnInit, ChangeDetectionStrategy, ChangeDetectorRef, HostListener, ViewChild, ElementRef } from '@angular/core'; import { ModalDismissReasons, NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { FormControl, FormGroup, Validators } from '@angular/forms'; import { Observable, Subject, Subscription, takeUntil } from 'rxjs'; @@ -16,7 +16,7 @@ import { FileSizePipe } from 'src/app/core/_pipes/file-size.pipe'; import { PageTitle } from 'src/app/core/_decorators/autotitle'; import { ShowHideTypeFile } from '../../shared/utils/forms'; import { validateFileExt } from '../../shared/utils/util'; -import { UploadFileTUS } from '../../core/_models/files'; +import { UploadFileTUS } from '../../core/_models/file.model'; import { SERV } from '../../core/_services/main.config'; @Component({ @@ -30,12 +30,12 @@ export class NewHashlistComponent implements OnInit { * Fa Icons * */ - faMagnifyingGlass=faMagnifyingGlass; - faFileUpload=faFileUpload; - faInfoCircle=faInfoCircle; - faSearchPlus=faSearchPlus; - faUpload=faUpload; - faLink=faLink; + faMagnifyingGlass = faMagnifyingGlass; + faFileUpload = faFileUpload; + faInfoCircle = faInfoCircle; + faSearchPlus = faSearchPlus; + faUpload = faUpload; + faLink = faLink; /** * Form Settings @@ -43,8 +43,8 @@ export class NewHashlistComponent implements OnInit { */ signupForm: FormGroup; ShowHideTypeFile = ShowHideTypeFile; - radio=true; - brainenabled:any; + radio = true; + brainenabled: any; hashcatbrain: string; subscriptions: Subscription[] = [] @@ -54,15 +54,15 @@ export class NewHashlistComponent implements OnInit { private maxResults = environment.config.prodApiMaxResults; constructor( - private uploadService:UploadTUSService, - private uiService: UIConfigService, - private modalService: NgbModal, - private alert: AlertService, - private gs: GlobalService, - private fs:FileSizePipe, - private router: Router, - ) { - } + private uploadService: UploadTUSService, + private uiService: UIConfigService, + private modalService: NgbModal, + private alert: AlertService, + private gs: GlobalService, + private fs: FileSizePipe, + private router: Router, + ) { + } ngOnInit(): void { @@ -78,11 +78,11 @@ export class NewHashlistComponent implements OnInit { this.ngUnsubscribe.complete(); } - loadData(){ + loadData() { this.brainenabled = this.uiService.getUIsettings('hashcatBrainEnable').value; - const params = {'maxResults': this.maxResults}; + const params = { 'maxResults': this.maxResults }; this.subscriptions.push(this.gs.getAll(SERV.ACCESS_GROUPS, params).subscribe((agroups: any) => { this.accessgroup = agroups.values; @@ -96,7 +96,7 @@ export class NewHashlistComponent implements OnInit { 'isSalted': new FormControl(false), 'isHexSalt': new FormControl(false), 'accessGroupId': new FormControl(null, [Validators.required]), - 'useBrain': new FormControl(+this.brainenabled=== 1? true:false), + 'useBrain': new FormControl(+this.brainenabled === 1 ? true : false), 'brainFeatures': new FormControl(null || 3), 'notes': new FormControl(''), "sourceType": new FormControl('import' || null), @@ -110,15 +110,15 @@ export class NewHashlistComponent implements OnInit { ngAfterViewInit() { - const params = {'maxResults': this.maxResults}; + const params = { 'maxResults': this.maxResults }; - this.subscriptions.push(this.gs.getAll(SERV.HASHTYPES,params).subscribe((htypes: any) => { + this.subscriptions.push(this.gs.getAll(SERV.HASHTYPES, params).subscribe((htypes: any) => { const self = this; this.hashtypes = htypes.values; const prep = htypes.values; const response = []; - for(let i=0; i < prep.length; i++){ - const obj = { hashTypeId: prep[i].hashTypeId, descrId: prep[i].hashTypeId +' '+prep[i].description }; + for (let i = 0; i < prep.length; i++) { + const obj = { hashTypeId: prep[i].hashTypeId, descrId: prep[i].hashTypeId + ' ' + prep[i].description }; response.push(obj) } ($("#hashtype") as any).selectize({ @@ -130,39 +130,39 @@ export class NewHashlistComponent implements OnInit { loadingClass: 'Loading..', highlight: true, onChange: function (value) { - self.OnChangeValue(value); + self.OnChangeValue(value); }, render: { option: function (item, escape) { return '
' + escape(item.descrId) + '
'; }, }, - onInitialize: function(){ + onInitialize: function () { const selectize = this; - selectize.addOption(response); - const selected_items = []; - $.each(response, function( i, obj) { - selected_items.push(obj.id); - }); - selectize.setValue(selected_items); - } + selectize.addOption(response); + const selected_items = []; + $.each(response, function (i, obj) { + selected_items.push(obj.id); }); - })); + selectize.setValue(selected_items); + } + }); + })); - } + } - OnChangeValue(value){ + OnChangeValue(value) { const id = Number(value); // Map with hashtype and get if its salted or not - const filter = this.hashtypes.filter(u=> u._id === id); + const filter = this.hashtypes.filter(u => u._id === id); const salted = filter[0]['isSalted']; - if(id === 2500 || id === 16800 || id === 16801){ + if (id === 2500 || id === 16800 || id === 16801) { this.signupForm.patchValue({ hashTypeId: id, format: Number(1), isSalted: salted }); - }else{ + } else { this.signupForm.patchValue({ hashTypeId: id, isSalted: salted @@ -171,7 +171,7 @@ export class NewHashlistComponent implements OnInit { } // FILE UPLOAD: TUS File Uload - @ViewChild('file', {static: false}) file: ElementRef; + @ViewChild('file', { static: false }) file: ElementRef; uploadProgress = 0; filenames: string[] = []; private ngUnsubscribe = new Subject(); @@ -181,14 +181,14 @@ export class NewHashlistComponent implements OnInit { const upload: Array = []; for (let i = 0; i < files.length; i++) { upload.push( - this.uploadService.uploadFile( + this.uploadService.uploadFile( files[i], files[i].name, SERV.HASHLISTS, form, ['/hashlists/hashlist'] ).pipe(takeUntil(this.ngUnsubscribe)) - .subscribe( - (progress) => { - this.uploadProgress = progress; - } - ) + .subscribe( + (progress) => { + this.uploadProgress = progress; + } + ) ) } } @@ -201,14 +201,14 @@ export class NewHashlistComponent implements OnInit { * Drop Zone Area * */ - fileList : any = []; - invalidFiles : any = []; + fileList: any = []; + invalidFiles: any = []; - onFilesChange(fileList : Array | DragEvent){ + onFilesChange(fileList: Array | DragEvent) { this.fileList = fileList; } - onFileInvalids(fileList : Array | DragEvent){ + onFileInvalids(fileList: Array | DragEvent) { this.invalidFiles = fileList; } @@ -229,7 +229,7 @@ export class NewHashlistComponent implements OnInit { this.fileToUpload = event.target.files[0]; this.fileSize = this.fileToUpload.size; this.fileName = this.fileToUpload.name; - $('.fileuploadspan').text( this.fs.transform(this.fileToUpload.size,false)); + $('.fileuploadspan').text(this.fs.transform(this.fileToUpload.size, false)); } /** @@ -237,24 +237,24 @@ export class NewHashlistComponent implements OnInit { * */ - onSubmit(): void{ - if (this.signupForm.valid) { + onSubmit(): void { + if (this.signupForm.valid) { const res = this.handleUpload(this.signupForm.value); - this.subscriptions.push(this.gs.create(SERV.HASHLISTS,res).subscribe(() => { - this.alert.okAlert('New HashList created!',''); + this.subscriptions.push(this.gs.create(SERV.HASHLISTS, res).subscribe(() => { + this.alert.okAlert('New HashList created!', ''); this.router.navigate(['/hashlists/hashlist']); } - )); + )); } } - handleUpload(arr: any){ + handleUpload(arr: any) { const str = arr.sourceData; const filereplace = str.replace("C:\\fakepath\\", ""); let filename = filereplace; - if(arr.sourceType === 'paste'){ + if (arr.sourceType === 'paste') { filename = Buffer.from(filereplace).toString('base64'); } @@ -274,8 +274,8 @@ export class NewHashlistComponent implements OnInit { 'hashCount': arr.hashCount, 'isArchived': arr.isArchived, 'isSecret': arr.isSecret, - } - return res; + } + return res; } // @HostListener allows us to also guard against browser refresh, close, etc. @@ -288,7 +288,7 @@ export class NewHashlistComponent implements OnInit { canDeactivate(): Observable | boolean { if (this.signupForm.valid) { - return false; + return false; } return true; } From 9585e5df1943dd5a78918fa11042b9c8484aa56c Mon Sep 17 00:00:00 2001 From: itbf9 Date: Tue, 31 Oct 2023 13:48:47 +0100 Subject: [PATCH 143/419] Add export menu component --- .../export-menu/export-menu.component.html | 2 ++ .../export-menu/export-menu.component.ts | 36 +++++++++++++++++++ .../export-menu/export-menu.constants.ts | 13 +++++++ 3 files changed, 51 insertions(+) create mode 100644 src/app/core/_components/menus/export-menu/export-menu.component.html create mode 100644 src/app/core/_components/menus/export-menu/export-menu.component.ts create mode 100644 src/app/core/_components/menus/export-menu/export-menu.constants.ts diff --git a/src/app/core/_components/menus/export-menu/export-menu.component.html b/src/app/core/_components/menus/export-menu/export-menu.component.html new file mode 100644 index 00000000..fe24b6ed --- /dev/null +++ b/src/app/core/_components/menus/export-menu/export-menu.component.html @@ -0,0 +1,2 @@ + \ No newline at end of file diff --git a/src/app/core/_components/menus/export-menu/export-menu.component.ts b/src/app/core/_components/menus/export-menu/export-menu.component.ts new file mode 100644 index 00000000..36962bf8 --- /dev/null +++ b/src/app/core/_components/menus/export-menu/export-menu.component.ts @@ -0,0 +1,36 @@ +/* eslint-disable @angular-eslint/component-selector */ +import { Component, OnInit, } from '@angular/core'; +import { BaseMenuComponent } from '../base-menu/base-menu.component'; +import { ExportMenuAction, ExportMenuLabel } from './export-menu.constants'; + + +@Component({ + selector: 'export-menu', + templateUrl: './export-menu.component.html' +}) +export class ExportMenuComponent extends BaseMenuComponent implements OnInit { + ngOnInit(): void { + this.actionMenuItems[0] = [ + { + label: ExportMenuLabel.EXCEL, + action: ExportMenuAction.EXCEL, + icon: 'file_download', + }, + { + label: ExportMenuLabel.CSV, + action: ExportMenuAction.CSV, + icon: 'file_download', + }, + { + label: ExportMenuLabel.PRINT, + action: ExportMenuAction.PRINT, + icon: 'print', + }, + { + label: ExportMenuLabel.COPY, + action: ExportMenuAction.COPY, + icon: 'content_copy', + }, + ]; + } +} \ No newline at end of file diff --git a/src/app/core/_components/menus/export-menu/export-menu.constants.ts b/src/app/core/_components/menus/export-menu/export-menu.constants.ts new file mode 100644 index 00000000..6b77e3b0 --- /dev/null +++ b/src/app/core/_components/menus/export-menu/export-menu.constants.ts @@ -0,0 +1,13 @@ +export class ExportMenuLabel { + static readonly EXCEL = 'Download Excel'; + static readonly CSV = 'Download CSV'; + static readonly PRINT = 'Print'; + static readonly COPY = 'Copy'; +} + +export class ExportMenuAction { + static readonly EXCEL = 'excel'; + static readonly CSV = 'csv'; + static readonly PRINT = 'print'; + static readonly COPY = 'copy'; +} From 50ecec9c0c0099eb2ff4d27317c593643a712df0 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Tue, 31 Oct 2023 13:49:24 +0100 Subject: [PATCH 144/419] Register export menu component --- src/app/core/_components/core-components.module.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/app/core/_components/core-components.module.ts b/src/app/core/_components/core-components.module.ts index fbfae98e..a8ebfc51 100644 --- a/src/app/core/_components/core-components.module.ts +++ b/src/app/core/_components/core-components.module.ts @@ -26,6 +26,7 @@ import { MatMenuModule } from '@angular/material/menu'; import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; import { MatListModule } from '@angular/material/list'; import { MatDividerModule } from '@angular/material/divider'; +import { ExportMenuComponent } from "./menus/export-menu/export-menu.component"; @NgModule({ @@ -37,6 +38,7 @@ import { MatDividerModule } from '@angular/material/divider'; ActionMenuComponent, RowActionMenuComponent, BulkActionMenuComponent, + ExportMenuComponent, ColumnSelectionDialogComponent, ], imports: [ @@ -70,6 +72,7 @@ import { MatDividerModule } from '@angular/material/divider'; ActionMenuComponent, RowActionMenuComponent, BulkActionMenuComponent, + ExportMenuComponent, ], providers: [ { provide: MAT_SNACK_BAR_DEFAULT_OPTIONS, useValue: { duration: 2500 } } From 74bb01f1ed3aa4a18ee12d417f99113b11e55dec Mon Sep 17 00:00:00 2001 From: itbf9 Date: Tue, 31 Oct 2023 13:49:58 +0100 Subject: [PATCH 145/419] Handle export action events --- src/app/core/_components/ht-table/ht-table.component.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/app/core/_components/ht-table/ht-table.component.ts b/src/app/core/_components/ht-table/ht-table.component.ts index 4ada3d17..32d1b886 100644 --- a/src/app/core/_components/ht-table/ht-table.component.ts +++ b/src/app/core/_components/ht-table/ht-table.component.ts @@ -116,6 +116,9 @@ export class HTTableComponent implements OnInit, AfterViewInit { /** Event emitter for when the user triggers a bulk action */ @Output() bulkActionClicked: EventEmitter> = new EventEmitter>(); + /** Event emitter for when the user triggers an export action */ + @Output() exportActionClicked: EventEmitter> = new EventEmitter>(); + /** Fetches user customizations */ private uiSettings: UISettingsUtilityClass @@ -167,6 +170,10 @@ export class HTTableComponent implements OnInit, AfterViewInit { this.bulkActionClicked.emit(event) } + exportAction(event: ActionMenuEvent): void { + this.exportActionClicked.emit(event) + } + /** * Sets the displayed columns based on user selection. * From 20796524ee7537ec858b3edc5b3ed24804169db3 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Tue, 31 Oct 2023 13:50:40 +0100 Subject: [PATCH 146/419] Use export menu in ht-table --- .../ht-table/ht-table.component.html | 34 ++++++------------- 1 file changed, 11 insertions(+), 23 deletions(-) diff --git a/src/app/core/_components/ht-table/ht-table.component.html b/src/app/core/_components/ht-table/ht-table.component.html index baa8bf87..bd3cc886 100644 --- a/src/app/core/_components/ht-table/ht-table.component.html +++ b/src/app/core/_components/ht-table/ht-table.component.html @@ -10,41 +10,29 @@
-
+ -
- + +
- - - - - - - + + + +
From 9f61fd1b628601e5676d4921a708b3c7cc0bc413 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Tue, 31 Oct 2023 13:54:57 +0100 Subject: [PATCH 147/419] Document exportActionClicked --- src/app/core/_components/ht-table/ht-table.component.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/app/core/_components/ht-table/ht-table.component.ts b/src/app/core/_components/ht-table/ht-table.component.ts index 32d1b886..d253c219 100644 --- a/src/app/core/_components/ht-table/ht-table.component.ts +++ b/src/app/core/_components/ht-table/ht-table.component.ts @@ -50,6 +50,7 @@ import { ActionMenuEvent } from '../menus/action-menu/action-menu.model'; * - `[filterFn]`: A custom filter function for advanced filtering. * - `(rowActionClicked)`: Emits an `ActionMenuEvent` when a row action is triggered. * - `(bulkActionClicked)`: Emits an `ActionMenuEvent` when a bulk action is triggered. + * - `(exportActionClicked)`: Emits an `ActionMenuEvent` when an export action is triggered. * * @see `BaseDataSource` for creating a data source. * @see `HTTableColumn` for defining column configurations. From 904e282ab0ceb5354093d2b234e5e94057810459 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Wed, 1 Nov 2023 14:09:32 +0100 Subject: [PATCH 148/419] Add interfaces for the exports --- src/app/core/_services/export/export.model.ts | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 src/app/core/_services/export/export.model.ts diff --git a/src/app/core/_services/export/export.model.ts b/src/app/core/_services/export/export.model.ts new file mode 100644 index 00000000..20a23681 --- /dev/null +++ b/src/app/core/_services/export/export.model.ts @@ -0,0 +1,5 @@ +export interface ExcelColumn { + header: string; + key: string; + width?: number +} \ No newline at end of file From ddcf6329e70709945cf11d3b966f6e8289b8c239 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Wed, 1 Nov 2023 14:10:38 +0100 Subject: [PATCH 149/419] New export utility class --- src/app/core/_services/export/export.util.ts | 97 ++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 src/app/core/_services/export/export.util.ts diff --git a/src/app/core/_services/export/export.util.ts b/src/app/core/_services/export/export.util.ts new file mode 100644 index 00000000..dacd893f --- /dev/null +++ b/src/app/core/_services/export/export.util.ts @@ -0,0 +1,97 @@ +import { Injectable } from "@angular/core"; +import { HTTableColumn } from "../../_components/ht-table/ht-table.models"; +import { ExcelColumn } from "./export.model"; + + +@Injectable({ + providedIn: 'root', +}) +export class ExportUtil { + + /** + * Converts table columns to Excel columns. + * + * @param tableColumns The table columns. + * @returns Excel columns. + */ + toExcelColumns(tableColumns: HTTableColumn[]): ExcelColumn[] { + return tableColumns.map((col: HTTableColumn) => { + return { + key: col.dataKey, + header: col.name, + } + }) + } + + /** + * Converts table columns to CSV columns. + * + * @param tableColumns The table columns. + * @returns CSV columns. + */ + toCsvColumns(tableColumns: HTTableColumn[]): string[] { + return tableColumns.map((col: HTTableColumn) => col.name) + } + + /** + * Converts data to rows for Excel export. + * + * @param tableColumns The table columns. + * @param rawData The data to be exported. + * @returns Rows for Excel export. + */ + async toExcelRows(tableColumns: HTTableColumn[], rawData: T[]): Promise { + let rowNum = 0 + const data: any[] = [] + + for (const row of rawData) { + data[rowNum] = {} + for (const column of tableColumns) { + data[rowNum][column.dataKey] = column.export ? await column.export(row) : '' + } + rowNum += 1 + } + + return data + } + + /** + * Converts data to rows for CSV export. + * + * @param tableColumns The table columns. + * @param rawData The data to be exported. + * @returns Rows for CSV export. + */ + async toCsvRows(tableColumns: HTTableColumn[], rawData: T[]): Promise { + const data: string[][] = [] + + for (const row of rawData) { + const rowData: string[] = []; + for (const column of tableColumns) { + rowData.push(column.export ? await column.export(row) : ''); + } + data.push(rowData); + } + + return data + } + + /** + * Downloads data as a file. + * + * @param data The data to download. + * @param fileName The name of the file. + * @param fileType The type of the file (e.g., 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' for Excel). + */ + download(data: ArrayBuffer, fileName: string, fileType: string): void { + const blob = new Blob([data], { type: fileType }); + const url = window.URL.createObjectURL(blob); + const a = document.createElement('a'); + + a.href = url; + a.download = fileName; + a.click(); + window.URL.revokeObjectURL(url); + document.body.removeChild(a); + } +} From fffa5f1985191f4469438a3aae16b886c8f53e88 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Wed, 1 Nov 2023 14:13:18 +0100 Subject: [PATCH 150/419] New export service --- .../core/_services/export/export.service.ts | 92 +++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 src/app/core/_services/export/export.service.ts diff --git a/src/app/core/_services/export/export.service.ts b/src/app/core/_services/export/export.service.ts new file mode 100644 index 00000000..681fda68 --- /dev/null +++ b/src/app/core/_services/export/export.service.ts @@ -0,0 +1,92 @@ +import { Injectable } from '@angular/core'; +import { Workbook } from 'exceljs'; +import { unparse } from 'papaparse'; +import { HTTableColumn } from '../../_components/ht-table/ht-table.models'; +import { ExportUtil } from './export.util'; +import { ExcelColumn } from './export.model'; +import { Clipboard } from '@angular/cdk/clipboard'; + +/** + * Service for exporting data to Excel, CSV formats and to the clipboard. + */ +@Injectable({ + providedIn: 'root', +}) +export class ExportService { + constructor(private exportUtil: ExportUtil, private clipboard: Clipboard) { } + + /** + * Exports data to an Excel file and triggers a download. + * + * @param fileName - The name of the Excel file without ext. + * @param tableColumns - Columns configuration for the export. + * @param rawData - The data to export. + */ + async toExcel(fileName: string, tableColumns: HTTableColumn[], rawData: T[]): Promise { + try { + const workbook = new Workbook(); + const worksheet = workbook.addWorksheet('Sheet 1'); + const columns: ExcelColumn[] = this.exportUtil.toExcelColumns(tableColumns); + + const data = await this.exportUtil.toExcelRows(tableColumns, rawData); + + if (data && data.length) { + worksheet.columns = columns; + worksheet.addRows(data); + + const buffer = await workbook.xlsx.writeBuffer(); + this.saveExcelFile(buffer, fileName); + } + } catch (error) { + console.error('Error during Excel export:', error); + } + } + + /** + * Exports data to a CSV file and triggers a download. + * + * @param fileName - The name of the CSV file without ext. + * @param tableColumns - Columns configuration for the export. + * @param rawData - The data to export. + */ + async toCsv(fileName: string, tableColumns: HTTableColumn[], rawData: T[]): Promise { + try { + const columns: string[] = this.exportUtil.toCsvColumns(tableColumns); + const data = await this.exportUtil.toCsvRows(tableColumns, rawData); + + if (data && data.length) { + const csv = unparse([columns, ...data]); + this.saveCsvFile(csv, fileName); + } + } catch (error) { + console.error('Error during CSV export:', error); + } + } + + /** + * Copies data to the clipboard. + * + * @param tableColumns - Columns configuration for the export. + * @param rawData - The data to export. + */ + async toClipboard(tableColumns: HTTableColumn[], rawData: T[]): Promise { + try { + const columns: string[] = this.exportUtil.toCsvColumns(tableColumns); + const data = await this.exportUtil.toCsvRows(tableColumns, rawData); + + const textToCopy = [columns, ...data].map((row: string[]) => row.join('\t')).join('\n'); + + this.clipboard.copy(textToCopy); + } catch (error) { + console.error('Error during Clipboard export:', error); + } + } + + private saveExcelFile(data: ArrayBuffer, fileName: string): void { + this.exportUtil.download(data, `${fileName}.xlsx`, 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); + } + + private saveCsvFile(data: ArrayBuffer, fileName: string): void { + this.exportUtil.download(data, `${fileName}.csv`, 'text/csv'); + } +} From 70d413f34d9c6ce2b43fb64c1837182b7db155b0 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Wed, 1 Nov 2023 14:14:20 +0100 Subject: [PATCH 151/419] Add export attribute to table col + remove print --- src/app/core/_components/ht-table/ht-table.models.ts | 1 + .../menus/export-menu/export-menu.component.ts | 11 ++++++----- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/app/core/_components/ht-table/ht-table.models.ts b/src/app/core/_components/ht-table/ht-table.models.ts index 749437a5..170db967 100644 --- a/src/app/core/_components/ht-table/ht-table.models.ts +++ b/src/app/core/_components/ht-table/ht-table.models.ts @@ -17,4 +17,5 @@ export interface HTTableColumn { render?: (data: any) => SafeHtml async?: (data: any) => Promise routerLink?: (data: any) => any[], + export?: (data: any) => Promise } \ No newline at end of file diff --git a/src/app/core/_components/menus/export-menu/export-menu.component.ts b/src/app/core/_components/menus/export-menu/export-menu.component.ts index 36962bf8..e5c6d2bf 100644 --- a/src/app/core/_components/menus/export-menu/export-menu.component.ts +++ b/src/app/core/_components/menus/export-menu/export-menu.component.ts @@ -21,11 +21,12 @@ export class ExportMenuComponent extends BaseMenuComponent implements OnInit { action: ExportMenuAction.CSV, icon: 'file_download', }, - { - label: ExportMenuLabel.PRINT, - action: ExportMenuAction.PRINT, - icon: 'print', - }, + // Not yet implemented + //{ + // label: ExportMenuLabel.PRINT, + // action: ExportMenuAction.PRINT, + // icon: 'print', + //}, { label: ExportMenuLabel.COPY, action: ExportMenuAction.COPY, From c3e27593731c715f901980bd203fad015897f801 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Wed, 1 Nov 2023 14:15:21 +0100 Subject: [PATCH 152/419] Add exceljs and papaparse for exports --- package-lock.json | 476 ++++++++++++++++++++++++++++++++++++++++++++-- package.json | 4 +- 2 files changed, 467 insertions(+), 13 deletions(-) diff --git a/package-lock.json b/package-lock.json index be937523..bc82f9cf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -49,6 +49,7 @@ "datatables.net-select": "^1.7.0", "datatables.net-select-dt": "^1.7.0", "echarts": "^5.4.2", + "exceljs": "^4.4.0", "hashtype-detector": "^0.0.6", "jquery": "^3.7.0", "jspdf": "^2.5.1", @@ -60,6 +61,7 @@ "ngx-lottie": "^10.0.0", "ngx-moment": "^6.0.2", "opentracing": "^0.14.7", + "papaparse": "^5.4.1", "pdfmake": "^0.2.7", "popper.js": "^1.16.1", "rxjs": "~7.8.1", @@ -3049,6 +3051,43 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@fast-csv/format": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/@fast-csv/format/-/format-4.3.5.tgz", + "integrity": "sha512-8iRn6QF3I8Ak78lNAa+Gdl5MJJBM5vRHivFtMRUWINdevNo00K7OXxS2PshawLKTejVwieIlPmK5YlLu6w4u8A==", + "dependencies": { + "@types/node": "^14.0.1", + "lodash.escaperegexp": "^4.1.2", + "lodash.isboolean": "^3.0.3", + "lodash.isequal": "^4.5.0", + "lodash.isfunction": "^3.0.9", + "lodash.isnil": "^4.0.0" + } + }, + "node_modules/@fast-csv/format/node_modules/@types/node": { + "version": "14.18.63", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.63.tgz", + "integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==" + }, + "node_modules/@fast-csv/parse": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/@fast-csv/parse/-/parse-4.3.6.tgz", + "integrity": "sha512-uRsLYksqpbDmWaSmzvJcuApSEe38+6NQZBUsuAyMZKqHxH0g1wcJgsKUvN3WC8tewaqFjBMMGrkHmC+T7k8LvA==", + "dependencies": { + "@types/node": "^14.0.1", + "lodash.escaperegexp": "^4.1.2", + "lodash.groupby": "^4.6.0", + "lodash.isfunction": "^3.0.9", + "lodash.isnil": "^4.0.0", + "lodash.isundefined": "^3.0.1", + "lodash.uniq": "^4.5.0" + } + }, + "node_modules/@fast-csv/parse/node_modules/@types/node": { + "version": "14.18.63", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.63.tgz", + "integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==" + }, "node_modules/@foliojs-fork/fontkit": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/@foliojs-fork/fontkit/-/fontkit-1.9.1.tgz", @@ -6402,6 +6441,56 @@ "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==" }, + "node_modules/archiver": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.2.tgz", + "integrity": "sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==", + "dependencies": { + "archiver-utils": "^2.1.0", + "async": "^3.2.4", + "buffer-crc32": "^0.2.1", + "readable-stream": "^3.6.0", + "readdir-glob": "^1.1.2", + "tar-stream": "^2.2.0", + "zip-stream": "^4.1.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/archiver-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz", + "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", + "dependencies": { + "glob": "^7.1.4", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^2.0.0" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/archiver/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/are-we-there-yet": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", @@ -6808,6 +6897,14 @@ "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", "dev": true }, + "node_modules/big-integer": { + "version": "1.6.51", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz", + "integrity": "sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==", + "engines": { + "node": ">=0.6" + } + }, "node_modules/big.js": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", @@ -6817,6 +6914,18 @@ "node": "*" } }, + "node_modules/binary": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/binary/-/binary-0.3.0.tgz", + "integrity": "sha512-D4H1y5KYwpJgK8wk1Cue5LLPgmwHKYSChkbspQg5JtVuR5ulGckxfR62H3AE9UDkdMC8yyXlqYihuz3Aqg2XZg==", + "dependencies": { + "buffers": "~0.1.1", + "chainsaw": "~0.1.0" + }, + "engines": { + "node": "*" + } + }, "node_modules/binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -6848,6 +6957,11 @@ "node": ">= 6" } }, + "node_modules/bluebird": { + "version": "3.4.7", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz", + "integrity": "sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==" + }, "node_modules/body-parser": { "version": "1.20.2", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", @@ -7079,7 +7193,6 @@ "version": "0.2.13", "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", - "dev": true, "engines": { "node": "*" } @@ -7097,6 +7210,22 @@ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" }, + "node_modules/buffer-indexof-polyfill": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/buffer-indexof-polyfill/-/buffer-indexof-polyfill-1.0.2.tgz", + "integrity": "sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A==", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/buffers": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz", + "integrity": "sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ==", + "engines": { + "node": ">=0.2.0" + } + }, "node_modules/builtins": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz", @@ -7287,6 +7416,17 @@ "node": ">=10.0.0" } }, + "node_modules/chainsaw": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz", + "integrity": "sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ==", + "dependencies": { + "traverse": ">=0.3.0 <0.4" + }, + "engines": { + "node": "*" + } + }, "node_modules/chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -7494,6 +7634,33 @@ "integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==", "dev": true }, + "node_modules/compress-commons": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.2.tgz", + "integrity": "sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==", + "dependencies": { + "buffer-crc32": "^0.2.13", + "crc32-stream": "^4.0.2", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/compress-commons/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/compressible": { "version": "2.0.18", "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", @@ -7829,6 +7996,42 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/crc32-stream": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.3.tgz", + "integrity": "sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==", + "dependencies": { + "crc-32": "^1.2.0", + "readable-stream": "^3.4.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/crc32-stream/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/critters": { "version": "0.0.20", "resolved": "https://registry.npmjs.org/critters/-/critters-0.0.20.tgz", @@ -8181,6 +8384,11 @@ "node": ">=4.0" } }, + "node_modules/dayjs": { + "version": "1.11.10", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz", + "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==" + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -8638,7 +8846,6 @@ "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, "dependencies": { "once": "^1.4.0" } @@ -9386,6 +9593,38 @@ "node": ">=0.8.x" } }, + "node_modules/exceljs": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/exceljs/-/exceljs-4.4.0.tgz", + "integrity": "sha512-XctvKaEMaj1Ii9oDOqbW/6e1gXknSY4g/aLCDicOXqBE4M0nRWkUu0PTp++UPNzoFY12BNHMfs/VadKIS6llvg==", + "dependencies": { + "archiver": "^5.0.0", + "dayjs": "^1.8.34", + "fast-csv": "^4.3.1", + "jszip": "^3.10.1", + "readable-stream": "^3.6.0", + "saxes": "^5.0.1", + "tmp": "^0.2.0", + "unzipper": "^0.10.11", + "uuid": "^8.3.0" + }, + "engines": { + "node": ">=8.3.0" + } + }, + "node_modules/exceljs/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/execa": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", @@ -9644,6 +9883,18 @@ "@types/yauzl": "^2.9.1" } }, + "node_modules/fast-csv": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/fast-csv/-/fast-csv-4.3.6.tgz", + "integrity": "sha512-2RNSpuwwsJGP0frGsOmTb9oUF+VkFSM4SyLTDgwf2ciHWTarN0lQTC+F2f/t5J9QjW+c65VFIAAu85GsvMIusw==", + "dependencies": { + "@fast-csv/format": "4.3.5", + "@fast-csv/parse": "4.3.6" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -9991,8 +10242,7 @@ "node_modules/fs-constants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", - "dev": true + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" }, "node_modules/fs-extra": { "version": "11.1.1", @@ -10043,6 +10293,31 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/fstream": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz", + "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==", + "dependencies": { + "graceful-fs": "^4.1.2", + "inherits": "~2.0.0", + "mkdirp": ">=0.5 0", + "rimraf": "2" + }, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/fstream/node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, "node_modules/function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", @@ -11981,6 +12256,17 @@ "shell-quote": "^1.7.3" } }, + "node_modules/lazystream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", + "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", + "dependencies": { + "readable-stream": "^2.0.5" + }, + "engines": { + "node": ">= 0.6.3" + } + }, "node_modules/less": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/less/-/less-4.1.3.tgz", @@ -12137,6 +12423,11 @@ "node": "^12.20.0 || ^14.13.1 || >=16.0.0" } }, + "node_modules/listenercount": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/listenercount/-/listenercount-1.0.1.tgz", + "integrity": "sha512-3mk/Zag0+IJxeDrxSgaDPy4zZ3w05PRZeJNnlWhzFz5OkX49J4krc+A8X2d2M69vGMBEX0uyl8M+W+8gH+kBqQ==" + }, "node_modules/loader-runner": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", @@ -12219,6 +12510,61 @@ "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", "dev": true }, + "node_modules/lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==" + }, + "node_modules/lodash.difference": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", + "integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==" + }, + "node_modules/lodash.escaperegexp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz", + "integrity": "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==" + }, + "node_modules/lodash.flatten": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==" + }, + "node_modules/lodash.groupby": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.groupby/-/lodash.groupby-4.6.0.tgz", + "integrity": "sha512-5dcWxm23+VAoz+awKmBaiBvzox8+RqMgFhi7UvX9DHZr2HdxHXM/Wrf8cfKpsW37RNrvtPn6hSwNqurSILbmJw==" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==" + }, + "node_modules/lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==" + }, + "node_modules/lodash.isfunction": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz", + "integrity": "sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==" + }, + "node_modules/lodash.isnil": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/lodash.isnil/-/lodash.isnil-4.0.0.tgz", + "integrity": "sha512-up2Mzq3545mwVnMhTDMdfoG1OurpA/s5t88JmQX809eH3C8491iu2sfKhTfhQtKY78oPNhiaHJUpT/dUDAAtng==" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" + }, + "node_modules/lodash.isundefined": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash.isundefined/-/lodash.isundefined-3.0.1.tgz", + "integrity": "sha512-MXB1is3s899/cD8jheYYE2V9qTHwKvt+npCwpD+1Sxm3Q3cECXCiYHjeHWXNwr6Q0SOBPrYUDxendrO6goVTEA==" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -12230,6 +12576,16 @@ "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", "integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==" }, + "node_modules/lodash.union": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", + "integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==" + }, + "node_modules/lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==" + }, "node_modules/lodash.uniqby": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.uniqby/-/lodash.uniqby-4.5.0.tgz", @@ -12724,7 +13080,6 @@ "version": "0.5.6", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "dev": true, "dependencies": { "minimist": "^1.2.6" }, @@ -14362,6 +14717,11 @@ "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" }, + "node_modules/papaparse": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.4.1.tgz", + "integrity": "sha512-HipMsgJkZu8br23pW15uvo6sib6wne/4woLZPlFf3rpDyMe9ywEXUsuD7+6K9PRkJlVT51j/sCOYDKGGS3ZJrw==" + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -15386,6 +15746,33 @@ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" }, + "node_modules/readdir-glob": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz", + "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", + "dependencies": { + "minimatch": "^5.1.0" + } + }, + "node_modules/readdir-glob/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/readdir-glob/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -15770,7 +16157,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", - "dev": true, "dependencies": { "xmlchars": "^2.2.0" }, @@ -17207,7 +17593,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", - "dev": true, "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", @@ -17223,7 +17608,6 @@ "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -17439,7 +17823,6 @@ "version": "0.2.1", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", - "dev": true, "dependencies": { "rimraf": "^3.0.0" }, @@ -17538,6 +17921,14 @@ "node": ">=8" } }, + "node_modules/traverse": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz", + "integrity": "sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ==", + "engines": { + "node": "*" + } + }, "node_modules/tree-kill": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", @@ -18069,6 +18460,23 @@ "node": ">= 0.8" } }, + "node_modules/unzipper": { + "version": "0.10.14", + "resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.10.14.tgz", + "integrity": "sha512-ti4wZj+0bQTiX2KmKWuwj7lhV+2n//uXEotUmGuQqrbVZSEGFMbI68+c6JCQ8aAmUWYvtHEz2A8K6wXvueR/6g==", + "dependencies": { + "big-integer": "^1.6.17", + "binary": "~0.3.0", + "bluebird": "~3.4.1", + "buffer-indexof-polyfill": "~1.0.0", + "duplexer2": "~0.1.4", + "fstream": "^1.0.12", + "graceful-fs": "^4.2.2", + "listenercount": "~1.0.1", + "readable-stream": "~2.3.6", + "setimmediate": "~1.0.4" + } + }, "node_modules/update-browserslist-db": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", @@ -18142,7 +18550,6 @@ "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true, "bin": { "uuid": "dist/bin/uuid" } @@ -18871,8 +19278,7 @@ "node_modules/xmlchars": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", - "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", - "dev": true + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==" }, "node_modules/xmldoc": { "version": "1.3.0", @@ -18963,6 +19369,52 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/zip-stream": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.1.tgz", + "integrity": "sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==", + "dependencies": { + "archiver-utils": "^3.0.4", + "compress-commons": "^4.1.2", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/zip-stream/node_modules/archiver-utils": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-3.0.4.tgz", + "integrity": "sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==", + "dependencies": { + "glob": "^7.2.3", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/zip-stream/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/zone.js": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.13.1.tgz", diff --git a/package.json b/package.json index 29c133f9..b45054a9 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ "datatables.net-select": "^1.7.0", "datatables.net-select-dt": "^1.7.0", "echarts": "^5.4.2", + "exceljs": "^4.4.0", "hashtype-detector": "^0.0.6", "jquery": "^3.7.0", "jspdf": "^2.5.1", @@ -65,6 +66,7 @@ "ngx-lottie": "^10.0.0", "ngx-moment": "^6.0.2", "opentracing": "^0.14.7", + "papaparse": "^5.4.1", "pdfmake": "^0.2.7", "popper.js": "^1.16.1", "rxjs": "~7.8.1", @@ -100,4 +102,4 @@ "puppeteer": "^20.9.0", "typescript": "~5.1.6" } -} \ No newline at end of file +} From 856c4d4270ccfdfb68f9b2ac113e84aa17ea5b5f Mon Sep 17 00:00:00 2001 From: itbf9 Date: Fri, 3 Nov 2023 12:29:47 +0100 Subject: [PATCH 153/419] Allow routerLink's in action menu + flag active item --- .../action-menu/action-menu.component.html | 5 +- .../action-menu/action-menu.component.ts | 88 +++++++++++++++++-- .../menus/action-menu/action-menu.model.ts | 5 +- 3 files changed, 87 insertions(+), 11 deletions(-) diff --git a/src/app/core/_components/menus/action-menu/action-menu.component.html b/src/app/core/_components/menus/action-menu/action-menu.component.html index 80d445c4..64028692 100644 --- a/src/app/core/_components/menus/action-menu/action-menu.component.html +++ b/src/app/core/_components/menus/action-menu/action-menu.component.html @@ -1,5 +1,5 @@ - diff --git a/src/app/core/_components/menus/action-menu/action-menu.component.ts b/src/app/core/_components/menus/action-menu/action-menu.component.ts index afb781ed..b724a29b 100644 --- a/src/app/core/_components/menus/action-menu/action-menu.component.ts +++ b/src/app/core/_components/menus/action-menu/action-menu.component.ts @@ -1,30 +1,106 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @angular-eslint/component-selector */ -import { Component, EventEmitter, Input, Output } from '@angular/core'; +import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core'; import { ActionMenuEvent, ActionMenuItem } from './action-menu.model'; +import { ActivatedRoute, NavigationEnd, Router } from '@angular/router'; +import { Subscription } from 'rxjs'; +/** + * Component representing an action menu with a list of menu items. + * + * Each action menu item can have either an `action` or a `routerLink`. If + * the menu item `action` attribute is set an event is emitted with the + * menu item itself and the `data` privided when clicked. If the `routerLink` + * attribute is set the user will be routed on click. + * + * The actionMenuItems are divided into sections by providing them in separate + * arrays like this: `[[A, B, C], [D, E, F]]`. This will generate two sections + * separetad by a divider. + * + * @example + * + */ @Component({ selector: 'action-menu', templateUrl: './action-menu.component.html' }) -export class ActionMenuComponent { +export class ActionMenuComponent implements OnInit, OnDestroy { + private subscriptions: Subscription[] = [] + + currentUrl: any[]; + isActive = false + + /** Icon to be displayed in the menu button. */ @Input() icon: string; + /** Label for the menu button. */ @Input() label: string; + /** Determines if the menu button is disabled. */ @Input() disabled = false; + /** Custom CSS classes for styling. */ @Input() cls = '' + /** Custom data to be associated with the menu. */ @Input() data: any; + /** Two-dimensional array of sections / menu items. */ @Input() actionMenuItems: ActionMenuItem[][] = [] @Output() menuItemClicked: EventEmitter> = new EventEmitter>(); + constructor(private router: Router, private route: ActivatedRoute) { } + + ngOnInit(): void { + this.subscriptions.push(this.router.events.subscribe((event: any) => { + if (event instanceof NavigationEnd) { + this.currentUrl = event.url.split('/').slice(1) + this.checkIsActive() + } + })) + } + + ngOnDestroy(): void { + for (const sub of this.subscriptions) { + sub.unsubscribe() + } + } + + /** + * Checks if a menu item should be marked as active based on the current URL. + */ + checkIsActive(): void { + this.isActive = false + for (const section of this.actionMenuItems) { + if (this.isActive) { + break; + } + for (const item of section) { + if (item.routerLink && item.routerLink.length === this.currentUrl.length && + item.routerLink.every((value, index) => value === this.currentUrl[index])) { + this.isActive = true + break; + } + } + } + } + /** * Handle the click event when a menu item is selected. * @param menuItem - The selected menu item. */ onMenuItemClick(menuItem: ActionMenuItem): void { - this.menuItemClicked.emit({ - menuItem: menuItem, - data: this.data - }); + if (menuItem.routerLink) { + this.router.navigate(menuItem.routerLink) + } else { + this.menuItemClicked.emit({ + menuItem: menuItem, + data: this.data + }); + } } } \ No newline at end of file diff --git a/src/app/core/_components/menus/action-menu/action-menu.model.ts b/src/app/core/_components/menus/action-menu/action-menu.model.ts index 013dcf10..9c2c2928 100644 --- a/src/app/core/_components/menus/action-menu/action-menu.model.ts +++ b/src/app/core/_components/menus/action-menu/action-menu.model.ts @@ -5,7 +5,8 @@ export interface ActionMenuEvent { export interface ActionMenuItem { label: string - action: string + action?: string icon?: string red?: boolean -} \ No newline at end of file + routerLink?: any[] +} \ No newline at end of file From 613211358f60354e421c5b986b2ba1d8717f391c Mon Sep 17 00:00:00 2001 From: itbf9 Date: Fri, 3 Nov 2023 12:38:41 +0100 Subject: [PATCH 154/419] Add a breakpoint service for detecting screen size --- .../_services/shared/breakpoint.service.ts | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 src/app/core/_services/shared/breakpoint.service.ts diff --git a/src/app/core/_services/shared/breakpoint.service.ts b/src/app/core/_services/shared/breakpoint.service.ts new file mode 100644 index 00000000..08f79bd3 --- /dev/null +++ b/src/app/core/_services/shared/breakpoint.service.ts @@ -0,0 +1,75 @@ +import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout'; +import { Injectable } from '@angular/core'; +import { Observable, map } from 'rxjs'; + +/** + * Service for detecting the current screen breakpoint using Angular's CDK BreakpointObserver. + * It provides methods to check for specific breakpoints such as XSmall, Small, Medium, Large, and XLarge. + * + * @example + * // Inject the BreakpointService into your component or service and use it to observe breakpoints. + * constructor(private breakpointService: BreakpointService) { + * this.breakpointService.isXSmall().subscribe(isXSmall => { + * if (isXSmall) { + * // Actions for XSmall breakpoint + * } + * }); + * } + */ +@Injectable({ + providedIn: 'root', +}) +export class BreakpointService { + + constructor(private breakpointObserver: BreakpointObserver) { } + + /** + * Observes the XSmall breakpoint. + * @returns An Observable that emits a boolean value indicating whether the XSmall breakpoint is active. + */ + isXSmall(): Observable { + return this.is(Breakpoints.XSmall) + } + + /** + * Observes the Small breakpoint. + * @returns An Observable that emits a boolean value indicating whether the Small breakpoint is active. + */ + isSmall(): Observable { + return this.is(Breakpoints.Small) + } + + /** + * Observes the Medium breakpoint. + * @returns An Observable that emits a boolean value indicating whether the Medium breakpoint is active. + */ + isMedium(): Observable { + return this.is(Breakpoints.Medium) + } + + /** + * Observes the Large breakpoint. + * @returns An Observable that emits a boolean value indicating whether the Large breakpoint is active. + */ + isLarge(): Observable { + return this.is(Breakpoints.Large) + } + + /** + * Observes the XLarge breakpoint. + * @returns An Observable that emits a boolean value indicating whether the XLarge breakpoint is active. + */ + isXLarge(): Observable { + return this.is(Breakpoints.XLarge) + } + + /** + * Internal method to observe a specific breakpoint. + * @param breakpoint - The breakpoint to observe, such as Breakpoints.XSmall. + * @returns An Observable that emits a boolean value indicating whether the specified breakpoint is active. + */ + private is(breakpoint: string): Observable { + return this.breakpointObserver.observe(breakpoint) + .pipe(map(result => result.matches)); + } +} \ No newline at end of file From 6c2dbd288426150aaf139b1b241d384d501c17ae Mon Sep 17 00:00:00 2001 From: itbf9 Date: Fri, 3 Nov 2023 15:54:51 +0100 Subject: [PATCH 155/419] Update header component to use material theme --- src/app/layout/header/header.component.html | 206 +------------ src/app/layout/header/header.component.ts | 309 +++++++++++++++----- src/app/layout/header/header.model.ts | 6 + 3 files changed, 245 insertions(+), 276 deletions(-) create mode 100644 src/app/layout/header/header.model.ts diff --git a/src/app/layout/header/header.component.html b/src/app/layout/header/header.component.html index 6e4f8438..4683fa09 100644 --- a/src/app/layout/header/header.component.html +++ b/src/app/layout/header/header.component.html @@ -1,197 +1,9 @@ - + + Hashtopolis + {{ this.headerConfig.brand.name }} + + + + + \ No newline at end of file diff --git a/src/app/layout/header/header.component.ts b/src/app/layout/header/header.component.ts index fb08690d..2db42f48 100644 --- a/src/app/layout/header/header.component.ts +++ b/src/app/layout/header/header.component.ts @@ -1,115 +1,266 @@ -import { faServer, faTasks, faDatabase, faFileArchive, faCogs, faUserGroup,faPowerOff, faSun, faMoon, faUserCircle, faInbox, faQuestionCircle, faBell, faEye, faExchange, faArrowsH, faCog, faFileCode } from '@fortawesome/free-solid-svg-icons'; -import { Component, OnDestroy, OnInit, Renderer2, ViewChild } from '@angular/core'; +import { Component, OnDestroy, OnInit } from '@angular/core'; import { environment } from './../../../environments/environment'; -import { NgbDropdown } from '@ng-bootstrap/ng-bootstrap'; import { Subscription } from 'rxjs'; - -import { ThemeService } from 'src/app/core/_services/shared/theme.service'; import { AuthService } from '../../core/_services/access/auth.service'; +import { MainMenuItem } from './header.model'; +import { ActionMenuItem } from 'src/app/core/_components/menus/action-menu/action-menu.model'; + @Component({ selector: 'app-header', templateUrl: './header.component.html' }) - export class HeaderComponent implements OnInit, OnDestroy { - headerConfig = environment.config.header; + private subscriptions: Subscription[] = [] + headerConfig = environment.config.header; isAuthentificated = false; - isMobile = false; - private userSub: Subscription; - public dropdown: NgbDropdown; - storedToggletheme:string = localStorage.getItem('toggledarkmode'); - storedWidthScreentheme:string = localStorage.getItem('screenmode') || 'true'; - - // Icons User Menu - faFileArchive=faFileArchive; - faUserGroup=faUserGroup; - faDatabase=faDatabase; - faFileCode=faFileCode; - faServer=faServer; - faTasks=faTasks; - faCogs=faCogs; - // SubMenu User - faQuestionCircle=faQuestionCircle; - faUserCircle=faUserCircle; - faPowerOff=faPowerOff; - faExchange=faExchange; - faArrowsH=faArrowsH; - faInbox=faInbox; - faMoon=faMoon; - faBell=faBell; - faCog=faCog; - faSun=faSun; - faEye=faEye; - - public notifbell: {title: string, description: string, datetime: string}[] = []; - - constructor( - private authService: AuthService, - private theme: ThemeService, - private ren: Renderer2, - ) { } - - collapsed = true; - public toggleCollapsed(): void { - this.collapsed = !this.collapsed; - } - - public currentTheme(): string { - return this.theme.current; - } + mainMenu: MainMenuItem[] = [ + this.getAgentsMenu(), + this.getTasksMenu(), + this.getHashlistsMenu(), + this.getFilesMenu(), + this.getBinariesMenu(), + this.getConfigMenu(), + this.getUsersMenu(), + this.getAdminMenu() + ] - public getUser(){ - const userData: { _username: string} = JSON.parse(localStorage.getItem('userData')); - return userData._username; - } + constructor(private authService: AuthService) { } ngOnInit(): void { - this.userSub = this.authService.user - .subscribe(user => { - this.isAuthentificated = !!user; - }); + this.subscriptions.push(this.authService.user + .subscribe(user => { + this.isAuthentificated = !!user; + }) + ) } ngOnDestroy(): void { - this.userSub.unsubscribe(); + for (const sub of this.subscriptions) { + sub.unsubscribe(); + } } - onLogOut(){ + onLogOut(): void { this.authService.logOut(); } - switchMode(){ - if(this.storedToggletheme === 'dark'){ - localStorage.setItem('toggledarkmode','light') - this.storedToggletheme = localStorage.getItem('toggledarkmode'); - }else{ - localStorage.setItem('toggledarkmode','dark') - this.storedToggletheme = localStorage.getItem('toggledarkmode'); + /** + * Retrieves the 'Agents' menu item. + * @returns A MainMenuItem for the 'Agents' menu. + */ + getAgentsMenu(): MainMenuItem { + return { + label: 'Agents', + actions: [[ + { + label: 'Show Agents', + routerLink: ['agents', 'show-agents'] + }, + { + label: 'Agent Status', + routerLink: ['agents', 'agent-status'] + } + ]] } } - switchScreen(){ - if(this.storedWidthScreentheme === 'true'){ - localStorage.setItem('screenmode','false') - this.storedWidthScreentheme = localStorage.getItem('screenmode'); - }else{ - localStorage.setItem('screenmode','true') - this.storedWidthScreentheme = localStorage.getItem('screenmode'); + /** + * Retrieves the 'Tasks' menu item. + * @returns A MainMenuItem for the 'Tasks' menu. + */ + getTasksMenu(): MainMenuItem { + return { + label: 'Tasks', + actions: [[ + { + label: 'Show Tasks', + routerLink: ['tasks', 'show-tasks'] + }, + { + label: 'Preconfigured Tasks', + routerLink: ['tasks', 'preconfigured-tasks'] + }, + { + label: 'Supertasks', + routerLink: ['tasks', 'supertasks'] + }, + { + label: 'Import Supertask', + routerLink: ['tasks', 'import-supertasks', 'masks'] + }, + { + label: 'Chunk activity', + routerLink: ['tasks', 'chunks'] + }, + ]] } - location.reload(); } + /** + * Retrieves the 'Hashlists' menu item. + * @returns A MainMenuItem for the 'Hashlists' menu. + */ + getHashlistsMenu(): MainMenuItem { + return { + label: 'Hashlists', + actions: [[ + { + label: 'Hashlists', + routerLink: ['hashlists', 'hashlist'] + }, + { + label: 'Superhashlists', + routerLink: ['hashlists', 'superhashlist'] + }, + { + label: 'Search Hash', + routerLink: ['hashlists', 'search-hash'] + }, + { + label: 'Show cracks', + routerLink: ['hashlists', 'show-cracks'] + } + ]] + } + } - onMouseEnter(drop:NgbDropdown){ - drop.open() + /** + * Retrieves the 'Files' menu item. + * @returns A MainMenuItem for the 'Files' menu. + */ + getFilesMenu(): MainMenuItem { + return { + label: 'Files', + actions: [[ + { + label: 'Wordlists', + routerLink: ['files', 'wordlist'] + }, + { + label: 'Rules', + routerLink: ['files', 'rules'] + }, + { + label: 'Other', + routerLink: ['files', 'other'] + }, + ]] + } } - onMouseLeave(drop:NgbDropdown){ - drop.close() + /** + * Retrieves the 'Admin' menu item. + * @returns A MainMenuItem for the 'Admin' menu. + */ + getAdminMenu(): MainMenuItem { + return { + label: 'Admin', + actions: [ + [ + { + label: 'Account Settings', + routerLink: ['account', 'acc-settings'] + }, + { + label: 'UI Settings', + routerLink: ['account', 'ui-settings'] + }, + { + label: 'Notifications', + routerLink: ['account', 'notifications'] + }, + { + label: 'Support', + routerLink: ['https://discord.com/channels/419123475538509844/419123475538509846'] + }, + ] + ] + }; } + /** + * Retrieves the 'Users' menu item. + * @returns A MainMenuItem for the 'Users' menu. + */ + getUsersMenu(): MainMenuItem { + return { + label: 'Users', + actions: [ + [ + { + label: 'All users', + routerLink: ['users', 'all-users'] + }, + { + label: 'Global Permissions', + routerLink: ['users', 'global-permissions-groups'] + }, + { + label: 'Access Groups', + routerLink: ['users', 'access-groups'] + }, + ] + ] + }; + } + + /** + * Retrieves the 'Config' menu item. + * @returns A MainMenuItem for the 'Config' menu. + */ + getConfigMenu(): MainMenuItem { + return { + label: 'Config', + actions: [ + [ + { + label: 'Settings', + routerLink: ['config', 'agent'] + }, + { + label: 'Hashtypes', + routerLink: ['config', 'hashtypes'] + }, + { + label: 'Health Checks', + routerLink: ['config', 'health-checks'] + }, + { + label: 'Log', + routerLink: ['config', 'log'] + }, + ] + ] + }; + } + + /** + * Retrieves the 'Binaries' menu item. + * @returns A MainMenuItem for the 'Binaries' menu. + */ + getBinariesMenu(): MainMenuItem { + return { + label: 'Binaries', + actions: [ + [ + { + label: 'Crackers', + routerLink: ['config', 'engine', 'crackers'] + }, + { + label: 'Preprocessors', + routerLink: ['config', 'engine', 'preprocessors'] + }, + { + label: 'Agent Binaries', + routerLink: ['config', 'engine', 'agent-binaries'] + }, + ] + ] + }; + } } diff --git a/src/app/layout/header/header.model.ts b/src/app/layout/header/header.model.ts new file mode 100644 index 00000000..29483e2b --- /dev/null +++ b/src/app/layout/header/header.model.ts @@ -0,0 +1,6 @@ +import { ActionMenuItem } from "src/app/core/_components/menus/action-menu/action-menu.model" + +export interface MainMenuItem { + label: string + actions: ActionMenuItem[][] +} \ No newline at end of file From 00ad6741d9f783fad0eecc1d64740f860fcfde7a Mon Sep 17 00:00:00 2001 From: itbf9 Date: Fri, 3 Nov 2023 15:56:54 +0100 Subject: [PATCH 156/419] update header style --- src/styles/layout/_header.scss | 696 ++------------------------------- 1 file changed, 28 insertions(+), 668 deletions(-) diff --git a/src/styles/layout/_header.scss b/src/styles/layout/_header.scss index 03d24099..8f83916c 100644 --- a/src/styles/layout/_header.scss +++ b/src/styles/layout/_header.scss @@ -1,683 +1,43 @@ -/* ================================== - SECTION HEADER - ================================== */ - -/* - 01- Main Header Navbar -*/ -/* smartphones, iPhone, portrait 480x320 phones */ -/* portrait e-readers (Nook/Kindle), smaller tablets @ 600 or @ 640 wide. */ -/* portrait tablets, portrait iPad, landscape e-readers, landscape 800x480 or 854x480 phones */ -@media (min-width:320px) and (min-width:481px) and (min-width:641px) and (max-width: 767px) { - .top-cat { - font-weight: 700; - cursor: pointer; - position: relative; - display: inline-block; - font-size: 14px; - margin: 12px 12px 0 0; - padding: 2px 20px; - border-radius: 100px; - text-decoration: none; - color: #353539; - width: auto; - } - .top-cat:hover { - border-color: #4B5563; - } - .top-cat-sub a { - text-decoration: none; - display: block; - color: #353539; - margin: 0 auto; - font-size: 11px; - padding: -1px 10px; - } -} - -/* tablet, landscape iPad */ -@media (min-width:767px) { - .top-cat { - font-weight: 450; - cursor: pointer; - position: relative; - display: inline-block; - border-radius: 100px; - background-color: #F9F9F9; - margin: 1px 1px 0 0; - height: 20px; - line-height: 21px; - font-size: 13px; - padding: 0px 5px; - color: #353539; - text-decoration: none; - width: auto; - } - .top-cat:hover { - margin: 0px 2px 0 0; - border-color: #4B5563; - } - .top-cat-sub a { - text-decoration: none; - display: block; - color: #353539; - margin: 0 auto; - padding: px 20px; - font-weight: 700; - font-size: 12px; - padding: -3px 10px; - } -} -/* Lo-res laptops ands desktops */ -@media (min-width:890px) { - .top-cat { - font-weight: 550; - cursor: pointer; - position: relative; - display: inline-block; - border-radius: 100px; - background-color: #F9F9F9; - margin: 1px 1px 0 0; - height: 30px; - line-height: 31px; - font-size: 14px; - padding: 0px 5px; - color: #353539; - text-decoration: none; - width: auto; - } - .top-cat:hover { - margin: 0px 2px 0 0; - border-color: #4B5563; - } - .top-cat-sub a { - text-decoration: none; - display: block; - color: #353539; - margin: 0 auto; - padding: px 20px; - font-weight: 700; - font-size: 12px; - padding: -3px 10px; - } -} - /* big landscape tablets, laptops, desktops and hi-res devices */ -@media (min-width:990px) { - .top-cat { - font-weight: 700; - cursor: pointer; - position: relative; - display: inline-block; - background-color: #F9F9F9; - font-size: 14px; - height: 38px; - line-height: 38px; - margin: 12px 12px 0 0; - padding: 2px 14px; - border-radius: 100px; - text-decoration: none; - color: #353539; - width: auto; - } - .top-cat:hover { - margin: 11px 12px 0 0; - border-color: #4B5563; - } - .top-cat-sub a { - text-decoration: none; - display: block; - color: #353539; - margin: 0 auto; - padding: px 20px; - font-weight: 700; - font-size: 13px; - padding: -1px 10px; - } -} - -.top-cat-selected { - border-color: #4B5563; -} - -.top-nav-cat { - height: 35px; - line-height: 35px; -} - -.dropdown-menu-end { - --bs-position: end; -} - -.dropdown-menu-end[data-bs-popper] { - right: 0; - left: auto; -} - -.dropdown-menu-end:before { - right: 20px; left: auto; -} - -.dropdown-item { - display: block; - width: 100%; - padding: 0.25rem 1rem; - clear: both; - font-weight: 400; - color: #111827; - text-align: inherit; - white-space: nowrap; - background-color: transparent; - border: 0; - overflow: hidden; - position: relative; -} - -.dropdown-item:hover, .dropdown-item:focus { - color: #090d15; - background-color: #F2F4F6; -} - -.dropdown-item.active, .dropdown-item:active { - color: #111827; - text-decoration: none; - background-color: #E5E7EB; -} - -.dropdown-item.disabled, .dropdown-item:disabled { - color: #4B5563; - pointer-events: none; - background-color: transparent; -} - -.dropdown-menu .dropdown-header .dropdown-icon, -.dropdown-menu .dropdown-item .dropdown-icon { - height: 1.25rem; - width: 1.25rem; -} - -.navbar { - position: relative; - display: -webkit-box; - display: -ms-flexbox; - display: flex; - -ms-flex-wrap: wrap; - flex-wrap: wrap; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - -webkit-box-pack: justify; - -ms-flex-pack: justify; - justify-content: space-between; - padding-top: 0.5rem; - padding-left: 0rem; - padding-bottom: 0.5rem; - // padding-right: 1rem; -} - -.navbar > .container, -.navbar > .container-fluid, .navbar > .container-sm, .navbar > .container-md, .navbar > .container-lg, .navbar > .container-xl, .navbar > .container-xxl { - display: -webkit-box; - display: -ms-flexbox; - display: flex; - -ms-flex-wrap: inherit; - flex-wrap: inherit; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - -webkit-box-pack: justify; - -ms-flex-pack: justify; - justify-content: space-between; -} - -.navbar-brand { - padding-top: -0.046rem; - padding-bottom: -0.046rem; - margin-left: 1rem; - font-size: calc(1.2978rem + 0.5736vw); - white-space: nowrap; -} - -@media (min-width: 1200px) { - .navbar-brand { - font-size: 1.728rem; - } -} - -.navbar-brand:hover, .navbar-brand:focus { - text-decoration: none; -} - -.navbar-nav { - display: -webkit-box; - display: -ms-flexbox; - display: flex; - -webkit-box-orient: vertical; - -webkit-box-direction: normal; - -ms-flex-direction: column; - flex-direction: column; - padding-right: 0; - margin-bottom: 0; - list-style: none; -} - -.navbar-nav .nav-link { - padding-left: 0; - padding-right: 0; -} - -.navbar-nav .dropdown-menu { - position: static; -} - -.navbar-text { - padding-top: 0.5rem; - padding-bottom: 0.5rem; -} - -.navbar-collapse { - -ms-flex-preferred-size: 100%; - flex-basis: 100%; - -webkit-box-flex: 1; - -ms-flex-positive: 1; - flex-grow: 1; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; -} - -.navbar-toggler { - padding: 0.25rem 0.75rem; - font-size: 1.2rem; - line-height: 1; - background-color: transparent; - border: 1px solid transparent; - border-radius: 0.125rem; - -webkit-transition: -webkit-box-shadow 0.15s ease-in-out; - transition: -webkit-box-shadow 0.15s ease-in-out; - -o-transition: box-shadow 0.15s ease-in-out; - transition: box-shadow 0.15s ease-in-out; - transition: box-shadow 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out; -} - -@media (prefers-reduced-motion: reduce) { - .navbar-toggler { - -webkit-transition: none; - -o-transition: none; - transition: none; - } -} - -.navbar-toggler:hover { - text-decoration: none; -} - -.navbar-toggler:focus { - text-decoration: none; - outline: 0; - -webkit-box-shadow: 0 0 0 0; - box-shadow: 0 0 0 0; -} - -.navbar-toggler-icon { - display: inline-block; - width: 1.5em; - height: 1.5em; - vertical-align: middle; - background-repeat: no-repeat; - background-position: center; - background-size: 100%; -} - -.navbar-nav-scroll { - max-height: var(--bs-scroll-height, 75vh); - overflow-y: auto; -} - -@media (min-width: 576px) { - .navbar-expand-sm { - -ms-flex-wrap: nowrap; - flex-wrap: nowrap; - -webkit-box-pack: start; - -ms-flex-pack: start; - justify-content: flex-start; - } - .navbar-expand-sm .navbar-nav { - -webkit-box-orient: horizontal; - -webkit-box-direction: normal; - -ms-flex-direction: row; - flex-direction: row; - } - .navbar-expand-sm .navbar-nav .dropdown-menu { - position: absolute; - } - .navbar-expand-sm .navbar-nav .nav-link { - padding-left: 0.5rem; - padding-right: 0.5rem; - } - .navbar-expand-sm .navbar-nav-scroll { - overflow: visible; - } - .navbar-expand-sm .navbar-collapse { - display: -webkit-box !important; - display: -ms-flexbox !important; - display: flex !important; - -ms-flex-preferred-size: auto; - flex-basis: auto; - } - .navbar-expand-sm .navbar-toggler { - display: none; - } -} - -@media (min-width: 768px) { - .navbar-expand-md { - -ms-flex-wrap: nowrap; - flex-wrap: nowrap; - -webkit-box-pack: start; - -ms-flex-pack: start; - justify-content: flex-start; - } - .navbar-expand-md .navbar-nav { - -webkit-box-orient: horizontal; - -webkit-box-direction: normal; - -ms-flex-direction: row; - flex-direction: row; - } - .navbar-expand-md .navbar-nav .dropdown-menu { - position: absolute; - } - .navbar-expand-md .navbar-nav .nav-link { - padding-left: 0.5rem; - padding-right: 0.5rem; - } - .navbar-expand-md .navbar-nav-scroll { - overflow: visible; - } - .navbar-expand-md .navbar-collapse { - display: -webkit-box !important; - display: -ms-flexbox !important; - display: flex !important; - -ms-flex-preferred-size: auto; - flex-basis: auto; - } - .navbar-expand-md .navbar-toggler { - display: none; - } -} - -@media (min-width: 992px) { - .navbar-expand-lg { - -ms-flex-wrap: nowrap; - flex-wrap: nowrap; - -webkit-box-pack: start; - -ms-flex-pack: start; - justify-content: flex-start; - } - .navbar-expand-lg .navbar-nav { - -webkit-box-orient: horizontal; - -webkit-box-direction: normal; - -ms-flex-direction: row; - flex-direction: row; - } - .navbar-expand-lg .navbar-nav .dropdown-menu { - position: absolute; - } - .navbar-expand-lg .navbar-nav .nav-link { - padding-left: 0.5rem; - padding-right: 0.5rem; - } - .navbar-expand-lg .navbar-nav-scroll { - overflow: visible; - } - .navbar-expand-lg .navbar-collapse { - display: -webkit-box !important; - display: -ms-flexbox !important; - display: flex !important; - -ms-flex-preferred-size: auto; - flex-basis: auto; - } - .navbar-expand-lg .navbar-toggler { - display: none; - } -} - -@media (min-width: 1200px) { - .navbar-expand-xl { - -ms-flex-wrap: nowrap; - flex-wrap: nowrap; - -webkit-box-pack: start; - -ms-flex-pack: start; - justify-content: flex-start; - } - .navbar-expand-xl .navbar-nav { - -webkit-box-orient: horizontal; - -webkit-box-direction: normal; - -ms-flex-direction: row; - flex-direction: row; - } - .navbar-expand-xl .navbar-nav .dropdown-menu { - position: absolute; - } - .navbar-expand-xl .navbar-nav .nav-link { - padding-left: 0.5rem; - padding-right: 0.5rem; - } - .navbar-expand-xl .navbar-nav-scroll { - overflow: visible; - } - .navbar-expand-xl .navbar-collapse { - display: -webkit-box !important; - display: -ms-flexbox !important; - display: flex !important; - -ms-flex-preferred-size: auto; - flex-basis: auto; - } - .navbar-expand-xl .navbar-toggler { - display: none; - } -} - -.navbar li a:hover { - color: #151fa9; -} - -.navbar li a:hover i { - color: #151fa9; +.admin-menu-trigger { + margin-left: auto; } -.navbar li:hover .sub-menu { - visibility: visible; - opacity: 1; - margin-top: 0; +.menu-spacer { + flex: 1; } -.navbar li:hover>a { - color: #151fa9; -} - -// Logo SVG animation -svg { - position: center; - height: 60px; - margin: 0px 5px 0px 5px; -} - -.square { - stroke-dasharray: 650; - stroke-dashoffset: -650; - animation: draw-square 3s ; - // animation-fill-mode: forwards; //leave square after animation - transform-origin: 50% 50%; -} - -@keyframes draw-square { - from { - stroke-dashoffset: -650; - transform: rotate(0deg); - } - to{ - stroke-dashoffset: 0; - transform: rotate(720deg); - } -} - -.hexa { - stroke-dasharray: 450; - stroke-dashoffset: -450; - animation: draw-hexa 3s; - animation-delay: 1s; - transform-origin: 50% 50%; -} - -@keyframes draw-hexa { - from { - stroke-dashoffset: -450; - transform: rotate(0deg); - } - to{ - stroke-dashoffset: 0; - transform: rotate(360deg); +.xsmall, +.small, +.medium { + .hashtopolis-toolbar { + .mdc-button { + margin-left: 2px; + } } } -.account-active { - border: 1px solid #ccc; /* Set a gray border */ - background-color: #fff; /* Set the background color to white */ - color: #333; /* Set the text color to a darker shade of gray */ - padding: 8px 12px; /* Add padding for spacing */ - margin-top: 2px !important; /* Set the top margin to match the text height */ - border-radius: 10px; /* Set a more rounded border */ -} - -/* - 01- End -*/ +.hashtopolis-toolbar { + border-bottom: 1px solid $primary-100; -/* - 02- Breadcrum -*/ + .mdc-button { + margin-left: 8px; -@media (min-width: 768px) { -.d-md-inline-block { - display: inline-block !important; -} -} - -.breadcrumb-transparent { - background: transparent; - padding: 0; -} - -.breadcrumb-primary { - background: #1F2937; -} + .mdc-button__label { + font-weight: 400; + } -.breadcrumb-primary .breadcrumb-item.active { - color: #ffffff; -} - -.breadcrumb-primary.breadcrumb-transparent { - background: transparent; -} - -.breadcrumb-primary.breadcrumb-transparent .breadcrumb-item.active { - color: #1F2937; -} + &.active { + background-color: $primary-900; -.breadcrumb-item + .breadcrumb-item { - padding-left: 0.5rem; -} - -.breadcrumb-item + .breadcrumb-item::before { - float: left; - padding-right: 0.5rem; - color: #4B5563; - content: var(--bs-breadcrumb-divider, "/") /* rtl: var(--bs-breadcrumb-divider, "/") */; -} - -.breadcrumb-item.active { - color: #4B5563; -} - -/* - 02- End -*/ - -/* - 03- Notifications Navbar -*/ -.notif-bell-scroll { - overflow-y: auto; - height:300px; -} - -.notif-bell.unread::before { - position: absolute; - content: ""; - background-color: #E11D48; - right: 12px; - top: 7px; - height: 0.75rem; - width: 0.75rem; - border-radius: 50%; - border: 2px solid #F2F4F6; -} + .mdc-button__label { + font-weight: 400; + color: #ffffff !important; + } -@media (max-width: 575.98px) { - .notif-bell.unread::before { - background-color: #ffffff; + } } } -.icon { - height: 2rem; -} - -.icon.icon-sm { - height: 1.4rem; -} - -.list-group { - display: flex; - flex-direction: column; - padding-left: 0; - margin-bottom: 0; - border-radius: 0.5rem; -} - -.list-group-numbered { - list-style-type: none; - counter-reset: section; -} - -.list-group-numbered > li::before { - content: counters(section, ".") ". "; - counter-increment: section; -} - -.list-group-item-action { - width: 100%; - color: #374151; - text-align: inherit; -} - -.list-group-item-action:hover, .list-group-item-action:focus { - z-index: 1; - color: #374151; - text-decoration: none; - background-color: #F2F4F6; -} - -.list-group-item-action:active { - color: #374151; - background-color: #E5E7EB; -} - - -/* - 03- End -*/ - - - - +.light-theme { + --mat-toolbar-container-background-color: #ffffff; +} \ No newline at end of file From 3a5a9a6f7c53b4068966ea336824939442ed7097 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Sat, 4 Nov 2023 15:49:21 +0100 Subject: [PATCH 157/419] wip --- angular.json | 33 +- package-lock.json | 17 + package.json | 1 + src/app/account/account.module.ts | 27 +- .../ui-settings/ui-settings.component.html | 53 +-- .../ui-settings/ui-settings.component.ts | 68 ++-- src/app/app.component.html | 25 +- src/app/app.component.ts | 237 ++++++------ src/app/app.module.ts | 16 +- src/app/core/_constants/settings.config.ts | 58 ++- src/app/core/_models/auth-user.model.ts | 6 + src/app/core/_pipes/date.pipe.ts | 18 +- src/app/core/_services/access/auth.service.ts | 362 +++++++++--------- src/app/home/home.component.html | 306 +++++---------- src/app/home/home.component.ts | 139 ++++--- src/app/home/home.module.ts | 16 +- src/index.html | 5 +- src/styles/abstract/_abstract-dir.scss | 0 src/styles/base/_base-dir.scss | 8 +- src/styles/base/_base.scss | 312 ++++----------- src/styles/layout/_layout-dir.scss | 9 +- src/styles/styles.scss | 14 +- 22 files changed, 802 insertions(+), 928 deletions(-) delete mode 100644 src/styles/abstract/_abstract-dir.scss diff --git a/angular.json b/angular.json index f16d6bb2..a707613d 100644 --- a/angular.json +++ b/angular.json @@ -29,32 +29,13 @@ "src/assets" ], "styles": [ - "node_modules/bootstrap/dist/css/bootstrap.min.css", - "node_modules/bootstrap-darkmode/css/darktheme.css", - "node_modules/sweetalert2/src/sweetalert2.scss", - { - "input": "src/styles/styles.scss", - "bundleName": "default", - "inject": true - }, - { - "input": "src/styles/dark.scss", - "bundleName": "dark", - "inject": false - }, - { - "input": "src/styles/light.scss", - "bundleName": "light", - "inject": false - }, - "node_modules/datatables.net-bs5/css/dataTables.bootstrap5.min.css", - "node_modules/datatables.net-buttons-dt/css/buttons.dataTables.css", - "node_modules/datatables.net-select-dt/css/select.dataTables.css", - "node_modules/datatables.net-responsive-dt/css/responsive.dataTables.css", - "node_modules/@selectize/selectize/dist/css/selectize.css", - "node_modules/@selectize/selectize/dist/css/selectize.bootstrap5.css", - "node_modules/jquery-ui/themes/base/selectable.css" + "src/styles/styles.scss" ], + "stylePreprocessorOptions": { + "includePaths": [ + "node_modules" + ] + }, "scripts": [ "node_modules/jquery/dist/jquery.min.js", "node_modules/jquery-ui/dist/jquery-ui.js", @@ -184,4 +165,4 @@ ], "analytics": false } -} +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index bc82f9cf..9c3e56d3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "@angular/common": "^16.1.4", "@angular/compiler": "^16.1.4", "@angular/core": "^16.1.4", + "@angular/flex-layout": "^15.0.0-beta.42", "@angular/forms": "^16.1.4", "@angular/localize": "^16.1.4", "@angular/material": "^16.2.9", @@ -604,6 +605,22 @@ "zone.js": "~0.13.0" } }, + "node_modules/@angular/flex-layout": { + "version": "15.0.0-beta.42", + "resolved": "https://registry.npmjs.org/@angular/flex-layout/-/flex-layout-15.0.0-beta.42.tgz", + "integrity": "sha512-cTAPVMMxnyIFwpZwdq0PL5mdP9Qh+R8MB7ZBezVaN3Rz2fRrkagzKpLvPX3TFzepXrvHBdpKsU4b8u+NxEC/6g==", + "deprecated": "This package has been deprecated. Please see https://blog.angular.io/modern-css-in-angular-layouts-4a259dca9127", + "dependencies": { + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/cdk": ">=15.0.0", + "@angular/common": ">=15.0.2", + "@angular/core": ">=15.0.2", + "@angular/platform-browser": ">=15.0.2", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, "node_modules/@angular/forms": { "version": "16.2.1", "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-16.2.1.tgz", diff --git a/package.json b/package.json index b45054a9..80ff87b7 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "@angular/common": "^16.1.4", "@angular/compiler": "^16.1.4", "@angular/core": "^16.1.4", + "@angular/flex-layout": "^15.0.0-beta.42", "@angular/forms": "^16.1.4", "@angular/localize": "^16.1.4", "@angular/material": "^16.2.9", diff --git a/src/app/account/account.module.ts b/src/app/account/account.module.ts index ecd58982..a97679be 100644 --- a/src/app/account/account.module.ts +++ b/src/app/account/account.module.ts @@ -2,7 +2,6 @@ import { FontAwesomeModule } from "@fortawesome/angular-fontawesome"; import { FormsModule, ReactiveFormsModule } from "@angular/forms"; import { AccountRoutingModule } from "./account-routing.module"; import { ComponentsModule } from "../shared/components.module"; -import { NgbModule } from "@ng-bootstrap/ng-bootstrap"; import { DataTablesModule } from "angular-datatables"; import { PipesModule } from "../shared/pipes.module"; import { CommonModule } from "@angular/common"; @@ -17,8 +16,17 @@ import { UiSettingsComponent } from './settings/ui-settings/ui-settings.componen import { AccountSettingsComponent } from "./settings/acc-settings/acc-settings.component"; +import { MAT_FORM_FIELD_DEFAULT_OPTIONS, MatFormFieldModule } from "@angular/material/form-field"; +import { MatInputModule } from "@angular/material/input"; +import { MatIconModule } from "@angular/material/icon"; +import { MatSelectModule } from "@angular/material/select"; +import { MatButtonModule } from "@angular/material/button"; +import { MAT_SNACK_BAR_DEFAULT_OPTIONS, MatSnackBarModule } from "@angular/material/snack-bar"; + + + @NgModule({ - declarations:[ + declarations: [ NewNotificationComponent, AccountSettingsComponent, NotificationsComponent, @@ -26,7 +34,7 @@ import { AccountSettingsComponent } from "./settings/acc-settings/acc-settings.c AccountComponent, EditNotificationComponent ], - imports:[ + imports: [ AccountRoutingModule, ReactiveFormsModule, FontAwesomeModule, @@ -36,7 +44,16 @@ import { AccountSettingsComponent } from "./settings/acc-settings/acc-settings.c CommonModule, PipesModule, FormsModule, - NgbModule, + MatFormFieldModule, + MatInputModule, + MatSelectModule, + MatIconModule, + MatButtonModule, + MatSnackBarModule, + ], + providers: [ + { provide: MAT_FORM_FIELD_DEFAULT_OPTIONS, useValue: { appearance: 'outline' } }, + { provide: MAT_SNACK_BAR_DEFAULT_OPTIONS, useValue: { duration: 2500 } } ] }) -export class AccountModule {} +export class AccountModule { } diff --git a/src/app/account/settings/ui-settings/ui-settings.component.html b/src/app/account/settings/ui-settings/ui-settings.component.html index f6a7544b..790b17f4 100644 --- a/src/app/account/settings/ui-settings/ui-settings.component.html +++ b/src/app/account/settings/ui-settings/ui-settings.component.html @@ -1,24 +1,29 @@ - - - - -
- - - - - - - -
- -
+ + +
+

+ + Time Format + + {{ d.description }} + + +

+

+ + Layout + + {{ d.description }} + + +

+

+ + Theme + + {{ t.description }} + + +

+ +
\ No newline at end of file diff --git a/src/app/account/settings/ui-settings/ui-settings.component.ts b/src/app/account/settings/ui-settings/ui-settings.component.ts index 6e04b916..36696032 100644 --- a/src/app/account/settings/ui-settings/ui-settings.component.ts +++ b/src/app/account/settings/ui-settings/ui-settings.component.ts @@ -1,9 +1,10 @@ -import { CookieService } from '../../../core/_services/shared/cookies.service'; -import { dateFormat } from '../../../core/_constants/settings.config'; import { FormControl, FormGroup } from '@angular/forms'; import { Component, OnInit } from '@angular/core'; - -import { AlertService } from 'src/app/core/_services/shared/alert.service'; +import { LocalStorageService } from 'src/app/core/_services/storage/local-storage.service'; +import { UIConfig } from 'src/app/core/_models/config-ui.model'; +import { Setting, dateFormats, layouts, themes } from 'src/app/core/_constants/settings.config'; +import { UISettingsUtilityClass } from 'src/app/shared/utils/config'; +import { MatSnackBar } from '@angular/material/snack-bar'; @Component({ selector: 'app-ui-settings', @@ -11,43 +12,46 @@ import { AlertService } from 'src/app/core/_services/shared/alert.service'; }) export class UiSettingsComponent implements OnInit { - dateFormat = dateFormat; - uiForm: FormGroup; + form!: FormGroup; + util: UISettingsUtilityClass + dateFormats: Setting[] = dateFormats + layouts: Setting[] = layouts + themes: Setting[] = themes constructor( - private cookieService: CookieService, - private alert: AlertService - ) { } - - - ngOnInit(): void { - - this.uiForm = new FormGroup({ - 'localtimefmt': new FormControl(), - }); - + private service: LocalStorageService, + private snackBar: MatSnackBar, + ) { this.initForm(); - } - private initForm() { - - const localtimefmt = this.getCookieValue('localtimefmt'); + ngOnInit(): void { + this.util = new UISettingsUtilityClass(this.service) + this.updateForm(); + } - this.uiForm = new FormGroup({ - 'localtimefmt': new FormControl(localtimefmt), + private initForm(): void { + this.form = new FormGroup({ + 'timefmt': new FormControl(''), + 'layout': new FormControl(''), + 'theme': new FormControl('') }); - } - setCookieValue(name: string, value: string){ - this.cookieService.setCookie(name, value, 365); - this.alert.okAlert('UI Setting saved!',''); - this.ngOnInit(); + private updateForm(): void { + this.form.patchValue({ + 'timefmt': this.util.uiConfig.timefmt, + 'layout': this.util.uiConfig.layout, + 'theme': this.util.uiConfig.theme + }) } - getCookieValue(name: string){ - return this.cookieService.getCookie(name); - } + onSubmit(): void { + const changedValues = this.util.updateSettings(this.form.value) + const message = changedValues > 0 + ? `Successfully updated ${changedValues} settings!` + : 'No changes were saved' -} + this.snackBar.open(message, 'Close'); + } +} \ No newline at end of file diff --git a/src/app/app.component.html b/src/app/app.component.html index 263cc20f..c0f4c345 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -1,16 +1,9 @@ - - - -
- - - - - - - -
- - - - +
+ +
+ +
+ +
\ No newline at end of file diff --git a/src/app/app.component.ts b/src/app/app.component.ts index a15b6817..520ccc49 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,5 +1,4 @@ -import { Component, HostBinding, Inject, OnInit, PLATFORM_ID } from '@angular/core'; -import { Meta, Title } from '@angular/platform-browser'; +import { AfterViewInit, Component, ElementRef, Inject, OnInit, PLATFORM_ID } from '@angular/core'; import { NavigationEnd, Router } from '@angular/router'; import { isPlatformBrowser } from '@angular/common'; import { filter } from 'rxjs'; @@ -9,70 +8,54 @@ import { filter } from 'rxjs'; **/ import { UIConfigService } from './core/_services/shared/storage.service'; import { CookieService } from './core/_services/shared/cookies.service'; -import { ConfigService } from './core/_services/shared/config.service'; import { AuthService } from './core/_services/access/auth.service'; /** * Idle watching * **/ -import { CheckTokenService } from './core/_services/access/checktoken.service'; import { TimeoutComponent } from './shared/alert/timeout/timeout.component'; import { ModalDismissReasons, NgbModal } from '@ng-bootstrap/ng-bootstrap'; -import { ThemeService } from './core/_services/shared/theme.service'; import { Idle, DEFAULT_INTERRUPTSOURCES } from '@ng-idle/core'; import { Keepalive } from '@ng-idle/keepalive'; +import { UISettingsUtilityClass } from './shared/utils/config'; +import { LocalStorageService } from './core/_services/storage/local-storage.service'; +import { UIConfig } from './core/_models/config-ui.model'; +import { BreakpointService } from './core/_services/shared/breakpoint.service'; @Component({ selector: 'app-root', templateUrl: './app.component.html', }) -export class AppComponent implements OnInit { +export class AppComponent implements OnInit, AfterViewInit { currentUrl: string; currentStep: string; appTitle = 'Hashtopolis'; idleState = 'Not Started'; timedOut = false; - lastPing?: Date = null; - timeoutCountdown:number = null; - timeoutMax= this.onTimeout(); + lastPing?: Date = null; + timeoutCountdown: number = null; + timeoutMax = this.onTimeout(); idleTime: number = this.onTimeout(); showingModal = false; modalRef = null; - screenmode:string = localStorage.getItem('screenmode') || 'true'; - theme: string; - - @HostBinding('class.light-theme') - public isLightTheme = false; - - @HostBinding('class.dark-theme') - public isDarkTheme = false; + uiSettings: UISettingsUtilityClass + isLogged: boolean; - /** - * set theme - */ - private setTheme(val: string) { - this.theme = val; - this.isLightTheme = (val === 'light'); - this.isDarkTheme = (val === 'dark'); - } constructor( - private configService: ConfigService, private cookieService: CookieService, - private uicService:UIConfigService, - private checkt: CheckTokenService, + private uicService: UIConfigService, private authService: AuthService, private modalService: NgbModal, - private themes: ThemeService, private keepalive: Keepalive, - private metaTitle: Title, private router: Router, - private meta: Meta, private idle: Idle, + private storage: LocalStorageService, + public screen: BreakpointService, + private elementRef: ElementRef, @Inject(PLATFORM_ID) private platformId: object - ){ - this.setTheme(this.themes.theme); - this.router.events + ) { + this.router.events .pipe(filter(e => e instanceof NavigationEnd)) .subscribe((e: NavigationEnd) => { this.currentUrl = e.url; @@ -82,58 +65,52 @@ export class AppComponent implements OnInit { } }); - idle.setIdle(this.idleTime); - idle.setTimeout(this.timeoutMax); - idle.setInterrupts(DEFAULT_INTERRUPTSOURCES); - - idle.onIdleStart.subscribe(() => { - idle.clearInterrupts(); - this.checkLogin(); - this.idleState = 'You\'ll be logged out in 15 seconds!' - }); - - idle.onIdleEnd.subscribe(() => { - this.idleState = "NOT_IDLE."; - this.modalRef.componentInstance.timedOut = false; - this.timeoutCountdown = null; - this.reset(); - this.closeModal(); - }); - - idle.onTimeout.subscribe(() => { - this.idleState = 'TIMED_OUT'; - this.timedOut = true; - this.timeoutCountdown = null; - this.modalRef.componentInstance.timedOut = true; - this.onLogOut(); - }); + idle.setIdle(this.idleTime); + idle.setTimeout(this.timeoutMax); + idle.setInterrupts(DEFAULT_INTERRUPTSOURCES); - idle.onTimeoutWarning.subscribe((countdown) => { - if(!this.showingModal && this.idleTime > 1){ - this.openModal(); - } - this.timeoutCountdown = this.timeoutMax - countdown +1; - this.modalRef.componentInstance.timeoutCountdown = this.timeoutCountdown; - }); + idle.onIdleStart.subscribe(() => { + idle.clearInterrupts(); + this.checkLogin(); + this.idleState = 'You\'ll be logged out in 15 seconds!' + }); - keepalive.interval(15); + idle.onIdleEnd.subscribe(() => { + this.idleState = "NOT_IDLE."; + this.modalRef.componentInstance.timedOut = false; + this.timeoutCountdown = null; + this.reset(); + this.closeModal(); + }); - keepalive.onPing.subscribe(() => this.lastPing = new Date()); + idle.onTimeout.subscribe(() => { + this.idleState = 'TIMED_OUT'; + this.timedOut = true; + this.timeoutCountdown = null; + this.modalRef.componentInstance.timedOut = true; + this.onLogOut(); + }); - this.authService.getUserLoggedIn().subscribe(userLoggedIn => { - if (userLoggedIn) { - idle.watch() - this.timedOut = false; - } else { - idle.stop(); - } - }) + idle.onTimeoutWarning.subscribe((countdown) => { + if (!this.showingModal && this.idleTime > 1) { + this.openModal(); + } + this.timeoutCountdown = this.timeoutMax - countdown + 1; + this.modalRef.componentInstance.timeoutCountdown = this.timeoutCountdown; + }); - // this.reset(); + keepalive.interval(15); + keepalive.onPing.subscribe(() => this.lastPing = new Date()); - } - - isLogged: boolean; + this.authService.getUserLoggedIn().subscribe(userLoggedIn => { + if (userLoggedIn) { + idle.watch() + this.timedOut = false; + } else { + idle.stop(); + } + }) + } ngOnInit(): void { this.authService.autoLogin(); @@ -144,6 +121,44 @@ export class AppComponent implements OnInit { } }); this.authService.checkStatus(); + this.uiSettings = new UISettingsUtilityClass(this.storage) + } + + ngAfterViewInit() { + this.setBodyClasses() + } + + /** + * Sets CSS classes on the `` element of the document based on user interface settings. + * The classes reflect the chosen layout and theme, allowing dynamic theming of the application. + * + * - For layout, it supports 'fixed' and 'full' layouts. + * - For themes, it supports 'light' and 'dark' themes. + * + * @remarks + * This function retrieves layout and theme settings from the `uiSettings` service and sets + * corresponding CSS classes on the `` element to apply visual styling changes. + */ + private setBodyClasses(): void { + const classes: string[] = [] + if (this.uiSettings) { + const layout = this.uiSettings.getSetting('layout') + if (layout === 'fixed') { + classes.push('fixed-width-layout') + } else if (layout === 'full') { + classes.push('full-width-layout') + } + + const theme = this.uiSettings.getSetting('theme') + if (theme === 'light') { + classes.push('light-theme') + } else if (theme === 'dark') { + classes.push('dark-theme') + } + } + if (classes) { + this.elementRef.nativeElement.ownerDocument.body.className = classes.join(' '); + } } private findCurrentStep(currentRoute) { @@ -153,17 +168,17 @@ export class AppComponent implements OnInit { } checkLogin() { - const userData: { _token: string, _expires: string} = JSON.parse(localStorage.getItem('userData')); - if(!userData){ - return; + const userData: { _token: string, _expires: string } = JSON.parse(localStorage.getItem('userData')); + if (!userData) { + return; } - if(new Date(userData._expires) < new Date()){ + if (new Date(userData._expires) < new Date()) { this.idle.stop(); window.location.reload(); } } - reset(){ + reset() { this.idle.setTimeout(false); this.idle.watch(); this.idle.setInterrupts(DEFAULT_INTERRUPTSOURCES); @@ -174,26 +189,26 @@ export class AppComponent implements OnInit { this.closeModal(); } - onLogOut(){ + onLogOut() { this.authService.logOut(); this.closeModal(); } - storageInit(){ + storageInit() { this.cookieService.checkDefaultCookies(); this.uicService.checkStorage(); } - onTimeout() : number { + onTimeout(): number { const uisData = JSON.parse(localStorage?.getItem('uis')); let timeoutidle = 1; - if(uisData !== null){ - timeoutidle = Number(uisData.find(o => o.name === 'maxSessionLength').value*60*60); //Convert max session hours to seconds + if (uisData !== null) { + timeoutidle = Number(uisData.find(o => o.name === 'maxSessionLength').value * 60 * 60); //Convert max session hours to seconds } return timeoutidle; } - openModal(){ + openModal() { if (this.isLogged) { this.showingModal = true; @@ -202,7 +217,7 @@ export class AppComponent implements OnInit { } } - closeModal(){ + closeModal() { this.showingModal = false; this.modalService.dismissAll(); this.ngOnInit(); @@ -210,24 +225,24 @@ export class AppComponent implements OnInit { closeResult = ''; open(content) { - this.modalService.open(content, { ariaLabelledBy: 'modal-basic-title' }).result.then( - (result) => { - this.closeResult = `Closed with: ${result}`; - }, - (reason) => { - this.closeResult = `Dismissed ${this.getDismissReason(reason)}`; - }, - ); - } - - private getDismissReason(reason: ModalDismissReasons | string): string { - if (reason === ModalDismissReasons.ESC) { - return 'by pressing ESC'; - } else if (reason === ModalDismissReasons.BACKDROP_CLICK) { - return 'by clicking on a backdrop'; - } else { - return `with: ${reason}`; - } - } + this.modalService.open(content, { ariaLabelledBy: 'modal-basic-title' }).result.then( + (result) => { + this.closeResult = `Closed with: ${result}`; + }, + (reason) => { + this.closeResult = `Dismissed ${this.getDismissReason(reason)}`; + }, + ); + } + + private getDismissReason(reason: ModalDismissReasons | string): string { + if (reason === ModalDismissReasons.ESC) { + return 'by pressing ESC'; + } else if (reason === ModalDismissReasons.BACKDROP_CLICK) { + return 'by clicking on a backdrop'; + } else { + return `with: ${reason}`; + } + } } diff --git a/src/app/app.module.ts b/src/app/app.module.ts index d9190db0..50f1e6ee 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -44,6 +44,12 @@ import { DirectivesModule } from './shared/directives.module'; import { configReducer } from './core/_store/config.reducer'; import { PipesModule } from './shared/pipes.module'; import { AuthModule } from './auth/auth.module'; +import { MatButtonModule } from '@angular/material/button'; +import { MatIconModule } from '@angular/material/icon'; +import { MatMenuModule } from '@angular/material/menu'; +import { MatTooltipModule } from '@angular/material/tooltip'; +import { MatToolbarModule } from '@angular/material/toolbar'; +import { CoreComponentsModule } from './core/_components/core-components.module'; @NgModule({ declarations: [ @@ -70,10 +76,16 @@ import { AuthModule } from './auth/auth.module'; PipesModule, FormsModule, AuthModule, + MatToolbarModule, + MatButtonModule, + MatIconModule, + MatMenuModule, + MatTooltipModule, + CoreComponentsModule, NgbModule, AppRoutingModule, // Main routes for the App NgIdleKeepaliveModule.forRoot(), - StoreModule.forRoot({configList: configReducer}) + StoreModule.forRoot({ configList: configReducer }) ], providers: [ Title, @@ -98,7 +110,7 @@ export class AppModule { static injector: Injector; constructor( injector: Injector - ){ + ) { AppModule.injector = injector; } } diff --git a/src/app/core/_constants/settings.config.ts b/src/app/core/_constants/settings.config.ts index a5233160..8918e834 100644 --- a/src/app/core/_constants/settings.config.ts +++ b/src/app/core/_constants/settings.config.ts @@ -1,31 +1,47 @@ /** * Date formats, used in general settings and when app is initialized **/ - -export const dateFormat = [ - {format:'d/M/yy', description:'d/M/yy (ie. 6/7/23 )'}, - {format:'dd/MM/yyyy h:mm:ss', description:'dd/MM/yyyy h:mm:ss (ie. 06/07/2023, 9:03 AM)'}, - {format:'d MMM, y h:mm:ss a', description:'dd/MM/yyyy h:mm:ss (ie. 06 Jul, 2023 9:03:01 AM)'}, - {format:'M/d/yy', description:'M/d/yy (ie. 7/6/23)'}, - {format:'M/d/yy, h:mm a', description:'M/d/yy, h:mm a (ie. 7/6/23, 9:03 AM)'}, - {format:'MMM d, y, h:mm:ss a', description:'MMM d, y, h:mm:ss a (ie. Jul 06, 2023, 9:03:01 AM)'}, - {format:'yy/M/d', description:'yy/M/d (ie. 23/7/6 )'}, - {format:'yyyy/M/d, h:mm:ss a', description:'yyyy/M/d (ie. 2023/7/6, 9:03:01 AM )'}, - {format:'yyyy/MM/dd h:mm:ss', description:'yyyy/MM/dd h:mm:ss (ie. 2023/07/06, 9:03 AM)'}, +export interface Setting { + value: string + description: string +} + +export const dateFormats: Setting[] = [ + { value: 'd/M/yy', description: 'd/M/yy (ie. 6/7/23 )' }, + { value: 'dd/MM/yyyy h:mm:ss', description: 'dd/MM/yyyy h:mm:ss (ie. 06/07/2023, 9:03 AM)' }, + { value: 'd MMM, y h:mm:ss a', description: 'dd/MM/yyyy h:mm:ss (ie. 06 Jul, 2023 9:03:01 AM)' }, + { value: 'M/d/yy', description: 'M/d/yy (ie. 7/6/23)' }, + { value: 'M/d/yy, h:mm a', description: 'M/d/yy, h:mm a (ie. 7/6/23, 9:03 AM)' }, + { value: 'MMM d, y, h:mm:ss a', description: 'MMM d, y, h:mm:ss a (ie. Jul 06, 2023, 9:03:01 AM)' }, + { value: 'yy/M/d', description: 'yy/M/d (ie. 23/7/6 )' }, + { value: 'yyyy/M/d, h:mm:ss a', description: 'yyyy/M/d (ie. 2023/7/6, 9:03:01 AM )' }, + { value: 'yyyy/MM/dd h:mm:ss', description: 'yyyy/MM/dd h:mm:ss (ie. 2023/07/06, 9:03 AM)' }, + { value: 'yyyy-MM-dd h:mm:ss', description: 'yyyy-MM-dd h:mm:ss (ie. 2023-07-06, 09:03)' }, ]; +export const layouts: Setting[] = [ + { value: 'fixed', description: 'Fixed width layout' }, + { value: 'full', description: 'Full screen layout' }, +] + +export const themes: Setting[] = [ + { value: 'light', description: 'Light Mode' }, + { value: 'dark', description: 'Dark Mode' }, +] + + /** * Logs, used in general settings **/ export const serverlog = [ - {id:0, value: 'TRACE'}, - {id:10, value: 'DEBUG'}, - {id:20, value: 'INFO'}, - {id:30, value: 'WARNING'}, - {id:40, value: 'ERROR'}, - {id:50, value: 'FATAL'} + { id: 0, value: 'TRACE' }, + { id: 10, value: 'DEBUG' }, + { id: 20, value: 'INFO' }, + { id: 30, value: 'WARNING' }, + { id: 40, value: 'ERROR' }, + { id: 50, value: 'FATAL' } ]; /** @@ -33,10 +49,10 @@ export const serverlog = [ **/ export const proxytype = [ - {value:'HTTP'}, - {value:'HTTPS'}, - {value:'SOCKS4'}, - {value:'SOCKS5'} + { value: 'HTTP' }, + { value: 'HTTPS' }, + { value: 'SOCKS4' }, + { value: 'SOCKS5' } ]; /** diff --git a/src/app/core/_models/auth-user.model.ts b/src/app/core/_models/auth-user.model.ts index ea254306..e31695e9 100644 --- a/src/app/core/_models/auth-user.model.ts +++ b/src/app/core/_models/auth-user.model.ts @@ -1,3 +1,9 @@ +export interface UserData { + _expires: Date + _token: string + _username: string +} + export class User { constructor( private _token: string, diff --git a/src/app/core/_pipes/date.pipe.ts b/src/app/core/_pipes/date.pipe.ts index 451b7c19..0eaf5647 100644 --- a/src/app/core/_pipes/date.pipe.ts +++ b/src/app/core/_pipes/date.pipe.ts @@ -5,7 +5,7 @@ import { Pipe } from '@angular/core'; import { CookieService } from '../_services/shared/cookies.service'; -import { dateFormat } from '../../core/_constants/settings.config'; +import { dateFormats } from '../../core/_constants/settings.config'; import { DatePipe } from '@angular/common'; /** @@ -28,28 +28,28 @@ export class uiDatePipe extends DatePipe implements PipeTransform { @Inject(LOCALE_ID) locale: string ) { super(locale); } - override transform(epoch: number):any { + override transform(epoch: number): any { - if(epoch === undefined || epoch === null) return epoch; + if (epoch === undefined || epoch === null) return epoch; - if(!this.cookieService.getCookie('localtimefmt')){ + if (!this.cookieService.getCookie('localtimefmt')) { this.cookieService.setCookie('localtimefmt', 'dd/MM/yyyy h:mm:ss', 365); } const format = this.checkFormat(this.cookieService.getCookie('localtimefmt')); - return super.transform(epoch*1000, format); + return super.transform(epoch * 1000, format); } //Check that format is correct - checkFormat(format: any){ + checkFormat(format: any) { let res; //Default date format - for(let i=0; i < dateFormat.length; i++){ - if(dateFormat[i]['format']== format){ + for (let i = 0; i < dateFormats.length; i++) { + if (dateFormats[i]['format'] == format) { res = format; } } - if(!res){ + if (!res) { res = 'dd/MM/yyyy h:mm:ss'; this.cookieService.setCookie('localtimefmt', res, 365); } diff --git a/src/app/core/_services/access/auth.service.ts b/src/app/core/_services/access/auth.service.ts index bf267109..fd45e094 100644 --- a/src/app/core/_services/access/auth.service.ts +++ b/src/app/core/_services/access/auth.service.ts @@ -1,196 +1,210 @@ -import { HttpClient, HttpErrorResponse, HttpHeaders } from "@angular/common/http"; -import { BehaviorSubject, throwError, Observable, ReplaySubject, Subject } from 'rxjs'; -import { environment } from '../../../../environments/environment'; -import { Injectable, Output, EventEmitter } from "@angular/core"; -import { User } from '../../_models/auth-user.model'; +import { Injectable } from "@angular/core"; +import { HttpClient, HttpHeaders, HttpErrorResponse } from "@angular/common/http"; +import { BehaviorSubject, Observable, ReplaySubject, Subject, throwError } from 'rxjs'; import { catchError, tap } from 'rxjs/operators'; -import { Router } from "@angular/router"; import { Buffer } from 'buffer'; + import { ConfigService } from "../shared/config.service"; +import { User, UserData } from '../../_models/auth-user.model'; +import { LocalStorageService } from "../storage/local-storage.service"; export interface AuthResponseData { - token: string, - expires: string, + token: string; + expires: string; } -@Injectable({providedIn: 'root'}) +@Injectable({ providedIn: 'root' }) export class AuthService { - user = new BehaviorSubject(null); - userId!: any; - @Output() authChanged: EventEmitter = new EventEmitter(); - isAuthenticated = false; - private logged = new ReplaySubject(1); - isLogged = this.logged.asObservable(); - redirectUrl = ''; - private userLoggedIn = new Subject(); - private accessToken: string; - private tokenExpiration: any; - private endpoint = '/auth'; - - constructor( - private http: HttpClient, - private router: Router, - private cs: ConfigService, - ){ - this.accessToken = localStorage.getItem('userData'); - this.userLoggedIn.next(false); - if(this.logged){ - this.userId = this.getUserId(this.token); - } - } - - autoLogin(){ - const userData: { _token: string, _expires: string} = JSON.parse(localStorage.getItem('userData')); - if(!userData){ - return; - } - - const loadedUser = new User(userData._token, new Date(userData._expires), '-'); - - if(loadedUser.token){ - this.user.next(loadedUser); - const tokenExpiration = new Date(userData._expires).getTime() - new Date().getTime(); - // this.autologOut(tokenExpiration); - this.getRefreshToken(tokenExpiration); - } - } - - logIn(username: string, password: string): Observable{ - return this.http.post(this.cs.getEndpoint() + this.endpoint + '/token', {username: username, password: password}, - {headers: new HttpHeaders({ 'Authorization': 'Basic '+ window.btoa(username+':'+password) })}) - .pipe( - catchError(this.handleError), - tap(resData => { - this.handleAuthentication(resData.token, +resData.expires, username); - this.isAuthenticated = true; - this.userAuthChanged(true); - })); + static readonly STORAGE_KEY = 'userData' + + private tokenExpiration: any; + private endpoint = '/auth'; + + userId!: number + redirectUrl = '' + isAuthenticated = false; + authenticationStatus = new ReplaySubject(1); + userLoggedIn = new Subject(); + user = new BehaviorSubject(null); + + constructor( + private http: HttpClient, + private configService: ConfigService, + private storage: LocalStorageService, + ) { + this.initUser(); + } + + private initUser() { + this.userLoggedIn.next(false); + if (this.authenticationStatus) { + this.checkAuthenticationStatus(); } + } - get token(): any { - let res; - const token = localStorage.getItem('userData'); - if(token){ - res = JSON?.parse(token)._token - }else{ - res = 'notoken' - } - return res; + public autoLogin(): void { + if (!this.userData) { + return; } - private getUserId(token: any){ - if(token == 'notoken'){ - return false - }else{ - const b64string = Buffer.from(token.split('.')[1], 'base64'); - return JSON.parse(b64string.toString()).userId - } - } + const loadedUser = new User(this.userData._token, new Date(this.userData._expires), '-'); - setUserLoggedIn(userLoggedIn: boolean) { - this.userLoggedIn.next(userLoggedIn); + if (loadedUser.token) { + this.user.next(loadedUser); + const expirationDuration = new Date(this.userData._expires).getTime() - new Date().getTime(); + this.getRefreshToken(expirationDuration); } - - getUserLoggedIn(): Observable { - return this.userLoggedIn.asObservable(); - } - - // With autologOut we use only the expiration date of the bearer token, approx. 2 hours - autologOut(expirationDuration: number){ - this.tokenExpiration = setTimeout(() => { - this.logOut(); - }, expirationDuration); - } - - getRefreshToken(expirationDuration: number){ - this.tokenExpiration = setTimeout(() => { - const userData: {_token: string, _expires: string, _username: string} = JSON.parse(localStorage.getItem('userData')); - return this.http.post(this.cs.getEndpoint() + this.endpoint + '/refresh', {headers: new HttpHeaders({ Authorization: `Bearer ${userData._token}` })}) - .pipe( - tap((response: any) => { - if (response && response.token) { - this.accessToken = response.token; - const expirationDate = new Date(response.expires * 1000); - const user = new User(response.token, expirationDate, userData._username); - localStorage.setItem('userData', JSON.stringify(user)); - } else { - this.logOut(); - } - }) - ); - }, expirationDuration); - } - - refreshToken(): Observable { - const userData: {_token: string, _expires: string, _username: string} = JSON.parse(localStorage.getItem('userData')); - return this.http.post(this.cs.getEndpoint() + this.endpoint + '/refresh ', {headers: new HttpHeaders({ Authorization: `Bearer ${userData._token}` })}) - .pipe( - tap((response: any) => { - if (response && response.token) { - this.accessToken = response.token; - const expirationDate = new Date(response.expires * 1000); - const user = new User(response.token, expirationDate, userData._username); - localStorage.setItem('userData', JSON.stringify(user)); - } else { - this.logOut(); - } - }), - catchError((error) => { - // Handle the error here - console.error('An error occurred:', error); - return throwError(error); // Rethrow the error for further handling if needed - }) - ); + } + + public logIn(username: string, password: string): Observable { + return this.http.post( + `${this.configService.getEndpoint()}${this.endpoint}/token`, + { username, password }, + { + headers: new HttpHeaders({ + 'Authorization': `Basic ${window.btoa(username + ':' + password)}` + }) + } + ).pipe( + catchError(this.handleError), + tap(response => { + this.handleAuthentication(response.token, +response.expires, username); + this.isAuthenticated = true; + }) + ); + } + + public get token(): string { + return this.userData ? this.userData._token : 'notoken'; + } + + private getUserId(token: string): number | undefined { + if (token == 'notoken') { + return undefined; + } else { + const b64string = Buffer.from(token.split('.')[1], 'base64'); + return parseInt(JSON.parse(b64string.toString()).userId); } - - logOut(){ - this.user.next(null); - this.router.navigate(['/auth']); - localStorage.removeItem('userData'); - if(this.tokenExpiration){ - clearTimeout(this.tokenExpiration) + } + + public setUserLoggedIn(userLoggedIn: boolean): void { + this.userLoggedIn.next(userLoggedIn); + } + + public getUserLoggedIn(): Observable { + return this.userLoggedIn.asObservable(); + } + + public autologOut(expirationDuration: number): void { + this.tokenExpiration = setTimeout(() => { + this.logOut(); + }, expirationDuration); + } + + public getRefreshToken(expirationDuration: number): void { + this.tokenExpiration = setTimeout(() => { + if (this.userData) { + this.http.post( + this.configService.getEndpoint() + this.endpoint + '/refresh', + {}, + { + headers: new HttpHeaders({ Authorization: `Bearer ${this.userData._token}` }) + } + ).pipe( + tap((response: any) => { + if (response && response.token) { + const expirationDate = new Date(response.expires * 1000); + this.storage.setItem('userData', { + _token: response.token, + _expires: expirationDate, + _username: this.userData._username + }, 0) + + } else { + this.logOut(); + } + }) + ).subscribe(); + } + }, expirationDuration); + } + + public refreshToken(): Observable { + return this.http.post( + this.configService.getEndpoint() + this.endpoint + '/refresh ', + {}, + { + headers: new HttpHeaders({ Authorization: `Bearer ${this.userData ? this.userData._token : ''}` }) + } + ).pipe( + tap((response: any) => { + if (response && response.token) { + const expirationDate = new Date(response.expires * 1000); + + this.storage.setItem('userData', { + _token: response.token, + _expires: expirationDate, + _username: this.userData._username + }, 0) + } else { + this.logOut(); } - this.tokenExpiration = null; + }), + catchError((error) => { + // Handle the error here + console.error('An error occurred:', error); + return throwError(() => error); // Rethrow the error for further handling if needed + }) + ); + } + + public logOut(): void { + this.user.next(null); + this.isAuthenticated = false; + this.storage.removeItem('userData'); + if (this.tokenExpiration) { + clearTimeout(this.tokenExpiration); } - - checkStatus() { - const userData = JSON.parse(localStorage.getItem('userData')); - if (userData) { - this.logged.next(true); - } else { - this.logged.next(false); - } + this.tokenExpiration = null; + } + + get userData(): UserData { + return this.storage.getItem(AuthService.STORAGE_KEY); + } + + checkAuthenticationStatus(): void { + if (this.userData) { + this.authenticationStatus.next(true); + this.userId = this.getUserId(this.userData._token) + } else { + this.authenticationStatus.next(false); } - - private userAuthChanged(status: boolean) { - this.authChanged.emit(status); // Raise changed event - } - - handleAuthentication(token: string, expires: number, username: string) { - console.log('handling auth') - const expirationDate = new Date(expires * 1000); // expires, its epoch time in seconds and returns milliseconds sin Jan 1, 1970. We need to multiple by 1000 - const user = new User(token, expirationDate, username); - this.user.next(user); - this.logged.next(true); - this.autologOut(expires); // Epoch time - localStorage.setItem('userData', JSON.stringify(user)); - this.userId = this.getUserId(token); + } + + private handleAuthentication(token: string, expires: number, username: string): void { + const expirationDate = new Date(expires * 1000); + const user = new User(token, expirationDate, username); + this.user.next(user); + this.isAuthenticated = true; + this.storage.setItem('userData', { + _token: token, + _expires: expirationDate, + _username: username + }, 0) + this.userId = this.getUserId(token); + } + + private handleError(errorRes: HttpErrorResponse): Observable { + let errorMessage = 'An unknown error occurred!'; + if (errorRes.error && errorRes.error.error) { + switch (errorRes.error.error.message) { + case 'INVALID_PASSWORD': + errorMessage = 'Wrong username/password/OTP!'; + break; } - - private handleError ( errorRes : HttpErrorResponse ) { - let errorMessage = 'An unknown error ocurred!'; - if (!errorRes.error || !errorRes.error.error){ - return throwError(() => errorMessage); - } - switch(errorRes.error.error.message){ - case 'INVALID_PASSWORD': //We can add easily more common errors but for security better dont give more hints - errorMessage = 'Wrong username/password/OTP!'; - break; - } - return throwError(() => errorMessage); } - + console.error(errorMessage); + return throwError(() => errorMessage); + } } - - diff --git a/src/app/home/home.component.html b/src/app/home/home.component.html index 3e1b8804..48c8f24d 100644 --- a/src/app/home/home.component.html +++ b/src/app/home/home.component.html @@ -1,222 +1,104 @@ - -
-
-
-
-
-
-
-
- -
-
-
-
-
Agents
-

{{ activeAgents }}/{{totalAgents}}

-
-
-
-
- -
+
+
+
+ + +

+ dns + Agents +

+ {{ activeAgents }} / {{ totalAgents }} +
+ + check_circle + Active Agents / Total Agents + +
+
-
-
-
-
-
-
- -
-
-
-
-

Tasks

-

{{ totalTasks }}

-
-
-
-
- -
+
+ + +

+ checklist + Tasks +

+ {{ totalTasks }} +
+ + check_circle + Total Live Tasks + +
+
-
-
-
-
-
-
- -
-
-
-
-

SuperTasks

-

{{ allsupertasks }}

-

-

-
-
-
- -
+
+ + +

+ checklist + Supertasks +

+ {{ allsupertasks }} +
+ + check_circle + Total Supertasks + +
+
-
-
-
-
-
-
- -
-
-
-
-

Total Cracks

-

{{ totalCracks }}

-

-

-
-
-
- -
+
+ + +

+ lock_open + Cracks +

+ {{ totalCracks }} +
+ + event_available + Last 7 days + +
+
- -
-
-
-
-
Project Information
-
-
- Hashtopolis is available in:   - - -    -
- To get more information about how to use it, please visit the - - Wiki - or - - Discord - -
-
+
+
+
-
-
-
-
Cracked
-
-
-
-
-
-
+
+
+
+ + + Hashtopolis is available on Github. + To get information about how to use it, please visit the Wiki or Discord. + +
-
+
+ + + + - - + + \ No newline at end of file diff --git a/src/app/home/home.component.ts b/src/app/home/home.component.ts index 965398f0..343d86ae 100644 --- a/src/app/home/home.component.ts +++ b/src/app/home/home.component.ts @@ -7,6 +7,8 @@ import { CanvasRenderer } from 'echarts/renderers'; import { interval, Subscription } from 'rxjs'; import { HeatmapChart } from 'echarts/charts'; import * as echarts from 'echarts/core'; +import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout'; + import { GlobalService } from 'src/app/core/_services/main.service'; import { PageTitle } from 'src/app/core/_decorators/autotitle'; @@ -15,29 +17,37 @@ import { CookieService } from '../core/_services/shared/cookies.service'; @Component({ selector: 'app-home', - templateUrl: './home.component.html' + templateUrl: './home.component.html', + styleUrls: ['./home.component.scss'] }) @PageTitle(['Dashboard']) export class HomeComponent implements OnInit { + + username = 'Admin'; - faCalendarWeek=faCalendarWeek; - faChainBroken=faChainBroken; - faCheckCircle=faCheckCircle; - faCalendarDay=faCalendarDay; - faPauseCircle=faPauseCircle; - faInfoCircle=faInfoCircle; - faUserSecret=faUserSecret; - faTasksAlt=faTasksAlt; - faRefresh=faRefresh; - faTasks=faTasks; + faCalendarWeek = faCalendarWeek; + faChainBroken = faChainBroken; + faCheckCircle = faCheckCircle; + faCalendarDay = faCalendarDay; + faPauseCircle = faPauseCircle; + faInfoCircle = faInfoCircle; + faUserSecret = faUserSecret; + faTasksAlt = faTasksAlt; + faRefresh = faRefresh; + faTasks = faTasks; - faGithub=faGithub; + faGithub = faGithub; + + isMediumScreen = false; + + screenXS = false + screenS = false + screenM = false + screenL = false + screenXL = false - getUsername(){ - return this.username; - } // Dashboard variables activeAgents = 0; @@ -47,15 +57,50 @@ export class HomeComponent implements OnInit { allsupertasks = 0; private maxResults = environment.config.prodApiMaxResults; - storedAutorefresh: any =[] + storedAutorefresh: any = [] private updateSubscription: Subscription; public punchCardOpts = {} public punchCardOptss = {} constructor( private gs: GlobalService, - private cs: CookieService - ) { } + private cs: CookieService, + private breakpointObserver: BreakpointObserver + ) { + this.breakpointObserver.observe([Breakpoints.XSmall, Breakpoints.Small, Breakpoints.Medium, Breakpoints.Large, Breakpoints.XLarge]) + .subscribe(result => { + this.isMediumScreen = result.matches; + + const breakpoints = result.breakpoints; + + this.screenXS = false + this.screenS = false + this.screenM = false + this.screenL = false + this.screenXL = false + + if (breakpoints[Breakpoints.XSmall]) { + this.screenXS = true + } + else if (breakpoints[Breakpoints.Small]) { + this.screenS = true + } + else if (breakpoints[Breakpoints.Medium]) { + this.screenM = true + } + else if (breakpoints[Breakpoints.Large]) { + this.screenL = true + } + else if (breakpoints[Breakpoints.XLarge]) { + this.screenXL = true + } + + }); + } + + getUsername() { + return this.username; + } async ngOnInit(): Promise { @@ -65,61 +110,61 @@ export class HomeComponent implements OnInit { } - onAutorefresh(){ - if(this.storedAutorefresh.active == true){ + onAutorefresh() { + if (this.storedAutorefresh.active == true) { setTimeout(() => { window.location.reload() - },this.storedAutorefresh.value*1000); + }, this.storedAutorefresh.value * 1000); } } // Manage Auto reload - setAutoreload(value: any){ + setAutoreload(value: any) { const set = Number(this.storedAutorefresh.value); let val; - if(value == false){ + if (value == false) { val = true; - }if(value == true){ + } if (value == true) { val = false; } - this.cs.setCookie('autorefresh', JSON.stringify({active:val, value: set}), 365); + this.cs.setCookie('autorefresh', JSON.stringify({ active: val, value: set }), 365); this.ngOnInit(); } - getAutoreload(){ + getAutoreload() { return JSON.parse(this.cs.getCookie('autorefresh')); } async initData() { // Agents - const params = {'maxResults': this.maxResults} + const params = { 'maxResults': this.maxResults } - this.gs.getAll(SERV.AGENTS,params).subscribe((agents: any) => { + this.gs.getAll(SERV.AGENTS, params).subscribe((agents: any) => { this.totalAgents = agents.total | 0; - this.activeAgents = agents.values.filter(u=> u.isActive == true).length | 0; + this.activeAgents = agents.values.filter(u => u.isActive == true).length | 0; }); // Tasks - const paramst = {'maxResults': this.maxResults, 'filter': 'isArchived=false'} + const paramst = { 'maxResults': this.maxResults, 'filter': 'isArchived=false' } - this.gs.getAll(SERV.TASKS,paramst).subscribe((tasks: any) => { - this.totalTasks = tasks.values.filter(u=> u.isArchived != true).length | 0; + this.gs.getAll(SERV.TASKS, paramst).subscribe((tasks: any) => { + this.totalTasks = tasks.values.filter(u => u.isArchived != true).length | 0; }); // SuperTasks - this.gs.getAll(SERV.SUPER_TASKS,params).subscribe((stasks: any) => { + this.gs.getAll(SERV.SUPER_TASKS, params).subscribe((stasks: any) => { this.allsupertasks = stasks.total | 0; }); // Cracks // let paramsc = {'maxResults': this.maxResults, 'filter': 'isCracked='+true+''} - const paramsc = {'maxResults': this.maxResults } + const paramsc = { 'maxResults': this.maxResults } - this.gs.getAll(SERV.HASHES,paramsc).subscribe((hashes: any) => { - let lastseven:any = new Date() ; - lastseven = lastseven.setDate(lastseven.getDate() - 7).valueOf()/1000; - const lastsevenObject = hashes.values.filter(u=> (u.isCracked == true && u.timeCracked > lastseven )); + this.gs.getAll(SERV.HASHES, paramsc).subscribe((hashes: any) => { + let lastseven: any = new Date(); + lastseven = lastseven.setDate(lastseven.getDate() - 7).valueOf() / 1000; + const lastsevenObject = hashes.values.filter(u => (u.isCracked == true && u.timeCracked > lastseven)); this.totalCracks = lastsevenObject.length | 0; this.initCrackCard(hashes.values); }); @@ -128,19 +173,19 @@ export class HomeComponent implements OnInit { // Graphs Section - initCrackCard(obj: any){ + initCrackCard(obj: any) { const date_today = new Date(); const year = (new Date()).getFullYear(); - const first_day_of_the_week = new Date(date_today.setDate(date_today.getDate() - date_today.getDay() )); - const epochtime = Math.round(first_day_of_the_week.setDate(first_day_of_the_week.getDate()).valueOf()/1000); + const first_day_of_the_week = new Date(date_today.setDate(date_today.getDate() - date_today.getDay())); + const epochtime = Math.round(first_day_of_the_week.setDate(first_day_of_the_week.getDate()).valueOf() / 1000); - const filterdate = obj.filter(u=> (u.isCracked == true )); + const filterdate = obj.filter(u => (u.isCracked == true)); const arr = []; - for(let i=0; i < filterdate.length; i++){ - const date:any = new Date(filterdate[i]['timeCracked']* 1000); - const iso = date.getUTCFullYear()+'-'+(date.getUTCMonth() + 1)+'-'+date.getUTCDate(); + for (let i = 0; i < filterdate.length; i++) { + const date: any = new Date(filterdate[i]['timeCracked'] * 1000); + const iso = date.getUTCFullYear() + '-' + (date.getUTCMonth() + 1) + '-' + date.getUTCDate(); arr.push([iso]); } @@ -205,10 +250,10 @@ export class HomeComponent implements OnInit { label: { show: true, formatter: function (p) { - if(date_today.getDate() == p.data[0]){ + if (date_today.getDate() == p.data[0]) { return 'X'; } - else{ + else { return ''; } } diff --git a/src/app/home/home.module.ts b/src/app/home/home.module.ts index cab6f79b..59a18035 100644 --- a/src/app/home/home.module.ts +++ b/src/app/home/home.module.ts @@ -10,12 +10,16 @@ import { ComponentsModule } from "../shared/components.module"; import { PipesModule } from "../shared/pipes.module"; import { HomeComponent } from "./home.component"; import { HomeRoutingModule } from "./home-routing.module"; +import { MatGridListModule } from '@angular/material/grid-list'; +import { MatIconModule } from "@angular/material/icon"; +import { MatCardModule } from "@angular/material/card"; +import { FlexLayoutModule } from '@angular/flex-layout'; @NgModule({ - declarations:[ + declarations: [ HomeComponent, ], - imports:[ + imports: [ ReactiveFormsModule, HomeRoutingModule, FontAwesomeModule, @@ -25,8 +29,12 @@ import { HomeRoutingModule } from "./home-routing.module"; RouterModule, PipesModule, FormsModule, + MatGridListModule, + FlexLayoutModule, + MatIconModule, + MatCardModule, NgbModule - ] + ] }) -export class HomeModule {} +export class HomeModule { } diff --git a/src/index.html b/src/index.html index 9e132fbc..6ffae50e 100644 --- a/src/index.html +++ b/src/index.html @@ -1,16 +1,19 @@ + Hashtopolis + + - + \ No newline at end of file diff --git a/src/styles/abstract/_abstract-dir.scss b/src/styles/abstract/_abstract-dir.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/src/styles/base/_base-dir.scss b/src/styles/base/_base-dir.scss index 8746712c..dbc16b40 100644 --- a/src/styles/base/_base-dir.scss +++ b/src/styles/base/_base-dir.scss @@ -2,7 +2,7 @@ SECTION BASE DIR ================================== */ - @import 'animations'; - @import 'base'; - @import 'typography'; - +@import 'animations'; +@import 'base'; +@import 'typography'; +@import 'colors'; \ No newline at end of file diff --git a/src/styles/base/_base.scss b/src/styles/base/_base.scss index 04c819b2..7d317d3c 100644 --- a/src/styles/base/_base.scss +++ b/src/styles/base/_base.scss @@ -1,268 +1,126 @@ -/* ================================== - SECTION BASE - ================================== */ +@import './colors'; -/* - 00- Main Container Full Screen -*/ - -.full-container-app { - margin-left: 2%; - margin-right: 2%; -} - -.layout-col { - background-color: rgb(255, 255, 255); -} - -/* - 0- Main Container Full Screen End -*/ - -/* - 01- Margin style -*/ - -.mt-0 { - margin-top: 0rem !important; -} - -.mt-1 { - margin-top: 0.25rem !important; -} - -.mt-2 { - margin-top: 0.5rem !important; -} - -.mt-3 { - margin-top: 0.75rem !important; -} - -.mt-4 { - margin-top: 1rem !important; -} - -.mt-5 { - margin-top: 1.5rem !important; -} - -.mb-0 { - margin-bottom: 0 !important; -} - -.mb-1 { - margin-bottom: 0.25rem !important; -} - -.mb-2 { - margin-bottom: 0.5rem !important; -} - -.mb-3 { - margin-bottom: 1rem !important; -} - -.mb-4 { - margin-bottom: 1.5rem !important; -} - -.ms-0 { - margin-left: 0rem !important; -} - -.ms-1 { - margin-left: 0.25rem !important; -} - -.ms-2 { - margin-left: 0.5rem !important; -} - -.ms-3 { - margin-left: 0.75rem !important; -} - -.ms-4 { - margin-left: 1rem !important; -} - -.my-1 { - margin-top: 0.25rem !important; - margin-bottom: 0.25rem !important; +a { + color: $accent-800 !important; } -/* - 01- Margin Style End -*/ - - -/* - 02- Padding style -*/ - -.p-0 { - padding: 0rem !important; -} - -.p-1 { - padding: 0.25rem !important; -} - -.p-3 { - padding: 0.5rem !important; -} - -.p-4 { - padding: 0.75rem !important; -} - -.p-lg-0 { - padding: 1rem !important; +.text-ok { + color: $color-green !important; } -.p-lg-1 { - padding: 1.25rem !important; +.text-warning { + color: $color-orange !important; } -.p-lg-2 { - padding: 1.5rem !important; +.text-critical { + color: $warn-600 !important; } -.p-lg-3 { - padding: 1.75rem !important; +.text-inactive { + color: $color-grey-light !important; } -.p-lg-4 { - padding: 2rem !important; +.text-unknown { + color: $color-blue-light !important; } -.p-lg-5 { - padding: 2.25rem !important; +.text-invalid { + color: $color-red !important; } -.py-0 { - padding-top: 0 !important; - padding-bottom: 0 !important; +.text-primary { + color: $primary-600 !important; } -.py-1 { - padding-top: 0.25rem !important; - padding-bottom: 0.25rem !important; +.text-accent-900 { + color: $accent-900 !important; } -.py-2 { - padding-top: 0.5rem !important; - padding-bottom: 0.5rem !important; +.text-accent-800 { + color: $accent-800 !important; } -.py-3 { - padding-top: 1rem !important; - padding-bottom: 1rem !important; +.text-accent-300 { + color: $accent-300 !important; } -.py-4 { - padding-top: 1.5rem !important; - padding-bottom: 1.5rem !important; +.text-primary-300 { + color: $primary-300 !important; } -.ps-0 { - padding-left: 0 !important; +.truncate { + display: inline-block !important; + max-width: 150px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } +.page-title-wrapper { + display: flex; + justify-content: space-between; + margin-top: 1em; + margin-bottom: 1em; -/* - 02- Padding style End -*/ + h2 { + margin: 0; + } -/* - 03- Max Width style -*/ -.fmxw-200 { - max-width: 200px !important; + button { + margin-left: auto; + } } -.fmxw-300 { - max-width: 300px !important; +h2 { + .mat-icon { + position: relative; + top: 4px; + } } -.fmxw-400 { - max-width: 400px !important; -} +.home-grid { + margin-top: 50px; -.fmxw-500 { - max-width: 500px !important; -} - -.fmxw-600 { - max-width: 600px !important; -} + .project-info { + background-color: $primary-900; + color: #ffffff; + } -.fmxw-700 { - max-width: 700px !important; -} + .mat-mdc-card { + position: relative; + } -.fmxw-700 { - max-width: 700px !important; -} + .mat-mdc-card-content { + h3 { + color: $primary-900; + font-weight: 200; + margin-top: 0; + font-size: 1.5em; -.fmxw-800 { - max-width: 800px !important; -} + .mat-icon { + position: relative; + top: 4px; + } + } -.fmxw-900 { - max-width: 900px !important; -} + span { + display: inline-block; + width: 100%; -.fmxw-1000 { - max-width: 1000px !important; -} + &.value { + text-align: right; + font-size: 2em; + color: $accent-900; + } -/* - 03- Padding style End -*/ - -/* - 04- Other -*/ - -.justify-content-between { - justify-content: space-between !important; -} - -.align-items-center { - align-items: center !important; -} - -.align-items-right { - align-items: right !important; -} - -.d-flex { - display: flex !important; -} - -.flex-wrap { - flex-wrap: wrap !important; -} - -.flex-md-nowrap { - flex-wrap: nowrap !important; -} - -.d-block { - display: block !important; -} - -.mb-md-0 { - margin-bottom: 0 !important; -} - -.align-inner-right { - margin-left: auto; - margin-right: 0; -} + &.label { + color: $primary-400; -/* - 04- Other End -*/ + .mat-icon { + position: relative; + top: 6px; + } + } + } + } +} \ No newline at end of file diff --git a/src/styles/layout/_layout-dir.scss b/src/styles/layout/_layout-dir.scss index 11ad1b96..853b3b96 100644 --- a/src/styles/layout/_layout-dir.scss +++ b/src/styles/layout/_layout-dir.scss @@ -2,7 +2,8 @@ SECTION LAYOUT DIR ================================== */ - @import 'footer'; - @import 'header'; - @import 'grid'; - @import 'navigation'; +@import 'footer'; +@import 'header'; +@import 'grid'; +@import 'navigation'; +@import 'layout'; \ No newline at end of file diff --git a/src/styles/styles.scss b/src/styles/styles.scss index 01048276..0f544026 100644 --- a/src/styles/styles.scss +++ b/src/styles/styles.scss @@ -6,12 +6,8 @@ Import directories */ - @import './abstract/abstract-dir'; - @import './base/base-dir'; - @import './components/components-dir'; - @import './layout/layout-dir'; - @import './pages/pages-dir'; - - - - +@import './theme.scss'; +@import './base/base-dir'; +@import './components/components-dir'; +@import './layout/layout-dir'; +@import './pages/pages-dir'; \ No newline at end of file From c584b6ae1af5377e1ce8cc1b345aaff76e2a9448 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Sat, 4 Nov 2023 22:32:11 +0100 Subject: [PATCH 158/419] label constants + user menu --- src/app/layout/header/header.component.html | 3 +- src/app/layout/header/header.component.ts | 133 ++++++++++++-------- src/app/layout/header/header.constants.ts | 37 ++++++ src/app/layout/header/header.model.ts | 4 +- 4 files changed, 123 insertions(+), 54 deletions(-) create mode 100644 src/app/layout/header/header.constants.ts diff --git a/src/app/layout/header/header.component.html b/src/app/layout/header/header.component.html index 4683fa09..b866db68 100644 --- a/src/app/layout/header/header.component.html +++ b/src/app/layout/header/header.component.html @@ -4,6 +4,7 @@ {{ this.headerConfig.brand.name }} - + \ No newline at end of file diff --git a/src/app/layout/header/header.component.ts b/src/app/layout/header/header.component.ts index 2db42f48..72b4d939 100644 --- a/src/app/layout/header/header.component.ts +++ b/src/app/layout/header/header.component.ts @@ -3,7 +3,9 @@ import { environment } from './../../../environments/environment'; import { Subscription } from 'rxjs'; import { AuthService } from '../../core/_services/access/auth.service'; import { MainMenuItem } from './header.model'; -import { ActionMenuItem } from 'src/app/core/_components/menus/action-menu/action-menu.model'; +import { UserData } from 'src/app/core/_models/auth-user.model'; +import { ActionMenuEvent } from 'src/app/core/_components/menus/action-menu/action-menu.model'; +import { HeaderMenuAction, HeaderMenuLabel } from './header.constants'; @Component({ @@ -12,28 +14,22 @@ import { ActionMenuItem } from 'src/app/core/_components/menus/action-menu/actio }) export class HeaderComponent implements OnInit, OnDestroy { private subscriptions: Subscription[] = [] + private username = '' headerConfig = environment.config.header; - isAuthentificated = false; - mainMenu: MainMenuItem[] = [ - this.getAgentsMenu(), - this.getTasksMenu(), - this.getHashlistsMenu(), - this.getFilesMenu(), - this.getBinariesMenu(), - this.getConfigMenu(), - this.getUsersMenu(), - this.getAdminMenu() - ] + mainMenu: MainMenuItem[] = [] - constructor(private authService: AuthService) { } + constructor(private authService: AuthService) { + this.rebuildMenu() + } ngOnInit(): void { - this.subscriptions.push(this.authService.user - .subscribe(user => { - this.isAuthentificated = !!user; - }) - ) + this.subscriptions.push(this.authService.user$.subscribe((user: UserData) => { + if (user) { + this.username = user._username + } + this.rebuildMenu() + })) } ngOnDestroy(): void { @@ -42,8 +38,25 @@ export class HeaderComponent implements OnInit, OnDestroy { } } - onLogOut(): void { - this.authService.logOut(); + menuItemClicked(event: ActionMenuEvent): void { + if (event.menuItem.action === HeaderMenuAction.LOGOUT) { + this.authService.clearAuthenticationData(); + this.rebuildMenu() + window.location.reload(); + } + } + + rebuildMenu(): void { + this.mainMenu = [ + this.getAgentsMenu(), + this.getTasksMenu(), + this.getHashlistsMenu(), + this.getFilesMenu(), + this.getBinariesMenu(), + this.getConfigMenu(), + this.getUsersMenu(), + this.getAdminMenu() + ] } /** @@ -52,14 +65,15 @@ export class HeaderComponent implements OnInit, OnDestroy { */ getAgentsMenu(): MainMenuItem { return { - label: 'Agents', + display: true, + label: HeaderMenuLabel.AGENTS, actions: [[ { - label: 'Show Agents', + label: HeaderMenuLabel.SHOW_AGENTS, routerLink: ['agents', 'show-agents'] }, { - label: 'Agent Status', + label: HeaderMenuLabel.AGENT_STATUS, routerLink: ['agents', 'agent-status'] } ]] @@ -72,26 +86,27 @@ export class HeaderComponent implements OnInit, OnDestroy { */ getTasksMenu(): MainMenuItem { return { - label: 'Tasks', + display: true, + label: HeaderMenuLabel.TASKS, actions: [[ { - label: 'Show Tasks', + label: HeaderMenuLabel.SHOW_TASKS, routerLink: ['tasks', 'show-tasks'] }, { - label: 'Preconfigured Tasks', + label: HeaderMenuLabel.PRECONFIGURED_TASKS, routerLink: ['tasks', 'preconfigured-tasks'] }, { - label: 'Supertasks', + label: HeaderMenuLabel.SUPERTASKS, routerLink: ['tasks', 'supertasks'] }, { - label: 'Import Supertask', + label: HeaderMenuLabel.IMPORT_SUPERTASK, routerLink: ['tasks', 'import-supertasks', 'masks'] }, { - label: 'Chunk activity', + label: HeaderMenuLabel.CHUNK_ACTIVITY, routerLink: ['tasks', 'chunks'] }, ]] @@ -104,22 +119,23 @@ export class HeaderComponent implements OnInit, OnDestroy { */ getHashlistsMenu(): MainMenuItem { return { - label: 'Hashlists', + display: true, + label: HeaderMenuLabel.HASHLISTS, actions: [[ { - label: 'Hashlists', + label: HeaderMenuLabel.SHOW_HASHLISTS, routerLink: ['hashlists', 'hashlist'] }, { - label: 'Superhashlists', + label: HeaderMenuLabel.SUPERHASHLISTS, routerLink: ['hashlists', 'superhashlist'] }, { - label: 'Search Hash', + label: HeaderMenuLabel.SEARCH_HASH, routerLink: ['hashlists', 'search-hash'] }, { - label: 'Show cracks', + label: HeaderMenuLabel.SHOW_CRACKS, routerLink: ['hashlists', 'show-cracks'] } ]] @@ -132,18 +148,19 @@ export class HeaderComponent implements OnInit, OnDestroy { */ getFilesMenu(): MainMenuItem { return { - label: 'Files', + display: true, + label: HeaderMenuLabel.FILES, actions: [[ { - label: 'Wordlists', + label: HeaderMenuLabel.WORDLISTS, routerLink: ['files', 'wordlist'] }, { - label: 'Rules', + label: HeaderMenuLabel.RULES, routerLink: ['files', 'rules'] }, { - label: 'Other', + label: HeaderMenuLabel.OTHER, routerLink: ['files', 'other'] }, ]] @@ -156,7 +173,9 @@ export class HeaderComponent implements OnInit, OnDestroy { */ getAdminMenu(): MainMenuItem { return { - label: 'Admin', + display: this.username !== '', + icon: 'person', + label: this.username, actions: [ [ { @@ -175,6 +194,13 @@ export class HeaderComponent implements OnInit, OnDestroy { label: 'Support', routerLink: ['https://discord.com/channels/419123475538509844/419123475538509846'] }, + ], + [ + { + label: 'Logout', + action: HeaderMenuAction.LOGOUT, + red: true + }, ] ] }; @@ -186,19 +212,20 @@ export class HeaderComponent implements OnInit, OnDestroy { */ getUsersMenu(): MainMenuItem { return { - label: 'Users', + display: true, + label: HeaderMenuLabel.USERS, actions: [ [ { - label: 'All users', + label: HeaderMenuLabel.ALL_USERS, routerLink: ['users', 'all-users'] }, { - label: 'Global Permissions', + label: HeaderMenuLabel.GLOBAL_PERMISSIONS, routerLink: ['users', 'global-permissions-groups'] }, { - label: 'Access Groups', + label: HeaderMenuLabel.ACCESS_GROUPS, routerLink: ['users', 'access-groups'] }, ] @@ -212,23 +239,24 @@ export class HeaderComponent implements OnInit, OnDestroy { */ getConfigMenu(): MainMenuItem { return { - label: 'Config', + display: true, + label: HeaderMenuLabel.CONFIG, actions: [ [ { - label: 'Settings', + label: HeaderMenuLabel.SETTINGS, routerLink: ['config', 'agent'] }, { - label: 'Hashtypes', + label: HeaderMenuLabel.HASHTYPES, routerLink: ['config', 'hashtypes'] }, { - label: 'Health Checks', + label: HeaderMenuLabel.HEALTH_CHECKS, routerLink: ['config', 'health-checks'] }, { - label: 'Log', + label: HeaderMenuLabel.LOG, routerLink: ['config', 'log'] }, ] @@ -242,19 +270,20 @@ export class HeaderComponent implements OnInit, OnDestroy { */ getBinariesMenu(): MainMenuItem { return { - label: 'Binaries', + display: true, + label: HeaderMenuLabel.BINARIES, actions: [ [ { - label: 'Crackers', + label: HeaderMenuLabel.CRACKERS, routerLink: ['config', 'engine', 'crackers'] }, { - label: 'Preprocessors', + label: HeaderMenuLabel.PREPROCESSORS, routerLink: ['config', 'engine', 'preprocessors'] }, { - label: 'Agent Binaries', + label: HeaderMenuLabel.AGENT_BINARIES, routerLink: ['config', 'engine', 'agent-binaries'] }, ] diff --git a/src/app/layout/header/header.constants.ts b/src/app/layout/header/header.constants.ts new file mode 100644 index 00000000..e27a8453 --- /dev/null +++ b/src/app/layout/header/header.constants.ts @@ -0,0 +1,37 @@ +export const HeaderMenuAction = { + LOGOUT: 'logout' +} + +export const HeaderMenuLabel = { + AGENTS: 'Agents', + SHOW_AGENTS: 'Show Agents', + AGENT_STATUS: 'Agent Status', + TASKS: 'Tasks', + SHOW_TASKS: 'Show Tasks', + PRECONFIGURED_TASKS: 'Preconfigured Tasks', + SUPERTASKS: 'Supertasks', + IMPORT_SUPERTASK: 'Import Supertask', + CHUNK_ACTIVITY: 'Chunk activity', + HASHLISTS: 'Hashlists', + SHOW_HASHLISTS: 'Hashlists', + SUPERHASHLISTS: 'Superhashlists', + SEARCH_HASH: 'Search Hash', + SHOW_CRACKS: 'Show cracks', + FILES: 'Files', + WORDLISTS: 'Wordlists', + RULES: 'Rules', + OTHER: 'Other', + USERS: 'Users', + ALL_USERS: 'All users', + GLOBAL_PERMISSIONS: 'Global Permissions', + ACCESS_GROUPS: 'Access Groups', + CONFIG: 'Config', + SETTINGS: 'Settings', + HASHTYPES: 'Hashtypes', + HEALTH_CHECKS: 'Health Checks', + LOG: 'Log', + BINARIES: 'Binaries', + CRACKERS: 'Crackers', + PREPROCESSORS: 'Preprocessors', + AGENT_BINARIES: 'Agent Binaries', +} \ No newline at end of file diff --git a/src/app/layout/header/header.model.ts b/src/app/layout/header/header.model.ts index 29483e2b..7c474a38 100644 --- a/src/app/layout/header/header.model.ts +++ b/src/app/layout/header/header.model.ts @@ -1,6 +1,8 @@ import { ActionMenuItem } from "src/app/core/_components/menus/action-menu/action-menu.model" export interface MainMenuItem { - label: string + label?: string + icon?: string actions: ActionMenuItem[][] + display: boolean } \ No newline at end of file From d8bf889845d1b8fa12b147abdacfb5942ba49546 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Sat, 4 Nov 2023 22:35:02 +0100 Subject: [PATCH 159/419] use storage service when storing session + cleanup --- src/app/app.component.ts | 18 +- src/app/app.module.ts | 2 - src/app/core/_guards/auth.guard.ts | 43 ++--- src/app/core/_guards/permission.guard.ts | 45 +++-- .../_interceptors/auth-interceptor.service.ts | 41 +++-- src/app/core/_models/settings.model.ts | 15 ++ src/app/core/_services/access/auth.service.ts | 160 +++++++++--------- .../_services/access/checktoken.service.ts | 45 ----- 8 files changed, 161 insertions(+), 208 deletions(-) create mode 100644 src/app/core/_models/settings.model.ts delete mode 100644 src/app/core/_services/access/checktoken.service.ts diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 520ccc49..5a43ea42 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -2,17 +2,9 @@ import { AfterViewInit, Component, ElementRef, Inject, OnInit, PLATFORM_ID } fro import { NavigationEnd, Router } from '@angular/router'; import { isPlatformBrowser } from '@angular/common'; import { filter } from 'rxjs'; -/** - * Authentification Service, Cookies and Local Storage - * -**/ import { UIConfigService } from './core/_services/shared/storage.service'; import { CookieService } from './core/_services/shared/cookies.service'; import { AuthService } from './core/_services/access/auth.service'; -/** - * Idle watching - * -**/ import { TimeoutComponent } from './shared/alert/timeout/timeout.component'; import { ModalDismissReasons, NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { Idle, DEFAULT_INTERRUPTSOURCES } from '@ng-idle/core'; @@ -114,13 +106,13 @@ export class AppComponent implements OnInit, AfterViewInit { ngOnInit(): void { this.authService.autoLogin(); - this.authService.isLogged.subscribe(logged => { - this.isLogged = logged; - if (logged) { + this.authService.authenticationStatus$.subscribe(status => { + this.isLogged = status; + if (status) { this.storageInit(); } }); - this.authService.checkStatus(); + this.authService.checkAuthenticationStatus(); this.uiSettings = new UISettingsUtilityClass(this.storage) } @@ -190,7 +182,7 @@ export class AppComponent implements OnInit, AfterViewInit { } onLogOut() { - this.authService.logOut(); + this.authService.clearAuthenticationData(); this.closeModal(); } diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 50f1e6ee..fc733979 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -3,7 +3,6 @@ * */ import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; -import { CheckTokenService } from './core/_services/access/checktoken.service'; import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http'; import { AppPreloadingStrategy } from './core/app_preloading_strategy'; import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; @@ -89,7 +88,6 @@ import { CoreComponentsModule } from './core/_components/core-components.module' ], providers: [ Title, - CheckTokenService, { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptorService, diff --git a/src/app/core/_guards/auth.guard.ts b/src/app/core/_guards/auth.guard.ts index 04decf41..e7014d98 100644 --- a/src/app/core/_guards/auth.guard.ts +++ b/src/app/core/_guards/auth.guard.ts @@ -1,6 +1,6 @@ import { ActivatedRouteSnapshot, CanActivateFn, Router, RouterStateSnapshot } from "@angular/router"; import { Injectable, inject } from "@angular/core"; -import { map, take, of, Observable } from "rxjs"; +import { map, take, Observable } from "rxjs"; import { AuthService } from "../_services/access/auth.service"; @@ -8,27 +8,28 @@ import { AuthService } from "../_services/access/auth.service"; providedIn: 'root' }) class AuthGuard { - isAuthenticated: boolean; + isAuthenticated: boolean; - constructor( - private authService: AuthService, - private router: Router - ){} + constructor( + private authService: AuthService, + private router: Router + ) { } - canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { - return this.authService.user.pipe( - take(1), - map(user =>{ - const isAuth = !!user; - if(isAuth){ - return true; // user authorised then return - } - this.authService.redirectUrl = state.url; - this.router.navigate(['/auth']); - return false; - })); - } + canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { + return this.authService.user$.pipe( + take(1), + map(user => { + const isAuth = !!user; + if (isAuth) { + return true; + } + this.authService.redirectUrl = state.url; + this.router.navigate(['/auth']); + return false; + })); + } } -export const IsAuth: CanActivateFn = (route:ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable => { - return inject(AuthGuard).canActivate(route,state); + +export const IsAuth: CanActivateFn = (route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable => { + return inject(AuthGuard).canActivate(route, state); } diff --git a/src/app/core/_guards/permission.guard.ts b/src/app/core/_guards/permission.guard.ts index 90a23bbf..dbf538b5 100644 --- a/src/app/core/_guards/permission.guard.ts +++ b/src/app/core/_guards/permission.guard.ts @@ -1,6 +1,5 @@ -import { ActivatedRouteSnapshot, CanActivate, CanActivateFn, RouterStateSnapshot, UrlTree } from "@angular/router"; +import { ActivatedRouteSnapshot, CanActivateFn, RouterStateSnapshot } from "@angular/router"; import { Perm } from "../_constants/userpermissions.config"; -import Swal from 'sweetalert2/dist/sweetalert2.js'; import { Injectable, inject } from "@angular/core"; import { map, Observable, take } from "rxjs"; @@ -12,29 +11,29 @@ import { SERV } from '../_services/main.config'; providedIn: 'root' }) export class PermissionGuard { - isAuthenticated: boolean; + isAuthenticated: boolean; - constructor( - private alert: AlertService, - private gs: GlobalService - ){} + constructor( + private alert: AlertService, + private gs: GlobalService + ) { } - canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { - return this.gs.get(SERV.USERS,this.gs.userId,{'expand':'globalPermissionGroup'}).pipe( - take(1), - map(perm =>{ - const permissions = perm.globalPermissionGroup.permissions; //Check all permissions - const permName = Perm[route.data['permission']].READ; //Get permission name - const hasAccess = permissions[permName]; //returns true or false - if(hasAccess || typeof hasAccess == 'undefined'){ - return true; - } - this.alert.okAlert('ACCESS DENIED','Please contact your Administrator.','error'); - return false; - })); - } + canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { + return this.gs.get(SERV.USERS, this.gs.userId, { 'expand': 'globalPermissionGroup' }).pipe( + take(1), + map(perm => { + const permissions = perm.globalPermissionGroup.permissions; //Check all permissions + const permName = Perm[route.data['permission']].READ; //Get permission name + const hasAccess = permissions[permName]; //returns true or false + if (hasAccess || typeof hasAccess == 'undefined') { + return true; + } + this.alert.okAlert('ACCESS DENIED', 'Please contact your Administrator.', 'error'); + return false; + })); + } } -export const CheckPerm: CanActivateFn = (route:ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable => { - return inject(PermissionGuard).canActivate(route,state); +export const CheckPerm: CanActivateFn = (route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable => { + return inject(PermissionGuard).canActivate(route, state); } diff --git a/src/app/core/_interceptors/auth-interceptor.service.ts b/src/app/core/_interceptors/auth-interceptor.service.ts index 50d8a80c..71bee33c 100644 --- a/src/app/core/_interceptors/auth-interceptor.service.ts +++ b/src/app/core/_interceptors/auth-interceptor.service.ts @@ -3,28 +3,27 @@ import { Injectable } from '@angular/core'; import { take, exhaustMap } from 'rxjs'; import { AuthService } from '../_services/access/auth.service'; +import { UserData } from '../_models/auth-user.model'; @Injectable() -export class AuthInterceptorService implements HttpInterceptor{ - constructor(private authService: AuthService){} - intercept(req: HttpRequest, next: HttpHandler){ - return this.authService.user.pipe( - take(1), - exhaustMap(user => { - if(!user){ - return next.handle(req); - } - const modifiedReq = req.clone({ - // // Interceptor using Params instead of header authentification - //const modifiedReq = req.clone({ - // params: new HttpParams().set('auth', user.token) - //}); - setHeaders: { - Authorization: `Bearer ${user.token}` //Use this one as soon as token is fixed - } - }); - return next.handle(modifiedReq); - })); - } +export class AuthInterceptorService implements HttpInterceptor { + constructor(private authService: AuthService) { } + intercept(req: HttpRequest, next: HttpHandler) { + return this.authService.user$.pipe( + take(1), + exhaustMap((user: UserData) => { + if (!user) { + return next.handle(req); + } + const now = new Date().getTime() + const exp = new Date(user._expires).getTime() + const modifiedReq = req.clone({ + setHeaders: { + Authorization: `Bearer ${user._expires && now < exp ? user._token : ''}` + } + }); + return next.handle(modifiedReq); + })); + } } diff --git a/src/app/core/_models/settings.model.ts b/src/app/core/_models/settings.model.ts new file mode 100644 index 00000000..8fe91e19 --- /dev/null +++ b/src/app/core/_models/settings.model.ts @@ -0,0 +1,15 @@ +export interface HashtopolisSettings { + hashcatBrainEnable: boolean + hashlistAlias: string + blacklistChars: string + chunktime: number + agentStatLimit: number + agentStatTension: number + agentTempThreshold1: number + agentTempThreshold2: number + agentUtilThreshold1: number + agentUtilThreshold2: number + statustimer: number + agenttimeout: number + maxSessionLength: number +} diff --git a/src/app/core/_services/access/auth.service.ts b/src/app/core/_services/access/auth.service.ts index fd45e094..32d42916 100644 --- a/src/app/core/_services/access/auth.service.ts +++ b/src/app/core/_services/access/auth.service.ts @@ -5,12 +5,12 @@ import { catchError, tap } from 'rxjs/operators'; import { Buffer } from 'buffer'; import { ConfigService } from "../shared/config.service"; -import { User, UserData } from '../../_models/auth-user.model'; +import { UserData } from '../../_models/auth-user.model'; import { LocalStorageService } from "../storage/local-storage.service"; export interface AuthResponseData { token: string; - expires: string; + expires: number; } @Injectable({ providedIn: 'root' }) @@ -18,15 +18,16 @@ export class AuthService { static readonly STORAGE_KEY = 'userData' - private tokenExpiration: any; + private tokenExpirationTimout: any; private endpoint = '/auth'; userId!: number redirectUrl = '' isAuthenticated = false; - authenticationStatus = new ReplaySubject(1); - userLoggedIn = new Subject(); - user = new BehaviorSubject(null); + + authenticationStatus$ = new ReplaySubject(1); + userLoggedIn$ = new Subject(); + user$ = new BehaviorSubject(null); constructor( private http: HttpClient, @@ -37,8 +38,9 @@ export class AuthService { } private initUser() { - this.userLoggedIn.next(false); - if (this.authenticationStatus) { + console.log('initUser') + this.userLoggedIn$.next(false); + if (this.authenticationStatus$) { this.checkAuthenticationStatus(); } } @@ -48,10 +50,11 @@ export class AuthService { return; } - const loadedUser = new User(this.userData._token, new Date(this.userData._expires), '-'); + const now = new Date().getTime() + const exp = new Date(this.userData._expires).getTime() - if (loadedUser.token) { - this.user.next(loadedUser); + if (this.userData._expires && now < exp) { + this.user$.next(this.userData); const expirationDuration = new Date(this.userData._expires).getTime() - new Date().getTime(); this.getRefreshToken(expirationDuration); } @@ -69,7 +72,7 @@ export class AuthService { ).pipe( catchError(this.handleError), tap(response => { - this.handleAuthentication(response.token, +response.expires, username); + this.setAuthenticationData(response.token, +response.expires, username); this.isAuthenticated = true; }) ); @@ -89,84 +92,76 @@ export class AuthService { } public setUserLoggedIn(userLoggedIn: boolean): void { - this.userLoggedIn.next(userLoggedIn); + this.userLoggedIn$.next(userLoggedIn); } public getUserLoggedIn(): Observable { - return this.userLoggedIn.asObservable(); + return this.userLoggedIn$.asObservable(); } - public autologOut(expirationDuration: number): void { - this.tokenExpiration = setTimeout(() => { - this.logOut(); - }, expirationDuration); + public autologOut(timeoutMs: number): void { + this.tokenExpirationTimout = setTimeout(() => { + this.clearAuthenticationData(); + }, timeoutMs); } - public getRefreshToken(expirationDuration: number): void { - this.tokenExpiration = setTimeout(() => { + public getRefreshToken(timeoutMs: number): void { + this.tokenExpirationTimout = setTimeout(() => { if (this.userData) { - this.http.post( - this.configService.getEndpoint() + this.endpoint + '/refresh', - {}, - { - headers: new HttpHeaders({ Authorization: `Bearer ${this.userData._token}` }) + this.requestRefreshToken().subscribe((response: AuthResponseData) => { + if (response && response.token) { + const expirationMs = new Date(response.expires * 1000); + this.storage.setItem('userData', { + _token: response.token, + _expires: expirationMs, + _username: this.userData._username + }, 0) + } else { + this.clearAuthenticationData(); } - ).pipe( - tap((response: any) => { - if (response && response.token) { - const expirationDate = new Date(response.expires * 1000); - this.storage.setItem('userData', { - _token: response.token, - _expires: expirationDate, - _username: this.userData._username - }, 0) - - } else { - this.logOut(); - } - }) - ).subscribe(); + }) } - }, expirationDuration); + }, timeoutMs); + } + + private requestRefreshToken(): Observable { + const httpHeaders = { headers: new HttpHeaders({ Authorization: `Bearer ${this.userData ? this.userData._token : ''}` }) } + const apiUrl = `${this.configService.getEndpoint()}${this.endpoint}/refresh` + + return this.http.post(apiUrl, {}, httpHeaders) } public refreshToken(): Observable { - return this.http.post( - this.configService.getEndpoint() + this.endpoint + '/refresh ', - {}, - { - headers: new HttpHeaders({ Authorization: `Bearer ${this.userData ? this.userData._token : ''}` }) - } - ).pipe( - tap((response: any) => { - if (response && response.token) { - const expirationDate = new Date(response.expires * 1000); - - this.storage.setItem('userData', { - _token: response.token, - _expires: expirationDate, - _username: this.userData._username - }, 0) - } else { - this.logOut(); - } - }), - catchError((error) => { - // Handle the error here - console.error('An error occurred:', error); - return throwError(() => error); // Rethrow the error for further handling if needed - }) - ); + return this.requestRefreshToken() + .pipe( + tap((response: AuthResponseData) => { + if (response && response.token) { + const expirationDate = new Date(response.expires * 1000); + this.storage.setItem('userData', { + _token: response.token, + _expires: expirationDate, + _username: this.userData._username + }, 0) + } else { + this.clearAuthenticationData(); + } + }), + catchError((error) => { + // Handle the error here + console.error('An error occurred:', error); + return throwError(() => error); // Rethrow the error for further handling if needed + }) + ); } - public logOut(): void { - this.user.next(null); + public clearAuthenticationData(): void { + this.user$.next(null); this.isAuthenticated = false; this.storage.removeItem('userData'); - if (this.tokenExpiration) { - clearTimeout(this.tokenExpiration); + if (this.tokenExpirationTimout) { + clearTimeout(this.tokenExpirationTimout); } - this.tokenExpiration = null; + this.tokenExpirationTimout = null; } get userData(): UserData { @@ -174,24 +169,24 @@ export class AuthService { } checkAuthenticationStatus(): void { + console.log() if (this.userData) { - this.authenticationStatus.next(true); + this.authenticationStatus$.next(true); this.userId = this.getUserId(this.userData._token) } else { - this.authenticationStatus.next(false); + this.authenticationStatus$.next(false); } } - private handleAuthentication(token: string, expires: number, username: string): void { - const expirationDate = new Date(expires * 1000); - const user = new User(token, expirationDate, username); - this.user.next(user); - this.isAuthenticated = true; - this.storage.setItem('userData', { + private setAuthenticationData(token: string, expires: number, username: string): void { + const userData = { _token: token, - _expires: expirationDate, + _expires: new Date(expires * 1000), _username: username - }, 0) + }; + this.user$.next(userData); + this.isAuthenticated = true; + this.storage.setItem('userData', userData, 0) this.userId = this.getUserId(token); } @@ -204,7 +199,6 @@ export class AuthService { break; } } - console.error(errorMessage); return throwError(() => errorMessage); } } diff --git a/src/app/core/_services/access/checktoken.service.ts b/src/app/core/_services/access/checktoken.service.ts deleted file mode 100644 index 83999225..00000000 --- a/src/app/core/_services/access/checktoken.service.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { AuthService } from "./auth.service"; -import { Injectable,} from "@angular/core"; - -export interface AuthResponseData { - token: string, - expires: string, -} - -@Injectable({providedIn: 'root'}) -export class CheckTokenService { - constructor( - private authService: AuthService, - ) { - // We Listen using visibility api to look change events, tab inactive and active - document.addEventListener('visibilitychange', () => { - if (document.visibilityState === 'visible') { - // When tab is now active; we check token validity and refresh if we have time - this.checkTokenValidity(); - } - }); - } - - checkTokenValidity() { - const userData: { _token: string, _expires: string} = JSON.parse(localStorage.getItem('userData')); - if(!userData){ - return; - } - let tokendate = new Date(userData._expires).getTime(); - let currentDate = new Date().getTime(); - let timeDifference = tokendate - currentDate; - // We should be refreshing but when using refresh token, we get an error "Signature verification failure" - // if(timeDifference > 0 && timeDifference < 600){ - // console.log('trying to refresh token') - // this.authService.refreshToken().subscribe( - // (data) => { - // console.log(data) - // } - // ); - // } - if(timeDifference < 15){ - this.authService.logOut(); - } - } - -} From 9974a1b2ff4c6f8df8b27b46d6c6c374c43119ad Mon Sep 17 00:00:00 2001 From: itbf9 Date: Sat, 4 Nov 2023 22:35:38 +0100 Subject: [PATCH 160/419] Update style --- src/app/home/home.component.scss | 58 +++++++++++++++++++ src/styles/base/_base-dir.scss | 3 +- src/styles/base/_colors.scss | 50 ++++++++++++++++ src/styles/base/_form.scss | 81 ++++++++++++++++++++++++++ src/styles/layout/_header.scss | 11 ++++ src/styles/layout/_layout.scss | 22 +++++++ src/styles/theme.scss | 98 ++++++++++++++++++++++++++++++++ 7 files changed, 322 insertions(+), 1 deletion(-) create mode 100644 src/app/home/home.component.scss create mode 100644 src/styles/base/_colors.scss create mode 100644 src/styles/base/_form.scss create mode 100644 src/styles/layout/_layout.scss create mode 100644 src/styles/theme.scss diff --git a/src/app/home/home.component.scss b/src/app/home/home.component.scss new file mode 100644 index 00000000..3c0d5d4e --- /dev/null +++ b/src/app/home/home.component.scss @@ -0,0 +1,58 @@ +.ro-1 { + box-sizing: border-box; + + .co { + display: inline-block; + box-sizing: border-box; + padding: 8px; + + &.co-25 { + width: 100%; + } + } + + &.ro-s { + .co-25 { + width: 50%; + } + } + + &.ro-m, + &.ro-l, + &.ro-xl { + .co-25 { + width: 25%; + } + } +} + +.ro-2 { + box-sizing: border-box; + + .co { + display: inline-block; + box-sizing: border-box; + padding: 8px; + + &.co-100 { + width: 100%; + } + } + + &.ro-m, + &.ro-l, + &.ro-xl { + .co-100 { + width: 100%; + padding: 0; + overflow: hidden; + + .app-echarts { + margin: 0; + margin-top: -30px !important; + padding: 0; + height: 250px; + } + } + } +} \ No newline at end of file diff --git a/src/styles/base/_base-dir.scss b/src/styles/base/_base-dir.scss index dbc16b40..39e08c82 100644 --- a/src/styles/base/_base-dir.scss +++ b/src/styles/base/_base-dir.scss @@ -5,4 +5,5 @@ @import 'animations'; @import 'base'; @import 'typography'; -@import 'colors'; \ No newline at end of file +@import 'colors'; +@import 'form'; \ No newline at end of file diff --git a/src/styles/base/_colors.scss b/src/styles/base/_colors.scss new file mode 100644 index 00000000..2226700e --- /dev/null +++ b/src/styles/base/_colors.scss @@ -0,0 +1,50 @@ +@use '@angular/material' as mat; + +$color-white: #ffffff; +$color-grey-light: #cccccc; +$color-grey-border: #999999; +$color-grey: #666666; +$color-grey-metal: #4B5563; +$color-grey-dark: #444444; +$color-black: #111111; +$color-orange: #ff9900; +$color-orange-dark: #b36e06; +$color-green: #009933; +$color-green-dark: #1c6d37; +$color-red: #ff0000; +$color-red-dark: #800000; +$color-blue-light: #42d4f4; +$color-blue: #0b83b3; + +$primary-50: mat.get-color-from-palette($hashtopolis-primary-palette, 50); +$primary-100: mat.get-color-from-palette($hashtopolis-primary-palette, 100); +$primary-200: mat.get-color-from-palette($hashtopolis-primary-palette, 200); +$primary-300: mat.get-color-from-palette($hashtopolis-primary-palette, 300); +$primary-400: mat.get-color-from-palette($hashtopolis-primary-palette, 400); +$primary-500: mat.get-color-from-palette($hashtopolis-primary-palette, 500); +$primary-600: mat.get-color-from-palette($hashtopolis-primary-palette, 600); +$primary-700: mat.get-color-from-palette($hashtopolis-primary-palette, 700); +$primary-800: mat.get-color-from-palette($hashtopolis-primary-palette, 800); +$primary-900: mat.get-color-from-palette($hashtopolis-primary-palette, 900); + +$accent-50: mat.get-color-from-palette($hashtopolis-accent-palette, 50); +$accent-100: mat.get-color-from-palette($hashtopolis-accent-palette, 100); +$accent-200: mat.get-color-from-palette($hashtopolis-accent-palette, 200); +$accent-300: mat.get-color-from-palette($hashtopolis-accent-palette, 300); +$accent-400: mat.get-color-from-palette($hashtopolis-accent-palette, 400); +$accent-500: mat.get-color-from-palette($hashtopolis-accent-palette, 500); +$accent-600: mat.get-color-from-palette($hashtopolis-accent-palette, 600); +$accent-700: mat.get-color-from-palette($hashtopolis-accent-palette, 700); +$accent-800: mat.get-color-from-palette($hashtopolis-accent-palette, 800); +$accent-900: mat.get-color-from-palette($hashtopolis-accent-palette, 900); + +$warn-50: mat.get-color-from-palette($hashtopolis-warn-palette, 50); +$warn-100: mat.get-color-from-palette($hashtopolis-warn-palette, 100); +$warn-200: mat.get-color-from-palette($hashtopolis-warn-palette, 200); +$warn-300: mat.get-color-from-palette($hashtopolis-warn-palette, 300); +$warn-400: mat.get-color-from-palette($hashtopolis-warn-palette, 400); +$warn-500: mat.get-color-from-palette($hashtopolis-warn-palette, 500); +$warn-600: mat.get-color-from-palette($hashtopolis-warn-palette, 600); +$warn-700: mat.get-color-from-palette($hashtopolis-warn-palette, 700); +$warn-800: mat.get-color-from-palette($hashtopolis-warn-palette, 800); +$warn-900: mat.get-color-from-palette($hashtopolis-warn-palette, 900); \ No newline at end of file diff --git a/src/styles/base/_form.scss b/src/styles/base/_form.scss new file mode 100644 index 00000000..02790234 --- /dev/null +++ b/src/styles/base/_form.scss @@ -0,0 +1,81 @@ +.form-field { + label { + font-weight: 600; + margin-bottom: 4px; + display: inline-block; + color: $primary-700; + } + + &.xs-field { + width: 10%; + } + + &.s-field { + width: 25%; + } + + &.m-field { + width: 40% + } + + &.l-field { + width: 60% + } + + &.xl-field { + width: 100% + } + + .mat-mdc-form-field { + width: 100%; + } + + .mat-mdc-text-field-wrapper.mdc-text-field--outlined .mat-mdc-form-field-infix { + padding-top: 8px; + padding-bottom: 8px; + } + + .mat-mdc-form-field-infix { + min-height: 36px; + } +} + +.xsmall { + .form-field { + &.xs-field { + width: 100%; + } + + &.s-field { + width: 100%; + } + + &.m-field { + width: 100%; + } + + &.l-field { + width: 100% + } + } +} + +.small { + .form-field { + &.xs-field { + width: 30%; + } + + &.s-field { + width: 50%; + } + + &.m-field { + width: 75%; + } + + &.l-field { + width: 100% + } + } +} \ No newline at end of file diff --git a/src/styles/layout/_header.scss b/src/styles/layout/_header.scss index 8f83916c..132f61a4 100644 --- a/src/styles/layout/_header.scss +++ b/src/styles/layout/_header.scss @@ -22,6 +22,11 @@ .mdc-button { margin-left: 8px; + &>.mat-icon { + margin-left: 0; + margin-right: 4px; + } + .mdc-button__label { font-weight: 400; } @@ -34,8 +39,14 @@ color: #ffffff !important; } + .mat-icon { + color: #ffffff !important; + } + } } + + } .light-theme { diff --git a/src/styles/layout/_layout.scss b/src/styles/layout/_layout.scss new file mode 100644 index 00000000..b81e9bb4 --- /dev/null +++ b/src/styles/layout/_layout.scss @@ -0,0 +1,22 @@ +body.full-width-layout { + + .large, + .xlarge, + .small, + .xsmall, + .medium { + width: 100% + } +} + +body.fixed-width-layout { + + .large, + .xlarge, + .small, + .xsmall, + .medium { + max-width: 1500px; + margin: 0 auto; + } +} \ No newline at end of file diff --git a/src/styles/theme.scss b/src/styles/theme.scss new file mode 100644 index 00000000..b03c441f --- /dev/null +++ b/src/styles/theme.scss @@ -0,0 +1,98 @@ +@use '@angular/material' as mat; +@import "@angular/material/theming"; +@include mat.core(); + +$hashtopolis-primary-palette: ( + 50: #eceff1, + 100: #cfd8dc, + 200: #b0bec5, + 300: #90a4ae, + 400: #78909c, + 500: #607d8b, + 600: #546e7a, + 700: #455a64, + 800: #37474f, + 900: #263238, + contrast: (50: rgba(black, 0.87), + 100: rgba(black, 0.87), + 200: rgba(black, 0.87), + 300: rgba(black, 0.87), + 400: rgba(black, 0.87), + 500: #ffffff, + 600: #ffffff, + 700: #ffffff, + 800: #ffffff, + 900: #ffffff) +); + +$hashtopolis-accent-palette: ( + 50: #e0f7fa, + 100: #b2ebf2, + 200: #80deea, + 300: #4dcfe1, + 400: #26c5da, + 500: #00bbd4, + 600: #00abc1, + 700: #0096a7, + 800: #00828f, + 900: #005f64, + contrast: (50: rgba(black, 0.87), + 100: rgba(black, 0.87), + 200: rgba(black, 0.87), + 300: rgba(black, 0.87), + 400: #ffffff, + 500: #ffffff, + 600: #ffffff, + 700: #ffffff, + 800: #ffffff, + 900: #ffffff) +); + +$hashtopolis-warn-palette: ( + 50: #f3e4e8, + 100: #e3bbc8, + 200: #d191a5, + 300: #c06c84, + 400: #b3566d, + 500: #af4159, + 600: #9f3e55, + 700: #8b384e, + 800: #773348, + 900: #56283a, + contrast: (50: rgba(black, 0.87), + 100: rgba(black, 0.87), + 200: rgba(black, 0.87), + 300: rgba(black, 0.87), + 400: #ffffff, + 500: #ffffff, + 600: #ffffff, + 700: #ffffff, + 800: #ffffff, + 900: #ffffff) +); + +$primary: mat.define-palette($hashtopolis-primary-palette); +$accent: mat.define-palette($hashtopolis-accent-palette); +$warn: mat.define-palette($hashtopolis-warn-palette); + +// Include custom palettes in the theme +$hashtopolis-light-theme: mat.define-light-theme((color: (primary: $primary, + accent: $accent, + warn: $warn ), + typography: mat.define-typography-config(), + density: 0, + )); + +$hashtopolis-dark-theme: mat.define-dark-theme((color: (primary: $primary, + accent: $accent, + warn: $warn ), + typography: mat.define-typography-config(), + density: 0, + )); + +@include mat.all-component-themes($hashtopolis-light-theme); + +.dark-theme { + @include mat.all-component-themes($hashtopolis-dark-theme); + background-color: var(--mat-toolbar-container-background-color); +} \ No newline at end of file From bca511e404b86a5b18a54fdc725c07a082d04124 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Sat, 4 Nov 2023 22:36:26 +0100 Subject: [PATCH 161/419] Update ui settings form --- .../ui-settings/ui-settings.component.html | 26 +++++++++---------- .../ui-settings/ui-settings.component.ts | 5 ++-- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/app/account/settings/ui-settings/ui-settings.component.html b/src/app/account/settings/ui-settings/ui-settings.component.html index 790b17f4..9e3c3580 100644 --- a/src/app/account/settings/ui-settings/ui-settings.component.html +++ b/src/app/account/settings/ui-settings/ui-settings.component.html @@ -1,29 +1,29 @@
-

+

+ - Time Format - - {{ d.description }} + + {{ d.description }} -

-

+

+
+ - Layout - + {{ d.description }} -

-

+

+
+ - Theme - + {{ t.description }} -

+
\ No newline at end of file diff --git a/src/app/account/settings/ui-settings/ui-settings.component.ts b/src/app/account/settings/ui-settings/ui-settings.component.ts index 36696032..5b7ecf99 100644 --- a/src/app/account/settings/ui-settings/ui-settings.component.ts +++ b/src/app/account/settings/ui-settings/ui-settings.component.ts @@ -12,9 +12,10 @@ import { MatSnackBar } from '@angular/material/snack-bar'; }) export class UiSettingsComponent implements OnInit { - form!: FormGroup; + form!: FormGroup util: UISettingsUtilityClass - dateFormats: Setting[] = dateFormats + + formats: Setting[] = dateFormats layouts: Setting[] = layouts themes: Setting[] = themes From 82034785a3c4b9812498ea711fe535a103b0d66d Mon Sep 17 00:00:00 2001 From: itbf9 Date: Mon, 6 Nov 2023 11:19:35 +0100 Subject: [PATCH 162/419] Add hash model --- src/app/core/_models/hash.model.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 src/app/core/_models/hash.model.ts diff --git a/src/app/core/_models/hash.model.ts b/src/app/core/_models/hash.model.ts new file mode 100644 index 00000000..2f8469b2 --- /dev/null +++ b/src/app/core/_models/hash.model.ts @@ -0,0 +1,13 @@ +export interface Hash { + _id: number + _self: string + chunkId: number + crackPos: number + hash: string + hashId: number + hashlistId: number + isCracked: boolean + plaintext: string + salt: string + timeCracked: number +} \ No newline at end of file From b1894b3a643adb64384c28c8b96b2bc8865d8686 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Mon, 6 Nov 2023 11:20:34 +0100 Subject: [PATCH 163/419] Add response model --- src/app/core/_models/response.model.ts | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 src/app/core/_models/response.model.ts diff --git a/src/app/core/_models/response.model.ts b/src/app/core/_models/response.model.ts new file mode 100644 index 00000000..57039c7a --- /dev/null +++ b/src/app/core/_models/response.model.ts @@ -0,0 +1,8 @@ +export interface ListResponseWrapper { + _expandable: string + startAt: number + maxResults: number + total: number + isLast: number + values: T[] +} \ No newline at end of file From e420e6abdca919646cc17999c1e203aead53e707 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Mon, 6 Nov 2023 11:22:29 +0100 Subject: [PATCH 164/419] Add supertask model --- src/app/core/_models/supertask.model.ts | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 src/app/core/_models/supertask.model.ts diff --git a/src/app/core/_models/supertask.model.ts b/src/app/core/_models/supertask.model.ts new file mode 100644 index 00000000..b6ea16f7 --- /dev/null +++ b/src/app/core/_models/supertask.model.ts @@ -0,0 +1,6 @@ +export interface SuperTask { + _id: number + _self: string + supertaskId: number + supertaskName: string +} \ No newline at end of file From de17fa4eca60cd3e5f061ef454e47d9ba5fba9b1 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Mon, 6 Nov 2023 11:23:05 +0100 Subject: [PATCH 165/419] Add datetime utils --- src/app/shared/utils/datetime.ts | 79 ++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 src/app/shared/utils/datetime.ts diff --git a/src/app/shared/utils/datetime.ts b/src/app/shared/utils/datetime.ts new file mode 100644 index 00000000..7265fa3d --- /dev/null +++ b/src/app/shared/utils/datetime.ts @@ -0,0 +1,79 @@ +//import moment from 'moment'; + +/** + * Calculate a Unix timestamp for a date in the past. + * + * @param {number} days - The number of days to go back in the past. + * @returns {number} The Unix timestamp (in seconds) for the specified date in the past. + */ +export const unixTimestampInPast = (days: number): number => { + const currentDate = new Date(); + const inPast = new Date(currentDate.getTime() - days * 24 * 60 * 60 * 1000); + + return Math.floor(inPast.getTime() / 1000); +} + +/** + * Formats a Unix timestamp into a date-time string using a custom format. + * + * @param unixTimestamp The Unix timestamp to format, in seconds. + * @param fmt The format string to define the output format. Supported placeholders: + * - yy: 2-digit year + * - yyyy: 4-digit year + * - MM: Zero-padded month (01-12) + * - M: Month (1-12) + * - dd: Zero-padded day of the month (01-31) + * - d: Day of the month (1-31) + * - HH: Zero-padded hours in 24-hour format (00-23) + * - H: Hours in 24-hour format (0-23) + * - mm: Zero-padded minutes (00-59) + * - m: Minutes (0-59) + * - ss: Zero-padded seconds (00-59) + * - s: Seconds (0-59) + * + * @returns The formatted date-time string. + */ +export function formatUnixTimestamp(unixTimestamp: number, fmt: string): string { + //return moment.unix(unixTimestamp).format(fmt) + const date = new Date(unixTimestamp * 1000); + + return formatDate(date, fmt) +} + +/** + * Formats a Date into a date-time string using a custom format. + * + * @param date The date to format. + * @param fmt The format string to define the output format. Supported placeholders: + * - yy: 2-digit year + * - yyyy: 4-digit year + * - MM: Zero-padded month (01-12) + * - M: Month (1-12) + * - dd: Zero-padded day of the month (01-31) + * - d: Day of the month (1-31) + * - HH: Zero-padded hours in 24-hour format (00-23) + * - H: Hours in 24-hour format (0-23) + * - mm: Zero-padded minutes (00-59) + * - m: Minutes (0-59) + * - ss: Zero-padded seconds (00-59) + * - s: Seconds (0-59) + * + * @returns The formatted date-time string. + */ +export function formatDate(date: Date, fmt: string): string { + //return moment(date).format(fmt) + const pad = (value: number) => (value < 10 ? `0${value}` : value.toString()); + + return fmt.replace(/yyyy/g, date.getFullYear().toString()) + .replace(/yy/g, date.getFullYear().toString().slice(-2)) + .replace(/MM/g, pad(date.getMonth() + 1)) + .replace(/M/g, (date.getMonth() + 1).toString()) + .replace(/dd/g, pad(date.getDate())) + .replace(/d/g, date.getDate().toString()) + .replace(/hh/g, pad(date.getHours())) + .replace(/h/g, date.getHours().toString()) + .replace(/mm/g, pad(date.getMinutes())) + .replace(/m/g, date.getMinutes().toString()) + .replace(/ss/g, pad(date.getSeconds())) + .replace(/s/g, date.getSeconds().toString()); +} \ No newline at end of file From 9f119b8705fea06fb3ded8e4144a4a2d831c364f Mon Sep 17 00:00:00 2001 From: itbf9 Date: Mon, 6 Nov 2023 11:23:31 +0100 Subject: [PATCH 166/419] update styles --- src/app/home/home.component.scss | 17 +++++++++++ src/styles/base/_base.scss | 48 +++++++++++++++++++++++++++++--- src/styles/layout/_footer.scss | 23 +++++++++++---- src/styles/layout/_header.scss | 3 +- src/styles/theme.scss | 2 +- 5 files changed, 82 insertions(+), 11 deletions(-) diff --git a/src/app/home/home.component.scss b/src/app/home/home.component.scss index 3c0d5d4e..34e10eed 100644 --- a/src/app/home/home.component.scss +++ b/src/app/home/home.component.scss @@ -55,4 +55,21 @@ } } } +} + +.chart-controls { + color: #666; + font-size: 0.8em; + text-align: right; + + .mdc-button { + padding: 0; + min-width: 32px; + margin-left: 4px; + margin-bottom: 3px; + + .mat-icon { + margin-right: 0; + } + } } \ No newline at end of file diff --git a/src/styles/base/_base.scss b/src/styles/base/_base.scss index 7d317d3c..d7bad090 100644 --- a/src/styles/base/_base.scss +++ b/src/styles/base/_base.scss @@ -1,5 +1,9 @@ @import './colors'; +body { + font-family: var(--mdc-typography-button-font-family, var(--mdc-typography-font-family, Roboto, sans-serif)); +} + a { color: $accent-800 !important; } @@ -78,12 +82,39 @@ h2 { } } +.xsmall, +.small, +.medium, +.large, +.xlarge { + display: flex; + flex-direction: column; + min-height: calc(100vh - 81px); + margin: 0; + + main { + flex: 1; + } + + footer { + margin-top: -52px; + padding: 0; + width: 100%; + margin-bottom: 0; + } +} + +.mat-card-box { + border: 1px solid $primary-700 !important; +} + .home-grid { margin-top: 50px; .project-info { - background-color: $primary-900; - color: #ffffff; + text-align: center; + border-radius: 0; + border: 3px solid $primary-900 } .mat-mdc-card { @@ -103,6 +134,11 @@ h2 { } } + hr { + border-width: 1px; + border-style: solid; + } + span { display: inline-block; width: 100%; @@ -114,11 +150,15 @@ h2 { } &.label { - color: $primary-400; + color: $primary-900; + font-size: .8em; .mat-icon { + font-size: 16px; + height: 16px; + width: 16px; position: relative; - top: 6px; + top: 3px; } } } diff --git a/src/styles/layout/_footer.scss b/src/styles/layout/_footer.scss index 5356a26e..21c58a4c 100644 --- a/src/styles/layout/_footer.scss +++ b/src/styles/layout/_footer.scss @@ -1,7 +1,20 @@ -/* ================================== - SECTION FOOTER - ================================== */ +footer { + margin-top: 1em; + font-size: .9em; -.footer { + .mdc-card { text-align: center; -} + background-color: $primary-900; + color: #eee; + border-radius: 0; + + .mat-mdc-card-content { + padding-top: 8px; + + .mat-icon { + position: relative; + top: 6px; + } + } + } +} \ No newline at end of file diff --git a/src/styles/layout/_header.scss b/src/styles/layout/_header.scss index 132f61a4..8cca72da 100644 --- a/src/styles/layout/_header.scss +++ b/src/styles/layout/_header.scss @@ -17,7 +17,7 @@ } .hashtopolis-toolbar { - border-bottom: 1px solid $primary-100; + border-bottom: 5px solid $primary-900; .mdc-button { margin-left: 8px; @@ -33,6 +33,7 @@ &.active { background-color: $primary-900; + border-radius: 0; .mdc-button__label { font-weight: 400; diff --git a/src/styles/theme.scss b/src/styles/theme.scss index b03c441f..5fd07c22 100644 --- a/src/styles/theme.scss +++ b/src/styles/theme.scss @@ -93,6 +93,6 @@ $hashtopolis-dark-theme: mat.define-dark-theme((color: (primary: $primary, @include mat.all-component-themes($hashtopolis-light-theme); .dark-theme { - @include mat.all-component-themes($hashtopolis-dark-theme); + @include mat.all-component-colors($hashtopolis-dark-theme); background-color: var(--mat-toolbar-container-background-color); } \ No newline at end of file From c1fbea7ac7eba3216c2ae2e041ae348ebf476271 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Mon, 6 Nov 2023 11:24:26 +0100 Subject: [PATCH 167/419] Add refreshPage and refreshInterval to ui-config --- src/app/core/_models/config-ui.model.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/app/core/_models/config-ui.model.ts b/src/app/core/_models/config-ui.model.ts index 3360a3bc..bdc8d286 100644 --- a/src/app/core/_models/config-ui.model.ts +++ b/src/app/core/_models/config-ui.model.ts @@ -8,13 +8,17 @@ export interface TableSettings { export interface UIConfig { layout: Layout theme: Theme - tableSettings: TableSettings, - timefmt: string, + tableSettings: TableSettings + timefmt: string + refreshPage: boolean + refreshInterval: number } export const uiConfigDefault: UIConfig = { layout: 'fixed', theme: 'light', timefmt: 'dd/MM/yyyy h:mm:ss', - tableSettings: {} + tableSettings: {}, + refreshPage: false, + refreshInterval: 10 } From a90793db075a74fdc6c96364eb70e5e43b75beb6 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Mon, 6 Nov 2023 11:25:39 +0100 Subject: [PATCH 168/419] Remove mat setings from feature module --- src/app/account/account.module.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/app/account/account.module.ts b/src/app/account/account.module.ts index a97679be..e9caa954 100644 --- a/src/app/account/account.module.ts +++ b/src/app/account/account.module.ts @@ -16,12 +16,12 @@ import { UiSettingsComponent } from './settings/ui-settings/ui-settings.componen import { AccountSettingsComponent } from "./settings/acc-settings/acc-settings.component"; -import { MAT_FORM_FIELD_DEFAULT_OPTIONS, MatFormFieldModule } from "@angular/material/form-field"; +import { MatFormFieldModule } from "@angular/material/form-field"; import { MatInputModule } from "@angular/material/input"; import { MatIconModule } from "@angular/material/icon"; import { MatSelectModule } from "@angular/material/select"; import { MatButtonModule } from "@angular/material/button"; -import { MAT_SNACK_BAR_DEFAULT_OPTIONS, MatSnackBarModule } from "@angular/material/snack-bar"; +import { MatSnackBarModule } from "@angular/material/snack-bar"; @@ -51,9 +51,5 @@ import { MAT_SNACK_BAR_DEFAULT_OPTIONS, MatSnackBarModule } from "@angular/mater MatButtonModule, MatSnackBarModule, ], - providers: [ - { provide: MAT_FORM_FIELD_DEFAULT_OPTIONS, useValue: { appearance: 'outline' } }, - { provide: MAT_SNACK_BAR_DEFAULT_OPTIONS, useValue: { duration: 2500 } } - ] }) export class AccountModule { } From 4b3b6cdf593c1794f4af6a966e8cb2a0070436d3 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Mon, 6 Nov 2023 11:26:15 +0100 Subject: [PATCH 169/419] Add mat settings to app module --- src/app/app.module.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/app/app.module.ts b/src/app/app.module.ts index fc733979..444a31f0 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -48,7 +48,10 @@ import { MatIconModule } from '@angular/material/icon'; import { MatMenuModule } from '@angular/material/menu'; import { MatTooltipModule } from '@angular/material/tooltip'; import { MatToolbarModule } from '@angular/material/toolbar'; +import { MatCardModule } from "@angular/material/card"; import { CoreComponentsModule } from './core/_components/core-components.module'; +import { MAT_FORM_FIELD_DEFAULT_OPTIONS } from '@angular/material/form-field'; +import { MAT_SNACK_BAR_DEFAULT_OPTIONS, MatSnackBarModule } from '@angular/material/snack-bar'; @NgModule({ declarations: [ @@ -79,7 +82,9 @@ import { CoreComponentsModule } from './core/_components/core-components.module' MatButtonModule, MatIconModule, MatMenuModule, + MatCardModule, MatTooltipModule, + MatSnackBarModule, CoreComponentsModule, NgbModule, AppRoutingModule, // Main routes for the App @@ -100,7 +105,10 @@ import { CoreComponentsModule } from './core/_components/core-components.module' }, ThemeService, AppPreloadingStrategy, - ConfigService + ConfigService, + { provide: MAT_FORM_FIELD_DEFAULT_OPTIONS, useValue: { appearance: 'outline' } }, + { provide: MAT_SNACK_BAR_DEFAULT_OPTIONS, useValue: { duration: 2500, verticalPosition: 'top' } } + ], bootstrap: [AppComponent] }) From 5124d461cc9537e259828dc67724d8d8b27f1860 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Mon, 6 Nov 2023 11:26:39 +0100 Subject: [PATCH 170/419] update footer --- src/app/layout/footer/footer.component.html | 49 +++++++-------------- 1 file changed, 17 insertions(+), 32 deletions(-) diff --git a/src/app/layout/footer/footer.component.html b/src/app/layout/footer/footer.component.html index cc292433..731a8c53 100644 --- a/src/app/layout/footer/footer.component.html +++ b/src/app/layout/footer/footer.component.html @@ -1,32 +1,17 @@ - - - + \ No newline at end of file From 6fc02e3aa014f5a34c92932e154b4998e6d79c09 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Mon, 6 Nov 2023 11:27:48 +0100 Subject: [PATCH 171/419] Allow footer to fill screen --- src/app/app.component.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/app/app.component.html b/src/app/app.component.html index c0f4c345..180b59e1 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -1,9 +1,9 @@
-
+
- -
\ No newline at end of file + + \ No newline at end of file From c534c5c433072675c4c13ecfd7261a99aeb449b6 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Mon, 6 Nov 2023 11:28:04 +0100 Subject: [PATCH 172/419] Update startpage --- src/app/home/home.component.html | 40 ++-- src/app/home/home.component.ts | 303 +++++++++++++++++-------------- src/app/home/home.module.ts | 6 +- 3 files changed, 196 insertions(+), 153 deletions(-) diff --git a/src/app/home/home.component.html b/src/app/home/home.component.html index 48c8f24d..84c140f4 100644 --- a/src/app/home/home.component.html +++ b/src/app/home/home.component.html @@ -2,7 +2,7 @@
- +

dns @@ -12,13 +12,13 @@


check_circle - Active Agents / Total Agents + Active / Total Agents

- +

checklist @@ -34,13 +34,13 @@

- +

checklist Supertasks

- {{ allsupertasks }} + {{ totalSupertasks }}
check_circle @@ -50,7 +50,7 @@

- +

lock_open @@ -70,6 +70,20 @@

[ngClass]="{'ro-2': true, 'ro-m': screenM, 'ro-l': screenL, 'ro-s': screenS, 'ro-xs': screenXS, 'ro-xl': screenXL}">
+ +
+ Last updated: {{ lastUpdated }} + + + + + + +

- - - - - - - \ No newline at end of file + \ No newline at end of file diff --git a/src/app/home/home.component.ts b/src/app/home/home.component.ts index 343d86ae..ff3b0e7a 100644 --- a/src/app/home/home.component.ts +++ b/src/app/home/home.component.ts @@ -1,19 +1,24 @@ -import { faRefresh, faPauseCircle, faInfoCircle, faUserSecret, faTasks, faTasksAlt, faChainBroken, faCalendarWeek, faCalendarDay, faCheckCircle } from '@fortawesome/free-solid-svg-icons'; import { TitleComponent, CalendarComponent, TooltipComponent, VisualMapComponent } from 'echarts/components'; -import { Component, ElementRef, OnInit } from '@angular/core'; -import { faGithub } from '@fortawesome/free-brands-svg-icons'; +import { Component, OnDestroy, OnInit } from '@angular/core'; import { environment } from 'src/environments/environment'; import { CanvasRenderer } from 'echarts/renderers'; -import { interval, Subscription } from 'rxjs'; import { HeatmapChart } from 'echarts/charts'; import * as echarts from 'echarts/core'; import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout'; - - import { GlobalService } from 'src/app/core/_services/main.service'; import { PageTitle } from 'src/app/core/_decorators/autotitle'; import { SERV } from '../core/_services/main.config'; -import { CookieService } from '../core/_services/shared/cookies.service'; +import { LocalStorageService } from '../core/_services/storage/local-storage.service'; +import { UIConfig } from '../core/_models/config-ui.model'; +import { UISettingsUtilityClass } from '../shared/utils/config'; +import { Subscription } from 'rxjs'; +import { ListResponseWrapper } from '../core/_models/response.model'; +import { Agent } from '../core/_models/agent.model'; +import { Task } from '../core/_models/task.model'; +import { SuperTask } from '../core/_models/supertask.model'; +import { Hash } from '../core/_models/hash.model'; +import { formatDate, formatUnixTimestamp, unixTimestampInPast } from '../shared/utils/datetime'; +import { MatSnackBar } from '@angular/material/snack-bar'; @Component({ selector: 'app-home', @@ -21,56 +26,40 @@ import { CookieService } from '../core/_services/shared/cookies.service'; styleUrls: ['./home.component.scss'] }) @PageTitle(['Dashboard']) -export class HomeComponent implements OnInit { - - - - username = 'Admin'; - - faCalendarWeek = faCalendarWeek; - faChainBroken = faChainBroken; - faCheckCircle = faCheckCircle; - faCalendarDay = faCalendarDay; - faPauseCircle = faPauseCircle; - faInfoCircle = faInfoCircle; - faUserSecret = faUserSecret; - faTasksAlt = faTasksAlt; - faRefresh = faRefresh; - faTasks = faTasks; +export class HomeComponent implements OnInit, OnDestroy { - faGithub = faGithub; - - isMediumScreen = false; + util: UISettingsUtilityClass + /** Flags for responsive design */ screenXS = false screenS = false screenM = false screenL = false screenXL = false + /** Counters for dashboard statistics */ + activeAgents = 0 + totalAgents = 0 + totalTasks = 0 + totalCracks = 0 + totalSupertasks = 0 - // Dashboard variables - activeAgents = 0; - totalAgents = 0; - totalTasks = 0; - totalCracks = 0; - allsupertasks = 0; + lastUpdated: string private maxResults = environment.config.prodApiMaxResults; - storedAutorefresh: any = [] - private updateSubscription: Subscription; - public punchCardOpts = {} - public punchCardOptss = {} + private subscriptions: Subscription[] = [] + private pageReloadTimeout: NodeJS.Timeout + private crackedChart: echarts.ECharts constructor( private gs: GlobalService, - private cs: CookieService, + private service: LocalStorageService, + private snackBar: MatSnackBar, private breakpointObserver: BreakpointObserver ) { + // Observe screen breakpoints for responsive design this.breakpointObserver.observe([Breakpoints.XSmall, Breakpoints.Small, Breakpoints.Medium, Breakpoints.Large, Breakpoints.XLarge]) .subscribe(result => { - this.isMediumScreen = result.matches; - const breakpoints = result.breakpoints; this.screenXS = false @@ -94,114 +83,149 @@ export class HomeComponent implements OnInit { else if (breakpoints[Breakpoints.XLarge]) { this.screenXL = true } - }); } - getUsername() { - return this.username; + ngOnInit(): void { + this.util = new UISettingsUtilityClass(this.service) + this.initChart(); + this.initData(); + this.onAutorefresh(); } - async ngOnInit(): Promise { - this.initData(); - this.storedAutorefresh = this.getAutoreload(); - this.onAutorefresh(); + ngOnDestroy(): void { + for (const sub of this.subscriptions) { + sub.unsubscribe() + } + } + /** + * Get the autorefresh interval setting. + */ + get refreshInterval(): number { + return this.util.getSetting('refreshInterval') } + /** + * Check if autorefresh of the page is enabled. + */ + get refreshPage(): boolean { + return this.util.getSetting('refreshPage') + } + + /** + * Automatically refresh the page based on the configured interval. + */ onAutorefresh() { - if (this.storedAutorefresh.active == true) { - setTimeout(() => { - window.location.reload() - }, this.storedAutorefresh.value * 1000); + const timeout = this.refreshInterval + if (this.refreshPage) { + this.pageReloadTimeout = setTimeout(() => { + this.initData() + this.onAutorefresh() + }, timeout * 1000); } } - // Manage Auto reload - setAutoreload(value: any) { - const set = Number(this.storedAutorefresh.value); - let val; - if (value == false) { - val = true; - } if (value == true) { - val = false; + /** + * Toggle the autoreload setting and refresh the data. + * + * @param flag - New autoreload flag. + */ + setAutoreload(flag: boolean) { + const updatedSettings = this.util.updateSettings({ refreshPage: flag }) + let message = '' + + if (updatedSettings) { + if (flag) { + message = 'Autoreload is enabled' + } else { + message = 'Autoreload is paused' + clearTimeout(this.pageReloadTimeout) + } + this.snackBar.open(message, 'Close'); } - this.cs.setCookie('autorefresh', JSON.stringify({ active: val, value: set }), 365); - this.ngOnInit(); + this.initData(); + this.onAutorefresh(); } - getAutoreload() { - return JSON.parse(this.cs.getCookie('autorefresh')); + /** + * Initialize dashboard data. + */ + initData(): void { + this.getAgents() + this.getTasks() + this.getSuperTasks() + this.getCracks() } - async initData() { - - // Agents - const params = { 'maxResults': this.maxResults } - - this.gs.getAll(SERV.AGENTS, params).subscribe((agents: any) => { - this.totalAgents = agents.total | 0; - this.activeAgents = agents.values.filter(u => u.isActive == true).length | 0; - }); - - // Tasks - const paramst = { 'maxResults': this.maxResults, 'filter': 'isArchived=false' } - - this.gs.getAll(SERV.TASKS, paramst).subscribe((tasks: any) => { - this.totalTasks = tasks.values.filter(u => u.isArchived != true).length | 0; - }); - - // SuperTasks - this.gs.getAll(SERV.SUPER_TASKS, params).subscribe((stasks: any) => { - this.allsupertasks = stasks.total | 0; - }); - - // Cracks - // let paramsc = {'maxResults': this.maxResults, 'filter': 'isCracked='+true+''} - const paramsc = { 'maxResults': this.maxResults } - - this.gs.getAll(SERV.HASHES, paramsc).subscribe((hashes: any) => { - let lastseven: any = new Date(); - lastseven = lastseven.setDate(lastseven.getDate() - 7).valueOf() / 1000; - const lastsevenObject = hashes.values.filter(u => (u.isCracked == true && u.timeCracked > lastseven)); - this.totalCracks = lastsevenObject.length | 0; - this.initCrackCard(hashes.values); - }); - + /** + * Get the list of active agents. + */ + private getAgents(): void { + const params = { maxResults: this.maxResults, filter: 'isActive=true' } + this.subscriptions.push(this.gs.getAll(SERV.AGENTS, params).subscribe((response: ListResponseWrapper) => { + this.totalAgents = response.total | 0; + this.activeAgents = response.values.length | 0; + })) } - // Graphs Section + /** + * Get the list of tasks. + */ + private getTasks(): void { + const params = { 'maxResults': this.maxResults, filter: 'isArchived=false' } - initCrackCard(obj: any) { + this.subscriptions.push(this.gs.getAll(SERV.TASKS, params).subscribe((response: ListResponseWrapper) => { + this.totalTasks = response.values.length | 0; + })); + } - const date_today = new Date(); - const year = (new Date()).getFullYear(); - const first_day_of_the_week = new Date(date_today.setDate(date_today.getDate() - date_today.getDay())); - const epochtime = Math.round(first_day_of_the_week.setDate(first_day_of_the_week.getDate()).valueOf() / 1000); + /** + * Get the list of supertasks. + */ + private getSuperTasks(): void { + const params = { 'maxResults': this.maxResults } - const filterdate = obj.filter(u => (u.isCracked == true)); + this.subscriptions.push(this.gs.getAll(SERV.SUPER_TASKS, params).subscribe((response: ListResponseWrapper) => { + this.totalSupertasks = response.total | 0; + })); + } - const arr = []; - for (let i = 0; i < filterdate.length; i++) { - const date: any = new Date(filterdate[i]['timeCracked'] * 1000); - const iso = date.getUTCFullYear() + '-' + (date.getUTCMonth() + 1) + '-' + date.getUTCDate(); - arr.push([iso]); + /** + * Get the list of cracked hashes from the last seven days. + */ + private getCracks(): void { + const timestampInPast = unixTimestampInPast(7) + const params = { + maxResults: this.maxResults, + filter: 'isCracked=true' } - const counts = arr.reduce((p, c) => { - const weekd = c[0]; - if (!p.hasOwnProperty(weekd)) { - p[weekd] = 0; - } - p[weekd]++; - return p; - }, {}); + this.subscriptions.push(this.gs.getAll(SERV.HASHES, params).subscribe((response: ListResponseWrapper) => { + const lastsevenObject = response.values.filter(u => (u.isCracked == true && u.timeCracked > timestampInPast)); + this.totalCracks = lastsevenObject.length | 0; + this.updateChart(response.values); + })); + } - const countsExtended = Object.keys(counts).map(k => { - return [k, counts[k]] + /** + * Count the occurrences of items in an array. + * + * @param arr - The array to count occurrences in. + * @returns An object with the occurrences of each item. + */ + countOccurrences(arr: string[]): { [key: string]: number } { + return arr.reduce((counts, date) => { + counts[date] = (counts[date] || 0) + 1; + return counts; }, {}); + } + /** + * Initialize the heatmap chart. + */ + initChart(): void { echarts.use([ TitleComponent, CalendarComponent, @@ -212,10 +236,29 @@ export class HomeComponent implements OnInit { ]); const chartDom = document.getElementById('pcard'); - const myChart = echarts.init(chartDom); - let option; + this.crackedChart = echarts.init(chartDom); + } + + /** + * Update the heatmap chart. + * + * @param data - Hash data used for the chart. + */ + updateChart(data: Hash[]): void { + + const currentDate = new Date(); + const currentYear = currentDate.getFullYear(); - option = { + // Extract and format cracked dates + const formattedDates: string[] = data.map(item => formatUnixTimestamp(item.timeCracked, 'yyyy-MM-dd')); + + // Count occurrences of each date + const dateCounts: { [key: string]: number } = this.countOccurrences(formattedDates); + + // Convert date counts to the required format + const countsExtended = Object.keys(dateCounts).map(date => [date, dateCounts[date]]); + + const option = { title: {}, tooltip: { position: 'top', @@ -237,7 +280,7 @@ export class HomeComponent implements OnInit { left: 30, right: 30, cellSize: ['auto', 13], - range: year, + range: currentYear, itemStyle: { borderWidth: 0.5 }, @@ -249,18 +292,14 @@ export class HomeComponent implements OnInit { data: countsExtended, label: { show: true, - formatter: function (p) { - if (date_today.getDate() == p.data[0]) { - return 'X'; - } - else { - return ''; - } + formatter: function (params) { + return currentDate.getDate() === params.data[0] ? 'X' : ''; } }, } }; - option && myChart.setOption(option); - } -} + this.crackedChart.setOption(option); + this.lastUpdated = formatDate(new Date(), this.util.getSetting('timefmt')) + } +} \ No newline at end of file diff --git a/src/app/home/home.module.ts b/src/app/home/home.module.ts index 59a18035..6fd743bb 100644 --- a/src/app/home/home.module.ts +++ b/src/app/home/home.module.ts @@ -13,7 +13,8 @@ import { HomeRoutingModule } from "./home-routing.module"; import { MatGridListModule } from '@angular/material/grid-list'; import { MatIconModule } from "@angular/material/icon"; import { MatCardModule } from "@angular/material/card"; -import { FlexLayoutModule } from '@angular/flex-layout'; +import { MatButtonModule } from "@angular/material/button"; +import { MatTooltipModule } from "@angular/material/tooltip"; @NgModule({ declarations: [ @@ -30,9 +31,10 @@ import { FlexLayoutModule } from '@angular/flex-layout'; PipesModule, FormsModule, MatGridListModule, - FlexLayoutModule, MatIconModule, MatCardModule, + MatButtonModule, + MatTooltipModule, NgbModule ] }) From 53543b5dc624370b651bf04e2284670b0d3e2ff0 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Mon, 6 Nov 2023 11:32:23 +0100 Subject: [PATCH 173/419] Remove unused dependency --- package-lock.json | 17 ----------------- package.json | 1 - 2 files changed, 18 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9c3e56d3..bc82f9cf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,6 @@ "@angular/common": "^16.1.4", "@angular/compiler": "^16.1.4", "@angular/core": "^16.1.4", - "@angular/flex-layout": "^15.0.0-beta.42", "@angular/forms": "^16.1.4", "@angular/localize": "^16.1.4", "@angular/material": "^16.2.9", @@ -605,22 +604,6 @@ "zone.js": "~0.13.0" } }, - "node_modules/@angular/flex-layout": { - "version": "15.0.0-beta.42", - "resolved": "https://registry.npmjs.org/@angular/flex-layout/-/flex-layout-15.0.0-beta.42.tgz", - "integrity": "sha512-cTAPVMMxnyIFwpZwdq0PL5mdP9Qh+R8MB7ZBezVaN3Rz2fRrkagzKpLvPX3TFzepXrvHBdpKsU4b8u+NxEC/6g==", - "deprecated": "This package has been deprecated. Please see https://blog.angular.io/modern-css-in-angular-layouts-4a259dca9127", - "dependencies": { - "tslib": "^2.3.0" - }, - "peerDependencies": { - "@angular/cdk": ">=15.0.0", - "@angular/common": ">=15.0.2", - "@angular/core": ">=15.0.2", - "@angular/platform-browser": ">=15.0.2", - "rxjs": "^6.5.3 || ^7.4.0" - } - }, "node_modules/@angular/forms": { "version": "16.2.1", "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-16.2.1.tgz", diff --git a/package.json b/package.json index 80ff87b7..b45054a9 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,6 @@ "@angular/common": "^16.1.4", "@angular/compiler": "^16.1.4", "@angular/core": "^16.1.4", - "@angular/flex-layout": "^15.0.0-beta.42", "@angular/forms": "^16.1.4", "@angular/localize": "^16.1.4", "@angular/material": "^16.2.9", From 8b2c2760ba1890c4c9b154f47f50b905c3f233d7 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Mon, 6 Nov 2023 16:45:45 +0100 Subject: [PATCH 174/419] Add swal css again --- angular.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/angular.json b/angular.json index a707613d..35546506 100644 --- a/angular.json +++ b/angular.json @@ -29,7 +29,8 @@ "src/assets" ], "styles": [ - "src/styles/styles.scss" + "src/styles/styles.scss", + "sweetalert2/src/sweetalert2.scss" ], "stylePreprocessorOptions": { "includePaths": [ From 19b93e25dba21c8eea9c6064d7a8ad28243d2a59 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Tue, 7 Nov 2023 15:34:44 +0100 Subject: [PATCH 175/419] put checktoken service back --- .../_services/access/checktoken.service.ts | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 src/app/core/_services/access/checktoken.service.ts diff --git a/src/app/core/_services/access/checktoken.service.ts b/src/app/core/_services/access/checktoken.service.ts new file mode 100644 index 00000000..cc140cd1 --- /dev/null +++ b/src/app/core/_services/access/checktoken.service.ts @@ -0,0 +1,45 @@ +import { AuthService } from "./auth.service"; +import { Injectable, } from "@angular/core"; + +export interface AuthResponseData { + token: string, + expires: string, +} + +@Injectable({ providedIn: 'root' }) +export class CheckTokenService { + constructor( + private authService: AuthService, + ) { + // We Listen using visibility api to look change events, tab inactive and active + document.addEventListener('visibilitychange', () => { + if (document.visibilityState === 'visible') { + // When tab is now active; we check token validity and refresh if we have time + this.checkTokenValidity(); + } + }); + } + + checkTokenValidity() { + const userData: { _token: string, _expires: string } = JSON.parse(localStorage.getItem('userData')); + if (!userData) { + return; + } + let tokendate = new Date(userData._expires).getTime(); + let currentDate = new Date().getTime(); + let timeDifference = tokendate - currentDate; + // We should be refreshing but when using refresh token, we get an error "Signature verification failure" + // if(timeDifference > 0 && timeDifference < 600){ + // console.log('trying to refresh token') + // this.authService.refreshToken().subscribe( + // (data) => { + // console.log(data) + // } + // ); + // } + if (timeDifference < 15) { + this.authService.logOut(); + } + } + +} \ No newline at end of file From 8d54050aca07d9a26374e0434627806e08c79f28 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Tue, 7 Nov 2023 15:37:07 +0100 Subject: [PATCH 176/419] Revert some changes ragarding auth --- src/app/app.component.ts | 8 +- src/app/core/_guards/auth.guard.ts | 2 +- .../_interceptors/auth-interceptor.service.ts | 6 +- .../_interceptors/http-res.interceptor.ts | 138 ++++----- src/app/core/_models/auth-user.model.ts | 6 +- src/app/core/_services/access/auth.service.ts | 264 +++++++++--------- src/app/core/_services/access/idle.service.ts | 74 +++++ src/app/layout/header/header.component.ts | 6 +- src/app/shared/components.module.ts | 14 +- .../timeout/timeout-dialog.component.html | 9 + .../timeout/timeout-dialog.component.ts | 17 ++ .../dialog/timeout/timeout-dialog.model.ts | 6 + 12 files changed, 330 insertions(+), 220 deletions(-) create mode 100644 src/app/core/_services/access/idle.service.ts create mode 100644 src/app/shared/dialog/timeout/timeout-dialog.component.html create mode 100644 src/app/shared/dialog/timeout/timeout-dialog.component.ts create mode 100644 src/app/shared/dialog/timeout/timeout-dialog.model.ts diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 5a43ea42..c727089e 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -13,6 +13,7 @@ import { UISettingsUtilityClass } from './shared/utils/config'; import { LocalStorageService } from './core/_services/storage/local-storage.service'; import { UIConfig } from './core/_models/config-ui.model'; import { BreakpointService } from './core/_services/shared/breakpoint.service'; +import { CheckTokenService } from './core/_services/access/checktoken.service'; @Component({ selector: 'app-root', @@ -40,6 +41,7 @@ export class AppComponent implements OnInit, AfterViewInit { private authService: AuthService, private modalService: NgbModal, private keepalive: Keepalive, + private checkt: CheckTokenService, private router: Router, private idle: Idle, private storage: LocalStorageService, @@ -106,13 +108,13 @@ export class AppComponent implements OnInit, AfterViewInit { ngOnInit(): void { this.authService.autoLogin(); - this.authService.authenticationStatus$.subscribe(status => { + this.authService.isLogged.subscribe(status => { this.isLogged = status; if (status) { this.storageInit(); } }); - this.authService.checkAuthenticationStatus(); + this.authService.checkStatus(); this.uiSettings = new UISettingsUtilityClass(this.storage) } @@ -182,7 +184,7 @@ export class AppComponent implements OnInit, AfterViewInit { } onLogOut() { - this.authService.clearAuthenticationData(); + this.authService.logOut(); this.closeModal(); } diff --git a/src/app/core/_guards/auth.guard.ts b/src/app/core/_guards/auth.guard.ts index e7014d98..20e06487 100644 --- a/src/app/core/_guards/auth.guard.ts +++ b/src/app/core/_guards/auth.guard.ts @@ -16,7 +16,7 @@ class AuthGuard { ) { } canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { - return this.authService.user$.pipe( + return this.authService.user.pipe( take(1), map(user => { const isAuth = !!user; diff --git a/src/app/core/_interceptors/auth-interceptor.service.ts b/src/app/core/_interceptors/auth-interceptor.service.ts index 71bee33c..52021a8a 100644 --- a/src/app/core/_interceptors/auth-interceptor.service.ts +++ b/src/app/core/_interceptors/auth-interceptor.service.ts @@ -3,15 +3,15 @@ import { Injectable } from '@angular/core'; import { take, exhaustMap } from 'rxjs'; import { AuthService } from '../_services/access/auth.service'; -import { UserData } from '../_models/auth-user.model'; +import { User, UserData } from '../_models/auth-user.model'; @Injectable() export class AuthInterceptorService implements HttpInterceptor { constructor(private authService: AuthService) { } intercept(req: HttpRequest, next: HttpHandler) { - return this.authService.user$.pipe( + return this.authService.user.pipe( take(1), - exhaustMap((user: UserData) => { + exhaustMap((user: User) => { if (!user) { return next.handle(req); } diff --git a/src/app/core/_interceptors/http-res.interceptor.ts b/src/app/core/_interceptors/http-res.interceptor.ts index 9cacef43..2fad3e63 100644 --- a/src/app/core/_interceptors/http-res.interceptor.ts +++ b/src/app/core/_interceptors/http-res.interceptor.ts @@ -9,7 +9,7 @@ import { LoadingService } from '../_services/shared/loading.service'; import { AuthService } from '../_services/access/auth.service'; @Injectable() -export class HttpResInterceptor implements HttpInterceptor{ +export class HttpResInterceptor implements HttpInterceptor { private isRefreshing = false; private refreshTokenSubject: BehaviorSubject = new BehaviorSubject(null); @@ -19,78 +19,78 @@ export class HttpResInterceptor implements HttpInterceptor{ private modalService: NgbModal, public ls: LoadingService, private router: Router - ) {} + ) { } - modalRef = null; - intercept(req: HttpRequest, next: HttpHandler): - Observable> { - this.ls.handleRequest('plus'); - return next.handle(req) - .pipe( - retry(1), - finalize(this.finalize.bind(this)), - catchError((error: HttpErrorResponse) => { - let errmsg= ''; - if (error.error instanceof ErrorEvent) { - const err = error?.error.message || 'Unknown API error'; - errmsg = `Client Side Error: ${err}`; - } - else { - if(error.status === 0){ - alert(`Unable to Connect to the Server: `+error.message); - } - if(error.status === 401){ - if(!req.url.includes('/auth')){ - const token = this.authService.token; - const userData: { _expires: string} = JSON.parse(localStorage.getItem('userData')); - if(token !== 'notoken' && (new Date(userData._expires) < new Date(Date.now()-60000))){ - this.isRefresh(req); - }else{ - errmsg = `${error.error.title}`; - } - }else{ - errmsg = `${error.error.title}`; - } - } - if(error.status === 403){ - errmsg = `You don't have permissions. Please contact your Administrator.`; - } - if(error.status === 404 && !req.url.includes('config.json')){ - errmsg = `The requested URL was not found.`; - } - // if(error.status !== 404 && error.status !== 403 && error.status !== 401 && error.status >= 300){ - // this.router.navigate(['error']); - // } - else{ - errmsg = error.error.exception[0].message; + modalRef = null; + intercept(req: HttpRequest, next: HttpHandler): + Observable> { + this.ls.handleRequest('plus'); + return next.handle(req) + .pipe( + retry(1), + finalize(this.finalize.bind(this)), + catchError((error: HttpErrorResponse) => { + let errmsg = ''; + if (error.error instanceof ErrorEvent) { + const err = error?.error.message || 'Unknown API error'; + errmsg = `Client Side Error: ${err}`; + } + else { + if (error.status === 0) { + alert(`Unable to Connect to the Server: ` + error.message); + } + if (error.status === 401) { + if (!req.url.includes('/auth')) { + const token = this.authService.token; + const userData: { _expires: string } = JSON.parse(localStorage.getItem('userData')); + if (token !== 'notoken' && (new Date(userData._expires) < new Date(Date.now() - 60000))) { + this.isRefresh(req); + } else { + errmsg = `${error.error.title}`; } + } else { + errmsg = `${error.error.title}`; } - this.modalRef = this.modalService.open(ErrorModalComponent); - this.modalRef.componentInstance.status = error?.status; - this.modalRef.componentInstance.message = errmsg; - return throwError(() => errmsg); - }) - ) - } + } + if (error.status === 403) { + errmsg = `You don't have permissions. Please contact your Administrator.`; + } + if (error.status === 404 && !req.url.includes('config.json')) { + errmsg = `The requested URL was not found.`; + } + // if(error.status !== 404 && error.status !== 403 && error.status !== 401 && error.status >= 300){ + // this.router.navigate(['error']); + // } + else { + errmsg = error.error.exception[0].message; + } + } + this.modalRef = this.modalService.open(ErrorModalComponent); + this.modalRef.componentInstance.status = error?.status; + this.modalRef.componentInstance.message = errmsg; + return throwError(() => errmsg); + }) + ) + } - finalize = (): void => this.ls.handleRequest(); + finalize = (): void => this.ls.handleRequest(); - isNetworkError(errorObject) { - return errorObject.message === "net::ERR_INTERNET_DISCONNECTED" || - errorObject.message === "net::ERR_PROXY_CONNECTION_FAILED" || - errorObject.message === "net::ERR_PROXY_CONNECTION_FAILED" || - errorObject.message === "net::ERR_CONNECTION_TIMED_OUT" || - errorObject.message === "net::ERR_CONNECTION_RESET" || - errorObject.message === "net::ERR_CONNECTION_CLOSE" || - errorObject.message === "net::ERR_UNKNOWN_PROTOCOL" || - errorObject.message === "net::ERR_SLOW_CONNECTION" || - errorObject.message === "net::ERR_FAILED" || - errorObject.message === "net::ERR_NAME_NOT_RESOLVED" ; - } + isNetworkError(errorObject) { + return errorObject.message === "net::ERR_INTERNET_DISCONNECTED" || + errorObject.message === "net::ERR_PROXY_CONNECTION_FAILED" || + errorObject.message === "net::ERR_PROXY_CONNECTION_FAILED" || + errorObject.message === "net::ERR_CONNECTION_TIMED_OUT" || + errorObject.message === "net::ERR_CONNECTION_RESET" || + errorObject.message === "net::ERR_CONNECTION_CLOSE" || + errorObject.message === "net::ERR_UNKNOWN_PROTOCOL" || + errorObject.message === "net::ERR_SLOW_CONNECTION" || + errorObject.message === "net::ERR_FAILED" || + errorObject.message === "net::ERR_NAME_NOT_RESOLVED"; + } - isRefresh(request: HttpRequest){ - this.authService.refreshToken(); - // this.router.navigate([request]); - window.location.reload(); - } + isRefresh(request: HttpRequest) { + this.authService.refreshToken(); + // this.router.navigate([request]); + window.location.reload(); + } } diff --git a/src/app/core/_models/auth-user.model.ts b/src/app/core/_models/auth-user.model.ts index e31695e9..8f05e526 100644 --- a/src/app/core/_models/auth-user.model.ts +++ b/src/app/core/_models/auth-user.model.ts @@ -6,9 +6,9 @@ export interface UserData { export class User { constructor( - private _token: string, - private _expires: Date, - private _username: string + public _token: string, + public _expires: Date, + public _username: string ) { } get token() { diff --git a/src/app/core/_services/access/auth.service.ts b/src/app/core/_services/access/auth.service.ts index 32d42916..aea1773b 100644 --- a/src/app/core/_services/access/auth.service.ts +++ b/src/app/core/_services/access/auth.service.ts @@ -1,204 +1,194 @@ -import { Injectable } from "@angular/core"; -import { HttpClient, HttpHeaders, HttpErrorResponse } from "@angular/common/http"; -import { BehaviorSubject, Observable, ReplaySubject, Subject, throwError } from 'rxjs'; +import { HttpClient, HttpErrorResponse, HttpHeaders } from "@angular/common/http"; +import { BehaviorSubject, throwError, Observable, ReplaySubject, Subject } from 'rxjs'; +import { environment } from '../../../../environments/environment'; +import { Injectable, Output, EventEmitter } from "@angular/core"; +import { User, UserData } from '../../_models/auth-user.model'; import { catchError, tap } from 'rxjs/operators'; +import { Router } from "@angular/router"; import { Buffer } from 'buffer'; - import { ConfigService } from "../shared/config.service"; -import { UserData } from '../../_models/auth-user.model'; -import { LocalStorageService } from "../storage/local-storage.service"; export interface AuthResponseData { - token: string; - expires: number; + token: string, + expires: string, } @Injectable({ providedIn: 'root' }) export class AuthService { - static readonly STORAGE_KEY = 'userData' - - private tokenExpirationTimout: any; - private endpoint = '/auth'; - - userId!: number - redirectUrl = '' + user = new BehaviorSubject(null); + userId!: any; + @Output() authChanged: EventEmitter = new EventEmitter(); isAuthenticated = false; - - authenticationStatus$ = new ReplaySubject(1); - userLoggedIn$ = new Subject(); - user$ = new BehaviorSubject(null); + private logged = new ReplaySubject(1); + isLogged = this.logged.asObservable(); + redirectUrl = ''; + private userLoggedIn = new Subject(); + private accessToken: string; + private tokenExpiration: any; + private endpoint = '/auth'; constructor( private http: HttpClient, - private configService: ConfigService, - private storage: LocalStorageService, + private router: Router, + private cs: ConfigService, ) { - this.initUser(); - } - - private initUser() { - console.log('initUser') - this.userLoggedIn$.next(false); - if (this.authenticationStatus$) { - this.checkAuthenticationStatus(); + this.accessToken = localStorage.getItem('userData'); + this.userLoggedIn.next(false); + if (this.logged) { + this.userId = this.getUserId(this.token); } } - public autoLogin(): void { - if (!this.userData) { + autoLogin() { + const userData: { _token: string, _expires: string } = JSON.parse(localStorage.getItem('userData')); + if (!userData) { return; } - const now = new Date().getTime() - const exp = new Date(this.userData._expires).getTime() + const loadedUser = new User(userData._token, new Date(userData._expires), '-'); - if (this.userData._expires && now < exp) { - this.user$.next(this.userData); - const expirationDuration = new Date(this.userData._expires).getTime() - new Date().getTime(); - this.getRefreshToken(expirationDuration); + if (loadedUser.token) { + this.user.next(loadedUser); + const tokenExpiration = new Date(userData._expires).getTime() - new Date().getTime(); + // this.autologOut(tokenExpiration); + this.getRefreshToken(tokenExpiration); } } - public logIn(username: string, password: string): Observable { - return this.http.post( - `${this.configService.getEndpoint()}${this.endpoint}/token`, - { username, password }, - { - headers: new HttpHeaders({ - 'Authorization': `Basic ${window.btoa(username + ':' + password)}` - }) - } - ).pipe( - catchError(this.handleError), - tap(response => { - this.setAuthenticationData(response.token, +response.expires, username); - this.isAuthenticated = true; - }) - ); - } - - public get token(): string { - return this.userData ? this.userData._token : 'notoken'; + logIn(username: string, password: string): Observable { + return this.http.post(this.cs.getEndpoint() + this.endpoint + '/token', { username: username, password: password }, + { headers: new HttpHeaders({ 'Authorization': 'Basic ' + window.btoa(username + ':' + password) }) }) + .pipe( + catchError(this.handleError), + tap(resData => { + this.handleAuthentication(resData.token, +resData.expires, username); + this.isAuthenticated = true; + this.userAuthChanged(true); + })); + } + + get token(): any { + let res; + const token = localStorage.getItem('userData'); + if (token) { + res = JSON?.parse(token)._token + } else { + res = 'notoken' + } + return res; } - private getUserId(token: string): number | undefined { + private getUserId(token: any) { if (token == 'notoken') { - return undefined; + return false } else { const b64string = Buffer.from(token.split('.')[1], 'base64'); - return parseInt(JSON.parse(b64string.toString()).userId); + return JSON.parse(b64string.toString()).userId } } - public setUserLoggedIn(userLoggedIn: boolean): void { - this.userLoggedIn$.next(userLoggedIn); + setUserLoggedIn(userLoggedIn: boolean) { + this.userLoggedIn.next(userLoggedIn); } - public getUserLoggedIn(): Observable { - return this.userLoggedIn$.asObservable(); + getUserLoggedIn(): Observable { + return this.userLoggedIn.asObservable(); } - public autologOut(timeoutMs: number): void { - this.tokenExpirationTimout = setTimeout(() => { - this.clearAuthenticationData(); - }, timeoutMs); + // With autologOut we use only the expiration date of the bearer token, approx. 2 hours + autologOut(expirationDuration: number) { + this.tokenExpiration = setTimeout(() => { + this.logOut(); + }, expirationDuration); } - public getRefreshToken(timeoutMs: number): void { - this.tokenExpirationTimout = setTimeout(() => { - if (this.userData) { - this.requestRefreshToken().subscribe((response: AuthResponseData) => { - if (response && response.token) { - const expirationMs = new Date(response.expires * 1000); - this.storage.setItem('userData', { - _token: response.token, - _expires: expirationMs, - _username: this.userData._username - }, 0) - } else { - this.clearAuthenticationData(); - } - }) - } - }, timeoutMs); - } - - private requestRefreshToken(): Observable { - const httpHeaders = { headers: new HttpHeaders({ Authorization: `Bearer ${this.userData ? this.userData._token : ''}` }) } - const apiUrl = `${this.configService.getEndpoint()}${this.endpoint}/refresh` - - return this.http.post(apiUrl, {}, httpHeaders) + getRefreshToken(expirationDuration: number) { + this.tokenExpiration = setTimeout(() => { + const userData: { _token: string, _expires: string, _username: string } = JSON.parse(localStorage.getItem('userData')); + return this.http.post(this.cs.getEndpoint() + this.endpoint + '/refresh', { headers: new HttpHeaders({ Authorization: `Bearer ${userData._token}` }) }) + .pipe( + tap((response: any) => { + if (response && response.token) { + this.accessToken = response.token; + const expirationDate = new Date(response.expires * 1000); + const user = new User(response.token, expirationDate, userData._username); + localStorage.setItem('userData', JSON.stringify(user)); + } else { + this.logOut(); + } + }) + ); + }, expirationDuration); } - public refreshToken(): Observable { - return this.requestRefreshToken() + refreshToken(): Observable { + const userData: { _token: string, _expires: string, _username: string } = JSON.parse(localStorage.getItem('userData')); + return this.http.post(this.cs.getEndpoint() + this.endpoint + '/refresh ', { headers: new HttpHeaders({ Authorization: `Bearer ${userData._token}` }) }) .pipe( - tap((response: AuthResponseData) => { + tap((response: any) => { if (response && response.token) { + this.accessToken = response.token; const expirationDate = new Date(response.expires * 1000); - this.storage.setItem('userData', { - _token: response.token, - _expires: expirationDate, - _username: this.userData._username - }, 0) + const user = new User(response.token, expirationDate, userData._username); + localStorage.setItem('userData', JSON.stringify(user)); } else { - this.clearAuthenticationData(); + this.logOut(); } }), catchError((error) => { // Handle the error here console.error('An error occurred:', error); - return throwError(() => error); // Rethrow the error for further handling if needed + return throwError(error); // Rethrow the error for further handling if needed }) ); } - public clearAuthenticationData(): void { - this.user$.next(null); - this.isAuthenticated = false; - this.storage.removeItem('userData'); - if (this.tokenExpirationTimout) { - clearTimeout(this.tokenExpirationTimout); + logOut() { + this.user.next(null); + this.router.navigate(['/auth']); + localStorage.removeItem('userData'); + if (this.tokenExpiration) { + clearTimeout(this.tokenExpiration) } - this.tokenExpirationTimout = null; + this.tokenExpiration = null; } - get userData(): UserData { - return this.storage.getItem(AuthService.STORAGE_KEY); - } - - checkAuthenticationStatus(): void { - console.log() - if (this.userData) { - this.authenticationStatus$.next(true); - this.userId = this.getUserId(this.userData._token) + checkStatus() { + const userData = JSON.parse(localStorage.getItem('userData')); + if (userData) { + this.logged.next(true); } else { - this.authenticationStatus$.next(false); + this.logged.next(false); } } - private setAuthenticationData(token: string, expires: number, username: string): void { - const userData = { - _token: token, - _expires: new Date(expires * 1000), - _username: username - }; - this.user$.next(userData); - this.isAuthenticated = true; - this.storage.setItem('userData', userData, 0) + private userAuthChanged(status: boolean) { + this.authChanged.emit(status); // Raise changed event + } + + handleAuthentication(token: string, expires: number, username: string) { + console.log('handling auth') + const expirationDate = new Date(expires * 1000); // expires, its epoch time in seconds and returns milliseconds sin Jan 1, 1970. We need to multiple by 1000 + const user = new User(token, expirationDate, username); + this.user.next(user); + this.logged.next(true); + this.autologOut(expires); // Epoch time + localStorage.setItem('userData', JSON.stringify(user)); this.userId = this.getUserId(token); } - private handleError(errorRes: HttpErrorResponse): Observable { - let errorMessage = 'An unknown error occurred!'; - if (errorRes.error && errorRes.error.error) { - switch (errorRes.error.error.message) { - case 'INVALID_PASSWORD': - errorMessage = 'Wrong username/password/OTP!'; - break; - } + private handleError(errorRes: HttpErrorResponse) { + let errorMessage = 'An unknown error ocurred!'; + if (!errorRes.error || !errorRes.error.error) { + return throwError(() => errorMessage); + } + switch (errorRes.error.error.message) { + case 'INVALID_PASSWORD': //We can add easily more common errors but for security better dont give more hints + errorMessage = 'Wrong username/password/OTP!'; + break; } return throwError(() => errorMessage); } -} + +} \ No newline at end of file diff --git a/src/app/core/_services/access/idle.service.ts b/src/app/core/_services/access/idle.service.ts new file mode 100644 index 00000000..920e8a3b --- /dev/null +++ b/src/app/core/_services/access/idle.service.ts @@ -0,0 +1,74 @@ +import { Injectable } from '@angular/core'; +import { Idle, DEFAULT_INTERRUPTSOURCES } from '@ng-idle/core'; +import { Keepalive } from '@ng-idle/keepalive'; +import { BehaviorSubject, Subscription } from 'rxjs'; + + +export enum IdleState { + INIT, + IDLE_START, + IDLE_END, + TIMEOUT, + TIMEOUT_WARNING, + PING, +} + +export interface IdleData { + state: IdleState + countdown?: number +} + +@Injectable({ + providedIn: 'root', +}) +export class IdleService { + private idleStateSource = new BehaviorSubject({ state: IdleState.INIT }); + private subscriptions: Subscription[] = [] + + idleState$ = this.idleStateSource.asObservable(); + + constructor(private idle: Idle, private keepalive: Keepalive) { } + + startIdleTimer(idleTime: number, timeoutMax: number): void { + console.log('startIdleTimer', idleTime, timeoutMax) + this.idle.setIdle(idleTime); + this.idle.setTimeout(timeoutMax); + this.idle.setInterrupts(DEFAULT_INTERRUPTSOURCES); + + this.subscriptions.push(this.idle.onIdleStart.subscribe(() => { + //this.idle.clearInterrupts(); + this.idleStateSource.next({ state: IdleState.IDLE_START }); + })); + + this.subscriptions.push(this.idle.onIdleEnd.subscribe(() => { + this.idleStateSource.next({ state: IdleState.IDLE_END }); + })); + + this.subscriptions.push(this.idle.onTimeout.subscribe(() => { + this.idleStateSource.next({ state: IdleState.TIMEOUT }); + })); + + this.subscriptions.push(this.idle.onTimeoutWarning.subscribe((countdown: number) => { + this.idleStateSource.next({ state: IdleState.TIMEOUT_WARNING, countdown: countdown }); + })); + + this.keepalive.interval(15); + this.subscriptions.push(this.keepalive.onPing.subscribe(() => { + this.idle.watch(); + this.idleStateSource.next({ state: IdleState.PING }); + })); + + // Start watching for idleness + this.idle.watch(); + } + + stopIdleTimer(): void { + for (const sub of this.subscriptions) { + sub.unsubscribe() + } + this.subscriptions = [] + + this.idle.stop(); + this.keepalive.stop(); + } +} \ No newline at end of file diff --git a/src/app/layout/header/header.component.ts b/src/app/layout/header/header.component.ts index 72b4d939..aa41ba41 100644 --- a/src/app/layout/header/header.component.ts +++ b/src/app/layout/header/header.component.ts @@ -3,7 +3,7 @@ import { environment } from './../../../environments/environment'; import { Subscription } from 'rxjs'; import { AuthService } from '../../core/_services/access/auth.service'; import { MainMenuItem } from './header.model'; -import { UserData } from 'src/app/core/_models/auth-user.model'; +import { User, UserData } from 'src/app/core/_models/auth-user.model'; import { ActionMenuEvent } from 'src/app/core/_components/menus/action-menu/action-menu.model'; import { HeaderMenuAction, HeaderMenuLabel } from './header.constants'; @@ -24,7 +24,7 @@ export class HeaderComponent implements OnInit, OnDestroy { } ngOnInit(): void { - this.subscriptions.push(this.authService.user$.subscribe((user: UserData) => { + this.subscriptions.push(this.authService.user.subscribe((user: User) => { if (user) { this.username = user._username } @@ -40,7 +40,7 @@ export class HeaderComponent implements OnInit, OnDestroy { menuItemClicked(event: ActionMenuEvent): void { if (event.menuItem.action === HeaderMenuAction.LOGOUT) { - this.authService.clearAuthenticationData(); + this.authService.logOut(); this.rebuildMenu() window.location.reload(); } diff --git a/src/app/shared/components.module.ts b/src/app/shared/components.module.ts index 7d04162e..370768ff 100644 --- a/src/app/shared/components.module.ts +++ b/src/app/shared/components.module.ts @@ -23,10 +23,16 @@ import { LottiesModule } from './lottie/lottie.module'; import { GraphsModule } from "./graphs/graphs.module"; import { ColorPickerModule } from 'ngx-color-picker'; import { FormsModule } from "@angular/forms"; +import { MatIconModule } from '@angular/material/icon'; +import { MatDialogModule } from '@angular/material/dialog'; +import { MatButtonModule } from '@angular/material/button'; +import { TimeoutDialogComponent } from './dialog/timeout/timeout-dialog.component'; +import { MatProgressBarModule } from '@angular/material/progress-bar'; @NgModule({ declarations: [ ButtonTruncateTextComponent, + TimeoutDialogComponent, HashtypeDetectorComponent, LoadingSpinnerComponent, ActiveSpinnerComponent, @@ -47,6 +53,11 @@ import { FormsModule } from "@angular/forms"; LottiesModule, GraphsModule, CommonModule, + MatIconModule, + MatButtonModule, + MatDialogModule, + MatProgressBarModule, + MatIconModule, FormsModule, TableModule, GridModule, @@ -54,6 +65,7 @@ import { FormsModule } from "@angular/forms"; ], exports: [ ButtonTruncateTextComponent, + TimeoutDialogComponent, HashtypeDetectorComponent, LoadingSpinnerComponent, ActiveSpinnerComponent, @@ -76,4 +88,4 @@ import { FormsModule } from "@angular/forms"; GridModule ], }) -export class ComponentsModule {} +export class ComponentsModule { } diff --git a/src/app/shared/dialog/timeout/timeout-dialog.component.html b/src/app/shared/dialog/timeout/timeout-dialog.component.html new file mode 100644 index 00000000..1b557316 --- /dev/null +++ b/src/app/shared/dialog/timeout/timeout-dialog.component.html @@ -0,0 +1,9 @@ +

+ warning + Timeout +

+
+

{{ data.message }}

+ +

Logging out...

+
\ No newline at end of file diff --git a/src/app/shared/dialog/timeout/timeout-dialog.component.ts b/src/app/shared/dialog/timeout/timeout-dialog.component.ts new file mode 100644 index 00000000..216b6856 --- /dev/null +++ b/src/app/shared/dialog/timeout/timeout-dialog.component.ts @@ -0,0 +1,17 @@ +/* eslint-disable @angular-eslint/component-selector */ +import { Component, Inject } from "@angular/core"; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { DialogData } from "./timeout-dialog.model"; + + +@Component({ + selector: 'timeout-dialog', + templateUrl: 'timeout-dialog.component.html', +}) +export class TimeoutDialogComponent { + + constructor( + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: DialogData + ) { } +} \ No newline at end of file diff --git a/src/app/shared/dialog/timeout/timeout-dialog.model.ts b/src/app/shared/dialog/timeout/timeout-dialog.model.ts new file mode 100644 index 00000000..cd9c0fc9 --- /dev/null +++ b/src/app/shared/dialog/timeout/timeout-dialog.model.ts @@ -0,0 +1,6 @@ +export interface DialogData { + message: string + value: number + bufferValue: number + timedOut: boolean +} \ No newline at end of file From 6b3d960b2e4175c6c82db89db2d60b0a55935ebf Mon Sep 17 00:00:00 2001 From: itbf9 Date: Tue, 7 Nov 2023 16:36:31 +0100 Subject: [PATCH 177/419] Use UserData interface in auth --- src/app/core/_services/access/auth.service.ts | 71 ++++++++++--------- src/app/layout/header/header.component.ts | 1 + 2 files changed, 40 insertions(+), 32 deletions(-) diff --git a/src/app/core/_services/access/auth.service.ts b/src/app/core/_services/access/auth.service.ts index aea1773b..c9c643e6 100644 --- a/src/app/core/_services/access/auth.service.ts +++ b/src/app/core/_services/access/auth.service.ts @@ -7,6 +7,7 @@ import { catchError, tap } from 'rxjs/operators'; import { Router } from "@angular/router"; import { Buffer } from 'buffer'; import { ConfigService } from "../shared/config.service"; +import { LocalStorageService } from "../storage/local-storage.service"; export interface AuthResponseData { token: string, @@ -16,15 +17,17 @@ export interface AuthResponseData { @Injectable({ providedIn: 'root' }) export class AuthService { - user = new BehaviorSubject(null); + static readonly STORAGE_KEY = 'userData' + + user = new BehaviorSubject(null); userId!: any; + @Output() authChanged: EventEmitter = new EventEmitter(); isAuthenticated = false; private logged = new ReplaySubject(1); isLogged = this.logged.asObservable(); redirectUrl = ''; private userLoggedIn = new Subject(); - private accessToken: string; private tokenExpiration: any; private endpoint = '/auth'; @@ -32,8 +35,9 @@ export class AuthService { private http: HttpClient, private router: Router, private cs: ConfigService, + private storage: LocalStorageService, ) { - this.accessToken = localStorage.getItem('userData'); + const userData: UserData = this.storage.getItem(AuthService.STORAGE_KEY); this.userLoggedIn.next(false); if (this.logged) { this.userId = this.getUserId(this.token); @@ -41,13 +45,12 @@ export class AuthService { } autoLogin() { - const userData: { _token: string, _expires: string } = JSON.parse(localStorage.getItem('userData')); + const userData: UserData = this.storage.getItem(AuthService.STORAGE_KEY); if (!userData) { return; } - const loadedUser = new User(userData._token, new Date(userData._expires), '-'); - + const loadedUser = new User(userData._token, new Date(userData._expires), userData._username); if (loadedUser.token) { this.user.next(loadedUser); const tokenExpiration = new Date(userData._expires).getTime() - new Date().getTime(); @@ -68,15 +71,9 @@ export class AuthService { })); } - get token(): any { - let res; - const token = localStorage.getItem('userData'); - if (token) { - res = JSON?.parse(token)._token - } else { - res = 'notoken' - } - return res; + get token(): string { + const userData: UserData = this.storage.getItem(AuthService.STORAGE_KEY); + return userData ? userData._token : 'notoken' } private getUserId(token: any) { @@ -105,15 +102,15 @@ export class AuthService { getRefreshToken(expirationDuration: number) { this.tokenExpiration = setTimeout(() => { - const userData: { _token: string, _expires: string, _username: string } = JSON.parse(localStorage.getItem('userData')); + const userData: UserData = this.storage.getItem(AuthService.STORAGE_KEY); return this.http.post(this.cs.getEndpoint() + this.endpoint + '/refresh', { headers: new HttpHeaders({ Authorization: `Bearer ${userData._token}` }) }) .pipe( tap((response: any) => { if (response && response.token) { - this.accessToken = response.token; - const expirationDate = new Date(response.expires * 1000); - const user = new User(response.token, expirationDate, userData._username); - localStorage.setItem('userData', JSON.stringify(user)); + userData._token = response.token + userData._expires = new Date(response.expires * 1000); + + this.storage.setItem(AuthService.STORAGE_KEY, userData, 0); } else { this.logOut(); } @@ -123,15 +120,15 @@ export class AuthService { } refreshToken(): Observable { - const userData: { _token: string, _expires: string, _username: string } = JSON.parse(localStorage.getItem('userData')); + const userData: UserData = this.storage.getItem(AuthService.STORAGE_KEY); return this.http.post(this.cs.getEndpoint() + this.endpoint + '/refresh ', { headers: new HttpHeaders({ Authorization: `Bearer ${userData._token}` }) }) .pipe( tap((response: any) => { if (response && response.token) { - this.accessToken = response.token; - const expirationDate = new Date(response.expires * 1000); - const user = new User(response.token, expirationDate, userData._username); - localStorage.setItem('userData', JSON.stringify(user)); + userData._token = response.token + userData._expires = new Date(response.expires * 1000); + + this.storage.setItem(AuthService.STORAGE_KEY, userData, 0); } else { this.logOut(); } @@ -147,7 +144,7 @@ export class AuthService { logOut() { this.user.next(null); this.router.navigate(['/auth']); - localStorage.removeItem('userData'); + this.storage.removeItem(AuthService.STORAGE_KEY); if (this.tokenExpiration) { clearTimeout(this.tokenExpiration) } @@ -155,7 +152,7 @@ export class AuthService { } checkStatus() { - const userData = JSON.parse(localStorage.getItem('userData')); + const userData: UserData = this.storage.getItem(AuthService.STORAGE_KEY); if (userData) { this.logged.next(true); } else { @@ -168,13 +165,23 @@ export class AuthService { } handleAuthentication(token: string, expires: number, username: string) { - console.log('handling auth') - const expirationDate = new Date(expires * 1000); // expires, its epoch time in seconds and returns milliseconds sin Jan 1, 1970. We need to multiple by 1000 - const user = new User(token, expirationDate, username); - this.user.next(user); + const userData = { + _token: token, + _expires: new Date(expires * 1000), + _username: username + }; + + this.user.next(userData); this.logged.next(true); + this.isAuthenticated = true; + + this.storage.setItem(AuthService.STORAGE_KEY, userData, 0) + this.userId = this.getUserId(token); + + this.autologOut(expires); // Epoch time - localStorage.setItem('userData', JSON.stringify(user)); + + this.storage.setItem(AuthService.STORAGE_KEY, userData, 0); this.userId = this.getUserId(token); } diff --git a/src/app/layout/header/header.component.ts b/src/app/layout/header/header.component.ts index aa41ba41..e41a158c 100644 --- a/src/app/layout/header/header.component.ts +++ b/src/app/layout/header/header.component.ts @@ -25,6 +25,7 @@ export class HeaderComponent implements OnInit, OnDestroy { ngOnInit(): void { this.subscriptions.push(this.authService.user.subscribe((user: User) => { + console.log(user) if (user) { this.username = user._username } From 9ab060ef3338fb9f948aa11bbb7efab715612571 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Tue, 7 Nov 2023 19:39:41 +0100 Subject: [PATCH 178/419] Set menuitem as selected if menu url partially match current url --- .../menus/action-menu/action-menu.component.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/app/core/_components/menus/action-menu/action-menu.component.ts b/src/app/core/_components/menus/action-menu/action-menu.component.ts index b724a29b..927dae0b 100644 --- a/src/app/core/_components/menus/action-menu/action-menu.component.ts +++ b/src/app/core/_components/menus/action-menu/action-menu.component.ts @@ -71,7 +71,13 @@ export class ActionMenuComponent implements OnInit, OnDestroy { } /** - * Checks if a menu item should be marked as active based on the current URL. + * Checks if a menu item should be considered active based on the current URL. + * It sets the 'isActive' property to true if the menu item's 'routerLink' matches + * or partially matches the beginning of the current URL. + * + * The 'actionMenuItems' array should contain sections of menu items, where each + * section is an array of menu items with 'routerLink' properties. + * */ checkIsActive(): void { this.isActive = false @@ -79,9 +85,11 @@ export class ActionMenuComponent implements OnInit, OnDestroy { if (this.isActive) { break; } + for (const item of section) { - if (item.routerLink && item.routerLink.length === this.currentUrl.length && - item.routerLink.every((value, index) => value === this.currentUrl[index])) { + const copy = this.currentUrl.slice(0, item.routerLink.length); + if (item.routerLink && + item.routerLink.every((value, index) => value === copy[index])) { this.isActive = true break; } From e557e45f27e293f742f706a9efa5d376b9fea666 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Tue, 7 Nov 2023 19:41:14 +0100 Subject: [PATCH 179/419] rename variable and format code --- .../_components/menus/action-menu/action-menu.component.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/app/core/_components/menus/action-menu/action-menu.component.ts b/src/app/core/_components/menus/action-menu/action-menu.component.ts index 927dae0b..8655a07d 100644 --- a/src/app/core/_components/menus/action-menu/action-menu.component.ts +++ b/src/app/core/_components/menus/action-menu/action-menu.component.ts @@ -87,9 +87,8 @@ export class ActionMenuComponent implements OnInit, OnDestroy { } for (const item of section) { - const copy = this.currentUrl.slice(0, item.routerLink.length); - if (item.routerLink && - item.routerLink.every((value, index) => value === copy[index])) { + const partial = this.currentUrl.slice(0, item.routerLink.length); + if (item.routerLink && item.routerLink.every((value, index) => value === partial[index])) { this.isActive = true break; } From 503c59b6061a40549223856447dc1436700b06f1 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Wed, 8 Nov 2023 08:27:08 +0100 Subject: [PATCH 180/419] Add simple in memory cache service --- .../core/_services/shared/cache.service.ts | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 src/app/core/_services/shared/cache.service.ts diff --git a/src/app/core/_services/shared/cache.service.ts b/src/app/core/_services/shared/cache.service.ts new file mode 100644 index 00000000..0b403868 --- /dev/null +++ b/src/app/core/_services/shared/cache.service.ts @@ -0,0 +1,44 @@ +import { Injectable } from '@angular/core'; + +/** + * Service for caching values in memory. + */ +@Injectable({ + providedIn: 'root' +}) +export class CacheService { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + private static cache: Map = new Map(); + + /** + * Retrieve a cached value by key. + * + * @param key - The unique key to retrieve the cached value. + * @returns The cached value associated with the provided key, or undefined if not found. + */ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + static get(key: string): any { + return this.cache.get(key); + } + + /** + * Set a cached value for a specified key. + * + * @param key - The unique key to associate with the cached value. + * @param value - The value to cache. + */ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + static set(key: string, value: any): void { + this.cache.set(key, value); + } + + /** + * Check if a cached value exists for a given key. + * + * @param key - The unique key to check for cached value existence. + * @returns True if a cached value exists for the provided key, otherwise false. + */ + static has(key: string): boolean { + return this.cache.has(key); + } +} \ No newline at end of file From 8ee4c4d48a07beb4a45ea0e64640c22a19bba027 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Wed, 8 Nov 2023 08:28:39 +0100 Subject: [PATCH 181/419] Add cacheable decorator for caching function output --- src/app/core/_decorators/cacheable.ts | 47 +++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 src/app/core/_decorators/cacheable.ts diff --git a/src/app/core/_decorators/cacheable.ts b/src/app/core/_decorators/cacheable.ts new file mode 100644 index 00000000..d7e5eb40 --- /dev/null +++ b/src/app/core/_decorators/cacheable.ts @@ -0,0 +1,47 @@ +import { CacheService } from "../_services/shared/cache.service"; + +/** + * A decorator for caching the results of a method based on its arguments. + * + * @param property - An optional array of attribute names to construct the cache key from. + * If provided, the cache key will be constructed based on the values of these attributes. + * If not provided, the cache key will be based on the stringified arguments. + * @returns A decorator function. +* + * @example + * ```typescript + * class YourClass { + * @Cacheable(['arg1', 'arg2']) + * yourMethod(arg1: string, arg2: number): any { + * // Your method logic here + * } + * } + * ``` + */ +export function Cacheable(property: string[] | undefined = undefined): MethodDecorator { + return function (target: object, propertyKey: string | symbol, descriptor: PropertyDescriptor) { + const originalMethod = descriptor.value; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + descriptor.value = function (...args: any[]): any { + let cacheKey: string + if (property) { + const attributeValues = property.map((key) => args[0][key] || '').join('_'); + cacheKey = `${propertyKey.toString()}_${attributeValues}`; + } else { + cacheKey = `${propertyKey.toString()}_${JSON.stringify(args)}`; + } + const cachedResult = CacheService.get(cacheKey); + + if (cachedResult !== undefined) { + return cachedResult; + } + + const result = originalMethod.apply(this, args); + CacheService.set(cacheKey, result); + return result; + }; + + return descriptor; + }; +} \ No newline at end of file From 7b676736a406920a12efce170c6c49331cfbcf10 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Wed, 8 Nov 2023 08:29:27 +0100 Subject: [PATCH 182/419] Add datasource for agent table --- .../core/_datasources/agents.datasource.ts | 98 +++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 src/app/core/_datasources/agents.datasource.ts diff --git a/src/app/core/_datasources/agents.datasource.ts b/src/app/core/_datasources/agents.datasource.ts new file mode 100644 index 00000000..b12595a7 --- /dev/null +++ b/src/app/core/_datasources/agents.datasource.ts @@ -0,0 +1,98 @@ +import { catchError, finalize, firstValueFrom, forkJoin, of } from "rxjs"; +import { SERV } from "../_services/main.config"; +import { BaseDataSource } from "./base.datasource"; +import { User } from "../_models/user.model"; +import { environment } from "src/environments/environment"; +import { ListResponseWrapper } from "../_models/response.model"; +import { Agent } from "../_models/agent.model"; +import { Chunk, ChunkData } from "../_models/chunk.model"; +import { Task } from "../_models/task.model"; + + +export class AgentsDataSource extends BaseDataSource { + + loadAll(): void { + this.loadingSubject.next(true); + + const agentParams = { maxResults: 999999, expand: 'accessGroups' } + const params = { maxResults: 999999 } + const agents$ = this.service.getAll(SERV.AGENTS, agentParams) + const users$ = this.service.getAll(SERV.USERS, params) + const agentAssign$ = this.service.getAll(SERV.AGENT_ASSIGN, params) + const tasks$ = this.service.getAll(SERV.TASKS, params) + const chunks$ = this.service.getAll(SERV.CHUNKS, params) + + forkJoin([agents$, users$, agentAssign$, tasks$, chunks$]).pipe( + catchError(() => of([])), + finalize(() => this.loadingSubject.next(false)) + ).subscribe(([a, u, aa, t, c]: [ + ListResponseWrapper, + ListResponseWrapper, + ListResponseWrapper, + ListResponseWrapper, + ListResponseWrapper + ]) => { + + const agents: Agent[] = a.values + const users: User[] = u.values + const assignments: any[] = aa.values + const tasks: Task[] = t.values + const chunks: Chunk[] = c.values + + agents.map((agent: Agent) => { + agent.user = users.find((e: User) => e._id === agent.userId) + agent.taskId = assignments.find(e => e.agentId === agent._id)?.taskId + if (agent.taskId) { + agent.task = tasks.find(e => e._id === agent.taskId) + agent.chunk = chunks.find(e => e.agentId === agent.agentId) + } + + return agent; + }) + + this.setPaginationConfig(this.pageSize, this.currentPage, a.total); + this.setData(agents) + }) + } + + async getChunkData(id: number): Promise { + const maxResults = environment.config.prodApiMaxResults; + const chunktime = this.uiService.getUIsettings('chunktime').value; + + const dispatched: number[] = []; + const searched: number[] = []; + const cracked: number[] = []; + const speed: number[] = []; + const now = Date.now() + + const params = { + maxResults: maxResults, + filter: `agentId=${id}` + }; + + const response: ListResponseWrapper = await firstValueFrom(this.service.getAll(SERV.CHUNKS, params)) + + for (const chunk of response.values) { + if (chunk.progress >= 10000) { + dispatched.push(chunk.length); + } + cracked.push(chunk.cracked) + searched.push(chunk.checkpoint - chunk.skip); + if (now / 1000 - Math.max(chunk.solveTime, chunk.dispatchTime) < chunktime && chunk.progress < 10000) { + speed.push(chunk.speed); + } + } + + return { + dispatched: 0, + searched: 0, + cracked: cracked.reduce((a, i) => a + i, 0), + speed: speed.reduce((a, i) => a + i, 0) + } + } + + reload(): void { + this.reset() + this.loadAll() + } +} \ No newline at end of file From a10cd3b157f396dc8d5618064264b3e3eb7d8b93 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Wed, 8 Nov 2023 08:33:12 +0100 Subject: [PATCH 183/419] Move tables into tables dir + new agents-table component --- .../agents-table/agents-table.component.html | 4 + .../agents-table/agents-table.component.ts | 398 ++++++++++++++++++ .../agents-table/agents-table.constants.ts | 13 + .../base-table/base-table.component.ts | 11 +- .../column-selection-dialog.component.html | 0 .../column-selection-dialog.component.ts | 0 .../ht-table/ht-table.component.html | 0 .../ht-table/ht-table.component.ts | 8 +- .../{ => tables}/ht-table/ht-table.models.ts | 0 .../table-dialog/table-dialog.component.html | 0 .../table-dialog/table-dialog.component.ts | 0 .../table-dialog/table-dialog.model.ts | 0 12 files changed, 425 insertions(+), 9 deletions(-) create mode 100644 src/app/core/_components/tables/agents-table/agents-table.component.html create mode 100644 src/app/core/_components/tables/agents-table/agents-table.component.ts create mode 100644 src/app/core/_components/tables/agents-table/agents-table.constants.ts rename src/app/core/_components/{ => tables}/base-table/base-table.component.ts (78%) rename src/app/core/_components/{ => tables}/column-selection-dialog/column-selection-dialog.component.html (100%) rename src/app/core/_components/{ => tables}/column-selection-dialog/column-selection-dialog.component.ts (100%) rename src/app/core/_components/{ => tables}/ht-table/ht-table.component.html (100%) rename src/app/core/_components/{ => tables}/ht-table/ht-table.component.ts (96%) rename src/app/core/_components/{ => tables}/ht-table/ht-table.models.ts (100%) rename src/app/core/_components/{ => tables}/table-dialog/table-dialog.component.html (100%) rename src/app/core/_components/{ => tables}/table-dialog/table-dialog.component.ts (100%) rename src/app/core/_components/{ => tables}/table-dialog/table-dialog.model.ts (100%) diff --git a/src/app/core/_components/tables/agents-table/agents-table.component.html b/src/app/core/_components/tables/agents-table/agents-table.component.html new file mode 100644 index 00000000..d7201cb4 --- /dev/null +++ b/src/app/core/_components/tables/agents-table/agents-table.component.html @@ -0,0 +1,4 @@ + \ No newline at end of file diff --git a/src/app/core/_components/tables/agents-table/agents-table.component.ts b/src/app/core/_components/tables/agents-table/agents-table.component.ts new file mode 100644 index 00000000..e2d4a651 --- /dev/null +++ b/src/app/core/_components/tables/agents-table/agents-table.component.ts @@ -0,0 +1,398 @@ +/* eslint-disable @angular-eslint/component-selector */ +import { Component, OnDestroy, OnInit } from "@angular/core"; +import { formatUnixTimestamp } from "src/app/shared/utils/datetime"; +import { SafeHtml } from "@angular/platform-browser"; +import { catchError, forkJoin } from "rxjs"; +import { HTTableColumn } from "../ht-table/ht-table.models"; +import { BaseTableComponent } from "../base-table/base-table.component"; +import { ChunkData } from "src/app/core/_models/chunk.model"; +import { AgentsTableColumnLabel } from "./agents-table.constants"; +import { Agent } from "src/app/core/_models/agent.model"; +import { DialogData } from "../table-dialog/table-dialog.model"; +import { TableDialogComponent } from "../table-dialog/table-dialog.component"; +import { RowActionMenuAction } from "../../menus/row-action-menu/row-action-menu.constants"; +import { BulkActionMenuAction } from "../../menus/bulk-action-menu/bulk-action-menu.constants"; +import { ActionMenuEvent } from "../../menus/action-menu/action-menu.model"; +import { ExportMenuAction } from "../../menus/export-menu/export-menu.constants"; +import { SERV } from "src/app/core/_services/main.config"; +import { AgentsDataSource } from "src/app/core/_datasources/agents.datasource"; +import { Cacheable } from "src/app/core/_decorators/cacheable"; + + +@Component({ + selector: 'agents-table', + templateUrl: './agents-table.component.html' +}) + +export class AgentsTableComponent extends BaseTableComponent implements OnInit, OnDestroy { + + tableColumns: HTTableColumn[] = [] + dataSource: AgentsDataSource; + chunkData: { [key: number]: ChunkData } = {} + + ngOnDestroy(): void { + for (const sub of this.subscriptions) { + sub.unsubscribe() + } + } + + ngOnInit(): void { + this.tableColumns = this.getColumns() + this.dataSource = new AgentsDataSource(this.gs, this.uiService); + this.dataSource.setColumns(this.tableColumns); + this.dataSource.loadAll(); + } + + filter(item: Agent, filterValue: string): boolean { + if (item.agentName.toLowerCase().includes(filterValue) || + item.clientSignature.toLowerCase().includes(filterValue) || + item.devices.toLowerCase().includes(filterValue)) { + return true + } + + return false; + } + + getColumns(): HTTableColumn[] { + const tableColumns = [ + { + name: AgentsTableColumnLabel.ID, + dataKey: '_id', + isSortable: true, + export: async (agent: Agent) => agent._id + '' + }, + { + name: AgentsTableColumnLabel.STATUS, + dataKey: 'status', + render: (agent: Agent) => this.renderStatus(agent), + isSortable: true, + export: async (agent: Agent) => agent.isActive ? 'Active' : 'Inactive' + }, + { + name: AgentsTableColumnLabel.NAME, + dataKey: 'agentName', + routerLink: (agent: Agent) => ['/agents', 'show-agents', agent._id, 'edit'], + isSortable: true, + export: async (agent: Agent) => agent.agentName + }, + { + name: AgentsTableColumnLabel.USER, + dataKey: 'userId', + render: (agent: Agent) => this.renderOwner(agent), + routerLink: (agent: Agent) => agent.userId ? ['/users', agent.userId, 'edit'] : [], + isSortable: true, + export: async (agent: Agent) => agent.user ? agent.user.name : '' + }, + { + name: AgentsTableColumnLabel.CLIENT, + dataKey: 'clientSignature', + render: (agent: Agent) => this.renderClient(agent), + isSortable: true, + export: async (agent: Agent) => agent.clientSignature ? agent.clientSignature : '' + }, + { + name: AgentsTableColumnLabel.CURRENT_TASK, + dataKey: 'taskId', + render: (agent: Agent) => this.renderCurrentTask(agent), + isSortable: true, + export: async (agent: Agent) => agent.task ? agent.task.taskName : '' + }, + { + name: AgentsTableColumnLabel.TASK_SPEED, + dataKey: 'taskId', + async: (agent: Agent) => this.renderCurrentSpeed(agent), + isSortable: false, + export: async (agent: Agent) => await this.getSpeed(agent) + '' + }, + { + name: AgentsTableColumnLabel.CURRENT_CHUNK, + dataKey: 'chunkId', + render: (agent: Agent) => this.renderCurrentChunk(agent), + isSortable: true, + export: async (agent: Agent) => agent.chunk ? agent.chunk._id + '' : '' + }, + { + name: AgentsTableColumnLabel.GPUS_CPUS, + dataKey: 'devices', + isSortable: true, + export: async (agent: Agent) => agent.devices + }, + { + name: AgentsTableColumnLabel.LAST_ACTIVITY, + dataKey: 'lastTime', + render: (agent: Agent) => this.renderLastActivity(agent), + isSortable: true, + export: async (agent: Agent) => formatUnixTimestamp(agent.lastTime, this.dateFormat) + }, + { + name: AgentsTableColumnLabel.ACCESS_GROUP, + dataKey: 'accessGroupId', + render: (agent: Agent) => this.renderAccessGroup(agent), + isSortable: true, + export: async (agent: Agent) => agent.accessGroups.map((item) => item.groupName).join(', ') + } + ] + + return tableColumns + } + + + openDialog(data: DialogData) { + const dialogRef = this.dialog.open(TableDialogComponent, { + data: data, + width: '450px', + }); + + this.subscriptions.push(dialogRef.afterClosed().subscribe(result => { + if (result && result.action) { + switch (result.action) { + case RowActionMenuAction.DELETE: + this.rowActionDelete(result.data); + break; + case BulkActionMenuAction.ACTIVATE: + this.bulkActionActivate(result.data, true); + break; + case BulkActionMenuAction.DEACTIVATE: + this.bulkActionActivate(result.data, false); + break; + case BulkActionMenuAction.DELETE: + this.bulkActionDelete(result.data); + break; + } + } + })); + } + + // --- Render functions --- + + + @Cacheable(['_id', 'agentName', 'isTrusted']) + renderName(agent: Agent): SafeHtml { + const agentName = agent.agentName?.length > 40 + ? `${agent.agentName.substring(40)}...` + : agent.agentName + const isTrusted = agent.isTrusted + ? '' + : '' + + return this.sanitize(`${agentName}${isTrusted}`) + } + + + @Cacheable(['_id', 'taskId']) + renderCurrentTask(agent: Agent): SafeHtml { + let html = '-' + if (agent.task) { + const taskName = agent.task.taskName?.length > 40 + ? `${agent.task.taskName.substring(40)}...` + : agent.task.taskName + + html = `${taskName}` + } + return this.sanitize(html) + } + + @Cacheable(['_id', 'taskId']) + async renderCurrentSpeed(agent: Agent): Promise { + let html = '-' + const speed = await this.getSpeed(agent) + if (speed) { + html = `${speed} H/s` + } + return this.sanitize(html) + } + + private async getSpeed(agent: Agent): Promise { + if (!(agent._id in this.chunkData)) { + this.chunkData[agent._id] = await this.dataSource.getChunkData(agent._id); + } + if (this.chunkData[agent._id].speed) { + return this.chunkData[agent._id].speed + } + + return 0 + } + + @Cacheable(['_id', 'chunk']) + renderCurrentChunk(agent: Agent): SafeHtml { + let html = '-' + if (agent.chunk) { + html = `${agent.chunk._id}` + } + + return this.sanitize(html) + } + + @Cacheable(['_id', 'isActive']) + renderStatus(agent: Agent): SafeHtml { + let html: string + if (agent.isActive) { + html = 'Active' + } else { + html = 'Inactive' + } + + return this.sanitize(html) + } + + @Cacheable(['_id', 'accessGroupId']) + renderAccessGroup(agent: Agent): SafeHtml { + const links: string[] = [] + + for (const group of agent.accessGroups) { + links.push(`${group.groupName}`) + if (agent.accessGroups.length > 1) { + links.push('
') + } + } + + return this.sanitize(links.join('\n')) + + } + + @Cacheable(['_id', 'userId']) + renderOwner(agent: Agent): SafeHtml { + if (agent.user) { + return this.sanitize(agent.user.name) + } + return '' + } + + @Cacheable(['_id', 'clientSignature']) + renderClient(agent: Agent): SafeHtml { + if (agent.clientSignature) { + return agent.clientSignature + } + return '' + } + + + @Cacheable(['_id', 'lastTime']) + renderLastActivity(agent: Agent): SafeHtml { + const formattedDate = formatUnixTimestamp(agent.lastTime, this.dateFormat) + const data = `${agent.lastAct} at
${formattedDate}
IP:${agent.lastIp}` + + + return this.sanitize(data) + } + + + // --- Action functions --- + + exportActionClicked(event: ActionMenuEvent): void { + switch (event.menuItem.action) { + case ExportMenuAction.EXCEL: + this.exportService.toExcel('hashtopolis-agents', this.tableColumns, event.data) + break; + case ExportMenuAction.CSV: + this.exportService.toCsv('hashtopolis-agents', this.tableColumns, event.data) + break; + case ExportMenuAction.COPY: + this.exportService.toClipboard(this.tableColumns, event.data).then(() => { + this.snackBar.open('The selected rows are copied to the clipboard', 'Close') + }) + break; + } + } + + rowActionClicked(event: ActionMenuEvent): void { + switch (event.menuItem.action) { + case RowActionMenuAction.EDIT: + this.rowActionEdit(event.data); + break; + case RowActionMenuAction.DELETE: + this.openDialog({ + rows: [event.data], + title: `Deleting ${event.data.agentName} ...`, + icon: 'warning', + body: `Are you sure you want to delete ${event.data.agentName}? Note that this action cannot be undone.`, + warn: true, + action: event.menuItem.action + }) + break; + } + } + + bulkActionClicked(event: ActionMenuEvent): void { + switch (event.menuItem.action) { + case BulkActionMenuAction.ACTIVATE: + this.openDialog({ + rows: event.data, + title: `Activating ${event.data.length} agents ...`, + icon: 'info', + listAttribute: 'agentName', + action: event.menuItem.action + }) + break; + case BulkActionMenuAction.DEACTIVATE: + this.openDialog({ + rows: event.data, + title: `Deactivating ${event.data.length} agents ...`, + icon: 'info', + listAttribute: 'agentName', + action: event.menuItem.action + }) + break; + case BulkActionMenuAction.DELETE: + this.openDialog({ + rows: event.data, + title: `Deleting ${event.data.length} agents ...`, + icon: 'warning', + body: `Are you sure you want to delete the above agents? Note that this action cannot be undone.`, + warn: true, + listAttribute: 'agentName', + action: event.menuItem.action + }) + break; + } + } + + private bulkActionActivate(agents: Agent[], isActive: boolean): void { + const requests = agents.map((agent: Agent) => { + return this.gs.update(SERV.AGENTS, agent._id, { isActive: isActive }) + }); + + const action = isActive ? 'activated' : 'deactivated' + + this.subscriptions.push(forkJoin(requests) + .pipe( + catchError((error) => { + console.error('Error during activation:', error); + return []; + }) + ) + .subscribe((results) => { + this.snackBar.open(`Successfully ${action} ${results.length} agents!`, 'Close'); + this.dataSource.reload() + })); + } + + private bulkActionDelete(agents: Agent[]): void { + const requests = agents.map((agent: Agent) => { + return this.gs.delete(SERV.AGENTS, agent._id) + }); + + this.subscriptions.push(forkJoin(requests) + .pipe( + catchError((error) => { + console.error('Error during deletion:', error); + return []; + }) + ) + .subscribe((results) => { + this.snackBar.open(`Successfully deleted ${results.length} agents!`, 'Close'); + this.dataSource.reload() + })); + } + + private rowActionDelete(agent: Agent): void { + this.subscriptions.push(this.gs.delete(SERV.AGENTS, agent._id).subscribe(() => { + this.snackBar.open('Successfully deleted agent!', 'Close'); + this.dataSource.reload() + })); + } + + private rowActionEdit(agent: Agent): void { + this.router.navigate(['agents', 'show-agents', agent._id, 'edit']); + } +} \ No newline at end of file diff --git a/src/app/core/_components/tables/agents-table/agents-table.constants.ts b/src/app/core/_components/tables/agents-table/agents-table.constants.ts new file mode 100644 index 00000000..5c178927 --- /dev/null +++ b/src/app/core/_components/tables/agents-table/agents-table.constants.ts @@ -0,0 +1,13 @@ +export const AgentsTableColumnLabel = { + ID: 'ID', + STATUS: 'Status', + NAME: 'Name', + USER: 'Owner', + CLIENT: 'Client', + GPUS_CPUS: 'GPUs/CPUs', + LAST_ACTIVITY: 'Last Activity', + ACCESS_GROUP: 'Access Group', + CURRENT_TASK: 'Task', + CURRENT_CHUNK: 'Chunk', + TASK_SPEED: 'Speed', +} \ No newline at end of file diff --git a/src/app/core/_components/base-table/base-table.component.ts b/src/app/core/_components/tables/base-table/base-table.component.ts similarity index 78% rename from src/app/core/_components/base-table/base-table.component.ts rename to src/app/core/_components/tables/base-table/base-table.component.ts index b43344f0..fafc461b 100644 --- a/src/app/core/_components/base-table/base-table.component.ts +++ b/src/app/core/_components/tables/base-table/base-table.component.ts @@ -1,15 +1,16 @@ /* eslint-disable @angular-eslint/component-selector */ import { Component, Renderer2 } from '@angular/core'; -import { GlobalService } from '../../_services/main.service'; import { Router } from '@angular/router'; import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; import { MatSnackBar } from '@angular/material/snack-bar'; import { MatDialog } from '@angular/material/dialog'; import { UISettingsUtilityClass } from 'src/app/shared/utils/config'; -import { LocalStorageService } from '../../_services/storage/local-storage.service'; -import { UIConfig, uiConfigDefault } from '../../_models/config-ui.model'; -import { UIConfigService } from '../../_services/shared/storage.service'; import { Subscription } from 'rxjs'; +import { GlobalService } from 'src/app/core/_services/main.service'; +import { LocalStorageService } from 'src/app/core/_services/storage/local-storage.service'; +import { UIConfig, uiConfigDefault } from 'src/app/core/_models/config-ui.model'; +import { UIConfigService } from 'src/app/core/_services/shared/storage.service'; +import { ExportService } from 'src/app/core/_services/export/export.service'; @Component({ selector: 'base-table', @@ -29,6 +30,7 @@ export class BaseTableComponent { protected sanitizer: DomSanitizer, protected snackBar: MatSnackBar, protected uiService: UIConfigService, + protected exportService: ExportService, public dialog: MatDialog, ) { this.uiSettings = new UISettingsUtilityClass(settingsService) @@ -37,7 +39,6 @@ export class BaseTableComponent { /** * Retrieves the date format for rendering timestamps. - * @todo Change to localstorage * @returns The date format string. */ private getDateFormat(): string { diff --git a/src/app/core/_components/column-selection-dialog/column-selection-dialog.component.html b/src/app/core/_components/tables/column-selection-dialog/column-selection-dialog.component.html similarity index 100% rename from src/app/core/_components/column-selection-dialog/column-selection-dialog.component.html rename to src/app/core/_components/tables/column-selection-dialog/column-selection-dialog.component.html diff --git a/src/app/core/_components/column-selection-dialog/column-selection-dialog.component.ts b/src/app/core/_components/tables/column-selection-dialog/column-selection-dialog.component.ts similarity index 100% rename from src/app/core/_components/column-selection-dialog/column-selection-dialog.component.ts rename to src/app/core/_components/tables/column-selection-dialog/column-selection-dialog.component.ts diff --git a/src/app/core/_components/ht-table/ht-table.component.html b/src/app/core/_components/tables/ht-table/ht-table.component.html similarity index 100% rename from src/app/core/_components/ht-table/ht-table.component.html rename to src/app/core/_components/tables/ht-table/ht-table.component.html diff --git a/src/app/core/_components/ht-table/ht-table.component.ts b/src/app/core/_components/tables/ht-table/ht-table.component.ts similarity index 96% rename from src/app/core/_components/ht-table/ht-table.component.ts rename to src/app/core/_components/tables/ht-table/ht-table.component.ts index d253c219..bd08daaf 100644 --- a/src/app/core/_components/ht-table/ht-table.component.ts +++ b/src/app/core/_components/tables/ht-table/ht-table.component.ts @@ -4,13 +4,13 @@ import { ChangeDetectionStrategy, AfterViewInit, Component, EventEmitter, Input, import { MatSort } from '@angular/material/sort'; import { MatPaginator, PageEvent } from '@angular/material/paginator'; import { DataType, HTTableColumn } from './ht-table.models'; -import { BaseDataSource } from '../../_datasources/base.datasource'; import { MatDialog } from '@angular/material/dialog'; import { ColumnSelectionDialogComponent } from '../column-selection-dialog/column-selection-dialog.component'; -import { LocalStorageService } from '../../_services/storage/local-storage.service'; -import { UIConfig } from '../../_models/config-ui.model'; import { UISettingsUtilityClass } from 'src/app/shared/utils/config'; -import { ActionMenuEvent } from '../menus/action-menu/action-menu.model'; +import { ActionMenuEvent } from '../../menus/action-menu/action-menu.model'; +import { LocalStorageService } from 'src/app/core/_services/storage/local-storage.service'; +import { UIConfig } from 'src/app/core/_models/config-ui.model'; +import { BaseDataSource } from 'src/app/core/_datasources/base.datasource'; /** * The `HTTableComponent` is a custom table component that allows you to display tabular data with diff --git a/src/app/core/_components/ht-table/ht-table.models.ts b/src/app/core/_components/tables/ht-table/ht-table.models.ts similarity index 100% rename from src/app/core/_components/ht-table/ht-table.models.ts rename to src/app/core/_components/tables/ht-table/ht-table.models.ts diff --git a/src/app/core/_components/table-dialog/table-dialog.component.html b/src/app/core/_components/tables/table-dialog/table-dialog.component.html similarity index 100% rename from src/app/core/_components/table-dialog/table-dialog.component.html rename to src/app/core/_components/tables/table-dialog/table-dialog.component.html diff --git a/src/app/core/_components/table-dialog/table-dialog.component.ts b/src/app/core/_components/tables/table-dialog/table-dialog.component.ts similarity index 100% rename from src/app/core/_components/table-dialog/table-dialog.component.ts rename to src/app/core/_components/tables/table-dialog/table-dialog.component.ts diff --git a/src/app/core/_components/table-dialog/table-dialog.model.ts b/src/app/core/_components/tables/table-dialog/table-dialog.model.ts similarity index 100% rename from src/app/core/_components/table-dialog/table-dialog.model.ts rename to src/app/core/_components/tables/table-dialog/table-dialog.model.ts From 11e09b23f58f089ea9209c93858622b6b334824f Mon Sep 17 00:00:00 2001 From: itbf9 Date: Wed, 8 Nov 2023 08:33:54 +0100 Subject: [PATCH 184/419] Add core module --- src/app/agents/agent.module.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/app/agents/agent.module.ts b/src/app/agents/agent.module.ts index 1403d50f..62724ab7 100644 --- a/src/app/agents/agent.module.ts +++ b/src/app/agents/agent.module.ts @@ -14,15 +14,17 @@ import { DirectivesModule } from "../shared/directives.module"; import { ComponentsModule } from "../shared/components.module"; import { AgentsRoutingModule } from "./agents-routing.module"; import { PipesModule } from "../shared/pipes.module"; +import { CoreComponentsModule } from '../core/_components/core-components.module'; @NgModule({ - declarations:[ + declarations: [ AgentStatusComponent, ShowAgentsComponent, EditAgentComponent, NewAgentComponent ], - imports:[ + imports: [ + CoreComponentsModule, ReactiveFormsModule, AgentsRoutingModule, FontAwesomeModule, @@ -36,4 +38,4 @@ import { PipesModule } from "../shared/pipes.module"; NgbModule ] }) -export class AgentsModule {} +export class AgentsModule { } From eb43c944625206f2616bf4430c5ee30fee18aaa8 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Wed, 8 Nov 2023 08:34:35 +0100 Subject: [PATCH 185/419] Reload page to apply new settings --- .../settings/ui-settings/ui-settings.component.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/app/account/settings/ui-settings/ui-settings.component.ts b/src/app/account/settings/ui-settings/ui-settings.component.ts index 5b7ecf99..63070d5f 100644 --- a/src/app/account/settings/ui-settings/ui-settings.component.ts +++ b/src/app/account/settings/ui-settings/ui-settings.component.ts @@ -48,11 +48,16 @@ export class UiSettingsComponent implements OnInit { } onSubmit(): void { + setTimeout(() => { + window.location.reload() + }, 800) + const changedValues = this.util.updateSettings(this.form.value) const message = changedValues > 0 - ? `Successfully updated ${changedValues} settings!` + ? 'Reloading settings ...' : 'No changes were saved' - this.snackBar.open(message, 'Close'); + + this.snackBar.open(message, 'Close') } } \ No newline at end of file From 8ee3e746291041f2bbbb4fc57b8225b60c745ac4 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Wed, 8 Nov 2023 08:37:48 +0100 Subject: [PATCH 186/419] Update import path + skip is active check if routerLink is not set --- .../menus/action-menu/action-menu.component.ts | 10 ++++++---- .../bulk-action-menu/bulk-action-menu.component.ts | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/app/core/_components/menus/action-menu/action-menu.component.ts b/src/app/core/_components/menus/action-menu/action-menu.component.ts index 8655a07d..76c67016 100644 --- a/src/app/core/_components/menus/action-menu/action-menu.component.ts +++ b/src/app/core/_components/menus/action-menu/action-menu.component.ts @@ -87,10 +87,12 @@ export class ActionMenuComponent implements OnInit, OnDestroy { } for (const item of section) { - const partial = this.currentUrl.slice(0, item.routerLink.length); - if (item.routerLink && item.routerLink.every((value, index) => value === partial[index])) { - this.isActive = true - break; + if (item.routerLink) { + const partial = this.currentUrl.slice(0, item.routerLink.length); + if (item.routerLink && item.routerLink.every((value, index) => value === partial[index])) { + this.isActive = true + break; + } } } } diff --git a/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.component.ts b/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.component.ts index 84f0c9c2..a36ae825 100644 --- a/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.component.ts +++ b/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.component.ts @@ -2,7 +2,7 @@ import { Component, Input, OnInit, } from '@angular/core'; import { BaseMenuComponent } from '../base-menu/base-menu.component'; import { BulkActionMenuAction, BulkActionMenuLabel } from './bulk-action-menu.constants'; -import { DataType } from '../../ht-table/ht-table.models'; +import { DataType } from '../../tables/ht-table/ht-table.models'; @Component({ selector: 'bulk-action-menu', From 5de41c1b063ffe5992b7d0c41369c72de467548c Mon Sep 17 00:00:00 2001 From: itbf9 Date: Wed, 8 Nov 2023 08:38:21 +0100 Subject: [PATCH 187/419] Update import path --- src/app/core/_datasources/base.datasource.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/core/_datasources/base.datasource.ts b/src/app/core/_datasources/base.datasource.ts index f659c341..73d6e642 100644 --- a/src/app/core/_datasources/base.datasource.ts +++ b/src/app/core/_datasources/base.datasource.ts @@ -2,10 +2,10 @@ import { CollectionViewer, DataSource } from "@angular/cdk/collections"; import { BehaviorSubject, Observable } from "rxjs"; import { GlobalService } from "../_services/main.service"; import { MatTableDataSourcePaginator } from "@angular/material/table"; -import { HTTableColumn } from "../_components/ht-table/ht-table.models"; import { MatSort } from "@angular/material/sort"; import { SelectionModel } from '@angular/cdk/collections'; import { UIConfigService } from "../_services/shared/storage.service"; +import { HTTableColumn } from "../_components/tables/ht-table/ht-table.models"; /** * BaseDataSource is an abstract class for implementing data sources From f7c6ca544a63492dedd757984bf8c2bfab2257ea Mon Sep 17 00:00:00 2001 From: itbf9 Date: Wed, 8 Nov 2023 08:39:11 +0100 Subject: [PATCH 188/419] Add agent table default settings --- src/app/core/_models/config-ui.model.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/app/core/_models/config-ui.model.ts b/src/app/core/_models/config-ui.model.ts index bdc8d286..ba01a699 100644 --- a/src/app/core/_models/config-ui.model.ts +++ b/src/app/core/_models/config-ui.model.ts @@ -1,3 +1,5 @@ +import { AgentsTableColumnLabel } from "../_components/tables/agents-table/agents-table.constants" + export type Layout = 'full' | 'fixed' export type Theme = 'light' | 'dark' @@ -18,7 +20,18 @@ export const uiConfigDefault: UIConfig = { layout: 'fixed', theme: 'light', timefmt: 'dd/MM/yyyy h:mm:ss', - tableSettings: {}, + tableSettings: { + agentTable: [ + AgentsTableColumnLabel.ID, + AgentsTableColumnLabel.STATUS, + AgentsTableColumnLabel.NAME, + AgentsTableColumnLabel.USER, + AgentsTableColumnLabel.CLIENT, + AgentsTableColumnLabel.GPUS_CPUS, + AgentsTableColumnLabel.LAST_ACTIVITY, + AgentsTableColumnLabel.ACCESS_GROUP + ], + }, refreshPage: false, refreshInterval: 10 } From c51522e44df6b79f2b010c8cb0ade0447aa5f516 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Wed, 8 Nov 2023 08:39:35 +0100 Subject: [PATCH 189/419] Update style --- src/styles/base/_base.scss | 62 +--------- src/styles/components/_card.scss | 195 ++++++++++++++++++++---------- src/styles/components/_table.scss | 97 ++++++++++++--- 3 files changed, 215 insertions(+), 139 deletions(-) diff --git a/src/styles/base/_base.scss b/src/styles/base/_base.scss index d7bad090..b11dc699 100644 --- a/src/styles/base/_base.scss +++ b/src/styles/base/_base.scss @@ -104,63 +104,11 @@ h2 { } } -.mat-card-box { - border: 1px solid $primary-700 !important; -} - -.home-grid { - margin-top: 50px; - - .project-info { - text-align: center; - border-radius: 0; - border: 3px solid $primary-900 - } - - .mat-mdc-card { - position: relative; - } - .mat-mdc-card-content { - h3 { - color: $primary-900; - font-weight: 200; - margin-top: 0; - font-size: 1.5em; - - .mat-icon { - position: relative; - top: 4px; - } - } - - hr { - border-width: 1px; - border-style: solid; - } - - span { - display: inline-block; - width: 100%; - - &.value { - text-align: right; - font-size: 2em; - color: $accent-900; - } - - &.label { - color: $primary-900; - font-size: .8em; - - .mat-icon { - font-size: 16px; - height: 16px; - width: 16px; - position: relative; - top: 3px; - } - } - } +.dark-theme { + hr { + border-width: 1px; + border-style: solid; + border-color: var(--mat-table-row-item-outline-color, rgba(0, 0, 0, 0.12)); } } \ No newline at end of file diff --git a/src/styles/components/_card.scss b/src/styles/components/_card.scss index 5c2f0c9c..684c5725 100644 --- a/src/styles/components/_card.scss +++ b/src/styles/components/_card.scss @@ -1,3 +1,4 @@ +@import '../base/colors'; /* ================================== SECTION CARDS ================================== */ @@ -9,35 +10,36 @@ .overflowAuto { overflow-x: hidden; overflow-y: auto; -/* height: calc(100vh - 163px); */ + /* height: calc(100vh - 163px); */ } // Overflow for texarea Firefox @-moz-document url-prefix() { textarea { - height: 4em; - width: 100%; + height: 4em; + width: 100%; } } .card-container-ag { - width:95%; + width: 95%; } + .card-ag { - background-color:#fff; + background-color: #fff; border: 1px solid #d4d4d4; - height:135px; + height: 135px; margin-bottom: 10px; position: relative; } .card-header-ag { - background-color:#545557; - font-size:14pt; - color:white; - padding:5px; - width:100%; + background-color: #545557; + font-size: 14pt; + color: white; + padding: 5px; + width: 100%; } .card-body-left-ag { @@ -66,7 +68,7 @@ color: rgb(177, 172, 172); } -.gray-light-ico:hover{ +.gray-light-ico:hover { color: rgb(177, 172, 172); cursor: pointer; } @@ -84,7 +86,7 @@ hr.break:after { position: absolute; // transform: rotate(-45deg); // width: 10px; - } +} .err-msg { background: #eee; @@ -101,7 +103,7 @@ hr.break:after { 02- Dashboard Card */ -.card{ +.card { box-shadow: 0 6px 10px -4px rgba(0, 0, 0, 0.15); background-color: #FFFFFF; color: black; @@ -115,73 +117,76 @@ hr.break:after { -ms-transition: transform 300ms cubic-bezier(0.34, 2, 0.6, 1), box-shadow 200ms ease; transition: transform 300ms cubic-bezier(0.34, 2, 0.6, 1), box-shadow 200ms ease; - .card-body{ - padding: 15px 15px 10px 15px; + .card-body { + padding: 15px 15px 10px 15px; - &.table-full-width{ - padding-left: 0; - padding-right: 0; - } + &.table-full-width { + padding-left: 0; + padding-right: 0; } + } - .card-header{ - &:not([data-background-color]){ - background-color: transparent; - } - padding: 15px 15px 0; - border: 0; + .card-header { + &:not([data-background-color]) { + background-color: transparent; + } - .card-title{ - margin-top: 10px; - } + padding: 15px 15px 0; + border: 0; + + .card-title { + margin-top: 10px; } + } - .card-footer{ - background-color: transparent; - border: 0; - - .stats{ - i{ - margin-right: 5px; - position: relative; - top: 0px; - color: rgb(144, 130, 122); - } - } + .card-footer { + background-color: transparent; + border: 0; + .stats { + i { + margin-right: 5px; + position: relative; + top: 0px; + color: rgb(144, 130, 122); + } } + } + } -.card-dash{ - .card-body{ - padding: 15px 15px 0px; +.card-dash { + .card-body { + padding: 15px 15px 0px; - .numbers{ - text-align: right; - font-size: 2em; + .numbers { + text-align: right; + font-size: 2em; - p{ - margin-bottom: 0; - } - .card-category { - color: darkblue; - font-size: 16px; - line-height: 1.4em; - } + p { + margin-bottom: 0; } - } - .card-footer{ - padding: 0px 15px 15px; - .stats{ + .card-category { color: darkblue; + font-size: 16px; + line-height: 1.4em; } + } + } - hr{ - margin-top: 10px; - margin-bottom: 15px; - } + .card-footer { + padding: 0px 15px 15px; + + .stats { + color: darkblue; + } + + hr { + margin-top: 10px; + margin-bottom: 15px; + } } } @@ -196,7 +201,71 @@ hr.break:after { 02- Dashboard Card End */ +.mat-card-box { + border: 1px solid $primary-700 !important; +} + +.home-grid { + margin-top: 50px; + + .project-info { + text-align: center; + border-radius: 0; + border: 3px solid $primary-900 + } + + .mat-mdc-card { + position: relative; + } + + .mat-mdc-card-content { + h3 { + color: $primary-900; + font-weight: 200; + margin-top: 0; + font-size: 1.5em; + + .mat-icon { + position: relative; + top: 4px; + } + } + + hr { + border-width: 1px; + border-style: solid; + } + + span { + display: inline-block; + width: 100%; + &.value { + text-align: right; + font-size: 2em; + color: $accent-900; + } + &.label { + color: $primary-900; + font-size: .8em; + .mat-icon { + font-size: 16px; + height: 16px; + width: 16px; + position: relative; + top: 3px; + } + } + } + } +} +.dark-theme { + .card { + box-shadow: 0 6px 10px -4px rgba(0, 0, 0, 0.75); + background-color: var(--mat-toolbar-container-background-color); + color: #fff; + } +} \ No newline at end of file diff --git a/src/styles/components/_table.scss b/src/styles/components/_table.scss index d4cc0798..906a9962 100644 --- a/src/styles/components/_table.scss +++ b/src/styles/components/_table.scss @@ -1,3 +1,4 @@ +@import '../base/colors'; /* ================================== SECTION TABLES ================================== */ @@ -14,7 +15,7 @@ color: #1F2937; } -.th{ +.th { border: 0 !important; color: blueviolet !important; } @@ -37,6 +38,7 @@ // Overrule datatables color setting table.dataTable tbody tr.selected>* { + // box-shadow: inset 0 0 0 9999px rgb(89, 143, 105) !important; a { color: rgb(246, 246, 246) !important; @@ -55,6 +57,7 @@ table.dataTable { color: #999; // text-transform: uppercase; //Chane table header to uppercase } + td { padding: 10px 10px; // vertical-align: top; @@ -76,7 +79,7 @@ table.dataTable { } } -.btn-actions{ +.btn-actions { margin-top: -3px !important; margin-bottom: 0px !important; } @@ -85,7 +88,7 @@ table.dataTable { display: none !important; } -.table.dataTable.table-hover>tbody>tr:hover>*{ +.table.dataTable.table-hover>tbody>tr:hover>* { background-color: rgb(249, 249, 167); } @@ -96,15 +99,18 @@ table.dataTable { font-weight: normal; } -div.dt-button-collection button.dt-button, div.dt-button-collection div.dt-button, div.dt-button-collection a.dt-button { +div.dt-button-collection button.dt-button, +div.dt-button-collection div.dt-button, +div.dt-button-collection a.dt-button { position: relative; - display: block !important;; + display: block !important; + ; padding: 0.5em 0 !important; margin: 4px 0 2px 0 !important; - overflow-x:visible !important; + overflow-x: visible !important; } -div.dt-button-background:not(.disabled){ +div.dt-button-background:not(.disabled) { position: fixed; top: 0; left: 0; @@ -120,21 +126,21 @@ div.dt-button-collection { border-color: #4B5563 !important; padding: 4px 4px 2px !important; line-height: 1.6em !important; - overflow-x:visible !important; + overflow-x: visible !important; - filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr="#f0f0f0", EndColorStr="#dadada"); + filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0, StartColorStr="#f0f0f0", EndColorStr="#dadada"); } .btn-overflow { - overflow-x:visible !important; + overflow-x: visible !important; } -.btn-actions{ +.btn-actions { margin-top: -3px !important; margin-bottom: 0px !important; } -.table.dataTable.table-hover>tbody>tr:hover>*{ +.table.dataTable.table-hover>tbody>tr:hover>* { background-color: rgb(117, 117, 114); } @@ -150,13 +156,16 @@ div.dt-button-collection span { color: #ffffff; } -button.dt-button, div.dt-button, a.dt-button, input.dt-button { +button.dt-button, +div.dt-button, +a.dt-button, +input.dt-button { box-sizing: border-box; - border: 1px solid rgba(0,0,0,.3); + border: 1px solid rgba(0, 0, 0, .3); line-height: 1.6em; white-space: nowrap; overflow: hidden; - background: linear-gradient(to bottom,rgba(230,230,230,.1) 0%,rgba(0,0,0,.1) 100%); + background: linear-gradient(to bottom, rgba(230, 230, 230, .1) 0%, rgba(0, 0, 0, .1) 100%); user-select: none; text-decoration: none; outline: none; @@ -166,8 +175,10 @@ button.dt-button, div.dt-button, a.dt-button, input.dt-button { } // Remove Previous when is in first and next when in last -.paginate_button.disabled, .paginate_button.disabled:hover, .paginate_button.disabled:active { - display:none +.paginate_button.disabled, +.paginate_button.disabled:hover, +.paginate_button.disabled:active { + display: none } /* @@ -186,11 +197,11 @@ button.dt-button, div.dt-button, a.dt-button, input.dt-button { .pagination>.active>span:hover { background-color: #4B5563; border-color: #4B5563; - color:#e0e4ea; + color: #e0e4ea; } .pagination a { - cursor: pointer; + cursor: pointer; } .container-pag { @@ -202,3 +213,51 @@ button.dt-button, div.dt-button, a.dt-button, input.dt-button { */ +table.hashtopolis-table { + .mat-mdc-header-cell { + background-color: $primary-800; + color: #fff; + + .mat-sort-header-arrow { + color: #fff; + } + } + + .mat-mdc-row:hover { + background-color: $primary-100; + } + + .flex-container { + display: flex; + justify-content: flex-start; + + .mat-icon { + margin-top: -3px; + } + } + +} + +div.table-actions { + display: flex; + justify-content: space-between; + margin-bottom: .5rem; + + .right-aligned { + margin-left: auto; + } +} + +.dark-theme { + table.hashtopolis-table { + background-color: var(--mat-toolbar-container-background-color); + + .mat-mdc-row:hover { + background-color: $primary-900; + } + } + + .mat-mdc-paginator { + background-color: var(--mat-toolbar-container-background-color); + } +} \ No newline at end of file From 746e57e1841d9e544dafaadd5966d8b2dad52c48 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Wed, 8 Nov 2023 08:41:29 +0100 Subject: [PATCH 190/419] Update page geader to use material components --- .../page-headers/page-title.component.ts | 29 +++++++------------ .../shared/page-headers/page-title.module.ts | 6 +++- 2 files changed, 16 insertions(+), 19 deletions(-) diff --git a/src/app/shared/page-headers/page-title.component.ts b/src/app/shared/page-headers/page-title.component.ts index 78f0cc6b..4f042b6f 100644 --- a/src/app/shared/page-headers/page-title.component.ts +++ b/src/app/shared/page-headers/page-title.component.ts @@ -1,29 +1,24 @@ -import { faPlus } from '@fortawesome/free-solid-svg-icons'; import { Component, Input } from '@angular/core'; import { Router } from '@angular/router'; @Component({ selector: 'app-page-title', template: ` -
-
-

{{ title }}

-
-
- - - {{ buttontitle }} - +
+

{{ title }}

+ + +
-
` }) -export class PageTitleComponent { - - faPlus=faPlus; +export class PageTitleComponent { @Input() title: any; @Input() buttontitle?: any; @@ -31,11 +26,9 @@ export class PageTitleComponent { @Input() subbutton?: boolean; @Input() usetoggle?: boolean; - constructor( - private router: Router - ) { } + constructor(private router: Router) { } - redirect(){ + navigate() { this.router.navigate([this.buttonlink]); } diff --git a/src/app/shared/page-headers/page-title.module.ts b/src/app/shared/page-headers/page-title.module.ts index b706a1a2..a3ba6e96 100644 --- a/src/app/shared/page-headers/page-title.module.ts +++ b/src/app/shared/page-headers/page-title.module.ts @@ -4,12 +4,16 @@ import { PageTitleComponent } from './page-title.component'; import { CommonModule } from '@angular/common'; import { FormsModule } from '@angular/forms'; import { NgModule } from '@angular/core'; +import { MatButtonModule } from '@angular/material/button'; +import { MatIconModule } from '@angular/material/icon'; @NgModule({ imports: [ FormsModule, CommonModule, - FontAwesomeModule + FontAwesomeModule, + MatButtonModule, + MatIconModule, ], exports: [ PageTitleComponent, From d37a56832201d45046b5b39f345e42d6c6c0af85 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Wed, 8 Nov 2023 08:42:26 +0100 Subject: [PATCH 191/419] update import paths --- src/app/core/_services/export/export.service.ts | 2 +- src/app/core/_services/export/export.util.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/core/_services/export/export.service.ts b/src/app/core/_services/export/export.service.ts index 681fda68..b8f73bb3 100644 --- a/src/app/core/_services/export/export.service.ts +++ b/src/app/core/_services/export/export.service.ts @@ -1,10 +1,10 @@ import { Injectable } from '@angular/core'; import { Workbook } from 'exceljs'; import { unparse } from 'papaparse'; -import { HTTableColumn } from '../../_components/ht-table/ht-table.models'; import { ExportUtil } from './export.util'; import { ExcelColumn } from './export.model'; import { Clipboard } from '@angular/cdk/clipboard'; +import { HTTableColumn } from '../../_components/tables/ht-table/ht-table.models'; /** * Service for exporting data to Excel, CSV formats and to the clipboard. diff --git a/src/app/core/_services/export/export.util.ts b/src/app/core/_services/export/export.util.ts index dacd893f..05ffac1f 100644 --- a/src/app/core/_services/export/export.util.ts +++ b/src/app/core/_services/export/export.util.ts @@ -1,6 +1,6 @@ import { Injectable } from "@angular/core"; -import { HTTableColumn } from "../../_components/ht-table/ht-table.models"; import { ExcelColumn } from "./export.model"; +import { HTTableColumn } from "../../_components/tables/ht-table/ht-table.models"; @Injectable({ From f17d985811b0eda9eab327ebef91afe8a2150205 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Wed, 8 Nov 2023 08:43:15 +0100 Subject: [PATCH 192/419] Register agents table component + update import paths --- src/app/core/_components/core-components.module.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/app/core/_components/core-components.module.ts b/src/app/core/_components/core-components.module.ts index a8ebfc51..c3edf4a3 100644 --- a/src/app/core/_components/core-components.module.ts +++ b/src/app/core/_components/core-components.module.ts @@ -2,14 +2,12 @@ import { CommonModule } from "@angular/common"; import { NgModule } from "@angular/core"; import { ReactiveFormsModule, FormsModule } from "@angular/forms"; import { RouterModule } from "@angular/router"; -import { HTTableComponent } from "./ht-table/ht-table.component"; import { ActionMenuComponent } from "./menus/action-menu/action-menu.component"; import { BaseMenuComponent } from "./menus/base-menu/base-menu.component"; import { BulkActionMenuComponent } from "./menus/bulk-action-menu/bulk-action-menu.component"; import { RowActionMenuComponent } from "./menus/row-action-menu/row-action-menu.component"; -import { TableDialogComponent } from "./table-dialog/table-dialog.component"; -import { BaseTableComponent } from "./base-table/base-table.component"; -import { ColumnSelectionDialogComponent } from "./column-selection-dialog/column-selection-dialog.component"; +import { TableDialogComponent } from "./tables/table-dialog/table-dialog.component"; +import { ColumnSelectionDialogComponent } from "./tables/column-selection-dialog/column-selection-dialog.component"; import { MatButtonModule } from "@angular/material/button"; import { MatDialogModule } from "@angular/material/dialog"; import { MatFormFieldModule } from "@angular/material/form-field"; @@ -27,6 +25,9 @@ import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; import { MatListModule } from '@angular/material/list'; import { MatDividerModule } from '@angular/material/divider'; import { ExportMenuComponent } from "./menus/export-menu/export-menu.component"; +import { BaseTableComponent } from "./tables/base-table/base-table.component"; +import { HTTableComponent } from "./tables/ht-table/ht-table.component"; +import { AgentsTableComponent } from "./tables/agents-table/agents-table.component"; @NgModule({ @@ -40,6 +41,7 @@ import { ExportMenuComponent } from "./menus/export-menu/export-menu.component"; BulkActionMenuComponent, ExportMenuComponent, ColumnSelectionDialogComponent, + AgentsTableComponent, ], imports: [ ReactiveFormsModule, @@ -73,6 +75,7 @@ import { ExportMenuComponent } from "./menus/export-menu/export-menu.component"; RowActionMenuComponent, BulkActionMenuComponent, ExportMenuComponent, + AgentsTableComponent, ], providers: [ { provide: MAT_SNACK_BAR_DEFAULT_OPTIONS, useValue: { duration: 2500 } } From 0d2e0c40b60ccdde87c260f9446be5c527eb140f Mon Sep 17 00:00:00 2001 From: itbf9 Date: Wed, 8 Nov 2023 08:43:34 +0100 Subject: [PATCH 193/419] Use agents table --- .../show-agents/show-agents.component.html | 84 +---- .../show-agents/show-agents.component.ts | 306 +----------------- 2 files changed, 7 insertions(+), 383 deletions(-) diff --git a/src/app/agents/show-agents/show-agents.component.html b/src/app/agents/show-agents/show-agents.component.html index 9ac2b8fd..525a629f 100644 --- a/src/app/agents/show-agents/show-agents.component.html +++ b/src/app/agents/show-agents/show-agents.component.html @@ -1,81 +1,5 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
IDStatusNameOwnerClientGPUs/CPUs+InfoLast activityAccess GroupActions
{{ agent.agentId }} - - Active - - - Inactive - - - - {{ agent.agentName | shortenString:40 }} - - - - - {{ agent.userId}} - - --- - {{ agent.clientSignature }} - Unknown - - notes - - {{ agent.devices }} - - - - {{ agent.lastAct }} at
- {{ agent.lastTime | uiDate }}
- IP: - {{ agent.lastIp }} -
- - - - - - - -
-
- - + + + \ No newline at end of file diff --git a/src/app/agents/show-agents/show-agents.component.ts b/src/app/agents/show-agents/show-agents.component.ts index 0c95a3a3..39942bad 100644 --- a/src/app/agents/show-agents/show-agents.component.ts +++ b/src/app/agents/show-agents/show-agents.component.ts @@ -1,309 +1,9 @@ -import { faEdit, faLock, faPauseCircle,faHomeAlt, faPlus, faFileText, faTrash, faCheckCircle, faArrowCircleDown, faMicrochip, faTerminal} from '@fortawesome/free-solid-svg-icons'; -import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core'; -import { environment } from 'src/environments/environment'; -import { DataTableDirective } from 'angular-datatables'; -import Swal from 'sweetalert2/dist/sweetalert2.js'; -import {Subject} from 'rxjs'; - -import { UIConfigService } from 'src/app/core/_services/shared/storage.service'; -import { AlertService } from 'src/app/core/_services/shared/alert.service'; -import { GlobalService } from 'src/app/core/_services/main.service'; -import { PageTitle } from 'src/app/core/_decorators/autotitle'; -import { SERV } from '../../core/_services/main.config'; - -declare let $:any; +import { Component } from '@angular/core'; @Component({ selector: 'app-show-agents', templateUrl: './show-agents.component.html' }) -@PageTitle(['Show Agents']) -export class ShowAgentsComponent implements OnInit, OnDestroy { - - faArrowCircleDown=faArrowCircleDown; - faCheckCircle=faCheckCircle; - faPauseCircle=faPauseCircle; - faMicrochip=faMicrochip; - faTerminal=faTerminal; - faFileText=faFileText; - faHome=faHomeAlt; - faTrash=faTrash; - faEdit=faEdit; - faLock=faLock; - faPlus=faPlus; - - @ViewChild(DataTableDirective) - dtElement: DataTableDirective; - - dtTrigger: Subject = new Subject(); - dtOptions: any = {}; - isChecked =false; - - ngOnDestroy(): void { - this.dtTrigger.unsubscribe(); - } - - // ToDo add model - showagents: any = []; - - private maxResults = environment.config.prodApiMaxResults - - constructor( - private uiService: UIConfigService, - private alert: AlertService, - private gs: GlobalService - ) { } - - ngOnInit(): void { - - const params = {'maxResults': this.maxResults, 'expand':'accessGroups'} - - this.gs.getAll(SERV.AGENTS,params).subscribe((agents: any) => { - this.showagents = agents.values; - // this.showagents.forEach(f => (f.checked = false)); - this.dtTrigger.next(void 0); - }); - - const self = this; - this.dtOptions = { - dom: 'Bfrtip', - scrollX: true, - pageLength: 25, - lengthMenu: [ - [10, 25, 50, 100, 250, -1], - [10, 25, 50, 100, 250, 'All'] - ], - stateSave: true, - destroy: true, - select: { - style: 'multi', - }, - // columnDefs: [ { - // width: "10% !important;", - // targets: 0, - // searchable: false, - // orderable: false, - // } ], - buttons: { - dom: { - button: { - className: 'dt-button buttons-collection btn btn-sm-dt btn-outline-gray-600-dt', - } - }, - buttons: [ - { - text: '↻', - autoClose: true, - action: function (e, dt, node, config) { - self.onRefresh(); - } - }, - { - extend: 'collection', - text: 'Export', - buttons: [ - { - extend: 'excelHtml5', - exportOptions: { - columns: [1, 2, 3, 4, 5, 6, 7, 8] - }, - }, - { - extend: 'print', - exportOptions: { - columns: [1, 2, 3, 4, 5, 6, 7, 8] - }, - customize: function ( win ) { - $(win.document.body) - .css( 'font-size', '10pt' ) - $(win.document.body).find( 'table' ) - .addClass( 'compact' ) - .css( 'font-size', 'inherit' ); - } - }, - { - extend: 'csvHtml5', - exportOptions: {modifier: {selected: true}}, - select: true, - customize: function (dt, csv) { - self.onSelectedAgents(); - let data = ""; - for (let i = 0; i < dt.length; i++) { - data = "Agents\n\n"+ dt; - } - return data; - } - }, - { - extend: 'copy', - } - ] - }, - { - extend: 'collection', - text: 'Bulk Actions', - className: 'dt-button buttons-collection btn btn-sm-dt btn-outline-gray-600-dt', - buttons: [ - { - text: 'Delete Agents', - autoClose: true, - action: function (e, dt, node, config) { - self.onDeleteBulk(); - } - }, - { - text: 'Activate Agents', - autoClose: true, - action: function ( e, dt, node, config ) { - const edit = {isActive: true}; - self.onUpdateBulk(edit); - } - }, - { - text: 'Deactivate Agents', - autoClose: true, - action: function ( e, dt, node, config ) { - const edit = {isActive: false}; - self.onUpdateBulk(edit); - } - }, - { - text: 'Edit Rack', - autoClose: true, - action: function ( e, dt, node, config ) { - const title = 'Update Rack (Missing Field)' - self.onModal(title) - } - }, - ] - }, - { - extend: 'colvis', - text: 'Column View', - columns: [ 1, 2, 3, 4, 5, 6, 7, 8 ], - }, - { - extend: "pageLength", - className: "btn-sm" - } - ], - } - }; - } - - onRefresh(){ - this.rerender(); - this.ngOnInit(); - } - - setCheckAll(){ - const chkBoxlength = $(".checkboxCls:checked").length; - if (this.isChecked == true) { - $(".checkboxCls").prop("checked", false); - this.dtElement.dtInstance.then((dtInstance: DataTables.Api) => { - dtInstance.rows( ).deselect(); - this.isChecked = false; - }); - } else { - $(".checkboxCls").prop("checked", true); - this.dtElement.dtInstance.then((dtInstance: DataTables.Api) => { - dtInstance.rows( ).select(); - this.isChecked = true; - }); - } - } - - rerender(): void { - this.dtElement.dtInstance.then((dtInstance: DataTables.Api) => { - // Destroy the table first - dtInstance.destroy(); - // Call the dtTrigger to rerender again - setTimeout(() => { - this.dtTrigger['new'].next(); - }); - }); - } - - onSelectedAgents(){ - $(".dt-button-background").trigger("click"); - const selection = $($(this.dtElement).DataTable.tables()).DataTable().rows({ selected: true } ).data().pluck(0).toArray(); - if(selection.length == 0) { - this.alert.okAlert('You have not selected any Agent',''); - return; - } - const selectionnum = selection.map(i=>Number(i)); - - return selectionnum; - } - - async onDeleteBulk() { - const AgentIds = this.onSelectedAgents(); - this.alert.bulkDeleteAlert(AgentIds,'Agents',SERV.AGENTS); - this.onRefreshTable(); - } - - async onUpdateBulk(value: any) { - const AgentIds = this.onSelectedAgents(); - this.alert.bulkUpdateAlert(AgentIds,value,'Agents',SERV.AGENTS); - this.onRefreshTable(); - } - - onModal(title: string){ - (async () => { - - $(".dt-button-background").trigger("click"); - const selection = $($(this.dtElement).DataTable.tables()).DataTable().rows({ selected: true } ).data().pluck(0).toArray(); - if(selection.length == 0) { - this.alert.okAlert('You have not selected any Agent',''); - return; - } - - const { value: formValues } = await Swal.fire({ - title: title, - html: - '', - focusConfirm: false, - confirmButtonColor: '#4B5563', - preConfirm: () => { - return [ - (document.getElementById('agent-input')).value, - ] - } - }) - - const rack = [] - if (formValues) { - rack.push({rack: formValues}) - // we need to send pus - // this.onUpdateBulk(formValues); - Swal.fire(JSON.stringify(rack)) - - } - - })() - } - - onDelete(id: number, name: string){ - this.alert.deleteConfirmation(name,'Agents').then((confirmed) => { - if (confirmed) { - // Deletion - this.gs.delete(SERV.AGENTS, id).subscribe(() => { - // Successful deletion - this.alert.okAlert(`Deleted Agent ${name}`, ''); - this.onRefreshTable(); // Refresh the table - }); - } else { - // Handle cancellation - this.alert.okAlert(`Agent ${name} is safe!`,''); - } - }); - } - - onRefreshTable(){ - setTimeout(() => { - this.ngOnInit(); - this.rerender(); // rerender datatables - },2000); - } +export class ShowAgentsComponent { -} +} \ No newline at end of file From 6ab4839f93699bb96b2a1b76cbdabb42d1bd4c1b Mon Sep 17 00:00:00 2001 From: itbf9 Date: Wed, 8 Nov 2023 20:05:59 +0100 Subject: [PATCH 194/419] Add prettier lib and config --- .eslintrc.json | 20 ++- .prettierrc.json | 9 ++ package-lock.json | 395 ++++++++++++++++++++++++++++++++++++++++++++++ package.json | 6 +- 4 files changed, 427 insertions(+), 3 deletions(-) create mode 100644 .prettierrc.json diff --git a/.eslintrc.json b/.eslintrc.json index db226e9d..8e037a18 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -12,7 +12,8 @@ "eslint:recommended", "plugin:@typescript-eslint/recommended", "plugin:@angular-eslint/recommended", - "plugin:@angular-eslint/template/process-inline-templates" + "plugin:@angular-eslint/template/process-inline-templates", + "plugin:prettier/recommended" ], "rules": { "@angular-eslint/directive-selector": [ @@ -30,6 +31,21 @@ "prefix": "app", "style": "kebab-case" } + ], + "sort-imports": [ + "error", + { + "ignoreCase": false, + "ignoreDeclarationSort": false, + "ignoreMemberSort": false, + "memberSyntaxSortOrder": [ + "none", + "all", + "multiple", + "single" + ], + "allowSeparatedGroups": false + } ] } }, @@ -44,4 +60,4 @@ "rules": {} } ] -} +} \ No newline at end of file diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 00000000..bb6c70cd --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,9 @@ +{ + "singleQuote": true, + "trailingComma": "none", + "endOfLine": "auto", + "tabWidth": 2, + "semi:": false, + "bracketSameLine:": true, + "arrowParens": "always" +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index bc82f9cf..668b8bb8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -87,6 +87,8 @@ "@typescript-eslint/eslint-plugin": "5.59.7", "@typescript-eslint/parser": "5.59.7", "eslint": "^8.40.0", + "eslint-config-prettier": "^9.0.0", + "eslint-plugin-prettier": "^5.0.1", "jasmine-core": "~5.0.1", "karma": "~6.4.2", "karma-chrome-launcher": "~3.2.0", @@ -94,6 +96,7 @@ "karma-jasmine": "~5.1.0", "karma-jasmine-html-reporter": "~2.1.0", "nodemon": "^3.0.1", + "prettier": "^3.0.3", "puppeteer": "^20.9.0", "typescript": "~5.1.6" } @@ -4888,6 +4891,56 @@ "node": ">=14" } }, + "node_modules/@pkgr/utils": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@pkgr/utils/-/utils-2.4.2.tgz", + "integrity": "sha512-POgTXhjrTfbTV63DiFXav4lBHiICLKKwDeaKn9Nphwj7WH6m0hMMCaJkMyRWjgtPFyRKRVoMXXjczsTQRDEhYw==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "fast-glob": "^3.3.0", + "is-glob": "^4.0.3", + "open": "^9.1.0", + "picocolors": "^1.0.0", + "tslib": "^2.6.0" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, + "node_modules/@pkgr/utils/node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@pkgr/utils/node_modules/open": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/open/-/open-9.1.0.tgz", + "integrity": "sha512-OS+QTnw1/4vrf+9hh1jc1jnYjzSG4ttTBB8UxOwAnInG3Uo4ssetzC1ihqaIHjLJnA5GGlRl6QlZXOTQhRBUvg==", + "dev": true, + "dependencies": { + "default-browser": "^4.0.0", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@popperjs/core": { "version": "2.11.8", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", @@ -7048,6 +7101,18 @@ "bootstrap": ">=5" } }, + "node_modules/bplist-parser": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.2.0.tgz", + "integrity": "sha512-z0M+byMThzQmD9NILRniCUXYsYpjwnlO8N5uCFaCqIOpqRsJCrQL9NK3JsD67CN5a08nF5oIL2bD6loTdHOuKw==", + "dev": true, + "dependencies": { + "big-integer": "^1.6.44" + }, + "engines": { + "node": ">= 5.10.0" + } + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -7235,6 +7300,21 @@ "semver": "^7.0.0" } }, + "node_modules/bundle-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-3.0.0.tgz", + "integrity": "sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw==", + "dev": true, + "dependencies": { + "run-applescript": "^5.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -8456,6 +8536,162 @@ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==" }, + "node_modules/default-browser": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-4.0.0.tgz", + "integrity": "sha512-wX5pXO1+BrhMkSbROFsyxUm0i/cJEScyNhA4PPxc41ICuv05ZZB/MX28s8aZx6xjmatvebIapF6hLEKEcpneUA==", + "dev": true, + "dependencies": { + "bundle-name": "^3.0.0", + "default-browser-id": "^3.0.0", + "execa": "^7.1.1", + "titleize": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-3.0.0.tgz", + "integrity": "sha512-OZ1y3y0SqSICtE8DE4S8YOE9UZOJ8wO16fKWVP5J1Qz42kV9jcnMVFrEE/noXb/ss3Q4pZIH79kxofzyNNtUNA==", + "dev": true, + "dependencies": { + "bplist-parser": "^0.2.0", + "untildify": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser/node_modules/execa": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-7.2.0.tgz", + "integrity": "sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.1", + "human-signals": "^4.3.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^3.0.7", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": "^14.18.0 || ^16.14.0 || >=18.0.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/default-browser/node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser/node_modules/human-signals": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-4.3.1.tgz", + "integrity": "sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==", + "dev": true, + "engines": { + "node": ">=14.18.0" + } + }, + "node_modules/default-browser/node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser/node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser/node_modules/npm-run-path": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz", + "integrity": "sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==", + "dev": true, + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser/node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser/node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/default-gateway": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz", @@ -9230,6 +9466,47 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint-config-prettier": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.0.0.tgz", + "integrity": "sha512-IcJsTkJae2S35pRsRAwoCE+925rJJStOdkKnLVgtE+tEpqU0EVVM7OqrwxqgptKdX29NUwC82I5pXsGFIgSevw==", + "dev": true, + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-prettier": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.0.1.tgz", + "integrity": "sha512-m3u5RnR56asrwV/lDC4GHorlW75DsFfmUcjfCYylTUs85dBRnB7VM6xG8eCMJdeDRnppzmxZVf1GEPJvl1JmNg==", + "dev": true, + "dependencies": { + "prettier-linter-helpers": "^1.0.0", + "synckit": "^0.8.5" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/prettier" + }, + "peerDependencies": { + "@types/eslint": ">=8.0.0", + "eslint": ">=8.0.0", + "prettier": ">=3.0.0" + }, + "peerDependenciesMeta": { + "@types/eslint": { + "optional": true + }, + "eslint-config-prettier": { + "optional": true + } + } + }, "node_modules/eslint-scope": { "version": "7.2.2", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", @@ -9900,6 +10177,12 @@ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, + "node_modules/fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "dev": true + }, "node_modules/fast-fifo": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.0.tgz", @@ -11366,6 +11649,39 @@ "node": ">=0.10.0" } }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-inside-container/node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true, + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-interactive": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", @@ -15238,6 +15554,33 @@ "node": ">= 0.8.0" } }, + "node_modules/prettier": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.3.tgz", + "integrity": "sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/pretty-bytes": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", @@ -16040,6 +16383,21 @@ "fsevents": "~2.3.2" } }, + "node_modules/run-applescript": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-5.0.0.tgz", + "integrity": "sha512-XcT5rBksx1QdIhlFOCtgZkB99ZEouFZ1E2Kc2LHqNW13U3/74YGdkQRmThTwxy4QIyookibDKYZOPqX//6BlAg==", + "dev": true, + "dependencies": { + "execa": "^5.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/run-async": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", @@ -17542,6 +17900,22 @@ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", "dev": true }, + "node_modules/synckit": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.5.tgz", + "integrity": "sha512-L1dapNV6vu2s/4Sputv8xGsCdAVlb5nRDMFU/E27D44l5U6cw1g0dGd45uLc+OXjNMmF4ntiMdCimzcjFKQI8Q==", + "dev": true, + "dependencies": { + "@pkgr/utils": "^2.3.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, "node_modules/tapable": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", @@ -17819,6 +18193,18 @@ "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==" }, + "node_modules/titleize": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/titleize/-/titleize-3.0.0.tgz", + "integrity": "sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/tmp": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", @@ -18460,6 +18846,15 @@ "node": ">= 0.8" } }, + "node_modules/untildify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", + "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/unzipper": { "version": "0.10.14", "resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.10.14.tgz", diff --git a/package.json b/package.json index b45054a9..b254b155 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "test": "ng test --karma-config=karma.conf.js --include=src/app", "test:ci": "ng test --watch=false --browsers=ChromeHeadlessCustom", "build:ci": "ng build --configuration production", + "format": "npx prettier 'src/**/*.{js,jsx,ts,tsx,html,css,scss}' --write", "lint": "ng lint" }, "private": true, @@ -92,6 +93,8 @@ "@typescript-eslint/eslint-plugin": "5.59.7", "@typescript-eslint/parser": "5.59.7", "eslint": "^8.40.0", + "eslint-config-prettier": "^9.0.0", + "eslint-plugin-prettier": "^5.0.1", "jasmine-core": "~5.0.1", "karma": "~6.4.2", "karma-chrome-launcher": "~3.2.0", @@ -99,7 +102,8 @@ "karma-jasmine": "~5.1.0", "karma-jasmine-html-reporter": "~2.1.0", "nodemon": "^3.0.1", + "prettier": "^3.0.3", "puppeteer": "^20.9.0", "typescript": "~5.1.6" } -} +} \ No newline at end of file From 31c734fe6b540d0e7c7b84165223864eb68029ed Mon Sep 17 00:00:00 2001 From: itbf9 Date: Thu, 9 Nov 2023 08:33:03 +0100 Subject: [PATCH 195/419] Add contributing docs --- CODE_OF_CONDUCT.md | 47 ++++++++++++++ CONTRIBUTING.md | 45 ++++++++++++++ development.md => DEVELOPMENT.md | 0 LICENCE.md | 1 + STYLE_GUIDE.md | 101 +++++++++++++++++++++++++++++++ 5 files changed, 194 insertions(+) create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md rename development.md => DEVELOPMENT.md (100%) create mode 100644 LICENCE.md create mode 100644 STYLE_GUIDE.md diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..67a55642 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,47 @@ +# Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to make participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment include: + +- Using welcoming and inclusive language +- Being respectful of differing viewpoints and experiences +- Gracefully accepting constructive criticism +- Focusing on what is best for the community +- Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +- The use of sexualized language or imagery and unwelcome sexual attention or advances +- Trolling, insulting/derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or electronic address, without explicit permission +- Other conduct which could reasonably be considered inappropriate in a professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project email address, posting via an official social media account, or acting as an appointed representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at [project email]. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0, available at [https://www.contributor-covenant.org/version/2/0/code_of_conduct.html](https://www.contributor-covenant.org/version/2/0/code_of_conduct.html) + +For answers to common questions about this code of conduct, see [https://www.contributor-covenant.org/faq](https://www.contributor-covenant.org/faq) + +[homepage]: https://www.contributor-covenant.org \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..8e0d1602 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,45 @@ +# Contributing to Hashtopolis Web UI + +First off, thank you for considering contributing to Hashtopolis Web UI! 👍 + +## Code of Conduct + +Please note that this project is released with a [Code of Conduct](CODE_OF_CONDUCT.md). By participating in this project, you agree to abide by its terms. + +## How Can I Contribute? + +### Reporting Bugs + +If you find a bug, open an issue. Make sure to include details like your operating system, browser version, and steps to reproduce the bug. + +### Suggesting Enhancements + +For new features or improvements, open an issue with your proposal. Discuss the scope, implementation details, and potential challenges. + +### Pull Requests + +1. Fork the repository and create your branch from `main`. +2. If you've added code that should be tested, add tests. +3. Ensure the test suite passes (`npm test`). +4. Make sure your code lints (`npm run lint`). +5. Issue a pull request. + +## Development Setup + +See [DEVELOPMENT.md](DEVELOPMENT.md) + +## Code Style + +Follow the [style guide](STYLE_GUIDE.md) to maintain consistency in the codebase. + +## Versioning + +This project follows semantic versioning. Before creating a pull request, consider how your changes will impact the version number. + +## License + +By contributing, you agree that your contributions will be licensed under the [LICENSE](LICENSE.md) file. + +--- + +Thank you for your contributions! 🚀 \ No newline at end of file diff --git a/development.md b/DEVELOPMENT.md similarity index 100% rename from development.md rename to DEVELOPMENT.md diff --git a/LICENCE.md b/LICENCE.md new file mode 100644 index 00000000..90a1d60a --- /dev/null +++ b/LICENCE.md @@ -0,0 +1 @@ +... \ No newline at end of file diff --git a/STYLE_GUIDE.md b/STYLE_GUIDE.md new file mode 100644 index 00000000..ed328659 --- /dev/null +++ b/STYLE_GUIDE.md @@ -0,0 +1,101 @@ +# Project Style Guide + +This document outlines the coding and documentation conventions for our project. + +## Table of Contents + +1. [General Guidelines](#general-guidelines) +2. [Naming Conventions](#naming-conventions) +3. [Comments](#comments) +4. [Formatting](#formatting) +5. [Error Handling](#error-handling) +6. [Documentation](#documentation) +7. [Angular-Specific Guidelines](#angular-specific-guidelines) + +## 1. General Guidelines + +- Write clear and concise code. +- Keep functions and methods focused on a single responsibility. +- Follow the [Code of Conduct](CONTRIBUTING.md). + +## 2. Naming Conventions + +### Variables and Constants + +- Use descriptive names. +- Avoid overly abbreviated or cryptic names. +- Prefer camelCase for variables and constants. +- Constants should be in UPPERCASE_SNAKE_CASE. + +```typescript +// Good +const maxItemCount = 10; +let userName = 'JohnDoe'; + +// Bad +const mxItmCnt = 10; +let x = 'JohnDoe'; +``` + +## 3. Comments + +- Use comments sparingly; prefer self-explanatory code. +- Add comments for complex logic or where clarification is needed. +- Keep comments up-to-date. + +```typescript +// Good +const calculateTotal = (items: number[]): number => { + // Sum up the array of items + return items.reduce((sum, item) => sum + item, 0); +}; + +// Bad +const calculateTotal = (arr: number[]): number => arr.reduce((s, i) => s + i, 0); +``` + +## 4. Formatting + +- Follow consistent indentation (2 spaces). +- Keep lines around 80-120 characters. +- Use a consistent line break style (e.g., Unix-style line endings). + +## 5. Error Handling + +- Always handle errors appropriately. +- Avoid empty catch blocks; log or handle the error. + +```typescript +// Good +try { + // some code that may throw an error +} catch (error) { + console.error('An error occurred:', error); +} + +// Bad +try { + // some code that may throw an error +} catch (error) { + // empty catch block +} +``` + +## 6. Documentation + +- Provide clear and concise documentation for public APIs. +- Use JSDoc comments for functions and methods. + +```typescript +/** + * Adds two numbers. + * @param {number} a - The first number. + * @param {number} b - The second number. + * @returns {number} The sum of the two numbers. + */ +const addNumbers = (a: number, b: number): number => a + b; +``` + +## 7. Angular-Specific Guidelines + +- Follow the official [Angular Style Guide](https://angular.io/guide/styleguide). \ No newline at end of file From 8d5f5b80f078b9d03a238d167b461a6cedb9c45c Mon Sep 17 00:00:00 2001 From: itbf9 Date: Thu, 9 Nov 2023 08:46:52 +0100 Subject: [PATCH 196/419] Update docs --- CONTRIBUTING.md | 2 +- STYLE_GUIDE.md | 70 ++++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 58 insertions(+), 14 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8e0d1602..6f803592 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -18,7 +18,7 @@ For new features or improvements, open an issue with your proposal. Discuss the ### Pull Requests -1. Fork the repository and create your branch from `main`. +1. Fork the repository and create your branch from `master`. 2. If you've added code that should be tested, add tests. 3. Ensure the test suite passes (`npm test`). 4. Make sure your code lints (`npm run lint`). diff --git a/STYLE_GUIDE.md b/STYLE_GUIDE.md index ed328659..88afd575 100644 --- a/STYLE_GUIDE.md +++ b/STYLE_GUIDE.md @@ -20,22 +20,66 @@ This document outlines the coding and documentation conventions for our project. ## 2. Naming Conventions -### Variables and Constants +### Variable Names: -- Use descriptive names. -- Avoid overly abbreviated or cryptic names. -- Prefer camelCase for variables and constants. -- Constants should be in UPPERCASE_SNAKE_CASE. +Choose variable names that are descriptive, clear, and contextually relevant, avoiding ambiguity, generic terms, and "magic" values, and strive for consistency and single responsibility to enhance code readability and maintainability. -```typescript -// Good -const maxItemCount = 10; -let userName = 'JohnDoe'; +### Constants: + +- Use uppercase letters and underscores to separate words (e.g., MAX_ATTEMPTS, API_URL). +- Group related constants with a common prefix (e.g., APP_CONSTANTS_MAX_ATTEMPTS). + +### Classes: + +- Use PascalCase for class names (e.g., AppComponent, UserService). +- Suffix services with "Service" (e.g., DataService). +- Use descriptive names that convey the purpose of the class. + +### Components: + +- Use kebab-case for component selectors (e.g., ). +- Use PascalCase for component class names (e.g., MyComponent). +- Suffix components with "Component" (e.g., UserListComponent). +- Choose clear and concise names that reflect the component's role. + +### Files: + +- Use kebab-case for file names (e.g., user-list.component.ts, data-service.service.ts). +- Match the file name with the primary class or component it contains. +- Include the file extension (e.g., .ts, .html, .scss). + +### Interfaces: + +- Use PascalCase for interface names (e.g., User, ApiResponse). +- Prefix interfaces with "I" (e.g., IUser, IApiResponse), although this is optional. + +### Modules: + +- Use PascalCase for module names (e.g., AppModule, UserModule). +- Choose names that represent the feature or functionality encapsulated by the module. + +### Directives: + +- Use camelCase for directive selectors (e.g., appMyDirective). +- Use PascalCase for directive class names (e.g., MyDirective). +- Suffix directives with "Directive" (e.g., HighlightDirective). + +### Services: + +- Use camelCase for service instances (e.g., dataService, authService). +- Use PascalCase for service class names (e.g., DataService, AuthService). +- Suffix services with "Service" (e.g., DataService). + +### Enums: + +- Use PascalCase for enum names (e.g., Color, UserRole). +- Use uppercase letters for enum values (e.g., RED, ADMIN). + +### Routes: + +- Use kebab-case for route paths (e.g., /user-profile, /dashboard). +- Use PascalCase for route components (e.g., UserProfileComponent, DashboardComponent). -// Bad -const mxItmCnt = 10; -let x = 'JohnDoe'; -``` ## 3. Comments From 7f87124e9c7ea9e4852853583d9e5e54d5459f7d Mon Sep 17 00:00:00 2001 From: itbf9 Date: Thu, 9 Nov 2023 13:13:00 +0100 Subject: [PATCH 197/419] Use predefined colors --- src/styles/layout/_header.scss | 1 - src/styles/theme.scss | 87 ++++------------------------------ 2 files changed, 9 insertions(+), 79 deletions(-) diff --git a/src/styles/layout/_header.scss b/src/styles/layout/_header.scss index 8cca72da..9fd9e61b 100644 --- a/src/styles/layout/_header.scss +++ b/src/styles/layout/_header.scss @@ -33,7 +33,6 @@ &.active { background-color: $primary-900; - border-radius: 0; .mdc-button__label { font-weight: 400; diff --git a/src/styles/theme.scss b/src/styles/theme.scss index 5fd07c22..16e3a095 100644 --- a/src/styles/theme.scss +++ b/src/styles/theme.scss @@ -2,90 +2,21 @@ @import "@angular/material/theming"; @include mat.core(); -$hashtopolis-primary-palette: ( - 50: #eceff1, - 100: #cfd8dc, - 200: #b0bec5, - 300: #90a4ae, - 400: #78909c, - 500: #607d8b, - 600: #546e7a, - 700: #455a64, - 800: #37474f, - 900: #263238, - contrast: (50: rgba(black, 0.87), - 100: rgba(black, 0.87), - 200: rgba(black, 0.87), - 300: rgba(black, 0.87), - 400: rgba(black, 0.87), - 500: #ffffff, - 600: #ffffff, - 700: #ffffff, - 800: #ffffff, - 900: #ffffff) -); - -$hashtopolis-accent-palette: ( - 50: #e0f7fa, - 100: #b2ebf2, - 200: #80deea, - 300: #4dcfe1, - 400: #26c5da, - 500: #00bbd4, - 600: #00abc1, - 700: #0096a7, - 800: #00828f, - 900: #005f64, - contrast: (50: rgba(black, 0.87), - 100: rgba(black, 0.87), - 200: rgba(black, 0.87), - 300: rgba(black, 0.87), - 400: #ffffff, - 500: #ffffff, - 600: #ffffff, - 700: #ffffff, - 800: #ffffff, - 900: #ffffff) -); - -$hashtopolis-warn-palette: ( - 50: #f3e4e8, - 100: #e3bbc8, - 200: #d191a5, - 300: #c06c84, - 400: #b3566d, - 500: #af4159, - 600: #9f3e55, - 700: #8b384e, - 800: #773348, - 900: #56283a, - contrast: (50: rgba(black, 0.87), - 100: rgba(black, 0.87), - 200: rgba(black, 0.87), - 300: rgba(black, 0.87), - 400: #ffffff, - 500: #ffffff, - 600: #ffffff, - 700: #ffffff, - 800: #ffffff, - 900: #ffffff) -); - -$primary: mat.define-palette($hashtopolis-primary-palette); -$accent: mat.define-palette($hashtopolis-accent-palette); -$warn: mat.define-palette($hashtopolis-warn-palette); +$hashtopolis-primary-palette: mat.define-palette(mat.$blue-grey-palette); +$hashtopolis-accent-palette: mat.define-palette(mat.$cyan-palette); +$hashtopolis-warn-palette: mat.define-palette(mat.$red-palette); // Include custom palettes in the theme -$hashtopolis-light-theme: mat.define-light-theme((color: (primary: $primary, - accent: $accent, - warn: $warn ), +$hashtopolis-light-theme: mat.define-light-theme((color: (primary: $hashtopolis-primary-palette, + accent: $hashtopolis-accent-palette, + warn: $hashtopolis-warn-palette ), typography: mat.define-typography-config(), density: 0, )); -$hashtopolis-dark-theme: mat.define-dark-theme((color: (primary: $primary, - accent: $accent, - warn: $warn ), +$hashtopolis-dark-theme: mat.define-dark-theme((color: (primary: $hashtopolis-primary-palette, + accent: $hashtopolis-accent-palette, + warn: $hashtopolis-warn-palette ), typography: mat.define-typography-config(), density: 0, )); From 304a6c40420522fbef382ea5609ca64cb08ceec3 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Thu, 9 Nov 2023 13:22:48 +0100 Subject: [PATCH 198/419] Add material fonts --- src/assets/fonts/material-icons.woff2 | Bin 0 -> 128352 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/assets/fonts/material-icons.woff2 diff --git a/src/assets/fonts/material-icons.woff2 b/src/assets/fonts/material-icons.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..5492a6e75937db19f9ba860cd2575744887fa26b GIT binary patch literal 128352 zcmV)EK)}CuPew8T0RR910rg-24*&oF1%B`V0rdp{0RR9100000000000000000000 z0000Q92*QAgWp63U;yh52m}!b3XsDrjK*{e#aI9VHUcCAqHqKt1%i49AY0vBlugt^ z-0b%Jye$%_R#YJBCL+M9QDf(;QTmXjw1U7cB5cD zGmctqU~3{eIH}u9YpsbE5fK%Sm{n3GRWkMaojeowl-nm^(oC2%6DG~z<;hS6Sh*U? zEP6H0YCJ0|xS7(syJlvJ)2LW6Num%goC%iyCEr>+<5g7UN0KQ_x*q$0zp0V5JF7tu zfyWe2er0y%AM!1F&HtY7r(W{6e73yYAFyymx&J`8bcZ+TE{Zh6@ILp~>wtazRa<$K zlowAp=AZMo{Of#er%W(3tPKT+bki5LXP)1k;E16wqspY)CgGq(yOSJG}43R(Sr8eDf`EJ&DrY1((#mJD=*@VXEs-{tg+%9+?tbM@w51|AM)vwK%0Rz#=>K{2Ybtg5Td7?wg?;5>D1|A_;1v#SQc z4GwFlfo?E=pdQi?>E?y85+EP)PbQfJ@*l>OCHX+Y2xH~LRCbWWfy!`H^G!I_Lx9SXeqgfi}MNlXlY|Nom>-CMPIZ`E?3dx&HyNz+iK zEh#X=0ZYDMseUZKIQ8>e@AuWOZ3VhBWa#FFzyLuU&32Y^_GI3i)=O9E8fud?14(1Y zZL<(83{bku-I1)$_|nw-zb{w&YQ4DyK0=ZXLP`U)rL=;N@DrF@KMTsi1}-GbBEb@Z zEt%CGOPx^KQiHQgvBcmJ%tU%9_3bxbk_)9h(7u+0@`w#$sYA1^jWWcFu{1`7D~SEx z;Ji@dh=UlXW*8$2aSlfaM>xV^-}QWJ%kazCm+iG@T(d0EPqjJCO_tx{5ewVANql`t z5xA&sh}mpb+1}-E$`oaaE~ImsZA`{&aJxpJrZGgdTolG)oDVr{_ZD-&e}?Dba<~5bw+ke}7!yx0q;RiW6Va&z zw^pw2C}S|)J~fD*UmJnxO{ta|MZ_S-v1~}c5EAs?<$8TT%{c>ZEr+oviBQvdl)o{< zDkv!U_~UtX_CKTmwsE;a_qt%Ciwy+L5ypUJYOG_d7%ZFEz&RJ&6caE`bVW(AG5www z60k{Wk{6RrNZ-{_7PgUOG4Dy~kKmM-m9$kUTau6Xm@O$UdAr=9$+!wstY- z?s?u|Um8>eICSAss_e({ebxHM^UX^T1R)BdD2WO)J3AZFOFzfTp?2lkZe4UPdQ%ZM z`R$6n4950Z^qvD5VtFLOr}V>5 zrLO<4vUlfL?x%P zcyj>!Lw;#^o%Z$AO6>!~!0w*SLmwy^I0nOBax~{6O)Ir_D+?WaKw!yCq4^sE!2xym zV@~U({Ws~)?MX81a%=}a0SMCgl0>uX%eB2uuVEJk+{8{woLT0y{cz)}O0D`LsZuqm z>VxW2HG8S%l;%|J@volitR0)z{PwutYnJJm31mhhK_C(d0trF_0a5}JBn!v{B_K&k z1VFMul%^~RQEC>eW=v9d?@FpZSn9)A>S|jM6jcC7Rg<7tB~ev1I;v`pdG2$c&waf2@p!F$Z)%mx8i_v5(S2&RPYT0Zp{;37 zpVHnLR`dOOmBae@RWZO+Gp2!J#S$zaw%Kg16!m^_HVPp30%Z4Y5Fq*jRRA|gcCQj= zD&+}jDQQ?|xMv$@8|Nv*wdDVAruL;>AVJylKed+HNVC=i8N~Hb>}=P4uIs&eRluum z0IWh2U^hY24Uj}PNJ zIBz?(YqA?*cHnk=6cS|+($fzp;eZs0)CEaB?DB}cv;dHV#WHB55>`|qb)eM8njvfe zNLmfVdSd_oZ)&%;{Z|{GHE10l$2SOPSmfUdKVYI9C}x{VJuSNE5W*8eDI!Pc{Cj@H zXJ(kayW3^QT4Ri=sEDelh={1#_j_(=3gm%h>Dp!nfYljU+-u zj7rhYp8fRc7o=zsbI}12fyhzNZLfayKE$@D1I$gVH4TH6Vo3nh(-A+{FaG^!mG4G`3e{N#C z#q-zv#?;Rz^W*;%3+(m^B&CJ5vF_jryD)5L9eO7ECmw^Lr$VB8gkLVIy(7c!A%Kp> zFyNI-GAnLbEWk-^HUq-}yR8*x8mK`k2!tb?7R0qwH2*i{*mYFcqET)X%)uH0v5`vG zw_-M4>V2?8@QN1k_M7ARSaBi}cWzZui`1T#xx^cCdJns9u3c(~n7THe^> zV9dJIP$^(!r36a&aVnmEq}@S0|0-m;pjA3dFxblx0kP>zs44?jzvq!BF;l5Zrnikh zl#LpYs*JiH+C)(!HAU*@C^7Uvkh(gDr=3%v9`z8|<)ueU6F4ON*DeDFOEcY9_n+AVH+grjTbr&*MrReI(Q0|5{Q(yG;ft#1#d5xDU7IN;0$~C zLC7GX^ zBE-@k%5YB~xH|b;eojPgF~%4Cjb#U^_pMUDfWn#$X)(6-5UxUl9uLElgY6PIoxX#` zrrMU5^eI|Zg?Z11f?sH`-H9l{zam5<@}irEmopSZuR)qA-gCBATboojRsI;b+*%CP zq&m&un?=ao4oTeU#={_C)N<7@FK^_Y11RhGC0GO0YQ2^0@F1zXuZh1&&&WS*w6rQX zr2B?%$-#z036*R?>$6-bB6i&cucVlq-o6ykwM-0E$dsvMN2YCJuf ziip4g(IinZzB;mEZME1_{LF_u;|^Cht^fvy_w;R`C$3O(-rHQmC!t(nDdQ%K>*PCN zw&gQNo&h^WvxsUO#%^4#NpdKc^N_tD5oldI&G&UblySrHSWq=D9jZ9~Y#S;K!hivZ ztFF~O6JMB!!O$UbCPtf-F!D56=_q;(sV%cjuPfNJ;7k(n*1#nTA`B~XDg*2|!`yng zEOt5DQQ0)M9PzKEHb&Cu0n%cO8v@Sh)%qWPIf=XtFOG8RMa2OE;tt8_UEd2Z!qj+U zou-_Vg7)>O&0_}w?#vv7L*_+CZ~DabUeS+3ePR-tl2_yWPU99bf7Zpo{)907E`qe{ zYdh>LefoUGG`OaAt_r06g$H`Hl=02<(>8iurZ0VRhtQ`2z@|ULOGR1WAiE3SXAS z(PH{@Mw$#>m%hH7wOz{rJX1AwJ)X1-F?i5FNiu*oQ+MF-Px2M0`^qi#bO*Ch6ZN*? zU~DX+tKt4>qN^oBp5g~_2&LXf;S`6W1uUGy1n|}a_O6v(g)*}?EI#y1hBxo|DvxdX ztvitLz(}*wv~B=|Dek>;!Qrqy{!=tF9Nke8X@Lo25(?Zef$yfekE%Lq`wW>N`j=QsMcTJuW~bL_2yfl=ZJ_$6OVDdj zAeP+>&h8r~ve94Uz}J`yS=KKOfw8HUL&!n>gY9052|nDeVvYr6&#SO~c0Wto0zZIL zp;RZ{a76gFz_-e^1#Z+eazdnq(y7bBduBlVlEZBbQTs_`Ck*ueGp7yK4dx7{49W~L z4B{+}x7f;~Z5Fx1l6eCMRHGgNmX57v>sKz%PYusajSb9<34Xcd2-l8v#t%kf0m`q- zxF@jRPt&(Nbg6>1cNnmTEJh+&(d^2>_>56ayN}1_N8aFg4iz?8MsXfr+)x zK$OL=x=RnE*3SqqYAOaa1fsMRTq12bEGtO&^aNrbEPT2Z5)?adF6yBUPiVaRDM-CN zN)b5n)5J^?j- zbU`J(Gghj{QJcYMEV#v>Y)K;5yKIB6H>R3C&n8|%dK!xphk(+$t$DR?O1sl^Lvcxn z2!MH%n@U2ap0mKI5a+xLCq;&pAG(V@fir7)sR55cb^e+?5 z6OA++SBV)Qo8pRj&R$FJp%Pk0Pe?Htp0w6s4pX0TDQJt?Dj9-=nTp**hl9Ugkge{W@q2cAJzo4xYFkbVLidquOyCgq8|a14-G9@Z}<; zUVqscr#=~;D*+>MSn!3{k{s%Bw64R8+nmjy314?5-;|R0*~T1!FBc_;JlNHbQ9*K# zU@{@Ny~nHu3=xc?jW~h@56S3$);A>eZ2Tl_V8?rPC$eoLF#9TYlKPkq1Mb;Rr#WW& z#xd(ReY+O}nnw{JSTF{5UxS6#Mb zO_r+EYz#XJ`BP?<`q(AL1R2zAZ;|B-maYcsW)eCJ)5qkQniMS+hWEB zQx++7_*bn+y+YLr4^z3tix!Yof@t|ATm~eW3{$3Dm~+cDONJ9M!D3($0Juhlz&t8& zBti*tuxU$*fEIE@DUhW2;6}{xp+ap%IniQu7<rHKhkr1zU!rAVR?%IUBmB#LtuTx_HEkRUyX z&U>1S*xazIj1yBSAEP#r)7KnU@R64Hb?+}J2ys$hD zg%n@(92CHE{7mc`-NE1E>oG&HbwBV0JlzN0+oCHc&jFzMM(IGY7?<`jD?vVwQ{RVN zf!TC}AY0(+7LVO6zgeIREZbCE0rmLIx7I*Gr0}&Kreh9|!j|&*d0L{bad42iFhgDw zHaSSza5dg^Xtbld55)ue^89ZTT;iT*XbGIL5}Z37C-FmvvJ=_T(5p9V=tupmRdAgC zi7UtoG2<{q($2Dcm*DPgPhvU|$jai3=d1uMF{lQli#MN0LYhy8H7~csuX{1(03Gmv z(0K~dMP0$o)`!954VtN>)TzzpE;QvyeOoW78l=;iwzTGChsfV7h^DzH7Aljhj`$cO=Z8p}ju!KwEe@|~Pn z7h^|Hdlr~c=sM$afF?4g-^62CRf#Qj+4-nk1JYR4smRC7S+sdZ9P}hrcL+^Pr;o~4 zuq639sOambMZ1M60+cU8Yr`}Vte86Qii$0iU=*CiqUtS@gHXX;q5fuDTr?vEQx>|o zb3W4=rf)GtLN5tSV|;kF6kVrTJ^z-e+hPmcz1(sGCL}5`-fTq?pdIj}J6mt1u(vJk zHEy=p0L%iFTP1F+w`((OcN?e^F_*SLG|sGH)CEo8EkUm;T>%uDn{9PA!A$*VJoVwi zmkUu*Yr(4*XIR08qO(RTnTFlsfCmp(?Vi;07^%Gs3JANJUweJ3GIeE)^3Q}8v@&1X zBq@wt@kK}q!1U0|FaUgx`BMGun~T?tj9k`hr6zk`%^&80B8EvEav_>@ zWJ&?1mf&zSFxx*F6IAbMP^I4}siQtni`4T5*kKqeO$HPt3&*ni2gihaFuQai4Ns6` zKTR`?v`_uqXyG47ek>Was^&f3r>#uOFlL+-{IHbe_}9an^1T$b#(90YJZPaUj<|7KkCa3cVZj|Jk!!#jK zj>dfiSQ>}nEWJ;3x4%0&(tjR0Myxy5Wf9I71!(rOp`zwRsUn4yrcU+T+=oM_ee-3o z_>|7Td0o;v@}TQJLcm1us#*zMfD3O>+G#B@saiG*Nyl-d0gdt_O5=ovX7qr3X@q`N zw#6eUR|$q6DrSoXYgLL1R4d|AcPgLplYCQqnR_X_X6hAIp}o=8i5^#|tYa=$@V#~h z%*y1b_Zslf99+N8dIhB`TcCjwrqaWLYTp4W5y~7rTSIQ>OVeTu<+&^K>|_{L*D>}| zm^os=o2WU>_639L^eHzj)K#)T-IeX&ZGero1K$LSCx550gPeKq9_*+5#O;T>gR*91O_B=9(2Q(xtAP1#@!eU zPJ5PwZnThvnG)p{s?&AOQm5G|ht{x@so6%KAd8)#_RULJ6ELi>0W_Wn|Ipxr;AZ0e zRsGA?!OGq72IsCw^7XV_;_I$tOuyjYXM4h1lsmo{MjdgG_P&e=z2R;ARe3s6^YSF$lc7D3OkEx>nJ^qTv+fO4 zs&;TEhH7?_AM#a#qynK}dS1?UyEE$_oXGhT61QXwrX|p4l0x&SI>H1 z>%)qj$f|dQNXWI6$|VC!%T;Zwf((r*)$nDSP45F9!R=o1A$2|LH{rgxM6sixrDlH% z9#(Vc5;%zPI`g7kZc1p|05y-?vdlOLa(7#oqHLFn+aVHGuiAE4eM`hLed*1S_SEMu ziFM=_b^{&4P#kXxkG!AZZsF0qQMWevf`3rDvX2Rb;v6K2H~8MW zH5n4nO|6Ywwuw4n{OkN`!50e!@hSD}rr>U;AdI!U6}Ii++5o!!(O45`OBMVb|`<_E$NY;H1Vn~XKpa4qmPucFzG_0 z2iwj~Z9KU!F*Lh0wzkKwbEtYTU824|)?q2z=L$<*6*HOZGK(`Qs^O|g%FeySq!HOj zue05#M6hN-O+!QQAQlKvbTz+%{Z#P#Ji2z8#S{F)uY6$Zh^p9mz!Tq^_5l;G1F>V5 zphG^91qo5*A%Bc?Con8*|xiDDHN-vDc2Wn;sxhK6-(tQK8jk)uv z6reCo#ndLg=s=US%*_Tvk2tKSfgb~q*^)-i{g&E&5ESPBS_PEvD8;pr64NPqLyO4{ zBT!?;Ex)OIn}budgiH6m?7MQFSHjTfD-*8x;F-%lYot zyh!@ukJ%%gqZR|l!C`ywpyPcfz4!!hDmQ0oPgyCs@SE~~Eb2z!TJXRv5QnbAG5M)9 zYfg>ITf@q%X>%4@+N2_NLLPjb4#SK9FEQ+=Sq=1htDCamm>1<%?)!_}S;oo2cPV%_U^|1fM)4 ztc0Y!H=$E=7{d->?mdDY56Pe8gYb4AgmxTFgP=@!{tn>OaRg57M~(%iTn@-|y%9@0 zX=AJpEqhQ)^uyjGWNmUr)z>2OcRYr&kYk{h{{(pRJY#8>H!r+3=_6#u$&9hZ{B_iF zO(X?8w88Nlzwte9xWlE6w4Jpeck{ZIT9q52P`Jx-R^mM?`h8@tY-}=BfpN&x3$L!o zmHfjnRIewXO~Hr|>pFGP5i0wOhM1k-=-7#!LMc1r!&?W{)SQRU79osR=vA#t4LR}x z0@&*s#~XM+ttP)r#qnV6xffvqH=aAGWq_;8MdZw~(Z9dGG0uyskETHoN_MO{U&dt6h| zwywOSB6aq`vDu`I3eO3DNK6WyP)Xni9h8E=BLVOwGK}TC391(ge&~5v!jwn@;p^88 z)@L`2E+3(C&1p?Jz@N4#lI<*7^AjPe7*NI>90|I zM+5a~#iLMqm|KPw=hpXA>0v4zJb0?F5iJo~<}VlL=ThvNeBtHTE;f&`EF@JQLW|J$ zMOV{wsG0EWA^nA;Y%5QLISODGG>+vG)37b!V9tWz1|YHgcWjrM$5$$;IIki#$FwlnR>SO^?030F zd1Kl;)TeFT&BbII1XF0qWx#zis4(uOZOwE!RWneVdMz-QT*0p$m0T&h%+(;@_PPYJ z(Bi2{=xp6+7gN`U9Ecb60bsv{$1E?yGCzzqoKX$#Zyxdc=IU*P6p{nplSG&4D=_f& z=vId$QpU01V7*CXc%x1uWH_Ujb-7l*OkaVK;B4iRXo?M_Et&1MNeqHb&$hRjK34#s z`>NmxLTp?B6CXB~&XSStK1K9i4R8oCE(z}QmP&*Be`==Fn5f}B!&m{p%E5$HQeb)A zDK^KeLAxNhFTYO)`x!*B$(A}uF?hvHXDCMoEZtakH4PUW)2-VypAU;m?f34Bm+KZ& zb}t?!WMLHI2jy{R)>1V$0^eC0 zlJWX@L><57#H&hnJE&=CnZgbYTy7Tdoaw=e;Wo96#Vnxu=mjX9As|lqb^W2oTiX0# zk*!TPf8pev*g5eTxhIyAvuOSqAV1v3E1|4h0BU>x0t?Upo1fN1LQ*ldRL<=+^|pQ~ z_?rPK)7RQo0(iqFG_AaBaP@kzg2~{>`b>(n-W7JE^wEW=#f%}OaxG0Xe5=hRC9)?) zcC=O#nzT4*<4l9hc7SBLXQv#KdwTo-+cD{PyW1n)lE_gvIAN)*6>5)CeZ!#th!gls zRjuZ|FQa|TC9>gKjsUts{BkKw<60l$h`M^sN;SLFgD0Wc>FO(l&G|dU3Rk=cwW;BK8}AmJ-vc z#vra$x=HLV7X5|tlyf&CeR<-FhLx?i-3O(%~SX(d_iPRIjHYu=MJP{a@EROvFMHP1b#c; z-HB#UypA~WmR{AtMOu7;rnK68^qPPfPN(AL<4xQJ7-G>r|K&@mB#uNC-{P;>ipl7V zf{2Nl&;}YryugpV;U*V4)K1noFr_{xYZj}`?~Gnzkk_s{7#$$>YCOdJ;I7s%&ZMQD z<7n;1_|Acw4_{hHoBk^r?UlUMulHeGKZf%!?Hisg^mp~uw=X=tL2aR>sN>Omg26z! zw4oTpZKXk&2jR}gVGzS9_R6kQFBU69@l6U2*>kg4W40j$VD1mE1wZIEmu`g)4unS3 ztmWM{g|H*kQD#Y2Q?!Ch*O_|~+5=dm#Nr7SY5PBa6$3cu@6y_36DtRo@BpenniS-& z8QcEu>rM-8vXK_WO4R3Ltpy9VKVF&&#jQ!>)F`)<31ni}rL*{p>u@XpzG2&mtrKgf zHZ(QZg1CnddCEO*sbaTxL0h{*ke1GsJPYuSdG*N2n?Bm0;uJn}g_Dev-KdkCju978phP=c#vY?$QjT8Z?DyS7Rg9D z*)VeCEAUz3WuuEAA|la7k>lJ@)aS$rlf`i++sJOU&O(#W^_XlxrlxaN+|3&g&?UmH z5a{*XC91`X%hflPm4Q5?bZS{u0_lP~e#KiDIyN>Ke7vSRN`8hO`I+JO+fB+7X;KwI zY~wARht-fMn|Gi$AQ5g7T&lvmq9s7U(hB$PaPhz*m?6%!_&@<#h{*}y!^C0}y*loI%h6$r%uB?m^`($rwf^uDc}8r_rKTAM`W75?M5pc2C1I1(u3pBOvl zb|Cy_pUC(-B)4)Xnr#VjZ(ZD`T+k28y7Yc{_w;xg-1BvrJx6paT3&r0H4!lBwopne z9R$JsjKb-!*~AGSD5hxifwJh?A0RYZxA^&SL>m!)-#?NcV#5lIR#6-R2OzMY5396f zpk!P6I81~Mc6K}pE@N_h@{u93kl2nnf%d0GmD&q^}>Y4myLb%MN!Jkk?Je^cIht1SEI6V~-~Fi0Xiw^F?ch zu6cX`oNL^NgJUn0b2}-jH~L)T4w60@!zH%o#FIO(Q6jpj*_ix;>b}k07>K^k1qv7P}F~15ViI*mN;hhhwOb9zpY#Z>0v(Is6?+XBd|uz|P#0xEorLvo;mf zxF1{pc&iHi5l{3TW7~s(~ERmi5MSXS%Z4 zQZg0$1F@@&#q(T_kPC1}Cy$hdyVU`r=m!ngu^pwAycMvo5g0nbd)1o}P>dAlbi0zSjWefllC- z)gQ6{+*Rs;dyb~qwnc&Onw^ltQ1}Z&fb^!gLnG}op*ImPLb=+N1q5&}k~8oiYM$AQ zrIE}f`iG-7ha-5r=7HD(g{U%B98R(y`Y#d@V}nv8B{e-QoJ$%O&Nvl08Fw(SCFG_$ z-KVIVSpd1zD0o~6H7K!qe$A$fBP&O@8YR)fB0w0*cbVx^ve9fXQ#?U$a;ZqYlxb6y zF3pt0tt(55TBet(g|OrjO^$;TkYH{gH!Mx;a(!-Wv$LaXKgf85KeQi9$#f5&ddls+ z<~}-$m|cmn<_$~HFD3pox4oY0ViR5|LJGC?@Kb)X$x6ir0L!WMDdn(yNe`9Dv;9ukDc6|}!wr%fNszd!n%?mn{M7)%ilZYU zawzQht%R6qCRn|-FN!U^TghQLSn%CL)1FnLD2yp#lpcws^?QU8p0yRcqhm9sH?DL( zGL+A8PP#eOecV%Z6MKnZJtPa?h`FpDr|?q>kE__3b+A#&ZTj1>cQR_feu+PJ{N zP5Tn%0qMuneT7H)45#BmM#DHoPe2#{14GP>xfhV2L^6076s>C!dH7~c%1F)0mRlIp zQMP{!zUY_qIXk~+#+2u_)eAnS^^~iToXcE}<54v3ZIZz&3rZ0xBj5M3+#&p_d3t$B zIX?~{ z+Fpj?($P2-WYA-=cTp9;u8gWt8mWJwzwBb~SK9R-2dKEa8Po;%fpy?LdV1Xu8%fKDf1HWFevUg4(ZgBnigLj*;C-9ES+7 zxBnnqz2~6!DRi~0_-s%ZL_~er_o-8N3%8qo@8npcetaB81`DJgWBj6U3G6ylearM^ zMtq79i}M|Yh7@^2_;f~5QHSDCK`IFMEBYtvzHY59_>j@ydYo(JkN~h<9zc49#I1Bz z1?Q3h2X)kW7(7bbr&#?!)sTWo(qk;Gh~W&Q-u6Xb!L$1-#kc^-irym0VCgcGXbcvt zgmkEl>q4s0^7d=c9kHof4W_{G<>~oy8JSJi$VeW#u1LwI1x8bqHorJi?&4g9KdDd= z1z-0*>YO<^vtfT1Tha6-be;HaggPEz#U9NK+QNi~BMFJ(RdcoF@!2Y3tZXR1q~x_L z=BIl{q$#qgxVHj0f`sLtny|!v(<)pv{*r2i@Qe?90|29MV@tzRL$2@=-}8){T958!B)yPk$=(8{e0ACJW5bf>i`VMsOTQWUC-dV9@{F`s9>A z&o}?%Y6-f&`6o{v>31ZL%~xs^|sTgZi5IH3}-uc zJ5}CUrOTSje(Z_6unjNikQuwppG2|n;y4J#Y5zekSk#I7koSN#VrfmNs6xhnvL+{|hXk0cyG zSNPz>d`)O2(Qqf8wwqcIYK~UeB+;xk=BA)_ZfW?uwvcWXf(`fi?GEVZ^E9FQQ~v-) zGP^gMu@5pCAcbVx#NV;x*NR3TXp43hJPpEbExs@c&HB76Ac`QSb=OPRi_sSmy;EQ7 zW8YmLRuI}8ivmrl&y!!J2{M2?7H<0^JfqA`e~((phyn3r5q&yGtVJB=?sa+i`^IKD zWZ(VL`()G>zgVkqP;-}s_G*~=GLGxU?g&7Fb_@jxBA2K)`>gxc-|B}_>aBp;95AlB zn-+vM@{;T41*48ZK}RB?6f`W?$K51tAvI;m0q1e<2Eo0KMdMsqz_<2ONSvKfhLQTy zLp}^TYjR%+e@>ej=p$xjqpV`>Mn?pU8_xgEw%e$It)@oR;l-1t8s!`AQm&M*4K?*P z(WT;vQD4kOTWih1Tq2ZkaIY4dRbodiOueBI!;%F+!GfvCiAW>2%XMQroST?kvbLNf zn1X<3_=BH$#a*?p-Oz0vkXyl3vx}cE@)LOVR_PSkBDI0gEHv*Ra~z9q8XWOEZ_dXb z6dYGjkpS^+(UqjOR57ZzE7EoN04Vt?fyg}8W+YIU+X^2Clb+Cl(ZNy_1-@GxPDc?H++FMxq+EY-ghl}l)Z#tpg{J`DH~Ak#S^sG)u(d2 zVHA!^+t3`6^uew$&S4oZJxTJnnNL*SeO+nY_};u^TRdmB=&ZqZ3bc#&)Jz-4_#Zw0 zN#9*6~6Vnw+QJ>mT zx>SM4EwLOb?WCZ2dZ!ilZ7*P_W#andJp>HR^ej8mK|Sb0x5#sL?LsE_{<~du!lD4( z4q?ml5Y*kM+RVC`@L@Tdj84+&7lu+@-LLlO`VSIgW7mjC*ZbPr{7}uJ0m$$XmvB)QYS~!P& zg~es0+Z?XC^9kX9(e?my7(T|HaMXPbxK@Kh_(sqVf9bhCL{vLE7y&;_OywG2=5bY83u?>UM{Wi4Yiqpm>GN(M^KlwU#5la7D@zsok47*>}k7J_*kl zU7ZO&Yi9nJC;VDgUx zPS7g{GbL^ubVtUz*eRh|;HeOLh#quWrp#b!B#;vqd@}xX>R#TEe&mR$4)JUc!v6jFkryH*g6Ba_tg3$Cfm_Q zXx*Ey`&w8CtOfi*;&muFajUIBvMy7QBA820RSh81$da^G7S8DD1}-m_FN?MOGJ7e> z&gZtO^jwH}Q{&9_QUoB#3ggGWStx)EC5=7f9MQG`UyoFc$|$MStjk@~V_C@bF2ON* zZfA;Ph8gAxG$$OVsUN6I!4SdXvvBe<=-P^}&zVc9ae6it$Q4GebnkUsgiurS6wH?S zxLESCr4q?sDwV>eGAUXrm*S-gDOqYFrAtkv?58QEoMt-5bJhn8Q*CF~4&c`ZeL#wJ z_qI5nk4TpOiEpsaPyM0ui3DIdb&yo=)imG=ei-!SrQ7lFpW%dCXRgh%Lioi!%wZV& zzpe?9Z+X7S$@rPappy17;QIlwG{X1}#@Na^3Sbby;EUUZ&h&JJ<1213M9pA;uh_sr z05WB_ARN@i+s=k$pjD;-SmD_(RRVE9-Is$kojxawaI;A^5k*X|1=XB>61aQuyzrnDr z;~;$OJ@o82QiP(6(>j5tnx^E;75Ej@F%v!8^qyc4E+6GR6^6Q}xp?2LKMZo+-n9C) z+qy{Wpm}aaEYVWdo7fYq&+7+E>p(#nNJuLkx_WXOulW*gvo9im-9nrpE?sKCEIuW| zHIofX#}Xrl!%fGiwuzRQJrg)!3YL? zG8Kqpr{z&K9qaYcmq$$rL1MXY)VG!7JZPx-Mnl0N{&Pm5 z(k{eIq@{GI@dq@HkM4Ov)D6Dh?UD!Sz43&)f=KXi9|L%UrbS-gdTXPfv^xr3W?Y|}wBdJw90J#yrKe2m&E`K~m69FS zeN2C1ZM=JaD)f@hg+W!rimEAB2xZ=%Q4bH5J(s3Jl3K~1_hEKXxw67mH#l}h_ca05QpemV-4){!xf?h z+sLkw?-zoWT<)e)?^Jm}zBS6>wJ5k=y}Vhn5{XGlvNF|VwJJ-XI z=6qD4(z)U}`b2ZP$QY^OX`DML13S&am)nUY6aDEE$q=Y%jaa%vl#=sr(|jpf?;)x>yD zY+z~#wUO(g@n7+B`wx+Iy3Acj zGGsCq+q>1S4v>*lplAs2!n(lT5@yK$D}jVzSbz3F{|FYr7I{h)qUsDc7uTk_h9W4t zk8I-+>P}J+=Zhr)31x47IxmWOo$AiPDC-o~Q@PTU(j?C`S~P^NcrA)$~#@Fk7v@4%58Q8t}R{bw%cgMOBVJ&I3wx5;b#vp~s-j`(Uur z1#2oQ((AhK_5}1cb>w&}L^(7_JkAbedW2h%9mbNNxuZ%Zi$H81*$9O+ZYNaJBDPgt z9$VF;L{U%qZ(Dj=JBg2&*zd05+dw9Aw1blRoFC>fqhVHH+tpq&w)9<-&X&ELisT+W zp{6OGzunV2NHA2HSB?tG74MkQGApocySvjhgYjNuiOcI%Vjs5(|50lfLOsL)y3`P( zAFqm+=vj83?C25v6uCCVc!@CYVEaw6&BHf+Q!MG)80Ra7!A> z>PFYpe_qG*As7!`pc#x8Mt&j!_7IsP(i)5}iJgi{*O;+DY@2|uXHVYS|8{0k)m(j! zLE1#SvwvSeXl9}quvbMFr8Y4{c_u(WVnoJ1a;-{Z3_!OXy9;&eL|>QYt;nO9wr)@& zr*l7!iG_ok%#?~-#+zxd)1Zy^j^td-Wf9-)_)oY{fN$!d@x)IN@S3u(9StB~K5kIs2TsOEQcG!dK&h$KyY@I(~;&Z#;d;iG%dr z=;pCC6HBJ+8mqhv|LXxO8povzNso_=3(6)kc=Ssn_t06O5xG;ElAb3lj0Fp&gfW0# za$R+N%D1a2gY`#XAO_};Hj%+-vBj$Cq4h?#n9>Wt`Q71^1-Y)ebkL(kqLOI7US+(v zzz8Zk>13R12zNtu2CrTDo1clWR*KakSRmLZBAFyWLSN3VS`QK1DK^`K)xZWsP&+xI zQ}WR2pV5#)>h^(;|1o+Y!x0CaMkP~s{*$sK$&W~Fck;A{i-bN<Xdgg+PL^V;A^V#NroGy@|sx+?%umo>+%5Qn2uBvc@}N4lN8 zPjWr{A|pR^Red+`|d*;=?&6{viZlfIr8qt1?Q9?r*NykV~KhT$7f>kncuD zF@bYz(MfYfRINs~c^E9z)QP45+s%nr&2ybXaY&L)Ncj+B(HmcYziy9h>SpW63suPO zij}mnjy2=i6guv)>=tutB8cU7+udrBQp@ii`Bww;TAL#89$1P}Sw2vM|o3K1%+xWLweB1G1IPnA2RVV8J-?ae#qKoGVEl65n z6h{Wqb509dMDkn{C$v#YK_{Bf-M#GqOcxKHq67d-P9iU<#pB5$A!dy8rdGKE30er`($1Kx3f>=q^4dcF!IV7 zwI8BBnN7*%AVnY^si{F(n?QC)@68sH*@3?4%{BmBCU)c_y!ArKLdtnoC^@1dgSljO z+HMXf%X2`nE<5L&oq65f)!IH4Nt(NClWJ8YBtjAPYIwLp*%cMp+&N}>LbU(YNy^ON z@o1hulhn1+Ufh$?Ez_63@{?Zn2jhbaeLJ0de7csGJ3^tinb1=krRQ!q^DD-oqwf@Kn4}E7O1`HE zIE%Sv&p4?2)<49f+{V{M%^br(4ry$2oEWz%yrl%AAU{q}Q}H#-@US-gvDEH^%K{SZ zv@}}eXZRX|Z#0dd%FwI0Spm|t=nBEmrZJCojrb!D>n@#|czFhGmLxNpt4H%d66Uqp zwYYxDCZwDou;#q$vIRG$tXJm(wuhohrH_-0;5{^#kQB&i z^+Pd(7gQnatpaNkgGd znO->)?!Mi-V5PIo+q(6;cG85Xov|R8dWcFfcb}Fd@IXw61rrUbhZj`C!<_=52SVR5 zY=&Mt-~TZRPYT+ODZP0CRM9zDB&>C0safE)1Kx#?}7=RyBrt#mzpgY|dqV-2PZO`o=hWrVvr$9VoY z`sG}@2~}!dOfqWW*jK1nEdM=eG((ohv2vzJU}6^6?&TfuQWAOhQc`*EQZjk}QgZp=QVRL-XPk&oLqok6FjjB# z83TbOC~xSQhe+w7iKy)b<{{6w6h0L5y+{K-z=pV(ngwDBCk)y2YvyR`Y%)&7irg6X#rkkA04TQo||Rnv|cQ3E`y)HQWUprEeV zm6I(PKu8lbzs}B}BNLSR$PMG%Sq>O_y$_>OT}g-Piq-8u!~8E7 zU*`fyXFEWRSaxJRo16tfAUDbHv^*Q zFBb!eRpT`kKG-_>Ga|oPW!EbV8Z&qKy>SfqfrDMenA^qPY4zOjGJZc{W_c$~^Hfg_ zzXu-PToHp@dje^ta83?u^xUA44_?LseLNy2^N}nNvdH4*TV7RI?TP-#ut!?V*eA$u zcE;g=fK^&nT0XTqCO3)bI_6$}5_2mz0t<}@m==O18WqGKd3&nPfFK+s@)O<<0G!Mw zX-;qDqkr~)7eI!X!j?clYfpm^&d98qi`&=wIiGl!z57Y#mOI$G3V`tU8}6Hol0Ob- zh`Lha7kM!uTECC92livqtoX`H+!XaXPNK=~N4k1OD^MM=_b=r!$oeB?{Y0vW4w88B)8a1O*kE10uM;~Mq%CB>r_LYg5Uc@U^gu^>=~lEUGc94tYX;i z?hncOO&dMR2SFU6;Ic|HNDHJJUEhi8%FuG=?;da=&KHXZHuAx9GZcU&!yDZ{4S@O} zUc)X*g4!vRZ5uyv4OUvw1!-w4bK$=OVPwpoQQ|=Ks4(dH+;A|JZDE1@LvJ4$J+asrIw=EP zZ?8}H=(v!BA~|_4e1RqX@HM1NaF=TY;5LO_a1BR(c7sGDxG~RCG2C0}#yuC$FqF_a zwz8KiG3z+#vqbcy<|zKN7VdE=4DCY-K2FAgHDO78udRqTc^%`>ismq_92JwN8=BQg z!r=5m$fBi5eg|f0$c_^t+ER34#|tsC3=RBnNLICXRF-b@LkMY^-0g&9V1k{jHaWCD zW%n?BPiStqZqapZZ(Z>)KVc06vF>$7$2s;2vE~aW>gHAIqzBqR511N6;T3GcE-%+b zFln71U}?o=c1%q}iu+|qsjCvoG0Sa6k^kug&C#8n2{#9w!`6+%c!>3kOKGsmBoZ5G zJ9zYF<6Kx4W7Ej^sJ(7O9%SlP&IwZ5a%U3}C1YypN*C0#y49*@Ue~F{`0E}%II)C) zk_w`CCOFUNuke4Op{;E4?27iKrUo$N5P{Foi~x9MSANde8RE6A(^A|M(6@D-EccwtN|O{n)HC*GhKLu}o&oh&ZCrUGK3Jg83>s4NuCn zu2TK*e8MD@EVt-ut9)&Q1qh#&K_bOr@85km1SLMa8Mlc&EI%m}E>qJP277&rsQS!? zg;5}TS$$H;2fd04dckh36Ohq>yjx8_cpKd0C3qTQ3ezMF@_d(7K``M46H#Dp5Ra$@SuAx^RIU`nIPJ0=`wKYLh-(uRf+=1!;& z>C4L{;O*NSOny}1*SFw2H@eW-4zZ0*AVg@6;LnJ5LX};+V66LguI4{ybCsdnbzsKs z=U}pX<&K2jLVd5Cee7fl)?<~B?ZHVdkkaQ^7ZHDRi|7!G*pP_$kcz~RZAqQ{f9L$3 z*Sz2{_qg1-PIjb2>}7`yUm30)$8iM5HfNkq`%1s0rgu8$KIa*E_mB(UPze9vy2*Ag zoG=aDoaP;%I{`7%LPAg|--6Ju=Mnw^6v<$Bmd@S3z<8k$Rhs1&1ePIU#pni>NS204 z#{v)Uov@Y}@6#y~P1`6DU)=ENFR<2s7NRNsVjpr!9b#n#IR13krXypmyz7|w3DLk={n602^=R$ zD3l^5cADvsVUvk6x_lhT1}kH}oW&3~9wP!oOiukuvLsE>mNIKQw!-Y83$p^{WaeWN zeKoz@!K^t)i(Y9=fpUZsVfM=fB z7Rk?kCTrR>PES46F~9n(!pin8pC<|U;Yh-hqHy{=`-B7cVa=fLuaj@}`0r42Mw)5ne3dVl&!7IZV2Xu*vo5Fl*#c_RC{n9dxjJu|0mwY~KQluc0DwUfI8T=HylGEZ*_Ld^30it7~9+AbglRiK5HZuM3 zM;2!O`7C=0DDW@K5n=I!<%{=Y*b-kUhIL6Pz#`=Ufm8y*QVnQGEnqA4hmhW>m*KNH zW0Zjo83)E?5}=T204uYAkjxtguZ409TL(D>n&b>Wa@nO54@a(Cg>&PE1QZkp_X;?6 z7?|O0AGjO5ARu5MA)&r1tl2dP-Ea@WEG7vT&QF90Q;F2<{{thw3`LUMDGFL35FnF* z$>lusdQhVg!eruOHuJYwc-m}&91h`5C#2gg(BlF3`^AYU6oMiVDHhWb2_%(LGC8VH zAW9{tQZZ^ZtkDo!tvI9rfE*B1fFLSRR0)R3#&LxNA(x_L(X=v_Rm5@fd0x68C>KQ; zk|a%*Wh#nlRaL5Ks&rkgVZfTrYOGexHk(GfU4z4+-sKh}9{?tVB7}q(iwUt%YG%y9 zxk?DDEGra6s;Wv&GwHg{FpQ=tvn+{is~t!0x?0bZ=le`^Hq}lbT2GO1;V_hx%A>OW zBO*jN5-HN2C{Y$gi?%67j6<mN|6)Q&P`HW(PJGBVm#uH2jo6_!=1w53XwX|-xSR;SK`diCx! zXmFuPlY7mYT`@6vqD6~S^UQOtRjW&87B4Ka$gwtU&MdaXjiov~)v42oE?v%Z24O9* zB|9IWsIM62J&yYlp#?ZAcYdNM-)Y)ghVhYQeT&lqS-Qt7Ws(Q3U;r^SHnH);lh5DJN;CzH(-iZhjJqR|vOU1l%@ zCX;8eD4XMk%eC-$PJF&95LksmyGVq^VkD6`q*9wqrZX5slgX{w%q$kLS`9XvWVd^C zIJ`KW-n(4x-EJQNzy}cY070H%*b{=dLs737<{ggvL=fJ*URR&bw|>7k=K$~^NC3l9 z1QDaC2E$l5t|bUHNir#lL(@WrQL-$X<77O~6$F(iN+d}k%OXXwsH#uXjJobO46kWA zEX!)!ZpU%CuHEx&zHbv7_`@G8z3_sOzx>76OE1~@&wrf!?|+VYtpyH1l=y7mj4xkq z{P+n(M;CyB0mGj^v;YCZ1PT<2i75mG5{!ifB}kAUY-~s{7y<$T7c3YI2Zui{EQ5e2@_%e2f{BV8VnnQ>GM{F{90#Ieiu^7{wC&y{#@btXXqn z!-hLscD&fL=gWZue~z34apnxog^L)jT*YzYCIJ*w0e4=CdGl7mho36`0yGE|s2L1Q zH#nqTL4u4yL75ON*tQTM_Jj&`AWWEZXlPe3Fz$p4_gsVsue51{uw5r7P&;#h&8644 z^2UOjx9?qzwD<1=az6S+(Qp4t{}X`|@tDDf&ki9D_BioE5g-FYmWC9Xv=!2!tCT)t z6-?NwWye`(TypUP*9Q&#+@A--Ji(1YKw3;tF3CYXbXbLX;p~VKZBL9ihvKC;l`6xH zOqm|bkxyHJLMAE7252nbkW3}4WmT%2q+P>2#O@YGV}HXVh|7b=Zg$-eX5YSC2M!dv za;4pa2ZvsJEzcWoZ2Iae2EY7L=eOT({qe^OfBjYOpMP@lUjzRKSl9ysasmuY4GtVu z@ZhP14_^xc1RM||gmhaaL=0mVgb1ZnmmqKzMdGq-S5fS1ngQltQ+OJ^cera)P;z-7x8AV&_ED%EHm0X1szs8dH$y?W{zG|<$jk*+393^i+$ zs9n1>9Xf32)ahE6F3)u9rlm)Z8@+n%=+h@%zkX5%40vqNpd~|w$Qm|m#)uKq88tY6 z#x&r3#s}w_3AczgWlE)4vrf&KBWK<`YmYp#@NaP4v*?D`mMmHRxwQF!62wKQah_pr zxWsXz1fi3n%+WM3!^me@0vtz#7aaXuiey=aqDa*=24*uKi-n-wPTt{?>GoRj`8?Ig z0tE{HjdvX>GT!5!a^t;crqV6WSEb4l)vA4|Mvbm&)p|*tI`h@5H%x;Dz0;z3-{`7X zZy7?oOi{Mdv>b+UB9CVU%X(I_WF)#|FND&2Hbqr12|>C%cI|1f zZ(p7R2P)BXs880;e=p7`>$%)*`QLmO@{9@=mLqB+L>Z(gog5`nzA_i}jOJAm$5+;3 zmKCM4qiArxq89gz&fU1)4YI;00@7Ew9ZUW%hHFprzdy(O)*acmgKLlktYH$jIV%H4 zGt8C9RWZqBVVF$#qU^oztC0yVdU&y0E zH?F&s>$^KzV>&RVXR9q3TUyUabg z5BKAM@1i7yG;;CAj{s0q(?k#b?C~cad5#x_;@J9OOJ3?SX1UOfZs*fQAllX-OMp9N z%9gK4i82+c)M(P8U8f#{hK-rAD3vx$%XV-*Unp_BBLL}3D9NbF#T-YV1b`%rNa9E& zojl5@qK0~!7-Eqo9aii)lleDOX^Q#U=%lAG!_BbUQDS>l*lrCeoJivv^l&?s+D($-UTdsTcuwK{4I@h0ntC=>^Sv&3FNmHiJIdSSnGi;{K zyjeHbh9;WRBKUU+#1zCSGzMS1sMoN|z_7@X({6kC*RK8qVKuS~oR8bwDQ7@SwS>6h zOQvu|Do&{)gjS~dG^@q`^i>q;T)_2i@)DNf4*v|Y_ZUpSl{$wKUBAio-I{x#%|HF< zjIqOYLF9=@a@GYEStQok5?ZhECC4@N5|E3EH|uDm zl?AG_>9OI+m4_9>Rhep`c0%KJ`=!sggQUCdn8zw|JmY`i=_N|M8t5}& z#}h)790mH7#z52iHIW?#hbB_yDxSU7jCs|ZtG{1>38OlaP;y|2VBq*+tLzIGH$aju z9>3mN`?UQNJ$x?OUt%c^60^jCCf3B5)FwLka2QYYrRcWkrf6Ids;Cw%6E!HVh-yN= zIzk(@9c{%A+xo5j*J=TmRDZ3}8mXcC9V)USD*PH3?Fz91SHKEBd)q~R+ktZfEFL>V zC-+3=^%3;iT%LVZU%l14XKQYU?a=B-S=v3>j5WsIyY~9swgRBHfo^Op(D5C@hgO5m z(Te>8G;e>cCmws`p$G1}=dL?$yXlI{E;#R;v(7l}l;e)!u!Hv7XRqDZYO_r?T4#+_ zVy&>uQY@p*_n z%N=__*&bcp>V0^*Pls>;&dljJU8m_JTyNLac|er*dN=N$*l##*L@*@kS{G3geY-Vq z94rlH$$yM{->ZV%voFoHAu^txkDLvlJ=d$}w{xh1cW7qD`&Ff%~aJy@{ ze{D~@f&b`OKG}|2^+tS^o*%C!{_i#Z?*B%w=N>8Ma@}X{kaj#VQP0SLb@^BujnCRj z<}OzljIjQH-o~>$ru|Wjk4Hengph+pZIokM%8~^ulX(OqoRA)HT`TNO^GZ5^LVxk_ zRWIW1zkrKjCO@x*lr!pb^_ECwa+O9qQB>7apQmwBn_f4E&t?P=62UlNd^{pzA(*(> zFfn4OprS>Jk}N@-B=J(rl5Vy%sWPc);4dg1uB%tRw!4NGDY&GE0-ZnrfQ|C z)WWLKtWkq{En3xS*QUdy38VT=>D8srxM5?ubs8{YNRPpbx#b=?+Z$Oqp=|3rSRkz! znjh!5#*Vg@W_Eg(tq~qWe^20zar5ya5MA7T{jfN^tH+l7Tsmh9e)6eI-WEN%&l0go zGJ#aGWgqz)-HwHOo}W+4%z-^C-@II&*6Tep@p0SnZ3`YIe*BWW)u>*JvotI!jdIYR z_XgAPrqs4}$9qxHE-6SWuwMzI(6*Ox!b@2G10?@4jCLZ zC^8~Etbf10eR}um*(22Vf}-NmvKI{#hny~4d_o~aBov~>Ns>vQ5lc3lxbx!8m%kt= zXffg>C{V0KsWKI+RIAaTQL}En`t%z!VcVVq=dRp&?v)?fpO2MPR5chdWW<;ma~3RF zv1Y?gp(4dfmFd)_TaR9S{Xh&DG;G8ow8>*QK~fCM@uDQFnr@gaHoL>=_GcQ@GNLF% zu0nY>)w%UTy&U|?oltX73kOJIiiPUpO582`?6e957XXG9LM~JcH4R;c5+zBNBF%!37>zI5%mX=k<1tUo2Hcv^Tt2z(poZvqI;ZQw!tlsx9#@791;CAC@g1F`Nd zq8WzAQa;MP?}Nw4;Igo?7Amj_T)k54qFd?4-y=czK|-x+&PR?BF*OLyA7P$ z7$ZXLI#t6YcL5<`j_^t4x_RcvUfK2tcK8GFJ=J7r zwqx!Hc>t;xLK(J4UQ6h_DTM()o!~2YfgxG0+hnF_WR^k$8!h;R#fTZOyBMFS0sP_o zxf@+z?LuE4b5zE3P#(P)IF~<}7c*?BS0VEdYmamtt#i|aT)F6oU~7+ArgtIjvT)s; zW)fhEhqd=J0knyjlgNYQL+UND%j8*CikS|WNyG!QMn6Q9hN!m}e(&

A;8>_mO!tKmDQZZYD`4VG$_$j{AO(sHF4}*bM-dV zS}%WGTKC-SYLs7BMa@ABC@SUvczaNsa)vn##it=%fU1E)rkX&lN@g8&S*ur5wp44S z4rN6gOQ)L{k(dvOJzopC1v}h=6dcEpu)tOE~8*=G%>I|Bfwuqk=q0uyE? z_G?)cE0?A`H(21tzl7t8C+Cf;&pi^e@MC?Wo)j9JOcPj$oph~;6Za?*>hW(fJ(L23Dvobl|Mz7+iQ-23?<1% z%|sd2VHB*~IIfj(CWXFKoUH6;i0oqq&uBIB%(JY5QZIF{adduW4bBDU51tcYQtds@ z+GdWeH)_dM?)s|d7sNZ_F|}eYf;~WCRwmWCLD_LAx(%U%q$1vTkX}NNeHoz-Jxr

l*K8_>P7#5$l zs-dj-g2G&s^ZuYuV2_F!bnG}_@a9I_84X4UM3B2y6QU^;G{lA(p8(IkEjl(>H&SvP zs)#s}h-QNnX0gZ|E0~P4e5HXDmGi2Ed^xk$qQd50!N_>fQH*1WSo#TAh#LL;KuH4O zfc{{CC}Tb2GSD1NR*Pw+i$1WI|MlD$k%=0PXyLjj_bHv-5FOB$kM;nh#Kb^At{bBdD_A9;>zyVZ0j*6qqQ{ z#oq#8uTBmWgL<%JMl%6th#XJlyoYmwm0*3Rh#sQIsRCBgvxgd{k{T+`KYJp&Azn>Y ztXMUWO~$H6U31|x(OFg18*`J`YN%x1bLHp_5hBWV>zq5`AfB`s4QU{+IZ@%j=^JsUF3vyR5C3tr6j6I z!mmV(S3X!x&%$hhMQMf-jxYmZ|c}3w4g&Py3;{owx(Vo2_Be3d+ zoplgDknj-9akvt{cC3r4nOI)IHyg*sf_%#CDh6Wq{B$CX2x82MVS#OtK03!Ps`NBb z-~cxXn+kBHG*Fsz4U7Y0Cj+-X;qyMjf*zD#K~cCsHl@vBl)#r)Z`@8&pC7- zcO9K7SXa>bf)G#M(*#U;SmUFqqoE{WBAK$_g3%=~LlwJZWQQUg@2`V#%EU0!F%%k>$Vm~*$S@4$Rj9j!bdvt#7%ezJ?;zpw~F609CgDgj=#3%4B2r1lG&_VhW_xo2Xx^1(){iyRB<96sF?LJXXi#}o}4{#_Nz zg4W1j6RcwyFJ)DG%*}JFs;^|{nvCMe*e<^ zURlDl=Hl>piOnqgg-EAH124was}h8hFFjFkyhP0H+bsco-V_bcOcZ{R4npRJ3WTtT z8WWP?ywym{vWkRhw5sIhqrGNW6|LYTq!6klGDQm}t76qef37M-CLa(JF~N~2a1ZIu zk_%|Q;YwA&n)LOxxKb=zVqK&;iw^CmZ9qm;&+$B>x4PcxXkwwD<3yZt`wgC-lUSwU zAe>UwG%Zrylp_kfx})O!o%@E^DROWuG;oo%urU?u2EUxTbl>zyX`gF~lOCldGs)JpBQkCn+kQ)57Ot=SjO#xbLg7q%KlF z=aj;^2lc>~_mmPpVtasYBN~{0eBVioT}I^r%rjrW{Oo&->a%2Y&i3CXpHqgUYUpy; zIpzU!n(t@WY}8T^MSLY=vz{S%hwdu%L#vZ+FlS!480fzK0E%&M4hG{+8`>A23~n*s zseZJ!7 zXwW)^5r9G?U0KF~Q52pH?|AUbGsPr&F@uo>vMzMfEILbQQx@)z`S$ zg=H8S-eyIs z%?g`pCCS=rFBL4BI4ARtYATvh{{M{T`dW7m3wFRg_O>+|^Br>SmG1w+ZHVZ)_XDBk`iwd3jU(Frj{Z%rmrPSAJ#XYBK zMZ^_li}eU$01dkiyWNe`gMU!P0bw1n(_j^;XXsHYsI}*WGL?G!Fd-0Xa5QwG>R603_U>{VQu(v}17o|^DVXetYO+!I*n+q6rx1B+k1 z_NsDUFPgOlCGmti%v$EnTJMg=sV8m4He%JP+VH3Y<&uaQ6x~*{b3_8o5hJZun`Xk$ zYcmiA%e7`ki`8`NWl=9R)bCJAsTBWH30G?(3zsjeTfDjceEnYS;@pKAb1~*MA<&rg>!el;Kia@HnN?$j8#L8trHVeUGBN!} zH3wLG+0CFL{H9RRNE@Ou5@wJj>87fc(BvKjKDw6PwQ}jXJ^fnSCw(KA>#EaAF%7^qS#-^) zOG7={deagK+7woLOJhV8tQtcQKfUeZPgGxaQkpGlN^uv$+%&d#;hX=|qlLL-Od>Ig z{7Ug+IYU~)7xXBZYrw=DDAyw6h6!S56MEM>NVN_6856=9=K1Zilxf}kZeEsqzf90I z#JDy21r1Yxs&s6r!)`(7jnHnTyqm9u@?LJl+2bXibw0?Xf9d4RQ^fy#`cz;#*Lr(Q z5`o&HByC`HMB3mcDaRW!Sdidd(1?ebK2~;xctLh71g)pj2w! z6sGyWcao(Q3`aYbfLcl4GEVBaU!Er@S?J2dlnn@bWaW zNR(||>0@4E&P-Ej83WV`HJiI$Tg=e?UR1AiGiqVF8ue)c2Z^J%_(3(2Iy>jGN83%= zRqOMseSUa&<^%ue49z2*Dqa@m|@69nW06SdzBzq1Ns2N zUWQVr%6+24({P0qW11t5zm-MTdQUhG#$1mcrg_nnp<<;aw&UT8a)p+}hLm39UezSF5r{i(6c3S_hiOHVji0eQ0IS(jma6z87tQqp| zKlJzg+3ZS@qx0+L$klgFikIwLG5hL&EScxjRlUCW)RN&h|Ddy2-vsOZG&%}hXZKzX zFUe&Zb7q_q#vK6(6eoX+OJ9U4&|C*96#k#jI~W52vx4}fnrsqM!WlDa9|{~|q^lUM z@jl?k5@c?A;ee$9e2J_`Ue08>bMK$L?YF{CAox*m7#{fg@lw!DL^_3vZkul|3r2?8 zv6n!}HH_*KP?L@GemCeKN-dyiV{W?;It%U&B~|ve52~hziZpZX!h zhnYc2sZg%&8JUAuAIs_pXgXRU3@c;ghb<$Yt~|7V=Pz|01Z>i-L2eFEiar0RU3fn8 z2u95%h_1f9K(kfZpSF@D<%=pW?I)MF~b4Ty1*uLGK7-u}S=Et7iege{qA2WPWm-O^Zt+k9Y9+7ri&silj$ zz6~`uYMzyCm94qN2zyhq5{|Ow4v5la@T#3Nd%pc0lzM)jufY<%P|bs_sECP~k5|oI zT~)$&@zR~%W+#pB@(e`zy~pE>DCex8MKlRWQgmMy9J+tAm5RGPkQSCYp*|p?5yV1E z*q}hgD&PWg?a&OvjbIbE(J#xfM_8QkfzZ%{lScY=388d(daw>td z+E&Zz?9j!VIcF7T)T!_C0?Yw{utXIMgoMvC-9$`P6Cs~q&U%2LL!hB)hQSrDRvq)v zhlRqi(5jF@1kZESjq1C)C(IYs^-7r)gd*r3gxC-drKmD6_xvNos0y7|!1?!TV*J?a zMn!2lT+Hp;dQSUh&MgvT>5F1YO-;d{6MU54W5IUDL5eKsCKB`Y1sc5raglH=SP`up(*aD&y zN!@P5w3>J}!?urkp6mYTTVRk*u%P2Dj9tj(vNtxPa6NqV=pbwOnjvS977td{|1+@z zQIQBoYV@R=4#HA0wK{8o=0(PARz?ZbIyNy55Zm7KBySuLCoxjHalLlUWo1ic@GT^* zu$j)s1te1p)dZ4OD^mV1XTI!h%06Dhf_980*T{atL zhD!cejaWZ@|CjJ^x%c(5*jM(8)6S1i_=6O5bkZN!ygxMb;;)EhFvH4l1~{b@a8MtV zk&mJ(DX~IXDX@cuL4JL*2JS&Vy1t?mR8yxZ1b85~pUh-|PxAP-3iH4V@e_9c~%L9U_eN-JX$gNu7eJJuXV(srPff-%Xy>XhKgces^6Vh1n|{>;~c zLD=(^I&BwQiQ|CjR7t1Z$olLEAuqDwWoNb785jlEF%Dp>8acd1hWwaNFmhJ%Zuhp2 z;h#k5V?h)6)7tn5#3_~y)qF}eB`T(i!J)MrNO8eHULh82^>?ExS26> zB_JzX4ez3~2*1Sb)wVuWYYm=BCKgf6?}>C^{!M&&>@OTIW+(=EHEwX9{LrNN{|4Do z+vqzBd$Tck$=>XYXA(=tgWI(%Uof#1MmPiL+P12v`sINn)nGlK5N_?z!*54vps8t5 zmjP&oC>4g`AxF?XpHGySfs{0V)Mvjr#d*40m0f!6Inh?VrIlhw@lROljU)+eA~D23 z279(-sdCLZf^dQN(Mt%_HX*{oFd8-jXZ{_37nPtA?gY1Hj{m}!BcW`sqt^~(74UQ76vfD99N7a%O z+BM^>Z(@H^b>h3@>XqHx8Pl&(5(D8}PkgfqVwXda(X*+ZGPuu!P{^EBRn*f>zpZSm zrT)PYZlmSW7Ggh1S5l9ivWH>IZ}YC=_43WN(-Sim=BzO1(PHecxC*(O%>3J`c};r^ zOV}u(P-I}O`gf9&bJc&k@PwhgmFZpww(Y?@KEdr1;M9Xu^M|-iPKrOl?RJ_4-9JES z14iB0iEBoQywvJN9`C{U^IJduaeNZ9E*8MnA@h*8U-q(dL;&L^S{iYSMCP3#ojW(m0Ayn$E~{b&*L0lQOS3WRrfxIjAqEn(1IQL~n^xQ5|8h7Fw2gfIf8t%T(S zX;wNVpHJ1gmGqPUs!*CIK>ef3Q(EKaSRsVfwRVlh28w)OdWqy^CiT2PH2QDeAP$vfK^|kvzT2-Hn2<<(N zh90%AffHn@FTpm5YZlefV}FRN+^1X_P~kDJ_b{Z{K1$hVKPM5k`4?0<52uqM7+w?` z{#^?6vaTC+j-1)7t1OKIDw-HR$?XX2rL^B7oC*BwTc%|$SgI56D@@C^YqxOs)fW;n zoYy2}y+Jj2UnPJkydgPf!YW6-W7s7Wb$vuQ#`%cTTBIRB{1(C<;JX_z<|UAvM5#9& z7?2=m2&rOK^V5WfdqfQ`BYIwo>1Am%)GE()0~!lqYy|0(Bj3AA2#|%fLVO@sT z6rviKbxYy%rPh0zPRiylOBd@pPy%7}JZGTD2R-e57IhQf472y}Ox)6rW!wHW^cBSk zYLn@W+(X^*31zf$Gml^6c6|&Pi|JuKMe~9(p7N8J{qnF;)K|)*-^`4*keN>xoCc8T zv3d9Rsr2Y!{-E^Ri`&uJPb^9YlyPN!L92ssefz(p=v}7YoC|I#yN6q91?av85}c#` zh!ZQPHuO-V5HN7#MA~KrBU=a4Ui$n_H0c1~+KX#cb}0I`SUmMj%`!1iVy@Y285c+( zLxdK15=?9cXD1Dt>Q?|V=T=drU+3YgaZY}zijV8VgoqtAjc;Q1Xbl9->Yp^U zNvoKw1t67N-izvLhdc*;x11 zkFozB@NeO77>JLF5PH!_UXvH|JuE?-oEAr%WKEN#F1uYeo`D?3q^3X7HD~Qie{=W#0kIhL-j^R zT~Z3HvR$AXD&+E% zLUzG*Xs$QdjCH+GDZuEUI|>}Kf=9&vS5*vruRxrd!V3lg-hxSKqA|gRSyU=g`C%7) zC>eoo+_NGNTU0@*(Eiq7>`c}>9Yk0HHSK5%NOt=NeLDFZdPFH#n5^2VB>zN_7~oc7?eD1F+1d`|NO<7*BHOFijCJc^TOQ#B;n*pxoeZ zXZ~%pkS0jMNWgH1^v58Tb0*+>6xgW7ciJtY84)xf3c?c8j)0+b zO&R@yNk`Hvb3B-;PsvBJKKUnmR9x($n1HKM1xJA4a(oMC`P_Lr13o2zs7|;e#RBp= znsQcPDkgKlBd7vhSX_5d&8m;l%Sjc24vu(tM&MW!JWf!3Q@MfHpmqs|iA*SV)-caK zs2U!fsVk_vN(U@KY=eNKsxb=^)w~y=2Vok-i(LK9gX&HyQesAbNo>Nv(BSum5yvE8 zt?G+}beVEdI2`AG+!o)5Z&i_IU#&ryeW;Lr4>*Uuw5}V(5Q;A0yOe03BLMxY`V=Mh zD^*ZpCPhFMpvgFAd+$68qS5y?g4RgfDa|N-dJ)=`n5x-?w>H2E`B8#_AzAm3s30IO zEOoQr$e2%aP4EFy(>g1kwJnvlgI?jBgM7x;&bfuEQtDX*=9}y5a{g1gP8|!OfFnQ( za5?-3<;SZjshnDkW34*D`kdn1-4Xq8eiQVL((BIe^`V(<@ zwTBw>v_SltR}cTYAO(Z_ZMS9yy$*#oIsJ zz1(eJR21~7ERWZW0@ z-&W9?(RShEmpbS)>%ye~6`zqefSxeS(61Tv=ebG@3ge$7z%8B@T0yn5R^<& zuPt)l?g?S*C1LbQ24Vq;lh|gcgz4vN#LK{hAbf?2Y01Wf^%5TW({S0}49dnf16Ynf z$b0%0#6FzNaA?-)qW^PKv=H*fiSl7* z3|D3JLBW;HLNH?^lD_y#&e?j!g&*V>rIi0-iiPhmi*%`BA$LBHptSXyXYJD$jz?lg zReJz*|HGCdPY(U9QZEmW_XoCTYI#w|NRt+0jd&^99^jyP3D^|>w_hGX>1{*&o6IME zk%kURIh5xfBe+3Dzu5vh24^(*w=PNZ#K&#)Xb9dM`}~?bB(qg4YNHN1=;_d3veO75 z$_F}+Fj);FS5ugP&g9L6VkR4am-rCkvz8i6BW8iLClItJLTkCGMJiRN&P%27Y8B#} zM9!08$<8nTrfZmDeKviR?@{R|kg`S_kNHLmK>9oM>7O6j!*d+5^y2EpqmJaa0c0T^1o>HajBg6jM< zXQz;6oGx&~aNYdeD)Or<{Ui7))Ba?scx;UhfiUq!LpP`W zyz{obOOI-I%~8|f_DOOPtCBPbk?7+WMN@8KxJ;lMFy=O2H8iFGfoPk zIJ=KiM#c9aY~}n%pmntff>~51EM#C%pQF7~a3-QQ*+YJ$hpmU{u-M&}nQ_-WdZThk zmrqrYkaaM)m5CT=z#JJ!7)T%Nd}v0Wyi1sEAl_;5_+4cW*5(%7u2lx;b2aD=JZ~6T zhYhIqquC3RV>vVLasXH0D&u4?|2;Vt{d++wQSAfoP*X?7baDY0$f@mohaP>GQ8Ef< zz{8};Rno>i72uNzhQ#)tRn?y5FczUP2sZ_bjBDDPnvst|pS`)EKzny{z*^%VY#$)o zPx+9V+rwep$TGs+WLQxxZ=8ps` zY#aC>VXlc?9e{wJGBF$0c2tZoh-D3i^pck4a!EhEOd5;&Bk4$T+N{_p0W_ItMK)7c zxLuhjEWo4)>=ih7Qvg;JhzZS^y*wUbe$g!AJ!{SFt*i=h_)oAXQjp6(_fKv?FYxe* z*qtH>giK3AU7-g^s8V+h)alYls+--|26o=O5S@3ZXx$dtA8H}%dgqWEFvEjS_IUON zo00ZR@*1FO=!DPE1*n>2rbX{KOJ2QIjXAOKW;Om-oxvgSeGH_FL`8BGbPx?wwm_JS zmoCpwUNW~d`RtX2M(^V4yZ0OaUG}MWoc(}%x(w2BA#KMwR^l`YKhtfA@cGXwo!7*! zq^IOTVarBNtUQ#|_576?ZmDNkB@;D)AXHI)old-&tKnyYzLo!z*hl|gm%cFmh|-qd zW7><&dn0+QwD6#EsasBgRJf!<8UU5SS3)(AIia(!tWqNpe9U|) zlI;B=?lYyQ=5dYYs~}IrgqRl6WL5G*ln2TkW#|RJ>WA8C)qnTzEbqh1;t#ZJ3o?dC zZQKPiJ-xgt5>-d{w4eruI*pA{Y{3Fb^;<)y7Yio^T}eoW$SJqDUtK>W?S>wR7_M<^6S@ES#X;two1u70VamU7b>DCW?D}A3ETh~>H^$$P}F!rUT`(*a*`Z-K=(8@YqF+r2yli_W9K=Ba|2oX9&@KDF zp8kkW05QwPK**Y zCE!IEqK6n(QOL>3qYswLEd3i)je77%Qd$=7tmC|zFtM1fk4!Gc6D-C>!4TI0A-71AQ9&{us~Fw z$*ZGH?9Qs>8nnlq*c>4WQugQ3*3wx9n&-_2)#DZ1FZ{f7n|+0XGVkU*HU*o7{C(<9 z?cCn$Q->R3-KRQFfN?-J&Pj1i;7`oz6 zw!dY-CeoM4>M7NLE-r}zds@$Iu@*UO_UtO{WB1*g=y}07_|A_ormr{J-ub6QFn&ys zO12*7wWVN0Sd{#T@jef`>+{RTdb66IImh&l;tSXm_R=8j$|fexv(qjPXHvq3h9*nnzOJx9DopT?ynKF* zUC(^*aQ5L~_g73H5cUHH1wid7GSu{9S?d{n$2WbfmM^>o?xfaiG;93dVFXPX#rt%t zL}y9NS=unt1D_;@YdTx?p{jdC>8zfQ8|VKljF5&+3D$tuqmmu?m)LlUEyj5Mhrs`5 zE;)P-Ptqwn3ssY*6X_bmP&|K(oM!N4PVJtSreLr?370v{o335qM2~6)`TtG05el5$_BTzYiv_kaEXyu#_tFY;|F@35Oif&Y7Xxx)oq5~Pl9Xzh6B{Yq~SY7>85 z7{e37W3I+QP>KI727v!3_tOkVKd?!DwdzAs`&Inu0;}zXA~E_;k81bYYFh1;pwg{^ zG*Jm%EvQ(f@4sJ&ChfV<;q0ebWh%9(yjS5^maCdGwPugR$#jnFKk zSwwDdR0tNxA8;F$ogqXijz+C!MLWVd?}v%9+t@UoxS%TX=vQBqBm#XoWE-Ba;tyci zthAgD#fvn69JHP{ciw#Cu5ilKnuPD481~zd-|%VQ7diej3EEn9K0eCBfmremv=CsG z$nu5NSnvgohOaJQa}*K_tINeXn7c|4Hs*wduT8Z3#atJls6zjc<%i$Q7%7&1ua1-V`_ z_&oC{C?bym1_wL8f}%A%5YTH>fpO)`hnjL<*gZp&WNA**qVfxe-VvdIiN!^3cOd{w z(V|CrZ4U$pKQs&O(T9aMs3fKds|jS;iJ1E$_LdBO7p;M-V^TNu2p_krp>HsEVv2I9 zXpTl(J9VlgYCuEttD68EAEC3MEQ&$gPK3f-KE9Zu9BZA|oFleGE+&Ebo&D@R{wnZ` zgHlj-zk-wX>(7@t(nGuc3CJc-j{Mg7+hP9AFl8XURiy8ekbW3MPKh|ird}%65D-Ke z4M9|e_q2NgWx3)Tl)??JsT+2qs2wWMm>p@1^##_qzhCbr`((PP1(hO*&U)b8p`7T= zzk*e!T*?+Dk8=4vz)i5Wm{(QJ^X(l-p(-vhZ-u1ZaO|A^kOVe+ZeDqBmlMo3AmyPm zce;A!Tepl(XH%C_2+<-}!#+R(hDo0X0S-ZFUFIm|sV_(waD|1GWFPuP%$(dC&Cc|B zPVdp#jmU&>6DO!u5VShpu1g!}4dMuoGb?`rl2*LBxapmL%5R9<&KLP-Tj*rO4?6o< zZ@s{2g8di_8QUF8{I;59D(Q9Pq*nf}t+PJ?H4>ij>;S#6(hSgLxDDz8)FjF8gu4$dSSz3NP@MW9T$DLhw!qJU&x~b~NRQTgSMXt~hp} zskcH3KpP4ae!6Tdfns2wVo)WYrqx7m`}UVvDImIqJ%5dbxX=1jZN_V5eYA7tR=4CZ zq`Ak~ebt6Q;p^FX^+)3NUlMJ#`vjx5X7sJW&t_*V-p}p2-kRh2ajuWfFr>c}EU)5z zv(d=qBGn7@R2NxpxUS0Z|I>&qOL|s?N(SH?CM<$^LwlzM-O~tK{GDF1 z@cK{PuC6)r7frW*F6OHZumW2;CS)RgYF7;IKgc_l)wP|@KkInr{%Ab#Lx31$BWndE za#`4uQ=(suvQUs2aCvuso1?pvrCVTsz?9ZC3C0j02ueomhL^?fmK;u>v2zIfL}>>( zyTjLoL)|vHB(0k%1M{;Wmr4npNeDJMUOy#%MDooa1;3mR-fZ7;_-8Elo|KMcaJQk? zMc;qoD&7}l`cP7^sRt_1?l1I7c<;-L(?4Q@zv9tS^3M#M6vr;ErK*5Tv|>;Fh{9K1 zC`yqfq)@%mTCxZYUo;n=<;X}XPUT~zABkfQQK@}{wO0@mjWI@mvJjhkie3=G)Y89$=C;vr~Pi78D@6QJz z$c!0bo+bGsWJ=?SPmmfw@mM04ANv(kSU}N*Z#+#XgK2{P7Q9t?4{sOlp>i=puHD

sT5+C~)5bw)yD`@rz&ACITGUr^7Yy~hpqhE?+sqcF(I9WPH3J|jpk;*#j z<6=;#BQ)x-kHBmUot0jW^lQrhLH#f(eVY9tNX77r-MBi;U0*AF9G)sU!*=)(=vb$K z96^ORsK>0fp!0~r-yWnF>C54WFMQ4o*JBFQZT8BnwdHOD+6G>DJam0ux(xRUAbDrt ziiDNEnGXDXQep)+HwuaoHSHP9kG;a!D^i(XxaP=BX(xYZOlJGZ>C?$^2^z=ZCf9rV zkgENeN~P)*FL%+wG@29eVjHuOtu-0R8_Ekm+z&BM+^QdX2njBB*Pd9 ziu5qdC@##yrY5H;4WM87DZ>K6OaW;GNqVIiO_e5H2X2wBMs-dNeOlU=D*MwRf7U+- zmvu<0i6H-`3Yr1J{4RKt^Pt7b!A%nGooP*asx6;5Y%-JM6@_0 z{J``R*Zm&tdbys)DJiJu@GDNeA-9Emu6guG@F>CB@L$~OXo;_ijZ zOf;Gk+d%zr3{a!`iU>kwC4aR3=#$&I&7^FP^UR26`bl^`Ap}~XVDQadL~4q_JU{S= zO|oW?Na>LCO?3)|Dj_#>%D3F16XA&$B!T^C>66hO)8fJ3ba{Ho|ND1Nc}}hx&}Jv3 zv)k`uCbRoF6bMGA$(xC}oCG@**VdfPCMJw63lnDbQ!A##hfjWE)cydVV#nmsmo$#} z;V#VLQSW5(F;oU$?!pgcvJZ?u?#C_~%$5`^Cjv`!f-VincP`xt^ieN*C3Wa)x0wBR z5c3m!%EB`if1vo^9Ue3AoP}Ekh+M-m=b`gtOwPyXc#@8vPUJ*F&c@_)g1su`|MM-6 z`PLRRFkqs2isE=#oJy4~X)G!$`(IiqyS~Jpi(<(U)bYssOObNWfYzV{FnI$L+Gg#H z5N3roWxmF;%X{7lKyrA==q`6qJ;2nRQNI*Acpl91^?lo{2~+Ls{|0*iaxHmWJBw0X zk7cz*|FxE8!JRI{XB2HAor{rdLH5(}lh0UO3fHdkr=ZV?f3kA$iI+HH@scfnJACwL z(#x*j`ZgQB|FoUoIfvGbfHFs^pvL;B!L`k^i=ZDc4@sa-3&uX)(K7>2?~$Y8Aw0YT z<-EVp;47my*F1F+O`RY1oInmdt7hqCw5F10$0ofMV!Pq>0J*HpGZTOl(}yS9#876z z!6?*D{H>@f8_Qx`#-%lQJq z05kR4)AP?ljtU32X8QrK`m#`ZUcsV~z;UUK(EdXwi%rFC1;wQIC*W|F18YXzB?=wc zj4A)sG9<0d{Iq>MT17jZ1T55QO}29W>#y-)@+TiFVY;t5M!H&Nac+{z_;B+z!dMfK zR;b1TzOPYScyO&5rG!O#b}@mBr(={26PUB5K=l;mlUQAwW^0RPoF|}EFfazm#&ymK zpiXKE3xI?2WGx7FFV=*8?9=FAghg^P&(}|(NT&6OAEQIKZx}Xwp(gA!CCPQD#6@~G zrB@DjT6>Os*T0LBs3Oqx&Hz_vGv|kF72RRT`!o;~x*-prgOeORFNp~tJC!iY{6+m>{6wx9MDYqs{)L4H z_BpwHlH^B-Vi}Cazf3{#_=i7$w?v(>xz!!b=Se3Elwi0oux-xY<4kbKo zv54<{&s)i3ekV1MueX20Kl#iE|v{jm|woWH?>YNN%;UD{)f zmMY)gJ5Xp)Ea0dYECVsqMgswCHIqafn4}+Pg%?gt(F zk5Vb|HTr=xNg6l8H-5%E9O!%LC>3Q#Ep<_tiJPoC2tOUPW5eIuZ7%Nw*qh2?=4qvm zeV-;&Lk90${9DPlXk0DV2$c|Lu>SEom$;mKV4mMC%BSL6Rxk5^0{->Tk+J|aSl89~ zuW!CHggf6F4^eVrPoK0qvs$tdlX$|&hg)>ImyB0825rM+bRlgSMtOJgy!G7IPTu)n z2R{i;4@4T2Qp35^DgP5?}|kY-t zU;i=u=Fh?9iO;23uKFwq=rS>74;rO-a<*8WOrFfy#=CrDn7oe^<47eg{>@#9=WP6z zCQ#58%Ut8S>rN*nmdZqQe(5ziOrJz&Y@Kw!eyV7ab_fJtt4C0?6!>G*NEQ~+f3|`3 zo)JSP4YSE%1_yWT_fDxvGdeX780L_DO}g)a(koB!AX!i|=yjlVB`+e?f|o$XP8W+{ zii7Au(+na@A(EGWX_e2pg}fgWyj-9Z=hXIPULF4_F}B_D!KD-LILQ<)P0seSMKErE zW&_OT z@Q-*cE$m9F?gcB3?W6FYVXd#Qg{M{tF0D+LIsgAmPqh_t5t1K;lq1EElFH~J(c#_T zD#k{D0}Ekimz;pYx$)Q!Sso0QvdR}i@W=|d;5s-4Q)p;}a@?6UKxrn|!$lE$euZ0s zfTS476Tp-vJ?2Vfv#&!SAv z_Myt)sRv%5Ym1!>?W%A)$Jp*rZE;|vZK^=I)SRYh>z2GKhJ>$0z9!eh!>lq2GOTez zQyGt^ss8s6biDe9=8*49EYh9{Y&epa>o%YEUtp+O;em)m8pbySi`{tT) zocimstmI-}W`O>_`mb3hU}CLVcr%du@(F6p`n2yLIoF^N7y;Ifh)gjg!^t#2GOWpY z|8&8K*F-aM=}Hx8I+NOl)j%u*O~xm^vxf2zM@1qE3jvL*oEHKk*ecsr3sS+>8wN zSlzrV(!o|AcCY4>rTYu(2{GRag2{h>2}RthHzPssa+FTDU*99Yup(#G4hJBtpe#9;30YRN z`pLLVUTn&tshnEY5}7_+P5-Qaw&hjdF?RXkviw1WL(GfVLf+Na*=O*nwR-^Qz)xl| zH3zymp!^c~k)>HQr~q(d@f)l=YhAKZ#pBZ)Mdft#RSn11DYjUwg$+iEy0;n*qr5_@(7_a)qNO#$hG#bloO}du5!c|-`6>cIxV*kwW#uf)AYUp#Fw~%! zp(_y30|x@=4+?8#=Wl69)chMyGd`=OgmFI#xhtA^LaL6dPjR4yX;TD8GdmS)p3KgK zYNoD8k@z3Uc#zNXt)=~nD%suskf8h^zSjBP zBpg_B-~TATHIo&?e=`u4PJn3SwxjMy+xjQ?QL%IA# z{wp`kOJZtV&vkFShBEn7Tidv{?jvRY>@6S=ftparKtI@-I6Ze=IyfYfz$1o&Ev&Kyy3rOIuPy)C zx>5dVGAfiOhFHBD?6ul0uh>_ENK(0VvR*~WHuO>y{bTlE3z1mfkW9r4BLqK`5O91? zfLHj!p5DdNCvp2^pu-Mz3VW2m#tkxq@ZF!%pf$Nu#Ym#XucZyn`V7Sf=aU4Vzne%m zcj3|TQ(s=IgnCtOL@f0^cz2R5xi8N!9nqf6gKX|uv({p=VrnOU(ugKg8g^<=FYlb^ z+siw}1z;Z6rGZxoVo=K9YZRdo75jAhQNDQlf|+vt8GX78*`7d{KjVI&nSb&a1-+O^&wHx#l9n6BJHLKv z+kR(3TeO^=n1g*p;ps4~$kKd_FveF_{gZ96xY7@I7@jV<^Ll=d9EL9OyI?kchlWO7 z;~TQ?bgE=y;S0snVYT)cpSsa&T-v19z(w zYpPq4;9lkkFr^*dmYNt(0Z>S%n)kF^gEr;)!%Qwn=LJ9G45eI<#6+0O50MRVQD2Jx z%zYF+EBW5Sj=W&Py}5f6`0C5vA?+OJAO7;+ zb9+nrb64yMw_1*nNCG-}YJXFNq~ zh8FqRTau<#;fsZ&(E^Ag(|}FIUvqEuCQ*0CV%blJXqw!7?V=FdUp#K>$O+hdEnOXH_nRn{CFC_V%|}Io7u|?!mg06 zJ{1W)e1iZ;m4uN+xPXFdbFU<m0Fi0(S0c#_O5zQ--BZPvb>^#Sxl%7X_jOLU2EKKIG5&jfa zb2f^{{x5)$UkPO*F72o3Y&MUKbWr&gm_T7KUnDzgZu;jGf~oUtL4}xjevxuK<|G-1 z2D9i#-VPtk0JZoOfGCQ`8}aRKJ=Q;x)o{i9`xu-P;p-l7Mt0f`f=b`C7rP3BA+x? z3-+@GEWLjxu>>vFiRD4Y4Lk`O2AcYvDYrt8ufb`HjWJ3^oBL)H80-aGff(tw{n>Pa z67oChL>(%I`k{9C(f7@w_xM>W(99+!mJl|;nq~pa@}=(xion|O3yH20!FO#+ zI)0|Lh({up%-j`m8#OWt?XBP558W&BRioo@+j8x98+8ss0#uE50+xJ;N8R~fIrGXE zoG0Mx>zqK(T55Tw+Mr_6i2&FP&aUnOQIg0#?k4x!ey0%)Rw!PMck6xM6?DMd^YN-E zuCfdrG^>2@0(uMTTV0g!4$028tR|FNbpjonJt2}=yF6=~lta-^P91h>Y{`IWqcJn- zSQaT}>1X4EurTwLXa;(}*v{rnSm%`NS_Y>v4*eOyAef%USrml7P)p>O*@B44?)ZLD z&p96W`4KRoJhFWZ^G@(mhjJd5e@n)nEv1#G@6@alVQKTPDk0Y*jS$&Q$ok;LP>1e= zcf~?I+W1q+Ff!D7FB<1}&STvs+vI|1RK2A9KhHSfg6^+r)rq*NZzA1mA(th^q^-b* z#i}vD5_M&hti21_{^;D<%EV5%>tHzldi{!p7J{+%{u33yUit_2OXv9Z*3v^@qVWns zO9A01tdzV-8t;{Laa@kJ6Csq+UPG zV!D~6SHGLgXwZIC2}Vkn?K^lPM#}fR^sEesdAyD?N0q-$-J*! zcBP3CCf2XHD~n-IUR3`~(wT*BfI5-%BPyX0trzlw52U}tr!qwSL>1WR#ZQb{+cPkC|>`(BlFC^VaKlWaE+?gUS%ptC9 zP`yMY*18>ez6{PXm^tq582ad(De{FCMK!sbT2PZ|%Hd;A{9AG##r(Z4bKhQh=UtnO zP%QHH_jPPZ#O$qH)+wr?>NhwIEm6-Vv>w^#I3v|Q7dVDXyAC;v^aKv_;Tf^@BP`rJ zN#cQ_w4frcoEMZ?+$?Ks12g^ny7k3(aa-6xOywe0HVXtMT%YgwC%K`Z9hUxmU_t8B z2{H)b^;{FglNz-fU}bECkX9ZZzEujeDUyPO`$MhUGLx)C<8JSk6I0kKj>r28p{yhg zZNYw~GACAmbEwtqBG`HA?)n)u>$@co5U<`G57_Cbg)dZcFg#d+JFz@OJq{xpKQ5i}xGkI++i#RrArshKP?5rCa$4E&$^t zf}zSPkz7*7e!QUj7)^xoCpc<{5}X#01?xA@eujxNSKK&Tzk}>Pd<&)Xjl1PZkD}9Q zXCSQ`4v4JpO!+|Pqj9x|oOJSLnp=~J3=Lk#HLLosFIl>xT^4Ue1 z!TNNn5TO!4AJY2M=$zb(%C&Iv%D}=VynbOw9Rx??=|fRJ#n{~w=z;^H)OtkD>>+sF zjm(6KT8Wfye(QsGaXULeHqSMH_O4ehojQH-^2x1RaIVDa^u>or*&;CDSA$|)5<1~{ zNd@KnUbv*ZDYv}9Zm`2?#q7|UE6L|j3E!CN=;x1-9{dKkU(D(8&nTUIZ+gezQ~fbX z!($Nh$yu4_)&Lhf@p*=Gp)(1HTO346Z(T}Z;Y3wxAQ#Kevp{ugj61d>xVsXnDSnpe zpnxknl$C$zO`(2{@nTcoH>9XcM>uxow4LoBrF>v!Xk^bc{4^im&4a;x>#1iH?a761 z-%zft#b%x(Ip~_h4+lL+W;t z3gzVaQ#lS-&C(qBKpZW)sdplj+@1=UEgOwtyEETMV|ar z=I)q}Klw$!H27l1`wqoyEVUNj_2tik*R#bYy=#KHn~t)2pW_)zRAJ}bNX9DMOd*z8 zvS?mXx znc?qWDku{Svp)!p{76Y&s%BFCl_p85DyidggQh#OBL^m1_hejU^c)`ex7o9M%IVtQ zF&D$(pL$=uC^-};$8Aoc!uPzgH83$(fO0|BE7PR{m2Nh#@;cs?{@S8kDC?M* zh|6qGQ{C2ziHHhRYT%;5^L$wk+ji9j&T;Ps5H(UurC6VcLc($8Ld4-E$Ms#}!bH$D zQy&d-R4GfXsp{CtPDWcpMM|Pf{;g;^4}wQg_Q|PsWwc|ia?2aiVWB_u3RzEB#Ckk; zt_bB~u^~M#9FZ??c{t1?W}5a_m-SL7%R0_dpCCJW8mX1Hy1>2TagXm&X6nlk`)wRg zm7t=J8tBICuNU(;&0`6j^sN5jZBM&_VjY&YEL>xLaO?D8ua|X&X(opb0qhuK+<9S* zHyA513=?oe2hh9~;z<)t^8glDkn!+;Dr%S+hqI8r^JaDwNMp8BHIF2%N+u;CpRsU) z!9BFj)f7vF6W)Oi5p3@~BK|o1EC5$CO6`!o@2FiJ0t_i_pmretP|t2acSC|3Rd^ER zq|}Y}W!OKx4Q##LkdN@|;J`|=UXMa`j&(sey%SZgBFxV!;Y0~4*}}l)&7iC&wD{%E z>^hT_qil0Squ%UGXBF!ECJ13kn}q9n#XbzV;R)lgvqr#e?u26nK#a@?{Q7#(GYGtT zqpbn;wOQRr{xRNlvF<2vZWxVi*iWoIFLmoqbHAYN;y1X4X^#w_A-CG9FSO#9KwNUA z;cW`a*+2kE%IXq+LQ-N%QF+ly#dZUtaWzo8v35JG4c+JrSibB_wF!qK&L()l(!IP< zl)F?kYk(+i(0u%bPZY&R;b@DZkSgKX_^CjXR_z6c;I|b)aWyXA@b> z^6!rT9C>$(=pq_KUZ@2l6^EVlQ&btL!$1$zsFNYu@8L8k6B>!`efj_6D> z==e!Pd~Hs(9B6DjYyl})Cpgil5~YYY;aq1C%RucX31?b}BfpgCB=a4CHMQ0{R8nIl z6s?pCtqJKfV3_K$4a~|@gAleyfuOt)k^Wlwx}@%aX_IbxNT+Ol^B*+f0m&8-O>$W8 zek%t#crkdPo!{(#FZQx{E41Cpto6Q={atxGvOdUO7^l{H|FYYBgq`=zz)t5WsP##D zx4E-1Ozd^jBgSBwsH(J#_r9Ov?#t^rdPmaZ6@N=*d3DCIgPMI#RoI!RS-MjO40 zI&uEo2glBw4PhJZbjkt#{@tKzP|S!sSQW3&>4LIcd4<5I&?@g*Pm`fXz`2D29@!bJ zuvOE&RN5&R9=c<0MKLH|<@HQeL?sqmrp&)R(3gz0`S{Zli2z1}p(E0@ejP8t7Pmk7 z#J4i+x;2@#f}^YQ6W^K)9(cA~!IsCe$FkX9{>Bd=hOo9uWvOH;^F4laTPrt~nd&1H zTTykZqOQrtoM@z8OK z@q=ewO+ubS0ujGnXWv!2H~OsA4Y>1+LMfDpG879&fb%te`j^!;bQj zWrCYEo~S5CuQuL1_iDaNhLk{cfLA`+na5sx2ko2^Zjz3`;4BYUAzvK5CK8^3B5!X8+=M9nWYtp0b8(qnl3P*tvDDZe9}_c zhpm-p`_gKDgNj6>(%Q&!Y3X;Y$D~A|(ZoeUb9u&24* z#07YvSMl3z*{8jqf|}KDcSMO@^%fD_)|ji{agBXFsj*@fA_N!rslWAD8$fOjhfY2g zcs0)(F4TP2NENlq6mECeu&L*xmNY1P>BPkgiNpovEpC$C87JSAyfY=}Stvy|29s^t z&VfJ)ak*AggqqR@S|t?G;;z+19r6CS^!W1NcIu8R`)A*qh$H^=Ucu@bxZ3{;zF)`SE(^B&+sp!E zxyx_}`++@Jng`LF&_=N~?7ZKhroihz)-lu-|vbv48u}6Hd@4X`h#fF>hiXRb!`O z{~eU%VUMYzYIuTqR1{uQ5wPAgf>Uo&*K$xK=R__G-+ituK+OF17`EqZF;YSr`H zt5NmPDVn?(-{A7kN~t1u&@qNy+1EVqar>o4{bm{-7_oBo6zA%5MXWU17z#Er_g7gY(btgBukk$eRfY+6K>O&c~tSh8iuENn0hmWqS(g{>OQs*yH6jfq$P48p?91sAbkOB<zy43T5bpr)3#bQMZbQo_S;ukmXtG0<%P*~vq;kEcM+81 zGQZYLN7o=x)x2W5$XtDkn)mAq}GC5(R zeJ9US^+p}B)T_jL!Ip3h1I2r1zCcNB)Ex3a27g4S(@fbxkf9NVHta8>KF*S2!o7f1 zA65PwRjCa)RXiH@@tNOjYAv&!-s+i-y=F+2&#l{18>Dtf!i?fKAW6 z?R;X&lyz7HR`48JW6^jF9SnEN?8S4QC9BH8vh=~dzLo4j07HF=rFfUwDG|yw7AHE*2Gj~*rB~2`s zI29H54J*=zeJEhmOxtJ}LEBkEAbY9pH#e%gV+suo`Sc$+L zv~ESj$L_(%d6ebrZb|1Qm7<;5>rNH9R0v7i#Of9M$pQ{vE>uMUWpyhDyMM1fb*z1P zO48}$9*)+9vfzUGOtfaShNi|Sj!T&%p$jW0RSTW%9j2zeV(rU!3fSj<`?v@D0&9`b zg1xAekd(Kn7gtvn=9ZRcRhYkghOh$AX!DYaWQ{a-SuHMEzlIx*JejrMlC+GrfGFO7 zNbuA&Wrsol2bm3OXJqwOqj{^Xpv_{D=$ADQ2m>kTxYrNW)vTH!T#n2OI^HXqp8}k; zJkJy$GpGXoejNF;)~rC4CRSFl?-xoaVx9-H{R6c6@f?}eQ=SMAf(R9nk-NZ3rD+<{ zrn|O}-lH^qxbMF3Gp*IzPPfRPq-o9~RXWYN4Aq!0Hqs9KbEiv8IS?FHa$SzP_oa1J zs|Lx1P%a4w;DCHXptW!yuwv|Cl0gdzgPoG7|3T%cvy%WppCsygzaZcASMkelfT-`t zq|>1Xp7D6=`ICskW0Ngfoy}R$BXEbiC8iUu!_2?v3eC~ZQY#=Xb#;l81s69wr8#=rnEAsAEQOm1J%4$|v3ReukQ`J)30k~6p zMW~#u?R1%6)NUoUhqz|yljSUCbL>mjpe zIlEsO^`77dgSE0(2r8vv6hdq|XDNp_Pc=@u(UQNCU~G!kvK5lye)2S=EGYejZ>IZ@~#WxbQVlo!tAqVaNpgL#`w6_XyU9&*TGWm|zO2}PtL zMUqP`Tv9$AiY%fmzf{1H3Ks@!h;%xP#!}s!!M=OTG-t?Bn8dJspms_28Ih0snQB@FuhlbEVxGZ}lIsakU5*`xQszG@DlUWF zDR{DTn0uWC*Hj#kH4?Ss0$*=2XLHxx*)r+Jvp@`79xOvVSO|CE`h#WC>vVklY9DK` zXiW@FwP=_xr@_<+e=f-f8r-e9KlcoO?JOm&b4u-MwXJYGmjH|vBDoH1$ys^Kmz}wo z7@f(|yk?CNcOQhAoY8&>lFPOG5@A#kSw3me5Cs9HRE;GAbpc_ggY zoxWWrt9eNm1*Pg;E`oSO+ld$~Bv1)VLUr$b*V>1+*3F?)!wXPwFu+to0s^S^YGTI- zqCq0K`ooPVA2%rN{6fnN408FkInX_1k1tS0`RPByJtWPa9+kuWT-wL6m%}woBy^ny zss9QW1;)XLd3qxnF-Q8feQ$MOEyTw-;n7UBW<@8Pim_EH(CNzXsvG_d@WY9K{d{f^ zB<K7q%?3d_Y+kZAwHDVI1%9ulUhg+~&kPMHpG-24xFXn;F{jtF zGGFUD#(GyVLC6uKE&%~l1VQtjKyc4L(yTEGdPr{9M1zS_3IFz0XV-F2fjl1Kp(UhI zWK>a@Npdf&nviQ5bHK+di1!hbk0WykNWEplXWBgx_ds%AbHrh|wihu_*{j>$j(Eh8 z92Tj6c8)edc{)H*7`$IbzH5`zAFiP*%l`Ss-D~Hoh`rxu1(|oSBzQ1LURDtLR|L48 z`hG%5Z+T_|*c-54%(_}Rv&dzyg3u$jKiq`!nSG-RF^EoXq_9vD^!akyw949&LIdnT zz!->(1M9)|PRXokDM-FbXfj%W847AfC=zmQ;`wYmRJ64Hi{~V?XvW4!#Xb9f*G)+Z zn!5`WK!3l~JtX{h07H~VYqm(MAb*sLXaE&n_R3?(EI(|n-@Lw<` z$*7Rb0FCIf+;nvqmXr~7Gs1W@&c#!?UJdBjv>|(`&B*KqkO<3Uu$xXzAQ&brhJ7V_ ztPN2=mE{YY-`K}6_32+p$|qbR48r<_m!+;n@)23DNX|{Ncw1rx+K(NxB_NWh#Iqwy zrk_>Sug9@@=>*!8p&rBKow3-FRT$o02iJqMN4VIN|Djn4Hec8TPUhpH{7UuNs{41m zN`GguU4EqS19Xipi_w(wEsz;rYJgmw1%rK_JOKn z-2(I^$o~M2{X6#xevEv%?QMGaDFVS#A_{ltuwQ=aq`zg_?TRi`!HoPA%%CfDtiT5` zp1rYcq5YN7kRlGH+GbR(p=867%&WyLpKsM}v$#*R^)pZXCJqg&@342&&sT4O@5pJn&6N!-&}(w+zNXhF`k8o#2FW|_NT#Eb zc>;gM>$>vPJ5&=Axrr~kj8_fc5Y7<_B*}~Odn;Ri$0uaJqR0xCjVE6esv`noK|x?Ir0skaR)Py_ znq8BPl5;t+bbQVm^*URBt6u@g)3$ZZ8Vyg`R?^}@h>c(}cpfRN^9OA1Tl5bJ`>-!Y zP4wNSGRscfj>2}a$cmh-ebdfen^C?1wnSu&*uJ@jKl7sE2<`@p>?thbmAYX_=o_b? z$T}fo@1ffCA9;K}DfVUo;f;RPX|<#~8Tim_N^x*udEKdYTUJUCIu7MSjjFiq>?|j5 z5wc$O>+Z#eu)UC=dgnL_q2zNjCl^8Q6SoUUvV=H5j-v~aTXW`jlxb71tIULML`b7j zCYVU6dhA~UHy~DnG$@*N7WxRr3L5-chDqVHhJtJ*%Mus21E5>g;$E>Lq|gJUGp;VT zr}No_^FIMAZ57K3%(~n8oWo9@WU2Q>v#S7ub+@}ND%Tz?d|@qz>hi`wQV1KrJAE!m z4;&JV>iaZIz*$iybfaHTYeq>1yC!(7cD@?c3uSt;dnRO66uJ%c#27t`@uVDK=xv> z-4unp$n)!EXLRO&=UffG`LetH%T&;*PEWOyZ+?7v_f<9TGj+$)ja_D^mQWy}D*ECv zQ_&<@^f9>6XbBZGI5|hI87)OMCou~SqC41OVx7g?2Sheu*5~va9ALZ?)rMlIUH4&T z@7wfzF;H|MycAF}zj^57-P_;Hf&SFbSDZGD!I)nKb%zZPygZ-Gq-h(CvV;!u%0d8T zN^R%5QiLeo-jA}e`@;3-jk72HA?ijkoz35*kgms5%}d6Lurx~E0@cctm0_AcGxZ+` z>gQ%wC!SAjaNH&iPR`1Y!{|lYy4e<_IjcX8HQIJ#)!0T(4Kzt(pU0(tFs_@tU3}l# zqOnF>4vll;_T`p%#GE70)362Ymh&=!Wb-|%c4q1&#JR%Awf?{s zRPrKu%PO0I7w+rRds9tkwt582uLZ|260$V9gQ_Bd4_}N$vVT5JlyNsy)QtU2s`JB2 zSvtF235el5VPn>;g~|O*_PJ0(k3t5BYBw`Fa^4Zw+(qA`ZCl#QGFbv88bXtzp$M3` z+kwgeGJ+Z+r^X*Fv!lb#kX16%y|Ej<|l39Ua1; zZ`v|?U+TbBp|MMbfKNHF$v)B)pnDdbKBKrWoY|uGS-=3jnH)o`IXsh zlQEA#y>YUe#GiIobBlO23Is(?b&m=uCe|bkEoOvjQd1NC<$F2Oz@2vEOKx~z5|O4<7*W7AF9Gii*4Y?2##rQd{bFks{piwpFm=RqRQ4LAKU-T7LDd&XUIy;Xt}Zu==p*Z_#r)qWpxP2*B!WAZiKHYOF+7U z_or6Q!|@2$;OHh>+SZVru}1EW4pI-El$RjO(z|{)`tGWSl6otSx8#E04lX~=h9!8Y zTNG&6WbtWU5xN--z{nnfj^{a^ur;?LWe3EuVHRWk%S$)#4g0jys))O$o?LCGfzwp^ zDJ{!eQj%Xd*ilo3?_anutiRe2ErEg+IXb%E*@WRiF!&bVA@&yA_-d`JC}ykkMD1QG zPq6T^{ScCb=UNRLUV2a$!tnuNJN0?3pWe2>fPB?Oq*iN^9K%&A45so5uCI@T9=3POrKyFHl~r^#M$-6K0==}EAnvvXCZcrM5)7>#-TL<5v`Jx=u;k4D=?u->;meygzm$V zxaR}Jqa^CoJgO8-m!!yKjFJ@h(I*t5K&a(`th$eek0zeYHq@UsnaDYkK+84gy+~=b zNzU3ET%}^34wE1$#lqIjqtdjnfA8MyaLrADu{$%t&LeL zzO)6A-6WzuPI&6yTEG$(BrT<&J4s7dNtmV*;Ex;7uFA2Vo11zQ+wucHpEbx8pN!qQ z>k1FLMH|PqcaCpfP&;bZzz^YLi?{iLO69wf!=KPQ3Hc6w6r&5Y9kWQ5ee$dXX=~(b zdFxqa`P5Ucw8_)bCn9z7wL8l-*(2MVvj$K#V_v}$?m2#11k{j2Qn}2A=0GKrYU(x^ zLkhRV1g-W2w4@d#GMdVc-Bnh0;!KjArg2{0cAfw#%G^iW^@(k^=4ZU zcie&|JZA%+V#Kw@t5Ysf7f&XHdaEw-LmgC~2ZrBbD2?<+%sEYA-e#_}B}hWX^@@`2 z8#?`H4I35VavvEE2dJ&7uMGCYZ-j7s{azT|rSqP_YmS^AUFCYdHQ4y?2)^knGW7Ne z7PmjYnxDA4W{g@NUNSQmeK8n_K^2%=2dt%;{$Hk~)+oA136`DBuv92YUB$9@bvlp| z3$Wu?9VQ@@CZkVkZT%GpIR3Z5*Q(1*zDt}yO3?hQjM7r?-99h^g#@3&lgJviDh^t; zBH&F@SEE^>Frw^=dN$!*M+~`P$FX@x9Pge^lTvUBP;r5Zyyo70rBi%Ify@XzDH=ad z%dAPy%lSdC%r}#C<`jZRd{>^-o^m5`HtXwEKMOlixhT3Vw^c)l06l?3H&*(I<+F-v z=qG;SlPPUQ%!Fje+tVz!ko+K3cUzr9lU6&5bTLSmHW8cWcQ<6+$?8uv^*v|*9f#ER z!84^PHyUNy9h?gcKWmuykSlh`fWums(0c9?=#JO$b>UXR5tE;Ejm?j&)=s^xxS@Sms9;++Fe5cO*<&ZHjb|q5sXk;EV%TPI@{_LhpQg< zT~pa_JDjiacy;j2b~`NTOeEiUwHjzb3~A<#E4bgV^a{DDk>Jo!!Hz3S)!=`s*7CcFpg`XbFWT|;y{M^3qX1pe z)(tQ)a=@A6R-Kv9)FjzQ_}$x5co=e1;kHm3yq|D%0K&HWnl+hq!nfR;%B5;zUXBp9f;AE?W>G8RY)(s> zz{V23?SyN8f;Fy6X9_DH81X}=k4expiyqIiImHU+Ju>8K_YTy6Ly}?#i=^2q@AROeiw|zT)e255tEcx97fF$pxLsypUIC(8C*?jT zNw|_jdu>oJ5{kD2An=liy|L3^EBxw{a*_zAX84_mXKF}<6dvx3l_MzCBF(&0kj z2azE;K?8-FA3?^)xhel!3%deo!sqHS*#!)d45XSK4Yn~1Qz?39H2s=2xRI#RA3JF9 zbdKZXrmF3w9}LM$SM6gsOa0%W#+Hf6LS))Sgt{Wehv9sGgDglAlP5|M?Pd5 zeht^EoV*;e(|!@x>Oy^Ng+)oDuEx0@vaQf^zi@OWz_KrwNYJrhp49rlwKqWL-; z_Lp$IKP5<*y$6?15E{smY5OL1H?p8u?P}7ITol8f_hXT|XTlVprY&C|fYF8yy?Z_I zH>5l7OG`;bPwhQLQC`1?{IG^~=6WkgOcA$h^vA2z*`T8eq*u$qVk{^#PbweFexjOd zLNh3uG?l9Au;SN9z*UFI?Es5uL)&ZXRcyB7&R!a-@S3{3N+lx>_NQ+4j*4%dZ) z!Gc3*yzs(weBIt_N%0mbBMl3xf0KP~;tkiqFV*T<(&;%gipE4*YESKq)_GAJ;3N^o zp2<1r2%nvQO@Ci}Ox2~K+$QYGNuv!q@%Z{CjAkW$7q+UnaPI1crc<@v>b&vU9{DKN zx7fS0gdYAX%X>u5ql#^Z8l?Aw2`HCwXRkUrkR<~}iKK3|HRWPlRnq!yC8S~mK>~-J z*Cu>x&4>sc190v5nP2T=svACXv|wF&l$AdBZdWMKi&w6D4utvcQZ5uQ2bGYtAX{tK zYHN&=x3{t>sXlTyg1*|Vbuo0*3;}}MB2{kgY?=zi$rwVS61yTEg`(6IdpE$rt=M^r!KT!JJc6!vgwI6a_Uxi~CC#IK4O8~p6E2x{E1;Ly8C_1xQ!Pw7}xRlLc zWtZ&hPIP+n;5EfMgc>9ChWBI6h&*leOhjrJ=_I^rVMYbs|KUb@pj9t4#X8?*RjVR8%#ofe{nL z4nm%t_PAi($`lSsa`Q zs4~iv0f&jQ62Z9TK8+t$CoH~UWNVRD zfw~d&ka_A}cQk#jy{57*UIn_w!nuUwPTAYMQ%M{*MZxgP5Y42ikgA_=jI({6X3M=G zfv?j(fFu$pqlMa}>>Ki6ZJ-qRNir3uXR*mKWHA&7GCq*jp_weA1RW$9@^n<%s#nP9 zP2I8Ob_TT}Lru2650q2u4FjLzs+ENEP%$~;`y+FyfAQrqBjD8VXMS8W2p6OnuB zvHpJuaGKRa7lY=s_!8H%5oaQuFOOgJf!mXV`0#~3*2{#g%05E@CWV;5-AP*>tiU^i z8^d1vrPRHnr(`K~?~HXmqb3c+ff*ci^;3s3(zbnHLG#A3wR7;8$vTMdQ~pBft0(mR z!QQOZ9&h`w4YrWqLW^YXCTb^W5`H{hYi{QN*52J^SQ=(&c^>mdf@~!W#Q}A%nj%$< z!M#WQO9RoUdLk4d9lP@v9=D=#N_NMX_o-iX+%Sv$koK&gHS<>qeG{n$_}oyWg2LK1 z3|JuJ1}@f%17`>>z|9m}RJQ&*E&dZRH?Ez!0ncv1HBCI;RzZelnk%f2JDA_Hb$O~C8ql+pj?rudM zLRd@b2;^Y`lZYrsE&H>N9fyU31vq{z))l&nAXPvcvFx2~Mw~-+h~O@a_GXlzLhIGs zQ0Pu^wt(W?c;B4d6y(O#4qq2RxmlW)qhh>u^xoQF(WR5L{Ra1H*KmixPez!coC&Hx z1{aLss{j~Psuq;Z$c)ZaxGmbGFk@=HgDXkmsE}OnLyG*%CB{I3H`>mLZ+Ob}DqW>b zvMh*gR!zssRLiAN6Ep&{$VNsk@ zkQlW9`|%JWiVu8)^2398D$pc@#*z_?LeFyam~dt5G?Esk67y|koMC@OvHkcey>`C+ zY6WutqVX3#u15cZ$v&|HNby<~Lq7(K(L<2%fCW4EG(U&4#rS-{;BlvK3so;WAS)-8 zUhfx@=#aZz1hvRV#C|G$ogvh2SW6m6^sSjcXv{EHQ;Qm_2%K_!4NKxkoz&Q8=8t_= zkyq^X2NM&{v~n~a4%Y>7p|Jj5GGLe4NpFF?g=*iM$75LUd_xea(f?eC;({56{c)*l z@JF7yxB1PU&IXg<$@6Y=eJ5x(%U`rR^QcwHvp7Luo+eDlXTjx->Zr{5@+vXv^m}9^O1pvlx3T@h|e=Z(C zhhVO=h`gEDTxYW+_Ga$+qN%~qRv7$DtKzIDAJ|UM4sUdeVFfk(W~{Wqh?fRcLG#e0 zkOB4^3Dw6U^_m6@USU^L?gu2A91@h9^D-0=M?ku0=B{OGH*S}30I-S77&r?ge?~fe z8!h5FO~XhN;WgH4?}1T?dg6DUxxj;l!SH)9B1}s+Q9l0GAsU6xyMad-6oypJrluusQM^$gc^ud1F9$ zRlM-ytH5JBQOBvQ&4~`ogOpB04Wp#HoKRt z(^L!`sJL}d^a*ei$3;8}>ls5o!3pVmaS@mFA;bdNBr2~`?Fy!>Czdld&H~v#8D2*o zzH8b2xHk%o(Vnm(n~O9Xv=B=*l8O+?gN=RpPNHs6`$!@AfOqXm+$>lTZFf$ghT&F3 zOB1Km_XnD#ZKnnuy=-J(X*eMLg{&L*sA*7`?p-=&=HW#3zn5HNNP^o>@28mBdE*h- z%(S0)r-4FO_3Zmz@FrNLBSdvCXG^IV?l9Y#Ymg`oR)v^6XsB{FJ#x7UX2=b)PRD1> z6IcVfOI23zVF=(i0RCb-_m32<$27$(>+U3+EXyj1TECre;=44KsVc@iqE7}z(nr>+ z8QJ3pGBZr=;B4vEKGLaakF!kvRhZ}x;>-=fJiV*1^#?*}#f;<0j2V-|oJ;cT_0mGO z-y|TgLLM9CZ%{X;BfmSo-0+_|J6kVTHk!};Sffb+*s=Uk&=-V5GMig|IcIQiX88GM zaOP7nS8!ED{OwT!aj*za#qttfDbhet8tssk%@@oZvyn1IecUQD5hVvK<48zIkw2ql zMwx!e1h7b?BPP5J!CQF{yKmV5{j8@Sx_Zv*p1d#gqD0juI?izuV!ttTUW}1u|Je_y zIX%7f#-H|HjFK`m_ubYkXq4ku-)Izm9N}7E^p1S}8&P|?6b_f2H$JB`!wpeB$W*$G z*7y=eVTI4aEm62l*6h@|pLn|BMb=Ii@-Faq%{#bl24N|F3U%D++yDjhT^QX(TB*{Z{d21^# zg|@xhirT+p2toO5bx1D?Cl)4xsa;vK5-#)}^@u{z;QhJorsHF|@WwxCj3?h=G{o;J z0>iP=qz3UB*$^PY65$7AYHtejg}naKRSTmD2>S6yR(;l>g+cAyNrlno}KQN%#K0C#n-6iG_-A8^mEkp8%>pANX5dj!$2uIr)woVhibF)@BU; zmFyR+F-17Yh;qv(QWPP`-ivGAAxp@R^Az=4jW~0N`i*T3)$X%2>e~iq#K2I@A9EmB zlHi3z0`B6OjC0RZho&IQwyGeU&1$?cQFX$=mFz5f`c($WC#K^df^I0nl4t+g96nm~ ze^w^p8bHsgjzJhz&D#B;8MY4+^fD(F)UQWHVnwI{v~+>71n8tlo23Vyr#29!$FqCm zG=J&8IR?9sMWa4XwFwGS1%w{7m#tii!`m}1=C z7)g@DQEQzL+_JV^j&=Q{c?VTBj@8xxQr{bQHEoqxK@xm0UBV_d+lGwQ^u< zC)Z8ocnqMg0bzm#!Zfkqj#q<%<4y{y$9E1iw1m*BGYrT^KfNuWic-#%8(Grgn7!J1 z=zgU;@*(2Ppf?W{Y^wJ>$&GH&aWwSkAdKa>uYp`CghkqFfc<2?;UFjGS#`1Re{^+x z{*!fweS)k`f}7%8bE+LCZDHdId!O?5%{x?sg-+}%Yw*VJP0GMIicO&tmB$HEuxjFB zkQ+#r#0O|+SDUN8g&i7=vnMwgw$~qAWw!=H2*2qwB1h^LRQG+dq2MX)shAFzL*@3JAm#j)o>-0|jV{oqWaXP#tf!>D);_Hgx!H@i1-PtXmMt+{A?mtr1r6 zwShbRvapN5FCv|!-H)qn3CbgJpL_Ab_=u@kE2Y-FPVq#dZg}O;lf-m2h7s$)-43io zxh~2b1rNhdiEs$$9BlS5N|4Ihh=uzF`{L=TLHB$osrUYR-$2rZ#V$K<6=pUVbQpZj zKm{N)S4Lm;Gc5~BD<*zTs6@!u>uPGLP@7>`ggagKsjcbMdRF4K5PVrQ9gD~QVyqb} z$EvZP8T+IrFUjJ8wjJtSLl%dCmYdMm27NZO$>1=QUS3-{f=cZK!L-~rU3XDBv!gjY zY=N#NbJ(H{h#Bufr6LN|n*^&d3Q#)UKjPT1YG^%G`!K^~zJCxBxwm&Kj5}3Wh8OR9 zidU*L_gorOLE6LswMbYm0Bm_4!wGCjgj2o14;3G!Vq|Qj0Yo7;uqkN{|Hi-^uuRMx z3Rnn;-4l7we*uf;P?p8+>45+d&8x?oQSBPfznq5FNF2f^z?Yq$L8O#4u;rF}VW58+ zO_A_baalDc!k0Q(+hxzJHy0w+?(dS8*8cw8yMuS-`CJ{3U3nZkwIF7#br&NAcXRsg zKsHq1taPu``&g`P+taWtzb-Aod{BKj>9`*s*S9@JF!8D4HOvn>>wHIGOM}Y7|SbO){Y{Hm%L%FzzDmD_0)by)gww+&0dGl z=u!cPmVY~E?&E|CgJk%+lW08O8P~_`d zI#HMl{-$NZr>a2u{t`btln#*uJrE^`r(xEqA<@H`YYH{DoK8*YBp^wkj$FMWxZFWI z>Ku#r*PLcOYCj*0G^yO>Gl&_itFXsITrDBn)mDo4qPlhFmI;?$YZm6CaTVP0J_Wez zJ%tz-@BaCDyn6y@ri)Tymc0l!Ra8V(;m%Uk1rg*ov5pzl#KrRRScF5pW#S(?#p|_! zYt`+r#(5(bVrUj#ao%?1B3Tl5zLF>AtAY}kImwc@;czure|b#RBaFn-jf9jiD9-by z^5F0|#+HD>glCbp=(N5~XbX*fW_18Dc4Za`7g+^@oMV6Vucj0m5M3tIsy&&@5 z7Yp`0h0AUkS8!aXVWp?*bW(&<3q6NO^#ZU9j`nTSztjt0iG?uRC7wjkf5p`V_bu!T z?oTcpvDC7p0bVlJw6e537yy>rS#?jhk^E;P0d!)!orEK}qjnkr@nO)t4bUZ{6DD(% zYOg848}rjrwO#GB!()&%=`l?q-dvmoA&SW^Qu2-JiU4Qqt++KL_;g+F=?S$@uV|8P zx0$V0tuXXEI=NvF!5ARl4kWsX%=!H}jroy)>}Wyf;QrxsCDMz7E8vH_0QS>;d4PX! zZPGa(y|mT)-cnc2v69$I^TQfcn$7J2um?+>HCaJ>9}tv#fTbi)h^oou`bx(!WBoqBqe%2SD`6^N z#C3Uj2=cr$#rZKw#9h%4JEz=fQ&X&_89i?5f!nVPuwR4j=_QoWT5zZ`Tl zS6+vqKyhp7X_*?VihgaeEcjEYCb3X28pw+UF(QKb7XfP`Ct=UT6QHGu zagQQVZik$GKposTZH){xSEUEwa;g;S!@>cY$xAbKzyR_P&6O^pMRn<1(XH8o5pGK* z%lw3NlxFy8QO#`>Y15&Vf^XI6kCl;_jCEV~_LMSpP-;gT&8M{WJM?wc6}xT)#!%Ok z+9=@==MD2BFF-?7EOKqINum(Sxe1lZ+7OQMwV+=`i?;)rm*d|@Ia!cJbROuyE5_`$9CXI@Ke2fHH=Qjcb zJWS{rNhDeOjKT~=-5XXFS&DUhtOh|2?jhucha0g4ug^{+G58FoIm0b~K;coV!Ljt- zghVTyENrMqv+)aA$Ah!(?WcD3(09u)DN-E{PscrOw9GrJU4;n*{%1aiFd1VSh>3%b zeCK`6F^$I}HVQm1CPZ3CZ~p|&SJ?P)kApS-er9oJ-gp`qiEhU7R*`J4u4~(5K#P}J7{>Wq#0^j4$i<3Pz;1#5T83I$- zyPHC5z(Cz3#@Bb&Pm}(SbzBXFMVCBn16za5Q#CJwN)gXpqV0 z`+`R4eYh8LbMU5@2fpD{=+U`)5=2CrU7|^duqYP@w(q%-Z8(J~ zEPRf_ofa?vCdnoY195*NnS~uYG$%z(7QQ5gTU5aBFMlPG)-z`@uwy?4s7$6%T)-u~ zIRSSMTf*JwkkC(}q-Q}f$mUUF6qFcqrHOb)QH)x)F@UL{IVK3l5$+D;Sl;e%2N%Q} zzU&Tz7T)p-POH~Eu5M;Ub#LZ~1#xZ16NVZdiimT>p#&xVieCzj93b1^?A;~88PeW*k$3V%-W(7c%Ax?uN?k;= zy0=n4$r=e!l7oK*{h!%|N8aq(le^W8XU^2l{fFi4hi-lI>3wtkgRzi`qDgec@vo2Hs8Ufdfq3SoF#7=82z|RMqj_nq&m&&>LOB2$;yZqEwXq3v z3$VV1i}U>en|@P6IK*{4_Z0&&IvUu<&1dJvE)!)^Pb&i~P=+Nwfvz}5B2FYZ|)WMcuKsQC%6|_CAm17TOo(k;( zc(E)a5lK5`7A^n*BZ*rKgtN%YpxF=cC6NX@CjONkWQh}cghiaf7$w{@N7nTL% zpd4F)LdPe_G9Zs?TSi!I406b2Cwt8b;@%_r)0O8U&VTqFtun{A`P3`QCP|l&0QwJN zTF-fcu(RNMi>^fI%{!F6{1P^<2&Z5!v|)8;cxVlUPQ+g;bm6XGATyw zfnKnKNFF#f#`0cne;>~KukT4(uv{<{bZr7YLA2e-enm*crl`HlZ1K_rKWtww{+O-478*s-BW+|FeZ6Ft5lo65Ym9_75DJmawXEr&6xH)J1dWQHVg;2517= z4*);s?^kQ*=w5E)>E>(zM~;6R$`?+WdzN2ON;Pv+;2m1H1<4jv&Vba*RS2Hft}-tm zFj71aDR!TaZJY9_(nb|=!`@qP-E*c(QRl!(yas&6Cl72n$Hgf&BjZoyruY-8mn0<# z%aaautPk_eBxJ*=MPt8V+DGzqs9!Dbci)$}@|wwmOOls{^^wrsbjIeB-?A=(h{e|! z==>`Gz=F4R%vjn4BaEWbDrQEABkBT7TWStT|1bLyG^yv2r&?JRyeB{TX1!I=s;fxP zf@*A*#9l7Dov||Cr(E2tDh!<(PQ%i{Ie?SLc7`=5c4Vl=6U!kgh`_;Ds4$})di{XN z|2Sg{hW=lnr1WwyEe!LyVD5HzAFIwf1-7~B(bzV;kK103?v9*}OVxAj^hV8pG+PPStmQ0~+F%@`aTDpCQSKH- zXH~TX1s8wlNLy8J})0$=vrhu)x_`M5iUL2=I1ta0_wh!Uh z4^8OgjT3V!gCV@vkHg2SGa2rc{atfsMxq?$8$5HEcqKO@YV0rw>Q;t~rBuGx3p=F> z+BUbQ)!D`XBaQQ7k~4ttYJ~cPXrAc*-j#?VcZOVIR%>jdYceLMkK^_=wdqV$@6i(I zNGZC10^WP<|A-v?SW-fOB0pZAf!aBHYwBrlzqBm>oORN`MCmV}RT7=~Q2@gYAU*n_ z2&UfU_Nyr<;E=rmfM2~Jg1stpyf6p9nSlpj3TK^b!%k(ae1Q{MBaAtU6~=W@wTg;Y zN;Yy9pyL<-OXQFffD{DA_%i#i8VVoedy0A&_EtlDGG`Dd+?*g;;rr>qgVWCfFPH~= zdQ5A}ZpOtOD3DAkAiaTxo5f(tmkmQKm?MNz^z{7u)S>pVVo^MHu%=;Mwy+?sd|^SM zpSpeF`6N?@Du;lTWoMx<(}(p<2h`Mtt?R=Fd;@J4j=0yrUAqv2n3 z+|fQ@p7dW6U2vkzU*z&Xg**MARNHs&er9<_?OR$GPk3~1=AOGJC)69pLBptD+`MkR z8$WfN=M_dB08+~tC8_TiX@IA(zozFAO7i-VbJ0j+lj1J4DkBQu6VWl~fR!0-TsB}^ zDu#w(fYwB$1>uAvCs8ZYz@QZ*oK?;QfDewdL_1H5L51!Y{dRY^Z3}ieCu^LpcsO37 zx~6hY$#w1xXN4De%lb3jPp%X$bWIOXDF;K=Br%gRQO$&ZJo}J+h z1Zwd^@ZL>^8FFS_pnYXipIcdC>SoScv3w)-GKy+>nJDA)LEL})j{o$&+;KX0hK9PF zYwXu?63)YBTp7$FK$}zUQ8*(6N#T7c0koJxOA6=3;jmk_ltDpUbZ+{PIC75MG+rgU?vqd+X_w zdvEmB#3Kb1g?cwfZV+RmVT?eWN5h2XI147BiC=~=-F5P>rCAt5W>ry)m>V48adLwk z(Tj?O6Z{u^eWvMq$1(@x&vO_k0%wS5SgSRZE_XVu=+XYyyy6E${(Ufu% zBw*1%0h~LI(e&X;0O@qDYcHr=rWIjaxoun1%ARcx(q~vnyn%q}^BSf~hHGvQ+`c$v z>@>UN2RY;I+n;^*=JDCfckRkRcj6C0D?pZC3uYz|7fuo}yR~yQ5jVAV1mXvc^`Xw# z!2j=OcHj9l%!`Fvp1E!9nfF|`tvLgW000mG(vvhvF=7y0dEsSSa=d5+Q8=n9M;6@k zySY|Q5SBMJj`P|&|DhYRpa3mPvH2%5{q08`i4KtoTD9L;+# z{OE06U60l{#rDGr*b57nuD;@-`DOF`^E&?~TXWpw=5>7z>cuHHXoECgb!Eu+4w2f2 zmp8wBExTqvAQlJ`M#8x&mw9$wkYg)p&8P&r#S~VfEUQ8!?A2I@kIv}Ln`*7}EPvJ6 zRniamjcdx33;hF2{|Jj)5Eu55v=NH6G(*5SM0KD9NDD_r9_)Q;o7J2vQ1FsBg>tb$ zZZ@6AW>{s3k-6Cdm-y>uH+#SvU@}Tu#$3-0)X$nvETu6f6{Z)(va@u7#j0jQR%2r} zvqg;Yd3rNh=D8Cve!RWFgp@+pf@_7nV|QT0pE6U!%NUR+^*e1k@hv5|^EY9MlZ1WdNR#BnsAJodpR=8BMhz2x&>dD2tsG~DV&v=4bCVfjpzT}5Y~v&3s6S?IC+Cgh)5E09PIHrK$71= zw&Y8kR|G^9+jHwWMJRFlh)STiMPkD zZSe-8Ep>r{Y0XZ|XTk_5AN1jAq^uamQVWs`orIYj7i8LJ(4qCkrYzbLlT zgCWcRfPFHi3@QffF+K8)Vs9cSWf{bPY}B?zwshOnhbl_sNlS(q{5mp@J074&FCv*^ z&>@Kw&_)o1yNnul*GZ9YZD0Pv&VB8ZurK)*!MA)#+9%B{&Mf;HJGx?;_tdR#esb^T z)r`saU-5^e@=qjXjzL>xm@>1(HgT8}wfOkkXo1hHiDQF0^^vEqTM8~5~ojcE@DbsZ2G z5C@wvShY6jXdPum)^b)=1325B)m@_ty{5a%rK?n5a9cbX3PwNm(cg8vGfkKoZ>iEsApDM2Zy9b2+1AfU8DQsfbz{NY3o0Uo0OyGPk>@CWrfaGM54C+D}## z4*l279o!G^9os4$do0Ul{IXz$2ppzT_|Ug6Dd^Y+8Plzbw+vcV=AOF;tVuc98CVQF zVl(l0IJ}j_Vr1){JwPkk?3iZ(z!S`v@k@NJrw7t)Lcn2*0hoL?vPg`C2*g#`bqEp0 z#KDV}*hVHvqs@`Y>S5nnH7UWE|857#N&<^|$*KxJ=}Pp&zn?QYDzx7)-)f8)1XeC; zeG)D0!^6qp!)Z-W{%rZ+xOeT^b>qF0-D8&B^r@z40ZIu`?QGF3gyjAj$B-6+qrqR2 zOSkU|j+9uarfi;mUAwWDF-LKy1AHIwNeAS ztP+Tt0E4b)co@Us;9GT=0R_CPx)2kgRa*WFaasi~DiP7``?LGt1Mh0D-?f>nSG;tiF+EzQ%`XV>7MExkmDlIUl&YMX%8s(0 z?}ILb#lFweSM^O%4ZY14Q(r3@-UIiaLM2n_btHL1hI z5MyMA6R;!@<1|vGG$~aP?sF>T|dKU%0Q zQvJl%#zqSI=WFvLgfsdZ=aVyA&XD;~=Vflbp~~l$WembO-}FY){8@uFr+1bK00xUd zq*5s%DI-M)jhKx*(ct}PVEzW?UnGO9)ThiVn>R(@kn5ti_yy8C{C@LS$ZJ1cYpeEY zr_WXt0cDKzPK;o|ij5~2M{8>r+)`59(9j?1;~%tL1URCcDEvuk3R7z9tU|qK`Z>b% zfg9`N9BgVswC{ywH{Ph4cw_1debI)gemDs#BVHb!ZuQdZ@BO_u-k2Bj+jH}$GSv%s zRtSHLkQ-U*5gv=XY6}Ke`%Mlu_`X(aZkp|CJ+1E zErBysY<89S<6d*gJJhG1yh$kI;9VyR&ON}c4x_9%GYYYDT z!V7Zb$jKrH9OpME?&knuu#?bP+o__kS&!@^cBXi_br-W~uJXI?m|LMVl9WFiQy2-LT#uqz# zT=`XHRr#(aw@+;fVW{I}$2+_GH;vU(JJ;mO8-0!Sot%xk9S};FW5D-x%)O0QZ`f2b zKKSM`xZGrVdxJvpXFZYWEyE)`APtd(MaMnc&bzT(7!F{#8Qbw}>J|2Yl*%Kg%RJ+r z>n84jG_K|G^cX)UqxI03u#Z+BwMWO=&r2xi1q~Y`nV)CQRX$d^eJN)Q&icIqew?Tp z+B#H~cyaUVo$&CAvdU1t*RjKZZcDu&yzq_O64wcVLR5dSUEX-aw_Ka=J4y=l zo&SG@_3UOW2<|VHF)77hQ z?vh*h{^DyN#yB_5lUZ*Ms7aPwveTfrcoI=mt|UdYab-u4 z*iE;$XyPQ8hQrD(=dXfcdXw`DMfu`nRzPDQO;a?UvYxZYS$WV2#ej(0g?4WJ7GE~N z3)Q`CZN1#V74EP;y?oh{oYW2+&g?wX8v^%TpHc&nROzzBop2tO0R#l4!-9x#k{ARo zfxW5WeS0GqN&p)}itndJOajVtX9x7!zxk8YyKUg_%za%BQBZ^^;iY#6z? z0C3gHTE;?A7FesgUvXb$R2uoFL=#{1tya{5(w;ju8IuR?uMIv}NyF)R`JI6z{Lbtq%jR%-(Jv0DqXERo?ag|g+N|Nmd}bZ$?$wR zE@uM*J#!}a>L^TqRgXV)eR#HUthl@~#nTu;e(^XB= zVhX9!uU8`I;fUH~E+vpmk$aJxl>V|He{?i2|0i%mZNz*yd_EkZOl$8hSMR{K3LgH7 zbcFR_6yuX3HE7TzJ|ly+fEDM}bN(9r=*gnb>=<<97Crff5^9;zS8Y2N4SfUHi5-Yq z@p4}1GO7{YR0UK{DAltq)dT0ntl8C$u3+Ve5+a9$5MfiEB2_>kf-1X(bLmVaMpS|A zM+earMld2c;h^^4-QK>W1zY9}v=7Y5L)#{KOHPaQg=efs5G8;Yp+HE8u9!#-H-{n^ zlAxMoPmknm)~vs!poOCwou$*7@pPW)o#(z>jW>)W^F-J1)VTi_#qI$(zN(36zF8z& zS65z_s9fDD&+T@uS<~w<_CE2r59Gf)GcZxV%$^U>ijzn!a*-NAKu=S>Kjd;+Yuzo# z=P}UQ=+W*F37JMv+~3#NW?ADFoViQ`*ZCZlXl~Ao?%0`mXHL_MrphKqll4P8L1y<= z*A=#d+z*r-dO|SB9b6Z>H`<2Qc~!p;$j+YEs@JckjTu?pEDR|NcR5aCRj6Ls4@Qnn zVXh29h0OgBYdPJ5O7;c+B>`F7h)>%w5}S4voq#l)yx*aZ z0wv8G-0yiD4YZdDb{S*`Gu_~|8DjovN+mNI9&mPvEFoi1+aOW=26wR`)_2YnQd8eh z49?>0vc-ow z2IA4xMP{DEjApV=oGA3S7^MEdkt4pt+dp9LaP7UqZ3fa?PzzjAqyc4suKH??`Y=;| zdeD_yGNK?D42Q5zw#SACUi)Yyxr8tJmlMV%NUV~6XYzXD;)112D4;AXTv@LSoO<`ez4nQP;8~)T~()xN+ib($=jb zKB*lR_;v!0Q!J35!{?c{;IYO>v(r3WnH9MN0q*I=-3u-vrFH}*gYt>CXR$6zM~YAj z=?3nCS3cqtU;un-P!eI$Q3V(CJ{Fez#n-kA00Z|mCi%|VXe%1^*?0Pk z9ej7Wb-VVqPr}Qa*DbC$jJ0=IQft=LXm>tYrn(14Q$|jg$8$tqd*(J1%B*O7<%5h= z>Q=+vp%mmb+$;h-4)b9%o-<0O$n&Hi$9&R7QpL+@N)WJ8w;+H_vPy17aYZqU&Nv85 zjRRJ!N=JcRwv{vQsq5By(6ejXwl5aT7bAB$J_Qv~{bGo-x&~rcQHj#pFDjBsMx(H! zC$zR>EQrP%_Off9QDFvo9OXMqYc`ZlkJ{1=HOi0@F`X#Ht*Jd8Oc)-<;Sd(#V*2T9;R*HGx8>Q81x#Qw^(NNB) zxknGjv6;slnq%@HoW?Y>fsLFrxRKA|<@cgJp3tl?Ih)r^57dURq7f-0&O@{W#5~8Z zh1?gz?d>1+JbrA_R)XEjl~;f-`!>E=T!ch+abVLoUc?=qO@jN36Cv?_^rlz&M-`4uQ*)!I4 z&!d0*!v&88Mj}DxGo-;Hs{WMpxZ~e7pe;94`fqyL@KpKK*qqqZ@~0YT{t!G*O>>@` zea>0_podE0<33I#MHM`i0_R+iX1T}^cpkqUbZy*o4M_-kGM<|QTWz+R>ATtDmUh_e zetPgg=hDSn^9-iyTdSM4ZF_jzwx)S=S3{1yJ6T5+Cj2Mn3&|KzlEIa2kLFgMfRJOn zTfJu>L$8xoj6^KAxX1_r1rk+NtvPef^XJ*mPcQOZ>A2!qbXrbdR~Bse{Lmpea-zLm zdVejp+SuY-BTr6GS1mo6>iy`$4>}(m8IgWhf6-yhvjPDKnqH=sMFq7vH7Hbo{JGyN^CV>ScD1})Pi9BXrCXoHG+=`{1 zI6rD7ZA~~5&bPqXw38AM*_)DuBR`3taUP>cTsaGAVJ*r;ADBPF$v_Gg&nCeQU1=l4 zNP==>F|6nWB&TIBY#nK98)2l+@cUD#_I3^dbrhDZCo9xoQ54J^zj!>3OeKP9(MxYE5kDuEn^a;XiSlARU)vT$xIJREAtYX4 zsx8mnDscS~Rgp)hT694AGi&%%sD&6TZ%mHuDTFzEI|u37TdBs zxo)NeirUou!dU2J`g)4dwt1VI1I=DT0y#q$ zl8~T~w>o*Cmf(nUILc;}&w%XMxd{M<5RA52e2{C@8M?_PZz#lJ4Gnj^RW+!FxvHk- z0uYVG5{fNj5GIfxFrE7oDjhaj7;ZZ80R`yf6f&P zP)ctkXqpWo6yl8*tw+ZZSf9o;87^d_@;tk|Z=i(tgdp{v{=Tp)W?4XeaLFDco<8XB z6HjdsL67C9A2Xk^XtS8TWSJ4F@HeCkIn++&RpX&FJVL+N*7oLZ?6#Id$=XvHjEcN~ zC#m&d;L)G7)`uhE2p2-I6cPl`L>Pi_SRg+CIWav(&b;XjB}TcuZ6mQVS`fH3e|82- z$7Oz>m_9%DR98ft{CKF3=C6My8fekbIJ}|E7Ioy)0M7fR-+a@&xxNaJ93yq=52s5|TEs|IDiBmK2tMbcd{W{RR zwq)6=Rlm@urpD9GOFZw}5jqOS5yJ%WzQ9+<7EfCA(f}4d3&kT%!K);u zXBvpuK=AAhv!724^pac++H;|38ayliuzd5Z1NUhFgV(YUiM$`ZtnbgiSFHJ4;=GJX&feJ!aue>Tp)wHk8Iu$zNI#cB<#p8nyW_Ily%_z;$uM`_7H#jxT zA4YxsO=?#0l#dDvSzR3c{wl6^W%G;~*j&q^5636WF5_i--u%YK`Q<;nHgDboMkomD z5}xzv3x&~lt33UP3e*hZD@6_Cu5cj{Pz@*x$wOynoNbH?ey4&b8EZM|5{09Jl#$~0 zu|)_8h}zVbYG0bFZI_iJl_#b>`8q@7dQ(0#m&#TiKplQCrMhis=r+=R=k7LAzuu@P zo^cN;<7HG@LS46@r2ihAJJO8d*q3XW>Am%vyZ>CpBk5o;Cm?{tcVPKo)zkU3NL#kZ zq-W%mD%DdFP1&{J$O9!FK;LXZBri!Ux!_hRluBAAr`+;|jF9c7@x&xOL}{uLx*U%{ zq@)G}<5DseO_3)cBpe0Pz(LNn&^{TS*?ni*a76+!P8N%h;EK_mJly6*r0-CWh|0YY z_Hq6c#BRH%n-fXYh$G*3K5-GBGN_~>8nz!E94f}5O@*bQ53ae49tY1pcLV#9Eyt zvJD0WHN8CQ4KT2v&azt?B~%YzL);s13Rn3GLvxfnKl!Ngz+~A5F=DTI*!}K%%`q!eB z?d?busqC*SQTSx_I}N4M4|;<2>I9kc*lE@g1rgV^r#Rs?_rt&<-4BP0-BXT4)f|C$ zuB#uTXoXb8CtZ5>5XL01nvT%wa;dHh-eUK#Rya2H{DBU2(H^xulsrPk^|IwP7V{qo6gBk(7{ig_1K=Po^j8IO-hrasj{i}O67@}#~jMBnbnO&y+sR)wio@Pi2J@q z5g>pP2E2t_VFQ-tKFjO)+n2CGCJEsH8zG*NALr}1v%k^CwPA?FUIBQoI=)<3&es(? zcR7pKO`w$Rj_usRPhPK7*=`sP$(wYecPE+{$$`D|OG>>3@5n^7Q=R7HA;@=*oR^W~ zC;&YN6$V-)Y2b8GS3o#iVSPJ+Ly@xyO2zHT53I7#g16Yq9i|bFImv8`!~si$#iB7( zBBzlAf-qPZZ&<#OeZtSMoGa{t!TmD#q=E0T5){5YgYPgln0 zvO~mO%vQjR*($Y0@h1YU0cY+)tn-2kKXy1A)LLIzx$GEZL%i~XJt*ZRoOG{ z3Au~fiuTN#myV_z%a+8LvI(siKn!f z+)znwG(Z4FVWDB%7;BCnP0hE1uFA?tQcJz43egcN9NR#r|Mq{5A7))(jkEqedUckZ zneZI8x6rAzOU`WJ$68(V@`WG25&)-T?k+|Kx7|Tv{nbXgNK+!I#OxWnoWw}>yz@2FEQ$T5Zq5&!~!BuLVol9fu6NCh+XHu1N zX+bRC3S%WMx!KD-tLl7FF%2R6zE|)?w0%4ggRwB`pN|X=^s-IWH~07-eu?0DqI>2< z#S0s@Jo4Ft6_p+slb3$q6k5nGf}ZW(>&o$D${Fw{N;3|C(-TFvouZtc@9y!wjM0T( z(~zOeq^~;UnKDocF(Y$=o}pawQRGSv?^y|R(s5HY^cvg^9T(({x%Ml5!&F@w9vpoWT z6hcAU`7=0(qtuZQ($#+LpQyj`oy7xx+5!D%+Ut(bh|>PUwoObl33av}kd;H7Z=|w} z2m*uByn~e|-eWmv1;!B>2$Pt zCyagF#5qagp`7G@w6gco+R^99;q_=DJ=O--;-U^^7xA@QYIvx zWNlTZZ7lH>wy&qttk0ktC6V-92PLpeS~5OiU}~>B{hPf)w2o3j9p^@(lea{-6Wwm7 zt%vmJDH;Kh{O~7Fqs%K|*%$`Okme%6SyuT+;)*)xs)gyVZx=+yM9RB*Ft_-4Jv(4h zMS@>7Ai$#B%KL5@;z8rtw$#}(c`b=)-U!s5+Ruh52S-P!$1p;Rr&+_|{Bv6Spa-aP z!w)Ce8<9qP!r}1br0tD#q4R@vsq4I!7is)!{8Y5~^$#Zg6!>%E!3yL)aPk!^DM6DZ zv^<=T6-+5-e+t<^gfTTDzbycoPq1X;Hc|4JX2zpXE!T3wk$r2`^DFab4-N&`qqGyS zm1yCkiF}D4o<|pVOnrAm0qTT^zuYzO1A?;|OkI5(w z1w<8(?@R%`T&fBCn7Z0YXr+|Z@??EUK=1$BA|fpNg=IJzfp0X)yDyTbF?qU>EPiSI z@mjx!4B78Kp=#}dImuw)kf8XM=3Xdp8{wQgZK%hdfXiU!@dpaxcG{BNR>`vVmKFdp z^CvEHoW7g>D1IjV$HnF=g_!aix#{r96u4}d|JYHmxb`%ax9y@pfoy)%Djv|9idTKJP!G{eiuiiC4;~wQF1>? zCD-u?>sj}ihArd1XLQ0bup zEFx`3@L@&B?s~-bH4Y>bRuXR@JcZYU_1g({sKXvwT+`UKta6ZefejW+Qfg+vqx0j+ z);ht$IlfgHtOR3R<4*@h1BToB^m5Bc7MF9&7OwEy!V9;h3@C$2K(gtS9}2fRTqj+t z$uMj`kkJ{kevHj+?`PL7y0p^lZCkwy_B{LSs%AU5^nW3f``W(=PqxGJEA!oU_hgu; zn0=kCEqNuic(zB!5|Hf_sk>YA6gLIP?=kU@2e^DW%}R^Y9f&GH6hBPxXU0ZF?Kh)I z-$?9kh9Tr~gXiSuT-QFBG}3gnwyv(&#|}7F@fYIwH(cN97g&9+(ydC*LdJTFdN*w3 zIetXhyrxzM-?-6ITW}rE^^L1GtXan_)l|)(rC&!TfRvc;Lw49!)SToh`&f%1J>+gy ziaY14DVlvR_*Ly|dbh_)>!3LhNVZ>wn!9)JZb$2AlQ=}eyJi^Dc})VD7inlvU6#XOI+K zr~~E_F)BlO82q;{Pznkl{u5Z)T59DXc%v9Xn{KP2jokK=+WqU-myhd+TC4|+X)-w- zTyVou$H316H3)8Y&yv=5UxRLnW*X{!(?!oDT-Kom4MXg60DF4i5(7v?>_PYSFnGuxAVtUeJ$1NKx= z3e#*~pkF{pIH0N8r>ahcj=HXEZ8V=%;1%8ihgqlPSLS<#j%vQ9TQ0l<>J5tJX@9|E zj{ZncT%J1og5m;L`X^2B9Lpa&Xf5YwBbnKU(*k!jP~yT2agnK|BVjkRmz20vrpq-D+h=J=+pZf;%Os>RN_3GC0L zZTT;$p*i?mZVFa&6-;@?YnpA&&|-L^&YAk>o{Lz4*W;_@y4zw|JP}K0cQQ^X*lIQ# z5RBxRDSzPSgbwQj+#0iZVt4KbqAE*X35Y6Y91rzVe{*aAJ;CR92D*$p0o$zH?!) z-_ix~2^@~cn98b-S&_A%Oc_?4tXx=HwvZnm5*!emcX>_dl5gAfx%A-won|F_Wm92n zsa6u~Rg^y&Ub5tzDp+ygG$e~w^H4bG!?VdvaxV~n_fKF>AAesOQpZ)4!QWofRMila z^!!k!>~l6~{y3Ay=UiB}{KLb-1FF!)jrt#@82jz(5Lz0^lW@fw*>tOww3PE-2!Q&bi;>?7YA3nlxN1M6WI^JaU=Bhi8_|KH<_DTRTdk;gSsINSp20JDdy0 ztO&Dm#*$r*T~u~XaI!eo!zVtrSW1B=4~uNbBK7-nE0X@lgY;3vCW3dU5v%(mvL>_uo~bKn$s93 zjWZmQCfP$se3t7!u*mF^z?y~gS8nTh&~LhO?YyGWqpqWsuLE~}$?D?O`U=Zgx_%W* zU;Ia&abBQ=nnx1uDgbk_Rqn*?A{!@^q^_w^-+!+)G6oEv?OdWZH#Y~xm%QsinV!v`5RjTV&(Ym+#6b1gn*g`yEJHA%7-OAZ}NrEn5=8+}sk62UEHamu|fO41(|_7Z5Fvxj8drXFog#=MbcRtXIA zv`D5zMAulXjy0hvWGp-n4{r;Y1P=wxuYQ1Kk=4mhc&4!`!!r#NDQ9NyKJ=m3oVxDo z3fQ{T4E#h;EJ6M@4qV}AYN0WZLd}QKjJ?;Yi|y^#x6Qrq)#QH62L`HomsgBblnZCa z6`Z~~HEb^3BOvHE1syADZ#lZlmAj>LaS2>KFR3e~GYRZFSIO@zU8E9ze2Uz!K-b`D zv)!yk4S--^4q<(d2e*qIJ5p1T)WjWdIQks}kOw7qk`CdA(D(57Fy0vS?%m%%FwkOF zrbZcWK}U&7WfU9LdhKtU%&bhvoHI21>vbX+Kw$(B2qx^;#;H}wjnR3jm|y<=)cJFS z-jEP|Nz=s-CG_s-psphRV=v=Y_Wv zE4vh){w^3aX|mqp<*sAl?`NmYv(APAWwir~NJ4ICYH}=J9!mDbcnu5y?^v|dkt2C| zloE!Fu;GYzmZCobX0r;;Y;IEY2^KIrNy3fC0u-LOoBnaJ!JI_;`SjuR!F0ii>Yg*t2_Nw{6*-5iKbP)hYWo3@7ErU_ zN^Q!gSeuxmlX2ye6Vw$qDJWC?UW-q3BaRndC$JU(D^XGH^gVT{JjL3m;g>M$;Ia^~ zWYBY4yvJrWaHqKdfUqjxr3j22wvC3ek6vrk*18raalEzhWL9G-7?qh@M>vMy!#9pt zzqD%U`Up+rN^^~QWl_oGNL#rK^RufVtWv$Cj)~5TTy&80-_J4@2SAyDoo46DW2~N^ z#gt3$+(;VtaF`Q((EBv0stASSJc%8?acd$QO}|p|7i7!6nMzK(OHn~h?}x^&RpF@=x}20{7DJktVR@Q zoRL;Jszg;~RSIlqPvU!$5IL=EoO9kVjWXvvg@6jNEhmk4dr21W&0SWRpYz}HeP5~m z9jtQ4&a|H876XPuDlu52VxA64HB=pyJCnYqk%nPNt&3j>vv8U5QA|F{{3r1QFX*4= z&(+x&&zec1>qYI%DQ3F}#2Sq-C|&%bPU3V*5EoMT9U>k(o*^F30y6p~`Wk^g_s{_J zdOXk?-1CF19xO`{Q5*nsmHcuBGrhLa9Gc<`9t+xK5g(o+#t)YrmaaPAhWUIuHZjCe zL@pd}1`|N4G+QLPFe^FDx1WSUvp1dL9+JAD=u-@co%}Md7vTUwnPS-f(X{LW~O#6>ygW#EKp!#C#&0v^lj6Nit})WMbmBI z;B>`|3KOb^G1dBBEh*W+#kC@;>%jmDw+5gWmy8~8?Au)ap86S$SqH{_ClJCn2G>e6yodKdv@dkZ31S z$v=JXbIdp%EH?oou>>yoba_^87)CD)E-aTbY+HSr58hF9Aw@UEW)wHy2I7ajfQ%xb z?f+KQ|1ZMmbP}BjPOOm$yn(eRACC(5*I zU?50BZGbsK-Vx*!31e)tk!@x>Fe8R;jQdF~?~!*;z0h@_p>Gd}u${GoYEd7k+|3#s z2}wK0!9!BdaeaqKHw-?nr-tC1b7^fVh{!Y~Mt_rVz<~yvV8uMP42b(M3~5eT4Vk3z#uPt|f2dxF6!^L&EF(roeMdN0sX^mIgK1joUND81i1 zeDu6+(lWU_Gj1Jc^)MKq!ULcjZ_-$Ft%a~C4ICgK`}h{Cstl6p#5zj)qoJ8u`KOC? zzE=UCTy=jBlcjaeXTmMKqz=wRJh*Wf4bCs8*-w5XVuy6@uGE&;^;bYT{O_`gGru)O2LU#ShtUWd5leY7%-9tiqn3US6J_lv`S!!7o!l z8$eGYxcgN0eZ*S8pNuP)mU(EAa2ag2@4u0*l4QkiQMEe#)$8(*h7#lrhUVNiDF%aq z&}sbgDw6co8WUx?zO?*zIzq(q7Fk_*S`s+@{Yz&)B?YPX_Hh z7|BvHo2YFWs2oaDKAl3-L`r=rimMclgw`0;55`Qn#_g7(m%KF+F=!z>WQvU-vkqDC zsFI}V`0HRoI7;#|pS`Scs`>Mk;bFg@*DCDm@rLI&8&)n8nbu8jDu>Dy2h2B`<`)=!Bp%(YMkBj#0Gx0bQr#D#Y8~+KmQ^%TUxecdken zWWvum{5e+kJYtW1{AJ<<+k_b7skNg{Qc4@1}>$4Va4FKHVe|7x*(%a{CP=;j9YwN;f6 zhBt4IZuXVJ<79g5@ZrYB-m%+nkJa%S_AXpF*BN&+D38Nv2V<9WhDn;hQY>oP?IN1( z>6e6L<`%Wvcx=APJL9$cKao_GKsbO2f0&~FEMUsLgRuK@q3cWWzNAtCn%44 zMyPSWycNY#j;VN{na6pUMrcDSO+MXgwto3P76XI`)Wc%+KnQFm%@~UK^pj*ikv^g; ziHs=Lb@2kHivvD0$$m1zKoDE9vOXdR-dDauQYyddBl_^nD>X|;^2d18_j=hfC) zty-HMA^8g1F}UN+Q5nprxna7HO+9-kYDc3|5h$@zff3jAVbYOOo=BQ0 z%o0ZRzn&2yWv+2iW_1)6IiOV?swL|O2d9W+3}bUhiT&Aj0|@HedN@W{P*$qcaziMR z>AFT5U}5t-<|&wDDM_JuJMY+xnhC3*h07k<@fJ)If%+y2Q{-m17fUm>PS^MJG@wOo zZAAoiM=xaklDOHCDGK6-3;`S~Lmc!qzoApu6k9MI*?zjr=pzY$A2eiY2h&74>-mKB zc^q9TBc^6Gq9N-3KZ|*>GuORQfriQoMdTY-wbG!z{iIFepn1p1f@uFI+JMNjewtK& zT_80x{RtE@;-Z7Q|8f)fLSi8g3KDRoF?^mi)q#?j10x4oQJ5T+vB3Rj+gT8MVI>RF z{4}he6R_OPQ9s*ueN!iqI_jd_zrjnwmz~kHgmCc$hmliLSTBy?Y`)!L={r*>%dy#c z1aFWe^XSiKX4_T_b#G}B96Kqgy002VK`Z#1{UrWnt#Ocr-A`6p=Ubc#25g%36BrS? zg!IZtMPQlCHjs<{p02coFe1SOx1!e7uHKClBnVc~P>5UmoJNx&?l$@&Ir8O~u(82_ z^(ww5l($jk^~7{eI_q?4s^ZSwhEzZ|IQ9DZJc73}Xgr9f8@qkoHbYeU>LUFo?7a{l z7tsokiAG3A?d!{WatPW~YX@R6we1R=vJp0e3nkfGRRvSAoHMs&m|pg zEbB`!hOYTm#()j34Q~BO9Osnd$wk+|fXTh*jtqHI&^-J$W*X(2?AL6*LmWgzOOHQTMdmO zHI+I)Vg#uv$#>iyIk0mPGXr*6v_4ab_Kz+38g|FQ^ z-KM|7cgN@Hx37%oFC&(FrRFjZMu5kk+PI+-AxXG;(+21`8mdU_$cR^Voh8r94P+s= z(N3r+9)hCP($dq~_n{6Y+TB7!4>Cf}LTUuI6OHagp?CB13jDyBu<9Va^BFQhC zzD*{ole&b0&Xi7p5O@ooqxVX;m2M;I420F{I(5oGr0G($MD8&vTU}+!Doo1+i8-?t z+&e%GjTs04nn&;?P>*z&qK0L|Hx;bJ`M-v0V(8K^a$&IwHT`tA}>Enrozj7q=_6%f#&TV~Ss_rcg43f&J!YL&= z0k@TfPo%KLr2}~Vlh4IACM_SzgXbv=p2+dSABpRdm)*Ek;c$e)4vRAItsao4hJ>lU-ocAQ>OU^@6MeD=1UUsRw;XFI^c|!G6=+7 z+vU=)m~xslTd#yTZ&aGG(b)_!UYS+3BuoM(EhSWXNk*@hksdUXSQ1jLRZjUn;Tl%} zM(H=0s|Rxq23Ib85zo(0TKg&I6g&>U%^L6N-ZZ>G;iaqSgf>CJe%&uUMSX|JLDEBl z{l6~q)w3z~jeNV0bb8y!$oV_G!V@dpgD)@cmT&9TcQ4SE*cuMyk!*sZJj}(`o2_@0+zzM8-C>uJTZ&!?nThji#l9{M=A5`?DqEA4II6T~f|p7T7G!jHto_xB30N3mC!Y*XOmG ztk%p-BA{z$qgqz&VY#`Ng=Mmp88JuXVcN@0mzS9MT^SkNpoc zH}@nLdK`I_GV|aAy_v!PLaQhKcma|DQ90#^eh*E*a%ZDIUcWp(eaNb6b^(gDb;rVP zoCxdFRGwZQ_95cm@7jWzR?TX&Xog=gdDtVPfvG5pjS%_>_>-dE#t1BpaWw5HW3uhM z%G=`et%0Wf(k7*5z7P3+hLV!sg0IYtZY#l2=cP;O#Z1aoSZ<$F?b=XXQtm%S6&3#g zfT0%-Vhqk4JSR16ofV6gf1I%Ra z`9J_GE~ESm`#v>R$1_{NVsg;h6{yp>j^x`;a`2re4e%n$)vO6g_VrGPMr0J6eVZSB z-wY7B2|C}8l(JN%aK>d1bk0;%Y`n6uo1!5rOB+~Dv4U%E6y;XXTNrcSLBD8`3?B@>=y85wqO&EfI5=(M*oGWb}O6I0zfXNEs&DZY=HI zt}LY0z>jQ-w~31irJm}vVlU1&Uwn&(UQyN-#BmCZU(ND=(bH+|?3;0AVM`Ah z-zU3$8Hwi8{E`yBNUkcFl)34HIdk6*IBB5H?opWZvZ=+{VtON*=JOC5CP~5>A^%_r zrrq|I{L2R4Mf%q@FP^m-G^%>+wA~DYrNV6$O@? z0kx!4rRoOv`@b!(JYlf&LmLT(USJyh5+M8qX zMhlna#+6H!OVc-InZZB$*xMCB(8ip1oza}Kz((&1c)qKuTatYG&fU+@%xyDn)q6h~ zy}5q9ad5rtrLvTB$J({D9lSAPG#SV0G-e6|ILZtyvLza4Y!FIXkZ#+>*9ic8*x%0( z#ee})6DLBANxq2G5u{ww1EoM=Fdcmi{wr$(CvZATnzs4(j_rkn8H%@`qwd~%+W;HO_V#2|*=_yyH zFW@G>YQyvmt7dxU96z=(?fXH=n$0z{2aB73__d2`FD_1}C09r@0VV+()K=5FC< zwqp6(?7JKw>5a#V&b_R0I>|*Mr1hhZGUROyy0_d2ltyKCH1O9HQ^7it$z&bAV|#R9 z($K~{&;G{Jc6SU%=Q@}L$9^56;?jS6r@vqy4O;QIeQdl~8@&8oT`%X+NqGnq`dM=W zuZVGyQDot{IY_~Rs{3~dR*X}Df?i64cHq)%Nnt8GMbH~s!iJQiG{Yrxz3QV$v4ObH znDj8`oW3CUwHzBl5t*;!GT z<~Ao5wFN$l=uHx&mC?8d0JCQgdvlc z+50tYEe&*}EOWzQ94fa2X@fNV=Sowf8snqBC4xc`FjwAw7n$CjF=tMOkMXI)0Z#0z zn(|mDjj#_-^&b#bUVUv5$q&pqDRXX!ycB+*ACFWv5Sv?1HiIq?;{J7`CEfh%{?MJtFd>p8h8C z>7roJrll$zTXB3!aZ!75aeGm53LJln0G|(~4?lg>f8u+kG6&5~T&peov5Vv5B)sdx z7%3YQ(cGM-%U*t1#F>NXKeIiaW+r?sFDbk}WQ08@V`$+Y8w)iFUxALe+NT~TFN^7^ z0xw5O$b1?41tP+;&Xy0#PdZB(^!!Yun5v4oa@a)j8Vm;KnNE*4&$aJ$6?^B_&#tbj zu3o;JDq(e3vrwO{a@>MZLqI|y2f;<@o5QvdIX|#wlALr`*lXL~-X1@tsh|}WHs14U z%-y(1+I61@1Yoe?6_?&O1?A53!$W~9m%SZGNVXqqTD2-;b0*78|56!??|$ur**JBP zm4RJVwPtsbuBW-di4cNt2~${Z8d=aX?#q#34X(LQi}u-;bS}(evX#|y{QehcY)yJ1 zil|{PnVwOF&!Uq0fKaJC%K)&y&f!fQX<9H$Z+#<~rfY7>Td8V+F`8 z-*;?WW-xirywmLC#|>7aJ|5NUt!IuK!OOjbYqK~{@aF4)FI&2NJ2T@d@Q^)2(UQm1 z)f~#6T|?9dRi%^YNyMNn0$xGgCRRJwu=$*_Jpf6B!5G%p1q}l z!DLXW#~Ex8bEvJwY_}-u7vJk;)}XuKcYH?f+_2223JQBas=zu74|XiGM$Bi2Q#mjJ zf=xx*c|M_~;dS%8E2;LYOekkOJqsH=y(}^kiNu(S=TJ&wTDWkz+S{bMCgLsL)==0h z1ln9>HQAc7qV578_b;s+P&xokYb};i)|->D<7(%*l+tZHE7UqKC)^tdoO*9C%F|qV z7hBcsiQ+3@7b)@1L!Xl|n!p4FXHzkrGTC0xYVNkLIS|y>O!+jAO4GbyXTe9JFnK@bdJ$`r<)Qjj|nWB1<=Y2n#0 z0GvAT3IS)tSR2Wqld(s`fXuSt-wIf^;!{XQ_U32(LP~NBgJV>UXYMqnv+j@x%JMW1 zhAEZ=S0dhxN8RkZ`$;zgc)y)SETkwELw={zNh37H5r&sXac%1_P`oJW0jDmxFB$14sSbe`@zKUUtLN(XSSJ#y!Ocz^#)i6N`t3<-UE=-9EDfgUlbx(^fs8t!xqI4*a*CUYY)m*6jd z!j44BO>3vWj?VNEK}_5*qQwC(06YeO$<7qzFbbFD5U@TNylR?7{9ldgoIhQg6;xL% z8CcKOFz=EzYnCi+2OTY+s*DdM%eg+OP*R>8k#gi@Q(MP)2ioaK2x;qAxbeQn51&Gl zQZ?;=gVExe!`=erMCAzus|x-XBY}QXKC2o9irgJrQQF-)*3F6#wYzs4O&Is}hdQK& z@`*(>*{Dj?7nJBwNF_uUeJ*{AF61r4zM18!(b)<%q-7X*eL#R(|>3 z&AWRR3$JuPn(GITn3$}ZImUWVlig535Q$JwTszg z3c4m5E8f;mCbn;<+S+=k$$i)q#=?zm2TJQRGwZw8U*KC<_(nPL2C-kcMNK>p4mb8c z4`1@aQ@kdH299H_KBbzF@I$CqSlkMQM5+}iyn{2LK5(6K{|NN$jq`bi#3mlpt#}hk zt0M5oyhz1IvhWFILipcU*i61LAwJjCpyM?rmhqsyiXJGbYq+1}2dC`|ZC1LyPi{mE zT^|I5?w6*}X==hz4~zT%miVZw%%-f&C_j-3IZ)ag7A?8fG?(6Th`hS-=O^&dIPv9^mn7HFGlTUU<@xY>n%_RSBT{vaV&r=FH%2&dSQ+h=}1H zE{F>;_4^02pmJmIkqAMIF=pQ1fclsU|J4Ug!>pqm04|__39`<-#Q&*lbh8t)X6rOX zBL-F%%Ys@+5Cci0gD8gC^Q?q=)`+#&B9B`f%!0wcNMLPCINL+BnD$riV_Y?@pehQP zpbYjAYc@y%8aekRi(>}k<@@>*`isrET_BJLs)jG+DoQ06DpZO#m`CtnZ~J?keKjy7ER*h%H-Us12tQqKIjfJm0Jg$_+6;oO5lID2WXz6PnD9;5Spn#v1~^T0def1 zO)}RbgaTRfS5EdK+b#K~(s22Hbv!VstKLYO}eTl2ekHx#~;yFl_xcN|`jG{!xbR?DiWV0d$|TWJq=avbq$$kh9arv|Hy!a7|>;!-|P1&ePBS zbpxU{vvO5WsuIl;s$GM_E5M6+`(yXV-HCkyIc8%;on42!IuCT23dI?PV$(h_p_yJf zagmtJpaqN#n-gUc*}m6IZ#xEPRTQ3I9QRs~dZq)ySODh-1q=yi!^%am(_bLawQ==C zu|B;adsZH%@$^R%ZF#ePegLP3I;c z@Qim{@htt@Am@s8`&8Gt`ccSzJVoBiHO z7#-6eEda6Ajjb0Cut_kcMST-5#5wHp;;?dA$NE6uWY)vUH>=q!ltS;JOa1?H z$3b=2Lgm{60W`Ttu0+{t_ZOSP4ox?yelY)_DrE1fiNy<>io?wRG(AzJo6}XWLU5t3 zqApyR8NNVC&v!+(?3(+lGg6=T!?R!KfM>7#O;9l(%eq>kI7h>?AM)~MyXMSnDEkhc z*$USjHeWT}J`Elrx#98wIp?@Pg1 zEXQym82dU4Xg3GkKmglnZOj^33+pSpc>$MK!O>;zl{|}EX}~BDBO{G?Heli=XPZqk z3>7HCx%Y!a6+)Cn2A;$j#o8cQw){Hz3*OTs$DQsQCtADs;_{w*^zP_O8#c6-R4G*@ zZw(~x=;*j%>jW1NOdnw@*^WlDUf=RHR|+IAy#NK?Lp8-1AcS~phs|J1^QNP%GihF4 zYj7A08^j2i4#w|Ok8G6@*O7;p&N}Weu%!}NlX;=CqQbE_{^JN*ZnA@ zfWniU$pPbRZ->MQRwBoTXi?O2!Rk+QCDaoTFb zoTv1j0TN!(YN^B9@7d_=A-zAMLkT4*0g^aH{FaRinPbwZPSq{H43zhr zUot5tRagpIKeuFeDNJ$xffmwe&yy`;s*x7)TPpW`AkZvZE3H-ORH9P7E!57~x4&1(@a zF7Lj@g3X1eY{OULmo($qDztwGiu7WIw|ffUfLg910^V#imaR1t`ay}x!WE)gxmwjG zwcRO2pKCG*nd3;$uj=~7+f_UJ@l~@0dqjMdt(gt|#}CtU#>fC~;|nfmcyGzhWp^Kj zJ&S{!3j!#)QXSo+6%e9*fBZcT%{^Yt^D*b*&2H($Eq|)?CbxJ#A65KIa%9fO{=R-) zlh&8|DQCIq|6fV2`)jXRbn4ryr|MPb3toB;91+8?ON!~~&tkFS?{mHi%o)4=p8}Lj ziVquy{qSz5G2@zktOV56cmFfYFzFc3lheAL1oZ%w#+NE^dH)a;wEeF=Sr?KXo}~}k z=sXMjeTruhCc7244DizT52}|=g$X&WoWvl(K77>p*K`)$l@M(d_itej6+B5g(unH4RDA>Bk z61gAUu82(eUNj|qM82>-N{eS>@q^xe$i*Z~vm~qgp@sq#*huUK(K}2-By^?!h!sx` zlR`^p`eL<1$Jt`wWrPw*Xe1`j zu?T4B-1OaOeCXo@=sH(dG@F{%<2Z)Jw2e>EG$VpWvubP=Xw!9&o#qIPQ!9i;L`3x4 zfTeG`VQR4BipPuyB&cBY%bO8TRwED>P5-6toEW+AD^^xc&K`8_eDtls!LFj#dEGE6 z?lmZKO@-k&u)&YR2z{q74pG8fslsqhO4SbyZwT*R(I4 z;kn{*xyD?T2RTMlHQeX3Pqvvn@^u8U*Jd09dc7f4$`7)>5OQkJ^y`m5{`dbuI#Q>< zrhRh2{x@&z**5BtxlWZ7%C_zbv*rP~TdpV_q+`P8T^eT3AQ;Alu?$~(ZRFM0$||lO zvyA=YxpDo~@yeh89jU|KEg7W~JC)B{mM>wQ_#W#O!^BcA@VzqDW$oX<_8(Zc;egc% zo=ZV(y-o%7ToM*02klzP9R+rD@)7mS534Uc4mi`IFoE3SUb9A7E9OnAe@ROo_q_EB zrNgBdo6}77+(r4H3M{h=Rk`hFE?BNu>>gZst<1kGl6v}Hr*SPjr33>sK6t0^HK1q! zJ9^9KZ+h%1KkeRov68!OBnYntQLjU%7UX$9$kRS zqJr)uK+8GDqwcZn;T~9Ya%M0@1@%2Uo%lnQ__<|WTf6W#n1^}3civhy_wC$yxp;)3 z@#X?{(t?@|A`${2y**72C>_F>h!L8KaRkqrUAiQ-*B3OHL3xsW)jXzHoB0@iB9A|q z*|Bppla(CY6AWUE#4;|jV$-d!UGqur@9=lGYXaDcgt?W+Hc1^H1mBF&qy+{sKzjys z3fi89&;c9Xh*2H^KN?g1gOir4H|N|Oj`ud~eJuBI@h`w_w-U#Ymmg<^G1#ngzoZ$1 zF_2(g7rVp|sSuYnn+{pCWmdjb7Oe+8fxtFFy1_#nlp7ncPEJzHURlD&vAg{f9|+WH zpzZ#bY0o`QU#fn&Wma)fdNmWt94X$D{1)RFdwk-a?s;00&T;piu__`iqdRObmQ5Kbu=ei-M+EJMxQ!VD9?rfsoZ{rX?-Dms^|G-xq)ik}g@I zO}~xrbGpH>Z{*^z!7zbwjUqGKx+=CdLhAZJ4%POTt+cL-h^O(bFEf6q&-ozo?|-Q7 zZYpkYCj4_}Pul75i2c@sSp{KRabsJmttJX6a?&SD!%SgPtrVU?q%H?eJY;!lW2k(^|g?pKV zXrdq#yZ^r6AlMMOowjloE3AYicH9AX)$#w$Z&?bSebd*#c*SBu;pU?NOx>#+hc|qSy<=>H|wxEEbr7~U{ z-MoqVEQM-qrRKHtbaqk|6^>tLOSd{T@YsVNeNoHnCL(k-V9l@1Q;Iw3(U5cQCkhjbhzY4 z(dtSFcGW&OhLa#&GuURV)y%aQSjYtQT_f9ZOBTjG#Ho zW5TnV8nYu!;igDs5D3lmDKOrPx7u-R^&0Vs6G3!M;lS^0_^f))^D(E%=%QMdBT$B> zYvwhwc=4P$Gojex5USZ=q>QwJ;#;)*j_~En@@0QLX&k~qk(QP&vw7Gv&p3}Z_3W!P zcde|Ew9OP4h-eo12CDa|+nJ%lXoltyISQI>iLpqYmjVHbFlX0tpJA>ZM1V*p$<@F75EOc^B zg4bKGAWZc=V9c}^SIc)DH`C;wP7=wye$+&g4VzQy@iP4$zGzUySb2H&v1l=?a4egz zIfNp!cYYHQF@&!rYvo}JBtY|yybo2N70k2wPc0U^(P3@+60AB)q&JTSuY6Y4JZT*| zlyf4tQ;6sWif9edh8QQo8$%_YrToL{X8qHi`A?7er7mSI1q5{Hsf?I_oR{|W9hR~zl8&X(PEp$@qte^g55rTLvCLfpDaPZ{knSm^*8f&zrOnMsqbGZ) zao|S~k|YS{OA333#fR^=?yCFIGDr+s9)e zwzbL4-cW{ole`kcynho20*1P$qD_U5{8zcRQOaJ!-)wQM(4b{;o$z`><39O zob-TwTYApA7 zfAaH({pivHknUPdenP2NZ;#ltB$q(N)@CDem%wu(76*i~Cx_7pQrzm|Zf@F`$8 zlE7dTgWbwD4jLT}fD~X*)%_1CgIBEVjBI!^$Z1Ecn=nhzvK(ng3i~wIBZZ%(}_z!$z+`?GTVwzTHx)_aK z7zaA8od_8oX9m9MN7E`m2uR%=*T~7(^=|tT>ohl$qi9_6eof6aDIyGWd~`9Kp<4zY zrCSVZ11&D;ttWoVbCZG?8$a68BctU)MxhgKmb`fL#Elqld5xb&UB$9hY7I5J3B?*4 z*uiDGNL?9+1;&n@TT7K0Tu}4u{F&6kSWHE!X7QRM%7!xnw!))Yg~U|7CZwE&?o|kC zncLOXDa{JIPni-XXQb2Ru}vlyg$cEKpS|0i$vO+dx?4w}cpdVCygvDjRc!8&kjmr7 zDLVOyM@AeSL~)~eWDp$$*0T3WtuCugP64%P*N>+>0WKe5D; z?eFw*ky#br9zN-{jn#lT4X!-_x zArZ>?v<-_2uWsLPr76RRO<~52rYjrTtHTkBN4pw9l%(p3RQjwr32`9?LMUM)j2e^$ zR;2m);hOJ)VxUcsS8w!?mp^AQLErrxP_P!)ockWU8dM|S=gUnGWAi?gwrGa8bjmZv zqg(WycS|ykyF4`J%b$-B%HMg73p(%fUb-#A8lY=jd#o^+o_wCFs+`eZAy~u(xXuho zew+nBWoY;ozkbsHj$kN^V*PIydffyMAJOF!wp~3QOhX~@X!<|m%c55Wz>Qe4L_9Fu zH#6MTMRj%6|8(?dVA0%`ioh55s=wwju1zAW1}&hx(bnd5|9#f@DR$9cM16(E@j#P4l=#6D;MR7vt4K? zDD_{(mq%F zZS;79zxh~usJ|nH>8B1A%>LCw@$Wh+!O=Yia$YUVtbY;5tT;Ft9HcxXf*o0 zO;=&C-Yt;+2f5p$g=uVgK&=IhK_(G`sa;=c_B{&uW&Wopi=VXnO*us-h zRh3jh{z95w1#_cWS>sUPR;BFuoM^?o5_eb&L$C}{_0ZeO5?T^$LZMt;E|xQQYfxkG z@_OSLCuI)Y3VQb|6Q{gXoo-RxCu&u+DO@ ztx;JBsA$!psu>mU4d)argJdXb-qt^RNiAbJwc0M($z@X@+jv2{@A6K9=;G*gE_yQF z2JN>q=FC}d%t)&&Trrpn5G3jVc;Omo8;r!VLQ04FvUCMuife#lPD&7i7(q+{3Mj89 zV2CMD;o%{1X3%&L+cj#O+x@P02Z5)!gWc&m_gOlc=LVNt!d0u~T?I|Ajl?u`Gi%y( zR_V!~4NOBhIpW$b`X0|7g;98P!e(&vTLZ(QcZfVu!Zfx0a^i+CJ zw420t%_>m2PsMPbAS%V-Z~%`Q3ugZa?U`u0Kys>=FytrU02FS)djN06Sp;{GI{^#} zEYOz~xOO916G&rtzY8LsRF?)WAOCoKPTVZ1-@_<=`kVgay~1fI?5tl&xP5I0H;YTG z*)_!AoJVf6#^qK0y@6h~PxFIbD0U9~vY|(l<4;6y_9p@zh-J1I+pUsfpUbAaJZ^0{u0k3mwjjV z#^fIG2)-p$B77HP_ib7DBm%iVQ+U;XFZjgppaXIKFTWKqsP^Ui0VS5!-F}ovQKoxh z-%0N~NQHr%BunYVQM{Lz9V%(w0x|ux9CwP_TG$o=F`E_(gVGIR_lsv%(B39|l^19E z_J$40r{f0kz+}$A2yCEV6Zu;Ya}u6pZvGAHbt{AUc{g8i=!IUC8xGA)*xV1l^|S5+ z7=z!#5X{i(LiAV47}$<}BUtWzNj!0n8W*MUqDZqig;e}1rHLOS5^{=^_<3pOAGf6Y z7p|HWdtNp%8G>2+Kxq`9*G}w3Y?K(N#Rf4KPU04$um~4que}!=m-I@q;B`S>A{KcH zU5g+{e%*D^I0rw4p5vnH36Gi8^!sWt`)GnYw@wI$6%#RsIB3vsd;e?;6iMw}u}$51 zpUp1Coe!y+r0Dlk>B6Jh!yAb z)8~eRWo8X?#=~nZ_Y=u*61=l3Dj_Cn#MOg0ADty<9W51?(pb>VmY1kyw}#XZn(Bf& zEmuPYl-=2@8EGw$33Bri-Ewk_ov~shSj-|MU399RGcDO-;+V!v!x7s4YKx`0tLx=t z7mVXwNY@&?RXH*Omft`TD9@n-R3T;U^p-O?AAf(k+5e01@BQs^r-3ydLu6cyuDW@} zP%ZOx%VLn3rvPXd6Jmqfo)kW0K`rbNX299d4+6B)tZ;Xs8*jKzi7a_U9a;v+yGAxl z+Rf8AYPL^qb2XdsLL96OYvQ@VKQyKZa{if_*7!p(SH5P!r1p_^UFJG4KSChH=-z!kTxyS1(d=t zBL*B(l77drYF8gvt|!v^ z#CF=O*^(hJ^IxF`A4%9kggzcPk?RHSEFB=*9a`2pjf{fD(vm@xS z=JKN#V)0mUzNs+v3)(oS+@w7B&OT$OT&+GKST}cWdUAH#^dnu^dq#$rJxu3$vvhau zuMfj2dYKV)znK%xUtLVdTLiLCq|mJO$CkB@pPd;G;Y{`zfE1{0 zZ$z*vfLNd-q{`3Rev0GPbLyyG)!V zameZuMLPXRz$ zFu4l(r=Q%uKWc!}nm z_`g4wYCy1L$V8HGf25y5A{-6G-gqFrxdmt4yg%oji8T&g-T2E8w~0Gc`<6RLHs)?= z(ocAd@#M*XRm+hTuj(C>>ytN_~n6c$-QeiZeP$MYM-WOd4#wl|Tnk)KN|bR(f{I zEr)b?@>Y3p=E=8%*LW^Fb%rhH?n+jWEOHbkpYH{JBNRDGQlF622)RVFbFH_*;e%IF z_}Xu+JYO5upb zB?#JqppR;sP06z!NtWzc8^-p5D=PLZ4Pk{Phc99Kn5>GKv*cBIA;%Wg;VFOlGlG54 zjAM3Ff5C*LSsn=I7sB2gB`D^*5MKYT9P4zM+2aJY_tA1T5O7$TfuQ5PZf|wB#9sE0 z>vkDrkM#Zt#VnY?jN%A*OxV#*XJQ?AWaJM^Kfy7at7v1h7=t`(l)>xU6x`1T^2yR4 z3XTAyaZzYb1|pGUAU&ZXe8l0~Pnx12C$NAnE(??OR>Uqx5H}vm%Co12Gt)BvIFiNBr)`#YQ1jbkt}U z1#$4{31%jE;lVf-3<2SWfDX8N|HQK5z>8;3X8j(Og{MFpsGn9SPB~(5?QyZPH1XV| zAK|vpC^Rax4Tcz(dDbPyuO~quPslw3rmDNnQ!?2p=Uv$_k5c=7hEN-OfcqSMbzb@4TnO?rM zBJg6GX~J?U4h0Z3l<4(gdQQQle$K?6!tpqoV=i2CTyFvcx^MID+T|d;Wy=%_pJ z7sli(nNS#IJgxkX$=0|Ff_tK#`CsKr^DjW8ax#OAnVoTONF102bUJ#!NPJ#W5OJL@ zAza6^M*UV8HcP`R@h${LlBzGUa-iQ$l1e4IolzY&2NS+k{RA`obmWqN0$Z7IJrE2& z*-?m+FsIoxWrYNVX>$V{HiLC}Uq!2ppVl9ncy%Lr8+#NIGD0_YXQa!}yz%O+RY^by z<)DDY5{!Py9`Mi!DHevo;4?&$Lj?KU0?;af2th^^zcL}_qX-E;-wJu4s-aYg@O_3+ zqWg>Y5yqx%ml7LSHzp<@&Td@QIEzgLn3)rE8^F76R$E9{Wpg^;O256k(}~&XdQS@w z9`<=6ry}03V^NZW@CnE6-REAGI!WET=fp$$myd+B5Gu|mFe5wR&1cNww;&4Advc?j z8gp`_KKVk-PNRvUjN~QmQETG6fpsR!q8+Z~Ak57i*XEQR4+8^G30lm$^;O=#6{F?< z<~^Mw#+T)z71jA~3$#DYD@aSIB!oZ-q*V>*VLGIS+4r{JV}qvm12y^ir~V$3k5L!o z7pf{R$ktn^s!9qtM>_C=NF$2O!GtfUar`{!(2)N|g;+X2O#%zo3FC#@a4bewRm zHd1iJgk>9og&JT$UrmxM<+5cRtV;|I__J+oS-QbNU6wc(3@`~~tIyJfz*=&@{Ypl_ z8R;ARL=zpU_MvZS^zjfRw;_9pl~LtD5I@r0{q24zfVRmV?q{O&_f|mOXlW#!j*BRs z6cstl;Ov)f!b)#OJK>-hwx1l=;~L!NLFsidFe830;iPU3d*gjdgR408dCu80PU66U z@#h~;+p=CiNfvUsYE3;Rm4lOm4r?0#d&`!8Zq5dbNV5AzM#f?WW9Xv+VQfS)*z{mR z7j8xoR9-PGAMQa?d3jTFadC6=A=KB9MYJn9>&wf#fZ(YHs>f-)mGI7>I6^10)nF$p z&h+gx;LW8bKxCp!11VHLiD_XBRa6}8C@!L|a87>$LR?hqx}9oqt-jh~Hk&{X`zA<< z`_Gu&n_=HN_~ZJGzN-%xr+WRw{E2}dylVGCi;Db|m=DAa!#5_{8`BQ1!alq*zW(U$ zqx27k?*$&($^5T1_Z=lnzxU;*DXET(;^!XTUyCbiR=S0dUldkUsd|-}D7{PszcN#=nD#-vqIQeTV36S26Lb)Vl(C)xw$KR!{lcIeN5q@k>=)vKuP z?n2i6xpQakao;z4Yv%%aj<#Lqc&PQ$(3L}VjJ{GhA&Fc&N_RI{AFw809ivoArasoA z`Da3RH`UceE55Lu3&wULDxvB&6IgSPjK3!66|Eaoo)#WEqLU~f z7<7;>a?oma7q-(yjf4?zZ3QFmhUB^Ya83=xG!{Wz=r7tpD0X*^&-|K{KEV%uBk9{4 zBUl3oE7y-u!p?UP`nv9yT@aq?1Xceul}jJQQLNXl zG@~{{?^CFUg|m8~S7Nbv2d{TDm<%*h_4So#$xpS;QCh0QUuA#B?^Is$Qtu%1IF8Ci3~069L$0#Le2ZP`T7R(OVz`WkP(Kz;moz1 zs+TUhq2eW6qRhv`FB{U(OUjT}ntAr-x>Bg|wWQT@1ehP!XEs{Z7)pTkKKWnzH2rt7 zhOtld5F+7Whkkdgp&(>RmD~}xOd?)pQSCI%R3z*2{*aanB#K+1^Q|F(!%rn9YU~+jEXMigP&+oij!dLMD%;6C{)8kX_74VdIR^ zJYV>FWl+&oXN{`XIMu*G*#pPcp&Rz0ts=})V!HpXzB1{ry=AF!%GEl>{UoE(-S6cg z)9ob`Vdr!LZMa_{22mP%*6FPzJpoZhH_t8wN@8lxZf-Cny#PlKuA!4qCokpiz7Ors zTqro5mY+o3cwjBP<)ysJ8XJI}o`+a-M^U^Sqmd2*cPJq7epa~TKn+3pcnnthiJsON zC+jrhV2BQ!(sN1p>$vZ63}Tv5-0y1$x?H=)Ppf{-_>?rn>_nJ%HOlrBa_Wm3nbriWVIMlY`0JeHBo=PNtls5e+y^|KdrXTB=GYoNQh5e=xI)apdmmIRMe5oPFyS+UKk(Zr6_+8xs?;6_7K6cOq0PcbTz?=}%{JT%hh} zW>Ub=i4_y`gc1wLgoGmnwE-|&It}s37aX+@oTYJJZ#mWnpwk~Yyo^P3;6_m1UF&CD zb|AAV69`RQJPOKqqM}+k5ZJmu36^ z`OosxwOocrpe-dbiDaT&8%1btPp$o>+M*rJ%|PUujXyyWcVyu;F``akG$KtyjdR91 z<%9#WLUc%A&|1gzAQTHzk}bc$Uo7J7IA^Vah8QNpJ>x%__cP~jyX{Jyd51*-n-tj( z?hGycG&go<(1ZR@othxmAL!3biy(5F`SA!wVk<9bG?k(6U;7F=e{;VK2ntZ`R|$~| zxH;h-sA|qFL+oYn?#i|{n1I{b2o^Ek%xZX_x;frv9oOrr+0MK?=WNP2UQr(68usBK z<>gSsB2I{a1w|}a0N7XUQp#n@-7?v3rA)5eVU7`-21pjjsm2-ITD6nyhfPmomZAko z-_y%QQF2g6obD`8{X2poPZW%~hERgB;;H7`S{%he`X&B;j@a035~1^GK$MU1O1N(& z_SK7Dd@-|tm#d7C8(f(Yp)OpUGPuJlHvvby#Q(lc>h7k-^J@Za7=`TId1qf|m&b(y zKO`w+@5O@tsyep62)a&#pbuGc5BCZ6ZH^d)|HBY--~+r$&I=47ylWTiexoIr#&K*k zvZ+WI?6$5h0a$dcw*M>>WtxSN2CGUY&%lJsCpLGeAzy;NYZxs*+hMgB6ci($YXOcz z96~`ek(Cw9xfjWd#&L`UlxOpc!99`D%?h^aH5CVX<$kgSunpQ8XN4V>JQfmSwfeh3 zze!rB6k9U66O%f%>wmr;dhwNOf(uLJ45jPxPHQ8OSyR-oLQUBEK{3q|!%(XBtB16d zYvGaj(*M(xydkY{+her7s){Ns1V9+G+t8URr+kXjW+-{FFV0Q_>c z6eJAFq zuI}_QcHluUFmYSzj_h93oI0y3HBbLx7n=$7>|(o^B0V(PrIIk<0O%@VmSt3Wjyt=F zNDT9p$PlZk=Ywd^J!>7Fxu0*Qxj&QnmUk0|yKo)Enry!xVCu<$K?-uBPKYuPj*+L( zFb#Efp!iepgCjMRDgwq3|KrL$V9tv->&<#=LG0>$a8 z{wnh}O}jv4UVuFmrh1x1f**eRCGVG&Cu&0H_WOX&*pkqi6Dxnt|K+D21i0T&$Svd< z-VeIp0?$C{zOU&ykrweGLUzD;M<&_O*vwkS8fA5ZD09chJZ^6<=L34+|DF2{i!Fir zAK-h%(L-|OE}486S1#l3X6JjY(yI@&`DyDZz9YoyifF4qnWx{?HsIBxG3a&bFt5&vP;P#+6W*pS`bI?oDCoVxIj}3Lu0!NJ0aMU+TBeR6?5SU7;~GzU9p0?k_n*f z4Abjl(BXvx+Btsbz^60s3*koPrk9t$3Sx~iz+Q80*9OMX3ahuc>M_6~h9l;|#Rg8W z9)>4K_5=(h(KF65SaUk1JIeA8t?c#0-sCCcZkB)8oXkdd>`kuDeNIwSxIe$2pjg*E z(F2vrR(~{WqK^|stgh-g08N3mR+Pj6{oE2rLs)LSrEH&}sUgP8Me&*(uZ7yI$B&s? zSh+-9={Uj7&%cU-^FR42H1lU~qkNMhR}cS*A6+$iVxKsCvalo+=6v-}wefgiI>^U% zgPupHyNA<4RSD5jv3ugYj33ZmC?sSjVzU&SmF zW@CPH?HNYxs@i!)>glCvzN6zcW}9s?e%ZZ=o7I9`??!0Q+{b z39+CgnQmRKK#9ukIuFQX2b_0hs8Vz5Iw%R4UEutYtgMlF$Gf{Ji={;o(wmyv8=`1w zL%=KFr?S_Uz|WdF+_xsPbe2?Qh|pWt1F4%o)>lq_$J>!Bdc=;@>GSf$?!Lu%c6o+` z(JYg@+!LeY9=bh=DH0e`HO2C%V8aosW?h7ouL%V&L1AHDt^Zw+SI^@1znfAo%>aq} zEyvBzr@FhXneU!@7c6pGY~7yL)*Y1(Ut$rH43tEG0+-o2q07p@Y$OsZaL zgv|u_x1m>7ouJzy3_TYqn12C<7g)!SQ_GflkNZIUte=bG?xiKb_`v|?_Clo65h=QQ z|9;6%uR+^rhUnFT!w;8R$m-!5Y*?hjkZMQQwZOjM`_i1;=ZcG~MIiI%qYUF9-AE6l zf(vuK)-EVV{~gVNdRN0PuXI=iHRxTxl!x467&Qk0xc6kOj-Ee}nzcQsMPF_)pWOV6 zu7(&|qV?uKE*^XPZE9d3DZ?ll^xf?`{bPVQg@BUyZmPLC{ke7@(NGR&f&RC@y#MpbG*q__Q@LCR5GprC$aTylasnEAK128IL3{! zS{@hCgdcTIVo$79&S?ziuo4QY z#3~_*FuYf6nZUC%Xf{svLOEmrM-nSr^9kGRIl!9^ck^crP)yTYsl?q?x{To%u;tnS zuEGbTb6Il>!2YeGJYe#ac)xBbg)$P!-cso~l|Ou<9iSgP0`@@)nL;B9>>^e*0L^cx z7R>S#)G55`H-JBP@5M^u8$$A#dtcyCbDw7d$SuLM76F#0|LazWaP(iEmwv_=GQvO) zk7(i4E?%=zaUz)p|)w2uDv;ER=>gu*Sdu}gUNt*l#NBHD4 z-Ma=s*X}i~I1DeCa}q&L7OaBJG(1^0U^))l)E1Mu6B}j=wHg(~EngHVbyT6p`SaTgLg89^FVG$6#2+{9!E;jvocNAUd zf$0HnUIprwa2OXq_3Hn2Rs9Lg^x9|cSGL}VYL9p2j&^jkpJ~4iX-K3rpfw)fwUp+q znLmH`?k2o*YChkVy>tnrSE+;ZGo_*33J2L+-UA!S)76j4ckO2j%)=FA05|AwnCHC$$h6=@EJb(xl zY_6{kqB=~TCAJlvY9vu0hcY-d)8(19)Jp$^jZ1_Z5~{pS7$5_T+nY+FvLgu4KS0E` z!WM8C7N9q{yt}DsKtuvo@n{A__+C6;4xnJbkEl1Mtc zb0||M)!9XLwNNbw*t@LFuP0_t3{#Xo?|wr#L_ysl6oO{DgJ5^Ar&#GxUoN>wAo;Ct z7YD}n1+~xvg;*VT$xn%EG7t~YJa@%gm8W&p-s|z=)YFx_IgKI1z7pBz58mlBg_$VE zKG^o9G(=z^PUxg@lJ@NeDq&wI6PkaIHraEvhvA@*M?pc4B7=e=zYk*jr6gwn0gF9E z{2stHYKTxl=%vaaEcnC*F7M-hK5yU0Ja<`TLouJvLcXFB<6mntUA6vanOfkj zmGuiVeHoQsv|Rdvjj+G?VC6+Qs>+j%Mwf_>U7>58%qY4zir}8v3_>yAF1n-OrC&WZ zaz>9nVcow36=JvQ$$9|@937Amq}Yw5cyO%g)+utzWgs{?MdDy*&*~tNH7J)9lWc>1 zKI^Zfmv52j%2@x0D9Xa!fY1W@ije<;N_8>|-`O53xE>&>@5InMOPAqecuwTrPwR0*CA=aeTdXyg@<|%Z_Ojs}r zdZ?o#&Ejz7d6$(Tu|mf@$z&A7Rz+nHv8#yj!`~U1gMMA4c)p^zgw<10o^nNN7q_$k zZ$)7oF}myE6!DFrIEa)d=PY%jO_Lk@Yjz}S%x+6euHFHNxC4$wn0DU0TtATCaGciW z8z8Q8=FK-d$MmEbOx!8#Q{c^;aT>7~A~!7H73fggK245l6Qxv`3em&cqF(P=ziK`=1b{V;Bauk1&~rSyyfdDk793S(=T52YXgDnlLxm)Phz#gdG_6Q$%rOWHDi*(FWL?L=owEo)6Ky-oSfA zj;G*<$_fL!^hBfh>bQfS07`5A4)>#eh*L{y@i8(N_Y3_~&$8%{vtHOm(G+cpV+4h* z-hwmfjvWH(=@K27dU6mQWWmP4^TM>CMPbHo{tGK21y!x`}NVX>!Ka)OY^Tqk({W> zqk{-pY_mtLRsqI{gZK%;_&<+gQJ~Qv&k+Bg3jP41+VwYoWX!;>eJ_UZJJS!uT~F=u zW-K!dY+3g9#zJKfpU9Zr9^WDLRUkfQ43V4*IGZV(E2zeV+t;Q}nj++cm|gD{qlBR0 znS%?c3$?u9o5+7o$Z=&C%SazI*4C4lqa(gh0lb~&H@c6@-RBh0mbdEl;i;c)a$d{H znXe?S-luUvDXsdx5yXYP}EI) zos3iC_o#bS8cm6L3Y#K8-**Ve=ngRFK5Hl0**DQK*@5kQA8N|+AMU+6#bi!h9h&f0 zyWNI4p#(dI-s6nd4OET|!U-N#sLUH9Hbg;8Xfi#67Uza?7PbU}c{2$2Z$ zGtK*x8MJ{IhWlM28_7$QglrbA!BwHHiMa?o2}nN8@x>mI66=Zcx*ty( z%>ouseIm~s-$Ja22r$fAFq3bbHrlPdBaH=V^v5*F>(ns;9knY5)F7`RkaMwohyGar z;v>co$-6+9z#Mmd0#Q+UPqR&o2^sJDV=Ttt13RVB+dkJzii_aj0X!4e81p%KY`QQ! zJrCJCG69VuKaVC8{CTif)rZ|d={wspSBW7J=PX!$3Rg(?`(;K_}GBh>db)TAqc8= zdKrqQswi%LC7#{Q?AMB*Fh{ha`A7H#a^H=gn>0(t9M2FQ4+`&V922zn1yi9N+7t2xl34On&0!W<|R15p7!I_pkwiNuD=;%Hy>;B>>R7_ zVsb9Z(2)Opv;w6QOWWl}j@pYcF;;9hSQUBVyr~rUL!HgmCW|Xwr}2cq9_?}x5+V9J zOMIKz-A(1^(_pAUi#cq2<2(5dq*|(9)%Usfg&JJ7vjQH248Y&9jyYFa+b%J}l_>0vdhJ ziA&CAtPf_Wy_T1rU4ne*Q@QMn4{?F@>uqjMxjMy!6Jq&{S~WAArbu@IM*@7y9a^jQ zO{iGn0P6w=BTY8zcjR?2$1=F~tK*Xm*CtJlWycgeSgB$1+R`WAdh8!`9u=d}E*B(f zy*CW*1LEqPyS(%Fa*nqVClc^e!E+$0On(d5ncLYGkAG0rhDmTpJmfwcfuSVJ@AfC{ z9;6g8@y9T*$nCVP8=3^|P^;BoP>vbIfz!hf%g00K-KRkJ5WK4frC(ZqPR9*X6^Q%6 zv*!K+VC~nQFX(kK=_#lZ*O^Ssrtx)%>E^hp*?#C~IfqkzRNgK9;qM=3E2=FU&a|)g z$+oQ&gbs`Xzw`O?s>+$+iHWn7dgM33z<``teG*q??Mu4mQuLO8Vxa zC0yuPOxs_8o;U#_fN;@kA97_t3v$)d_Um(u--jpQKfE|~Vn4hjwZYdOBJOu;S}8J& zFIxuYm-hBvXyo=G@yrhn!8b}s()p{rI1&})B?H&9jO_xso^;zZ4Q|H6ke= zK{SmAm9&1!YgvMvjBi}#z_TKAMBD8yWit*~GvnDu?KiDMyVHf4iq0SW04Z4huEm2_ zAb2!mG-iZo$gGs(jSPbUkg*kfL?A6s(G-vm*V%O`cxsfk)kq`BtrWamZu2hOwJr=k zjwcv>Vm8PNbp6st)6}kiC5Wa3c!`q-{c?aIKl)+Yyr*0=HM+V*Q}^)Mr#|%|qfHbr z3)5S=xj;Yi-7paD=TRgUiyxv93DFsW=kIjRQ43Ug@&gn}N|P?if|ByYD7Sy7&lp9s zLd>ZR7yL2l${g!{1HXQK$GV_89eJl;cPEzo+3SXHKS$h7LXUIU+Q~YP-l{ugayCS$rlO=Q^;{fA+}#wzD}_?>R1O; zoSciEUrt1r#EU?lI|O`kMghQVd0i=INeDw^m{Y>?dfU>8eC-%yae=uQBe>PaJaS^# zu3}m!`i_R#XJ1De(Ev@dYYi z!{2`xJyT0n3-v;T6%im!HxVI;H~*gWo+B}(`=&a7s#BdhUnR68K^=AHp);qY4!7j6 zJb5`)=aUdTL0ttcbIO`{Zt~=h_rG&~zQ_B5XJN;0c-zf)eOX*`2<(v*z~cAgSWz$y zZH`G@wh3Lp!Y7?=dJ z9~FhlB-O-MK}nzGef&l`%COay;qu+-{08j{dTZNWP4iA)_bbLTt*(N$_2GQDsQ9Lu z3E&cfh=*KHPL>4p+F3GLD(i_X^XYnmx;lzVlQYxcVrM2xi=cuNIp+y2t4%)tc7PM! zhhw3WIEGdv6qf1rrS$T-@ymvC=m!qaGq@gBd`mqYjC(oEO%aT+`4;XK!@jT#OfL3>E zub^Xobg0cbNI7Bw%n-L-!>?v=cIh|tMy%bB=q}gh z&Gl;=Tnk+dGohQ350{jzU9o&kW#>e%scGEBry4+sX>`;UiYk=OXw{Nem*bSvj_?Jp zE0_++CQLp_Ot!^qb#TQgU=@`ho3$WH3MPo;h};`9jyS@uxI4ZHWE#`o8;JpD)s8^| z4q^n5OI^x%?L^C4#gOCt!-(OFT|>hX0U!cg@b(ynTjyOI(CFyAi?v*2T$%LnH{X+; zB80Bj#jxR+pj;H6#QhRzna+oB(vw{ZAhOP$p{f{aj9ke#Pn!9bbfXc&90+bDh8hw* zQxPWDb^v~~fu8a-L0_XB1v}N#c>~`*VB48jJ~u^rI2Gj@h-fq`0afR}W&qX#4ZJY? z7R(~m05b`?u(RciQB{?8`+Xn};t6|06am@UZR@rKD+RepI@#~g0fWN!jqR}j&1 z=MobtBR^|Pz6pYXo%t5GuADhzI_~ZF1B0*b6z}{R4$Q07`|R2TCmrY4K$aj zhxu20F#K$3AiL>3c5pl(9kFHC;NY$;mUYW2n~mMcBuX>9{`;eIyj=h2cLObC7pysY zvaxUm3%wgzJuKbsIz`z=;KKJ~1K8_V!7jmA;9Kn~da`#BvY!ZVKOZ>ccjV#Z0%d92 zJH!gggt!@!cpxoF4TC;tcC|upYfped?m_na==4w4+IzJ-2X?PRzrz5Ld!l}amXyU5 zOLvm&CG6iDg;*`PrFLX5VPZ{}O`m>(sfhx)JLR#{S?Fkc<0%uVS72p%Z;0IU$&=!Y zd78cq83$LKujSU()iQ41zu#x?*aSe*pFqXC+yU)YRQeO7ee(SDaK%O`3O|J0-F!p0 zAa|H#JRBuz@Cnfz;}jiNS?ef{)kcVkHt#~=qGsBPqNw$zwPw$>eW(^`$=M!wq%z;y%>wy6h>X*hS;gFo+wPgU0s4b-@9anvCr$c1+^H>CSW~@-Ne?a zrOuWa6_%}H@1R6G=9qE_*_Q@&PW(s|^kvAc|JVTC5_06;H1osbpcP`>M`_UGbIeEu z9R1MxF9pWn!b%z>3M?lv$ofZYwwp=cMu6W2HQ}BZiy}Z4$;E6S`?wA3pGQ+Yc&m$V zz`_?jueG%HVrs&ACMJggwc3#}v;Opu+A-w-%A&ofmFRYZnA^W3m|MHLz8(sIHk^)~ zSlTS!cP2k#?Yi-`YroiKJnZN;#yH1c+p>5=@9kCsqx>8;i=p4t_WIg{07W!=!Th$x zs}@FWaOx3F=#&$OYLK^_RCf=*qKGLFBb|J1&2Q+zxGX|`hb*cg5+Y6g{F0n80Hjb* z^%LuicKY5UC4bC3_~5u~Y;Vf|cqkL9=b5a`p3$tcP1EPX++Kd5^f%-;?gQl|+PjM5 z-HDq?j1l1|?)uH7L9B_@G%<@4I|PpIBJ?4A;qFp30IN};;@Cp|cePn;Ihj%9im%mtoCWuPf_@Tg1jUz-zwUNZ02SzkQTI%j!FqdTls2@BVi= zIp5vq=7q1Vy`%x2i*%Es<}N+d=Q&Upk}zA@pFsNXL9dFGW)g|orwonJJ}v;-*Ix%T zOR^`>6(crgclkQ;jSpl!k(Q)ddFKMUKMHWjJV791?_MC*bTF4?Ngri0$-(N>+UoG~ zX_Qpp9eR=%_Jbv>1J$Jxj&2s^kz$mi6l3l0`HJem>llKu{cIea+@{hb=Knl8P%u(e z00wc7J^?-h_tK_#Np{{WJAhh*5y)&V@86rsvw7EMG(oiVR{`M|8vE5a_vt^;On02` ztPoLgu0r>5j{+@^Z)|I6xhVQ$S1)Y3knQDj&A0DET-nf?B^t4$PW9Sf7DjnoLa;gB zJKot(?@Q``XmMi~NnHTgA(;R^-;c+*N;kl(DaD+TIRi57Mnxuzjm|r3ftr0)67s5L zPe!Me{p!gwwJt=~1!9k#?ZHdE-GkOqm+R6FL#S*a!t=u zshYPtzOY;DwCbFe9uWZ@Rn##Lb#N$3sdcx>%v_gELELYTEktEjs0r7^ad6EZs??X> zV*q(g1{T+m`Ck`kcjw)&%;3M5&NTDc5ciM1^($OClt_@oBgh7a)q>PICHEFqJ@V& ze+1~~w){@NGRaS(CS9ffyx~K7BDZ&iOAyj*HsVI$h z>>nVh6(wEntiyuh2o5*UbNB%Tp2s#Ylj_Yb9|9u_HTzjNmU)yR)^zrxZ)TXuS3h{l zfvi-tI|grYOz-`4E;g4rQ|E@O5KKj}dC9d{?Wb=(F|2n-XISMv!~@i3%xpnVNo#TO zXWudv)FCziZF}anmBl5kTe$34L?Tx2byV!6QchY(g0xK9%!g3B8L@99OpqjjW&hB* zMOu;|xF7B^fz@lkeU<(h8RxpX(61)O0n#OaPAw=U-GiT!uar)&)=9U%&UjHYT*$$2xzST zn-TcD?nV&%{Po#nXUTs{RcOTJA$#`>bgY5$!XD~^U%PyF9zVS?=}081s{6WO&&K*X zN-;sc5z?G@vK~p|pS|p&hmC@hfuEmXb6pm%;W5>fyLqR^ahBs%%LF|L!5tw_b^i(h z-!cc`$+@==bbVH9L4CcNE9)z{FJFl6LQz!dLSra+^O339c_-jBnw}3~en1r$DaM+v zsF->#M$w)x0f#v^2VHZ3ET+70?)>L3xmjUhyyMH~^XCdx1&2*m6%6RPFp&KDa%+DI zsXif3COb4E_^r@vQHb2rGeNf2$yyp-|E&57u!LW-SYHZRSoM|5eGpUI{K8R&F|hSL z$<8vWzJBOW|EZ?M2sr#z3#(4*ySQrD=*_pE>AcuIXftT$iuXuyQ4x`=SKreT0j-U3d<9RooSk(t#zcwX3N_C3%d1>H#pPWx`+?;L2p>g z5KuK}*bjFRWRs9XTYTfK`lVA^cfk_pbkJgiMG%<(i$8ey>wBsSTIfL^{B?r7Nm2rd zU8k_EI8m?9aa%_+{J*qpoo-eGZIiRY;{Y$UE`HQj3qbmH9^?QD?YnfBQ6^w$R>NHIjv5`A4p-o?pfUaUs@%I@0}7jIcgKuh>qupNFJACP@$F zelxeNJ1EQz;xJ5@KEqPfn(g)p%YGL6UHy|__PdsfD9}8)A6pilj5^G$ZkEvhtN-Cb z95l_haI^|loR_4~a?W=2&>*Plh#>#AqR~R5r$2>rWdP$pbaL)4IlJZ+rOEI+B59Y= z`b@(>@U?u5R_{3KiZn6G*s=_z2uywN112sKS;ekrGpKfbhJUQNSxbts;1qFM`zL>T$y5jO;>dQ@O=sS|#CW3XVu|Brf$iH@5RBCg!bBD|* zBhLg{GMP~b_v??+n4p`r7bz#+?0UX$)G?wADkF~3^X)EXjvCB+_g;|Y8o>70R@ySx zHzI-)ceuaN`27qJjNHGs@%IN%c0f;sL!&gUB&&lwjA?aZHhu z5oN=cYGt{C9Ho-E-0EG5+0JC|yp^dj_3|+1ZLX-RVefOSVV6MoY$j~E8Vlp9Tlt;M z2-4iiPcY<6@e<#UDRP9AnEIGDMR>}*#uI^U^bBC;X>MdZJ>7&_YSP}G3!JoC&35&& z)D8|$YqtPIUr!x{CLCeKXUKHc^f_P-F{Db~;gYJE=1q>Fm?C8Fv7{9gKmeH08Dkk@ zV1Puq91if)qodP1l`W*khHc|1GCW3I0IVl$n(&WgFKx{ADd+CY-5+iX^ougou`Sfc zG&@%R+!;|KEv^8G#ffFqz0@E{NMPmQrOOwt#X8_Ayi08$uFPWsi9!@cYY!D2T|aHs zGZSu3vtk!!^ml)kU?s88S@*8G*BR4G6yrK zRIo4(2dkh=0BHn~-GJpF0GO~w>=`14L{@|Xf;Bx^IWIjEsMF^Tqg{TqD5{%KqAYwy zbg%>?h~^DMJ=({(qj$f!Uyvs4xjHdX{X8vbB5vTw+Bq>?MBEWQ#JLMxZ$ip{%kX}0 zKKr(ddUi*9dj^?VAk?*M;?C4JtE#B{td5R$`qL=0ha>fS z9yA?lI&)h}j!~SanMvFs0u4BrjaZ{6b|mpi8>=x<=A*%B1 zLg%G`-Az0ta4aW@zC8)E-RLt-f{wwbf>0>nZ>%#I zrdzMaWJEmv-(aH5E*JA}0n-5Mc9u-=>`f@p7k%uWnxc;R*dvt(&u@KI6_lP{%RYIG zK})pyXpm&-)lqMySAPuEdHh;9cTZYQ?b1JV9)RI6JL7Qcv~6nI##Zh-5JBEiegaI2 z`Q5m$2-I3XyXH5H*r@9dLiNiG{`{(+SHz~V-k*}Afx!=A2YL5aLLh^v7u*VE+QL~^ zN4A+`!>pUXdFhaBL)hhlCPDS!acaEgqyuw|5Galw45IWF|Me1Du{fx3^1CLJ6c!3X zS@P4DyJ2&wIs~bkG4%~H1AE#_5Q@6MVu4wY=z-Y+x!dz+L-)^r!9r->8S9Jl_o3PR z`WncF2~nm~au~RzELGJ8oSHT2e^T+{3ddmJcs; zq&MY`_BI0uRS*^-D9@W1h{01~EIl&YI^);*2eR}EmD`97WK>(I4(iCJb%%y{mf8Zd z7IsGPtW_*FeAh6;;;+DSXMPrq*p&?REM!nMcF1~!=9nZ_~Y z*{dTrN7KK@{NS1PWkZQPj_YpN0*3$RwR>_C=FZkwAC% zfFn>$%|xI@rwzQ?@uess1d${)7AfvZIa%#x5yIeA1T>MAC3NcrmzwT>khIIMHC^Zo zhX=N0^7rVu6d;I9-n|v?y`G&)+P8OoQ^;c9!RQhKGj>PnPoba9&gjx}`F$%Mf9SfF zX*6{xFHHy1@$4{nFlRk;^-XiEPtHgM@4n>Ixec&4kg^g?7LdcqCTjvPNu4bxQMh}XS4C3!22mSxhY$N|upfnxr! zk-X^7ESR^V>+edopa-3QU{AbkZeHQ079^72=l?Uv}Z zAg;|eX1as9uLL>be1Y54fyjiNmmpm%tSz`Y>_0Zc)bZoD_vo@>yHyCz19m@6ZJ|Gt zC6#)HdQqf%eIFx*Nq(&!Jj&b|{XV(=ju;b9mVeuJ&_|muxf0Y4DB+*ZrP3<4K`(ZYKao<#6X%T11?2>E`WGiT%NzaKI^l60jNM7kSEMEdi1X~&8!81$1VctG#EdgU=ckRyz&0U zZ4~(lsQJg`0hHoV@e2t$A{po~CzqRZw|diOkIT>y#>KpC|NLl)_~zchYXt z7%KeWx6BUiaTq?%)vS*(`2UFq>HX_ofnp?RBP!YeY1nKHWLA!%jglDXgvCEghyQKx zKmSCO*K_q*6zN5D_Mww&2!Al1AIFM`22x>z6UV)P)&w&k2;V2H6R?S%lV*qomyY8GTiJ?>kXU1 z(_Or~R^-%!U(*IR{tb$SM+ectDHKHw$;&k&MK$AJX7upk(Xz1UpC57@J_f>rKRaKL z5rQIBhUf+ZB$t&4LI-w&w?vNYy&-Q2Thhwwa<1^Lki8!JFZWoWG=^h|sF1y6-!`O( zC)G}Z=4R$>K7TfoI{yWW^~HI+FlbISo%8MXqOwyhlHW;;N#Fh&1$IC|*Z~Dy{5D6EmgagM&wDF}n z*yN(rova=Z9j@E_R)1ac>OHZz$GnZru@O2b=Er?dZX7G~dOapC$kGU<*4ZYt0ff}9s98Ta#0hOJ6Ce5Ffg~?z~CM?+vaLlmu~p4`Q%{W_wP5<{;&Kl zYMK51T33Z%qG5gS{SzN8$&KrT>%Ywwe=ZPWN^Q6q?zZjUzFrKpAqW_wXDD3QP|{zC zsSRoarkseeoo18{9gK6WF}NpBnqv&zM^)7qW1p>C?|3M&Wik0f$FkEu2DncfQf7t6Wpm8^CzR7sWM9)vBqRJxP>ASFK@al|;Q5 zScuyTuzS(ZUb%jI>iVXHouWz+_S5xmkUR`{3(i+Aq(idSOR z>vAvu#0HXQ6A(|MKnY)a=n!RUQi)TZXzHX64RKx0@t`2*>vvq_xAEB(cG&4ERcCtqZo$42E(Xh)QV17ri{k* z4nP9TKQ%RJ3l{!N3O1$b_4F~qYMnA$FpFi%cIVp!Ox0tDZxn`}n30tv%F^wXQjx&G)JDP~%b_-PbaH-b0d5-~bV#D!v}ViJ=#bd~FN| z3mU8nNH~sb1%h9fU;|k;cLvD}A6pT%Xtc1nj71z~3n<=QwPz9-3JD+p>*<1bcP z|El&u2Wk$6>|kvXRmmw(m6IB+{W}<9 zw@QNFT1m(71$wrCmZohYm|5U|Z#bN~`7HK|*x)@YUwL`H(F|BcaK|=lZ+SS@lUT1Q z`!~3gU=h9b_uASbA~yzFJ;k096({YeT^;k4k+7M^HbqRnx5zQz=$~a? z^8?ZD473UUWwhigA1c-wX)rXl{kaprddU2~rT>@m561odH-{Z@ZP4PJD8T(t3ob6M zxrTFdJ)94f)c+bp{~0{1j;_P!J7bU3p9@E02ks4oSu51Jn#hnQ$^Qx~obwl0FEjfd zDHMU436O6{<+n?E=kGrGTt|W?Lm#Er(m5hLx|2KOr~P{V3E}ovcUySB=w#4);CCMR zEAZr5TOLgfh=l>#uqixmA@Y?1Ry3q|%X{A}bm?aTh#7~RI zsIl?o%fZ5`?+ull$`|`e{Bpf>(Wn9rBYswrpSqz_%*dH_uNl+bvSMqs@bDTiG)hm; z+m;|`Zx`scpSDT09QG%tIr$k2brL5P5NszEiK&sB&rJS3y^f^%`aUkMJ=E9tj~3_l z_4b;OmM)}g?i}ZqEy8y)-O82D-Md?6kY=s3y`Ag)t#IV%Xv?{0u8ViJjhT$bft)@F zK!;vZJYqL=m?sTtv(0QEMnB(I*4|J^iDN{6Y+IyD(dnf3L*tdy*EQXoIoE4qXPE(J zE}l_KH!0{ng8v)r4r8zwLAi6ZbSh^2i>=ZSx#y}?{G2swiZeo_AKm?!X(G7NlQ)Kk z$KIAo-yTkh=TA!PhzmiZY(}2h*_@%F>*$B(R4B%6l!N+f%SH?=U{_KeG75^+r7ae+#{W1$2Bibgj6%lNVh z{9YXQh1gbfe23qAcpHwpu=4j4i@iEPmFHqwZ6&r|+lPH>Fd&xHw=LAL69i6fYokh8 z9vzYR=bP!TjBovJ0)}-NijqH*O(I~#h!|kZ&w|h$j5tsl&!XtoE_^M9W9{+C1gmI+ zu~rX<*{vb;)Fs$aNRgb06~LN{ET37b^}azy9@Fd6-uuo2@x!Lru-RU>IY2rjnU{F{ zUSPY9C(d&m7CsPV!U%d$cTL-~h-^nX9#EYB)I@G57Fj0wFAOnXxgN?$1SDPJrjMUW zDdwZ4@-dH5>9P206NZRXK_Am{EjStwpQ7BMf%Ps!wUc4_#JbOzG9zV>z)#~EKtQVw zSoJ(VYBn3~sND#LB5zLctT{L_mXj+NtdrATAlGjoFMKDZ{>F7jTI90{awsggF{0X9 zN`oVq$rDkc_f_aqYoF^ZIUA5WE#Zt2HNsCOu}QEYoDDQjG4VJ0N$bdD{~5?SZ=Mx` z4nJYS6W(vU<~=MhpBx5*?|8QOvW$w4Ke-`yP<#n`seF}0zKb6gnqH5m#zIy8$qAh^ z@c|9CBEp;Kd6BMJ4El%}x%7)YK6pgY<{YB_&`F}vl5RM8$e$tB#T@vZ_OxQYFUVm> za^Kpx>M-QqL%(;3f^DPn(63e|aDMWA3vTVHQjUMiZgZ0RHgsGn%2IZXJ6H9Av-jWO%BKdb0BKyn-lV9zF5Kq z&Fq>K&;3y>JaYTW|AR7dp1N?2c^;SFKLD0g7pn8bvJd~ia{Gu-{Nr5lB#ukv&NCxl zt%GH434N~i9r}t0Pkwjwkrb{P^gpleRZi{PB82dKT{b1~ceM6my)eKUhBv8k$v8Em z*HiIbYHQEEkgZs&8dYYUk+b8A(=Fz)UuY4!;n5KTM{mT@r`?&e@aHRGNCLg8s2tAF zh}DOd`8Y>`Cd0@EWl>kTpXktB(%lg_Pl*l&_vlRK@NG39IFQ6Ghi6fe+!2KUzBN=< zfM3QV1F>wsJ$qlrBwxD_1oP>5Gpl)`086`?PuWQwQG$sqv<+iLAI5_iU1iwr=+dr; zg)a3|nIhvO#qNZMz)iQ{Kd#_Q>x6Xm(lpmz(g`4J#>IZSStAo|1Z|SPpiW?~m3XfM z_QK)3ttDC?3iCDxn+6HQVd8jVIK@vn8aE`*TOa}4^{U4@=oCp_UWJ7{#sx~vgj4rD2M6G_wM!)MCapriY z$4^L#oujhC=sy;(&MP?6+Pz<5hJ(vviN=SD}uF=pE$E(yC2p^-0@a?xTcVCI58(x$8>P50NA5`1lqd0nr-n0a)c{_qgpK z+A9~H5a3rgfdKg^Mp@^J04JSt0J?D6p}QnY>cKfr>E5{Tih%5bIdL0+zrTjaDD{~k z<)N$`y4d{`+(aCYe+)61+?h3&LfUaRL53>CX;2(LYO6RlxY+jndgh@w&~0|B+w+D ze{)llyNj$|75KXQM4f%hp~J3wk901~A#NiVsryZtnOB-m847_GTRM*2%DU$1ZCt_G z00J#GRO5^QOL%- z19I8GosA)|_H;xD=c~K3+T)G{3xYoG-qYaAPWjq$Ixymh8b1^fc?jpu17AP!^^o1v zbY-WVPPeX%GuqCq7!M>WZIr8D7}%CZ<)l#Q~&lbPX8ZDl$;f1+LbQ%bZ1GlP?J%9i(MGH3=i$^6AZgWR70rEs7CHk7ro0Ce~&z)eg*lhL^ zH+bBYA3Oz8f3Za>yaxl(;Zvcs{2JR=K)<{a?FKK^v*m7<1LxXHQa<*%8MU4U#9CX# zm_~XbbGn!B6KH)1;7#p`JL!e%1&mBc5?HK>43w>)e4k19L2?!~e5b{}0bgA-XLHKI@osaegY7;Xmc9vZM3t>=1^} z{#;xH<>ibs3%|+~giGJLx=SDlzFc*3cl<7uzu$(*w_KBl36jW(Bf(i2UI~&OFOsk$ zL_;YQlqN)`y{GWyHsp$SuO9zET*)lp$A_{yOt36?+}1E#nCl@E^SEB=yH1?Ydi^cG zB&1Ci@1-fflIPv|c-H6S?PVtK+#tCZF|%%0kS7{bQ0yJ|ua~B+KI(8=PH0ZXwiIE8 zzol^%n}6*eMvq+G&TyV4Uol40O4`J37(#?hEZ+4-D-ZS}#)s9FXo`2piamUuUFEY^ zhef&@zMB-Vzl`2%sxM9s^TAL<&DJ$_VJMo@zO|oDz#A7=y>fl#XN>d--b^}?au`H_ z;N{ae3dWq0=n2G$QF4hEoGc&W33hoK^OUP}w|p;yPW0nFK?EiL{g=*L9Z|8N8N6;F zgiOz*dS`oh99&o zU?+Y#V0;u2px~IHwIhJvb>?{SVw`xFpfiLz95@k>!>f71PvZUZz(F|RWN6Wnt^vk( z<3BG9`13|^rtA==jw+U?;sS##RLQ#8&rEARBEN>V892AQDF_El!VUrpk0Tw#fzrVk zaJE^rD4ztl6WmZxTMN<>!*?8QX!&dN*&#tWw;Vt-<)!8I}B_lAFB$!vlfohxg$u5)Oi6+n)F*KkMK_+t1hqL0mE)oPVDi58V$wOSVOjlSw zA^voubTX%*jqZFoGayAQNUJV(DV~elk?E~3y(vy|QSoNYsC2ydrp&{rC31aeAZDd$m@@kx<~;JJcXc^9A_U>wAzP zY{H^tvW2r2Ej+yABT7ttG*8s1;SvBl?LzU*K-pgmp9@ia-!8rYbe~s;#MRkO?`IZ` zM|0aRNT@F~pUWf^iFtA1)qa7M+Rv`D{nc>3+TcBzoh253`+=jw<)TLnRJf2&Lfdfl z1bYM*FA2QtEh&5~kv!d2pq)OKpLr_4Vt!l*jKQO7a3e8dwX)t<=q2oEM@Rb&1BsCi z)YcI@0jECf3H9`=a3%{pgI(U;-F-yLhdcWa17I#yUx!Z0YSz+6%Bj!_<^r{c@6Ggv z@1Iu#`R(V`ASjwV4T;)~c2pT(nz@-I(5eOqHh~e_fzZtm;|*%!%@P1LY(*-uN`)1M ztqQ}+T`=BiCQV=;F4H1b+|d}%OYbop+oyKQzjxINa!)K+74yD$Xj>8BYXhm{IrQ*w z{(b5=rO}y;K_y1H|MYB85w>I54op;(HuODV5`upme z1wMYzGMOkgv=rD&Byo~(7%fJnS)49+&uDiR&5E01MV!bWm`)aC@;P)HlM8vk-*w*O z@v?u2ykKsKGC10+4J*f*uqZF3a9L_w=^WuAE>ezf8}~tQKrV|UMSI8L(Mf=SmC{bo zU2gPgj3*L(6@veJ8T?Q6nrkDqb{;{%1e=&_r4S#B1xuJfOR^8#ym{{)_=|_$sSI$n zC@S2z=Q-3m1+rUpLnpwaAa0bu1T|vTW$NUPDg8b1z>~V(Y#Amv6%626G8+dL#t$WN zKg%u9v~Jnsx*z8Q>B4k$x%ghmNKQlCGde>QLc~#f2QtiYn@TuW;_Hl?TcqegR{Ppn zDcBWz^kjcb9%Vmpd1ZdShlG6MQpHf9goxRAUfwd=G=7mX5&95Y>2`<0SacAbYNBGv z>>@^?`9%B0OmOWu5+wi5Ca5KYGHDk~D)F2T@4XL-D{lR4lH zH9t^r!XUd|4~RX5QOxoaj1dN4rX_(JxDu-~#K#&EW-e|;bIqu-mTE;7zKnet$F%~b zMbB$8Vw&OBTS`9NCmXA3<9rMyu$EKR(Kb4uU@p18f~+HAEP+cG#|*w8E(+b2Q4~Yb zZDF*I9mgwvT-y!Q+3P4t77Y5@wTVa_2I7OCo1!o*!liOCci2*?0+uJRr8qE9EXz>~ zHu(vZ@MF<(izBvSAIujEdUG>ov7>ei*1UXVa-aF59sUphUJYbNXy9rTAhXtjr~ZU6VA5t#MvaC3)!L2x)W#__ zmY{t->vjWHN;Psisr3u`{0mMGm6_4E;|=#<*;}&pB}6 z%~3Tn*6-{e16M|7;qTS!$7L5XGsomk>cXBMsItpjPJJGDKi_96K4O_r=uL>#l&3e1 z!bGYKEA#xiFw8{?Y(TRyu&;+PD^e7U`J-AGDJ(hET*Dc62r4Dbdh>Ir`4uN=Np<&` zGs~(?<2vTil$Lc-OUfEMlE1l7D6Gn_4JfBJ0ok^Gjzyd?-ZE!>HAETMt3p9w>pdTM zg@ZE1rF)~b5~Hdq2T{dWOA9cu*(^>M7^R?XI#VD;|PK;zATd@gA zb$mP*oxsEvvpD@ri7`5wU!Py5i_I2je9bsaDDc=%M$+qf>D7VveTFS1AI-YKHM5bS zY%x)$3c@Oz+R0i*mM)?DvndMB(r73!&%%Z#m-UiE&QgF?GEWJ6@X7Rq86O4AG4KTQ z3A5AZ?T$gSTwFPezo>LWM?)=tR+HIbijOtw0{P;6Q`x6q5r2QUzCN0f47h@+3nYTs zGca|MU!70~3IBkpgDoIG|KpKB+N1YoshujL{&cB~hw|gfHi&5aH;b07?ODQPjRdms zodvYW>g6lz`6w?sbipeavHyJ)BS|d5UKSb4X@xKgU#4dAn6$>k|Im$ryvYk42-pcx zE%SwG2?%LKy4+qFqGFMBI_R72l`&-d5ovqS^0e+$l}Bo`bMq*-N4~e2>SEr>oufV5 zOdVc;v1DrtiV-*>=OF2}7+fUm!7vB$)3ZB=GJ@i=VIbDS1R5oG^D%cIj^wbQN1rKc z$Qp*dkEDXE9N4ueQyFyNVvF*Cg* zWW>{zjPV<5hU*u>5H_2i*6$g?kv$h&%d*hUBiHT|oU@EFq4DE+vt9jz>s^%4>HoTA zs~AoUM=(S~k#J%-0Xi)B<*+k#6KmXNUABii(asoLr4v0}XGv&VSo~yyC*1e5pgDW? znI45XDsw7(xhN<}zp-b5d`RRF4NYhN7k>4kLRgkcqGi=Sou_3!MBwSeKE(wnnJO$( zT+IHVZdF!yAmGRGK%DaK!@JsH;2JTurluh~!K63utl@6(kMUV+SN)f8ex229&=26b z4O8EUlY=bLx@&%lLLhgb$F{Px8pZ+%vD|*$J5695`x`_PFvGdJ`F#jFRqXfHlGz|W z4zA;5I{KNEhqoMy`&AL_Lxftti^D*!9F{_*Yf{o~K@-efr%IV+NqbL*F%6{$j(}i> zF?4xgR;U(#6uyCyq=E4sOJsUF%RS?{o8gPBXhQC~*UP4vY`W*-@)ZXK2U?I8m{tJY zoyV#4csEt$>mXe-qFp@d@@}!5Mwq#$6|O}!1NZn4u+ubp(qKr}3E4x_)L$?$iI}he znILYG52yUJ2#ad42!D)Ale9Yh5$rSW&8)6rTe6zA8wgelU(M&=%WcqRy{6}(%KQb0 zEuPAZWBACFLj!vN%ysHs(=Oz*V`I2zvX(`407(oDbQ0o|5w>8gT;A(Rx}_k3=M8s6 zz_FkrK2ZcCX(UaucV}r$@q~r4-KBG`Yi9_x2$l^Q=AN0#hQeYpXQ$`--Sxrkr4(3M zG1>hn(5M|YQm_fbM+8M&id{hWo^GX&1;fROb^>f4bw55gNBs zd&%ArYcJZ1afA}O1$nruP>i-p&l1#frH+Xa{SO9XYW5fgigKJFUDE@y<@rG8V;9r+ zPw?~hhndwIJNBEoeLG;*_NmmC=D&Ul37!4v{B?=%f7ls2U#6sKrmBN8HX_DuVUT&| zv(jLgd(21zQdwMf2nDc03jVGoP$CH+8k7W1EcH*+N$3Pwu4lx?@`izJ&F`e{n z$;mVo&#ucIyjxap4pii`y{1XYo;t&G-vOyyLW+zhG2`RenVtpZ-0_F8=q z?1uGd%OZ@HmD%55cbe{CC8pC>aZqN)EJtUNmlyOG7EtpoeXdMdwmhEa3bP|@8Tv`> z(opn%(9Zh40Yg6J)`IrDfz>hrbRbAR|0+9ED`c~Q%2mR+Ang?pt?!7zHii?ZloIlV z-N)B=82F=Bwe;l3NYqc2e|^^9*f{FfYx5Z7<4gGj&(h<

2@*Arq|@ox;IopRzro+D_*NQ^llF2tbr2FhgWku6}>FoMhlvK|fT zz3|pO^&keCWW9&>Incznsxy-{)g(DDNmD~wZ2AO}1X81s8=5Xs`q9=;etGt3Gzi3( zgzkTEuc`rO;kelp=>T8@bzerp<1{s8W;eFyZN%1%y&OHS|6Oi4jD`(yBxGHP?#KBhRI zAOh2}Kg#q!xUJ=BE|LL6-%8sjoy(T{^Mee>$>wq`XfqV5t3}cctB8mGm{^OgWhOq4 zeJO)$L6gBTb#Wm}nN_-5tT<+qUBdBAtwvZYl`D*M_k`2ttqa4{2?1ggTsUGH2@~-~ zmA)+HlQO>o*|(3>1kLBjbB>EJaSe2u=mrgO!8rvkwJ>+|!csO4X z(WX=ZDvGu{_~y;Skw2ankNlrqg2aLO7wq7UqoXrNqkfEx_jr{nD(dW{1MO^RcvfHk z`S6jl>Fb7iBSaWkM$UQZBOv3U`-2ZwzkkDjzq|qX^;gbPLql6IBerG1f~9^geW>(_ zz~Y^~q*5)PqMx>8XQL(;%S4%82m`Lx@R3EJ>x;;)o&q5fDPsPpxl&S{*7rE7so&TP zKOfinCC95#Yv78k3mFz?|O&NK(qtQpfGPd zkO6x51>$63E%*OA?&WJLaP1xLv!6G(-0Vi$Wp_VK$>d_Ln&4!-p=j011+Mvj(r$jg zB7OGp+5=~{`VQOthBMi{0Bi$V6=!r^eE|?ZE4(cVyRM*1kjTeEU<=uimehgi&FM}g zFFy+@vS|MuwuD)T1r8aGmk3Aym1=jSIidz;J0c}z!0=OlT7P68j?_Q9Z={X8sTQCz zFnlH+j~{~J2;Wok!dI_K`pJW~X5#K&2UPt_f7coLF_^|UDwj|_L-RyHZwg)CKkXOAPEp`{PjMNfTOr;gJRLj&LHm-ShGe3Xe^EdhV8~pr{pFhHa;aPLdZ9f_iiQziX)3m5$3^55u?2^JQht~(EVlT;Y)vgopXHL_pUD2Jy)f*eioZ- zLealbtl1}QJ3RTVg6XvZ<;!=2@%}Eys(&toH9eT;GSTpA4=}b5@luMlkT*M5N zsQ@bgwy{(i_?qNn`46lB8?;SeW(>hl40L&AtzLE}W5rTe@tuehd3tlUlnLaZrBSdG z_gY$o4o};$?&_U+`w8_h-+n~k{0VFy{ud)eG(*kw-CUqox8}-d3M%YoR5Y@!OHr@Tin+E z9UIG7Q>I%|xQ~Y0f`i<1RP)*Cf-!IIkmuHkSaQ&FHJf?ig!x{rkE&EHta-pc z5Wg~*a%C>gv9Cc(f?CiusR^%!@86e*VNL~OaF$K0!@4NJuG7F^oGg4cU1nQi7Th@* zZKiF#JZXPIfIOa!lCF@$b!ji8Xhe&onSi<3Y#x>mpFTa?X0IseOkhGusN|9qdE#dG zr~?uv0)`5ALB+x)E7GpC2O##I^il?KNp3<_Ek_zSv zVWkqOtPoB*8ZOku>8yGPbr2yKE(~Fc%7?mQ@yv9JFs2v@YK)(yCvC+!?bM`}5<(tw zD|WV+8Wv>fjWE1{$4iNV(oRlk?+=qC*CQ}+oKR-XL8O@USU+_oU*ap~ujG^pAt>7_ zSLP(=TObs&f5_lm3npA}G*^KV-B2qK3_7@Vgr~w_`VdAwSp5N_EF@(3=M{fFuQh9A zD*$rr-0&iw8oxkY7?%FLyJJ;yR4Ac5ju87wri3*Hw3ylEDe@U@#6-$N2`{=2d`@Gd zN3c43summADjIg@H_;=HHA0236u)(rZm7><8Lv1R6C;Nt!P&ee zGA_{}-U>i+P6?{ddzyWM{PZsL*1|;)VFPcme>ye!Y!{>(J={LG8+IJW`I*>tYGH&Z{Of7S6 zZNm%$H;|#t5r+9^VLBYk(mfM~uD^7s?N6*A+HIUv;E75DldWKm@We3XQ}N1u_pTD4 zB|XSA?TDu*gVMhzj|Bkxe`i^Fr8viypNOe;(dm8RSYp0S+m)7YIK9V`OG;dDUy%cI z0Cux=g3J(|Qd*PLNA<0{DhYD$z{tpC0p3#0mROIhE~JRNKZ7M$;V*)Pb)m*K6EU=oD!(oP}qc8Oh1w zdu&MG<^E2}ld*yb?vCr143w6{4f|#23TZHy~l>hHh* z{YXL-G?92>79Z3f_Gyc$#-jd zA@d|_Ea)RZfu2(q8+9y;&gU^TB2$-bQ?o|tJT>Ekt%-?Szbf+M=|Nxtf#0VGMgVa; z{_nVAgS&pjHo`_=9Dcrf;utP0I1x$KJ5J<@0wHc5Au;R>nYyWYnL)S^2wpRD|zo?f_cf&Cag43y*?YhAGL?UK5P?Ch_+7WH{$V;|ID zMBQJ*F3aR;gTeF`-aDdx>p|~3=2OI|W0W}c&IF31O~uqbiYw%l@r->qimR0pTh)2q zepA9x`X2M(F(4EkQHs(3c?t-sWo-;cNhZ9c9Vw4ZD}`(~i#;Ki+< z2u^PwBKLGn&dSmzMF*d+%ffrz_5SOX_|HGZeRmTvq`eWF0wDkdc%57hjoG=VW?IDg$)YZK2Oum3Frxpl1qMr_*LbNt3mOF_3u7i#Nt$MF%Jv z^F2wRZp(^<1f<&w=uT`)t=()Ij+ZwlMMP%bW;_tlV|?&ozks>s1-y`T1R^xAkS?Dk z>n=QulDaRw1v$o|Cn;>wvr~R^K|{$Z67r0`9VQIL@L1Gp z51uadsC@SA z{|<^eAta@iLjF>EVkqFWAom&2Y;+0HPe){rP|&mE!v&v!`1WD7l%Ua|;-HdC(Cd)A zl8}N;`pc1*;Xfr?&B9G^9lg~ zc5ZBpqDjwTK>UA5Qtrci)tlKyYni}k$^kv`=R8@u6}}lCWe~p8=cENJme!3!&VNp! z_r*S0DayZD4QyV1X~v$DXcWk*hWrkJX608Ow~Bj%$te1%>+<19_KYH#{Khn5eh!u&*$vTJ-zk>99H`{Tr!@WJuV4%)Av!wdq`9rj8!1oKXT;xlbWXdHB&N}W`bm;r4& zS+9p`eqO!3@}G9;&XP`cX09u@+^PKq)%E^AHDhwfy35*}eL<^q2L^Ku2K5AS`A6K`3K0 zSaw95`hE-y$Go5V+Z#)i2%=oF9~^ZSBVMQlAa@J3sn1Id?-N-JUnlKE;(ti+kAL}d z>1nrRjlD5~i^0Jc1u?xws0a85KrpIO0ML~1Y5eDyTz>37(x)>{P#-h>ILK=H3LkC< zeZBbKL;Pi%?5^`2d6*>gP%;gOzUrtv+1JtS?#nf{q4laqD0n{7D(Y`|B@W2=w>2bLKMfFr}Dt0sF!aKb-oP z#Tv-hsbFNm?Ad{4DUU+~k^FysK2?;l#>rtLV_IcDm|@U$i62PW$z~%P1G8nI0|==` zy5v<$2?SA_#Su%QO66Y91PJW86zi$&wGWdiyQGlc1L zBgGw&@SytMz2~F->H5}#qUQNLZgZ@=8JP>n(@9iA!-3fx@O?%R8f@`O7JqJoEXUqI zG{KJB6EX_(c~cj;QV}@I>e<^^@&G5@n%~v9sK5TrQ~WNpR25qO%G2mt`ds!_R%86a zST;L$VSFQNYYhG6bQ@9yZtg>B*^EhZ+ubJ4whDeTo!92W zDlpIobfaRqT?ZiOz&0p=XF$Q3fnL={01^_!vlE(K2@7GN;sNCwuLU$-9yDP7-N}gr zQ2E77M&>60#`#pzqYUpETO8vNTL6#$V4R_4DCDyeo1*s+d|_6oDp$TB*|oy{zO_~xEH z;8ie{`$WfD(5k+^%1Vw6f1etYjTyHV)3A)LI?~u^iZof|FqGh}VkqCfL1{E=)&SPs z)O)(td*7LTK-_I18^@5-5qoGD+)}@D%^IG~6uAr#v_`RU)xCV_IB_#ya_=RJ^1Hc= ze;Jgp?-JF#J<*Dt{N_5}@dlZt^A{)$Rrw6tqnxlQ%Q`5)86-YA)q{&f^te63tL8jP zj>2_gX5m;5A6euC+1&Ok8n?c@Sz3A-z5hOY%|gRrjM?uPiUCFm?if_4 zo6lVYImHg7KSoL*PB~JW|NTILWZnNFegEoqrRKn&axVt3=8u;$93CD$kmCNcHH^XJ zRb5e+kRQBw(h}-N7(oyJhzK#855w)PpyX-4AVCoUe>~D44Lhw(Ifnv!*H~XzS*}!19T)Wc2ZnI*NE)V7qt z?VIe8U?FO28)I=gx2R%a0a)(PvrziEfN_7EC%Q7smKRe7h?0j*WS4W!+6*K6O`D7R zM?id%P8QUW_d&MT9%I6DfZ-w*jM2n)za)k#valmeP;bA?M=n&0nWS3gSmOAcBD*A` z3_>eL$;!1~`QMlk5l%D%+Z{|76N80av&%xS%`t{k6df85E<9JqnME?QsFlUa=jZ>* zAq+TxL;RR%&j(b%;}*K2Z)og>c==yow`@MCc;Br?x$*(W-UY5oXnh4FryRVfQP04L zVpO%uc1ut@3|AFcgbA~b>^V)Vg7S{sGF=HipE*ClhUPOU$#_;ap2RF|{G2RBSH>D& zwv9{6L!XuTzjeABz}pz?CC(6?FnMjmInU3;z7&W+VKRD@K#X+jeq$P}YqpRLN~DQ zY<*~$U|P{0>bb}+s4N6IdU(80rS%ynoaI#*TS@KYM zQg{jXiP)r8h@6aDXX|YBkJS&iunKZT&I_{|7x;HvleD9Vtt9;_0ysLxee?>b+~D#Htj;;trz=dKNYf|34KB_Wl;X?cC}>>J27i zM4TVF*~p&G`bMml-Y{S|2DDTH-CFrh=hZUIk}V0BJSQuaUi(d=AtpJghgcJD46H1g z$h4BovTL^OhhB49XH6(c&hjb*& z=xmzZRU>TG*9lHNM*8_?Q}SZ+!44ylt4)ALr zEh{>WWQ`ik$rVyjB}AMO`2~N)TtprWV8l6vE8LqG_lbRZ_q?xAY77lnTI;OSajFAE z!`J!WHKLx%WbBo-MLI3r5&*Nhw#?Bt>%F6v_PNHci&)fc?q~cce;{d$-wsyB{dK!5 zmgjW7y@Z(O)~TYUzqBKw;MJ%z{iBRK;;$FXw8qJN4$(6*2}JDl!Tl*C3}60~1sE56 z%9wp@mm8NqC81}Pi=VOz5z8FrOh-B0aSn5$ogLv6Cp*LH?BgiMILnC+bGic_+!;=n z;+JA=)?lrle?xZ2gfmb6^ke=gvTB_p6PrNwbC-HB#g6q^opqsV)6ssg_ed^ Date: Thu, 9 Nov 2023 15:09:39 +0100 Subject: [PATCH 199/419] Add material icon font --- src/styles/base/_base-dir.scss | 1 + src/styles/base/_icons.scss | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 src/styles/base/_icons.scss diff --git a/src/styles/base/_base-dir.scss b/src/styles/base/_base-dir.scss index 39e08c82..57cac367 100644 --- a/src/styles/base/_base-dir.scss +++ b/src/styles/base/_base-dir.scss @@ -5,5 +5,6 @@ @import 'animations'; @import 'base'; @import 'typography'; +@import 'icons'; @import 'colors'; @import 'form'; \ No newline at end of file diff --git a/src/styles/base/_icons.scss b/src/styles/base/_icons.scss new file mode 100644 index 00000000..b8b32d72 --- /dev/null +++ b/src/styles/base/_icons.scss @@ -0,0 +1,32 @@ +@font-face { + font-family: 'Material Icons'; + src: url('../../assets/fonts/material-icons.woff2') format('woff2'); + font-weight: normal; + font-style: normal; +} + +.material-icons { + font-family: 'Material Icons'; + font-weight: normal; + font-style: normal; + font-size: 24px; + /* Preferred icon size */ + display: inline-block; + line-height: 1; + text-transform: none; + letter-spacing: normal; + word-wrap: normal; + white-space: nowrap; + direction: ltr; + + /* Support for all WebKit browsers. */ + -webkit-font-smoothing: antialiased; + /* Support for Safari and Chrome. */ + text-rendering: optimizeLegibility; + + /* Support for Firefox. */ + -moz-osx-font-smoothing: grayscale; + + /* Support for IE. */ + font-feature-settings: 'liga'; +} \ No newline at end of file From aed98cf5fe7190f75a1495d3c29c6c14e10702f5 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Thu, 9 Nov 2023 15:09:59 +0100 Subject: [PATCH 200/419] Remove web hosted fonts --- src/index.html | 1 - 1 file changed, 1 deletion(-) diff --git a/src/index.html b/src/index.html index 6ffae50e..a0748cc4 100644 --- a/src/index.html +++ b/src/index.html @@ -7,7 +7,6 @@ - From 062ff07bca66739a546cf52de881635282837be6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Thu, 9 Nov 2023 15:55:47 +0000 Subject: [PATCH 201/419] Migrating to Angular Material --- angular.json | 2 +- src/app/app.component.html | 2 +- src/app/config/server/server.component.html | 2 +- src/app/core/_constants/files.config.ts | 9 + src/app/core/_constants/settings.config.ts | 22 +- .../core/_directives/selectize.directive.ts | 15 - src/app/core/_models/horizontalnav.model.ts | 4 + src/app/core/_services/metadata.service.ts | 634 ++++++++++++++++++ .../core/_services/shared/alert.service.ts | 3 +- src/app/core/_services/unsubscribe.service.ts | 32 + src/app/home/home.component.html | 2 +- .../shared/alert/swal/page-title.component.ts | 0 src/app/shared/buttons/button-submit.ts | 13 +- src/app/shared/buttons/buttons.module.ts | 14 +- src/app/shared/buttons/grid-cancel.ts | 11 +- src/app/shared/components.module.ts | 36 +- src/app/shared/directives.module.ts | 3 - src/app/shared/form/dynamicform.component.ts | 346 ++++++++++ src/app/shared/form/dynamicform.module.ts | 57 ++ .../form/dynamicformlayout.component.ts | 369 ++++++++++ src/app/shared/form/form.component.ts | 244 +++++++ src/app/shared/form/formconfig.component.ts | 205 ++++++ .../shared/form/formuisettings.component.ts | 139 ++++ .../mat-autocomplete.component.ts | 43 ++ .../shared/grid-containers/grid-autocol.ts | 81 ++- src/app/shared/grid-containers/grid-main.ts | 11 +- src/app/shared/grid-containers/grid.module.ts | 2 + .../navigation/horizontalnav.component.ts | 69 ++ .../shared/navigation/navigation.module.ts | 10 + .../shared/page-headers/page-title.module.ts | 6 +- src/app/users/users-routing.module.ts | 48 +- src/app/users/users.component.html | 40 -- src/app/users/users.component.ts | 75 --- src/app/users/users.module.ts | 2 - src/styles/components/_button.scss | 6 + src/styles/components/_card.scss | 11 +- src/styles/components/_form.scss | 1 + src/styles/styles.scss | 2 +- 38 files changed, 2321 insertions(+), 250 deletions(-) create mode 100644 src/app/core/_constants/files.config.ts delete mode 100644 src/app/core/_directives/selectize.directive.ts create mode 100644 src/app/core/_models/horizontalnav.model.ts create mode 100644 src/app/core/_services/metadata.service.ts create mode 100644 src/app/core/_services/unsubscribe.service.ts delete mode 100644 src/app/shared/alert/swal/page-title.component.ts create mode 100644 src/app/shared/form/dynamicform.component.ts create mode 100644 src/app/shared/form/dynamicform.module.ts create mode 100644 src/app/shared/form/dynamicformlayout.component.ts create mode 100644 src/app/shared/form/form.component.ts create mode 100644 src/app/shared/form/formconfig.component.ts create mode 100644 src/app/shared/form/formuisettings.component.ts create mode 100644 src/app/shared/form/mat-autocomplete/mat-autocomplete.component.ts create mode 100644 src/app/shared/navigation/horizontalnav.component.ts create mode 100644 src/app/shared/navigation/navigation.module.ts delete mode 100644 src/app/users/users.component.html delete mode 100644 src/app/users/users.component.ts diff --git a/angular.json b/angular.json index 35546506..924293c4 100644 --- a/angular.json +++ b/angular.json @@ -166,4 +166,4 @@ ], "analytics": false } -} \ No newline at end of file +} diff --git a/src/app/app.component.html b/src/app/app.component.html index 180b59e1..239ff1b5 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -6,4 +6,4 @@

- \ No newline at end of file + diff --git a/src/app/config/server/server.component.html b/src/app/config/server/server.component.html index 6a609f9f..d0d6788e 100644 --- a/src/app/config/server/server.component.html +++ b/src/app/config/server/server.component.html @@ -660,7 +660,7 @@
Hashcat Brain Server Parameters
#serverLogLevel (change)="autoSave('serverLogLevel',serverLogLevel.value)" > - + diff --git a/src/app/core/_constants/files.config.ts b/src/app/core/_constants/files.config.ts new file mode 100644 index 00000000..309ded55 --- /dev/null +++ b/src/app/core/_constants/files.config.ts @@ -0,0 +1,9 @@ +/** + * Files values and label +**/ + +export const fileFormat = [ + {value: 0, label:'Wordlist'}, + {value: 1, label:'Rule'}, + {value: 2, label:'Other'}, +]; diff --git a/src/app/core/_constants/settings.config.ts b/src/app/core/_constants/settings.config.ts index 8918e834..3678f293 100644 --- a/src/app/core/_constants/settings.config.ts +++ b/src/app/core/_constants/settings.config.ts @@ -34,25 +34,23 @@ export const themes: Setting[] = [ /** * Logs, used in general settings **/ - export const serverlog = [ - { id: 0, value: 'TRACE' }, - { id: 10, value: 'DEBUG' }, - { id: 20, value: 'INFO' }, - { id: 30, value: 'WARNING' }, - { id: 40, value: 'ERROR' }, - { id: 50, value: 'FATAL' } + {value: 0, label: 'TRACE'}, + {value: 10, label: 'DEBUG'}, + {value: 20, label: 'INFO'}, + {value: 30, label: 'WARNING'}, + {value: 40, label: 'ERROR'}, + {value: 50, label: 'FATAL'} ]; /** * Proxy type, used in general settings **/ - export const proxytype = [ - { value: 'HTTP' }, - { value: 'HTTPS' }, - { value: 'SOCKS4' }, - { value: 'SOCKS5' } + {value:'HTTP', label:'HTTP'}, + {value:'HTTP', label:'HTTPS'}, + {value:'HTTP', label:'SOCKS4'}, + {value:'HTTP',label:'SOCKS5'} ]; /** diff --git a/src/app/core/_directives/selectize.directive.ts b/src/app/core/_directives/selectize.directive.ts deleted file mode 100644 index 38eb620e..00000000 --- a/src/app/core/_directives/selectize.directive.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { - Directive, - ElementRef, - OnInit } from "@angular/core"; - -@Directive({ - selector: '[appSelectize]' -}) -export class SelectizeDirective implements OnInit{ - constructor(private elementRef: ElementRef){} - - ngOnInit(): void { - - } -} diff --git a/src/app/core/_models/horizontalnav.model.ts b/src/app/core/_models/horizontalnav.model.ts new file mode 100644 index 00000000..16d4f578 --- /dev/null +++ b/src/app/core/_models/horizontalnav.model.ts @@ -0,0 +1,4 @@ +export class HorizontalNav { + label: string; + routeName: string; +} diff --git a/src/app/core/_services/metadata.service.ts b/src/app/core/_services/metadata.service.ts new file mode 100644 index 00000000..8de2722b --- /dev/null +++ b/src/app/core/_services/metadata.service.ts @@ -0,0 +1,634 @@ +import { dateFormats, serverlog, proxytype } from '../../core/_constants/settings.config'; +import { ACTIONARRAY, NOTIFARRAY } from '../../core/_constants/notifications.config'; +import { fileFormat } from '../../core/_constants/files.config'; +import { TooltipService } from '../../core/_services/shared/tooltip.service'; +import { environment } from 'src/environments/environment'; +import { FormControl, Validators } from '@angular/forms'; +import { SERV } from '../../core/_services/main.config'; +import { BehaviorSubject, Observable, map } from 'rxjs'; +import { HttpClient } from '@angular/common/http'; +import { GlobalService } from './main.service'; +import { Injectable } from '@angular/core'; + + +@Injectable({ + providedIn: 'root' +}) +export class MetadataService { + + tooltip:any; + + constructor( + private tooltipService: TooltipService, + private gs: GlobalService + ) { + this.tooltip = this.tooltipService.getConfigTooltips(); + } + + private maxResults = environment.config.prodApiMaxResults; + + // ToDo in validators, go to the database and add max lenght + //checboxes issues when value is false, + + // // + // Metadata Structure + // // + + // Info Metadata, it contains information about the page such as title, subtitles, and notifications configuration. + infoMetadataForm = { + title: "Title for the form page", + customform: false, + subtitle: false, + submitok: "Message displayed upon successful submission", + submitokredirect: "Redirect URL upon successful submission", + deltitle: "Title for deletion confirmation", + delsubmitok: "Message displayed upon successful deletion", + delsubmitokredirect: "Redirect URL upon successful deletion", + delsubmitcancel: "Message displayed when deletion is canceled", + }; + + // Metadata form, it contains information about each field. + metadataFormField = [ + { + name: "API name to be map with the formControl", + label: "Label name to be displayed", + type: "Type of the form field; (e.g., select, text, checkbox)", + placeholder: "Type option text, then add placeholder", + selectOptions: "Select options if the type is 'select'", + selectOptions$: "Select options if the type is 'selectd', used with selectEndpoint", + selectEndpoint$: "API endpoint route, use SERV", + fieldMapping: "Object with the dropdown options to be mapped, that is id and name. ie. id: _id, name:groupName", + requiredasterisk: "Indicates if the field is required", + tooltip: "Tooltip information as string or using ", + validators: "Validation rules", + isTitle: "boolean, if its true will use only the label field" + }, + ]; + + // Examples + // Create title between fields. use { label: 'More settings', isTitle: true } + + // // // // // // // // + // ACCOUNT SECTION // + // // // // // // // // + + // // + // Notifications + // // + + // This variable stores information about the edit notification page. + newnotifInfo = [ + { title: 'New Notification', customform: false, subtitle: false, submitok: 'New Notification created!', submitokredirect: '/account/notifications'}, + ]; + + // This variable stores information about the edit notification page. + editnotifInfo = [ + { title: 'Edit Notification', customform: false, subtitle: false, submitok: 'Saved!', submitokredirect: '/account/notifications'}, + ]; + + //This variable defines the fields and properties required when creating a cracker Version. + newnotif = [ + { name: 'action', type: 'seltextect', selectOptions: ACTIONARRAY }, + { name: 'actionFilter', label: 'Value', type: 'text'}, + { name: 'notification', label: 'Notification', type: 'select', selectOptions: NOTIFARRAY}, + { name: 'receiver', label: 'Receiver', type: 'text'}, + { name: 'isActive', label: 'Receiver', type: 'checkbox',defaultValue: true}, + ]; + + //This variable defines the fields and properties required when editing a notification. + editnotif = [ + { name: 'action', type: 'text', disabled: true}, + { name: 'notification', label: 'Notification', type: 'text', disabled: true}, + { name: 'receiver', label: 'Receiver', type: 'text', disabled: true}, + { name: 'isActive', label: 'Receiver', type: 'checkbox', validators: [Validators.required]}, + ]; + + + // // // // // // // // + // TASKS SECTION // + // // // // // // // // + + // // + // Pretask + // // + + + // // + // Supertask + // // + + supertaskInfo = [ + { title: 'New Supertask', customform: false, subtitle: false, submitok: 'New SuperTask created!', submitokredirect: 'tasks/supertasks'}, + ]; + + supertask = [ + { name: 'supertaskName', label: 'Name', type: 'text', requiredasterisk: true, tooltip: false, validators: [Validators.required] }, + { name: 'pretasks', label: 'Select or search tasks assigned to this supertask:', type: 'select', selectOptions: [], requiredasterisk: true, tooltip: false, validators: [Validators.required]}, + ]; + + // // // // // // // // + // FILES SECTION // + // // // // // // // // + + // // + // Files + // // + + // This variable stores information about the edit wordlist file page. + editwordlistInfo = [ + { title: 'Edit Wordlist File', customform: false, subtitle: false, submitok: 'Saved!', submitokredirect: '/files/wordlist'}, + ]; + + // This variable stores information about the edit rule file page. + editruleInfo = [ + { title: 'Edit Rule File', customform: false, subtitle: false, submitok: 'Saved!', submitokredirect: '/files/rules'}, + ]; + + // This variable stores information about the edit other file page. + editotherInfo = [ + { title: 'Edit Other File', customform: false, subtitle: false, submitok: 'Saved!', submitokredirect: '/files/other'}, + ]; + + //This variable defines the fields and properties required when editing a wonrdlist, rule or other file. + editfile = [ + {name: 'fileId', label: 'ID', type: 'number', disabled: true}, + {name: 'filename', label: 'Name', type: 'text'}, + {name: 'fileType', label: 'File Type', type: 'select', selectOptions: fileFormat}, + {name: 'accessGroupId', label: 'Access group', type: 'selectd', requiredasterisk: true, selectEndpoint$: SERV.ACCESS_GROUPS, selectOptions$: [], fieldMapping: {id: '_id', name: 'groupName' }}, + {name: 'isSecret', label: 'Secret', type: 'checkbox'} + ]; + + // // // // // // // // + // CONFIG SECTION // + // // // // // // // // + + // // + // New Cracker + // // + + // This variable stores information about the new cracker page. + newcrackerInfo = [ + { title: 'New Cracker Type', customform: false, subtitle: false, submitok: 'New Cracker created!', submitokredirect: '/config/engine/crackers'}, + ]; + + //This variable defines the fields and properties required when creating a new cracker. + newcracker = [ + { name: 'typeName', label: 'Type', type: 'select', selectOptions: [{ label: 'Hashcat', value: 'hashcat' },{ label: 'Generic Cracker', value: 'generic' }], requiredasterisk: true, tooltip: false, validators: [Validators.required] }, + { name: 'isChunkingAvailable', label: 'Chunking Available', type: 'select', selectOptions: [{ label: 'Yes', value: true },{ label: 'No', value: false }], requiredasterisk: true, tooltip: false, validators: [Validators.required] } + ]; + + // // + // Agent Binary + // // + + // This variable stores information about the New Agent Binary page. + newagentbinaryInfo = [ + { title: 'New Agent Binary', customform: false, subtitle: false, submitok: 'New Agent Binary created!', submitokredirect: 'config/engine/agent-binaries'}, + ]; + + // This variable stores information about the Edit Agent Binary page. + editagentbinaryInfo = [ + { title: 'Edit Agent Binary', customform: false, subtitle: false, submitok: 'Agent Binary saved!', submitokredirect: 'config/engine/agent-binaries', deltitle: 'Agent Binaries', delsubmitok: 'Deleted Agent Binary', delsubmitokredirect: 'config/engine/agent-binaries', delsubmitcancel:'Agent Binary is safe!'}, + ]; + + //This variable defines the fields and properties required when creating/editing an Agent Binary. + agentbinary = [ + { name: 'type', label: 'Type', type: 'text', requiredasterisk: true, tooltip: false, validators: [Validators.required] }, + { name: 'operatingSystems', label: 'Operating Systems', type: 'text', requiredasterisk: true, tooltip: false, validators: [Validators.required] }, + { name: 'filename', label: 'Filename', type: 'text', requiredasterisk: true, tooltip: 'Placed in bin folder', validators: [Validators.required] }, + { name: 'version', label: 'Version', type: 'text', requiredasterisk: true, tooltip: false, validators: [Validators.required] }, + { name: 'updateTrack', label: 'Update Track', type: 'select', selectOptions: [{ label: 'Release', value: 'release' },{ label: 'Stable', value: 'stable' }], requiredasterisk: true, tooltip: false, validators: [Validators.required] } + ]; + + // // + // Cracker Version + // // + + // This variable stores information about the New Cracker Version page. + newcrackerversionInfo = [ + { title: 'New Binary Version', customform: true, subtitle: false, submitok: 'New Version created!', submitokredirect: '/config/engine/crackers'}, + ]; + + // This variable stores information about the Edit Cracker Version page. + editcrackerversionInfo = [ + { title: 'Edit Binary Version', customform: false, subtitle: false, submitok: 'Cracker saved!', submitokredirect: '/config/engine/crackers', deltitle: 'Crackers', delsubmitok: 'Deleted cracker', delsubmitokredirect: 'config/engine/crackers', delsubmitcancel:'Cracker is safe!'}, + ]; + + //This variable defines the fields and properties required when creating a cracker Version. + newcrackerversion = [ + { name: 'binaryName', label: 'Binary Base Name', type: 'text', requiredasterisk: true, tooltip: 'Which needs to be called on the client without os-dependent extension', validators: [Validators.required] }, + { name: 'version', label: 'Binary Version', type: 'text', requiredasterisk: true, tooltip: false, validators: [Validators.required] }, + { name: 'downloadUrl', label: 'Download URL', type: 'text', requiredasterisk: true, tooltip: 'Link where the client can download a 7zip with the binary', validators: [Validators.required] }, + { name: 'crackerBinaryTypeId', label: 'crackerBinaryTypeId', type: 'hidden', replacevalue: 'editedIndex',requiredasterisk: true, tooltip: false, validators: false }, + ]; + + //This variable defines the fields and properties required when editing a cracker Version. + editcrackerversion = [ + { name: 'binaryName', label: 'Binary Base Name', type: 'text', requiredasterisk: true, tooltip: 'Which needs to be called on the client without os-dependent extension', validators: [Validators.required] }, + { name: 'version', label: 'Binary Version', type: 'text', requiredasterisk: true, tooltip: false, validators: [Validators.required] }, + { name: 'downloadUrl', label: 'Download URL', type: 'text', requiredasterisk: true, tooltip: 'Link where the client can download a 7zip with the binary', validators: [Validators.required] }, + ]; + + // // + // Preprocessor + // // + + // This variable stores information about the New Preprocessor page. + newpreprocessorInfo = [ + { title: 'New Preprocessor', customform: false, subtitle: false, submitok: 'New Preprocessor created!', submitokredirect: 'config/engine/preprocessors'}, + ]; + + // This variable stores information about the Edit Preprocessor page. + editpreprocessorInfo = [ + { title: 'Edit Preprocessor', customform: false, subtitle: false, submitok: 'Preprocessor saved!', submitokredirect: 'config/engine/preprocessors', deltitle: 'Preprocessors', delsubmitok: 'Deleted Preprocessor', delsubmitokredirect: 'config/engine/preprocessors', delsubmitcancel:'Preprocessor is safe!'}, + ]; + + //This variable defines the fields and properties required when creating/editing a Hashtype. + preprocessor = [ + { name: 'name', label: 'Name', type: 'text', requiredasterisk: true, tooltip: false, validators: [Validators.required] }, + { name: 'binaryName', label: 'Binary Basename', type: 'text', requiredasterisk: true, tooltip: false, validators: [Validators.required] }, + { name: 'url', label: 'Download URL', type: 'text', requiredasterisk: true, tooltip: false, validators: [Validators.required] }, + { label: 'Commands (set to empty if not available)',isTitle: true}, + { name: 'keyspaceCommand',label: 'Keyspace Command',type: 'text',requiredasterisk: false,tooltip: false,validators: false,defaultValue: '--keyspace',}, + { name: 'skipCommand',label: 'Skip Command',type: 'text',requiredasterisk: false,tooltip: false,validators: false,defaultValue: '--skip'}, + { name: 'limitCommand', label: 'Limit Command', type: 'text', requiredasterisk: false, tooltip: false, validators: false, defaultValue: '--limit'}, + ]; + + // // + // Hashtypes + // // + + // This variable stores information about the New Hashtypes page. + newhashtypeInfo = [ + { title: 'Create Hashtype', customform: false, subtitle: false, submitok: 'New Hashtype created!', submitokredirect: '/config/hashtypes'}, + ]; + + // This variable stores information about the Editing Hashtypes page. + edithashtypeInfo = [ + { title: 'Edit Hashtype', customform: false, subtitle: false, submitok: 'Hashtype saved!', submitokredirect: '/config/hashtypes', deltitle: 'Hashtypes', delsubmitok: 'Deleted Hashtype', delsubmitokredirect: '/config/hashtypes', delsubmitcancel:'Hashtype is safe!'}, + ]; + + //This variable defines the fields and properties required when creating a new Hashtype. + newhashtype = [ + { name: 'hashTypeId', label: 'Hashtype', type: 'number', requiredasterisk: true, tooltip: 'ie. Hashcat -m', validators: [Validators.required, Validators.pattern("^[0-9]*$"), Validators.minLength(1), this.numberValidator]}, + { name: 'description', label: 'Description', type: 'text', requiredasterisk: true, tooltip: false, validators: [Validators.required, Validators.minLength(1)] }, + { name: 'isSalted', label: 'Salted', type: 'checkbox', requiredasterisk: false, tooltip: 'Only if there is a separate salt value', validators: false, defaultValue: false }, + { name: 'isSlowHash', label: 'Slow Hash', type: 'checkbox', requiredasterisk: false, tooltip: false, validators: false, defaultValue: false }, + ]; + + //This variable is similar to newhashtype but is used for editing an existing Hashtype. As difference include disable form variable. + edithashtype = [ + { name: 'hashTypeId', label: 'Hashtype', type: 'number', requiredasterisk: true, tooltip: 'ie. Hashcat -m', validators: [Validators.required, Validators.pattern("^[0-9]*$"), Validators.minLength(1), this.numberValidator], disabled: true}, + { name: 'description', label: 'Description', type: 'text', requiredasterisk: true, tooltip: false, validators: [Validators.required, Validators.minLength(1)] }, + { name: 'isSalted', label: 'Salted', type: 'checkbox', requiredasterisk: false, tooltip: 'Only if there is a separate salt value', validators: false, defaultValue: false }, + { name: 'isSlowHash', label: 'Slow Hash', type: 'checkbox', requiredasterisk: false, tooltip: false, validators: false, defaultValue: true }, + ]; + + // // + // Server + // // + + serveragentInfo = [ + { title: 'Agent Settings', customform: false, subtitle: false, submitok: 'Saved!', submitokredirect: '/config/agent'}, + ]; + + serveragent = [ + { label: 'Activity / Registration', isTitle: true }, + { name: "agenttimeout", label: "Delay before considering an agent as inactive(or timed out)", type: "number", tooltip: false }, + { name: "benchtime", label: "Delay before considering an issued chunk as inactive", type: "number", tooltip: false }, + { name: "statustimer", label: "Frequency of the agent reporting about a task to the server", type: "number", tooltip: false }, + { name: "agentDataLifetime", label: "Time during which util and temperature data are retained on the server", type: "number", tooltip: false }, + { name: "hideIpInfo", label: "Hide agents IP information", type: "checkbox", tooltip: false }, + { name: "voucherDeletion", label: "Voucher(s) can be used to register multiple agents", type: "checkbox", tooltip: false }, + { label: 'Graphical Feedback', isTitle: true }, + { name: "agentStatLimit", label: "Maximum number of data points in agent (gpu) graphs", type: "number", tooltip: false }, + { name: "agentStatTension", label: "Draw straight lines in agent data graph instead of bezier curves", type: "select", selectOptions: [ + { label: "Straight lines", value: "0" }, + { label: "Bezier curves", value: "1" } + ], tooltip: false + }, + { name: "agentTempThreshold1", label: "Temperature threshold above which an agent is displayed in orange in the status page", type: "number", tooltip: false }, + { name: "agentTempThreshold2", label: "Temperature threshold above which an agent is displayed in red in the status page", type: "number", tooltip: false }, + { name: "agentUtilThreshold1", label: "Util threshold below which an agent is displayed in orange in the status page", type: "number", tooltip: false }, + { name: "agentUtilThreshold2", label: "Util threshold below which an agent is displayed in red in the status page", type: "number", tooltip: false } + ]; + + servertaskchunkInfo = [ + { title: 'Task/Chunk Settings', customform: false, subtitle: false, submitok: 'Saved!', submitokredirect: '/config/task-chunk'}, + ]; + + servertaskchunk = [ + { label: 'Benchmark / Chunk', isTitle: true }, + { name: "chunktime", label: "Expected duration of a chunk", type: "number", tooltip: "tctip.chunktime" }, + { name: "disptolerance", label: "Authorised expansion in percentage of the final chunk of a task", type: "number", tooltip: false }, + { name: "defaultBenchmark", label: "Speed benchmark as default benchmark process", type: "checkbox", tooltip: false }, + { name: "disableTrimming", label: "Disable trimming of chunks and redo the whole chunk", type: "checkbox", tooltip: false }, + { label: 'Command Line & Misc.', isTitle: true }, + { name: "hashlistAlias", label: "Placeholder string for the hashlist in the command line during task creation", type: "text", tooltip: false }, + { name: "blacklistChars", label: "Forbidden characters in the attack command input", type: "text", tooltip: "tctip.blacklistChars" }, + { name: "priority0Start", label: "Also automatically assign tasks with priority 0 (Needed, check file)", type: "checkbox", tooltip: false }, + { name: "showTaskPerformance", label: "Show cracks/minute for active tasks", type: "checkbox", tooltip: false }, + { label: 'Rule splitting', isTitle: true }, + { name: "ruleSplitSmallTasks", label: "When rule splitting is applied for tasks, always make them a small task", type: "checkbox", tooltip: false }, + { name: "ruleSplitAlways", label: "Even do rule splitting when there are not enough rules but just the benchmark is too high. Can result in subtasks with just one rule", type: "checkbox", tooltip: false }, + { name: "ruleSplitDisable", label: "Disable automatic task splitting with large rule files", type: "checkbox", tooltip: false }, + ]; + + serverhchInfo = [ + { title: 'Hashes/Cracks/Hashlist Settings', customform: false, subtitle: false, submitok: 'Saved!', submitokredirect: '/config/hch'}, + ]; + + serverhch = [ + { label: 'Import/Display of Hashlist', isTitle: true }, + { name: "maxHashlistSize", label: "Maximum number of lines in a hashlist", type: "number", tooltip: false }, + { name: "pagingSize", label: "Number of hashes shown per page in the Hash View", type: "number", tooltip: false }, + { name: "hashesPerPage", label: "Number of hashes per page on hashes view", type: "number", tooltip: false }, + { name: "fieldseparator", label: "The separator character used to separate hash and plain (or salt)", type: "text", tooltip: false }, + { name: "hashlistImportCheck", label: "Check if hashes have been previously cracked (in other hashlists) at hashlist creation time", type: "checkbox", tooltip:false }, + { label: 'Database Parameters', isTitle: true }, + { name: "batchSize", label: "Batch size of SQL query when hashlist is sent to the agent", type: "number", tooltip: false }, + { name: "plainTextMaxLength", label: "Maximum length of plain text", type: "number", tooltip: false }, + { name: "hashMaxLength", label: "Maximum length of a hash", type: "number", tooltip: 'Such change may take a long time depending on the database size' } + ]; + + servernotifInfo = [ + { title: 'Notification Settings', customform: false, subtitle: false, submitok: 'Saved!', submitokredirect: '/config/notifications'}, + ]; + + // hashMaxLength it should be this validator type +// +// Such change may take a long time depending on the database size +// + + servernotif = [ + { name: "emailSender", label: "Email address sending the notification emails", type: "text", tooltip: false }, + { name: "emailSenderName", label: "Sender's name on emails sent from Hashtopolis", type: "text", tooltip: false }, + { name: "telegramBotToken", label: "Telegram bot token used to send telegram notifications", type: "text", tooltip: false }, + { name: "notificationsProxyEnable", label: "Enable using a proxy for sending notifications", type: "checkbox", tooltip: false }, + { label: 'Proxy Settings', isTitle: true }, + { name: "notificationsProxyServer", label: "Server URL of the proxy to use for notification", type: "text", placeholder:"http...", tooltip: false }, + { name: "notificationsProxyPort", label: "Set the port for the notifications proxy", type: "number", tooltip: false }, + { name: "notificationsProxyType", label: "Proxy type to use for notifications", type: "select", selectOptions: proxytype, tooltip: false } + ]; + + //Evretyhing inside Enable using proxy + //
+ servergsInfo = [ + { title: 'General Settings', customform: false, subtitle: false, submitok: 'Saved!', submitokredirect: '/config/general-settings'}, + ]; + + servergs = [ + { name: "hashcatBrainEnable", label: "Enable Hashcat Brain", type: "checkbox", tooltip: false }, + { name: "hashcatBrainHost", label: "Host to be used for hashcat brain (must be reachable by agents)", type: "text", placeholder: "URL", tooltip: false }, + { name: "hashcatBrainPort", label: "Port for hashcat brain", type: "number", placeholder: "I.e. 8080", tooltip: false }, + { name: "hashcatBrainPass", label: "Password to be used to access hashcat brain server", type: "password", tooltip: false }, + { name: "hcErrorIgnore", label: "Ignore error messages from crackers containing the string below", type: "textarea", tooltip: false }, + { name: "numLogEntries", label: "Number of log entries to be retained", type: "number", tooltip: false }, + { name: "timefmt", label: "Set the time format", type: "text", tooltip: false }, + { name: "maxSessionLength", label: "Max session length users can configure (in hours)", type: "text", tooltip: false }, + { name: "baseHost", label: "Base hostname/port/protocol to use. Only fill this in to override the auto-determined value", type: "text", tooltip: false }, + { name: "contactEmail", label: "Admin email address displayed on the webpage footer (hidden if empty)", type: "text", tooltip: false }, + { name: "serverLogLevel", label: "Server level to be logged on the server to file", type: "select", selectOptions: serverlog, tooltip: false }, + ]; + + // // + // Health Check + // // + + // This variable holds information about the fields required when creating a new health check. + newhealthcheck = [ + { name: 'attack', label: 'Attack', type: 'select',requiredasterisk: true, selectOptions: [{ value: 0, label: 'Brute-Force' }], validators: [Validators.required] }, + { name: 'hashtypeId', label: 'Hashtype', type: 'select',requiredasterisk: true, selectOptions: [{ value: 0, label: 'MD5' },{ value: 3200, label: 'BCRYPT' }], validators: [Validators.required] }, + { name: 'crackerBinaryType', label: 'Binary', type: 'selectd', requiredasterisk: true, selectEndpoint$: SERV.ACCESS_PERMISSIONS_GROUPS, selectOptions$: [], fieldMapping: { id: 'crackerBinaryTypeId', name: 'typeName' },validators: [Validators.required]}, + { name: 'crackerBinaryId', label: 'Binary Version', type: 'selectd', requiredasterisk: true, selectEndpoint$: SERV.ACCESS_PERMISSIONS_GROUPS, selectOptions$: [], fieldMapping: { id: 'crackerBinaryId', name: 'version' },validators: [Validators.required]}, + ]; + + // // // // // // // // + // USER SECTION // + // // // // // // // // + + // // + // USERS + // // + + // This variable stores information about the user page. + newuserInfo = [ + { title: 'New User', customform: false, subtitle: false, submitok: 'New User created!', submitokredirect: 'users/all-users'}, + ]; + + // This variable edit information about the user page. + editInfo = [ + { title: 'Edit User', customform: false, subtitle: false, submitok: 'Saved!', submitokredirect: 'users/all-users'}, + ]; + + //This variable holds information about the fields required when creating a new user. + newuser = [ + { name: 'name', label: 'User Name', type: 'text', requiredasterisk: true, validators: [Validators.required] }, + { name: 'email', label: 'Email', type: 'email', requiredasterisk: true, validators: [Validators.required, Validators.email] }, + { name: 'globalPermissionGroupId', label: 'Global Permission Group', type: 'selectd', requiredasterisk: true, selectEndpoint$: SERV.ACCESS_PERMISSIONS_GROUPS, selectOptions$: [], fieldMapping: {id: 'id', name: 'name' }, validators: [Validators.required] }, + ]; + + //This variable is similar to newuser but is used for editing an existing user. + edituser = [ + { name: 'id', label: 'User ID', type: 'number', disabled: true }, + { name: 'name', label: 'User Name', type: 'text', disabled: true}, + { name: 'email', label: 'Email', type: 'email', disabled: true}, + { name: 'registered', label: 'Creation date', type: 'date', disabled: true}, + { name: 'lastLogin', label: 'Last login', type: 'date', disabled: true}, + { label: 'Update Settings',isTitle: true}, + { label: 'Member of access groups', type: 'date', disabled: true}, + { name: 'globalPermissionGroupId', label: 'Global Permission Group', type: 'selectd', requiredasterisk: true, selectEndpoint$: SERV.ACCESS_PERMISSIONS_GROUPS, selectOptions$: [],fieldMapping: {id: 'id', name: 'name' }, validators: [Validators.required] }, + { name: 'password', type: 'password' }, + { name: 'isValid', label: 'Valid', type: 'checkbox', requiredasterisk: false, tooltip: false, validators: false, defaultValue: false }, + ]; + + // // + // New Global Permission Group + // // + + // This variable stores information about the global permission group page. + newglobalpermissionsgpInfo = [ + { title: 'New Global Permission Group', customform: false, subtitle: false, submitok: 'New Global Permission Group created!', submitokredirect: '/users/global-permissions-groups'}, + ]; + + //This variable holds information about the fields required when creating a new global permission group. + newglobalpermissionsgp = [ + { name: 'name', label: 'Name', type: 'text', requiredasterisk: true, tooltip: false, validators: [Validators.required] } + ]; + + // // + // Access Groups + // // + + // This variable stores information about the access group page. + newaccessgroupsInfo = [ + { title: 'New Access Group', subtitle: false, submitok: 'New Access Group created!', submitokredirect: '/users/access-groups'}, + ]; + + // This variable contains information related to editing an access group. + editaccessgroupsInfo = [ + { title: 'Edit Access Group', subtitle: false, submitok: 'Access Group saved!', submitokredirect: '/users/access-groups', deltitle: 'Agent Groups', delsubmitok: 'Deleted Access Group', delsubmitokredirect: '/users/access-groups', delsubmitcancel:'Agent Group is safe!'}, + ]; + + // This variable contains information about the fields required when creating or editing an access group. + accessgroups = [ + { name: 'groupName', label: 'Name', type: 'text', requiredasterisk: true, tooltip: false, validators: [Validators.required] } + ]; + + // // // // // // // // // + // UI SETTINGS SECTION // + // // // // // // // // // + + uisettingsInfo = [ + { title: 'UI Settings', subtitle: false}, + ]; + + uisettings = [ + { name: "localtimefmt", label: "Set the time format", type: "select", selectOptions: dateFormats }, + { name: "autorefresh", label: "Dashboard Refresh Interval (seconds)", type: "text", tooltip: "Manage refresh interval in the show tasks view" }, + { name: "tooltip", label: "Manage Global level of tooltip details", type: "select", selectOptions: [ + { label: "Concise", value: 0 }, + { label: "Detailed", value: 1 }, + { label: "Very Detailed", value: 2 } + ]}, + ]; + + /** + * Retrieves form metadata based on the provided form name. + * @param formName - The name of the form for which metadata is requested. + * @returns An array of form metadata.editnotifInfo + */ + getFormMetadata(formName: string): any[] { + if (formName === 'editwordlist' || formName === 'editrule' || formName === 'editother') { + return this.editfile; + } else if (formName === 'uisettings') { + return this.uisettings; + } else if (formName === 'newcracker') { + return this.newcracker; + } else if (formName === 'newagentbinary' || formName === 'editagentbinary') { + return this.agentbinary; + } else if (formName === 'newcrackerversion') { + return this.newcrackerversion; + } else if (formName === 'editcrackerversion') { + return this.editcrackerversion; + } else if (formName === 'newpreprocessor' || formName === 'editpreprocessor') { + return this.preprocessor; + } else if (formName === 'newhashtype' ) { + return this.newhashtype; + } else if (formName === 'edithashtype') { + return this.edithashtype; + } else if (formName === 'newglobalpermissionsgp') { + return this.newglobalpermissionsgp; + } else if (formName === 'newaccessgroups' || formName === 'editaccessgroups') { + return this.accessgroups; + } else if (formName === 'serveragent') { + return this.serveragent; + } else if (formName === 'servertaskchunk') { + return this.servertaskchunk; + } else if (formName === 'serverhch') { + return this.serverhch; + } else if (formName === 'servernotif') { + return this.servernotif; + } else if (formName === 'servergs') { + return this.servergs; + } else if (formName === 'newuser') { + return this.newuser; + } else if (formName === 'editnotif') { + return this.editnotif; + } else { + return []; + } + } + + /** + * Retrieves info metadata based on the provided form name. + * @param formName - The name of the info metadata for which information is requested. + * @returns An array of info metadata. + */ + getInfoMetadata(formName: string): any[] { + if (formName === 'editwordlistInfo') { + return this.editwordlistInfo; + } else if (formName === 'editruleInfo') { + return this.editruleInfo; + } else if (formName === 'editotherInfo') { + return this.editotherInfo; + } else if (formName === 'uisettingsInfo') { + return this.uisettingsInfo; + } else if (formName === 'newcrackerInfo') { + return this.newcrackerInfo; + } else if (formName === 'newagentbinaryInfo') { + return this.newagentbinaryInfo; + } else if (formName === 'editagentbinaryInfo') { + return this.editagentbinaryInfo; + } else if (formName === 'newcrackerversionInfo') { + return this.newcrackerversionInfo; + } else if (formName === 'editcrackerversionInfo') { + return this.editcrackerversionInfo; + } else if (formName === 'newpreprocessorInfo') { + return this.newpreprocessorInfo; + } else if (formName === 'editpreprocessorInfo') { + return this.editpreprocessorInfo; + } else if (formName === 'newhashtypeInfo') { + return this.newhashtypeInfo; + } else if (formName === 'edithashtypeInfo') { + return this.edithashtypeInfo; + } else if (formName === 'newglobalpermissionsgpInfo') { + return this.newglobalpermissionsgpInfo; + } else if (formName === 'newaccessgroupsInfo') { + return this.newaccessgroupsInfo; + } else if (formName === 'editaccessgroupsInfo') { + return this.editaccessgroupsInfo; + } else if (formName === 'serveragentInfo') { + return this.serveragentInfo; + } else if (formName === 'servertaskchunkInfo') { + return this.servertaskchunkInfo; + } else if (formName === 'serverhchInfo') { + return this.serverhchInfo; + } else if (formName === 'servernotifInfo') { + return this.servernotifInfo; + } else if (formName === 'servergsInfo') { + return this.servergsInfo; + } else if (formName === 'newuserInfo') { + return this.newuserInfo; + } else if (formName === 'editnotifInfo') { + return this.editnotifInfo; + } else { + return []; + } + } + + /** + * Fetches select options for a form control from an API endpoint. + * + * @param apiEndpoint - The API endpoint to retrieve select options from. + * @returns An observable that emits an array of select options. + */ + fetchOptions(apiEndpoint: string): Observable { + return this.gs.getAll(apiEndpoint).pipe( + map((data: any) => { + // Adjust this based on your API response structure + return data.options.map((option: any) => ({ + label: option.label, + value: option.value, + })); + }), + ); + } + + // Custom validator to convert the input value to a number + numberValidator(control: FormControl) { + const value = control.value; + if (value === null || value === undefined) { + return null; + } + const parsedValue = Number(value); + if (isNaN(parsedValue)) { + return { invalidNumber: true }; + } + return null; + } + + +} + + diff --git a/src/app/core/_services/shared/alert.service.ts b/src/app/core/_services/shared/alert.service.ts index 44ee870d..a11f9ba8 100644 --- a/src/app/core/_services/shared/alert.service.ts +++ b/src/app/core/_services/shared/alert.service.ts @@ -33,7 +33,8 @@ export class AlertService { backdrop: false, toast: true, showConfirmButton: false, - timer: 2000 + timer: 2000, + timerProgressBar: true, }); } diff --git a/src/app/core/_services/unsubscribe.service.ts b/src/app/core/_services/unsubscribe.service.ts new file mode 100644 index 00000000..0d7d101f --- /dev/null +++ b/src/app/core/_services/unsubscribe.service.ts @@ -0,0 +1,32 @@ +import { Injectable } from '@angular/core'; +import { Subscription } from 'rxjs'; + +/** + * Service for managing and unsubscribing from subscriptions in Angular components. + */ +@Injectable({ + providedIn: 'root', +}) +export class UnsubscribeService { + private subscriptions: Subscription[] = []; + + /** + * Adds a subscription to the list of managed subscriptions. + * @param subscription - The subscription to be managed and unsubscribed later. + */ + add(subscription: Subscription): void { + this.subscriptions.push(subscription); + } + + /** + * Unsubscribes from all managed subscriptions and clears the subscription list. + */ + unsubscribeAll(): void { + this.subscriptions.forEach((subscription) => { + if (subscription && typeof subscription.unsubscribe === 'function') { + subscription.unsubscribe(); + } + }); + this.subscriptions.length = 0; // Clear the array + } +} diff --git a/src/app/home/home.component.html b/src/app/home/home.component.html index 84c140f4..c4e38b07 100644 --- a/src/app/home/home.component.html +++ b/src/app/home/home.component.html @@ -103,4 +103,4 @@

- \ No newline at end of file + diff --git a/src/app/shared/alert/swal/page-title.component.ts b/src/app/shared/alert/swal/page-title.component.ts deleted file mode 100644 index e69de29b..00000000 diff --git a/src/app/shared/buttons/button-submit.ts b/src/app/shared/buttons/button-submit.ts index 592b3a9c..714f3bc2 100644 --- a/src/app/shared/buttons/button-submit.ts +++ b/src/app/shared/buttons/button-submit.ts @@ -1,4 +1,4 @@ -import { Component, Input } from '@angular/core'; +import { Component, Input, ViewEncapsulation } from '@angular/core'; import { Router } from '@angular/router'; import { Location } from '@angular/common'; @@ -29,8 +29,11 @@ import { Location } from '@angular/common'; @Component({ selector: 'button-submit', template: ` - - ` + + `, + encapsulation: ViewEncapsulation.None, }) export class ButtonSubmitComponent { /** @@ -55,9 +58,9 @@ export class ButtonSubmitComponent { */ getCustomClass(): string { if (this.type === 'cancel' || this.type === 'delete') { - return "btn-danger shadow-sm btn-sm me-3"; + return 'mat-raised-button mat-warn mat-button-sm mat-button-shadow'; } else { - return "btn btn-gray-800"; // Default to normal style + return 'mat-raised-button mat-primary mat-button-sm mat-button-shadow'; } } diff --git a/src/app/shared/buttons/buttons.module.ts b/src/app/shared/buttons/buttons.module.ts index d933d4e4..10ac23a9 100644 --- a/src/app/shared/buttons/buttons.module.ts +++ b/src/app/shared/buttons/buttons.module.ts @@ -1,13 +1,21 @@ import { ButtonSubmitComponent } from './button-submit'; -import { GridButtonsComponent } from './grid-cancel'; import { CommonModule } from '@angular/common'; import { FormsModule } from '@angular/forms'; +import { GridButtonsComponent } from './grid-cancel'; +import { MatCardModule } from '@angular/material/card'; +import { MatDividerModule } from '@angular/material/divider'; +import { MatGridListModule } from '@angular/material/grid-list'; import { NgModule } from '@angular/core'; +import { MatButtonModule } from '@angular/material/button'; @NgModule({ imports: [ FormsModule, - CommonModule + CommonModule, + MatButtonModule, + MatGridListModule, + MatDividerModule, + MatCardModule ], exports: [ ButtonSubmitComponent, @@ -18,4 +26,4 @@ import { NgModule } from '@angular/core'; GridButtonsComponent ] }) -export class ButtonsModule { } +export class ButtonsModule {} diff --git a/src/app/shared/buttons/grid-cancel.ts b/src/app/shared/buttons/grid-cancel.ts index efde4c04..03c04c8b 100644 --- a/src/app/shared/buttons/grid-cancel.ts +++ b/src/app/shared/buttons/grid-cancel.ts @@ -20,12 +20,9 @@ import { Component } from '@angular/core'; @Component({ selector: 'grid-buttons', template: ` -
-
-
-
-
- `, + + + + ` }) export class GridButtonsComponent {} - diff --git a/src/app/shared/components.module.ts b/src/app/shared/components.module.ts index 370768ff..f6cf3ec9 100644 --- a/src/app/shared/components.module.ts +++ b/src/app/shared/components.module.ts @@ -8,33 +8,35 @@ import { ActiveSpinnerComponent } from './loading-spinner/loading-spinner-active import { PassStrenghtComponent } from './password/pass-strenght/pass-strenght.component'; import { ButtonTruncateTextComponent } from './table/button-truncate-text.component'; import { HexconvertorComponent } from "./utils/hexconvertor/hexconvertor.component"; +import { TimeoutDialogComponent } from './dialog/timeout/timeout-dialog.component'; import { PassMatchComponent } from './password/pass-match/pass-match.component'; import { CheatsheetComponent } from "./alert/cheatsheet/cheatsheet.component"; import { FilterTextboxModule } from "./filter-textbox/filter-textbox.module"; import { SwitchThemeModule } from "./switch-theme/switch-theme.module"; +import { MatProgressBarModule } from '@angular/material/progress-bar'; import { TimeoutComponent } from "./alert/timeout/timeout.component"; -import { PaginationModule } from "./pagination/pagination.module"; +import { HorizontalNavModule } from './navigation/navigation.module'; import { PageTitleModule } from "./page-headers/page-title.module"; +import { PaginationModule } from "./pagination/pagination.module"; +import { DynamicFormModule } from './form/dynamicform.module'; +import { MatDialogModule } from '@angular/material/dialog'; +import { MatButtonModule } from '@angular/material/button'; import { GridModule } from "./grid-containers/grid.module"; import { TableModule } from "./table/table-actions.module"; import { AlertComponent } from "./alert/alert.component"; import { ButtonsModule } from "./buttons/buttons.module"; import { LottiesModule } from './lottie/lottie.module'; +import { MatIconModule } from '@angular/material/icon'; import { GraphsModule } from "./graphs/graphs.module"; import { ColorPickerModule } from 'ngx-color-picker'; import { FormsModule } from "@angular/forms"; -import { MatIconModule } from '@angular/material/icon'; -import { MatDialogModule } from '@angular/material/dialog'; -import { MatButtonModule } from '@angular/material/button'; -import { TimeoutDialogComponent } from './dialog/timeout/timeout-dialog.component'; -import { MatProgressBarModule } from '@angular/material/progress-bar'; @NgModule({ declarations: [ ButtonTruncateTextComponent, - TimeoutDialogComponent, HashtypeDetectorComponent, LoadingSpinnerComponent, + TimeoutDialogComponent, ActiveSpinnerComponent, HexconvertorComponent, PassStrenghtComponent, @@ -44,20 +46,22 @@ import { MatProgressBarModule } from '@angular/material/progress-bar'; AlertComponent ], imports: [ + MatProgressBarModule, FilterTextboxModule, + HorizontalNavModule, + DynamicFormModule, SwitchThemeModule, ColorPickerModule, PaginationModule, PageTitleModule, + MatButtonModule, + MatDialogModule, ButtonsModule, LottiesModule, - GraphsModule, - CommonModule, MatIconModule, - MatButtonModule, - MatDialogModule, - MatProgressBarModule, MatIconModule, + GraphsModule, + CommonModule, FormsModule, TableModule, GridModule, @@ -65,16 +69,18 @@ import { MatProgressBarModule } from '@angular/material/progress-bar'; ], exports: [ ButtonTruncateTextComponent, - TimeoutDialogComponent, HashtypeDetectorComponent, LoadingSpinnerComponent, + TimeoutDialogComponent, ActiveSpinnerComponent, HexconvertorComponent, PassStrenghtComponent, FilterTextboxModule, + HorizontalNavModule, CheatsheetComponent, PassMatchComponent, SwitchThemeModule, + DynamicFormModule, ColorPickerModule, PaginationModule, TimeoutComponent, @@ -86,6 +92,6 @@ import { MatProgressBarModule } from '@angular/material/progress-bar'; CommonModule, TableModule, GridModule - ], + ] }) -export class ComponentsModule { } +export class ComponentsModule {} diff --git a/src/app/shared/directives.module.ts b/src/app/shared/directives.module.ts index 5d19e395..f046fd70 100644 --- a/src/app/shared/directives.module.ts +++ b/src/app/shared/directives.module.ts @@ -5,7 +5,6 @@ import { StartsWithActiveDirective } from "../core/_directives/nav-startswith.di import { HoverDashedDirective } from "../core/_directives/hover-dashed.directive"; import { FileSelectDirective } from "../core/_directives/file-select.directive"; import { CopyButtonDirective } from "../core/_directives/copy-button.directive"; -import { SelectizeDirective } from "../core/_directives/selectize.directive"; import { UnderlineDirective } from "../core/_directives/underline.directive"; import { FileDropDirective } from "../core/_directives/file-drop.directive"; @@ -15,7 +14,6 @@ import { FileDropDirective } from "../core/_directives/file-drop.directive"; HoverDashedDirective, FileSelectDirective, CopyButtonDirective, - SelectizeDirective, UnderlineDirective, FileDropDirective ], @@ -25,7 +23,6 @@ import { FileDropDirective } from "../core/_directives/file-drop.directive"; HoverDashedDirective, FileSelectDirective, CopyButtonDirective, - SelectizeDirective, UnderlineDirective, FileDropDirective ] diff --git a/src/app/shared/form/dynamicform.component.ts b/src/app/shared/form/dynamicform.component.ts new file mode 100644 index 00000000..bda867d3 --- /dev/null +++ b/src/app/shared/form/dynamicform.component.ts @@ -0,0 +1,346 @@ +import { FormBuilder, FormGroup, FormControl, ValidatorFn, Validators } from '@angular/forms'; +import { Component, Input, OnInit, Output, EventEmitter, AfterViewInit, OnDestroy } from '@angular/core'; +import { faInfoCircle } from '@fortawesome/free-solid-svg-icons'; +import { Router } from '@angular/router'; +import { Observable, Subject, Subscription, combineLatest, forkJoin, map, switchMap, takeUntil } from 'rxjs'; +import { MetadataService } from 'src/app/core/_services/metadata.service'; +import { GlobalService } from 'src/app/core/_services/main.service'; +import { ChangeDetectorRef } from '@angular/core'; + +/** + * This component renders a dynamic form based on the provided form metadata. + */ +@Component({ + selector: 'app-dynamic-form', + template: ` + + +
+ +
+
+ +
{{ field.label }}
+
+ +

+ + {{ field.label }} + + + + + + + + + + + + + + + + + + + + {{ option.label }} + + + + + Please Select an Option + {{ option.name }} + + + + {{ field.label }} + + + +

+
+
+
+ + + Delete + + +
+
+
+ `, +}) +export class DynamicFormComponent implements OnInit, AfterViewInit, OnDestroy { + /** + * FontAwesome icon for providing additional information in form fields. + */ + faInfoCircle = faInfoCircle; + + /** + * The subtitle to display. + * @type {string} + */ + @Input() subtitle: string; + + /** + * An array of form field metadata that describes the form structure. + */ + @Input() formMetadata: any[] = []; + + /** + * Additional CSS class for labels. + */ + @Input() labelclass?: any; + + /** + * Initial values for form fields (optional). If not provided, an empty object is used as the default. + */ + @Input() formValues: any = {}; + + /** + * The Angular FormGroup that represents the dynamic form. + */ + @Input() form: FormGroup; + + /** + * Indicates whether the form is in "create" mode or "update" mode. + * When true, it's in "create" mode, and when false, it's in "update" mode. + */ + @Input() isCreateMode: boolean; + + /** + * A boolean input property that controls the visibility of the "Delete" button. + * Set it to `true` to display the "Delete" button, and `false` to hide it. + * By default, the "Delete" button is displayed (set to `true`). + */ + @Input() showDeleteButton: boolean = true; + + /** + * The text to display on the "Create" or "Update" button. + */ + @Input() buttonText: string; + + /** + * Event emitter for submitting the form. Emits the form values when the form is submitted. + * Parent components can subscribe to this event to handle form submissions. + */ + @Output() formSubmit: EventEmitter = new EventEmitter(); + + /** + * Event emitter for handling the delete action. Emits when the "Delete" action is triggered. + * Parent components can subscribe to this event to perform delete operations. + */ + @Output() deleteAction: EventEmitter = new EventEmitter(); + + @Output() selectTypeChange: EventEmitter = new EventEmitter(); + + /** + * A Subject used for managing the lifecycle and unsubscribing from observables when the component is destroyed. + * The `destroy$` subject is used to signal the component's destruction. + */ + private destroy$: Subject = new Subject(); + + /** + * Constructor for the DynamicFormComponent. + * @param fb - The Angular FormBuilder for creating form controls and groups. + * @param gs - The GlobalService for handling global operations and API requests. + * @param cd - The Angular ChangeDetectorRef for triggering change detection manually. + */ + constructor(private fb: FormBuilder, private gs: GlobalService, private cd: ChangeDetectorRef) {} + + /** + * Initializes the dynamic form by creating form controls and setting their initial values. + * This method is called when the dynamic form component is initialized. + */ + ngOnInit() { + // Initialize an object to store the configuration of form controls. + const controlsConfig = {}; + + // Iterate through the form metadata to create and configure form controls. + for (const field of this.formMetadata) { + // Exclude fields marked as titles from form control creation. + if (!field.isTitle) { + // Get the name of the field. + const fieldName = field.name; + + // Determine the validators for the field, defaulting to an empty array if none are provided. + const validators: ValidatorFn[] = field.validators ? field.validators : []; + + // Initialize the initial value for the form control. + let initialValue; + + // Set the initial value for the form control based on the field's type. + if (field.type === 'checkbox') { + // For checkboxes, use the value directly from formValues. + initialValue = this.formValues[fieldName]; + } if (!this.isCreateMode) { + // For other field types, use formValues[fieldName] or 0 as a default value if not provided. + initialValue = fieldName in this.formValues ? this.formValues[fieldName] : 0; + } + + // In 'create' mode, override the initial value if a default value is specified in the field's metadata. + if (this.isCreateMode && field.defaultValue !== undefined) { + initialValue = field.defaultValue; + } + + // Create a form control with the initial value and any specified validators. + if (!this.isCreateMode && field.disabled) { + // If in 'update' mode and the field is disabled, create a disabled form control. + controlsConfig[fieldName] = { value: initialValue, disabled: true }; + } else { + // Create a form control with the initial value and optional validators. + controlsConfig[fieldName] = new FormControl(initialValue, validators); + } + } + } + + // Create the Angular FormGroup with the configured controls. + this.form = this.fb.group(controlsConfig); + } + + /** + * A subscription to handle dynamic select options data retrieval. + * This subscription is used to fetch and update select field options with dynamic data. + */ + private selectOptionsSubscription: Subscription; + + /** + * Indicates whether the dynamic select options are currently being loaded. + * When true, it represents that options are being fetched; when false, loading is complete. + */ + isLoadingSelect: boolean = true; + + /** + * Angular lifecycle hook: ngAfterViewInit + * Performs initialization and logic for select fields with dynamic options. + */ + ngAfterViewInit() { + // Check if there are any "select" type fields with "selectOptions$" + const selectFields = this.formMetadata.filter( + (field) => field.type === 'selectd' && field.selectOptions$ + ); + + if (selectFields.length > 0) { + // Handle logic for select fields with selectOptions$ after the view is initialized + selectFields.forEach((field) => { + // Fetch the select options dynamically here + this.selectOptionsSubscription = this.gs.getAll(field.selectEndpoint$,{'maxResults': 5000}) + .pipe(takeUntil(this.destroy$)) + .subscribe((options) => { + + // Sometimes fields need to be mapped + const transformedOptions = this.transformSelectOptions(options.values, field); + + // Assign the fetched options to the field's selectOptions$ + field.selectOptions$ = transformedOptions; + + // Update isLoadingSelect to indicate that loading is complete + this.isLoadingSelect = false; + + // Optionally, update the form control value if needed + const control = this.form.get(field.name); + + // Check if there are options available + if (control && options.values && options.values.length > 0 && !this.isCreateMode) { + // Ensure that options.values[0] and options.values[0].value exist before setting the value + const initialSelectedValue = options.values[0]?.value; + + if (initialSelectedValue !== undefined) { + control.setValue(initialSelectedValue); + } + } + + // Trigger change detection to prevent ExpressionChangedAfterItHasBeenCheckedError + this.cd.detectChanges(); + }); + }); + } + } + + /** + * Transforms API response options based on a field mapping configuration. + * + * @param apiOptions - The options received from an API response. + * @param field - The field configuration that contains the mapping between form fields and API fields. + * + * @returns An array of transformed select options to be used in the form. + */ + transformSelectOptions(apiOptions: any[], field: any): any[] { + return apiOptions.map((apiOption: any) => { + const transformedOption: any = {}; + + for (const formField of Object.keys(field.fieldMapping)) { + const apiField = field.fieldMapping[formField]; + + if (Object.prototype.hasOwnProperty.call(apiOption, apiField)) { + transformedOption[formField] = apiOption[apiField]; + } else { + // Handle the case where the API field doesn't exist in the response + transformedOption[formField] = null; // or set a default value + } + } + + return transformedOption; + }); + } + + /** + * Checks if the form is valid. + * @returns {boolean} True if the form is valid, false otherwise. + */ + formIsValid(): boolean { + return this.form.valid; + } + + /** + * Handles the form submission. + * Emits the form values to the parent component if the form is valid. + */ + onSubmit() { + if (this.form.valid) { + // Emit the form values to the parent component + this.formSubmit.emit(this.form.value); + } + } + + /** + * Handles the delete action. + * Emits the delete action to the parent component when the "Delete" button is clicked. + */ + onDelete(){ + this.deleteAction.emit(); + } + + /** + * Handles the onchange action. + * Emits the change action to the parent component when the select option is selected. + */ + onChange(value: any) { + this.selectTypeChange.emit(value); + } + + /** + * Angular lifecycle hook: ngOnDestroy + * Unsubscribes from all relevant subscriptions and cleans up resources + */ + ngOnDestroy() { + // Unsubscribe from the selectOptionsSubscription + // this.selectOptionsSubscription.unsubscribe(); + + // Complete and close the destroy$ subject to prevent memory leaks + this.destroy$.next(); + this.destroy$.complete(); + } + +} diff --git a/src/app/shared/form/dynamicform.module.ts b/src/app/shared/form/dynamicform.module.ts new file mode 100644 index 00000000..50249544 --- /dev/null +++ b/src/app/shared/form/dynamicform.module.ts @@ -0,0 +1,57 @@ +import { ButtonsModule } from '../buttons/buttons.module'; +import { CommonModule } from "@angular/common"; +import { DynamicFormComponent } from "./dynamicform.component"; +import { NgModule } from "@angular/core"; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; +import { FormComponent } from './form.component'; +import { FormConfigComponent } from './formconfig.component'; +import { FormUIsettingsComponent } from './formuisettings.component'; +import { GridModule } from '../grid-containers/grid.module'; +import { HorizontalNavModule } from '../navigation/navigation.module'; +import { MatDividerModule } from '@angular/material/divider'; +import { MatTooltipModule } from '@angular/material/tooltip'; +import { MatButtonModule } from "@angular/material/button"; +import { MAT_FORM_FIELD_DEFAULT_OPTIONS, MatFormFieldModule } from "@angular/material/form-field"; +import { MatIconModule } from "@angular/material/icon"; +import { MatInputModule } from "@angular/material/input"; +import { MatSelectModule } from "@angular/material/select"; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { PageTitleModule } from '../page-headers/page-title.module'; + +@NgModule({ + declarations: [ + FormUIsettingsComponent, + DynamicFormComponent, + FormConfigComponent, + FormComponent + ], + imports: [ + ReactiveFormsModule, + HorizontalNavModule, + FontAwesomeModule, + PageTitleModule, + ButtonsModule, + FormsModule, + GridModule, + NgbModule, + CommonModule, + MatTooltipModule, + MatFormFieldModule, + MatSelectModule, + MatButtonModule, + MatInputModule, + MatIconModule, + MatDividerModule + ], + exports: [ + FormUIsettingsComponent, + DynamicFormComponent, + FormConfigComponent, + FormComponent + ], + providers: [ + {provide: MAT_FORM_FIELD_DEFAULT_OPTIONS, useValue: {appearance: 'outline'}} + ] +}) +export class DynamicFormModule {} diff --git a/src/app/shared/form/dynamicformlayout.component.ts b/src/app/shared/form/dynamicformlayout.component.ts new file mode 100644 index 00000000..f7c89793 --- /dev/null +++ b/src/app/shared/form/dynamicformlayout.component.ts @@ -0,0 +1,369 @@ +import { FormBuilder, FormGroup, FormControl, ValidatorFn, Validators } from '@angular/forms'; +import { Component, Input, OnInit, Output, EventEmitter, AfterViewInit, OnDestroy } from '@angular/core'; +import { faInfoCircle } from '@fortawesome/free-solid-svg-icons'; +import { Router } from '@angular/router'; +import { Observable, Subject, Subscription, combineLatest, forkJoin, map, switchMap, takeUntil } from 'rxjs'; +import { MetadataService } from 'src/app/core/_services/metadata.service'; +import { GlobalService } from 'src/app/core/_services/main.service'; +import { ChangeDetectorRef } from '@angular/core'; + +/** + * This component renders a dynamic form based on the provided form metadata. + */ +@Component({ + selector: 'app-dynamic-form', + template: ` + + +
+ +
+
+ +
+
+
+ +
{{ field.label }}
+
+ +
+
+ + + +
+ + + +
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
IDStartLengthCheckpointProgressTaskAgentDispatch timeLast activityTime spentStateCracked
{{ c.chunkId }}{{ c.skip }}{{ c.length }}{{ c.checkpoint }} ({{ (c.checkpoint-c.skip)/c.length | percent:'1.2-2' }}){{ c.progress/100 }} %N/A - {{ c.taskName | shortenString:15 }} - - {{ c.agentName | shortenString:15 }} - {{ c.dispatchTime | uiDate }}(No acitivity){{ c.solveTime | uiDate }}{{ (c.solveTime - c.dispatchTime) | sectotime }} - - {{ c.state | staticArray:'states' }} - - - {{ c.cracked }} -
+ + - diff --git a/src/app/tasks/chunks/chunks.component.ts b/src/app/tasks/chunks/chunks.component.ts index f1ab8233..863e4eba 100644 --- a/src/app/tasks/chunks/chunks.component.ts +++ b/src/app/tasks/chunks/chunks.component.ts @@ -1,188 +1,14 @@ -import { faPlus, faEye } from '@fortawesome/free-solid-svg-icons'; -import { Component, OnInit, ViewChild } from '@angular/core'; -import { ActivatedRoute, Params } from '@angular/router'; -import { DataTableDirective } from 'angular-datatables'; -import { Subject } from 'rxjs'; +import { ChangeDetectionStrategy, Component } from '@angular/core'; -import { UIConfigService } from 'src/app/core/_services/shared/storage.service'; -import { GlobalService } from 'src/app/core/_services/main.service'; -import { PageTitle } from 'src/app/core/_decorators/autotitle'; -import { environment } from 'src/environments/environment'; -import { SERV } from '../../core/_services/main.config'; +import { AutoTitleService } from 'src/app/core/_services/shared/autotitle.service'; @Component({ selector: 'app-chunks', - templateUrl: './chunks.component.html' + templateUrl: './chunks.component.html', + changeDetection: ChangeDetectionStrategy.OnPush }) -@PageTitle(['Show Chunks']) -export class ChunksComponent implements OnInit { - - editedChunkIndex: number; - - faPlus=faPlus; - faEye=faEye; - - constructor( - private uiService: UIConfigService, - private route: ActivatedRoute, - private gs: GlobalService - ) { } - - private maxResults = environment.config.prodApiMaxResults; - - @ViewChild(DataTableDirective, {static: false}) - dtElement: DataTableDirective; - - dtTrigger: Subject = new Subject(); - dtOptions: any = {}; - - public chunks: {chunkId: number,taskId: number,format: string,skip: number,length: number,agentId: number,dispatchTime: number,solveTime: number,checkpoint: number,progress: number,state: number,cracked: number,speed: number, agentName: string, taskName: string, isEdit: false}[] = []; - - ngOnInit(): void { - - this.chunksInit(); - - } - - // Chunk View - chunkview: number; - chunkresults: Object; - - chunksInit(){ - let paramchunk = {}; - - this.route.data.subscribe(data => { - switch (data['kind']) { - - case 'chunks': - this.chunkview = 0; - this.chunkresults = this.maxResults; - paramchunk = {'maxResults': this.chunkresults }; - break; - - case 'chunks-view': - this.chunkview = 1; - this.chunkresults = this.maxResults; - this.route.params - .subscribe( - (params: Params) => { - this.editedChunkIndex = +params['id']; - } - ); - paramchunk = {'maxResults': this.chunkresults, 'filter': 'chunkId='+this.editedChunkIndex+''}; - break; - - case 'chunks-cAll': - this.chunkview = 2; - this.chunkresults = 10000; - paramchunk = {'maxResults': this.chunkresults }; - break; - - } - }); - - const params = {'maxResults': this.chunkresults}; - this.gs.getAll(SERV.CHUNKS,paramchunk).subscribe((chunks: any) => { - this.gs.getAll(SERV.TASKS,params).subscribe((tasks: any) => { - this.gs.getAll(SERV.AGENTS,params).subscribe((agents: any) => { - this.chunks = chunks.values.map(mainObject => { - const matchAObject = agents.values.find(element => element.agentId === mainObject.agentId) - const matchTObject = tasks.values.find(element => element.taskId === mainObject.taskId) - return { ...mainObject, ...matchAObject, ...matchTObject } - }) - this.dtTrigger.next(void 0); - }); - }); - }); - - const self = this; - this.dtOptions = { - dom: 'Bfrtip', - scrollX: true, - pageLength: 25, - lengthMenu: [ - [10, 25, 50, 100, 250, -1], - [10, 25, 50, 100, 250, 'All'] - ], - select: true, - processing: true, - deferRender: true, - destroy:true, - buttons: { - dom: { - button: { - className: 'dt-button buttons-collection btn btn-sm-dt btn-outline-gray-600-dt' - } - }, - buttons: [ - { - text: '↻', - autoClose: true, - action: function (e, dt, node, config) { - self.onRefresh(); - } - }, - { - extend: 'collection', - text: 'Export', - buttons: [ - { - extend: 'excelHtml5', - }, - { - extend: 'print', - customize: function ( win ) { - $(win.document.body) - .css( 'font-size', '10pt' ) - $(win.document.body).find( 'table' ) - .addClass( 'compact' ) - .css( 'font-size', 'inherit' ); - } - }, - { - extend: 'csvHtml5', - exportOptions: {modifier: {selected: true}}, - select: true, - customize: function (dt, csv) { - let data = ""; - for (let i = 0; i < dt.length; i++) { - data = "Chunks\n\n"+ dt; - } - return data; - } - }, - { - extend: 'copy', - } - ] - }, - { - extend: 'colvis', - text: 'Column View', - columns: [ 1,2,3,4,5,6,7,8,9,10,11 ], - }, - { - extend: "pageLength", - className: "btn-sm" - }, - ], - } - }; +export class ChunksComponent { + constructor(private titleService: AutoTitleService) { + this.titleService.set(['Show Chunks']); } - - onRefresh(){ - this.rerender(); - this.ngOnInit(); - } - - rerender(): void { - this.dtElement.dtInstance.then((dtInstance: DataTables.Api) => { - dtInstance.destroy(); - setTimeout(() => { - this.dtTrigger['new'].next(); - }); - }); - } - - } From fd3697b5c65eeee6a5c2d1d0638a393d46e238dc Mon Sep 17 00:00:00 2001 From: itbf9 Date: Fri, 10 Nov 2023 10:58:23 +0100 Subject: [PATCH 210/419] If no table headers are selected in localstorage or declared as default, display all --- .../tables/ht-table/ht-table.component.ts | 91 ++++++++++++------- 1 file changed, 59 insertions(+), 32 deletions(-) diff --git a/src/app/core/_components/tables/ht-table/ht-table.component.ts b/src/app/core/_components/tables/ht-table/ht-table.component.ts index bd08daaf..0920f768 100644 --- a/src/app/core/_components/tables/ht-table/ht-table.component.ts +++ b/src/app/core/_components/tables/ht-table/ht-table.component.ts @@ -1,16 +1,27 @@ /* eslint-disable @angular-eslint/component-selector */ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { ChangeDetectionStrategy, AfterViewInit, Component, EventEmitter, Input, OnInit, Output, ViewChild, ChangeDetectorRef } from '@angular/core'; -import { MatSort } from '@angular/material/sort'; -import { MatPaginator, PageEvent } from '@angular/material/paginator'; +import { + AfterViewInit, + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + EventEmitter, + Input, + OnInit, + Output, + ViewChild +} from '@angular/core'; import { DataType, HTTableColumn } from './ht-table.models'; -import { MatDialog } from '@angular/material/dialog'; -import { ColumnSelectionDialogComponent } from '../column-selection-dialog/column-selection-dialog.component'; -import { UISettingsUtilityClass } from 'src/app/shared/utils/config'; +import { MatPaginator, PageEvent } from '@angular/material/paginator'; + import { ActionMenuEvent } from '../../menus/action-menu/action-menu.model'; +import { BaseDataSource } from 'src/app/core/_datasources/base.datasource'; +import { ColumnSelectionDialogComponent } from '../column-selection-dialog/column-selection-dialog.component'; import { LocalStorageService } from 'src/app/core/_services/storage/local-storage.service'; +import { MatDialog } from '@angular/material/dialog'; +import { MatSort } from '@angular/material/sort'; import { UIConfig } from 'src/app/core/_models/config-ui.model'; -import { BaseDataSource } from 'src/app/core/_datasources/base.datasource'; +import { UISettingsUtilityClass } from 'src/app/shared/utils/config'; /** * The `HTTableComponent` is a custom table component that allows you to display tabular data with @@ -59,10 +70,9 @@ import { BaseDataSource } from 'src/app/core/_datasources/base.datasource'; @Component({ selector: 'ht-table', templateUrl: './ht-table.component.html', - changeDetection: ChangeDetectionStrategy.OnPush, + changeDetection: ChangeDetectionStrategy.OnPush }) export class HTTableComponent implements OnInit, AfterViewInit { - /** The list of column names to be displayed in the table. */ displayedColumns: string[]; @@ -76,13 +86,13 @@ export class HTTableComponent implements OnInit, AfterViewInit { @ViewChild(MatSort, { static: true }) matSort: MatSort; /** Name of the table, used when storing user customizations */ - @Input() name: string + @Input() name: string; /** Data type displayed in the table, used to load relevant context menus */ - @Input() dataType: DataType + @Input() dataType: DataType; /** Function used to filter table data. */ - @Input() filterFn: (item: any, filterValue: string) => boolean + @Input() filterFn: (item: any, filterValue: string) => boolean; /** The data source for the table. */ @Input() dataSource: BaseDataSource; @@ -112,24 +122,37 @@ export class HTTableComponent implements OnInit, AfterViewInit { @Input() defaultPageSize = this.paginationSizes[1]; /** Event emitter for when the user triggers a row action */ - @Output() rowActionClicked: EventEmitter> = new EventEmitter>(); + @Output() rowActionClicked: EventEmitter> = + new EventEmitter>(); /** Event emitter for when the user triggers a bulk action */ - @Output() bulkActionClicked: EventEmitter> = new EventEmitter>(); + @Output() bulkActionClicked: EventEmitter> = + new EventEmitter>(); /** Event emitter for when the user triggers an export action */ - @Output() exportActionClicked: EventEmitter> = new EventEmitter>(); + @Output() exportActionClicked: EventEmitter> = + new EventEmitter>(); /** Fetches user customizations */ - private uiSettings: UISettingsUtilityClass + private uiSettings: UISettingsUtilityClass; - constructor(public dialog: MatDialog, private cd: ChangeDetectorRef, private storage: LocalStorageService) { } + constructor( + public dialog: MatDialog, + private cd: ChangeDetectorRef, + private storage: LocalStorageService + ) {} ngOnInit(): void { - this.uiSettings = new UISettingsUtilityClass(this.storage) - this.columnNames = this.tableColumns.map((tableColumn: HTTableColumn) => tableColumn.name); - const displayedColumns = this.uiSettings.getTableSettings(this.name) - this.setDisplayedColumns(displayedColumns) + this.uiSettings = new UISettingsUtilityClass(this.storage); + this.columnNames = this.tableColumns.map( + (tableColumn: HTTableColumn) => tableColumn.name + ); + const displayedColumns = this.uiSettings.getTableSettings(this.name); + if (displayedColumns) { + this.setDisplayedColumns(displayedColumns); + } else { + this.setDisplayedColumns(this.columnNames); + } } ngAfterViewInit(): void { @@ -156,23 +179,23 @@ export class HTTableComponent implements OnInit, AfterViewInit { dialogRef.afterClosed().subscribe((selectedColumns: string[]) => { if (selectedColumns) { - this.setDisplayedColumns(selectedColumns) - this.uiSettings.updateTableSettings(this.name, selectedColumns) - this.cd.detectChanges() + this.setDisplayedColumns(selectedColumns); + this.uiSettings.updateTableSettings(this.name, selectedColumns); + this.cd.detectChanges(); } }); } rowAction(event: ActionMenuEvent): void { - this.rowActionClicked.emit(event) + this.rowActionClicked.emit(event); } bulkAction(event: ActionMenuEvent): void { - this.bulkActionClicked.emit(event) + this.bulkActionClicked.emit(event); } exportAction(event: ActionMenuEvent): void { - this.exportActionClicked.emit(event) + this.exportActionClicked.emit(event); } /** @@ -199,7 +222,7 @@ export class HTTableComponent implements OnInit, AfterViewInit { */ applyFilter() { if (this.filterFn) { - this.dataSource.filterData(this.filterFn) + this.dataSource.filterData(this.filterFn); } } @@ -230,7 +253,7 @@ export class HTTableComponent implements OnInit, AfterViewInit { * Checks if there are selected rows. */ hasSelected(): boolean { - return this.dataSource.hasSelected() + return this.dataSource.hasSelected(); } /** @@ -255,7 +278,7 @@ export class HTTableComponent implements OnInit, AfterViewInit { * Reloads the data in the table. */ reload(): void { - this.dataSource.reload() + this.dataSource.reload(); } /** @@ -264,7 +287,11 @@ export class HTTableComponent implements OnInit, AfterViewInit { * @param event - The `PageEvent` object containing information about the new page configuration. */ onPageChange(event: PageEvent): void { - this.dataSource.setPaginationConfig(event.pageSize, event.pageIndex, this.dataSource.totalItems); + this.dataSource.setPaginationConfig( + event.pageSize, + event.pageIndex, + this.dataSource.totalItems + ); this.dataSource.reload(); } -} \ No newline at end of file +} From 4ff2432714e42c23b89fc9a4be930143a30ee9ca Mon Sep 17 00:00:00 2001 From: itbf9 Date: Fri, 10 Nov 2023 10:59:24 +0100 Subject: [PATCH 211/419] Ignore no-explicit-any warning --- src/app/core/_components/tables/ht-table/ht-table.models.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/app/core/_components/tables/ht-table/ht-table.models.ts b/src/app/core/_components/tables/ht-table/ht-table.models.ts index 39d362fc..d22ede40 100644 --- a/src/app/core/_components/tables/ht-table/ht-table.models.ts +++ b/src/app/core/_components/tables/ht-table/ht-table.models.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import { SafeHtml } from '@angular/platform-browser'; export type DataType = 'agents' | 'tasks' | 'chunks'; From da978686ce2a18943d3bca0e61346e431d7543a9 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Fri, 10 Nov 2023 10:59:37 +0100 Subject: [PATCH 212/419] Add comment --- src/app/core/_datasources/chunks.datasource.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/core/_datasources/chunks.datasource.ts b/src/app/core/_datasources/chunks.datasource.ts index 892dc8f7..603a215f 100644 --- a/src/app/core/_datasources/chunks.datasource.ts +++ b/src/app/core/_datasources/chunks.datasource.ts @@ -25,8 +25,8 @@ export class ChunksDataSource extends BaseDataSource { const assignedChunks: Chunk[] = c.values; assignedChunks.map((chunk: Chunk) => { - console.log(chunk._id); chunk.agent = a.values.find((e: Agent) => e._id === chunk.agentId); + // Flatten row so that we can access agent name and task name by key when rendering the table. if (chunk.agent) { chunk.agentName = chunk.agent.agentName; } From 4610b5bf34a54ca06e7a63a41c71f5fe17ed785b Mon Sep 17 00:00:00 2001 From: itbf9 Date: Fri, 10 Nov 2023 13:26:12 +0100 Subject: [PATCH 213/419] merge --- .../_components/core-components.module.ts | 7 +- .../tables/ht-table/ht-table.models.ts | 2 +- src/app/core/_datasources/base.datasource.ts | 19 ++++- src/app/core/_models/config-ui.model.ts | 9 +++ src/app/core/_models/hashlist.model.ts | 74 +++++++++---------- 5 files changed, 69 insertions(+), 42 deletions(-) diff --git a/src/app/core/_components/core-components.module.ts b/src/app/core/_components/core-components.module.ts index d02d6db2..79ecc751 100644 --- a/src/app/core/_components/core-components.module.ts +++ b/src/app/core/_components/core-components.module.ts @@ -14,6 +14,7 @@ import { ColumnSelectionDialogComponent } from './tables/column-selection-dialog import { CommonModule } from '@angular/common'; import { ExportMenuComponent } from './menus/export-menu/export-menu.component'; import { HTTableComponent } from './tables/ht-table/ht-table.component'; +import { HashlistsTableComponent } from './tables/hashlists-table/hashlists-table.component'; import { MatButtonModule } from '@angular/material/button'; import { MatCheckboxModule } from '@angular/material/checkbox'; import { MatDialogModule } from '@angular/material/dialog'; @@ -46,7 +47,8 @@ import { TableDialogComponent } from './tables/table-dialog/table-dialog.compone ExportMenuComponent, ColumnSelectionDialogComponent, AgentsTableComponent, - ChunksTableComponent + ChunksTableComponent, + HashlistsTableComponent ], imports: [ ReactiveFormsModule, @@ -81,7 +83,8 @@ import { TableDialogComponent } from './tables/table-dialog/table-dialog.compone BulkActionMenuComponent, ExportMenuComponent, AgentsTableComponent, - ChunksTableComponent + ChunksTableComponent, + HashlistsTableComponent ], providers: [ { provide: MAT_SNACK_BAR_DEFAULT_OPTIONS, useValue: { duration: 2500 } } diff --git a/src/app/core/_components/tables/ht-table/ht-table.models.ts b/src/app/core/_components/tables/ht-table/ht-table.models.ts index d22ede40..641650fe 100644 --- a/src/app/core/_components/tables/ht-table/ht-table.models.ts +++ b/src/app/core/_components/tables/ht-table/ht-table.models.ts @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { SafeHtml } from '@angular/platform-browser'; -export type DataType = 'agents' | 'tasks' | 'chunks'; +export type DataType = 'agents' | 'hashlists' | 'chunks'; export interface HTTableIcon { name: string; diff --git a/src/app/core/_datasources/base.datasource.ts b/src/app/core/_datasources/base.datasource.ts index 9333c930..c80ff4c6 100644 --- a/src/app/core/_datasources/base.datasource.ts +++ b/src/app/core/_datasources/base.datasource.ts @@ -1,4 +1,4 @@ -import { BehaviorSubject, Observable } from 'rxjs'; +import { BehaviorSubject, Observable, Subscription } from 'rxjs'; import { CollectionViewer, DataSource } from '@angular/cdk/collections'; import { GlobalService } from '../_services/main.service'; @@ -7,6 +7,7 @@ import { MatSort } from '@angular/material/sort'; import { MatTableDataSourcePaginator } from '@angular/material/table'; import { SelectionModel } from '@angular/cdk/collections'; import { UIConfigService } from '../_services/shared/storage.service'; +import { environment } from './../../../environments/environment'; /** * BaseDataSource is an abstract class for implementing data sources @@ -30,6 +31,11 @@ export abstract class BaseDataSource< */ private originalData: T[] = []; + /** + * Array of subscriptions that will be unsubscribed on disconnect. + */ + protected subscriptions: Subscription[] = []; + /** * BehaviorSubject to track the loading state. */ @@ -45,6 +51,11 @@ export abstract class BaseDataSource< */ protected columns: HTTableColumn[] = []; + /** + * Max rows in API response + */ + protected maxResults = environment.config.prodApiMaxResults; + /** * Selection model for row selection in the table. */ @@ -87,7 +98,7 @@ export abstract class BaseDataSource< } /** - * Disconnect the data source from a collection viewer. + * Disconnect the data source from a collection viewer and unsubscribe. * * @param _collectionViewer - The collection viewer to disconnect. */ @@ -95,6 +106,10 @@ export abstract class BaseDataSource< disconnect(_collectionViewer: CollectionViewer): void { this.dataSubject.complete(); this.loadingSubject.complete(); + + for (const sub of this.subscriptions) { + sub.unsubscribe(); + } } /** diff --git a/src/app/core/_models/config-ui.model.ts b/src/app/core/_models/config-ui.model.ts index ec1195a1..eddf8a1c 100644 --- a/src/app/core/_models/config-ui.model.ts +++ b/src/app/core/_models/config-ui.model.ts @@ -1,5 +1,6 @@ import { AgentsTableColumnLabel } from '../_components/tables/agents-table/agents-table.constants'; import { ChunksTableColumnLabel } from '../_components/tables/chunks-table/chunks-table.constants'; +import { HashlistsTableColumnLabel } from '../_components/tables/hashlists-table/hashlists-table.constants'; export type Layout = 'full' | 'fixed'; export type Theme = 'light' | 'dark'; @@ -42,6 +43,14 @@ export const uiConfigDefault: UIConfig = { ChunksTableColumnLabel.TIME_SPENT, ChunksTableColumnLabel.STATE, ChunksTableColumnLabel.CRACKED + ], + hashlistsTable: [ + HashlistsTableColumnLabel.ID, + HashlistsTableColumnLabel.NAME, + HashlistsTableColumnLabel.HASHTYPE, + HashlistsTableColumnLabel.FORMAT, + HashlistsTableColumnLabel.CRACKED, + HashlistsTableColumnLabel.PRE_CRACKED ] }, refreshPage: false, diff --git a/src/app/core/_models/hashlist.model.ts b/src/app/core/_models/hashlist.model.ts index be163e7d..62d77e02 100644 --- a/src/app/core/_models/hashlist.model.ts +++ b/src/app/core/_models/hashlist.model.ts @@ -1,23 +1,23 @@ export interface BaseHashlist { - accessGroupId: number - brainFeatures: string - format: string - name: string - hashTypeId: number - isHexSalt: boolean - isSecret: boolean - isSalted: boolean - separator: string - useBrain: boolean - hashCount: number - cracked: number - notes: string - isArchived: boolean - sourceType: string - sourceData: string + accessGroupId: number; + brainFeatures: string; + format: string; + name: string; + hashTypeId: number; + isHexSalt: boolean; + isSecret: boolean; + isSalted: boolean; + separator: string; + useBrain: boolean; + hashCount: number; + cracked: number; + notes: string; + isArchived: boolean; + sourceType: string; + sourceData: string; } -export type CreateHashlist = BaseHashlist +export type CreateHashlist = BaseHashlist; // export interface Hashlist extends BaseHashlist { // id: number; @@ -27,23 +27,23 @@ export type CreateHashlist = BaseHashlist // } export interface Hashlist { - _id: number - _self: string - hashlistId?: number - accessGroupId: number - brainFeatures: string - format: string - name: string - hashTypeId: number - isHexSalt: boolean - isSecret: boolean - isSalted: boolean - separator: string - useBrain: boolean - hashCount: number - cracked: number - notes: string - isArchived: boolean - sourceType: string - sourceData: string -} \ No newline at end of file + _id: number; + _self: string; + hashlistId?: number; + accessGroupId: number; + brainFeatures: string; + format: number; + name: string; + hashTypeId: number; + isHexSalt: boolean; + isSecret: boolean; + isSalted: boolean; + separator: string; + useBrain: boolean; + hashCount: number; + cracked: number; + notes: string; + isArchived: boolean; + sourceType: string; + sourceData: string; +} From e036e9823b3b7bd6ca71c705aa30c531c4f32147 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Fri, 10 Nov 2023 14:54:54 +0000 Subject: [PATCH 214/419] 18 forms adapted to Angular Material --- src/app/account/account-routing.module.ts | 14 +- src/app/auth/forgot/forgot.component.html | 59 +- src/app/config/config-routing.module.ts | 139 +- src/app/config/config.module.ts | 54 +- .../agent-binaries.component.html | 1 - .../new-agent-binaries.component.html | 69 - .../new-agent-binaries.component.ts | 126 -- .../engine/crackers/crackers.component.html | 1 - .../edit-version/edit-crackers.component.html | 43 - .../edit-version/edit-crackers.component.ts | 93 -- .../new-cracker/new-cracker.component.html | 34 - .../new-cracker/new-cracker.component.ts | 46 - .../new-version/new-crackers.component.html | 52 - .../new-version/new-crackers.component.ts | 64 - src/app/config/engine/engine-menu.ts | 36 - .../new-preprocessor.component.html | 78 -- .../new-preprocessor.component.ts | 123 -- .../preprocessors.component.html | 1 - .../hashtype/hashtype.component.html | 75 - .../hashtypes/hashtype/hashtype.component.ts | 102 -- src/app/config/server/server.component.html | 690 ---------- src/app/config/server/server.component.ts | 369 ----- src/app/config/server/settings-menu.ts | 56 - src/app/core/_services/metadata.service.ts | 1210 ++++++++++++++--- src/app/shared/form/dynamicform.component.ts | 434 +++--- src/app/shared/form/dynamicform.module.ts | 26 +- src/app/shared/form/formconfig.component.ts | 103 +- .../shared/grid-containers/grid-autocol.ts | 57 +- .../navigation/horizontalnav.component.ts | 54 +- .../shared/navigation/navigation.module.ts | 6 +- ...new-globalpermissionsgroups.component.html | 19 - .../new-globalpermissionsgroups.component.ts | 47 - .../groups/cu-group/cu-group.component.html | 28 - .../groups/cu-group/cu-group.component.ts | 93 -- src/app/users/users.module.ts | 36 +- src/styles/base/_base.scss | 16 +- src/styles/base/_form.scss | 23 +- src/styles/components/_button.scss | 4 + 38 files changed, 1624 insertions(+), 2857 deletions(-) delete mode 100644 src/app/config/engine/agent-binaries/agent-binary/new-agent-binaries.component.html delete mode 100644 src/app/config/engine/agent-binaries/agent-binary/new-agent-binaries.component.ts delete mode 100644 src/app/config/engine/crackers/edit-version/edit-crackers.component.html delete mode 100644 src/app/config/engine/crackers/edit-version/edit-crackers.component.ts delete mode 100644 src/app/config/engine/crackers/new-cracker/new-cracker.component.html delete mode 100644 src/app/config/engine/crackers/new-cracker/new-cracker.component.ts delete mode 100644 src/app/config/engine/crackers/new-version/new-crackers.component.html delete mode 100644 src/app/config/engine/crackers/new-version/new-crackers.component.ts delete mode 100644 src/app/config/engine/engine-menu.ts delete mode 100644 src/app/config/engine/preprocessors/preprocessor/new-preprocessor.component.html delete mode 100644 src/app/config/engine/preprocessors/preprocessor/new-preprocessor.component.ts delete mode 100644 src/app/config/hashtypes/hashtype/hashtype.component.html delete mode 100644 src/app/config/hashtypes/hashtype/hashtype.component.ts delete mode 100644 src/app/config/server/server.component.html delete mode 100644 src/app/config/server/server.component.ts delete mode 100644 src/app/config/server/settings-menu.ts delete mode 100644 src/app/users/globalpermissionsgroups/new-globalpermissionsgroups/new-globalpermissionsgroups.component.html delete mode 100644 src/app/users/globalpermissionsgroups/new-globalpermissionsgroups/new-globalpermissionsgroups.component.ts delete mode 100644 src/app/users/groups/cu-group/cu-group.component.html delete mode 100644 src/app/users/groups/cu-group/cu-group.component.ts diff --git a/src/app/account/account-routing.module.ts b/src/app/account/account-routing.module.ts index d91984ea..6c0ead68 100644 --- a/src/app/account/account-routing.module.ts +++ b/src/app/account/account-routing.module.ts @@ -4,10 +4,12 @@ import { NgModule } from "@angular/core"; import { AccountSettingsComponent } from "./settings/acc-settings/acc-settings.component"; import { UiSettingsComponent } from "./settings/ui-settings/ui-settings.component"; -import { EditNotificationComponent } from "./notifications/notification/edit-notification.component"; import { NewNotificationComponent } from "./notifications/notification/new-notification.component"; +import { FormUIsettingsComponent } from "../shared/form/formuisettings.component"; import { NotificationsComponent } from "./notifications/notifications.component"; import { AccountComponent } from "./account.component"; +import { SERV } from '../core/_services/main.config'; +import { FormComponent } from "../shared/form/form.component"; const routes: Routes = [ { @@ -30,7 +32,9 @@ const routes: Routes = [ { path: 'ui-settings', component: UiSettingsComponent, data: { - kind: 'ui-settings', + kind: 'uisettings', + type: 'edit', + path: SERV.CONFIGS, breadcrumb: 'UI Settings' }, canActivate: [IsAuth]}, @@ -42,9 +46,11 @@ const routes: Routes = [ }, canActivate: [IsAuth]}, { - path: 'notifications/:id/edit', component: EditNotificationComponent, + path: 'notifications/:id/edit', component: FormComponent, data: { - kind: 'notifications-edit', + kind: 'editnotif', + type: 'edit', + path: SERV.NOTIFICATIONS, breadcrumb: 'Edit Notification' }, canActivate: [IsAuth]}, diff --git a/src/app/auth/forgot/forgot.component.html b/src/app/auth/forgot/forgot.component.html index 98820604..9ac10dbe 100644 --- a/src/app/auth/forgot/forgot.component.html +++ b/src/app/auth/forgot/forgot.component.html @@ -1,34 +1,39 @@ - -

Forgot password

- -
- + +

Forgot Password

+ +

+ + User Name - - - - + +

+ +

+ + Email + + +

+ -
-
+ diff --git a/src/app/config/config-routing.module.ts b/src/app/config/config-routing.module.ts index b6116ea1..bd8d49c5 100644 --- a/src/app/config/config-routing.module.ts +++ b/src/app/config/config-routing.module.ts @@ -3,66 +3,73 @@ import { Routes, RouterModule } from '@angular/router'; import { IsAuth } from "../core/_guards/auth.guard"; import { NgModule } from "@angular/core"; -import { NewAgentBinariesComponent } from "./engine/agent-binaries/agent-binary/new-agent-binaries.component"; import { EditHealthChecksComponent } from "./health-checks/edit-health-check/edit-health-checks.component"; -import { NewPreprocessorComponent } from "./engine/preprocessors/preprocessor/new-preprocessor.component"; import { NewHealthChecksComponent } from "./health-checks/new-health-check/new-health-checks.component"; -import { EditCrackersComponent } from "./engine/crackers/edit-version/edit-crackers.component"; -import { NewCrackersComponent } from "./engine/crackers/new-version/new-crackers.component"; import { AgentBinariesComponent } from "./engine/agent-binaries/agent-binaries.component"; -import { NewCrackerComponent } from "./engine/crackers/new-cracker/new-cracker.component"; import { PreprocessorsComponent } from "./engine/preprocessors/preprocessors.component"; import { HealthChecksComponent } from "./health-checks/health-checks.component"; -import { HashtypeComponent } from "./hashtypes/hashtype/hashtype.component"; +import { FormConfigComponent } from "../shared/form/formconfig.component"; import { CrackersComponent } from "./engine/crackers/crackers.component"; import { HashtypesComponent } from "./hashtypes/hashtypes.component"; -import { ServerComponent } from "./server/server.component"; +import { FormComponent } from "../shared/form/form.component"; +import { SERV } from '../core/_services/main.config'; import { LogComponent } from "./log/log.component"; const routes: Routes = [ { path: '', + canActivate: [IsAuth], children: [ { - path: 'agent', component: ServerComponent, + path: 'agent', component: FormConfigComponent, data: { - kind: 'agent', + kind: 'serveragent', + type: 'edit', + path: SERV.CONFIGS, breadcrumb: 'Agent Settings', permission: 'Config' }, - canActivate: [IsAuth,CheckPerm]}, + canActivate: [CheckPerm]}, { - path: 'task-chunk', component: ServerComponent, + path: 'task-chunk', component: FormConfigComponent, data: { - kind: 'task-chunk', + kind: 'servertaskchunk', + type: 'edit', + path: SERV.CONFIGS, breadcrumb: 'Task Chunk Settings', permission: 'Config' }, - canActivate: [IsAuth,CheckPerm]}, + canActivate: [CheckPerm]}, { - path: 'hch', component: ServerComponent, + path: 'hch', component: FormConfigComponent, data: { - kind: 'hch', + kind: 'serverhch', + type: 'edit', + path: SERV.CONFIGS, breadcrumb: 'Hashes/Cracks/Hashlist Settings', permission: 'Config' }, - canActivate: [IsAuth,CheckPerm]}, + canActivate: [CheckPerm]}, { - path: 'notifications', component: ServerComponent, + path: 'notifications', component: FormConfigComponent, data: { - kind: 'notif', + kind: 'servernotif', + type: 'edit', + path: SERV.CONFIGS, breadcrumb: 'Notifications', permission: 'Notif' }, - canActivate: [IsAuth,CheckPerm]}, + canActivate: [CheckPerm]}, { - path: 'general-settings', component: ServerComponent, + path: 'general-settings', component: FormConfigComponent, data: { - kind: 'gs', + kind: 'servergs', + type: 'edit', + path: SERV.CONFIGS, breadcrumb: 'General Settings', permission: 'Config' }, - canActivate: [IsAuth,CheckPerm]}, + canActivate: [CheckPerm]}, { path: 'hashtypes', component: HashtypesComponent, data: { @@ -70,23 +77,27 @@ const routes: Routes = [ breadcrumb: 'Hashtypes', permission: 'Hashtype' }, - canActivate: [IsAuth,CheckPerm]}, + canActivate: [CheckPerm]}, { - path: 'hashtypes/new', component: HashtypeComponent, + path: 'hashtypes/new', component: FormComponent, data: { - kind: 'new-hashtype', + kind: 'newhashtype', + type: 'create', + path: SERV.HASHTYPES, breadcrumb: 'New Hashtype', permission: 'Hashtype' }, - canActivate: [IsAuth,CheckPerm]}, + canActivate: [CheckPerm]}, { - path: 'hashtypes/:id/edit', component: HashtypeComponent, + path: 'hashtypes/:id/edit', component: FormComponent, data: { - kind: 'edit-hashtype', + kind: 'edithashtype', + type: 'edit', + path: SERV.HASHTYPES, breadcrumb: 'Edit Hashtype', permission: 'Hashtype' }, - canActivate: [IsAuth,CheckPerm]}, + canActivate: [CheckPerm]}, { path: 'log', component: LogComponent, data: { @@ -94,7 +105,7 @@ const routes: Routes = [ breadcrumb: 'Logs', permission: 'Logs' }, - canActivate: [IsAuth,CheckPerm]}, + canActivate: [CheckPerm]}, { path: 'health-checks', component: HealthChecksComponent, data: { @@ -102,7 +113,7 @@ const routes: Routes = [ breadcrumb: 'Health Checks', permission: 'HealthCheck' }, - canActivate: [IsAuth,CheckPerm]}, + canActivate: [CheckPerm]}, { path: 'health-checks/new', component: NewHealthChecksComponent, data: { @@ -110,7 +121,7 @@ const routes: Routes = [ breadcrumb: 'New Health Checks', permission: 'HealthCheck' }, - canActivate: [IsAuth,CheckPerm]}, + canActivate: [CheckPerm]}, { path: 'health-checks/:id/edit', component: EditHealthChecksComponent, data: { @@ -118,7 +129,7 @@ const routes: Routes = [ breadcrumb: 'Edit Health Checks', permission: 'HealthCheck' }, - canActivate: [IsAuth,CheckPerm]}, + canActivate: [CheckPerm]}, { path: 'engine/agent-binaries', component: AgentBinariesComponent, data: { @@ -126,23 +137,27 @@ const routes: Routes = [ breadcrumb: 'Engine > Agent-binaries', permission: 'AgentBinary' }, - canActivate: [IsAuth,CheckPerm]}, + canActivate: [CheckPerm]}, { - path: 'engine/agent-binaries/new-agent-binary', component: NewAgentBinariesComponent, + path: 'engine/agent-binaries/new-agent-binary', component: FormComponent, data: { - kind: 'new-agent-binary', + kind: 'newagentbinary', + type: 'create', + path: SERV.AGENT_BINARY, breadcrumb: 'Engine > New Agent binary', permission: 'AgentBinary' }, - canActivate: [IsAuth,CheckPerm]}, + canActivate: [CheckPerm]}, { - path: 'engine/agent-binaries/:id/edit', component: NewAgentBinariesComponent, + path: 'engine/agent-binaries/:id/edit', component: FormComponent, data: { - kind: 'edit-agent-binary', + kind: 'editagentbinary', + type: 'edit', + path: SERV.AGENT_BINARY, breadcrumb: 'Engine > Edit Agent binary', permission: 'AgentBinary' }, - canActivate: [IsAuth,CheckPerm]}, + canActivate: [CheckPerm]}, { path: 'engine/crackers', component: CrackersComponent, data: { @@ -150,31 +165,37 @@ const routes: Routes = [ breadcrumb: 'Engine > Crackers', permission: 'CrackerBinary' }, - canActivate: [IsAuth,CheckPerm]}, + canActivate: [CheckPerm]}, { - path: 'engine/crackers/new', component: NewCrackerComponent, + path: 'engine/crackers/new', component: FormComponent, data: { - kind: 'new-cracker', + kind: 'newcracker', + type: 'create', + path: SERV.CRACKERS_TYPES, breadcrumb: 'Engine > New Cracker', permission: 'CrackerBinary' }, - canActivate: [IsAuth,CheckPerm]}, + canActivate: [CheckPerm]}, { - path: 'engine/crackers/:id/new', component: NewCrackersComponent, + path: 'engine/crackers/:id/new', component: FormComponent, data: { - kind: 'new-cracker-version', + kind: 'newcrackerversion', + type: 'create', + path: SERV.CRACKERS, breadcrumb: 'Engine > New Cracker Version/Binary', permission: 'CrackerBinary' }, - canActivate: [IsAuth,CheckPerm]}, + canActivate: [CheckPerm]}, { - path: 'engine/crackers/:id/edit', component: EditCrackersComponent, + path: 'engine/crackers/:id/edit', component: FormComponent, data: { - kind: 'edit-cracker-version', + kind: 'editcrackerversion', + type: 'edit', + path: SERV.CRACKERS, breadcrumb: 'Engine > Edit Cracker Version/Binary', permission: 'CrackerBinaryType' }, - canActivate: [IsAuth,CheckPerm]}, + canActivate: [CheckPerm]}, { path: 'engine/preprocessors', component: PreprocessorsComponent, data: { @@ -182,23 +203,27 @@ const routes: Routes = [ breadcrumb: 'Engine > Preprocessors', permission: 'Prepro' }, - canActivate: [IsAuth,CheckPerm]}, + canActivate: [CheckPerm]}, { - path: 'engine/preprocessors/new-preprocessor', component: NewPreprocessorComponent, + path: 'engine/preprocessors/new-preprocessor', component: FormComponent, data: { - kind: 'new-preprocessor', + kind: 'newpreprocessor', + type: 'create', + path: SERV.PREPROCESSORS, breadcrumb: 'Engine > New Preprocessor', permission: 'Prepro' }, - canActivate: [IsAuth,CheckPerm]}, + canActivate: [CheckPerm]}, { - path: 'engine/preprocessors/:id/edit', component: NewPreprocessorComponent, + path: 'engine/preprocessors/:id/edit', component: FormComponent, data: { - kind: 'edit-preprocessor', + kind: 'editpreprocessor', + type: 'edit', + path: SERV.PREPROCESSORS, breadcrumb: 'Engine > Edit Preprocessor', permission: 'Prepro' }, - canActivate: [IsAuth,CheckPerm]}, + canActivate: [CheckPerm]}, ] }, ]; diff --git a/src/app/config/config.module.ts b/src/app/config/config.module.ts index cf3a9a45..1c448144 100644 --- a/src/app/config/config.module.ts +++ b/src/app/config/config.module.ts @@ -1,53 +1,35 @@ -import { FontAwesomeModule } from "@fortawesome/angular-fontawesome"; -import { FormsModule, ReactiveFormsModule } from "@angular/forms"; -import { NgbModule } from "@ng-bootstrap/ng-bootstrap"; -import { DataTablesModule } from "angular-datatables"; -import { CommonModule } from "@angular/common"; -import { RouterModule } from "@angular/router"; -import { NgModule } from "@angular/core"; +import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { DataTablesModule } from 'angular-datatables'; +import { CommonModule } from '@angular/common'; +import { RouterModule } from '@angular/router'; +import { NgModule } from '@angular/core'; -import { NewAgentBinariesComponent } from "./engine/agent-binaries/agent-binary/new-agent-binaries.component"; -import { NewPreprocessorComponent } from './engine/preprocessors/preprocessor/new-preprocessor.component'; import { EditHealthChecksComponent } from './health-checks/edit-health-check/edit-health-checks.component'; import { NewHealthChecksComponent } from './health-checks/new-health-check/new-health-checks.component'; -import { EditCrackersComponent } from './engine/crackers/edit-version/edit-crackers.component'; -import { NewCrackersComponent } from './engine/crackers/new-version/new-crackers.component'; -import { NewCrackerComponent } from './engine/crackers/new-cracker/new-cracker.component'; -import { AgentBinariesComponent } from "./engine/agent-binaries/agent-binaries.component"; -import { PreprocessorsComponent } from "./engine/preprocessors/preprocessors.component"; -import { HealthChecksComponent } from "./health-checks/health-checks.component"; -import { HashtypeComponent } from './hashtypes/hashtype/hashtype.component'; -import { CrackersComponent } from "./engine/crackers/crackers.component"; -import { HashtypesComponent } from "./hashtypes/hashtypes.component"; -import { ComponentsModule } from "../shared/components.module"; -import { SettingsMenuComponent } from "./server/settings-menu"; -import { ConfigRoutingModule } from "./config-routing.module"; -import { ServerComponent } from "./server/server.component"; -import { EngineMenuComponent } from "./engine/engine-menu"; -import { PipesModule } from "../shared/pipes.module"; -import { LogComponent } from "./log/log.component"; +import { AgentBinariesComponent } from './engine/agent-binaries/agent-binaries.component'; +import { PreprocessorsComponent } from './engine/preprocessors/preprocessors.component'; +import { HealthChecksComponent } from './health-checks/health-checks.component'; +import { CrackersComponent } from './engine/crackers/crackers.component'; +import { HashtypesComponent } from './hashtypes/hashtypes.component'; +import { ComponentsModule } from '../shared/components.module'; +import { ConfigRoutingModule } from './config-routing.module'; +import { PipesModule } from '../shared/pipes.module'; +import { LogComponent } from './log/log.component'; @NgModule({ - declarations:[ - NewAgentBinariesComponent, + declarations: [ EditHealthChecksComponent, - NewPreprocessorComponent, NewHealthChecksComponent, PreprocessorsComponent, AgentBinariesComponent, HealthChecksComponent, - EditCrackersComponent, - SettingsMenuComponent, - NewCrackersComponent, - NewCrackerComponent, - EngineMenuComponent, HashtypesComponent, CrackersComponent, - HashtypeComponent, - ServerComponent, LogComponent ], - imports:[ + imports: [ ReactiveFormsModule, ConfigRoutingModule, FontAwesomeModule, diff --git a/src/app/config/engine/agent-binaries/agent-binaries.component.html b/src/app/config/engine/agent-binaries/agent-binaries.component.html index 47a5e1b6..f1802c0b 100644 --- a/src/app/config/engine/agent-binaries/agent-binaries.component.html +++ b/src/app/config/engine/agent-binaries/agent-binaries.component.html @@ -1,5 +1,4 @@ - diff --git a/src/app/config/engine/agent-binaries/agent-binary/new-agent-binaries.component.html b/src/app/config/engine/agent-binaries/agent-binary/new-agent-binaries.component.html deleted file mode 100644 index 407fec63..00000000 --- a/src/app/config/engine/agent-binaries/agent-binary/new-agent-binaries.component.html +++ /dev/null @@ -1,69 +0,0 @@ - -
-
- -
-
- -
-
-
-
- - - - - - - - - - - - - - - -
-
-
- - - - -
-
- - - -
-
- -
diff --git a/src/app/config/engine/agent-binaries/agent-binary/new-agent-binaries.component.ts b/src/app/config/engine/agent-binaries/agent-binary/new-agent-binaries.component.ts deleted file mode 100644 index b5f5bc82..00000000 --- a/src/app/config/engine/agent-binaries/agent-binary/new-agent-binaries.component.ts +++ /dev/null @@ -1,126 +0,0 @@ -import { faHomeAlt, faPlus, faTrash, faEdit } from '@fortawesome/free-solid-svg-icons'; -import { FormControl, FormGroup } from '@angular/forms'; -import { ActivatedRoute, Params } from '@angular/router'; -import { Component, OnInit } from '@angular/core'; -import { Router } from '@angular/router'; - -import { GlobalService } from 'src/app/core/_services/main.service'; -import { PageTitle } from 'src/app/core/_decorators/autotitle'; -import { SERV } from '../../../../core/_services/main.config'; -import { AlertService } from 'src/app/core/_services/shared/alert.service'; - -@Component({ - selector: 'app-agent-binaries', - templateUrl: './new-agent-binaries.component.html' -}) -@PageTitle(['Agent Binary']) -export class NewAgentBinariesComponent implements OnInit { - - editMode = false; - editedABIndex: number; - - public isCollapsed = true; - faHome=faHomeAlt; - faPlus=faPlus; - faTrash=faTrash; - faEdit=faEdit; - - public binaries: {agentBinaryId: number, type: string, version: string, operatingSystems: string, filename: string, updateTrack: string, updateAvailable: string}[] = []; - - constructor( - private route:ActivatedRoute, - private alert: AlertService, - private gs: GlobalService, - private router:Router - ) { } - - // Create or Edit Binary - whichView: string; - - updateForm = new FormGroup({ - 'type': new FormControl(''), - 'filename': new FormControl(''), - 'operatingSystems': new FormControl(''), - 'version': new FormControl(''), - 'updateTrack': new FormControl(''), - }); - - ngOnInit(): void { - - this.route.params - .subscribe( - (params: Params) => { - this.editedABIndex = +params['id']; - this.editMode = params['id'] != null; - } - ); - - this.route.data.subscribe(data => { - switch (data['kind']) { - - case 'new-agent-binary': - this.whichView = 'create'; - break; - - case 'edit-agent-binary': - this.whichView = 'edit'; - this.initForm(); - - const id = +this.route.snapshot.params['id']; - this.gs.get(SERV.AGENT_BINARY,id).subscribe((bin: any) => { - this.binaries = bin.values; - }); - break; - - } - }); - } - - onSubmit(): void{ - if (this.updateForm.valid) { - - switch (this.whichView) { - - case 'create': - this.gs.create(SERV.AGENT_BINARY,this.updateForm.value) - .subscribe((prep: any) => { - const response = prep; - this.alert.okAlert('New Agent Binary created!',''); - this.router.navigate(['config/engine/agent-binaries']); - } - ); - break; - - case 'edit': - const id = +this.route.snapshot.params['id']; - this.gs.update(SERV.AGENT_BINARY,id,this.updateForm.value) - .subscribe((prep: any) => { - const response = prep; - this.alert.okAlert('Agent Binary saved!',''); - this.router.navigate(['config/engine/agent-binaries']); - } - ); - break; - - } - } - - } - - private initForm() { - if (this.editMode) { - this.gs.get(SERV.AGENT_BINARY,this.editedABIndex).subscribe((result)=>{ - this.updateForm = new FormGroup({ - 'type': new FormControl(result['type']), - 'filename': new FormControl(result['filename']), - 'operatingSystems': new FormControl(result['operatingSystems']), - 'version': new FormControl(result['version']), - 'updateTrack': new FormControl(result['updateTrack']), - }); - }); - } - } - - - -} diff --git a/src/app/config/engine/crackers/crackers.component.html b/src/app/config/engine/crackers/crackers.component.html index 15189033..5a7b6cf2 100644 --- a/src/app/config/engine/crackers/crackers.component.html +++ b/src/app/config/engine/crackers/crackers.component.html @@ -1,5 +1,4 @@ -
diff --git a/src/app/config/engine/crackers/edit-version/edit-crackers.component.html b/src/app/config/engine/crackers/edit-version/edit-crackers.component.html deleted file mode 100644 index 76dd1b64..00000000 --- a/src/app/config/engine/crackers/edit-version/edit-crackers.component.html +++ /dev/null @@ -1,43 +0,0 @@ - - - -
-
- - - - - - - -
- - - - -
-
-
- - - - -
-
- -
diff --git a/src/app/config/engine/crackers/edit-version/edit-crackers.component.ts b/src/app/config/engine/crackers/edit-version/edit-crackers.component.ts deleted file mode 100644 index 7bdddf0a..00000000 --- a/src/app/config/engine/crackers/edit-version/edit-crackers.component.ts +++ /dev/null @@ -1,93 +0,0 @@ -import { FormControl, FormGroup, Validators } from '@angular/forms'; -import { ActivatedRoute, Params, Router } from '@angular/router'; -import { faDownload } from '@fortawesome/free-solid-svg-icons'; -import Swal from 'sweetalert2/dist/sweetalert2.js'; -import { Component, OnInit } from '@angular/core'; - -import { AlertService } from 'src/app/core/_services/shared/alert.service'; -import { GlobalService } from 'src/app/core/_services/main.service'; -import { PageTitle } from 'src/app/core/_decorators/autotitle'; -import { SERV } from '../../../../core/_services/main.config'; - -@Component({ - selector: 'app-edit-crackers', - templateUrl: './edit-crackers.component.html' -}) -@PageTitle(['Edit Crackers']) -export class EditCrackersComponent implements OnInit { - - editMode = false; - editedCrackervIndex: number; - crackerV: any // Change to Model - - faDownload=faDownload; - - constructor( - private route:ActivatedRoute, - private alert: AlertService, - private gs: GlobalService, - private router: Router - ) { } - - updateForm = new FormGroup({ - 'binaryName': new FormControl(''), - 'version': new FormControl(''), - 'downloadUrl': new FormControl(''), - }); - - ngOnInit(): void { - - this.route.params - .subscribe( - (params: Params) => { - this.editedCrackervIndex = +params['id']; - this.editMode = params['id'] != null; - this.initForm(); - } - ); - - } - - onSubmit(){ - if (this.updateForm.valid) { - - this.gs.update(SERV.CRACKERS,this.editedCrackervIndex,this.updateForm.value).subscribe(() => { - this.alert.okAlert('Cracker saved!',''); - this.updateForm.reset(); // success, we reset form - this.router.navigate(['config/engine/crackers']); - } - ); - } - } - - onDelete(){ - this.alert.deleteConfirmation('','Crackers').then((confirmed) => { - if (confirmed) { - // Deletion - this.gs.delete(SERV.CRACKERS, this.editedCrackervIndex).subscribe(() => { - // Successful deletion - this.alert.okAlert(`Deleted cracker`, ''); - this.router.navigate(['config/engine/crackers']); - }); - } else { - // Handle cancellation - this.alert.okAlert(`Cracker is safe!`,''); - } - }); - } - - private initForm() { - if (this.editMode) { - this.gs.get(SERV.CRACKERS,this.editedCrackervIndex).subscribe((result)=>{ - this.crackerV = result; - this.updateForm = new FormGroup({ - 'binaryName': new FormControl(result['binaryName']), - 'version': new FormControl(result['version'], [Validators.required, Validators.minLength(1)]), - 'downloadUrl': new FormControl(result['downloadUrl'], [Validators.required, Validators.minLength(1)]), - }); - }); - } - } - - -} diff --git a/src/app/config/engine/crackers/new-cracker/new-cracker.component.html b/src/app/config/engine/crackers/new-cracker/new-cracker.component.html deleted file mode 100644 index 98729397..00000000 --- a/src/app/config/engine/crackers/new-cracker/new-cracker.component.html +++ /dev/null @@ -1,34 +0,0 @@ - - - -
-
- - - - - - - - - - -
- -
diff --git a/src/app/config/engine/crackers/new-cracker/new-cracker.component.ts b/src/app/config/engine/crackers/new-cracker/new-cracker.component.ts deleted file mode 100644 index 69183ace..00000000 --- a/src/app/config/engine/crackers/new-cracker/new-cracker.component.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { PageTitle } from 'src/app/core/_decorators/autotitle'; -import { Component, OnInit } from '@angular/core'; -import { Router } from '@angular/router'; - -import { AlertService } from 'src/app/core/_services/shared/alert.service'; -import { GlobalService } from 'src/app/core/_services/main.service'; -import { SERV } from '../../../../core/_services/main.config'; -import { FormControl, FormGroup } from '@angular/forms'; - -@Component({ - selector: 'app-new-cracker', - templateUrl: './new-cracker.component.html' -}) -@PageTitle(['New Cracker']) -export class NewCrackerComponent implements OnInit { - - createForm: FormGroup; - - constructor( - private alert: AlertService, - private gs: GlobalService, - private router:Router - ) { } - - ngOnInit(): void { - - this.createForm = new FormGroup({ - 'typeName': new FormControl(''), - 'isChunkingAvailable': new FormControl(''), - }); - - } - - onSubmit(){ - if (this.createForm.valid) { - - this.gs.create(SERV.CRACKERS_TYPES, this.createForm.value).subscribe(() => { - this.alert.okAlert('New Cracker created!',''); - this.createForm.reset(); // success, we reset form - this.router.navigate(['/config/engine/crackers']); - } - ); - } - } - -} diff --git a/src/app/config/engine/crackers/new-version/new-crackers.component.html b/src/app/config/engine/crackers/new-version/new-crackers.component.html deleted file mode 100644 index b822df93..00000000 --- a/src/app/config/engine/crackers/new-version/new-crackers.component.html +++ /dev/null @@ -1,52 +0,0 @@ - - -
-
- - - - - - -
-
- - -
- - - - -
-
-
-
- - - - - -
- diff --git a/src/app/config/engine/crackers/new-version/new-crackers.component.ts b/src/app/config/engine/crackers/new-version/new-crackers.component.ts deleted file mode 100644 index 7d3b75a8..00000000 --- a/src/app/config/engine/crackers/new-version/new-crackers.component.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { faDownload, faInfoCircle } from '@fortawesome/free-solid-svg-icons'; -import { FormControl, FormGroup, Validators } from '@angular/forms'; -import { ActivatedRoute, Params } from '@angular/router'; -import { Component, OnInit } from '@angular/core'; -import { Router } from '@angular/router'; - -import { GlobalService } from 'src/app/core/_services/main.service'; -import { PageTitle } from 'src/app/core/_decorators/autotitle'; -import { SERV } from '../../../../core/_services/main.config'; -import { AlertService } from 'src/app/core/_services/shared/alert.service'; - -@Component({ - selector: 'app-new-crackers', - templateUrl: './new-crackers.component.html' -}) -@PageTitle(['New Crackers']) -export class NewCrackersComponent implements OnInit { - - faInfoCircle=faInfoCircle; - faDownload=faDownload; - - editMode = false; - editedTypeIndex: number; - createForm: FormGroup; - - constructor( - private route:ActivatedRoute, - private alert: AlertService, - private gs: GlobalService, - private router: Router - ) { } - - ngOnInit(): void { - - this.route.params - .subscribe( - (params: Params) => { - this.editedTypeIndex = +params['id']; - this.editMode = params['id'] != null; - } - ); - - this.createForm = new FormGroup({ - 'binaryName': new FormControl('', [Validators.required]), - 'version': new FormControl('', [Validators.required]), - 'downloadUrl': new FormControl('', [Validators.required]), - 'crackerBinaryTypeId': new FormControl(this.editedTypeIndex), - }); - } - - onSubmit(){ - if (this.createForm.valid) { - - this.gs.create(SERV.CRACKERS, this.createForm.value).subscribe(() => { - this.alert.okAlert('New Version created!',''); - this.createForm.reset(); // success, we reset form - this.router.navigate(['/config/engine/crackers']); - } - ); - } - } - - -} diff --git a/src/app/config/engine/engine-menu.ts b/src/app/config/engine/engine-menu.ts deleted file mode 100644 index 8320ecfa..00000000 --- a/src/app/config/engine/engine-menu.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { Component, Input } from '@angular/core'; -import { Router } from '@angular/router'; - -@Component({ - selector: 'engine-menu', - template: ` -
- - - -
- ` -}) -export class EngineMenuComponent { - - @Input() aclass?: any; - @Input() cclass?: any; - @Input() pclass?: any; - - constructor( - private router: Router - ) { } - - Binary(){ - this.router.navigate(['/config/engine/agent-binaries']); - } - - Cracker(){ - this.router.navigate(['/config/engine/crackers']); - } - - Prepro(){ - this.router.navigate(['/config/engine/preprocessors']); - } - -} diff --git a/src/app/config/engine/preprocessors/preprocessor/new-preprocessor.component.html b/src/app/config/engine/preprocessors/preprocessor/new-preprocessor.component.html deleted file mode 100644 index 6d2bad9a..00000000 --- a/src/app/config/engine/preprocessors/preprocessor/new-preprocessor.component.html +++ /dev/null @@ -1,78 +0,0 @@ -
-
- -
-
- -
-
- - -
-
- - - - - - - - - - -
- - - - -
-
- - - - -
-
- - - -
-
- - diff --git a/src/app/config/engine/preprocessors/preprocessor/new-preprocessor.component.ts b/src/app/config/engine/preprocessors/preprocessor/new-preprocessor.component.ts deleted file mode 100644 index 8d5ebaef..00000000 --- a/src/app/config/engine/preprocessors/preprocessor/new-preprocessor.component.ts +++ /dev/null @@ -1,123 +0,0 @@ -import { FormControl, FormGroup, Validators } from '@angular/forms'; -import { ActivatedRoute, Router, Params } from '@angular/router'; -import { Component, OnInit } from '@angular/core'; - -import { AlertService } from 'src/app/core/_services/shared/alert.service'; -import { GlobalService } from 'src/app/core/_services/main.service'; -import { PageTitle } from 'src/app/core/_decorators/autotitle'; -import { SERV } from '../../../../core/_services/main.config'; - -@Component({ - selector: 'app-new-preprocessor', - templateUrl: './new-preprocessor.component.html' -}) -@PageTitle(['Preprocessor']) -export class NewPreprocessorComponent implements OnInit { - - editMode = false; - editedPreprocessorIndex: number; - Preprocessor: any // Change to Model - - constructor( - private route:ActivatedRoute, - private alert: AlertService, - private gs: GlobalService, - private router: Router, - ) { } - - // Create or Edit Preprocessor - whichView: string; - - prep: any[]; - - updateForm = new FormGroup({ - 'name': new FormControl(''), - 'url': new FormControl(''), - 'binaryName': new FormControl(''), - 'keyspaceCommand': new FormControl('--keyspace' || ''), - 'skipCommand': new FormControl('--skip' || ''), - 'limitCommand': new FormControl('--limit' || '') - }); - - - ngOnInit(): void { - - this.route.params - .subscribe( - (params: Params) => { - this.editedPreprocessorIndex = +params['id']; - this.editMode = params['id'] != null; - } - ); - - this.route.data.subscribe(data => { - switch (data['kind']) { - - case 'new-preprocessor': - this.whichView = 'create'; - break; - - case 'edit-preprocessor': - this.whichView = 'edit'; - - this.initForm(); - - const id = +this.route.snapshot.params['id']; - this.gs.get(SERV.PREPROCESSORS,id).subscribe((prep: any) => { - this.prep = prep; - }); - break; - - } - }); - - } - swap:any - onSubmit(): void{ - if (this.updateForm.valid) { - - switch (this.whichView) { - - case 'create': - this.gs.create(SERV.PREPROCESSORS,this.updateForm.value) - .subscribe((prep: any) => { - const response = prep; - this.alert.okAlert('New Preprocessor created!',''); - this.router.navigate(['config/engine/preprocessors']); - } - ); - break; - - case 'edit': - const id = +this.route.snapshot.params['id']; - this.gs.update(SERV.PREPROCESSORS,id,this.updateForm.value) - .subscribe((prep: any) => { - const response = prep; - this.alert.okAlert('Preprocessor saved!',''); - this.router.navigate(['config/engine/preprocessors']); - } - ); - break; - - } - } - - } - - private initForm() { - if (this.editMode) { - this.gs.get(SERV.PREPROCESSORS,this.editedPreprocessorIndex).subscribe((result)=>{ - this.updateForm = new FormGroup({ - 'name': new FormControl(result['name'], [Validators.required, Validators.minLength(1)]), - 'url': new FormControl(result['url'], [Validators.required, Validators.minLength(1)]), - 'keyspaceCommand': new FormControl(result['keyspaceCommand'], Validators.required), - 'binaryName': new FormControl(result['binaryName'], Validators.required), - 'skipCommand': new FormControl(result['skipCommand'], Validators.required), - 'limitCommand': new FormControl(result['limitCommand'], Validators.required), - }); - }); - } - } - -} - diff --git a/src/app/config/engine/preprocessors/preprocessors.component.html b/src/app/config/engine/preprocessors/preprocessors.component.html index c2eec031..ec051938 100644 --- a/src/app/config/engine/preprocessors/preprocessors.component.html +++ b/src/app/config/engine/preprocessors/preprocessors.component.html @@ -1,5 +1,4 @@ -
- - - - - -
diff --git a/src/app/config/hashtypes/hashtype/hashtype.component.html b/src/app/config/hashtypes/hashtype/hashtype.component.html deleted file mode 100644 index 27539ed7..00000000 --- a/src/app/config/hashtypes/hashtype/hashtype.component.html +++ /dev/null @@ -1,75 +0,0 @@ -
-
- -
-
- -
-
- - -
-
- - - - This name is already in use! - This field is required! - - - - - -
-
- -
- -
-
-
-
- -
- -
-
-
-
-
-
- - - - -
-
- - - -
-
-
- -
diff --git a/src/app/config/hashtypes/hashtype/hashtype.component.ts b/src/app/config/hashtypes/hashtype/hashtype.component.ts deleted file mode 100644 index f906fe36..00000000 --- a/src/app/config/hashtypes/hashtype/hashtype.component.ts +++ /dev/null @@ -1,102 +0,0 @@ -import { FormControl, FormGroup, Validators } from '@angular/forms'; -import { ActivatedRoute, Params, Router } from '@angular/router'; -import { Component, OnInit } from '@angular/core'; - -import { AlertService } from 'src/app/core/_services/shared/alert.service'; -import { GlobalService } from 'src/app/core/_services/main.service'; -import { PageTitle } from 'src/app/core/_decorators/autotitle'; -import { SERV } from '../../../core/_services/main.config'; - -@Component({ - selector: 'app-hashtype', - templateUrl: './hashtype.component.html' -}) -@PageTitle(['Hashtype']) -export class HashtypeComponent implements OnInit { - - // Create or Edit Hashtype - whichView: string; - editMode = false; - editedIndex: number; - - constructor( - private route:ActivatedRoute, - private alert: AlertService, - private gs: GlobalService, - private router:Router - ) { } - - // Create Hashtype - Form = new FormGroup({ - 'hashTypeId': new FormControl('', [Validators.required,Validators.pattern("^[0-9]*$"), Validators.minLength(1)]), - 'description': new FormControl('', [Validators.required, Validators.minLength(1)]), - 'isSalted': new FormControl(false), - 'isSlowHash': new FormControl(false) - }); - - ngOnInit(): void { - - this.route.params - .subscribe( - (params: Params) => { - this.editedIndex = +params['id']; - this.editMode = params['id'] != null; - } - ); - - this.route.data.subscribe(data => { - switch (data['kind']) { - - case 'new-hashtype': - this.whichView = 'create'; - break; - - case 'edit-hashtype': - this.whichView = 'edit'; - this.initForm(); - const id = +this.route.snapshot.params['id']; - break; - - } - }); - - } - - private initForm() { - if (this.editMode) { - this.gs.get(SERV.HASHTYPES,this.editedIndex).subscribe((result)=>{ - this.Form = new FormGroup({ - 'hashTypeId': new FormControl({value: result['hashTypeId'], disabled: true} ), - 'description': new FormControl(result['description']), - 'isSalted': new FormControl(result['isSalted']), - 'isSlowHash': new FormControl(result['isSlowHash']), - }); - }); - } - } - - onSubmit(): void{ - if (this.Form.valid) { - - switch (this.whichView) { - - case 'create': - this.gs.create(SERV.HASHTYPES,this.Form.value).subscribe(() => { - this.alert.okAlert('New Hashtype created!',''); - this.router.navigate(['/config/hashtypes']); - } - ); - break; - - case 'edit': - const id = +this.route.snapshot.params['id']; - this.gs.update(SERV.HASHTYPES,id,this.Form.value).subscribe(() => { - this.alert.okAlert('Hashtype saved!',''); - this.router.navigate(['/config/hashtypes']); - }); - break; - } - } -} - -} diff --git a/src/app/config/server/server.component.html b/src/app/config/server/server.component.html deleted file mode 100644 index d0d6788e..00000000 --- a/src/app/config/server/server.component.html +++ /dev/null @@ -1,690 +0,0 @@ -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- -
-
- -
- -
Activity / Registration
- -
+ ) {} + // @todo: fix ExpressionChangedAfterItHasBeenCheckedError. loading is causing trouble... + ngOnInit(): void { this.uiSettings = new UISettingsUtilityClass(this.storage); this.columnNames = this.tableColumns.map( @@ -285,7 +287,9 @@ export class HTTableComponent implements OnInit, AfterViewInit { */ reload(): void { this.dataSource.reload(); - this.bulkMenu.reload(); + if (this.bulkMenu) { + this.bulkMenu.reload(); + } } /** diff --git a/src/app/core/_components/tables/ht-table/ht-table.models.ts b/src/app/core/_components/tables/ht-table/ht-table.models.ts index 641650fe..1853d57e 100644 --- a/src/app/core/_components/tables/ht-table/ht-table.models.ts +++ b/src/app/core/_components/tables/ht-table/ht-table.models.ts @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { SafeHtml } from '@angular/platform-browser'; -export type DataType = 'agents' | 'hashlists' | 'chunks'; +export type DataType = 'agents' | 'hashlists' | 'chunks' | 'hashtypes'; export interface HTTableIcon { name: string; diff --git a/src/app/core/_datasources/agents.datasource.ts b/src/app/core/_datasources/agents.datasource.ts index a7605247..9ea5969a 100644 --- a/src/app/core/_datasources/agents.datasource.ts +++ b/src/app/core/_datasources/agents.datasource.ts @@ -11,7 +11,7 @@ import { environment } from 'src/environments/environment'; export class AgentsDataSource extends BaseDataSource { loadAll(): void { - this.loadingSubject.next(true); + this.loading = true; const agentParams = { maxResults: 999999, expand: 'accessGroups' }; const params = { maxResults: 999999 }; @@ -24,7 +24,7 @@ export class AgentsDataSource extends BaseDataSource { forkJoin([agents$, users$, agentAssign$, tasks$, chunks$]) .pipe( catchError(() => of([])), - finalize(() => this.loadingSubject.next(false)) + finalize(() => (this.loading = false)) ) .subscribe( ([a, u, aa, t, c]: [ diff --git a/src/app/core/_datasources/base.datasource.ts b/src/app/core/_datasources/base.datasource.ts index c80ff4c6..119de593 100644 --- a/src/app/core/_datasources/base.datasource.ts +++ b/src/app/core/_datasources/base.datasource.ts @@ -1,8 +1,10 @@ import { BehaviorSubject, Observable, Subscription } from 'rxjs'; import { CollectionViewer, DataSource } from '@angular/cdk/collections'; +import { ChangeDetectorRef } from '@angular/core'; import { GlobalService } from '../_services/main.service'; import { HTTableColumn } from '../_components/tables/ht-table/ht-table.models'; +import { MatPaginator } from '@angular/material/paginator'; import { MatSort } from '@angular/material/sort'; import { MatTableDataSourcePaginator } from '@angular/material/table'; import { SelectionModel } from '@angular/cdk/collections'; @@ -61,11 +63,6 @@ export abstract class BaseDataSource< */ public selection = new SelectionModel(true, []); - /** - * Observable to track the loading state. - */ - public loading$ = this.loadingSubject.asObservable(); - /** * Reference to the paginator, if pagination is enabled. */ @@ -82,10 +79,28 @@ export abstract class BaseDataSource< public sort: MatSort; constructor( + protected cdr: ChangeDetectorRef, protected service: GlobalService, protected uiService: UIConfigService ) {} + /** + * Gets the observable for the loading state. + * @return An observable that emits boolean values representing the loading state. + */ + get loading$(): Observable { + return this.loadingSubject.asObservable(); + } + + /** + * Sets the loading state and triggers change detection. + * @param value - The boolean value representing the loading state to be set. + */ + set loading(value: boolean) { + this.loadingSubject.next(value); + this.cdr.detectChanges(); + } + /** * Connect the data source to a collection viewer. * diff --git a/src/app/core/_datasources/chunks.datasource.ts b/src/app/core/_datasources/chunks.datasource.ts index 603a215f..7c25c475 100644 --- a/src/app/core/_datasources/chunks.datasource.ts +++ b/src/app/core/_datasources/chunks.datasource.ts @@ -8,7 +8,7 @@ import { SERV } from '../_services/main.config'; export class ChunksDataSource extends BaseDataSource { loadAll(): void { - this.loadingSubject.next(true); + this.loading = true; const agentParams = { maxResults: 999999 }; const chunkParams = { maxResults: 1000, expand: 'task' }; @@ -18,7 +18,7 @@ export class ChunksDataSource extends BaseDataSource { forkJoin([chunks$, agents$]) .pipe( catchError(() => of([])), - finalize(() => this.loadingSubject.next(false)) + finalize(() => (this.loading = false)) ) .subscribe( ([c, a]: [ListResponseWrapper, ListResponseWrapper]) => { diff --git a/src/app/core/_datasources/hashlists.datasource.ts b/src/app/core/_datasources/hashlists.datasource.ts index fb1c1a3f..15a021a2 100644 --- a/src/app/core/_datasources/hashlists.datasource.ts +++ b/src/app/core/_datasources/hashlists.datasource.ts @@ -14,7 +14,7 @@ export class HashlistsDataSource extends BaseDataSource { } loadAll(): void { - this.loadingSubject.next(true); + this.loading = true; const params = { maxResults: this.maxResults, @@ -28,7 +28,7 @@ export class HashlistsDataSource extends BaseDataSource { hashLists$ .pipe( catchError(() => of([])), - finalize(() => this.loadingSubject.next(false)) + finalize(() => (this.loading = false)) ) .subscribe((response: ListResponseWrapper) => { const rows: Hashlist[] = []; diff --git a/src/app/core/_datasources/hashtypes.datasource.ts b/src/app/core/_datasources/hashtypes.datasource.ts index 2a2dbbac..2357b812 100644 --- a/src/app/core/_datasources/hashtypes.datasource.ts +++ b/src/app/core/_datasources/hashtypes.datasource.ts @@ -7,16 +7,16 @@ import { SERV } from '../_services/main.config'; export class HashtypesDataSource extends BaseDataSource { loadAll(): void { - this.loadingSubject.next(true); + this.loading = true; - const params = { maxResults: this.maxResults }; + const params = { maxResults: this.paginator.pageSize }; const hashtypes$ = this.service.getAll(SERV.HASHTYPES, params); this.subscriptions.push( hashtypes$ .pipe( catchError(() => of([])), - finalize(() => this.loadingSubject.next(false)) + finalize(() => (this.loading = false)) ) .subscribe((response: ListResponseWrapper) => { this.setPaginationConfig( diff --git a/src/app/core/_models/config-ui.model.ts b/src/app/core/_models/config-ui.model.ts index 928b4873..9a484026 100644 --- a/src/app/core/_models/config-ui.model.ts +++ b/src/app/core/_models/config-ui.model.ts @@ -1,6 +1,7 @@ import { AgentsTableColumnLabel } from '../_components/tables/agents-table/agents-table.constants'; import { ChunksTableColumnLabel } from '../_components/tables/chunks-table/chunks-table.constants'; import { HashlistsTableColumnLabel } from '../_components/tables/hashlists-table/hashlists-table.constants'; +import { HashtypesTableColumnLabel } from '../_components/tables/hashtypes-table/hashtypes-table.constants'; export type Layout = 'full' | 'fixed'; export type Theme = 'light' | 'dark'; @@ -51,6 +52,12 @@ export const uiConfigDefault: UIConfig = { HashlistsTableColumnLabel.FORMAT, HashlistsTableColumnLabel.CRACKED, HashlistsTableColumnLabel.HASH_COUNT + ], + hashtypesTable: [ + HashtypesTableColumnLabel.HASHTYPE, + HashtypesTableColumnLabel.DESCRIPTION, + HashtypesTableColumnLabel.SALTED, + HashtypesTableColumnLabel.SLOW_HASH ] }, refreshPage: false, From 6918e66924515d2c871ae4e44fd15eb612ef88e8 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Sun, 12 Nov 2023 22:27:07 +0100 Subject: [PATCH 246/419] Add bulk and action menu --- .../menus/action-menu/action-menu.model.ts | 16 ++++++++-------- .../menus/base-menu/base-menu.component.ts | 4 ++++ .../bulk-action-menu.component.ts | 13 +++++++++++++ .../bulk-action-menu.constants.ts | 3 ++- .../row-action-menu/row-action-menu.component.ts | 16 ++++++++++++++++ .../row-action-menu/row-action-menu.constants.ts | 3 ++- .../hashtypes-table/hashtypes-table.component.ts | 2 +- src/app/core/_models/hashtype.model.ts | 8 ++++---- 8 files changed, 50 insertions(+), 15 deletions(-) diff --git a/src/app/core/_components/menus/action-menu/action-menu.model.ts b/src/app/core/_components/menus/action-menu/action-menu.model.ts index 9c2c2928..dc0d6ec1 100644 --- a/src/app/core/_components/menus/action-menu/action-menu.model.ts +++ b/src/app/core/_components/menus/action-menu/action-menu.model.ts @@ -1,12 +1,12 @@ export interface ActionMenuEvent { - menuItem: ActionMenuItem - data: T + menuItem: ActionMenuItem; + data: T; } export interface ActionMenuItem { - label: string - action?: string - icon?: string - red?: boolean - routerLink?: any[] -} \ No newline at end of file + label: string; + action?: string; + icon?: string; + red?: boolean; + routerLink?: any[]; +} diff --git a/src/app/core/_components/menus/base-menu/base-menu.component.ts b/src/app/core/_components/menus/base-menu/base-menu.component.ts index f0022efd..169a834e 100644 --- a/src/app/core/_components/menus/base-menu/base-menu.component.ts +++ b/src/app/core/_components/menus/base-menu/base-menu.component.ts @@ -54,6 +54,10 @@ export class BaseMenuComponent { } } + protected isHashtype(): boolean { + return 'hashTypeId' in this.data; + } + onMenuItemClick(event: ActionMenuEvent): void { this.menuItemClicked.emit(event); } diff --git a/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.component.ts b/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.component.ts index 3f683f32..b83f1c56 100644 --- a/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.component.ts +++ b/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.component.ts @@ -29,6 +29,8 @@ export class BulkActionMenuComponent this.getAgentMenu(); } else if (this.dataType === 'hashlists') { this.getHashlistMenu(); + } else if (this.dataType === 'hashtypes') { + this.getHashtypesMenu(); } } @@ -54,6 +56,17 @@ export class BulkActionMenuComponent } } + private getHashtypesMenu(): void { + const deleteMenuAction: ActionMenuItem = { + label: BulkActionMenuLabel.DELETE_HASHTYPES, + action: BulkActionMenuAction.DELETE, + icon: 'delete', + red: true + }; + + this.actionMenuItems[0] = [deleteMenuAction]; + } + private getTaskMenu(): void { this.actionMenuItems[0] = [ { diff --git a/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.constants.ts b/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.constants.ts index d0daf483..e6e27144 100644 --- a/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.constants.ts +++ b/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.constants.ts @@ -5,7 +5,8 @@ export const BulkActionMenuLabel = { ARCHIVE_TASKS: 'Archive Tasks', DELETE_TASKS: 'Delete Tasks', DELETE_HASHLISTS: 'Delete Hashlists', - ARCHIVE_HASHLISTS: 'Archive Hashlists' + ARCHIVE_HASHLISTS: 'Archive Hashlists', + DELETE_HASHTYPES: 'Delete Hashtypes' }; export const BulkActionMenuAction = { diff --git a/src/app/core/_components/menus/row-action-menu/row-action-menu.component.ts b/src/app/core/_components/menus/row-action-menu/row-action-menu.component.ts index d1bfe940..8920a92b 100644 --- a/src/app/core/_components/menus/row-action-menu/row-action-menu.component.ts +++ b/src/app/core/_components/menus/row-action-menu/row-action-menu.component.ts @@ -23,6 +23,8 @@ export class RowActionMenuComponent this.getTaskMenu(); } else if (this.isHashlist()) { this.getHashlistMenu(); + } else if (this.isHashtype()) { + this.getHashtypeMenu(); } } @@ -83,6 +85,7 @@ export class RowActionMenuComponent this.actionMenuItems[1] = [deleteMenuItem]; } } + /** * Get the context menu items for a task data row. */ @@ -129,4 +132,17 @@ export class RowActionMenuComponent icon: 'archive' }); } + + private getHashtypeMenu(): void { + this.actionMenuItems[0] = []; + + const deleteMenuItem: ActionMenuItem = { + label: RowActionMenuLabel.DELETE_HASHTYPE, + action: RowActionMenuAction.DELETE, + icon: 'delete', + red: true + }; + + this.actionMenuItems[0].push(deleteMenuItem); + } } 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 2fbdd98c..e715b680 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 @@ -10,7 +10,8 @@ export const RowActionMenuLabel = { EDIT_HASHLIST: 'Edit', DELETE_HASHLIST: 'Delete', IMPORT_HASHLIST: 'Import Hashlist', - EXPORT_HASHLIST: 'Export Hashlist' + EXPORT_HASHLIST: 'Export Hashlist', + DELETE_HASHTYPE: 'Delete' }; export const RowActionMenuAction = { diff --git a/src/app/core/_components/tables/hashtypes-table/hashtypes-table.component.ts b/src/app/core/_components/tables/hashtypes-table/hashtypes-table.component.ts index 3b4ff4b9..77588316 100644 --- a/src/app/core/_components/tables/hashtypes-table/hashtypes-table.component.ts +++ b/src/app/core/_components/tables/hashtypes-table/hashtypes-table.component.ts @@ -145,7 +145,7 @@ export class HashtypesTableComponent icon: 'warning', body: `Are you sure you want to delete the above hashtypes? Note that this action cannot be undone.`, warn: true, - listAttribute: 'name', + listAttribute: 'description', action: event.menuItem.action }); break; diff --git a/src/app/core/_models/hashtype.model.ts b/src/app/core/_models/hashtype.model.ts index 527c6bd2..bee55f6d 100644 --- a/src/app/core/_models/hashtype.model.ts +++ b/src/app/core/_models/hashtype.model.ts @@ -1,6 +1,6 @@ export interface Hashtype { - hashTypeId: number - description: string - isSalted: boolean - isSlowHash: boolean + hashTypeId: number; + description: string; + isSalted: boolean; + isSlowHash: boolean; } From e826272e7f871ca307ecd80d68547a3ce70a9123 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Mon, 13 Nov 2023 09:08:42 +0100 Subject: [PATCH 247/419] Swallow error on removal of dom element --- src/app/core/_services/export/export.util.ts | 62 ++++++++++++-------- 1 file changed, 36 insertions(+), 26 deletions(-) diff --git a/src/app/core/_services/export/export.util.ts b/src/app/core/_services/export/export.util.ts index 05ffac1f..5c5fa11a 100644 --- a/src/app/core/_services/export/export.util.ts +++ b/src/app/core/_services/export/export.util.ts @@ -1,16 +1,14 @@ -import { Injectable } from "@angular/core"; -import { ExcelColumn } from "./export.model"; -import { HTTableColumn } from "../../_components/tables/ht-table/ht-table.models"; - +import { ExcelColumn } from './export.model'; +import { HTTableColumn } from '../../_components/tables/ht-table/ht-table.models'; +import { Injectable } from '@angular/core'; @Injectable({ - providedIn: 'root', + providedIn: 'root' }) export class ExportUtil { - /** * Converts table columns to Excel columns. - * + * * @param tableColumns The table columns. * @returns Excel columns. */ @@ -18,52 +16,60 @@ export class ExportUtil { return tableColumns.map((col: HTTableColumn) => { return { key: col.dataKey, - header: col.name, - } - }) + header: col.name + }; + }); } /** * Converts table columns to CSV columns. - * + * * @param tableColumns The table columns. * @returns CSV columns. */ toCsvColumns(tableColumns: HTTableColumn[]): string[] { - return tableColumns.map((col: HTTableColumn) => col.name) + return tableColumns.map((col: HTTableColumn) => col.name); } /** * Converts data to rows for Excel export. - * + * * @param tableColumns The table columns. * @param rawData The data to be exported. * @returns Rows for Excel export. */ - async toExcelRows(tableColumns: HTTableColumn[], rawData: T[]): Promise { - let rowNum = 0 - const data: any[] = [] + async toExcelRows( + tableColumns: HTTableColumn[], + rawData: T[] + ): Promise { + let rowNum = 0; + const data: any[] = []; for (const row of rawData) { - data[rowNum] = {} + data[rowNum] = {}; for (const column of tableColumns) { - data[rowNum][column.dataKey] = column.export ? await column.export(row) : '' + data[rowNum][column.dataKey] = column.export + ? await column.export(row) + : ''; } - rowNum += 1 + rowNum += 1; } - return data + return data; } /** * Converts data to rows for CSV export. - * + * * @param tableColumns The table columns. * @param rawData The data to be exported. * @returns Rows for CSV export. */ - async toCsvRows(tableColumns: HTTableColumn[], rawData: T[]): Promise { - const data: string[][] = [] + async toCsvRows( + tableColumns: HTTableColumn[], + rawData: T[] + ): Promise { + const data: string[][] = []; for (const row of rawData) { const rowData: string[] = []; @@ -73,12 +79,12 @@ export class ExportUtil { data.push(rowData); } - return data + return data; } /** * Downloads data as a file. - * + * * @param data The data to download. * @param fileName The name of the file. * @param fileType The type of the file (e.g., 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' for Excel). @@ -92,6 +98,10 @@ export class ExportUtil { a.download = fileName; a.click(); window.URL.revokeObjectURL(url); - document.body.removeChild(a); + try { + document.body.removeChild(a); + } catch (error) { + // Do nothing + } } } From 19c67df4c7d7078e2bfbac49181bcd65f879a621 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Mon, 13 Nov 2023 09:09:12 +0100 Subject: [PATCH 248/419] Remove debug print + format --- src/app/layout/header/header.component.ts | 207 +++++++++++----------- 1 file changed, 107 insertions(+), 100 deletions(-) diff --git a/src/app/layout/header/header.component.ts b/src/app/layout/header/header.component.ts index e41a158c..8ce40be9 100644 --- a/src/app/layout/header/header.component.ts +++ b/src/app/layout/header/header.component.ts @@ -1,36 +1,37 @@ import { Component, OnDestroy, OnInit } from '@angular/core'; -import { environment } from './../../../environments/environment'; -import { Subscription } from 'rxjs'; -import { AuthService } from '../../core/_services/access/auth.service'; -import { MainMenuItem } from './header.model'; -import { User, UserData } from 'src/app/core/_models/auth-user.model'; -import { ActionMenuEvent } from 'src/app/core/_components/menus/action-menu/action-menu.model'; import { HeaderMenuAction, HeaderMenuLabel } from './header.constants'; +import { ActionMenuEvent } from 'src/app/core/_components/menus/action-menu/action-menu.model'; +import { AuthService } from '../../core/_services/access/auth.service'; +import { MainMenuItem } from './header.model'; +import { Subscription } from 'rxjs'; +import { User } from 'src/app/core/_models/auth-user.model'; +import { environment } from './../../../environments/environment'; @Component({ selector: 'app-header', templateUrl: './header.component.html' }) export class HeaderComponent implements OnInit, OnDestroy { - private subscriptions: Subscription[] = [] - private username = '' + private subscriptions: Subscription[] = []; + private username = ''; headerConfig = environment.config.header; - mainMenu: MainMenuItem[] = [] + mainMenu: MainMenuItem[] = []; constructor(private authService: AuthService) { - this.rebuildMenu() + this.rebuildMenu(); } ngOnInit(): void { - this.subscriptions.push(this.authService.user.subscribe((user: User) => { - console.log(user) - if (user) { - this.username = user._username - } - this.rebuildMenu() - })) + this.subscriptions.push( + this.authService.user.subscribe((user: User) => { + if (user) { + this.username = user._username; + } + this.rebuildMenu(); + }) + ); } ngOnDestroy(): void { @@ -42,7 +43,7 @@ export class HeaderComponent implements OnInit, OnDestroy { menuItemClicked(event: ActionMenuEvent): void { if (event.menuItem.action === HeaderMenuAction.LOGOUT) { this.authService.logOut(); - this.rebuildMenu() + this.rebuildMenu(); window.location.reload(); } } @@ -57,7 +58,7 @@ export class HeaderComponent implements OnInit, OnDestroy { this.getConfigMenu(), this.getUsersMenu(), this.getAdminMenu() - ] + ]; } /** @@ -68,17 +69,19 @@ export class HeaderComponent implements OnInit, OnDestroy { return { display: true, label: HeaderMenuLabel.AGENTS, - actions: [[ - { - label: HeaderMenuLabel.SHOW_AGENTS, - routerLink: ['agents', 'show-agents'] - }, - { - label: HeaderMenuLabel.AGENT_STATUS, - routerLink: ['agents', 'agent-status'] - } - ]] - } + actions: [ + [ + { + label: HeaderMenuLabel.SHOW_AGENTS, + routerLink: ['agents', 'show-agents'] + }, + { + label: HeaderMenuLabel.AGENT_STATUS, + routerLink: ['agents', 'agent-status'] + } + ] + ] + }; } /** @@ -89,29 +92,31 @@ export class HeaderComponent implements OnInit, OnDestroy { return { display: true, label: HeaderMenuLabel.TASKS, - actions: [[ - { - label: HeaderMenuLabel.SHOW_TASKS, - routerLink: ['tasks', 'show-tasks'] - }, - { - label: HeaderMenuLabel.PRECONFIGURED_TASKS, - routerLink: ['tasks', 'preconfigured-tasks'] - }, - { - label: HeaderMenuLabel.SUPERTASKS, - routerLink: ['tasks', 'supertasks'] - }, - { - label: HeaderMenuLabel.IMPORT_SUPERTASK, - routerLink: ['tasks', 'import-supertasks', 'masks'] - }, - { - label: HeaderMenuLabel.CHUNK_ACTIVITY, - routerLink: ['tasks', 'chunks'] - }, - ]] - } + actions: [ + [ + { + label: HeaderMenuLabel.SHOW_TASKS, + routerLink: ['tasks', 'show-tasks'] + }, + { + label: HeaderMenuLabel.PRECONFIGURED_TASKS, + routerLink: ['tasks', 'preconfigured-tasks'] + }, + { + label: HeaderMenuLabel.SUPERTASKS, + routerLink: ['tasks', 'supertasks'] + }, + { + label: HeaderMenuLabel.IMPORT_SUPERTASK, + routerLink: ['tasks', 'import-supertasks', 'masks'] + }, + { + label: HeaderMenuLabel.CHUNK_ACTIVITY, + routerLink: ['tasks', 'chunks'] + } + ] + ] + }; } /** @@ -122,25 +127,27 @@ export class HeaderComponent implements OnInit, OnDestroy { return { display: true, label: HeaderMenuLabel.HASHLISTS, - actions: [[ - { - label: HeaderMenuLabel.SHOW_HASHLISTS, - routerLink: ['hashlists', 'hashlist'] - }, - { - label: HeaderMenuLabel.SUPERHASHLISTS, - routerLink: ['hashlists', 'superhashlist'] - }, - { - label: HeaderMenuLabel.SEARCH_HASH, - routerLink: ['hashlists', 'search-hash'] - }, - { - label: HeaderMenuLabel.SHOW_CRACKS, - routerLink: ['hashlists', 'show-cracks'] - } - ]] - } + actions: [ + [ + { + label: HeaderMenuLabel.SHOW_HASHLISTS, + routerLink: ['hashlists', 'hashlist'] + }, + { + label: HeaderMenuLabel.SUPERHASHLISTS, + routerLink: ['hashlists', 'superhashlist'] + }, + { + label: HeaderMenuLabel.SEARCH_HASH, + routerLink: ['hashlists', 'search-hash'] + }, + { + label: HeaderMenuLabel.SHOW_CRACKS, + routerLink: ['hashlists', 'show-cracks'] + } + ] + ] + }; } /** @@ -151,21 +158,23 @@ export class HeaderComponent implements OnInit, OnDestroy { return { display: true, label: HeaderMenuLabel.FILES, - actions: [[ - { - label: HeaderMenuLabel.WORDLISTS, - routerLink: ['files', 'wordlist'] - }, - { - label: HeaderMenuLabel.RULES, - routerLink: ['files', 'rules'] - }, - { - label: HeaderMenuLabel.OTHER, - routerLink: ['files', 'other'] - }, - ]] - } + actions: [ + [ + { + label: HeaderMenuLabel.WORDLISTS, + routerLink: ['files', 'wordlist'] + }, + { + label: HeaderMenuLabel.RULES, + routerLink: ['files', 'rules'] + }, + { + label: HeaderMenuLabel.OTHER, + routerLink: ['files', 'other'] + } + ] + ] + }; } /** @@ -193,15 +202,17 @@ export class HeaderComponent implements OnInit, OnDestroy { }, { label: 'Support', - routerLink: ['https://discord.com/channels/419123475538509844/419123475538509846'] - }, + routerLink: [ + 'https://discord.com/channels/419123475538509844/419123475538509846' + ] + } ], [ { label: 'Logout', action: HeaderMenuAction.LOGOUT, red: true - }, + } ] ] }; @@ -228,16 +239,16 @@ export class HeaderComponent implements OnInit, OnDestroy { { label: HeaderMenuLabel.ACCESS_GROUPS, routerLink: ['users', 'access-groups'] - }, + } ] ] }; } /** - * Retrieves the 'Config' menu item. - * @returns A MainMenuItem for the 'Config' menu. - */ + * Retrieves the 'Config' menu item. + * @returns A MainMenuItem for the 'Config' menu. + */ getConfigMenu(): MainMenuItem { return { display: true, @@ -259,7 +270,7 @@ export class HeaderComponent implements OnInit, OnDestroy { { label: HeaderMenuLabel.LOG, routerLink: ['config', 'log'] - }, + } ] ] }; @@ -286,13 +297,9 @@ export class HeaderComponent implements OnInit, OnDestroy { { label: HeaderMenuLabel.AGENT_BINARIES, routerLink: ['config', 'engine', 'agent-binaries'] - }, + } ] ] }; } } - - - - From 333957b95a5b12c0433e8f146dd1523ef20b3bc4 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Mon, 13 Nov 2023 09:09:33 +0100 Subject: [PATCH 249/419] Add superhashlist table --- .../_components/core-components.module.ts | 7 +- .../menus/base-menu/base-menu.component.ts | 22 +- .../row-action-menu.component.ts | 23 ++ .../row-action-menu.constants.ts | 4 +- .../tables/ht-table/ht-table.component.html | 2 +- .../tables/ht-table/ht-table.component.ts | 3 + .../tables/ht-table/ht-table.models.ts | 7 +- .../super-hashlists-table.component.html | 17 + .../super-hashlists-table.component.ts | 367 ++++++++++++++++++ .../super-hashlists-table.constants.ts | 9 + .../super-hashlists.datasource.ts | 55 +++ src/app/core/_models/config-ui.model.ts | 8 + src/app/core/_models/hashlist.model.ts | 1 + .../superhashlist.component.html | 79 +--- .../superhashlist/superhashlist.component.ts | 161 +------- 15 files changed, 531 insertions(+), 234 deletions(-) create mode 100644 src/app/core/_components/tables/super-hashlists-table/super-hashlists-table.component.html create mode 100644 src/app/core/_components/tables/super-hashlists-table/super-hashlists-table.component.ts create mode 100644 src/app/core/_components/tables/super-hashlists-table/super-hashlists-table.constants.ts create mode 100644 src/app/core/_datasources/super-hashlists.datasource.ts diff --git a/src/app/core/_components/core-components.module.ts b/src/app/core/_components/core-components.module.ts index 9a9be9e4..8902d0a2 100644 --- a/src/app/core/_components/core-components.module.ts +++ b/src/app/core/_components/core-components.module.ts @@ -35,6 +35,7 @@ import { MatTooltipModule } from '@angular/material/tooltip'; import { NgModule } from '@angular/core'; import { RouterModule } from '@angular/router'; import { RowActionMenuComponent } from './menus/row-action-menu/row-action-menu.component'; +import { SuperHashlistsTableComponent } from './tables/super-hashlists-table/super-hashlists-table.component'; import { TableDialogComponent } from './tables/table-dialog/table-dialog.component'; @NgModule({ @@ -51,7 +52,8 @@ import { TableDialogComponent } from './tables/table-dialog/table-dialog.compone AgentsTableComponent, ChunksTableComponent, HashtypesTableComponent, - HashlistsTableComponent + HashlistsTableComponent, + SuperHashlistsTableComponent ], imports: [ ReactiveFormsModule, @@ -89,7 +91,8 @@ import { TableDialogComponent } from './tables/table-dialog/table-dialog.compone AgentsTableComponent, ChunksTableComponent, HashlistsTableComponent, - HashtypesTableComponent + HashtypesTableComponent, + SuperHashlistsTableComponent ], providers: [ { diff --git a/src/app/core/_components/menus/base-menu/base-menu.component.ts b/src/app/core/_components/menus/base-menu/base-menu.component.ts index 169a834e..94d6282e 100644 --- a/src/app/core/_components/menus/base-menu/base-menu.component.ts +++ b/src/app/core/_components/menus/base-menu/base-menu.component.ts @@ -5,6 +5,8 @@ import { /* eslint-disable @angular-eslint/component-selector */ import { Component, EventEmitter, Input, Output } from '@angular/core'; +import { HashListFormat } from 'src/app/core/_constants/hashlist.config'; + @Component({ selector: 'base-menu', template: '' @@ -48,7 +50,25 @@ export class BaseMenuComponent { */ protected isHashlist(): boolean { try { - return this.data['_id'] === this.data['hashlistId']; + return ( + this.data['_id'] === this.data['hashlistId'] && + this.data['format'] !== HashListFormat.SUPERHASHLIST + ); + } catch (error) { + return false; + } + } + + /** + * Check if the data row is of type "Hashlist" with format "Superhashlist" + * @returns `true` if the data row is a superhashlist; otherwise, `false`. + */ + protected isSuperHashlist(): boolean { + try { + return ( + this.data['_id'] === this.data['hashlistId'] && + this.data['format'] === HashListFormat.SUPERHASHLIST + ); } catch (error) { return false; } diff --git a/src/app/core/_components/menus/row-action-menu/row-action-menu.component.ts b/src/app/core/_components/menus/row-action-menu/row-action-menu.component.ts index 8920a92b..63934bf5 100644 --- a/src/app/core/_components/menus/row-action-menu/row-action-menu.component.ts +++ b/src/app/core/_components/menus/row-action-menu/row-action-menu.component.ts @@ -23,6 +23,8 @@ export class RowActionMenuComponent this.getTaskMenu(); } else if (this.isHashlist()) { this.getHashlistMenu(); + } else if (this.isSuperHashlist()) { + this.getSuperHashlistMenu(); } else if (this.isHashtype()) { this.getHashtypeMenu(); } @@ -86,6 +88,27 @@ export class RowActionMenuComponent } } + /** + * Get the context menu items for an agent data row. + */ + private getSuperHashlistMenu(): void { + this.actionMenuItems[0] = [ + { + label: RowActionMenuLabel.EDIT_SUPERHASHLIST, + action: RowActionMenuAction.EDIT, + icon: 'edit' + } + ]; + this.actionMenuItems[1] = [ + { + label: RowActionMenuLabel.DELETE_SUPERHASHLIST, + action: RowActionMenuAction.DELETE, + icon: 'delete', + red: true + } + ]; + } + /** * Get the context menu items for a task data row. */ 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 e715b680..c7fbad0d 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 @@ -11,7 +11,9 @@ export const RowActionMenuLabel = { DELETE_HASHLIST: 'Delete', IMPORT_HASHLIST: 'Import Hashlist', EXPORT_HASHLIST: 'Export Hashlist', - DELETE_HASHTYPE: 'Delete' + DELETE_HASHTYPE: 'Delete', + EDIT_SUPERHASHLIST: 'Edit', + DELETE_SUPERHASHLIST: 'Delete' }; export const RowActionMenuAction = { 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 3de5dff1..fdeb9a72 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 @@ -9,7 +9,7 @@ -
+
- - - - - - - - - - - - - - - - -
- -
- - -
-
- -
- -
-
Graphical Feedback
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- -
Benchmark / Chunk
- -
- - - - - - - - -
- -
- - -
-
- -
- -
-
Command Line & Misc.
- -
- - - - - - - - -
- -
- - -
-
- -
- -
-
Rule splitting
- -
-
- -
- - -
-
- -
- - -
-
- -
- - - -
Other
-
- -
- - - - - -
- -
- -
Import/Display of Hashlist
- -
- - - - - - - - - - - - - - - - -
- -
- -
Database Parameters
- -
- - - - - - - - - - - Such change may take a long time depending on the database size - - - - - - -
- -
- - -
- - - - - - - - - - - - -
- -
- -
- -
- - - - - - - - - - - - - - - -
- -
- - -
-
- -
- -
-
Hashcat Brain Server Parameters
- -
- - - - - - - - - - -
- - - - - -
- - - - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - - diff --git a/src/app/config/server/server.component.ts b/src/app/config/server/server.component.ts deleted file mode 100644 index d8b5a252..00000000 --- a/src/app/config/server/server.component.ts +++ /dev/null @@ -1,369 +0,0 @@ -import { faHomeAlt, faInfoCircle, faExclamationTriangle } from '@fortawesome/free-solid-svg-icons'; -import { environment } from './../../../environments/environment'; -import { FormControl, FormGroup } from '@angular/forms'; -import Swal from 'sweetalert2/dist/sweetalert2.js'; -import { Component, OnInit } from '@angular/core'; -import { ActivatedRoute } from '@angular/router'; -import { Store } from '@ngrx/store'; - -import { UIConfigService } from 'src/app/core/_services/shared/storage.service'; -import { TooltipService } from '../../core/_services/shared/tooltip.service'; -import { serverlog, proxytype } from '../../core/_constants/settings.config'; -import { CookieService } from '../../core/_services/shared/cookies.service'; -import { AlertService } from 'src/app/core/_services/shared/alert.service'; -import { GlobalService } from 'src/app/core/_services/main.service'; -import { PageTitle } from 'src/app/core/_decorators/autotitle'; -import { SERV } from '../../core/_services/main.config'; - -@Component({ - selector: 'app-server', - templateUrl: './server.component.html' -}) -@PageTitle(['Settings']) -export class ServerComponent implements OnInit { - - faHome=faHomeAlt; - faInfoCircle=faInfoCircle; - faExclamationTriangle=faExclamationTriangle; - - whichView: string; - - constructor( - private tooltipService: TooltipService, - private cookieService: CookieService, - private uicService: UIConfigService, - private route:ActivatedRoute, - private alert: AlertService, - private gs: GlobalService, - private store: Store<{configList: {}}> - ) { } - - private maxResults = environment.config.prodApiMaxResults; - - public config: {configId: number, configSectionId: number, item: string, value: number}[] = []; - - agentForm: FormGroup; - tcForm: FormGroup; - hchForm: FormGroup; - notifForm: FormGroup; - gsForm: FormGroup; - taskcookieForm: FormGroup; - cookieForm: FormGroup; - serverlog = serverlog; - proxytype = proxytype; - - // Tooltips - atip: any =[] - tctip: any =[] - hchtip: any =[] - notiftip: any =[] - gstip: any =[] - - ngOnInit(): void { - - this.store.select('configList'); - - this.route.data.subscribe(data => { - switch (data['kind']) { - - case 'agent': - this.whichView = 'agent'; - this.agentForm = new FormGroup({ - 'agenttimeout': new FormControl(), - 'benchtime': new FormControl(), - 'statustimer': new FormControl(), - 'agentDataLifetime': new FormControl(), - 'hideIpInfo': new FormControl(), - 'voucherDeletion': new FormControl(), - 'agentStatLimit': new FormControl(), - 'agentStatTension': new FormControl(), - 'agentTempThreshold1': new FormControl(), - 'agentTempThreshold2': new FormControl(), - 'agentUtilThreshold1': new FormControl(), - 'agentUtilThreshold2': new FormControl(), - }); - this.initAgentForm(); - this.atip = this.tooltipService.getConfigTooltips().agent; - break; - - case 'task-chunk': - this.whichView = 'task-chunk'; - this.tcForm = new FormGroup({ - 'chunktime': new FormControl(), - 'disptolerance': new FormControl(), - 'defaultBenchmark': new FormControl(), - 'disableTrimming': new FormControl(), - 'hashlistAlias': new FormControl(), - 'blacklistChars': new FormControl(), - 'priority0Start': new FormControl(), - 'showTaskPerformance': new FormControl(), - 'ruleSplitSmallTasks': new FormControl(), - 'ruleSplitAlways': new FormControl(), - 'ruleSplitDisable': new FormControl() - }); - this.taskcookieForm = new FormGroup({ - 'autorefresh': new FormControl(), - }); - this.tctip = this.tooltipService.getConfigTooltips().tc; - this.initTCForm(); - break; - - case 'hch': - this.whichView = 'hch'; - this.hchForm = new FormGroup({ - 'maxHashlistSize': new FormControl(), - 'pagingSize': new FormControl(), - 'hashesPerPage': new FormControl(), - 'fieldseparator': new FormControl(), - 'hashlistImportCheck': new FormControl(), - 'batchSize': new FormControl(), - 'plainTextMaxLength': new FormControl(), - 'hashMaxLength': new FormControl(), - }); - this.hchtip = this.tooltipService.getConfigTooltips().hch; - this.initHCHForm(); - break; - - case 'notif': - this.whichView = 'notif'; - this.notifForm = new FormGroup({ - 'emailSender': new FormControl(), - 'emailSenderName': new FormControl(), - 'telegramBotToken': new FormControl(), - 'notificationsProxyEnable': new FormControl(), - 'notificationsProxyServer': new FormControl(), - 'notificationsProxyPort': new FormControl(), - 'notificationsProxyType': new FormControl(), - }); - this.notiftip = this.tooltipService.getConfigTooltips().notif; - this.initNotifForm(); - break; - - case 'gs': - this.whichView = 'gs'; - this.gsForm = new FormGroup({ - 'hashcatBrainEnable': new FormControl(), - 'hashcatBrainHost': new FormControl(), - 'hashcatBrainPort': new FormControl(), - 'hashcatBrainPass': new FormControl(), - 'hcErrorIgnore': new FormControl(), - 'numLogEntries': new FormControl(), - 'timefmt': new FormControl(), - 'maxSessionLength': new FormControl(), - 'baseHost': new FormControl(), - 'contactEmail': new FormControl(), - 'serverLogLevel': new FormControl(), - }); - this.cookieForm = new FormGroup({ - 'cookieTooltip': new FormControl(), - }); - this.gstip = this.tooltipService.getConfigTooltips().gs; - this.initGSForm(); - break; - - - } - - }); - - } - - private initAgentForm() { - this.getTooltipLevel() - const params = {'maxResults': this.maxResults} - this.gs.getAll(SERV.CONFIGS,params).subscribe((result)=>{ - this.agentForm = new FormGroup({ - 'agenttimeout': new FormControl(result.values.find(obj => obj.item === 'agenttimeout').value), - 'benchtime': new FormControl(result.values.find(obj => obj.item === 'benchtime').value), - 'statustimer': new FormControl(result.values.find(obj => obj.item === 'statustimer').value), - 'agentDataLifetime': new FormControl(result.values.find(obj => obj.item === 'agentDataLifetime').value), - 'hideIpInfo': new FormControl(result.values.find(obj => obj.item === 'hideIpInfo').value === '0' ? false: true), - 'voucherDeletion': new FormControl((result.values.find(obj => obj.item === 'voucherDeletion').value) === '0' ? false: true), - 'agentStatLimit': new FormControl(result.values.find(obj => obj.item === 'agentStatLimit').value), - 'agentStatTension': new FormControl(Number(result.values.find(obj => obj.item === 'agentStatTension').value)), - 'agentTempThreshold1': new FormControl(result.values.find(obj => obj.item === 'agentTempThreshold1').value), - 'agentTempThreshold2': new FormControl(result.values.find(obj => obj.item === 'agentTempThreshold2').value), - 'agentUtilThreshold1': new FormControl(result.values.find(obj => obj.item === 'agentUtilThreshold1').value), - 'agentUtilThreshold2': new FormControl(result.values.find(obj => obj.item === 'agentUtilThreshold2').value), - }); - }); - } - - modelAgentActivity = [ - { - type: "number", - formcontrol: "agenttimeout", - label: "Delay before considering an agent as inactive(or timed out)", - }, - { - type: "number", - formcontrol: "benchtime", - label: "Delay before considering an issued chunk as inactive", - }, - { - type: "number", - formcontrol: "statustimer", - label: "Frequency of the agent reporting about a task to the server", - }, - { - type: "number", - formcontrol: "agentDataLifetime", - label: "Time during which util and temperature data are retained on the server", - }, - { - type: "checkbox", - formcontrol: "hideIpInfo", - label: "Hide agents IP information", - }, - { - type: "checkbox", - formcontrol: "voucherDeletion", - label: "Voucher(s) can be used to register multiple agents", - } - ]; - - - // sectiontwo: [ - // { - // type: "number", - // formcontrol: "agentStatLimit", - // label: "Maximum number of data points in agent (gpu) graphs", - // }, - // { - // type: "number", - // formcontrol: "agentStatTension", - // label: "Draw straigth lines in agent data graph instead of bezier curves", - // }, - // ] - - private initTCForm() { - const params = {'maxResults': this.maxResults} - this.gs.getAll(SERV.CONFIGS,params).subscribe((result)=>{ - this.tcForm = new FormGroup({ - 'chunktime': new FormControl(result.values.find(obj => obj.item === 'chunktime').value), - 'disptolerance': new FormControl(result.values.find(obj => obj.item === 'disptolerance').value), - 'defaultBenchmark': new FormControl(result.values.find(obj => obj.item === 'defaultBenchmark').value === '0' ? false: true), - 'disableTrimming': new FormControl(result.values.find(obj => obj.item === 'disableTrimming').value === '0' ? false: true), - 'hashlistAlias': new FormControl(result.values.find(obj => obj.item === 'hashlistAlias').value ), - 'blacklistChars': new FormControl(result.values.find(obj => obj.item === 'blacklistChars').value), - 'priority0Start': new FormControl(result.values.find(obj => obj.item === 'priority0Start').value === '0' ? false: true), - 'showTaskPerformance': new FormControl(result.values.find(obj => obj.item === 'showTaskPerformance').value === '0' ? false: true), - 'ruleSplitSmallTasks': new FormControl(result.values.find(obj => obj.item === 'ruleSplitSmallTasks').value === '0' ? false: true), - 'ruleSplitAlways': new FormControl(result.values.find(obj => obj.item === 'ruleSplitAlways').value === '0' ? false: true), - 'ruleSplitDisable': new FormControl((result.values.find(obj => obj.item === 'ruleSplitDisable').value) === '0' ? false: true) - }); - this.taskcookieForm = new FormGroup({ - 'autorefresh': new FormControl(this.getAutorefresh()), - }); - }); - } - - private initHCHForm() { - const params = {'maxResults': this.maxResults} - this.gs.getAll(SERV.CONFIGS,params).subscribe((result)=>{ - this.hchForm = new FormGroup({ - 'maxHashlistSize': new FormControl(result.values.find(obj => obj.item === 'maxHashlistSize').value), - 'pagingSize': new FormControl(result.values.find(obj => obj.item === 'pagingSize').value), - 'hashesPerPage': new FormControl(result.values.find(obj => obj.item === 'hashesPerPage').value), - 'fieldseparator': new FormControl(result.values.find(obj => obj.item === 'fieldseparator').value), - 'hashlistImportCheck': new FormControl(result.values.find(obj => obj.item === 'hashlistImportCheck').value), - 'batchSize': new FormControl(result.values.find(obj => obj.item === 'batchSize').value), - 'plainTextMaxLength': new FormControl(result.values.find(obj => obj.item === 'plainTextMaxLength').value), - 'hashMaxLength': new FormControl(result.values.find(obj => obj.item === 'hashMaxLength').value), - }); - }); - } - - private initNotifForm() { - const params = {'maxResults': this.maxResults} - this.gs.getAll(SERV.CONFIGS,params).subscribe((result)=>{ - this.notifForm = new FormGroup({ - 'emailSender': new FormControl(result.values.find(obj => obj.item === 'emailSender').value), - 'emailSenderName': new FormControl(result.values.find(obj => obj.item === 'emailSenderName').value), - 'telegramBotToken': new FormControl(result.values.find(obj => obj.item === 'telegramBotToken').value), - 'notificationsProxyEnable': new FormControl(result.values.find(obj => obj.item === 'notificationsProxyEnable').value === '0' ? false: true), - 'notificationsProxyServer': new FormControl(result.values.find(obj => obj.item === 'notificationsProxyServer').value), - 'notificationsProxyPort': new FormControl(result.values.find(obj => obj.item === 'notificationsProxyPort').value), - 'notificationsProxyType': new FormControl(result.values.find(obj => obj.item === 'notificationsProxyType').value), - }); - }); - } - - private initGSForm() { - const params = {'maxResults': this.maxResults}; - this.gs.getAll(SERV.CONFIGS,params).subscribe((result)=>{ - this.gsForm = new FormGroup({ - 'hashcatBrainEnable': new FormControl(result.values.find(obj => obj.item === 'hashcatBrainEnable').value === '0' ? false: true), - 'hashcatBrainHost': new FormControl(result.values.find(obj => obj.item === 'hashcatBrainHost').value), - 'hashcatBrainPort': new FormControl(result.values.find(obj => obj.item === 'hashcatBrainPort').value), - 'hashcatBrainPass': new FormControl(result.values.find(obj => obj.item === 'hashcatBrainPass').value), - 'hcErrorIgnore': new FormControl(result.values.find(obj => obj.item === 'hcErrorIgnore').value), - 'numLogEntries': new FormControl(result.values.find(obj => obj.item === 'numLogEntries').value), - 'timefmt': new FormControl(result.values.find(obj => obj.item === 'timefmt').value), - 'maxSessionLength': new FormControl(result.values.find(obj => obj.item === 'maxSessionLength').value), - 'baseHost': new FormControl(result.values.find(obj => obj.item === 'baseHost').value), - 'contactEmail': new FormControl(result.values.find(obj => obj.item === 'contactEmail').value), - 'serverLogLevel': new FormControl(result.values.find(obj => obj.item === 'serverLogLevel').value), - }); - this.cookieForm = new FormGroup({ - 'cookieTooltip': new FormControl(this.getTooltipLevel()), - }); - }); - } - - // Auto Save Settings - - searchTxt = ''; - timeout = null; - - autoSave(key: string, value: any, sw?: boolean, collap?: boolean){ - clearTimeout(this.timeout); - this.timeout = setTimeout(() => { - const params = {'filter=item': key}; - this.gs.getAll(SERV.CONFIGS,params).subscribe((result)=>{ - const indexUpdate = result.values.find(obj => obj.item === key).configId; - const valueUpdate = result.values.find(obj => obj.item === key).value; - const arr = {'item': key, 'value': this.checkSwitch(value, valueUpdate, sw)}; - this.gs.update(SERV.CONFIGS,indexUpdate, arr).subscribe((result)=>{ - this.uicService.onUpdatingCheck(key); - if(collap !== true){ - this.alert.okAlert('Saved',''); - this.ngOnInit(); - } - }); - }); - }, 1500); - } - - checkSwitch(value: any, ovalue: any, sw?: boolean){ - if(sw == true && ovalue === '1'){ - return '0'; - }if(sw == true && ovalue === '0') { - return '1'; - }else { - return value; - } - } - - getAutorefresh(){ - return JSON.parse(this.cookieService.getCookie('autorefresh')).value; - } - - setAutorefresh(value: string){ - this.cookieService.setCookie('autorefresh', JSON.stringify({active:true, value: value}), 365); - this.alert.okAlert('Saved',''); - this.ngOnInit(); - } - - getTooltipLevel(){ - return this.cookieService.getCookie('tooltip'); - } - - setTooltipLevel(value: string){ - this.cookieService.setCookie('tooltip', value, 365); - this.alert.okAlert('Saved',''); - this.ngOnInit(); - } - -} - diff --git a/src/app/config/server/settings-menu.ts b/src/app/config/server/settings-menu.ts deleted file mode 100644 index d8249154..00000000 --- a/src/app/config/server/settings-menu.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { Component, Input } from '@angular/core'; -import { Router } from '@angular/router'; - -@Component({ - selector: 'settings-menu', - template: ` - - ` -}) -export class SettingsMenuComponent { - - @Input() aclass?: any; - @Input() tclass?: any; - @Input() hchclass?: any; - @Input() nclass?: any; - @Input() gclass?: any; - - constructor( - private router: Router - ) { } - - Agent(){ - this.router.navigate(['/config/agent']); - } - - Task(){ - this.router.navigate(['/config/task-chunk']); - } - - Hch(){ - this.router.navigate(['/config/hch']); - } - - Notif(){ - this.router.navigate(['/config/notifications']); - } - - General(){ - this.router.navigate(['/config/general-settings']); - } - - -} diff --git a/src/app/core/_services/metadata.service.ts b/src/app/core/_services/metadata.service.ts index 8de2722b..8cbef903 100644 --- a/src/app/core/_services/metadata.service.ts +++ b/src/app/core/_services/metadata.service.ts @@ -1,5 +1,12 @@ -import { dateFormats, serverlog, proxytype } from '../../core/_constants/settings.config'; -import { ACTIONARRAY, NOTIFARRAY } from '../../core/_constants/notifications.config'; +import { + dateFormats, + proxytype, + serverlog +} from '../../core/_constants/settings.config'; +import { + ACTIONARRAY, + NOTIFARRAY +} from '../../core/_constants/notifications.config'; import { fileFormat } from '../../core/_constants/files.config'; import { TooltipService } from '../../core/_services/shared/tooltip.service'; import { environment } from 'src/environments/environment'; @@ -10,20 +17,18 @@ import { HttpClient } from '@angular/common/http'; import { GlobalService } from './main.service'; import { Injectable } from '@angular/core'; - @Injectable({ providedIn: 'root' }) export class MetadataService { - - tooltip:any; + tooltip: any; constructor( private tooltipService: TooltipService, private gs: GlobalService - ) { - this.tooltip = this.tooltipService.getConfigTooltips(); - } + ) { + this.tooltip = this.tooltipService.getConfigTooltips(); + } private maxResults = environment.config.prodApiMaxResults; @@ -36,33 +41,35 @@ export class MetadataService { // Info Metadata, it contains information about the page such as title, subtitles, and notifications configuration. infoMetadataForm = { - title: "Title for the form page", + title: 'Title for the form page', customform: false, subtitle: false, - submitok: "Message displayed upon successful submission", - submitokredirect: "Redirect URL upon successful submission", - deltitle: "Title for deletion confirmation", - delsubmitok: "Message displayed upon successful deletion", - delsubmitokredirect: "Redirect URL upon successful deletion", - delsubmitcancel: "Message displayed when deletion is canceled", + submitok: 'Message displayed upon successful submission', + submitokredirect: 'Redirect URL upon successful submission', + deltitle: 'Title for deletion confirmation', + delsubmitok: 'Message displayed upon successful deletion', + delsubmitokredirect: 'Redirect URL upon successful deletion', + delsubmitcancel: 'Message displayed when deletion is canceled' }; // Metadata form, it contains information about each field. metadataFormField = [ { - name: "API name to be map with the formControl", - label: "Label name to be displayed", - type: "Type of the form field; (e.g., select, text, checkbox)", - placeholder: "Type option text, then add placeholder", + name: 'API name to be map with the formControl', + label: 'Label name to be displayed', + type: 'Type of the form field; (e.g., select, text, checkbox)', + placeholder: 'Type option text, then add placeholder', selectOptions: "Select options if the type is 'select'", - selectOptions$: "Select options if the type is 'selectd', used with selectEndpoint", - selectEndpoint$: "API endpoint route, use SERV", - fieldMapping: "Object with the dropdown options to be mapped, that is id and name. ie. id: _id, name:groupName", - requiredasterisk: "Indicates if the field is required", - tooltip: "Tooltip information as string or using ", - validators: "Validation rules", - isTitle: "boolean, if its true will use only the label field" - }, + selectOptions$: + "Select options if the type is 'selectd', used with selectEndpoint", + selectEndpoint$: 'API endpoint route, use SERV', + fieldMapping: + 'Object with the dropdown options to be mapped, that is id and name. ie. id: _id, name:groupName', + requiredasterisk: 'Indicates if the field is required', + tooltip: 'Tooltip information as string or using ', + validators: 'Validation rules', + isTitle: 'boolean, if its true will use only the label field' + } ]; // Examples @@ -78,32 +85,63 @@ export class MetadataService { // This variable stores information about the edit notification page. newnotifInfo = [ - { title: 'New Notification', customform: false, subtitle: false, submitok: 'New Notification created!', submitokredirect: '/account/notifications'}, + { + title: 'New Notification', + customform: false, + subtitle: false, + submitok: 'New Notification created!', + submitokredirect: '/account/notifications' + } ]; // This variable stores information about the edit notification page. editnotifInfo = [ - { title: 'Edit Notification', customform: false, subtitle: false, submitok: 'Saved!', submitokredirect: '/account/notifications'}, + { + title: 'Edit Notification', + customform: false, + subtitle: false, + submitok: 'Saved!', + submitokredirect: '/account/notifications' + } ]; //This variable defines the fields and properties required when creating a cracker Version. newnotif = [ { name: 'action', type: 'seltextect', selectOptions: ACTIONARRAY }, - { name: 'actionFilter', label: 'Value', type: 'text'}, - { name: 'notification', label: 'Notification', type: 'select', selectOptions: NOTIFARRAY}, - { name: 'receiver', label: 'Receiver', type: 'text'}, - { name: 'isActive', label: 'Receiver', type: 'checkbox',defaultValue: true}, + { name: 'actionFilter', label: 'Value', type: 'text' }, + { + name: 'notification', + label: 'Notification', + type: 'select', + selectOptions: NOTIFARRAY + }, + { name: 'receiver', label: 'Receiver', type: 'text' }, + { + name: 'isActive', + label: 'Receiver', + type: 'checkbox', + defaultValue: true + } ]; //This variable defines the fields and properties required when editing a notification. editnotif = [ - { name: 'action', type: 'text', disabled: true}, - { name: 'notification', label: 'Notification', type: 'text', disabled: true}, - { name: 'receiver', label: 'Receiver', type: 'text', disabled: true}, - { name: 'isActive', label: 'Receiver', type: 'checkbox', validators: [Validators.required]}, + { name: 'action', type: 'text', disabled: true }, + { + name: 'notification', + label: 'Notification', + type: 'text', + disabled: true + }, + { name: 'receiver', label: 'Receiver', type: 'text', disabled: true }, + { + name: 'isActive', + label: 'Receiver', + type: 'checkbox', + validators: [Validators.required] + } ]; - // // // // // // // // // TASKS SECTION // // // // // // // // // @@ -112,18 +150,38 @@ export class MetadataService { // Pretask // // - // // // Supertask // // supertaskInfo = [ - { title: 'New Supertask', customform: false, subtitle: false, submitok: 'New SuperTask created!', submitokredirect: 'tasks/supertasks'}, + { + title: 'New Supertask', + customform: false, + subtitle: false, + submitok: 'New SuperTask created!', + submitokredirect: 'tasks/supertasks' + } ]; supertask = [ - { name: 'supertaskName', label: 'Name', type: 'text', requiredasterisk: true, tooltip: false, validators: [Validators.required] }, - { name: 'pretasks', label: 'Select or search tasks assigned to this supertask:', type: 'select', selectOptions: [], requiredasterisk: true, tooltip: false, validators: [Validators.required]}, + { + name: 'supertaskName', + label: 'Name', + type: 'text', + requiredasterisk: true, + tooltip: false, + validators: [Validators.required] + }, + { + name: 'pretasks', + label: 'Select or search tasks assigned to this supertask:', + type: 'select', + selectOptions: [], + requiredasterisk: true, + tooltip: false, + validators: [Validators.required] + } ]; // // // // // // // // @@ -136,26 +194,57 @@ export class MetadataService { // This variable stores information about the edit wordlist file page. editwordlistInfo = [ - { title: 'Edit Wordlist File', customform: false, subtitle: false, submitok: 'Saved!', submitokredirect: '/files/wordlist'}, + { + title: 'Edit Wordlist File', + customform: false, + subtitle: false, + submitok: 'Saved!', + submitokredirect: '/files/wordlist' + } ]; // This variable stores information about the edit rule file page. editruleInfo = [ - { title: 'Edit Rule File', customform: false, subtitle: false, submitok: 'Saved!', submitokredirect: '/files/rules'}, + { + title: 'Edit Rule File', + customform: false, + subtitle: false, + submitok: 'Saved!', + submitokredirect: '/files/rules' + } ]; // This variable stores information about the edit other file page. editotherInfo = [ - { title: 'Edit Other File', customform: false, subtitle: false, submitok: 'Saved!', submitokredirect: '/files/other'}, + { + title: 'Edit Other File', + customform: false, + subtitle: false, + submitok: 'Saved!', + submitokredirect: '/files/other' + } ]; //This variable defines the fields and properties required when editing a wonrdlist, rule or other file. editfile = [ - {name: 'fileId', label: 'ID', type: 'number', disabled: true}, - {name: 'filename', label: 'Name', type: 'text'}, - {name: 'fileType', label: 'File Type', type: 'select', selectOptions: fileFormat}, - {name: 'accessGroupId', label: 'Access group', type: 'selectd', requiredasterisk: true, selectEndpoint$: SERV.ACCESS_GROUPS, selectOptions$: [], fieldMapping: {id: '_id', name: 'groupName' }}, - {name: 'isSecret', label: 'Secret', type: 'checkbox'} + { name: 'fileId', label: 'ID', type: 'number', disabled: true }, + { name: 'filename', label: 'Name', type: 'text' }, + { + name: 'fileType', + label: 'File Type', + type: 'select', + selectOptions: fileFormat + }, + { + name: 'accessGroupId', + label: 'Access group', + type: 'selectd', + requiredasterisk: true, + selectEndpoint$: SERV.ACCESS_GROUPS, + selectOptions$: [], + fieldMapping: { id: '_id', name: 'groupName' } + }, + { name: 'isSecret', label: 'Secret', type: 'checkbox' } ]; // // // // // // // // @@ -168,13 +257,41 @@ export class MetadataService { // This variable stores information about the new cracker page. newcrackerInfo = [ - { title: 'New Cracker Type', customform: false, subtitle: false, submitok: 'New Cracker created!', submitokredirect: '/config/engine/crackers'}, + { + title: 'New Cracker Type', + customform: false, + subtitle: false, + submitok: 'New Cracker created!', + submitokredirect: '/config/engine/crackers' + } ]; //This variable defines the fields and properties required when creating a new cracker. newcracker = [ - { name: 'typeName', label: 'Type', type: 'select', selectOptions: [{ label: 'Hashcat', value: 'hashcat' },{ label: 'Generic Cracker', value: 'generic' }], requiredasterisk: true, tooltip: false, validators: [Validators.required] }, - { name: 'isChunkingAvailable', label: 'Chunking Available', type: 'select', selectOptions: [{ label: 'Yes', value: true },{ label: 'No', value: false }], requiredasterisk: true, tooltip: false, validators: [Validators.required] } + { + name: 'typeName', + label: 'Type', + type: 'select', + selectOptions: [ + { label: 'Hashcat', value: 'hashcat' }, + { label: 'Generic Cracker', value: 'generic' } + ], + requiredasterisk: true, + tooltip: false, + validators: [Validators.required] + }, + { + name: 'isChunkingAvailable', + label: 'Chunking Available', + type: 'select', + selectOptions: [ + { label: 'Yes', value: true }, + { label: 'No', value: false } + ], + requiredasterisk: true, + tooltip: false, + validators: [Validators.required] + } ]; // // @@ -183,21 +300,76 @@ export class MetadataService { // This variable stores information about the New Agent Binary page. newagentbinaryInfo = [ - { title: 'New Agent Binary', customform: false, subtitle: false, submitok: 'New Agent Binary created!', submitokredirect: 'config/engine/agent-binaries'}, + { + title: 'New Agent Binary', + customform: false, + subtitle: false, + submitok: 'New Agent Binary created!', + submitokredirect: 'config/engine/agent-binaries' + } ]; // This variable stores information about the Edit Agent Binary page. editagentbinaryInfo = [ - { title: 'Edit Agent Binary', customform: false, subtitle: false, submitok: 'Agent Binary saved!', submitokredirect: 'config/engine/agent-binaries', deltitle: 'Agent Binaries', delsubmitok: 'Deleted Agent Binary', delsubmitokredirect: 'config/engine/agent-binaries', delsubmitcancel:'Agent Binary is safe!'}, + { + title: 'Edit Agent Binary', + customform: false, + subtitle: false, + submitok: 'Agent Binary saved!', + submitokredirect: 'config/engine/agent-binaries', + deltitle: 'Agent Binaries', + delsubmitok: 'Deleted Agent Binary', + delsubmitokredirect: 'config/engine/agent-binaries', + delsubmitcancel: 'Agent Binary is safe!' + } ]; //This variable defines the fields and properties required when creating/editing an Agent Binary. agentbinary = [ - { name: 'type', label: 'Type', type: 'text', requiredasterisk: true, tooltip: false, validators: [Validators.required] }, - { name: 'operatingSystems', label: 'Operating Systems', type: 'text', requiredasterisk: true, tooltip: false, validators: [Validators.required] }, - { name: 'filename', label: 'Filename', type: 'text', requiredasterisk: true, tooltip: 'Placed in bin folder', validators: [Validators.required] }, - { name: 'version', label: 'Version', type: 'text', requiredasterisk: true, tooltip: false, validators: [Validators.required] }, - { name: 'updateTrack', label: 'Update Track', type: 'select', selectOptions: [{ label: 'Release', value: 'release' },{ label: 'Stable', value: 'stable' }], requiredasterisk: true, tooltip: false, validators: [Validators.required] } + { + name: 'type', + label: 'Type', + type: 'text', + requiredasterisk: true, + tooltip: false, + validators: [Validators.required] + }, + { + name: 'operatingSystems', + label: 'Operating Systems', + type: 'text', + requiredasterisk: true, + tooltip: false, + validators: [Validators.required] + }, + { + name: 'filename', + label: 'Filename', + type: 'text', + requiredasterisk: true, + tooltip: 'Placed in bin folder', + validators: [Validators.required] + }, + { + name: 'version', + label: 'Version', + type: 'text', + requiredasterisk: true, + tooltip: false, + validators: [Validators.required] + }, + { + name: 'updateTrack', + label: 'Update Track', + type: 'select', + selectOptions: [ + { label: 'Release', value: 'release' }, + { label: 'Stable', value: 'stable' } + ], + requiredasterisk: true, + tooltip: false, + validators: [Validators.required] + } ]; // // @@ -206,27 +378,95 @@ export class MetadataService { // This variable stores information about the New Cracker Version page. newcrackerversionInfo = [ - { title: 'New Binary Version', customform: true, subtitle: false, submitok: 'New Version created!', submitokredirect: '/config/engine/crackers'}, + { + title: 'New Binary Version', + customform: true, + subtitle: false, + submitok: 'New Version created!', + submitokredirect: '/config/engine/crackers' + } ]; // This variable stores information about the Edit Cracker Version page. editcrackerversionInfo = [ - { title: 'Edit Binary Version', customform: false, subtitle: false, submitok: 'Cracker saved!', submitokredirect: '/config/engine/crackers', deltitle: 'Crackers', delsubmitok: 'Deleted cracker', delsubmitokredirect: 'config/engine/crackers', delsubmitcancel:'Cracker is safe!'}, + { + title: 'Edit Binary Version', + customform: false, + subtitle: false, + submitok: 'Cracker saved!', + submitokredirect: '/config/engine/crackers', + deltitle: 'Crackers', + delsubmitok: 'Deleted cracker', + delsubmitokredirect: 'config/engine/crackers', + delsubmitcancel: 'Cracker is safe!' + } ]; //This variable defines the fields and properties required when creating a cracker Version. newcrackerversion = [ - { name: 'binaryName', label: 'Binary Base Name', type: 'text', requiredasterisk: true, tooltip: 'Which needs to be called on the client without os-dependent extension', validators: [Validators.required] }, - { name: 'version', label: 'Binary Version', type: 'text', requiredasterisk: true, tooltip: false, validators: [Validators.required] }, - { name: 'downloadUrl', label: 'Download URL', type: 'text', requiredasterisk: true, tooltip: 'Link where the client can download a 7zip with the binary', validators: [Validators.required] }, - { name: 'crackerBinaryTypeId', label: 'crackerBinaryTypeId', type: 'hidden', replacevalue: 'editedIndex',requiredasterisk: true, tooltip: false, validators: false }, + { + name: 'binaryName', + label: 'Binary Base Name', + type: 'text', + requiredasterisk: true, + tooltip: + 'Which needs to be called on the client without os-dependent extension', + validators: [Validators.required] + }, + { + name: 'version', + label: 'Binary Version', + type: 'text', + requiredasterisk: true, + tooltip: false, + validators: [Validators.required] + }, + { + name: 'downloadUrl', + label: 'Download URL', + type: 'text', + requiredasterisk: true, + tooltip: 'Link where the client can download a 7zip with the binary', + validators: [Validators.required] + }, + { + name: 'crackerBinaryTypeId', + label: 'crackerBinaryTypeId', + type: 'hidden', + replacevalue: 'editedIndex', + requiredasterisk: true, + tooltip: false, + validators: false + } ]; //This variable defines the fields and properties required when editing a cracker Version. editcrackerversion = [ - { name: 'binaryName', label: 'Binary Base Name', type: 'text', requiredasterisk: true, tooltip: 'Which needs to be called on the client without os-dependent extension', validators: [Validators.required] }, - { name: 'version', label: 'Binary Version', type: 'text', requiredasterisk: true, tooltip: false, validators: [Validators.required] }, - { name: 'downloadUrl', label: 'Download URL', type: 'text', requiredasterisk: true, tooltip: 'Link where the client can download a 7zip with the binary', validators: [Validators.required] }, + { + name: 'binaryName', + label: 'Binary Base Name', + type: 'text', + requiredasterisk: true, + tooltip: + 'Which needs to be called on the client without os-dependent extension', + validators: [Validators.required] + }, + { + name: 'version', + label: 'Binary Version', + type: 'text', + requiredasterisk: true, + tooltip: false, + validators: [Validators.required] + }, + { + name: 'downloadUrl', + label: 'Download URL', + type: 'text', + requiredasterisk: true, + tooltip: 'Link where the client can download a 7zip with the binary', + validators: [Validators.required] + } ]; // // @@ -235,23 +475,84 @@ export class MetadataService { // This variable stores information about the New Preprocessor page. newpreprocessorInfo = [ - { title: 'New Preprocessor', customform: false, subtitle: false, submitok: 'New Preprocessor created!', submitokredirect: 'config/engine/preprocessors'}, + { + title: 'New Preprocessor', + customform: false, + subtitle: false, + submitok: 'New Preprocessor created!', + submitokredirect: 'config/engine/preprocessors' + } ]; // This variable stores information about the Edit Preprocessor page. editpreprocessorInfo = [ - { title: 'Edit Preprocessor', customform: false, subtitle: false, submitok: 'Preprocessor saved!', submitokredirect: 'config/engine/preprocessors', deltitle: 'Preprocessors', delsubmitok: 'Deleted Preprocessor', delsubmitokredirect: 'config/engine/preprocessors', delsubmitcancel:'Preprocessor is safe!'}, + { + title: 'Edit Preprocessor', + customform: false, + subtitle: false, + submitok: 'Preprocessor saved!', + submitokredirect: 'config/engine/preprocessors', + deltitle: 'Preprocessors', + delsubmitok: 'Deleted Preprocessor', + delsubmitokredirect: 'config/engine/preprocessors', + delsubmitcancel: 'Preprocessor is safe!' + } ]; //This variable defines the fields and properties required when creating/editing a Hashtype. preprocessor = [ - { name: 'name', label: 'Name', type: 'text', requiredasterisk: true, tooltip: false, validators: [Validators.required] }, - { name: 'binaryName', label: 'Binary Basename', type: 'text', requiredasterisk: true, tooltip: false, validators: [Validators.required] }, - { name: 'url', label: 'Download URL', type: 'text', requiredasterisk: true, tooltip: false, validators: [Validators.required] }, - { label: 'Commands (set to empty if not available)',isTitle: true}, - { name: 'keyspaceCommand',label: 'Keyspace Command',type: 'text',requiredasterisk: false,tooltip: false,validators: false,defaultValue: '--keyspace',}, - { name: 'skipCommand',label: 'Skip Command',type: 'text',requiredasterisk: false,tooltip: false,validators: false,defaultValue: '--skip'}, - { name: 'limitCommand', label: 'Limit Command', type: 'text', requiredasterisk: false, tooltip: false, validators: false, defaultValue: '--limit'}, + { + name: 'name', + label: 'Name', + type: 'text', + requiredasterisk: true, + tooltip: false, + validators: [Validators.required] + }, + { + name: 'binaryName', + label: 'Binary Basename', + type: 'text', + requiredasterisk: true, + tooltip: false, + validators: [Validators.required] + }, + { + name: 'url', + label: 'Download URL', + type: 'text', + requiredasterisk: true, + tooltip: false, + validators: [Validators.required] + }, + { label: 'Commands (set to empty if not available)', isTitle: true }, + { + name: 'keyspaceCommand', + label: 'Keyspace Command', + type: 'text', + requiredasterisk: false, + tooltip: false, + validators: false, + defaultValue: '--keyspace' + }, + { + name: 'skipCommand', + label: 'Skip Command', + type: 'text', + requiredasterisk: false, + tooltip: false, + validators: false, + defaultValue: '--skip' + }, + { + name: 'limitCommand', + label: 'Limit Command', + type: 'text', + requiredasterisk: false, + tooltip: false, + validators: false, + defaultValue: '--limit' + } ]; // // @@ -260,147 +561,553 @@ export class MetadataService { // This variable stores information about the New Hashtypes page. newhashtypeInfo = [ - { title: 'Create Hashtype', customform: false, subtitle: false, submitok: 'New Hashtype created!', submitokredirect: '/config/hashtypes'}, + { + title: 'Create Hashtype', + customform: false, + subtitle: false, + submitok: 'New Hashtype created!', + submitokredirect: '/config/hashtypes' + } ]; // This variable stores information about the Editing Hashtypes page. edithashtypeInfo = [ - { title: 'Edit Hashtype', customform: false, subtitle: false, submitok: 'Hashtype saved!', submitokredirect: '/config/hashtypes', deltitle: 'Hashtypes', delsubmitok: 'Deleted Hashtype', delsubmitokredirect: '/config/hashtypes', delsubmitcancel:'Hashtype is safe!'}, + { + title: 'Edit Hashtype', + customform: false, + subtitle: false, + submitok: 'Hashtype saved!', + submitokredirect: '/config/hashtypes', + deltitle: 'Hashtypes', + delsubmitok: 'Deleted Hashtype', + delsubmitokredirect: '/config/hashtypes', + delsubmitcancel: 'Hashtype is safe!' + } ]; //This variable defines the fields and properties required when creating a new Hashtype. newhashtype = [ - { name: 'hashTypeId', label: 'Hashtype', type: 'number', requiredasterisk: true, tooltip: 'ie. Hashcat -m', validators: [Validators.required, Validators.pattern("^[0-9]*$"), Validators.minLength(1), this.numberValidator]}, - { name: 'description', label: 'Description', type: 'text', requiredasterisk: true, tooltip: false, validators: [Validators.required, Validators.minLength(1)] }, - { name: 'isSalted', label: 'Salted', type: 'checkbox', requiredasterisk: false, tooltip: 'Only if there is a separate salt value', validators: false, defaultValue: false }, - { name: 'isSlowHash', label: 'Slow Hash', type: 'checkbox', requiredasterisk: false, tooltip: false, validators: false, defaultValue: false }, + { + name: 'hashTypeId', + label: 'Hashtype', + type: 'number', + requiredasterisk: true, + tooltip: 'ie. Hashcat -m', + validators: [ + Validators.required, + Validators.pattern('^[0-9]*$'), + Validators.minLength(1), + this.numberValidator + ] + }, + { + name: 'description', + label: 'Description', + type: 'text', + requiredasterisk: true, + tooltip: false, + validators: [Validators.required, Validators.minLength(1)] + }, + { + name: 'isSalted', + label: 'Salted', + type: 'checkbox', + requiredasterisk: false, + tooltip: 'Only if there is a separate salt value', + validators: false, + defaultValue: false + }, + { + name: 'isSlowHash', + label: 'Slow Hash', + type: 'checkbox', + requiredasterisk: false, + tooltip: false, + validators: false, + defaultValue: false + } ]; //This variable is similar to newhashtype but is used for editing an existing Hashtype. As difference include disable form variable. edithashtype = [ - { name: 'hashTypeId', label: 'Hashtype', type: 'number', requiredasterisk: true, tooltip: 'ie. Hashcat -m', validators: [Validators.required, Validators.pattern("^[0-9]*$"), Validators.minLength(1), this.numberValidator], disabled: true}, - { name: 'description', label: 'Description', type: 'text', requiredasterisk: true, tooltip: false, validators: [Validators.required, Validators.minLength(1)] }, - { name: 'isSalted', label: 'Salted', type: 'checkbox', requiredasterisk: false, tooltip: 'Only if there is a separate salt value', validators: false, defaultValue: false }, - { name: 'isSlowHash', label: 'Slow Hash', type: 'checkbox', requiredasterisk: false, tooltip: false, validators: false, defaultValue: true }, + { + name: 'hashTypeId', + label: 'Hashtype', + type: 'number', + requiredasterisk: true, + tooltip: 'ie. Hashcat -m', + validators: [ + Validators.required, + Validators.pattern('^[0-9]*$'), + Validators.minLength(1), + this.numberValidator + ], + disabled: true + }, + { + name: 'description', + label: 'Description', + type: 'text', + requiredasterisk: true, + tooltip: false, + validators: [Validators.required, Validators.minLength(1)] + }, + { + name: 'isSalted', + label: 'Salted', + type: 'checkbox', + requiredasterisk: false, + tooltip: 'Only if there is a separate salt value', + validators: false, + defaultValue: false + }, + { + name: 'isSlowHash', + label: 'Slow Hash', + type: 'checkbox', + requiredasterisk: false, + tooltip: false, + validators: false, + defaultValue: true + } ]; // // - // Server + // Server Settings // // serveragentInfo = [ - { title: 'Agent Settings', customform: false, subtitle: false, submitok: 'Saved!', submitokredirect: '/config/agent'}, + { + title: 'Agent Settings', + customform: false, + subtitle: false, + submitok: 'Saved!', + submitokredirect: '/config/agent' + } ]; serveragent = [ { label: 'Activity / Registration', isTitle: true }, - { name: "agenttimeout", label: "Delay before considering an agent as inactive(or timed out)", type: "number", tooltip: false }, - { name: "benchtime", label: "Delay before considering an issued chunk as inactive", type: "number", tooltip: false }, - { name: "statustimer", label: "Frequency of the agent reporting about a task to the server", type: "number", tooltip: false }, - { name: "agentDataLifetime", label: "Time during which util and temperature data are retained on the server", type: "number", tooltip: false }, - { name: "hideIpInfo", label: "Hide agents IP information", type: "checkbox", tooltip: false }, - { name: "voucherDeletion", label: "Voucher(s) can be used to register multiple agents", type: "checkbox", tooltip: false }, + { + name: 'agenttimeout', + label: 'Inactivity Timeout Delay', + type: 'number', + tooltip: false + }, + { + name: 'benchtime', + label: 'Inactivity Timeout for Issued Chunks', + type: 'number', + tooltip: false + }, + { + name: 'statustimer', + label: 'Task Reporting Frequency', + type: 'number', + tooltip: false + }, + { + name: 'agentDataLifetime', + label: 'Retention Period for Utilization and Temperature Data', + type: 'number', + tooltip: false + }, + { + name: 'hideIpInfo', + label: 'Agent IP Information Privacy', + type: 'checkbox', + tooltip: false + }, + { + name: 'voucherDeletion', + label: 'Register Multiple Agents Using Voucher(s)', + type: 'checkbox', + tooltip: false + }, { label: 'Graphical Feedback', isTitle: true }, - { name: "agentStatLimit", label: "Maximum number of data points in agent (gpu) graphs", type: "number", tooltip: false }, - { name: "agentStatTension", label: "Draw straight lines in agent data graph instead of bezier curves", type: "select", selectOptions: [ - { label: "Straight lines", value: "0" }, - { label: "Bezier curves", value: "1" } - ], tooltip: false + { + name: 'agentStatLimit', + label: 'Maximum Data Points for Agent (GPU) Graphs', + type: 'number', + tooltip: false + }, + { + name: 'agentStatTension', + label: 'Straight Lines or bezier curves for Agent Data Graphs', + type: 'select', + selectOptions: [ + { label: 'Straight lines', value: '0' }, + { label: 'Bezier curves', value: '1' } + ], + tooltip: false }, - { name: "agentTempThreshold1", label: "Temperature threshold above which an agent is displayed in orange in the status page", type: "number", tooltip: false }, - { name: "agentTempThreshold2", label: "Temperature threshold above which an agent is displayed in red in the status page", type: "number", tooltip: false }, - { name: "agentUtilThreshold1", label: "Util threshold below which an agent is displayed in orange in the status page", type: "number", tooltip: false }, - { name: "agentUtilThreshold2", label: "Util threshold below which an agent is displayed in red in the status page", type: "number", tooltip: false } + { + name: 'agentTempThreshold1', + label: 'Orange Status Threshold for Agent Temperature', + type: 'number', + tooltip: false + }, + { + name: 'agentTempThreshold2', + label: 'Red Status Threshold for Agent Temperature', + type: 'number', + tooltip: false + }, + { + name: 'agentUtilThreshold1', + label: 'Orange Status Threshold for Agent Utilization', + type: 'number', + tooltip: false + }, + { + name: 'agentUtilThreshold2', + label: 'Red Status Threshold for Agent Utilization', + type: 'number', + tooltip: false + } ]; servertaskchunkInfo = [ - { title: 'Task/Chunk Settings', customform: false, subtitle: false, submitok: 'Saved!', submitokredirect: '/config/task-chunk'}, + { + title: 'Task/Chunk Settings', + customform: false, + subtitle: false, + submitok: 'Saved!', + submitokredirect: '/config/task-chunk' + } ]; servertaskchunk = [ { label: 'Benchmark / Chunk', isTitle: true }, - { name: "chunktime", label: "Expected duration of a chunk", type: "number", tooltip: "tctip.chunktime" }, - { name: "disptolerance", label: "Authorised expansion in percentage of the final chunk of a task", type: "number", tooltip: false }, - { name: "defaultBenchmark", label: "Speed benchmark as default benchmark process", type: "checkbox", tooltip: false }, - { name: "disableTrimming", label: "Disable trimming of chunks and redo the whole chunk", type: "checkbox", tooltip: false }, + { + name: 'chunktime', + label: 'Expected Chunk Duration', + type: 'number', + tooltip: false + }, + { + name: 'disptolerance', + label: 'Authorized Expansion Percentage for Final Chunk in a Task', + type: 'number', + tooltip: false + }, + { + name: 'defaultBenchmark', + label: 'Default Speed Benchmark Process', + type: 'checkbox', + tooltip: false + }, + { + name: 'disableTrimming', + label: 'Disable Chunk Trimming and Revert to Full Chunk Processing', + type: 'checkbox', + tooltip: false + }, { label: 'Command Line & Misc.', isTitle: true }, - { name: "hashlistAlias", label: "Placeholder string for the hashlist in the command line during task creation", type: "text", tooltip: false }, - { name: "blacklistChars", label: "Forbidden characters in the attack command input", type: "text", tooltip: "tctip.blacklistChars" }, - { name: "priority0Start", label: "Also automatically assign tasks with priority 0 (Needed, check file)", type: "checkbox", tooltip: false }, - { name: "showTaskPerformance", label: "Show cracks/minute for active tasks", type: "checkbox", tooltip: false }, + { + name: 'hashlistAlias', + label: 'Hashlist Placeholder in Command Line', + type: 'text', + tooltip: false + }, + { + name: 'blacklistChars', + label: 'Forbidden Characters in Attack Command Input', + type: 'text', + tooltip: false + }, + { + name: 'priority0Start', + label: + 'Automatic Assignment of Tasks with Priority 0 (Needed, Check File)', + type: 'checkbox', + tooltip: false + }, + { + name: 'showTaskPerformance', + label: 'Display Cracks per Minute for Active Tasks', + type: 'checkbox', + tooltip: false + }, { label: 'Rule splitting', isTitle: true }, - { name: "ruleSplitSmallTasks", label: "When rule splitting is applied for tasks, always make them a small task", type: "checkbox", tooltip: false }, - { name: "ruleSplitAlways", label: "Even do rule splitting when there are not enough rules but just the benchmark is too high. Can result in subtasks with just one rule", type: "checkbox", tooltip: false }, - { name: "ruleSplitDisable", label: "Disable automatic task splitting with large rule files", type: "checkbox", tooltip: false }, + { + name: 'ruleSplitSmallTasks', + label: 'Rule Splitting for Tasks: Always Create Small Tasks', + type: 'checkbox', + tooltip: false + }, + { + name: 'ruleSplitAlways', + label: + 'Rule Splitting with Benchmark Constraint: Allow Subtasks with a Single Rule', + type: 'checkbox', + tooltip: false + }, + { + name: 'ruleSplitDisable', + label: 'Disable Automatic Task Splitting for Large Rule Files', + type: 'checkbox', + tooltip: false + } ]; serverhchInfo = [ - { title: 'Hashes/Cracks/Hashlist Settings', customform: false, subtitle: false, submitok: 'Saved!', submitokredirect: '/config/hch'}, + { + title: 'Hashes/Cracks/Hashlist Settings', + customform: false, + subtitle: false, + submitok: 'Saved!', + submitokredirect: '/config/hch' + } ]; serverhch = [ { label: 'Import/Display of Hashlist', isTitle: true }, - { name: "maxHashlistSize", label: "Maximum number of lines in a hashlist", type: "number", tooltip: false }, - { name: "pagingSize", label: "Number of hashes shown per page in the Hash View", type: "number", tooltip: false }, - { name: "hashesPerPage", label: "Number of hashes per page on hashes view", type: "number", tooltip: false }, - { name: "fieldseparator", label: "The separator character used to separate hash and plain (or salt)", type: "text", tooltip: false }, - { name: "hashlistImportCheck", label: "Check if hashes have been previously cracked (in other hashlists) at hashlist creation time", type: "checkbox", tooltip:false }, + { + name: 'maxHashlistSize', + label: 'Maximum Lines in Hashlist', + type: 'number', + tooltip: false + }, + { + name: 'pagingSize', + label: 'Hashes size Page in Hash Vieww', + type: 'number', + tooltip: false + }, + { + name: 'hashesPerPage', + label: 'Hashes per Page in Hash View', + type: 'number', + tooltip: false + }, + { + name: 'fieldseparator', + label: 'Separator Character for Hash and Plain (or Salt)', + type: 'text', + tooltip: false + }, + { + name: 'hashlistImportCheck', + label: + 'Check for Previous Cracks in Other Hashlists at Hashlist Creation', + type: 'checkbox', + tooltip: false + }, { label: 'Database Parameters', isTitle: true }, - { name: "batchSize", label: "Batch size of SQL query when hashlist is sent to the agent", type: "number", tooltip: false }, - { name: "plainTextMaxLength", label: "Maximum length of plain text", type: "number", tooltip: false }, - { name: "hashMaxLength", label: "Maximum length of a hash", type: "number", tooltip: 'Such change may take a long time depending on the database size' } + { + name: 'batchSize', + label: 'SQL Query Batch Size for Hashlist Transmission to Agents', + type: 'number', + tooltip: false + }, + { + name: 'plainTextMaxLength', + label: 'Maximum Length of Plain Text', + type: 'number', + tooltip: false + }, + { + name: 'hashMaxLength', + label: 'Maximum length of a Hash', + type: 'number', + tooltip: 'Change Duration Dependent on Database Size' + } ]; servernotifInfo = [ - { title: 'Notification Settings', customform: false, subtitle: false, submitok: 'Saved!', submitokredirect: '/config/notifications'}, + { + title: 'Notification Settings', + customform: false, + subtitle: false, + submitok: 'Saved!', + submitokredirect: '/config/notifications' + } ]; // hashMaxLength it should be this validator type -// -// Such change may take a long time depending on the database size -// + // + // Such change may take a long time depending on the database size + // servernotif = [ - { name: "emailSender", label: "Email address sending the notification emails", type: "text", tooltip: false }, - { name: "emailSenderName", label: "Sender's name on emails sent from Hashtopolis", type: "text", tooltip: false }, - { name: "telegramBotToken", label: "Telegram bot token used to send telegram notifications", type: "text", tooltip: false }, - { name: "notificationsProxyEnable", label: "Enable using a proxy for sending notifications", type: "checkbox", tooltip: false }, + { + name: 'emailSender', + label: 'Notification Sender Email', + type: 'text', + tooltip: false + }, + { + name: 'emailSenderName', + label: "Sender's Display Name", + type: 'text', + tooltip: false + }, + { + name: 'telegramBotToken', + label: 'Telegram Bot Token for Notifications', + type: 'text', + tooltip: false + }, + { + name: 'notificationsProxyEnable', + label: 'Enable Notification Proxy', + type: 'checkbox', + tooltip: false + }, { label: 'Proxy Settings', isTitle: true }, - { name: "notificationsProxyServer", label: "Server URL of the proxy to use for notification", type: "text", placeholder:"http...", tooltip: false }, - { name: "notificationsProxyPort", label: "Set the port for the notifications proxy", type: "number", tooltip: false }, - { name: "notificationsProxyType", label: "Proxy type to use for notifications", type: "select", selectOptions: proxytype, tooltip: false } + { + name: 'notificationsProxyServer', + label: 'Notification Server URL', + type: 'text', + placeholder: 'http...', + tooltip: false + }, + { + name: 'notificationsProxyPort', + label: 'Notification Proxy Port', + type: 'number', + tooltip: false + }, + { + name: 'notificationsProxyType', + label: 'Notification Proxy Type', + type: 'select', + selectOptions: proxytype, + tooltip: false + } ]; //Evretyhing inside Enable using proxy //
servergsInfo = [ - { title: 'General Settings', customform: false, subtitle: false, submitok: 'Saved!', submitokredirect: '/config/general-settings'}, + { + title: 'General Settings', + customform: false, + subtitle: false, + submitok: 'Saved!', + submitokredirect: '/config/general-settings' + } ]; servergs = [ - { name: "hashcatBrainEnable", label: "Enable Hashcat Brain", type: "checkbox", tooltip: false }, - { name: "hashcatBrainHost", label: "Host to be used for hashcat brain (must be reachable by agents)", type: "text", placeholder: "URL", tooltip: false }, - { name: "hashcatBrainPort", label: "Port for hashcat brain", type: "number", placeholder: "I.e. 8080", tooltip: false }, - { name: "hashcatBrainPass", label: "Password to be used to access hashcat brain server", type: "password", tooltip: false }, - { name: "hcErrorIgnore", label: "Ignore error messages from crackers containing the string below", type: "textarea", tooltip: false }, - { name: "numLogEntries", label: "Number of log entries to be retained", type: "number", tooltip: false }, - { name: "timefmt", label: "Set the time format", type: "text", tooltip: false }, - { name: "maxSessionLength", label: "Max session length users can configure (in hours)", type: "text", tooltip: false }, - { name: "baseHost", label: "Base hostname/port/protocol to use. Only fill this in to override the auto-determined value", type: "text", tooltip: false }, - { name: "contactEmail", label: "Admin email address displayed on the webpage footer (hidden if empty)", type: "text", tooltip: false }, - { name: "serverLogLevel", label: "Server level to be logged on the server to file", type: "select", selectOptions: serverlog, tooltip: false }, - ]; - - // // + { + name: 'hashcatBrainEnable', + label: 'Enable Hashcat Brain', + type: 'checkbox', + tooltip: false + }, + { + name: 'hashcatBrainHost', + label: 'Host for Hashcat Brain (Accessible by Agents)', + type: 'text', + placeholder: 'URL', + tooltip: false + }, + { + name: 'hashcatBrainPort', + label: 'Port for Hashcat Brain', + type: 'number', + placeholder: 'I.e. 8080', + tooltip: false + }, + { + name: 'hashcatBrainPass', + label: 'Password for Accessing Hashcat Brain Server', + type: 'password', + tooltip: false + }, + { + name: 'hcErrorIgnore', + label: + 'Ignore Error Messages Containing the Following String from Crackers', + type: 'textarea', + tooltip: false + }, + { + name: 'numLogEntries', + label: 'Number of Retained Log Entries', + type: 'number', + tooltip: false + }, + { + name: 'timefmt', + label: 'Time Format Configuration', + type: 'text', + tooltip: false + }, + { + name: 'maxSessionLength', + label: 'Maximum User Session Duration (in hours)', + type: 'text', + tooltip: false + }, + { + name: 'baseHost', + label: 'Base Hostname/Port/Protocol Override', + type: 'text', + tooltip: false + }, + { + name: 'contactEmail', + label: 'Admin Email Address for Webpage Footer Display', + type: 'text', + tooltip: false + }, + { + name: 'serverLogLevel', + label: 'Server Level Logging to File', + type: 'select', + selectOptions: serverlog, + tooltip: false + } + ]; + + // // // Health Check // // // This variable holds information about the fields required when creating a new health check. newhealthcheck = [ - { name: 'attack', label: 'Attack', type: 'select',requiredasterisk: true, selectOptions: [{ value: 0, label: 'Brute-Force' }], validators: [Validators.required] }, - { name: 'hashtypeId', label: 'Hashtype', type: 'select',requiredasterisk: true, selectOptions: [{ value: 0, label: 'MD5' },{ value: 3200, label: 'BCRYPT' }], validators: [Validators.required] }, - { name: 'crackerBinaryType', label: 'Binary', type: 'selectd', requiredasterisk: true, selectEndpoint$: SERV.ACCESS_PERMISSIONS_GROUPS, selectOptions$: [], fieldMapping: { id: 'crackerBinaryTypeId', name: 'typeName' },validators: [Validators.required]}, - { name: 'crackerBinaryId', label: 'Binary Version', type: 'selectd', requiredasterisk: true, selectEndpoint$: SERV.ACCESS_PERMISSIONS_GROUPS, selectOptions$: [], fieldMapping: { id: 'crackerBinaryId', name: 'version' },validators: [Validators.required]}, + { + name: 'attack', + label: 'Attack', + type: 'select', + requiredasterisk: true, + selectOptions: [{ value: 0, label: 'Brute-Force' }], + validators: [Validators.required] + }, + { + name: 'hashtypeId', + label: 'Hashtype', + type: 'select', + requiredasterisk: true, + selectOptions: [ + { value: 0, label: 'MD5' }, + { value: 3200, label: 'BCRYPT' } + ], + validators: [Validators.required] + }, + { + name: 'crackerBinaryType', + label: 'Binary', + type: 'selectd', + requiredasterisk: true, + selectEndpoint$: SERV.ACCESS_PERMISSIONS_GROUPS, + selectOptions$: [], + fieldMapping: { id: 'crackerBinaryTypeId', name: 'typeName' }, + validators: [Validators.required] + }, + { + name: 'crackerBinaryId', + label: 'Binary Version', + type: 'selectd', + requiredasterisk: true, + selectEndpoint$: SERV.ACCESS_PERMISSIONS_GROUPS, + selectOptions$: [], + fieldMapping: { id: 'crackerBinaryId', name: 'version' }, + validators: [Validators.required] + } ]; // // // // // // // // @@ -413,47 +1120,115 @@ export class MetadataService { // This variable stores information about the user page. newuserInfo = [ - { title: 'New User', customform: false, subtitle: false, submitok: 'New User created!', submitokredirect: 'users/all-users'}, + { + title: 'New User', + customform: false, + subtitle: false, + submitok: 'New User created!', + submitokredirect: 'users/all-users' + } ]; // This variable edit information about the user page. editInfo = [ - { title: 'Edit User', customform: false, subtitle: false, submitok: 'Saved!', submitokredirect: 'users/all-users'}, + { + title: 'Edit User', + customform: false, + subtitle: false, + submitok: 'Saved!', + submitokredirect: 'users/all-users' + } ]; //This variable holds information about the fields required when creating a new user. newuser = [ - { name: 'name', label: 'User Name', type: 'text', requiredasterisk: true, validators: [Validators.required] }, - { name: 'email', label: 'Email', type: 'email', requiredasterisk: true, validators: [Validators.required, Validators.email] }, - { name: 'globalPermissionGroupId', label: 'Global Permission Group', type: 'selectd', requiredasterisk: true, selectEndpoint$: SERV.ACCESS_PERMISSIONS_GROUPS, selectOptions$: [], fieldMapping: {id: 'id', name: 'name' }, validators: [Validators.required] }, + { + name: 'name', + label: 'User Name', + type: 'text', + requiredasterisk: true, + validators: [Validators.required] + }, + { + name: 'email', + label: 'Email', + type: 'email', + requiredasterisk: true, + validators: [Validators.required, Validators.email] + }, + { + name: 'globalPermissionGroupId', + label: 'Global Permission Group', + type: 'selectd', + requiredasterisk: true, + selectEndpoint$: SERV.ACCESS_PERMISSIONS_GROUPS, + selectOptions$: [], + fieldMapping: { id: 'id', name: 'name' }, + validators: [Validators.required] + } ]; //This variable is similar to newuser but is used for editing an existing user. edituser = [ { name: 'id', label: 'User ID', type: 'number', disabled: true }, - { name: 'name', label: 'User Name', type: 'text', disabled: true}, - { name: 'email', label: 'Email', type: 'email', disabled: true}, - { name: 'registered', label: 'Creation date', type: 'date', disabled: true}, - { name: 'lastLogin', label: 'Last login', type: 'date', disabled: true}, - { label: 'Update Settings',isTitle: true}, - { label: 'Member of access groups', type: 'date', disabled: true}, - { name: 'globalPermissionGroupId', label: 'Global Permission Group', type: 'selectd', requiredasterisk: true, selectEndpoint$: SERV.ACCESS_PERMISSIONS_GROUPS, selectOptions$: [],fieldMapping: {id: 'id', name: 'name' }, validators: [Validators.required] }, + { name: 'name', label: 'User Name', type: 'text', disabled: true }, + { name: 'email', label: 'Email', type: 'email', disabled: true }, + { + name: 'registered', + label: 'Creation date', + type: 'date', + disabled: true + }, + { name: 'lastLogin', label: 'Last login', type: 'date', disabled: true }, + { label: 'Update Settings', isTitle: true }, + { label: 'Member of access groups', type: 'date', disabled: true }, + { + name: 'globalPermissionGroupId', + label: 'Global Permission Group', + type: 'selectd', + requiredasterisk: true, + selectEndpoint$: SERV.ACCESS_PERMISSIONS_GROUPS, + selectOptions$: [], + fieldMapping: { id: 'id', name: 'name' }, + validators: [Validators.required] + }, { name: 'password', type: 'password' }, - { name: 'isValid', label: 'Valid', type: 'checkbox', requiredasterisk: false, tooltip: false, validators: false, defaultValue: false }, + { + name: 'isValid', + label: 'Valid', + type: 'checkbox', + requiredasterisk: false, + tooltip: false, + validators: false, + defaultValue: false + } ]; // // // New Global Permission Group // // - // This variable stores information about the global permission group page. + // This variable stores information about the global permission group page. newglobalpermissionsgpInfo = [ - { title: 'New Global Permission Group', customform: false, subtitle: false, submitok: 'New Global Permission Group created!', submitokredirect: '/users/global-permissions-groups'}, + { + title: 'New Global Permission Group', + customform: false, + subtitle: false, + submitok: 'New Global Permission Group created!', + submitokredirect: '/users/global-permissions-groups' + } ]; //This variable holds information about the fields required when creating a new global permission group. newglobalpermissionsgp = [ - { name: 'name', label: 'Name', type: 'text', requiredasterisk: true, tooltip: false, validators: [Validators.required] } + { + name: 'name', + label: 'Name', + type: 'text', + requiredasterisk: true, + tooltip: false, + validators: [Validators.required] + } ]; // // @@ -462,35 +1237,69 @@ export class MetadataService { // This variable stores information about the access group page. newaccessgroupsInfo = [ - { title: 'New Access Group', subtitle: false, submitok: 'New Access Group created!', submitokredirect: '/users/access-groups'}, + { + title: 'New Access Group', + subtitle: false, + submitok: 'New Access Group created!', + submitokredirect: '/users/access-groups' + } ]; // This variable contains information related to editing an access group. editaccessgroupsInfo = [ - { title: 'Edit Access Group', subtitle: false, submitok: 'Access Group saved!', submitokredirect: '/users/access-groups', deltitle: 'Agent Groups', delsubmitok: 'Deleted Access Group', delsubmitokredirect: '/users/access-groups', delsubmitcancel:'Agent Group is safe!'}, + { + title: 'Edit Access Group', + subtitle: false, + submitok: 'Access Group saved!', + submitokredirect: '/users/access-groups', + deltitle: 'Agent Groups', + delsubmitok: 'Deleted Access Group', + delsubmitokredirect: '/users/access-groups', + delsubmitcancel: 'Agent Group is safe!' + } ]; // This variable contains information about the fields required when creating or editing an access group. accessgroups = [ - { name: 'groupName', label: 'Name', type: 'text', requiredasterisk: true, tooltip: false, validators: [Validators.required] } + { + name: 'groupName', + label: 'Name', + type: 'text', + requiredasterisk: true, + tooltip: false, + validators: [Validators.required] + } ]; // // // // // // // // // // UI SETTINGS SECTION // // // // // // // // // // - uisettingsInfo = [ - { title: 'UI Settings', subtitle: false}, - ]; + uisettingsInfo = [{ title: 'UI Settings', subtitle: false }]; uisettings = [ - { name: "localtimefmt", label: "Set the time format", type: "select", selectOptions: dateFormats }, - { name: "autorefresh", label: "Dashboard Refresh Interval (seconds)", type: "text", tooltip: "Manage refresh interval in the show tasks view" }, - { name: "tooltip", label: "Manage Global level of tooltip details", type: "select", selectOptions: [ - { label: "Concise", value: 0 }, - { label: "Detailed", value: 1 }, - { label: "Very Detailed", value: 2 } - ]}, + { + name: 'localtimefmt', + label: 'Set the time format', + type: 'select', + selectOptions: dateFormats + }, + { + name: 'autorefresh', + label: 'Dashboard Refresh Interval (seconds)', + type: 'text', + tooltip: 'Manage refresh interval in the show tasks view' + }, + { + name: 'tooltip', + label: 'Manage Global level of tooltip details', + type: 'select', + selectOptions: [ + { label: 'Concise', value: 0 }, + { label: 'Detailed', value: 1 }, + { label: 'Very Detailed', value: 2 } + ] + } ]; /** @@ -499,27 +1308,40 @@ export class MetadataService { * @returns An array of form metadata.editnotifInfo */ getFormMetadata(formName: string): any[] { - if (formName === 'editwordlist' || formName === 'editrule' || formName === 'editother') { + if ( + formName === 'editwordlist' || + formName === 'editrule' || + formName === 'editother' + ) { return this.editfile; } else if (formName === 'uisettings') { return this.uisettings; } else if (formName === 'newcracker') { return this.newcracker; - } else if (formName === 'newagentbinary' || formName === 'editagentbinary') { + } else if ( + formName === 'newagentbinary' || + formName === 'editagentbinary' + ) { return this.agentbinary; } else if (formName === 'newcrackerversion') { return this.newcrackerversion; } else if (formName === 'editcrackerversion') { return this.editcrackerversion; - } else if (formName === 'newpreprocessor' || formName === 'editpreprocessor') { + } else if ( + formName === 'newpreprocessor' || + formName === 'editpreprocessor' + ) { return this.preprocessor; - } else if (formName === 'newhashtype' ) { + } else if (formName === 'newhashtype') { return this.newhashtype; } else if (formName === 'edithashtype') { return this.edithashtype; } else if (formName === 'newglobalpermissionsgp') { return this.newglobalpermissionsgp; - } else if (formName === 'newaccessgroups' || formName === 'editaccessgroups') { + } else if ( + formName === 'newaccessgroups' || + formName === 'editaccessgroups' + ) { return this.accessgroups; } else if (formName === 'serveragent') { return this.serveragent; @@ -609,9 +1431,9 @@ export class MetadataService { // Adjust this based on your API response structure return data.options.map((option: any) => ({ label: option.label, - value: option.value, + value: option.value })); - }), + }) ); } @@ -627,8 +1449,4 @@ export class MetadataService { } return null; } - - } - - diff --git a/src/app/shared/form/dynamicform.component.ts b/src/app/shared/form/dynamicform.component.ts index 8e231cb1..feab2eac 100644 --- a/src/app/shared/form/dynamicform.component.ts +++ b/src/app/shared/form/dynamicform.component.ts @@ -1,8 +1,31 @@ -import { FormBuilder, FormGroup, FormControl, ValidatorFn, Validators } from '@angular/forms'; -import { Component, Input, OnInit, Output, EventEmitter, AfterViewInit, OnDestroy } from '@angular/core'; +import { + FormBuilder, + FormControl, + FormGroup, + ValidatorFn, + Validators +} from '@angular/forms'; +import { + AfterViewInit, + Component, + EventEmitter, + Input, + OnDestroy, + OnInit, + Output +} from '@angular/core'; import { faInfoCircle } from '@fortawesome/free-solid-svg-icons'; import { Router } from '@angular/router'; -import { Observable, Subject, Subscription, combineLatest, forkJoin, map, switchMap, takeUntil } from 'rxjs'; +import { + Observable, + Subject, + Subscription, + combineLatest, + forkJoin, + map, + switchMap, + takeUntil +} from 'rxjs'; import { MetadataService } from 'src/app/core/_services/metadata.service'; import { GlobalService } from 'src/app/core/_services/main.service'; import { ChangeDetectorRef } from '@angular/core'; @@ -13,73 +36,120 @@ import { ChangeDetectorRef } from '@angular/core'; @Component({ selector: 'app-dynamic-form', template: ` - - -
- -
-
- -
{{ field.label }}
-
- -

- - {{ field.label }} - - - - - - - - - - - - - - - - - - - - {{ option.label }} - - - - - Please Select an Option - {{ option.name }} - - - - {{ field.label }} - - - -

-
-
-
- - - Delete - - -
-
-
- `, + + +
+ +
+
+ +
{{ field.label }}
+
+ +
+ + + {{ field.label }} + + + + + + + + + + + + + + + + + + + + + {{ option.label }} + + + + + Please Select an Option + {{ option.name }} + + + + +
+
+ {{ + field.label + }} +
+
+
+
+ + Delete + +
+
+
+ ` }) export class DynamicFormComponent implements OnInit, AfterViewInit, OnDestroy { /** @@ -116,7 +186,7 @@ export class DynamicFormComponent implements OnInit, AfterViewInit, OnDestroy { /** * Indicates whether the form is in "create" mode or "update" mode. * When true, it's in "create" mode, and when false, it's in "update" mode. - */ + */ @Input() isCreateMode: boolean; /** @@ -124,7 +194,7 @@ export class DynamicFormComponent implements OnInit, AfterViewInit, OnDestroy { * Set it to `true` to display the "Delete" button, and `false` to hide it. * By default, the "Delete" button is displayed (set to `true`). */ - @Input() showDeleteButton: boolean = true; + @Input() showDeleteButton = true; /** * The text to display on the "Create" or "Update" button. @@ -143,6 +213,11 @@ export class DynamicFormComponent implements OnInit, AfterViewInit, OnDestroy { */ @Output() deleteAction: EventEmitter = new EventEmitter(); + /** + * Event emitter for notifying when the selection type changes. + * Emits a number representing the selected type. + */ + @Output() selectTypeChange: EventEmitter = new EventEmitter(); /** @@ -157,56 +232,68 @@ export class DynamicFormComponent implements OnInit, AfterViewInit, OnDestroy { * @param gs - The GlobalService for handling global operations and API requests. * @param cd - The Angular ChangeDetectorRef for triggering change detection manually. */ - constructor(private fb: FormBuilder, private gs: GlobalService, private cd: ChangeDetectorRef) {} + constructor( + private fb: FormBuilder, + private gs: GlobalService, + private cd: ChangeDetectorRef + ) {} /** * Initializes the dynamic form by creating form controls and setting their initial values. * This method is called when the dynamic form component is initialized. */ ngOnInit() { - // Initialize an object to store the configuration of form controls. - const controlsConfig = {}; - - // Iterate through the form metadata to create and configure form controls. - for (const field of this.formMetadata) { - // Exclude fields marked as titles from form control creation. - if (!field.isTitle) { - // Get the name of the field. - const fieldName = field.name; - - // Determine the validators for the field, defaulting to an empty array if none are provided. - const validators: ValidatorFn[] = field.validators ? field.validators : []; - - // Initialize the initial value for the form control. - let initialValue; - - // Set the initial value for the form control based on the field's type. - if (field.type === 'checkbox') { - // For checkboxes, use the value directly from formValues. - initialValue = this.formValues[fieldName]; - } if (!this.isCreateMode) { - // For other field types, use formValues[fieldName] or 0 as a default value if not provided. - initialValue = fieldName in this.formValues ? this.formValues[fieldName] : 0; - } + // Initialize an object to store the configuration of form controls. + const controlsConfig = {}; + + // Iterate through the form metadata to create and configure form controls. + for (const field of this.formMetadata) { + // Exclude fields marked as titles from form control creation. + if (!field.isTitle) { + // Get the name of the field. + const fieldName = field.name; + + // Determine the validators for the field, defaulting to an empty array if none are provided. + const validators: ValidatorFn[] = field.validators + ? field.validators + : []; + + // Initialize the initial value for the form control. + let initialValue; + + // Set the initial value for the form control based on the field's type. + if (field.type === 'checkbox') { + // For checkboxes, use the value directly from formValues. + initialValue = this.formValues[fieldName]; + } else { + // For other field types, use formValues[fieldName] or a default value if not provided. + initialValue = + fieldName in this.formValues ? this.formValues[fieldName] : null; + } + if (!this.isCreateMode) { + // For other field types, use formValues[fieldName] or 0 as a default value if not provided. + initialValue = + fieldName in this.formValues ? this.formValues[fieldName] : 0; + } - // In 'create' mode, override the initial value if a default value is specified in the field's metadata. - if (this.isCreateMode && field.defaultValue !== undefined) { - initialValue = field.defaultValue; - } + // In 'create' mode, override the initial value if a default value is specified in the field's metadata. + if (this.isCreateMode && field.defaultValue !== undefined) { + initialValue = field.defaultValue; + } - // Create a form control with the initial value and any specified validators. - if (!this.isCreateMode && field.disabled) { - // If in 'update' mode and the field is disabled, create a disabled form control. - controlsConfig[fieldName] = { value: initialValue, disabled: true }; - } else { - // Create a form control with the initial value and optional validators. - controlsConfig[fieldName] = new FormControl(initialValue, validators); + // Create a form control with the initial value and any specified validators. + if (!this.isCreateMode && field.disabled) { + // If in 'update' mode and the field is disabled, create a disabled form control. + controlsConfig[fieldName] = { value: initialValue, disabled: true }; + } else { + // Create a form control with the initial value and optional validators. + controlsConfig[fieldName] = new FormControl(initialValue, validators); + } } } - } - // Create the Angular FormGroup with the configured controls. - this.form = this.fb.group(controlsConfig); + // Create the Angular FormGroup with the configured controls. + this.form = this.fb.group(controlsConfig); } /** @@ -219,7 +306,7 @@ export class DynamicFormComponent implements OnInit, AfterViewInit, OnDestroy { * Indicates whether the dynamic select options are currently being loaded. * When true, it represents that options are being fetched; when false, loading is complete. */ - isLoadingSelect: boolean = true; + isLoadingSelect = true; /** * Angular lifecycle hook: ngAfterViewInit @@ -235,70 +322,51 @@ export class DynamicFormComponent implements OnInit, AfterViewInit, OnDestroy { // Handle logic for select fields with selectOptions$ after the view is initialized selectFields.forEach((field) => { // Fetch the select options dynamically here - this.selectOptionsSubscription = this.gs.getAll(field.selectEndpoint$,{'maxResults': 5000}) - .pipe(takeUntil(this.destroy$)) - .subscribe((options) => { - - // Sometimes fields need to be mapped - const transformedOptions = this.transformSelectOptions(options.values, field); - - // Assign the fetched options to the field's selectOptions$ - field.selectOptions$ = transformedOptions; - - // Update isLoadingSelect to indicate that loading is complete - this.isLoadingSelect = false; - - // Optionally, update the form control value if needed - const control = this.form.get(field.name); - - // Check if there are options available - if (control && options.values && options.values.length > 0 && !this.isCreateMode) { - // Ensure that options.values[0] and options.values[0].value exist before setting the value - const initialSelectedValue = options.values[0]?.value; - - if (initialSelectedValue !== undefined) { - control.setValue(initialSelectedValue); + this.selectOptionsSubscription = this.gs + .getAll(field.selectEndpoint$, { maxResults: 5000 }) + .pipe(takeUntil(this.destroy$)) + .subscribe((options) => { + // Sometimes fields need to be mapped + const transformedOptions = this.transformSelectOptions( + options.values, + field + ); + + // Assign the fetched options to the field's selectOptions$ + field.selectOptions$ = transformedOptions; + + // Update isLoadingSelect to indicate that loading is complete + this.isLoadingSelect = false; + + // Optionally, update the form control value if needed + const control = this.form.get(field.name); + + // Check if there are options available + if ( + control && + options.values && + options.values.length > 0 && + !this.isCreateMode + ) { + // Ensure that options.values[0] and options.values[0].value exist before setting the value + const initialSelectedValue = options.values[0]?.value; + + if (initialSelectedValue !== undefined) { + control.setValue(initialSelectedValue); + } } - } - // Trigger change detection to prevent ExpressionChangedAfterItHasBeenCheckedError - this.cd.detectChanges(); - }); + // Trigger change detection to prevent ExpressionChangedAfterItHasBeenCheckedError + this.cd.detectChanges(); + }); }); } } - /** - * Transforms API response options based on a field mapping configuration. - * - * @param apiOptions - The options received from an API response. - * @param field - The field configuration that contains the mapping between form fields and API fields. - * - * @returns An array of transformed select options to be used in the form. - */ - transformSelectOptions(apiOptions: any[], field: any): any[] { - return apiOptions.map((apiOption: any) => { - const transformedOption: any = {}; - - for (const formField of Object.keys(field.fieldMapping)) { - const apiField = field.fieldMapping[formField]; - - if (Object.prototype.hasOwnProperty.call(apiOption, apiField)) { - transformedOption[formField] = apiOption[apiField]; - } else { - // Handle the case where the API field doesn't exist in the response - transformedOption[formField] = null; // or set a default value - } - } - - return transformedOption; - }); - } - /** * Checks if the form is valid. * @returns {boolean} True if the form is valid, false otherwise. - */ + */ formIsValid(): boolean { return this.form.valid; } @@ -317,15 +385,15 @@ export class DynamicFormComponent implements OnInit, AfterViewInit, OnDestroy { /** * Handles the delete action. * Emits the delete action to the parent component when the "Delete" button is clicked. - */ - onDelete(){ + */ + onDelete() { this.deleteAction.emit(); } /** * Handles the onchange action. * Emits the change action to the parent component when the select option is selected. - */ + */ onChange(value: any) { this.selectTypeChange.emit(value); } @@ -343,4 +411,30 @@ export class DynamicFormComponent implements OnInit, AfterViewInit, OnDestroy { this.destroy$.complete(); } + /** + * Transforms API response options based on a field mapping configuration. + * + * @param apiOptions - The options received from an API response. + * @param field - The field configuration that contains the mapping between form fields and API fields. + * + * @returns An array of transformed select options to be used in the form. + */ + transformSelectOptions(apiOptions: any[], field: any): any[] { + return apiOptions.map((apiOption: any) => { + const transformedOption: any = {}; + + for (const formField of Object.keys(field.fieldMapping)) { + const apiField = field.fieldMapping[formField]; + + if (Object.prototype.hasOwnProperty.call(apiOption, apiField)) { + transformedOption[formField] = apiOption[apiField]; + } else { + // Handle the case where the API field doesn't exist in the response + transformedOption[formField] = null; // or set a default value + } + } + + return transformedOption; + }); + } } diff --git a/src/app/shared/form/dynamicform.module.ts b/src/app/shared/form/dynamicform.module.ts index 50249544..2af33ffa 100644 --- a/src/app/shared/form/dynamicform.module.ts +++ b/src/app/shared/form/dynamicform.module.ts @@ -1,7 +1,7 @@ import { ButtonsModule } from '../buttons/buttons.module'; -import { CommonModule } from "@angular/common"; -import { DynamicFormComponent } from "./dynamicform.component"; -import { NgModule } from "@angular/core"; +import { CommonModule } from '@angular/common'; +import { DynamicFormComponent } from './dynamicform.component'; +import { NgModule } from '@angular/core'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; import { FormComponent } from './form.component'; @@ -11,11 +11,15 @@ import { GridModule } from '../grid-containers/grid.module'; import { HorizontalNavModule } from '../navigation/navigation.module'; import { MatDividerModule } from '@angular/material/divider'; import { MatTooltipModule } from '@angular/material/tooltip'; -import { MatButtonModule } from "@angular/material/button"; -import { MAT_FORM_FIELD_DEFAULT_OPTIONS, MatFormFieldModule } from "@angular/material/form-field"; -import { MatIconModule } from "@angular/material/icon"; -import { MatInputModule } from "@angular/material/input"; -import { MatSelectModule } from "@angular/material/select"; +import { MatButtonModule } from '@angular/material/button'; +import { MatCheckboxModule } from '@angular/material/checkbox'; +import { + MAT_FORM_FIELD_DEFAULT_OPTIONS, + MatFormFieldModule +} from '@angular/material/form-field'; +import { MatIconModule } from '@angular/material/icon'; +import { MatInputModule } from '@angular/material/input'; +import { MatSelectModule } from '@angular/material/select'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { PageTitleModule } from '../page-headers/page-title.module'; @@ -36,6 +40,7 @@ import { PageTitleModule } from '../page-headers/page-title.module'; GridModule, NgbModule, CommonModule, + MatCheckboxModule, MatTooltipModule, MatFormFieldModule, MatSelectModule, @@ -51,7 +56,10 @@ import { PageTitleModule } from '../page-headers/page-title.module'; FormComponent ], providers: [ - {provide: MAT_FORM_FIELD_DEFAULT_OPTIONS, useValue: {appearance: 'outline'}} + { + provide: MAT_FORM_FIELD_DEFAULT_OPTIONS, + useValue: { appearance: 'outline' } + } ] }) export class DynamicFormModule {} diff --git a/src/app/shared/form/formconfig.component.ts b/src/app/shared/form/formconfig.component.ts index 41bb98a5..b42f753e 100644 --- a/src/app/shared/form/formconfig.component.ts +++ b/src/app/shared/form/formconfig.component.ts @@ -16,14 +16,22 @@ import { Subscription } from 'rxjs'; selector: 'app-form', template: ` - - `, + + ` }) /** * Component for managing forms, supporting both create and edit modes. */ export class FormConfigComponent implements OnInit, OnDestroy { - // Metadata Text, titles, subtitles, forms, and API path globalMetadata: any[] = []; apiPath: string; @@ -58,7 +66,7 @@ export class FormConfigComponent implements OnInit, OnDestroy { * Initial values for form fields (optional). * If provided, these values are used to initialize form controls in the dynamic form. * @type {any[]} - */ + */ formValues: any[] = []; /** @@ -80,7 +88,7 @@ export class FormConfigComponent implements OnInit, OnDestroy { * @param alert - The AlertService for displaying alerts. * @param gs - The GlobalService for handling global operations. * @param router - The Angular Router for navigation. - */ + */ constructor( private unsubscribeService: UnsubscribeService, private metadataService: MetadataService, @@ -92,15 +100,19 @@ export class FormConfigComponent implements OnInit, OnDestroy { private router: Router ) { // Subscribe to route data to initialize component data - this.route.data.subscribe((data: { kind: string, path: string, type: string }) => { - const formKind = data.kind; - this.apiPath = data.path; // Get the API path from route data - // Load metadata and form information - this.globalMetadata = this.metadataService.getInfoMetadata(formKind+'Info')[0]; - this.formMetadata = this.metadataService.getFormMetadata(formKind); - this.title = this.globalMetadata['title']; - titleService.set([this.title]); - }); + this.route.data.subscribe( + (data: { kind: string; path: string; type: string }) => { + const formKind = data.kind; + this.apiPath = data.path; // Get the API path from route data + // Load metadata and form information + this.globalMetadata = this.metadataService.getInfoMetadata( + formKind + 'Info' + )[0]; + this.formMetadata = this.metadataService.getFormMetadata(formKind); + this.title = this.globalMetadata['title']; + titleService.set([this.title]); + } + ); // Add this.mySubscription to UnsubscribeService this.unsubscribeService.add(this.mySubscription); } @@ -113,7 +125,7 @@ export class FormConfigComponent implements OnInit, OnDestroy { { label: 'Task/Chunk', routeName: 'config/task-chunk' }, { label: 'Hashes/Cracks/Hashlist', routeName: 'config/hch' }, { label: 'Notifications', routeName: 'config/notifications' }, - { label: 'General', routeName: 'config/general-settings' }, + { label: 'General', routeName: 'config/general-settings' } ]; /** @@ -129,20 +141,22 @@ export class FormConfigComponent implements OnInit, OnDestroy { */ loadEdit() { // Fetch data from the API for editing - this.mySubscription = this.gs.getAll(this.apiPath, { 'maxResults': 500 }).subscribe((result) => { - // Transform the retrieved array of objects into the desired structure for form rendering - this.formValues = result.values.reduce((result, item) => { - result[item.item] = item.value; - return result; - }, {}); - // Maps the item with the id, so can be used for update - this.formIds = result.values.reduce((result, item) => { - result[item.item] = item._id; - return result; - }, {}); - - this.isloaded = true; // Data is loaded and ready for form rendering - }); + this.mySubscription = this.gs + .getAll(this.apiPath, { maxResults: 500 }) + .subscribe((result) => { + // Transform the retrieved array of objects into the desired structure for form rendering + this.formValues = result.values.reduce((result, item) => { + result[item.item] = item.value; + return result; + }, {}); + // Maps the item with the id, so can be used for update + this.formIds = result.values.reduce((result, item) => { + result[item.item] = item._id; + return result; + }, {}); + + this.isloaded = true; // Data is loaded and ready for form rendering + }); } /** @@ -180,26 +194,23 @@ export class FormConfigComponent implements OnInit, OnDestroy { const key = fieldKeys[index]; const id = this.formIds[key]; const valueUpdate = changedFields[key]; - const arr = { 'item': key, 'value': String(valueUpdate) }; - - this.mySubscription = this.gs.update(SERV.CONFIGS, id, arr).subscribe((result) => { - this.uicService.onUpdatingCheck(key); - this.alert.okAlert('Saved', key); - - // Delay showing the next alert by 2000 milliseconds (2 seconds) - setTimeout(() => { - index++; - showAlertsSequentially(); - }, 2000); - }); + const arr = { item: key, value: String(valueUpdate) }; + + this.mySubscription = this.gs + .update(SERV.CONFIGS, id, arr) + .subscribe((result) => { + this.uicService.onUpdatingCheck(key); + this.alert.okAlert('Saved', key); + + // Delay showing the next alert by 2000 milliseconds (2 seconds) + setTimeout(() => { + index++; + showAlertsSequentially(); + }, 2000); + }); } }; showAlertsSequentially(); } - - } - - - diff --git a/src/app/shared/grid-containers/grid-autocol.ts b/src/app/shared/grid-containers/grid-autocol.ts index d62b2c20..cdb2b604 100644 --- a/src/app/shared/grid-containers/grid-autocol.ts +++ b/src/app/shared/grid-containers/grid-autocol.ts @@ -1,4 +1,4 @@ -import { Component, Input, OnInit, Renderer2, ElementRef } from '@angular/core'; +import { Component, ElementRef, Input, OnInit, Renderer2 } from '@angular/core'; @Component({ selector: 'grid-autocol', @@ -8,21 +8,22 @@ import { Component, Input, OnInit, Renderer2, ElementRef } from '@angular/core';
`, host: { - "(window:resize)": "onWindowResize($event)", + '(window:resize)': 'onWindowResize($event)' }, - styles: [` - .vertical-line { - position: absolute; - top: 200px; - bottom: 0; - left: 0; - width: 1px; /* Width of the vertical line */ - background-color: gray; /* Line color, you can customize this */ - } - `] + styles: [ + ` + .vertical-line { + position: absolute; + top: 200px; + bottom: 0; + left: 0; + width: 1px; /* Width of the vertical line */ + background-color: gray; /* Line color, you can customize this */ + } + ` + ] }) export class GridAutoColComponent implements OnInit { - // Input properties that can be set from the parent component @Input() itemCount: number; // Number of items to determine if the component should be enabled @Input() centered?: boolean; // Whether to center the content @@ -36,7 +37,10 @@ export class GridAutoColComponent implements OnInit { cardWidth = 300; // Width of each card (adjust as needed) cardHeight = 80; // Height of each card (adjust as needed) - constructor(private renderer: Renderer2, private el: ElementRef) {} + constructor( + private renderer: Renderer2, + private el: ElementRef + ) {} ngOnInit(): void { this.isMobile = this.width < this.mobileWidth; @@ -51,12 +55,16 @@ export class GridAutoColComponent implements OnInit { // Calculate the number of columns based on available space getCols() { - return Math.floor((this.width + this.gutterSize) / (this.cardWidth + this.gutterSize)); + return Math.floor( + (this.width + this.gutterSize) / (this.cardWidth + this.gutterSize) + ); } // Calculate the number of rows based on available space getRows() { - return Math.floor((this.height + this.gutterSize) / (this.cardHeight + this.gutterSize)); + return Math.floor( + (this.height + this.gutterSize) / (this.cardHeight + this.gutterSize) + ); } // Calculate and return the CSS styles for the grid layout @@ -76,16 +84,16 @@ export class GridAutoColComponent implements OnInit { const rows = this.getRows(); // Calculate the width of division lines between columns - const divisionLineWidth = (this.width - (cols * this.cardWidth)) / (cols - 1); + const divisionLineWidth = (this.width - cols * this.cardWidth) / (cols - 1); // Define the CSS styles for the grid const styles = { - 'display': 'grid', + display: 'grid', 'grid-template-columns': `repeat(2, ${this.cardWidth}px))`, 'grid-template-rows': `repeat(${rows}, auto)`, 'grid-gap': '10px', // Adjust the gap as needed 'grid-auto-flow': 'column', - 'position': 'relative', + position: 'relative' }; // Set the division line width if necessary @@ -101,7 +109,11 @@ export class GridAutoColComponent implements OnInit { for (let i = 1; i < cols; i++) { const lineElement = this.renderer.createElement('div'); this.renderer.addClass(lineElement, 'vertical-line'); - this.renderer.setStyle(lineElement, 'left', `${(i * this.cardWidth + (i - 1))}px`); + this.renderer.setStyle( + lineElement, + 'left', + `${i * this.cardWidth + (i - 1)}px` + ); this.renderer.appendChild(this.el.nativeElement, lineElement); } } @@ -111,8 +123,9 @@ export class GridAutoColComponent implements OnInit { // Remove previously added vertical lines private removeVerticalLines() { - const existingLines = this.el.nativeElement.querySelectorAll('.vertical-line'); - existingLines.forEach(line => { + const existingLines = + this.el.nativeElement.querySelectorAll('.vertical-line'); + existingLines.forEach((line) => { this.renderer.removeChild(this.el.nativeElement, line); }); } diff --git a/src/app/shared/navigation/horizontalnav.component.ts b/src/app/shared/navigation/horizontalnav.component.ts index e0517824..b073cc69 100644 --- a/src/app/shared/navigation/horizontalnav.component.ts +++ b/src/app/shared/navigation/horizontalnav.component.ts @@ -7,26 +7,40 @@ import { Subject } from 'rxjs'; @Component({ selector: 'horizontalnav', template: ` -
-
-
-
-
-
- - {{ item.label }} - + +
+
+
+
+ +
+
+ + + + {{ item.label }} + + + +
-
-
- `, + +
+ + + + + ` }) export class HorizontalNavComponent implements OnDestroy { @Input() menuItems: HorizontalNav[] = []; @@ -40,7 +54,7 @@ export class HorizontalNavComponent implements OnDestroy { * @returns The CSS class, including 'select' if the button's route is active. */ getButtonClass(routeName: string): string { - return this.router.url.includes(routeName) ? 'btn btn-sm btn-outline-gray-600 btn-outline-gray-600-select' : 'btn btn-sm btn-outline-gray-600'; + return this.router.url.includes(routeName) ? 'btn-toogle-select' : ''; } /** diff --git a/src/app/shared/navigation/navigation.module.ts b/src/app/shared/navigation/navigation.module.ts index 923a9ffc..7835e275 100644 --- a/src/app/shared/navigation/navigation.module.ts +++ b/src/app/shared/navigation/navigation.module.ts @@ -1,10 +1,12 @@ import { HorizontalNavComponent } from './horizontalnav.component'; +import { MatToolbarModule } from '@angular/material/toolbar'; import { CommonModule } from '@angular/common'; +import { MatButtonToggleModule } from '@angular/material/button-toggle'; import { NgModule } from '@angular/core'; @NgModule({ declarations: [HorizontalNavComponent], - imports: [CommonModule], - exports: [HorizontalNavComponent], + imports: [CommonModule, MatToolbarModule, MatButtonToggleModule], + exports: [HorizontalNavComponent] }) export class HorizontalNavModule {} diff --git a/src/app/users/globalpermissionsgroups/new-globalpermissionsgroups/new-globalpermissionsgroups.component.html b/src/app/users/globalpermissionsgroups/new-globalpermissionsgroups/new-globalpermissionsgroups.component.html deleted file mode 100644 index 0f302b3e..00000000 --- a/src/app/users/globalpermissionsgroups/new-globalpermissionsgroups/new-globalpermissionsgroups.component.html +++ /dev/null @@ -1,19 +0,0 @@ - - -
-
- - - - - - - -
-
-
diff --git a/src/app/users/globalpermissionsgroups/new-globalpermissionsgroups/new-globalpermissionsgroups.component.ts b/src/app/users/globalpermissionsgroups/new-globalpermissionsgroups/new-globalpermissionsgroups.component.ts deleted file mode 100644 index 706fbaca..00000000 --- a/src/app/users/globalpermissionsgroups/new-globalpermissionsgroups/new-globalpermissionsgroups.component.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { FormControl, FormGroup, Validators } from '@angular/forms'; -import { Component, OnInit } from '@angular/core'; -import { Router } from '@angular/router'; - -import { AlertService } from 'src/app/core/_services/shared/alert.service'; -import { GlobalService } from 'src/app/core/_services/main.service'; -import { PageTitle } from 'src/app/core/_decorators/autotitle'; -import { SERV } from '../../../core/_services/main.config'; - -@Component({ - selector: 'app-new-globalpermissionsgroups', - templateUrl: './new-globalpermissionsgroups.component.html' -}) -@PageTitle(['New Global Permission Group']) -export class NewGlobalpermissionsgroupsComponent implements OnInit { - - // Form - createForm: FormGroup; - public isCollapsed = true; - - constructor( - private alert: AlertService, - private gs: GlobalService, - private router:Router - ) { } - - ngOnInit(): void { - - this.createForm = new FormGroup({ - 'name': new FormControl('', [Validators.required, Validators.minLength(1)]), - }); - - } - - onSubmit(): void{ - if (this.createForm.valid) { - - this.gs.create(SERV.ACCESS_PERMISSIONS_GROUPS,this.createForm.value).subscribe(() => { - this.alert.okAlert('New Global Permission Group created!',''); - this.router.navigate(['/users/global-permissions-groups']); - } - ); - this.createForm.reset(); // success, we reset form - } -} - -} diff --git a/src/app/users/groups/cu-group/cu-group.component.html b/src/app/users/groups/cu-group/cu-group.component.html deleted file mode 100644 index 5766d3d2..00000000 --- a/src/app/users/groups/cu-group/cu-group.component.html +++ /dev/null @@ -1,28 +0,0 @@ - - -
-
- - - -
-
- - - - -
-
- - - -
-
-
-
-
diff --git a/src/app/users/groups/cu-group/cu-group.component.ts b/src/app/users/groups/cu-group/cu-group.component.ts deleted file mode 100644 index e90576bb..00000000 --- a/src/app/users/groups/cu-group/cu-group.component.ts +++ /dev/null @@ -1,93 +0,0 @@ -import { FormControl, FormGroup, Validators } from '@angular/forms'; -import { ActivatedRoute, Params, Router } from '@angular/router'; -import { Component, OnInit } from '@angular/core'; - -import { AlertService } from 'src/app/core/_services/shared/alert.service'; -import { GlobalService } from 'src/app/core/_services/main.service'; -import { PageTitle } from 'src/app/core/_decorators/autotitle'; -import { SERV } from '../../../core/_services/main.config'; - -@Component({ - selector: 'app-cu-group', - templateUrl: './cu-group.component.html' -}) -@PageTitle(['Group']) -export class CUGroupComponent implements OnInit { - - // Create or Edit Binary - whichView: string; - editMode = false; - editedIndex: number; - - constructor( - private route:ActivatedRoute, - private alert: AlertService, - private gs: GlobalService, - private router:Router - ) { } - - Form = new FormGroup({ - 'groupName': new FormControl('', [Validators.required, Validators.minLength(1)]), - }); - - ngOnInit(): void { - - this.route.params - .subscribe( - (params: Params) => { - this.editedIndex = +params['id']; - this.editMode = params['id'] != null; - } - ); - - this.route.data.subscribe(data => { - switch (data['kind']) { - - case 'new-access-groups': - this.whichView = 'create'; - break; - - case 'edit-access-groups': - this.whichView = 'edit'; - this.initForm(); - break; - - } - }); - - } - - private initForm() { - if (this.editMode) { - this.gs.get(SERV.ACCESS_GROUPS,this.editedIndex).subscribe((result)=>{ - this.Form = new FormGroup({ - 'groupName': new FormControl(result['groupName']), - }); - }); - } - } - - onSubmit(): void{ - if (this.Form.valid) { - - switch (this.whichView) { - - case 'create': - this.gs.create(SERV.ACCESS_GROUPS,this.Form.value).subscribe(() => { - this.alert.okAlert('New Access Group created!',''); - this.router.navigate(['/users/access-groups']); - } - ); - break; - - case 'edit': - this.gs.update(SERV.ACCESS_GROUPS,this.editedIndex,this.Form.value).subscribe(() => { - this.alert.okAlert('Access Group saved!',''); - this.router.navigate(['/users/access-groups']); - }); - break; - } - } -} - -} diff --git a/src/app/users/users.module.ts b/src/app/users/users.module.ts index 3c4de5b5..2087c271 100644 --- a/src/app/users/users.module.ts +++ b/src/app/users/users.module.ts @@ -1,33 +1,29 @@ -import { FontAwesomeModule } from "@fortawesome/angular-fontawesome"; -import { FormsModule, ReactiveFormsModule } from "@angular/forms"; -import { ComponentsModule } from "../shared/components.module"; -import { NgbModule } from "@ng-bootstrap/ng-bootstrap"; -import { DataTablesModule } from "angular-datatables"; -import { PipesModule } from "../shared/pipes.module"; -import { RouterModule } from "@angular/router"; -import { CommonModule } from "@angular/common"; -import { NgModule } from "@angular/core"; +import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { ComponentsModule } from '../shared/components.module'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { DataTablesModule } from 'angular-datatables'; +import { PipesModule } from '../shared/pipes.module'; +import { RouterModule } from '@angular/router'; +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; import { EditGlobalpermissionsgroupsComponent } from './globalpermissionsgroups/edit-globalpermissionsgroups/edit-globalpermissionsgroups.component'; -import { NewGlobalpermissionsgroupsComponent } from './globalpermissionsgroups/new-globalpermissionsgroups/new-globalpermissionsgroups.component'; import { GlobalpermissionsgroupsComponent } from './globalpermissionsgroups/globalpermissionsgroups.component'; -import { CUGroupComponent } from './groups/cu-group/cu-group.component'; -import { EditUsersComponent } from "./edit-users/edit-users.component"; -import { AllUsersComponent } from "./all-users/all-users.component"; -import { GroupsComponent } from "./groups/groups.component"; -import { UsersRoutingModule } from "./users-routing.module"; +import { EditUsersComponent } from './edit-users/edit-users.component'; +import { AllUsersComponent } from './all-users/all-users.component'; +import { GroupsComponent } from './groups/groups.component'; +import { UsersRoutingModule } from './users-routing.module'; @NgModule({ - declarations:[ + declarations: [ EditGlobalpermissionsgroupsComponent, - NewGlobalpermissionsgroupsComponent, GlobalpermissionsgroupsComponent, EditUsersComponent, AllUsersComponent, - GroupsComponent, - CUGroupComponent + GroupsComponent ], - imports:[ + imports: [ ReactiveFormsModule, UsersRoutingModule, FontAwesomeModule, diff --git a/src/styles/base/_base.scss b/src/styles/base/_base.scss index b11dc699..bc7f5a72 100644 --- a/src/styles/base/_base.scss +++ b/src/styles/base/_base.scss @@ -111,4 +111,18 @@ h2 { border-style: solid; border-color: var(--mat-table-row-item-outline-color, rgba(0, 0, 0, 0.12)); } -} \ No newline at end of file +} + +// Angular Material + +.btn-toogle-select { + color: #ffffff; + background-color: #1986b1; + border-color: #1986b1; +} + +// .btn-outline-gray-600-select:hover { +// color: #4B5563; +// border-color: #4B5563; +// } + diff --git a/src/styles/base/_form.scss b/src/styles/base/_form.scss index 02790234..25c55f11 100644 --- a/src/styles/base/_form.scss +++ b/src/styles/base/_form.scss @@ -78,4 +78,25 @@ width: 100% } } -} \ No newline at end of file +} + + +// Angular Material + +.custom-form { + min-width: 150px; + max-width: 500px; + width: 100%; +} + +.matfield-full-width { + width: 100%; +} + +.mat-input-element { + font-size: 14px !important; /* Adjust the font size as needed */ +} + +.mat-form-field { + max-width: 300px !important; /* Adjust the maximum width as needed */ +} diff --git a/src/styles/components/_button.scss b/src/styles/components/_button.scss index d492b9b7..154ca358 100644 --- a/src/styles/components/_button.scss +++ b/src/styles/components/_button.scss @@ -246,3 +246,7 @@ .separation-button { margin-right: 16px; /* Adjust the margin to control the spacing */ } + +mat-button-toggle-group { + margin-left: -15px; +} From 2cd2847029ba08552c97bb132920590619453f7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Fri, 10 Nov 2023 15:55:58 +0000 Subject: [PATCH 215/419] Last changes --- src/app/auth/auth.component.html | 54 -------- src/app/auth/auth.component.ts | 141 +++++++++++++++++---- src/app/auth/auth.module.ts | 50 ++++---- src/app/auth/forgot/forgot.component.html | 39 ------ src/app/auth/forgot/forgot.component.ts | 14 -- src/app/core/_services/metadata.service.ts | 40 +++++- 6 files changed, 182 insertions(+), 156 deletions(-) delete mode 100644 src/app/auth/auth.component.html delete mode 100644 src/app/auth/forgot/forgot.component.html delete mode 100644 src/app/auth/forgot/forgot.component.ts diff --git a/src/app/auth/auth.component.html b/src/app/auth/auth.component.html deleted file mode 100644 index 749d9527..00000000 --- a/src/app/auth/auth.component.html +++ /dev/null @@ -1,54 +0,0 @@ - - Login -
Enter your credentials to access.
- -
-

- - User Name - - - -

- -

- - Password - - -

- - -
- -

- - - - - - -
-
- This is a private closed system. If you need access, you need to contact an admin. -
-
-
-
- -
-
- -
- - diff --git a/src/app/auth/auth.component.ts b/src/app/auth/auth.component.ts index e63637fb..ef00e872 100644 --- a/src/app/auth/auth.component.ts +++ b/src/app/auth/auth.component.ts @@ -1,5 +1,13 @@ -import { AuthService, AuthResponseData } from '../core/_services/access/auth.service'; -import { faLock, faUser, faEye, faEyeSlash } from '@fortawesome/free-solid-svg-icons'; +import { + AuthResponseData, + AuthService +} from '../core/_services/access/auth.service'; +import { + faEye, + faEyeSlash, + faLock, + faUser +} from '@fortawesome/free-solid-svg-icons'; import { environment } from './../../environments/environment'; import { Component, Inject, OnInit } from '@angular/core'; import { Router } from '@angular/router'; @@ -8,20 +16,102 @@ import { Observable } from 'rxjs'; import { ConfigService } from '../core/_services/shared/config.service'; @Component({ - selector: 'app-auth', - templateUrl: './auth.component.html' -}) -export class AuthComponent implements OnInit { + selector: 'app-login', + template: ` + + `, + styles: [ + ` + .login-container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 100vh; + } - faEyeSlash=faEyeSlash; - faLock=faLock; - faUser=faUser; - faEye=faEye; + .login-card { + width: 400px; + margin-bottom: 16px; + } + mat-form-field { + width: 100%; + margin-bottom: 16px; + } + + .mat-raised-button { + width: 100%; + } + + .forgot-password-link { + margin-top: 16px; + text-align: center; + } + + .forgot-password-link a { + color: #2196f3; /* Use the color you prefer */ + text-decoration: none; + } + + .mat-card-alert { + background-color: #ffebee; /* Use the alert color you prefer */ + margin-top: 16px; + } + ` + ] +}) +export class AuthComponent implements OnInit { errorRes: string | null; - public showPassword: boolean; - public showPasswordOnPress: boolean; + public hide = true; headerConfig: any; constructor( @@ -36,8 +126,8 @@ export class AuthComponent implements OnInit { this.configService.getEndpoint(); } - onSubmit(form: NgForm){ - if(!form.valid){ + onSubmit(form: NgForm) { + if (!form.valid) { return; } const username = form.value.username; @@ -48,18 +138,20 @@ export class AuthComponent implements OnInit { authObs = this.authService.logIn(username, password); authObs.subscribe( - resData =>{ - if (this.authService.redirectUrl) { - const redirectUrl = this.authService.redirectUrl; - this.authService.redirectUrl = ''; - this.authService.setUserLoggedIn(true); - this.router.navigate([redirectUrl]); - } else { + (resData) => { + if (this.authService.redirectUrl) { + const redirectUrl = this.authService.redirectUrl; + this.authService.redirectUrl = ''; + this.authService.setUserLoggedIn(true); + this.router.navigate([redirectUrl]); + } else { this.router.navigate(['/']); + } + }, + (errorMessage) => { + this.errorRes = errorMessage; } - }, errorMessage => { - this.errorRes = errorMessage; - }); + ); form.reset(); } @@ -67,5 +159,4 @@ export class AuthComponent implements OnInit { onHandleError() { this.errorRes = null; } - } diff --git a/src/app/auth/auth.module.ts b/src/app/auth/auth.module.ts index 35a2954e..21a19a75 100644 --- a/src/app/auth/auth.module.ts +++ b/src/app/auth/auth.module.ts @@ -1,38 +1,44 @@ -import { FontAwesomeModule } from "@fortawesome/angular-fontawesome"; -import { NgbModule } from "@ng-bootstrap/ng-bootstrap"; -import { IsAuth } from "../core/_guards/auth.guard"; -import { CommonModule } from "@angular/common"; -import { RouterModule } from "@angular/router"; -import { FormsModule } from "@angular/forms"; +import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { IsAuth } from '../core/_guards/auth.guard'; +import { CommonModule } from '@angular/common'; +import { RouterModule } from '@angular/router'; +import { FormsModule } from '@angular/forms'; import { MatCardModule } from '@angular/material/card'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatTooltipModule } from '@angular/material/tooltip'; -import { MatButtonModule } from "@angular/material/button"; -import { MAT_FORM_FIELD_DEFAULT_OPTIONS } from "@angular/material/form-field"; -import { MatIconModule } from "@angular/material/icon"; -import { MatInputModule } from "@angular/material/input"; -import { MatSelectModule } from "@angular/material/select"; -import { NgModule } from "@angular/core"; +import { MatButtonModule } from '@angular/material/button'; +import { MAT_FORM_FIELD_DEFAULT_OPTIONS } from '@angular/material/form-field'; +import { MatIconModule } from '@angular/material/icon'; +import { MatInputModule } from '@angular/material/input'; +import { MatSelectModule } from '@angular/material/select'; +import { MatToolbarModule } from '@angular/material/toolbar'; +import { NgModule } from '@angular/core'; -import { ComponentsModule } from "../shared/components.module"; -import { ForgotComponent } from "./forgot/forgot.component"; -import { AuthComponent } from "./auth.component"; +import { ComponentsModule } from '../shared/components.module'; +import { AuthComponent } from './auth.component'; +import { FormComponent } from '../shared/form/form.component'; @NgModule({ - declarations:[ - ForgotComponent, - AuthComponent - ], - imports:[ + declarations: [AuthComponent], + imports: [ RouterModule.forChild([ - {path: 'auth', component: AuthComponent}, - {path: 'auth/forgot', component: ForgotComponent} + { path: 'auth', component: AuthComponent }, + { + path: 'auth/forgot', + component: FormComponent, + data: { + kind: 'authforgot', + type: 'create' + } + } ]), FontAwesomeModule, ComponentsModule, CommonModule, MatFormFieldModule, MatCardModule, + MatToolbarModule, MatTooltipModule, MatButtonModule, MatIconModule, diff --git a/src/app/auth/forgot/forgot.component.html b/src/app/auth/forgot/forgot.component.html deleted file mode 100644 index 9ac10dbe..00000000 --- a/src/app/auth/forgot/forgot.component.html +++ /dev/null @@ -1,39 +0,0 @@ - -

Forgot Password

-
-

- - User Name - - -

- -

- - Email - - -

- - - - - -
-
diff --git a/src/app/auth/forgot/forgot.component.ts b/src/app/auth/forgot/forgot.component.ts deleted file mode 100644 index 90c2611b..00000000 --- a/src/app/auth/forgot/forgot.component.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Component, OnInit } from '@angular/core'; - -@Component({ - selector: 'app-forgot', - templateUrl: './forgot.component.html' -}) -export class ForgotComponent implements OnInit { - - constructor() { } - - ngOnInit(): void { - } - -} diff --git a/src/app/core/_services/metadata.service.ts b/src/app/core/_services/metadata.service.ts index 8cbef903..de3eb1aa 100644 --- a/src/app/core/_services/metadata.service.ts +++ b/src/app/core/_services/metadata.service.ts @@ -75,6 +75,38 @@ export class MetadataService { // Examples // Create title between fields. use { label: 'More settings', isTitle: true } + // // // // // // // // + // AUTH SECTION // + // // // // // // // // + + // // + // Forgot Password + // // + authforgotInfo = [ + { + title: 'Forgot Password', + customform: false, + subtitle: false, + submitok: 'Requesting..', + submitokredirect: '/auth' + } + ]; + + authforgot = [ + { + name: 'User Name', + label: 'User Name', + type: 'text', + validators: [Validators.required] + }, + { + name: 'email', + label: 'Email', + type: 'email', + validators: [Validators.required] + } + ]; + // // // // // // // // // ACCOUNT SECTION // // // // // // // // // @@ -1308,7 +1340,9 @@ export class MetadataService { * @returns An array of form metadata.editnotifInfo */ getFormMetadata(formName: string): any[] { - if ( + if (formName === 'authforgot') { + return this.authforgot; + } else if ( formName === 'editwordlist' || formName === 'editrule' || formName === 'editother' @@ -1368,7 +1402,9 @@ export class MetadataService { * @returns An array of info metadata. */ getInfoMetadata(formName: string): any[] { - if (formName === 'editwordlistInfo') { + if (formName === 'authforgotInfo') { + return this.authforgotInfo; + } else if (formName === 'editwordlistInfo') { return this.editwordlistInfo; } else if (formName === 'editruleInfo') { return this.editruleInfo; From 8238553cebb3469650431baa02aa31dfd65bf419 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Sat, 11 Nov 2023 15:05:59 +0100 Subject: [PATCH 216/419] Update style --- src/styles/base/_base.scss | 16 +++------- src/styles/components/_table.scss | 52 ++++++++++++++++++++++++------- 2 files changed, 45 insertions(+), 23 deletions(-) diff --git a/src/styles/base/_base.scss b/src/styles/base/_base.scss index b11dc699..e7b2471a 100644 --- a/src/styles/base/_base.scss +++ b/src/styles/base/_base.scss @@ -1,7 +1,10 @@ @import './colors'; body { - font-family: var(--mdc-typography-button-font-family, var(--mdc-typography-font-family, Roboto, sans-serif)); + font-family: var( + --mdc-typography-button-font-family, + var(--mdc-typography-font-family, Roboto, sans-serif) + ); } a { @@ -52,14 +55,6 @@ a { color: $primary-300 !important; } -.truncate { - display: inline-block !important; - max-width: 150px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - .page-title-wrapper { display: flex; justify-content: space-between; @@ -104,11 +99,10 @@ h2 { } } - .dark-theme { hr { border-width: 1px; border-style: solid; border-color: var(--mat-table-row-item-outline-color, rgba(0, 0, 0, 0.12)); } -} \ No newline at end of file +} diff --git a/src/styles/components/_table.scss b/src/styles/components/_table.scss index 6c047d63..b34801b4 100644 --- a/src/styles/components/_table.scss +++ b/src/styles/components/_table.scss @@ -227,22 +227,50 @@ table.hashtopolis-table { background-color: $primary-100; } - .flex-container { - display: flex; - justify-content: flex-start; - - .mat-icon { - margin-top: -3px; + .mat-mdc-cell { + .row-action { + .mat-icon { + margin-right: 0; + } } + .flex-container { + display: flex; + justify-content: flex-start; + flex-direction: column; + + .mat-icon { + margin-top: -3px; + margin-right: 2px; + } - a { - display: block; - width: 100%; - border-bottom: 1px dotted $primary-300; - &:last-child { - border-bottom: none; + a { + display: inline; + border-bottom: 1px dotted $primary-300; + &:last-child { + border-bottom: none; + } + &::after { + content: '\a'; + white-space: pre; + } + } + &.icons { + flex-direction: row; } } + .tructate { + a { + display: inline-block; + max-width: 150px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + } + + &.text-right { + text-align: right; + } } } From 800c8f2c786709ae7a80dbca9ac5d5014b4cd80e Mon Sep 17 00:00:00 2001 From: itbf9 Date: Sat, 11 Nov 2023 15:08:47 +0100 Subject: [PATCH 217/419] Add hashtype and description to hashlist interface --- src/app/core/_models/hashlist.model.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/app/core/_models/hashlist.model.ts b/src/app/core/_models/hashlist.model.ts index 62d77e02..33c108b1 100644 --- a/src/app/core/_models/hashlist.model.ts +++ b/src/app/core/_models/hashlist.model.ts @@ -1,3 +1,5 @@ +import { Hashtype } from './hashtype.model'; + export interface BaseHashlist { accessGroupId: number; brainFeatures: string; @@ -35,6 +37,8 @@ export interface Hashlist { format: number; name: string; hashTypeId: number; + hashType?: Hashtype; + hashTypeDescription?: string; isHexSalt: boolean; isSecret: boolean; isSalted: boolean; From 50a17df78b266627d118f532e2f1515aff76edc5 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Sat, 11 Nov 2023 15:09:51 +0100 Subject: [PATCH 218/419] Add utility function to display percantage --- src/app/shared/utils/util.ts | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/src/app/shared/utils/util.ts b/src/app/shared/utils/util.ts index 92f960c1..cf2bf356 100644 --- a/src/app/shared/utils/util.ts +++ b/src/app/shared/utils/util.ts @@ -17,7 +17,7 @@ export function validateFileExt(filename: string): boolean { const filext = filename.split('.').pop(); - switch (filext.toLowerCase()){ + switch (filext.toLowerCase()) { case 'zip': case 'dic': case 'txt': @@ -37,35 +37,39 @@ export function validateFileExt(filename: string): boolean { * @beta */ - export function getBase64ImageFromURL(url: string) { return new Promise((resolve, reject) => { const img = new Image(); - img.setAttribute("crossOrigin", "anonymous"); + img.setAttribute('crossOrigin', 'anonymous'); img.onload = () => { - const canvas = document.createElement("canvas"); + const canvas = document.createElement('canvas'); canvas.width = img.width; canvas.height = img.height; - const ctx = canvas.getContext("2d"); + const ctx = canvas.getContext('2d'); ctx.drawImage(img, 0, 0); - const dataURL = canvas.toDataURL("image/png"); + const dataURL = canvas.toDataURL('image/png'); resolve(dataURL); }; - img.onerror = error => { + img.onerror = (error) => { reject(error); }; img.src = url; + }); +} - });} - - - - +export const formatPercentage = (value: number, total: number): string => { + if (total === 0) { + return ''; + } + const percentage = (value / total) * 100; + const formattedPercentage = percentage.toFixed(1); + return `${formattedPercentage}%`; +}; From 95c99f6d4a76303b7868844b7f36d307759ec339 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Sat, 11 Nov 2023 15:11:11 +0100 Subject: [PATCH 219/419] wrap routerlink in span --- .../tables/ht-table/ht-table.component.html | 145 +++++++++++++----- 1 file changed, 105 insertions(+), 40 deletions(-) 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 4d30752d..1cd873f4 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 @@ -17,8 +17,12 @@ - +
@@ -30,8 +34,11 @@ - + @@ -43,101 +50,159 @@
- +
-
- - - + - - - - - +
- + - + - + + - {{tableColumn.name}} + + {{ tableColumn.name }} - {{tableColumn.name}} + + {{ tableColumn.name }} -
+
+
- - {{ icon.name }} + + {{ icon.name }} - {{ icon.name }} + {{ + icon.name + }} - - - {{ link.label }} - - - {{ element[tableColumn.dataKey] }} - - + + + + {{ link.label }} + + + {{ element[tableColumn.dataKey] }} + + + -
+
- + {{ element[tableColumn.dataKey] }}

- +
- \ No newline at end of file + From dff5f40d4029f0e59d4a1af20b7607843c8e723b Mon Sep 17 00:00:00 2001 From: itbf9 Date: Sat, 11 Nov 2023 15:12:04 +0100 Subject: [PATCH 220/419] Format --- src/app/core/_decorators/cacheable.ts | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/app/core/_decorators/cacheable.ts b/src/app/core/_decorators/cacheable.ts index d7e5eb40..5fbec8e8 100644 --- a/src/app/core/_decorators/cacheable.ts +++ b/src/app/core/_decorators/cacheable.ts @@ -1,4 +1,4 @@ -import { CacheService } from "../_services/shared/cache.service"; +import { CacheService } from '../_services/shared/cache.service'; /** * A decorator for caching the results of a method based on its arguments. @@ -7,7 +7,7 @@ import { CacheService } from "../_services/shared/cache.service"; * If provided, the cache key will be constructed based on the values of these attributes. * If not provided, the cache key will be based on the stringified arguments. * @returns A decorator function. -* + * * @example * ```typescript * class YourClass { @@ -18,15 +18,23 @@ import { CacheService } from "../_services/shared/cache.service"; * } * ``` */ -export function Cacheable(property: string[] | undefined = undefined): MethodDecorator { - return function (target: object, propertyKey: string | symbol, descriptor: PropertyDescriptor) { +export function Cacheable( + property: string[] | undefined = undefined +): MethodDecorator { + return function ( + target: object, + propertyKey: string | symbol, + descriptor: PropertyDescriptor + ) { const originalMethod = descriptor.value; // eslint-disable-next-line @typescript-eslint/no-explicit-any descriptor.value = function (...args: any[]): any { - let cacheKey: string + let cacheKey: string; if (property) { - const attributeValues = property.map((key) => args[0][key] || '').join('_'); + const attributeValues = property + .map((key) => args[0][key] || '') + .join('_'); cacheKey = `${propertyKey.toString()}_${attributeValues}`; } else { cacheKey = `${propertyKey.toString()}_${JSON.stringify(args)}`; @@ -44,4 +52,4 @@ export function Cacheable(property: string[] | undefined = undefined): MethodDec return descriptor; }; -} \ No newline at end of file +} From daab526da35d1e5c05a605645023342f7dd6fd76 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Sat, 11 Nov 2023 15:13:09 +0100 Subject: [PATCH 221/419] Add func to check if type is hashlist --- .../menus/base-menu/base-menu.component.ts | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/src/app/core/_components/menus/base-menu/base-menu.component.ts b/src/app/core/_components/menus/base-menu/base-menu.component.ts index 0c0bef13..f0022efd 100644 --- a/src/app/core/_components/menus/base-menu/base-menu.component.ts +++ b/src/app/core/_components/menus/base-menu/base-menu.component.ts @@ -1,18 +1,20 @@ +import { + ActionMenuEvent, + ActionMenuItem +} from '../action-menu/action-menu.model'; /* eslint-disable @angular-eslint/component-selector */ import { Component, EventEmitter, Input, Output } from '@angular/core'; -import { ActionMenuEvent, ActionMenuItem } from '../action-menu/action-menu.model'; @Component({ selector: 'base-menu', template: '' }) export class BaseMenuComponent { - @Input() disabled = false; @Input() data: any; - @Output() menuItemClicked: EventEmitter> = new EventEmitter>(); - + @Output() menuItemClicked: EventEmitter> = + new EventEmitter>(); actionMenuItems: ActionMenuItem[][] = []; @@ -40,8 +42,19 @@ export class BaseMenuComponent { } } + /** + * Check if the data row is of type "Hashlist." + * @returns `true` if the data row is a hashlist; otherwise, `false`. + */ + protected isHashlist(): boolean { + try { + return this.data['_id'] === this.data['hashlistId']; + } catch (error) { + return false; + } + } + onMenuItemClick(event: ActionMenuEvent): void { this.menuItemClicked.emit(event); } - -} \ No newline at end of file +} From f8bd2243bb7788f7fa0a7aa2309718db32cadd30 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Sat, 11 Nov 2023 15:13:42 +0100 Subject: [PATCH 222/419] Add hashlists datasource --- .../core/_datasources/hashlists.datasource.ts | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 src/app/core/_datasources/hashlists.datasource.ts diff --git a/src/app/core/_datasources/hashlists.datasource.ts b/src/app/core/_datasources/hashlists.datasource.ts new file mode 100644 index 00000000..8deb0d28 --- /dev/null +++ b/src/app/core/_datasources/hashlists.datasource.ts @@ -0,0 +1,53 @@ +import { catchError, finalize, of } from 'rxjs'; + +import { BaseDataSource } from './base.datasource'; +import { HashListFormat } from '../_constants/hashlist.config'; +import { Hashlist } from '../_models/hashlist.model'; +import { ListResponseWrapper } from '../_models/response.model'; +import { SERV } from '../_services/main.config'; + +export class HashlistsDataSource extends BaseDataSource { + loadAll(isArchived: boolean): void { + this.loadingSubject.next(true); + + const params = { + maxResults: this.maxResults, + expand: 'hashType,accessGroup', + filter: `isArchived=${isArchived}` + }; + + const hashLists$ = this.service.getAll(SERV.HASHLISTS, params); + + this.subscriptions.push( + hashLists$ + .pipe( + catchError(() => of([])), + finalize(() => this.loadingSubject.next(false)) + ) + .subscribe((response: ListResponseWrapper) => { + const rows: Hashlist[] = []; + response.values.forEach((value: Hashlist) => { + if (value.format !== HashListFormat.SUPERHASHLIST) { + const hashlist = value; + + hashlist.hashTypeDescription = hashlist.hashType.description; + + rows.push(hashlist); + } + }); + + this.setPaginationConfig( + this.pageSize, + this.currentPage, + response.total // TODO: This is incorrect because we exclude superhashlists + ); + this.setData(rows); + }) + ); + } + + reload(): void { + this.reset(); + //this.loadAll(); + } +} From ac17fb45f36b0114115c1ca39c97a147ebbc8016 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Sat, 11 Nov 2023 15:14:21 +0100 Subject: [PATCH 223/419] Add hashlist constants --- src/app/core/_constants/hashlist.config.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 src/app/core/_constants/hashlist.config.ts diff --git a/src/app/core/_constants/hashlist.config.ts b/src/app/core/_constants/hashlist.config.ts new file mode 100644 index 00000000..e1ac4c25 --- /dev/null +++ b/src/app/core/_constants/hashlist.config.ts @@ -0,0 +1,13 @@ +export const HashListFormat = { + TEXT: 0, + HCCAPX_PMKID: 1, + BINARY: 2, + SUPERHASHLIST: 3 +}; + +export const HashListFormatLabel = { + [HashListFormat.TEXT]: 'Text', + [HashListFormat.HCCAPX_PMKID]: 'HCCAPX / PMKID', + [HashListFormat.BINARY]: 'Binary', + [HashListFormat.SUPERHASHLIST]: 'Superhashlist' +}; From 2533c6bd8c776317022acdbd4ac47af3551c0f52 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Sat, 11 Nov 2023 15:14:53 +0100 Subject: [PATCH 224/419] Add hashlists table --- .../hashlists-table.component.html | 14 + .../hashlists-table.component.ts | 346 ++++++++++++++++++ .../hashlists-table.constants.ts | 9 + 3 files changed, 369 insertions(+) create mode 100644 src/app/core/_components/tables/hashlists-table/hashlists-table.component.html create mode 100644 src/app/core/_components/tables/hashlists-table/hashlists-table.component.ts create mode 100644 src/app/core/_components/tables/hashlists-table/hashlists-table.constants.ts diff --git a/src/app/core/_components/tables/hashlists-table/hashlists-table.component.html b/src/app/core/_components/tables/hashlists-table/hashlists-table.component.html new file mode 100644 index 00000000..ee8f1c5f --- /dev/null +++ b/src/app/core/_components/tables/hashlists-table/hashlists-table.component.html @@ -0,0 +1,14 @@ + diff --git a/src/app/core/_components/tables/hashlists-table/hashlists-table.component.ts b/src/app/core/_components/tables/hashlists-table/hashlists-table.component.ts new file mode 100644 index 00000000..c56ce62a --- /dev/null +++ b/src/app/core/_components/tables/hashlists-table/hashlists-table.component.ts @@ -0,0 +1,346 @@ +/* eslint-disable @angular-eslint/component-selector */ +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { HTTableColumn, HTTableIcon } from '../ht-table/ht-table.models'; +import { catchError, forkJoin } from 'rxjs'; + +import { ActionMenuEvent } from '../../menus/action-menu/action-menu.model'; +import { BaseTableComponent } from '../base-table/base-table.component'; +import { BulkActionMenuAction } from '../../menus/bulk-action-menu/bulk-action-menu.constants'; +import { Cacheable } from 'src/app/core/_decorators/cacheable'; +import { DialogData } from '../table-dialog/table-dialog.model'; +import { ExportMenuAction } from '../../menus/export-menu/export-menu.constants'; +import { HashListFormatLabel } from 'src/app/core/_constants/hashlist.config'; +import { Hashlist } from 'src/app/core/_models/hashlist.model'; +import { HashlistsDataSource } from 'src/app/core/_datasources/hashlists.datasource'; +import { HashlistsTableColumnLabel } from './hashlists-table.constants'; +import { RowActionMenuAction } from '../../menus/row-action-menu/row-action-menu.constants'; +import { SERV } from 'src/app/core/_services/main.config'; +import { TableDialogComponent } from '../table-dialog/table-dialog.component'; +import { formatPercentage } from 'src/app/shared/utils/util'; + +@Component({ + selector: 'hashlists-table', + templateUrl: './hashlists-table.component.html' +}) +export class HashlistsTableComponent + extends BaseTableComponent + implements OnInit, OnDestroy +{ + tableColumns: HTTableColumn[] = []; + dataSource: HashlistsDataSource; + + ngOnInit(): void { + this.tableColumns = this.getColumns(); + this.dataSource = new HashlistsDataSource(this.gs, this.uiService); + this.dataSource.setColumns(this.tableColumns); + this.dataSource.loadAll(false); + } + + ngOnDestroy(): void { + for (const sub of this.subscriptions) { + sub.unsubscribe(); + } + } + + filter(item: Hashlist, filterValue: string): boolean { + if ( + item.name.toLowerCase().includes(filterValue) || + item.hashTypeDescription.toLowerCase().includes(filterValue) + ) { + return true; + } + + return false; + } + + getColumns(): HTTableColumn[] { + const tableColumns = [ + { + name: HashlistsTableColumnLabel.ID, + dataKey: '_id', + isSortable: true, + export: async (hashlist: Hashlist) => hashlist._id + '' + }, + { + name: HashlistsTableColumnLabel.NAME, + dataKey: 'name', + icons: (hashlist: Hashlist) => this.renderSecretIcon(hashlist), + routerLink: (hashlist: Hashlist) => [ + { + routerLink: ['/hashlists', 'hashlist', hashlist._id, 'edit'] + } + ], + isSortable: true, + export: async (hashlist: Hashlist) => hashlist.name + }, + { + name: HashlistsTableColumnLabel.HASH_COUNT, + dataKey: 'hashCount', + isSortable: true, + routerLink: (hashlist: Hashlist) => [ + { + routerLink: ['/hashlists', 'hashes', 'hashlists', hashlist._id] + } + ], + export: async (hashlist: Hashlist) => hashlist.hashCount + '' + }, + { + name: HashlistsTableColumnLabel.CRACKED, + dataKey: 'cracked', + icons: (hashlist: Hashlist) => this.renderStatusIcon(hashlist), + render: (hashlist: Hashlist) => + formatPercentage(hashlist.cracked, hashlist.hashCount), + isSortable: true, + export: async (hashlist: Hashlist) => + formatPercentage(hashlist.cracked, hashlist.hashCount) + }, + { + name: HashlistsTableColumnLabel.HASHTYPE, + dataKey: 'hashTypeDescription', + isSortable: true, + export: async (hashlist: Hashlist) => hashlist.hashTypeDescription + }, + { + name: HashlistsTableColumnLabel.FORMAT, + dataKey: 'format', + isSortable: true, + render: (hashlist: Hashlist) => + this.sanitize(HashListFormatLabel[hashlist.format]), + export: async (hashlist: Hashlist) => + HashListFormatLabel[hashlist.format] + } + ]; + + return tableColumns; + } + + openDialog(data: DialogData) { + const dialogRef = this.dialog.open(TableDialogComponent, { + data: data, + width: '450px' + }); + + this.subscriptions.push( + dialogRef.afterClosed().subscribe((result) => { + if (result && result.action) { + switch (result.action) { + case RowActionMenuAction.DELETE: + this.rowActionDelete(result.data); + break; + case BulkActionMenuAction.ACTIVATE: + this.bulkActionActivate(result.data, true); + break; + case BulkActionMenuAction.DEACTIVATE: + this.bulkActionActivate(result.data, false); + break; + case BulkActionMenuAction.DELETE: + this.bulkActionDelete(result.data); + break; + } + } + }) + ); + } + + // --- Render functions --- + + @Cacheable(['_id', 'isSecret']) + async renderSecretIcon(hashlist: Hashlist): Promise { + const icons: HTTableIcon[] = []; + if (hashlist.isSecret) { + icons.push({ + name: 'lock', + tooltip: 'Secret' + }); + } + + return icons; + } + + @Cacheable(['_id', 'hashCount', 'cracked']) + async renderStatusIcon(hashlist: Hashlist): Promise { + const icons: HTTableIcon[] = []; + if (hashlist.hashCount === hashlist.cracked) { + icons.push({ + name: 'check_circle', + tooltip: 'Cracked', + cls: 'text-ok' + }); + } + + return icons; + } + + // --- Action functions --- + + exportActionClicked(event: ActionMenuEvent): void { + switch (event.menuItem.action) { + case ExportMenuAction.EXCEL: + this.exportService.toExcel( + 'hashtopolis-hashlists', + this.tableColumns, + event.data + ); + break; + case ExportMenuAction.CSV: + this.exportService.toCsv( + 'hashtopolis-hashlists', + this.tableColumns, + event.data + ); + break; + case ExportMenuAction.COPY: + this.exportService + .toClipboard(this.tableColumns, event.data) + .then(() => { + this.snackBar.open( + 'The selected rows are copied to the clipboard', + 'Close' + ); + }); + break; + } + } + + rowActionClicked(event: ActionMenuEvent): void { + switch (event.menuItem.action) { + case RowActionMenuAction.EDIT: + this.rowActionEdit(event.data); + break; + case RowActionMenuAction.EXPORT: + this.rowActionExport(event.data); + break; + case RowActionMenuAction.IMPORT: + this.rowActionImport(event.data); + break; + case RowActionMenuAction.DELETE: + this.openDialog({ + rows: [event.data], + title: `Deleting hashlist with id ${event.data._id} (${event.data.hashTypeDescription}) ...`, + icon: 'warning', + body: `Are you sure you want to delete it? Note that this action cannot be undone.`, + warn: true, + action: event.menuItem.action + }); + break; + } + } + + bulkActionClicked(event: ActionMenuEvent): void { + switch (event.menuItem.action) { + case BulkActionMenuAction.ACTIVATE: + this.openDialog({ + rows: event.data, + title: `Activating ${event.data.length} hashlists ...`, + icon: 'info', + listAttribute: 'hashlistName', + action: event.menuItem.action + }); + break; + case BulkActionMenuAction.DEACTIVATE: + this.openDialog({ + rows: event.data, + title: `Deactivating ${event.data.length} hashlists ...`, + icon: 'info', + listAttribute: 'hashlistName', + action: event.menuItem.action + }); + break; + case BulkActionMenuAction.DELETE: + this.openDialog({ + rows: event.data, + title: `Deleting ${event.data.length} hashlists ...`, + icon: 'warning', + body: `Are you sure you want to delete the above hashlists? Note that this action cannot be undone.`, + warn: true, + listAttribute: 'hashlistName', + action: event.menuItem.action + }); + break; + } + } + + /** + * @todo Implement error handling. + */ + private bulkActionActivate(hashlists: Hashlist[], isActive: boolean): void { + const requests = hashlists.map((hashlist: Hashlist) => { + return this.gs.update(SERV.AGENTS, hashlist._id, { isActive: isActive }); + }); + + const action = isActive ? 'activated' : 'deactivated'; + + this.subscriptions.push( + forkJoin(requests) + .pipe( + catchError((error) => { + console.error('Error during activation:', error); + return []; + }) + ) + .subscribe((results) => { + this.snackBar.open( + `Successfully ${action} ${results.length} hashlists!`, + 'Close' + ); + this.dataSource.reload(); + }) + ); + } + + /** + * @todo Implement error handling. + */ + private bulkActionDelete(hashlists: Hashlist[]): void { + const requests = hashlists.map((hashlist: Hashlist) => { + return this.gs.delete(SERV.AGENTS, hashlist._id); + }); + + this.subscriptions.push( + forkJoin(requests) + .pipe( + catchError((error) => { + console.error('Error during deletion:', error); + return []; + }) + ) + .subscribe((results) => { + this.snackBar.open( + `Successfully deleted ${results.length} hashlists!`, + 'Close' + ); + this.dataSource.reload(); + }) + ); + } + + /** + * @todo Implement error handling. + */ + private rowActionDelete(hashlist: Hashlist): void { + this.subscriptions.push( + this.gs.delete(SERV.HASHLISTS, hashlist._id).subscribe(() => { + this.snackBar.open('Successfully deleted hashlist!', 'Close'); + this.dataSource.reload(); + }) + ); + } + + private rowActionEdit(hashlist: Hashlist): void { + this.router.navigate(['/hashlists', 'hashlist', hashlist._id, 'edit']); + } + + /** + * @todo Implement export action. + */ + private rowActionExport(hashlist: Hashlist): void { + this.router.navigate(['/hashlists', hashlist._id, 'copy']); + } + + /** + * @todo Implement import action. + */ + private rowActionImport(hashlist: Hashlist): void { + this.router.navigate(['/hashlists', hashlist._id, 'copy']); + } +} diff --git a/src/app/core/_components/tables/hashlists-table/hashlists-table.constants.ts b/src/app/core/_components/tables/hashlists-table/hashlists-table.constants.ts new file mode 100644 index 00000000..9a6395a8 --- /dev/null +++ b/src/app/core/_components/tables/hashlists-table/hashlists-table.constants.ts @@ -0,0 +1,9 @@ +export const HashlistsTableColumnLabel = { + ID: 'ID', + NAME: 'Name', + STATUS: 'Status', + HASHTYPE: 'Hash Type', + FORMAT: 'Format', + CRACKED: 'Cracked', + HASH_COUNT: 'Hash Count' +}; From bac631d9f671e1a0258658923e220fbd136cda7c Mon Sep 17 00:00:00 2001 From: itbf9 Date: Sat, 11 Nov 2023 15:15:52 +0100 Subject: [PATCH 225/419] Update default table columns for hashlists table --- src/app/core/_models/config-ui.model.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/core/_models/config-ui.model.ts b/src/app/core/_models/config-ui.model.ts index eddf8a1c..928b4873 100644 --- a/src/app/core/_models/config-ui.model.ts +++ b/src/app/core/_models/config-ui.model.ts @@ -50,7 +50,7 @@ export const uiConfigDefault: UIConfig = { HashlistsTableColumnLabel.HASHTYPE, HashlistsTableColumnLabel.FORMAT, HashlistsTableColumnLabel.CRACKED, - HashlistsTableColumnLabel.PRE_CRACKED + HashlistsTableColumnLabel.HASH_COUNT ] }, refreshPage: false, From 82e597593b68f44de825cca901e207250e496c4d Mon Sep 17 00:00:00 2001 From: itbf9 Date: Sat, 11 Nov 2023 15:16:30 +0100 Subject: [PATCH 226/419] Add hashlist menus --- .../bulk-action-menu.component.ts | 47 ++++++++++++---- .../row-action-menu.component.ts | 54 ++++++++++++++++--- .../row-action-menu.constants.ts | 42 ++++++++------- 3 files changed, 106 insertions(+), 37 deletions(-) diff --git a/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.component.ts b/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.component.ts index a36ae825..752e8caa 100644 --- a/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.component.ts +++ b/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.component.ts @@ -1,31 +1,56 @@ +import { + BulkActionMenuAction, + BulkActionMenuLabel +} from './bulk-action-menu.constants'; /* eslint-disable @angular-eslint/component-selector */ -import { Component, Input, OnInit, } from '@angular/core'; +import { Component, Input, OnInit } from '@angular/core'; + import { BaseMenuComponent } from '../base-menu/base-menu.component'; -import { BulkActionMenuAction, BulkActionMenuLabel } from './bulk-action-menu.constants'; import { DataType } from '../../tables/ht-table/ht-table.models'; @Component({ selector: 'bulk-action-menu', templateUrl: './bulk-action-menu.component.html' }) -export class BulkActionMenuComponent extends BaseMenuComponent implements OnInit { - - @Input() dataType: DataType +export class BulkActionMenuComponent + extends BaseMenuComponent + implements OnInit +{ + @Input() dataType: DataType; ngOnInit(): void { if (this.dataType === 'agents') { this.getAgentMenu(); - } else if (this.dataType === 'tasks') { - this.getTaskMenu(); + } else if (this.dataType === 'hashlists') { + this.getHashlistMenu(); } } + private getHashlistMenu(): void { + this.actionMenuItems[0] = [ + { + label: BulkActionMenuLabel.ARCHIVE_TASKS, + action: BulkActionMenuAction.ARCHIVE, + icon: 'archive' + } + ]; + + this.actionMenuItems[1] = [ + { + label: BulkActionMenuLabel.DELETE_TASKS, + action: BulkActionMenuAction.DELETE, + icon: 'delete', + red: true + } + ]; + } + private getTaskMenu(): void { this.actionMenuItems[0] = [ { label: BulkActionMenuLabel.ARCHIVE_TASKS, action: BulkActionMenuAction.ARCHIVE, - icon: 'archive', + icon: 'archive' } ]; @@ -44,12 +69,12 @@ export class BulkActionMenuComponent extends BaseMenuComponent implements OnInit { label: BulkActionMenuLabel.ACTIVATE_AGENTS, action: BulkActionMenuAction.ACTIVATE, - icon: 'radio_button_checked', + icon: 'radio_button_checked' }, { label: BulkActionMenuLabel.DEACTIVATE_AGENTS, action: BulkActionMenuAction.DEACTIVATE, - icon: 'radio_button_unchecked', + icon: 'radio_button_unchecked' } ]; @@ -62,4 +87,4 @@ export class BulkActionMenuComponent extends BaseMenuComponent implements OnInit } ]; } -} \ No newline at end of file +} diff --git a/src/app/core/_components/menus/row-action-menu/row-action-menu.component.ts b/src/app/core/_components/menus/row-action-menu/row-action-menu.component.ts index 585cc74b..c4a1a499 100644 --- a/src/app/core/_components/menus/row-action-menu/row-action-menu.component.ts +++ b/src/app/core/_components/menus/row-action-menu/row-action-menu.component.ts @@ -1,20 +1,27 @@ /* eslint-disable @angular-eslint/component-selector */ -import { Component, OnInit } from "@angular/core"; -import { RowActionMenuAction, RowActionMenuLabel } from "./row-action-menu.constants"; -import { BaseMenuComponent } from "../base-menu/base-menu.component"; +import { Component, OnInit } from '@angular/core'; +import { + RowActionMenuAction, + RowActionMenuLabel +} from './row-action-menu.constants'; +import { BaseMenuComponent } from '../base-menu/base-menu.component'; @Component({ selector: 'row-action-menu', templateUrl: './row-action-menu.component.html' }) -export class RowActionMenuComponent extends BaseMenuComponent implements OnInit { - +export class RowActionMenuComponent + extends BaseMenuComponent + implements OnInit +{ ngOnInit(): void { if (this.isAgent()) { this.getAgentMenu(); } else if (this.isTask()) { this.getTaskMenu(); + } else if (this.isHashlist()) { + this.getHashlistMenu(); } } @@ -39,6 +46,37 @@ export class RowActionMenuComponent extends BaseMenuComponent implements OnInit ]; } + /** + * Get the context menu items for an agent data row. + */ + private getHashlistMenu(): void { + this.actionMenuItems[0] = [ + { + label: RowActionMenuLabel.EDIT_HASHLIST, + action: RowActionMenuAction.EDIT, + icon: 'edit' + }, + { + label: RowActionMenuLabel.IMPORT_HASHLIST, + action: RowActionMenuAction.IMPORT, + icon: 'arrow_upwards' + }, + { + label: RowActionMenuLabel.EXPORT_HASHLIST, + action: RowActionMenuAction.EXPORT, + icon: 'arrow_downward' + } + ]; + this.actionMenuItems[1] = [ + { + label: RowActionMenuLabel.DELETE_HASHLIST, + action: RowActionMenuAction.DELETE, + icon: 'delete', + red: true + } + ]; + } + /** * Get the context menu items for a task data row. */ @@ -48,7 +86,7 @@ export class RowActionMenuComponent extends BaseMenuComponent implements OnInit label: RowActionMenuLabel.EDIT_TASK, action: RowActionMenuAction.EDIT, icon: 'edit' - }, + } ]; this.actionMenuItems[1] = [ @@ -56,7 +94,7 @@ export class RowActionMenuComponent extends BaseMenuComponent implements OnInit label: RowActionMenuLabel.DELETE_TASK, action: RowActionMenuAction.DELETE, icon: 'delete', - red: true, + red: true } ]; @@ -85,4 +123,4 @@ export class RowActionMenuComponent extends BaseMenuComponent implements OnInit icon: 'archive' }); } -} \ No newline at end of file +} 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 87fa0f6a..2fbdd98c 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 @@ -1,19 +1,25 @@ -export class RowActionMenuLabel { - static readonly EDIT_AGENT = 'Edit'; - static readonly DELETE_AGENT = 'Delete'; - static readonly EDIT_TASK = 'Edit'; - static readonly COPY_TO_TASK = 'Copy to Task'; - static readonly COPY_TO_PRETASK = 'Copy to Pretask'; - static readonly EDIT_SUBTASKS = 'Edit Subtasks'; - static readonly ARCHIVE_TASK = 'Archive'; - static readonly DELETE_TASK = 'Delete'; -} +export const RowActionMenuLabel = { + EDIT_AGENT: 'Edit', + DELETE_AGENT: 'Delete', + EDIT_TASK: 'Edit', + COPY_TO_TASK: 'Copy to Task', + COPY_TO_PRETASK: 'Copy to Pretask', + EDIT_SUBTASKS: 'Edit Subtasks', + ARCHIVE_TASK: 'Archive', + DELETE_TASK: 'Delete', + EDIT_HASHLIST: 'Edit', + DELETE_HASHLIST: 'Delete', + IMPORT_HASHLIST: 'Import Hashlist', + EXPORT_HASHLIST: 'Export Hashlist' +}; -export class RowActionMenuAction { - static readonly EDIT = 'edit'; - static readonly DELETE = 'delete'; - static readonly ARCHIVE = 'archive'; - static readonly COPY_TO_TASK = 'copy-to-task'; - static readonly COPY_TO_PRETASK = 'copy-to-pretask'; - static readonly EDIT_SUBTASKS = 'edit-subtasks'; -} \ No newline at end of file +export const RowActionMenuAction = { + EDIT: 'edit', + DELETE: 'delete', + ARCHIVE: 'archive', + COPY_TO_TASK: 'copy-to-task', + COPY_TO_PRETASK: 'copy-to-pretask', + EDIT_SUBTASKS: 'edit-subtasks', + IMPORT: 'import', + EXPORT: 'export' +}; From eb69d8209a15cb46ee1ee4e2b60115e345dff9d4 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Sat, 11 Nov 2023 15:17:41 +0100 Subject: [PATCH 227/419] Use hashlists table --- .../hashlist/hashlist.component.html | 74 +--- .../hashlists/hashlist/hashlist.component.ts | 355 +----------------- 2 files changed, 9 insertions(+), 420 deletions(-) diff --git a/src/app/hashlists/hashlist/hashlist.component.html b/src/app/hashlists/hashlist/hashlist.component.html index 8b9025fc..72f70519 100644 --- a/src/app/hashlists/hashlist/hashlist.component.html +++ b/src/app/hashlists/hashlist/hashlist.component.html @@ -1,69 +1,9 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - -
IDNameStatusHash typeFormatCrackedPre-crackedActions
{{ list.hashlistId }} - {{ list.name | shortenString:35 }} - - - - {{ list.hashType.description }}{{ list.format | staticArray:'formats' }} -
- - {{ list.cracked / list.hashCount | percent:'1.2-2' }} - - ( - {{ list.cracked }} - / - {{ list.hashCount }} - ) -
-
- - - - - - - - - - - - - -
+ +
diff --git a/src/app/hashlists/hashlist/hashlist.component.ts b/src/app/hashlists/hashlist/hashlist.component.ts index b782fd60..22b64351 100644 --- a/src/app/hashlists/hashlist/hashlist.component.ts +++ b/src/app/hashlists/hashlist/hashlist.component.ts @@ -1,358 +1,7 @@ -import { faEdit, faTrash, faLock, faFileImport, faFileExport, faArchive, faPlus, faHomeAlt, faCheckCircle } from '@fortawesome/free-solid-svg-icons'; -import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core'; -import { ActivatedRoute, Router } from '@angular/router'; -import { DataTableDirective } from 'angular-datatables'; -import { Subject, Subscription } from 'rxjs'; - -import { AutoTitleService } from 'src/app/core/_services/shared/autotitle.service'; -import { AlertService } from 'src/app/core/_services/shared/alert.service'; -import { GlobalService } from 'src/app/core/_services/main.service'; -import { environment } from './../../../environments/environment'; -import { SERV } from '../../core/_services/main.config'; - -declare let $:any; +import { Component } from '@angular/core'; @Component({ selector: 'app-hashlist', templateUrl: './hashlist.component.html' }) -/** - * HashlistComponent is a component that manages and displays all hashlist data. - * - * It uses DataTables to display and interact with the users hashlist data, including exporting, deleting, bulk actions - * and refreshing the table. - */ -export class HashlistComponent implements OnInit, OnDestroy { - - // Font Awesome icons - faCheckCircle=faCheckCircle; - faFileImport=faFileImport; - faFileExport=faFileExport; - faArchive=faArchive; - faHome=faHomeAlt; - faTrash=faTrash; - faLock=faLock; - faPlus=faPlus; - faEdit=faEdit; - - // ViewChild reference to the DataTableDirective - @ViewChild(DataTableDirective) - dtElement: DataTableDirective; - - dtTrigger: Subject = new Subject(); - dtOptions: any = {}; - - // List of hashlists ToDo. Change to interface - public allhashlists: { - hashlistId: number, - name: string, - format: number, - hashTypeId: number, - hashCount: number, - saltSeparator: string, - cracked: number, - isSecret: boolean, - isHexSalt: string, - isSalted: string, - accessGroupId: number, - notes: string, - useBrain: number, - brainFeatures: number, - isArchived: string, - accessGroup: {accessGroupId: number, groupName: string} - hashType: {description: string, hashTypeId: number, isSalted: string, isSlowHash: string} - }[] = []; - - // Subscriptions to unsubscribe on component destruction - subscriptions: Subscription[] = [] - - private maxResults = environment.config.prodApiMaxResults; - - // View type and filter options - isArchived: boolean; - whichView: string; - - constructor( - private titleService: AutoTitleService, - private route:ActivatedRoute, - private alert: AlertService, - private gs: GlobalService, - private router: Router - ) { - titleService.set(['Show Hashlists']) - } - - /** - * Initializes DataTable and retrieves pretasks. - */ - - ngOnInit(): void { - this.getHashlists(); - this.setupTable(); - } - - /** - * Unsubscribes from active subscriptions. - */ - ngOnDestroy(): void { - this.dtTrigger.unsubscribe(); - for (const sub of this.subscriptions) { - sub.unsubscribe(); - } - } - - // Refresh the data and the DataTable - onRefresh() { - this.rerender(); - this.ngOnInit(); - } - - /** - * Rerender the DataTable. - */ - rerender(): void { - this.dtElement.dtInstance.then((dtInstance: DataTables.Api) => { - dtInstance.destroy(); - setTimeout(() => { - this.dtTrigger['new'].next(); - }); - }); - } - - /** - * Fetches Hashlists from the server filtering by live or archived - * Subscribes to the API response and updates the Users list. - */ - getHashlists(): void { - this.route.data.subscribe(data => { - switch (data['kind']) { - - case 'hashlist': - this.whichView = 'live'; - this.isArchived = false; - break; - - case 'archived': - this.whichView = 'archived'; - this.isArchived = true; - break; - - } - - const params = {'maxResults': this.maxResults, 'expand': 'hashType,accessGroup', 'filter': 'isArchived='+this.isArchived+''} - - this.subscriptions.push(this.gs.getAll(SERV.HASHLISTS,params).subscribe((list: any) => { - this.allhashlists = list.values.filter(u=> u.format != 3); // Exclude superhashlists - this.dtTrigger.next(void 0); - })); - }) - } - - /** - * Sets up the DataTable options and buttons. - * Customizes DataTable appearance and behavior. - */ - setupTable(): void { - // DataTables options - const self = this; - this.dtOptions = { - dom: 'Bfrtip', - scrollX: true, - pageLength: 25, - lengthMenu: [ - [10, 25, 50, 100, 250, -1], - [10, 25, 50, 100, 250, 'All'] - ], - stateSave: true, - select: { - style: 'multi', - }, - buttons: { - dom: { - button: { - className: 'dt-button buttons-collection btn btn-sm-dt btn-outline-gray-600-dt', - } - }, - buttons: [ - { - text: '↻', - autoClose: true, - action: function (e, dt, node, config) { - self.onRefresh(); - } - }, - { - extend: 'collection', - text: 'Export', - buttons: [ - { - extend: 'excelHtml5', - exportOptions: { - columns: [0, 1, 2, 3, 4, 5] - }, - }, - { - extend: 'print', - exportOptions: { - columns: [0, 1, 2, 3, 4, 5] - }, - customize: function ( win ) { - $(win.document.body) - .css( 'font-size', '10pt' ) - $(win.document.body).find( 'table' ) - .addClass( 'compact' ) - .css( 'font-size', 'inherit' ); - } - }, - { - extend: 'csvHtml5', - exportOptions: {modifier: {selected: true}}, - select: true, - customize: function (dt, csv) { - let data = ""; - for (let i = 0; i < dt.length; i++) { - data = "Hashlist\n\n"+ dt; - } - return data; - } - }, - 'copy' - ] - }, - { - extend: 'collection', - text: 'Bulk Actions', - drawCallback: function() { - const hasRows = this.api().rows({ filter: 'applied' }).data().length > 0; - $('.buttons-excel')[0].style.visibility = hasRows ? 'visible' : 'hidden' - }, - buttons: [ - { - text: 'Delete Hashlist(s)', - autoClose: true, - action: function (e, dt, node, config) { - self.onDeleteBulk(); - } - }, - { - text: 'Archive Hashlist(s)', - autoClose: true, - enabled: !this.isArchived, - action: function (e, dt, node, config) { - const edit = {isArchived: true}; - self.onUpdateBulk(edit); - } - } - ] - }, - { - text: !this.isArchived? 'Show Archived':'Show Live', - action: function () { - if(!self.isArchived) { - self.router.navigate(['hashlists/archived']); - } - if(self.isArchived){ - self.router.navigate(['hashlists/hashlist']); - } - } - }, - { - extend: 'colvis', - text: 'Column View', - columns: [ 1,2,3,4,5], - }, - { - extend: "pageLength", - className: "btn-sm" - }, - ], - } - } - } - - // Refresh the table after a delete operation - onRefreshTable() { - setTimeout(() => { - this.ngOnInit(); - this.rerender(); // Rerender the DataTable - }, 2000); - } - - /** - * Archives a hashlist with the given ID. - * - * @param {number} id - The ID of the hashlist to archive. - */ - onArchive(id: number){ - this.gs.archive(SERV.HASHLISTS,id).subscribe((list: any) => { - this.alert.okAlert('Archived!',''); - this.ngOnInit(); - this.rerender(); // rerender datatables - }); - } - - /** - * Handles the deletion of a hashlist. - * Displays a confirmation dialog and deletes the hashlist if confirmed. - * - * @param {number} id - The ID of the hashlist to delete. - * @param {string} name - The name of the hashlist. - */ - onDelete(id: number, name: string){ - this.alert.deleteConfirmation(name,'Hashlists').then((confirmed) => { - if (confirmed) { - // Deletion - this.subscriptions.push(this.gs.delete(SERV.HASHLISTS, id).subscribe(() => { - // Successful deletion - this.alert.okAlert(`Deleted Hashlist ${name}`, ''); - this.onRefreshTable(); // Refresh the table - })); - } else { - // Handle cancellation - this.alert.okAlert(`Hashlist ${name} is safe!`,''); - } - }); - } - - // Bulk actions - - /** - * Handles hashlist selection for bulk actions. - * - * @returns {number[]} - An array of selected hashlist IDs. - */ - onSelectedHashlists(){ - $(".dt-button-background").trigger("click"); - const selection = $($(this.dtElement).DataTable.tables()).DataTable().rows({ selected: true } ).data().pluck(0).toArray(); - if(selection.length == 0) { - this.alert.okAlert('You have not selected any Hashlist',''); - return; - } - const selectionnum = selection.map(i=>Number(i)); - - return selectionnum; - } - - /** - * Handles bulk deletion - * Delete the hashlists showing a progress bar - * - */ - async onDeleteBulk() { - const HashlistIds = this.onSelectedHashlists(); - this.alert.bulkDeleteAlert(HashlistIds,'Hashlists',SERV.HASHLISTS); - this.onRefreshTable(); - } - - /** - * Updates the selected hashlists with the given value. - * - * @param {any} value - The value to update the selected hashlists with. - */ - async onUpdateBulk(value: any) { - const HashlistIds = this.onSelectedHashlists(); - this.alert.bulkUpdateAlert(HashlistIds,value,'Hashlists',SERV.HASHLISTS); - this.onRefreshTable(); - } - -} +export class HashlistComponent {} From 6a54c1b94167df653e0b6be829d0521bd259a2e6 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Sat, 11 Nov 2023 15:18:29 +0100 Subject: [PATCH 228/419] Import core components --- src/app/hashlists/hashlists.module.ts | 42 ++++++++++++++------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/src/app/hashlists/hashlists.module.ts b/src/app/hashlists/hashlists.module.ts index 4bcd475c..8279d026 100644 --- a/src/app/hashlists/hashlists.module.ts +++ b/src/app/hashlists/hashlists.module.ts @@ -1,26 +1,27 @@ -import { FontAwesomeModule } from "@fortawesome/angular-fontawesome"; -import { FormsModule, ReactiveFormsModule } from "@angular/forms"; -import { ComponentsModule } from "../shared/components.module"; -import { NgbModule } from "@ng-bootstrap/ng-bootstrap"; -import { DataTablesModule } from "angular-datatables"; -import { PipesModule } from "../shared/pipes.module"; -import { CommonModule } from "@angular/common"; -import { RouterModule } from "@angular/router"; -import { NgModule } from "@angular/core"; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; -import { NewSuperhashlistComponent } from "./new-superhashlist/new-superhashlist.component"; -import { SuperhashlistComponent } from "./superhashlist/superhashlist.component"; -import { EditHashlistComponent } from "./edit-hashlist/edit-hashlist.component"; -import { NewHashlistComponent } from "./new-hashlist/new-hashlist.component"; -import { SearchHashComponent } from "./search-hash/search-hash.component"; -import { ShowCracksComponent } from "./show-cracks/show-cracks.component"; -import { HashlistRoutingModule } from "./hashlists-routing.module"; -import { HashlistComponent } from "./hashlist/hashlist.component"; -import { DirectivesModule } from "../shared/directives.module"; +import { CommonModule } from '@angular/common'; +import { ComponentsModule } from '../shared/components.module'; +import { CoreComponentsModule } from '../core/_components/core-components.module'; +import { DataTablesModule } from 'angular-datatables'; +import { DirectivesModule } from '../shared/directives.module'; +import { EditHashlistComponent } from './edit-hashlist/edit-hashlist.component'; +import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; import { HashesComponent } from './hashes/hashes.component'; +import { HashlistComponent } from './hashlist/hashlist.component'; +import { HashlistRoutingModule } from './hashlists-routing.module'; +import { NewHashlistComponent } from './new-hashlist/new-hashlist.component'; +import { NewSuperhashlistComponent } from './new-superhashlist/new-superhashlist.component'; +import { NgModule } from '@angular/core'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { PipesModule } from '../shared/pipes.module'; +import { RouterModule } from '@angular/router'; +import { SearchHashComponent } from './search-hash/search-hash.component'; +import { ShowCracksComponent } from './show-cracks/show-cracks.component'; +import { SuperhashlistComponent } from './superhashlist/superhashlist.component'; @NgModule({ - declarations:[ + declarations: [ NewSuperhashlistComponent, SuperhashlistComponent, EditHashlistComponent, @@ -30,9 +31,10 @@ import { HashesComponent } from './hashes/hashes.component'; HashlistComponent, HashesComponent ], - imports:[ + imports: [ HashlistRoutingModule, ReactiveFormsModule, + CoreComponentsModule, FontAwesomeModule, DataTablesModule, DirectivesModule, From ebdeb55c342342e69cb00148d977083934303709 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Sat, 11 Nov 2023 15:18:49 +0100 Subject: [PATCH 229/419] Add todo comment --- .../tables/agents-table/agents-table.component.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/app/core/_components/tables/agents-table/agents-table.component.ts b/src/app/core/_components/tables/agents-table/agents-table.component.ts index 0d2462fd..dc8d0fde 100644 --- a/src/app/core/_components/tables/agents-table/agents-table.component.ts +++ b/src/app/core/_components/tables/agents-table/agents-table.component.ts @@ -368,6 +368,9 @@ export class AgentsTableComponent } } + /** + * @todo Implement error handling. + */ private bulkActionActivate(agents: Agent[], isActive: boolean): void { const requests = agents.map((agent: Agent) => { return this.gs.update(SERV.AGENTS, agent._id, { isActive: isActive }); @@ -393,6 +396,9 @@ export class AgentsTableComponent ); } + /** + * @todo Implement error handling. + */ private bulkActionDelete(agents: Agent[]): void { const requests = agents.map((agent: Agent) => { return this.gs.delete(SERV.AGENTS, agent._id); @@ -416,6 +422,9 @@ export class AgentsTableComponent ); } + /** + * @todo Implement error handling. + */ private rowActionDelete(agent: Agent): void { this.subscriptions.push( this.gs.delete(SERV.AGENTS, agent._id).subscribe(() => { From 81da8768f66896bc45c6a9b7e65e56edcd8f4e0e Mon Sep 17 00:00:00 2001 From: itbf9 Date: Sat, 11 Nov 2023 23:01:07 +0100 Subject: [PATCH 230/419] update style --- src/styles/components/_table.scss | 16 +++++++++++++ src/styles/theme.scss | 38 +++++++++++++++++++------------ 2 files changed, 40 insertions(+), 14 deletions(-) diff --git a/src/styles/components/_table.scss b/src/styles/components/_table.scss index b34801b4..15357afa 100644 --- a/src/styles/components/_table.scss +++ b/src/styles/components/_table.scss @@ -277,10 +277,26 @@ table.hashtopolis-table { div.table-actions { display: flex; justify-content: space-between; + align-items: flex-end; margin-bottom: 0.5rem; + font-size: 12px; .right-aligned { margin-left: auto; + + .mdc-notched-outline { + padding-top: 8px; + padding-bottom: 8px; + } + + .mat-icon { + padding-right: 0; + color: $primary-100; + } + + .mat-mdc-form-field-subscript-wrapper { + display: none; + } } } diff --git a/src/styles/theme.scss b/src/styles/theme.scss index 16e3a095..c0280ecc 100644 --- a/src/styles/theme.scss +++ b/src/styles/theme.scss @@ -1,5 +1,5 @@ @use '@angular/material' as mat; -@import "@angular/material/theming"; +@import '@angular/material/theming'; @include mat.core(); $hashtopolis-primary-palette: mat.define-palette(mat.$blue-grey-palette); @@ -7,23 +7,33 @@ $hashtopolis-accent-palette: mat.define-palette(mat.$cyan-palette); $hashtopolis-warn-palette: mat.define-palette(mat.$red-palette); // Include custom palettes in the theme -$hashtopolis-light-theme: mat.define-light-theme((color: (primary: $hashtopolis-primary-palette, - accent: $hashtopolis-accent-palette, - warn: $hashtopolis-warn-palette ), - typography: mat.define-typography-config(), - density: 0, - )); +$hashtopolis-light-theme: mat.define-light-theme( + ( + color: ( + primary: $hashtopolis-primary-palette, + accent: $hashtopolis-accent-palette, + warn: $hashtopolis-warn-palette + ), + typography: mat.define-typography-config(), + density: 0 + ) +); -$hashtopolis-dark-theme: mat.define-dark-theme((color: (primary: $hashtopolis-primary-palette, - accent: $hashtopolis-accent-palette, - warn: $hashtopolis-warn-palette ), - typography: mat.define-typography-config(), - density: 0, - )); +$hashtopolis-dark-theme: mat.define-dark-theme( + ( + color: ( + primary: $hashtopolis-primary-palette, + accent: $hashtopolis-accent-palette, + warn: $hashtopolis-warn-palette + ), + typography: mat.define-typography-config(), + density: 0 + ) +); @include mat.all-component-themes($hashtopolis-light-theme); .dark-theme { @include mat.all-component-colors($hashtopolis-dark-theme); background-color: var(--mat-toolbar-container-background-color); -} \ No newline at end of file +} From e4524766a2702c2ec2f26aa1bcfa8445d17d2a66 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Sat, 11 Nov 2023 23:07:41 +0100 Subject: [PATCH 231/419] Temp remove retries on delete --- src/app/core/_services/main.service.ts | 160 +++++++++++++------------ 1 file changed, 84 insertions(+), 76 deletions(-) diff --git a/src/app/core/_services/main.service.ts b/src/app/core/_services/main.service.ts index 54011377..a02e85f0 100644 --- a/src/app/core/_services/main.service.ts +++ b/src/app/core/_services/main.service.ts @@ -1,8 +1,8 @@ -import { Observable, tap, retryWhen, delay, take, debounceTime } from 'rxjs'; +import { Observable, debounceTime, delay, retryWhen, take, tap } from 'rxjs'; import { environment } from './../../../environments/environment'; import { Inject, Injectable, PLATFORM_ID } from '@angular/core'; import { AuthService } from './access/auth.service'; -import { HttpClient} from '@angular/common/http'; +import { HttpClient } from '@angular/common/http'; import { setParameter } from './buildparams'; import { Params } from '@angular/router'; import { ConfigService } from './shared/config.service'; @@ -11,66 +11,69 @@ import { ConfigService } from './shared/config.service'; providedIn: 'root' }) export class GlobalService { - constructor( private http: HttpClient, @Inject(PLATFORM_ID) private platformId: Object, - private as:AuthService, - private cs:ConfigService, - ) { } + private as: AuthService, + private cs: ConfigService + ) {} -/** - * Get logged user id - * @returns id -**/ + /** + * Get logged user id + * @returns id + **/ - get userId(){ + get userId() { return this.as.userId; } - -/** - * Returns all - * @param routerParams - to include multiple options such as Max number of results or filtering - * @returns Object -**/ - getAll(methodUrl: string, routerParams?: Params):Observable { + /** + * Returns all + * @param routerParams - to include multiple options such as Max number of results or filtering + * @returns Object + **/ + getAll(methodUrl: string, routerParams?: Params): Observable { let queryParams: Params = {}; if (routerParams) { - queryParams = setParameter(routerParams); + queryParams = setParameter(routerParams); } - return this.http.get(this.cs.getEndpoint() + methodUrl, {params: queryParams}) + return this.http.get(this.cs.getEndpoint() + methodUrl, { + params: queryParams + }); } -/** - * Returns an specific element - * @param id - element id - * @returns Object -**/ - get(methodUrl:string, id: number, routerParams?: Params):Observable { + /** + * Returns an specific element + * @param id - element id + * @returns Object + **/ + get(methodUrl: string, id: number, routerParams?: Params): Observable { let queryParams: Params = {}; if (routerParams) { - queryParams = setParameter(routerParams); + queryParams = setParameter(routerParams); } - return this.http.get(`${this.cs.getEndpoint() + methodUrl}/${id}`,{params: routerParams}) + return this.http.get(`${this.cs.getEndpoint() + methodUrl}/${id}`, { + params: routerParams + }); } -/** - * Create - * @param item - fields - * @returns Object -**/ - create(methodUrl:string, item: any): Observable { - return this.http.post(this.cs.getEndpoint() + methodUrl, item) + /** + * Create + * @param item - fields + * @returns Object + **/ + create(methodUrl: string, item: any): Observable { + return this.http.post(this.cs.getEndpoint() + methodUrl, item); } -/** - * Deletes a element - * @param id - element id - * @returns Object -**/ + /** + * Deletes a element + * @param id - element id + * @returns Object + **/ delete(methodUrl: string, id: number): Observable { - return this.http.delete(this.cs.getEndpoint() + methodUrl +'/'+ id) + return this.http.delete(this.cs.getEndpoint() + methodUrl + '/' + id); + /* .pipe( tap(data => console.log(JSON.stringify(data))), retryWhen(errors => { @@ -82,52 +85,57 @@ export class GlobalService { ); } ) ); + */ } -/** - * Update element information - * @param id - element id - * @param arr - fields to be updated - * @returns Object -**/ + /** + * Update element information + * @param id - element id + * @param arr - fields to be updated + * @returns Object + **/ update(methodUrl: string, id: number, arr: any): Observable { - return this.http.patch(this.cs.getEndpoint() + methodUrl + '/' + id, arr) - .pipe( - debounceTime(2000) - ); + return this.http + .patch(this.cs.getEndpoint() + methodUrl + '/' + id, arr) + .pipe(debounceTime(2000)); } -/** - * Update agent information - * @param id - agent id - * @param arr - fields to be updated - * @returns Object -**/ + /** + * Update agent information + * @param id - agent id + * @param arr - fields to be updated + * @returns Object + **/ archive(methodUrl: string, id: number): Observable { - return this.http.patch(this.cs.getEndpoint() + methodUrl + '/' + id, {isArchived: true}) + return this.http.patch( + this.cs.getEndpoint() + methodUrl + '/' + id, + { isArchived: true } + ); } -/** - * Helper Create function - * @param option - method used. ie. /abort /reset /importFile - * @param arr - fields to be updated - * @returns Object -**/ + /** + * Helper Create function + * @param option - method used. ie. /abort /reset /importFile + * @param arr - fields to be updated + * @returns Object + **/ chelper(methodUrl: string, option: string, arr: any): Observable { - return this.http.post(this.cs.getEndpoint() + methodUrl + '/' + option, arr) + return this.http.post( + this.cs.getEndpoint() + methodUrl + '/' + option, + arr + ); } -/** - * Helper Update function - * @param option - method used. ie. /abort /reset /importFile - * @param arr - fields to be updated - * @returns Object -**/ + /** + * Helper Update function + * @param option - method used. ie. /abort /reset /importFile + * @param arr - fields to be updated + * @returns Object + **/ uhelper(methodUrl: string, option: string, arr: any): Observable { - return this.http.patch(this.cs.getEndpoint() + methodUrl + '/' + option, arr) + return this.http.patch( + this.cs.getEndpoint() + methodUrl + '/' + option, + arr + ); } - - - - } From b7e670ddce8324cd68afae911d5831b3c677dfca Mon Sep 17 00:00:00 2001 From: itbf9 Date: Sat, 11 Nov 2023 23:10:15 +0100 Subject: [PATCH 232/419] Add snackbar options + import input + slidetoggle --- src/app/core/_components/core-components.module.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/app/core/_components/core-components.module.ts b/src/app/core/_components/core-components.module.ts index 79ecc751..84d060bf 100644 --- a/src/app/core/_components/core-components.module.ts +++ b/src/app/core/_components/core-components.module.ts @@ -27,6 +27,7 @@ import { MatMenuModule } from '@angular/material/menu'; import { MatPaginatorModule } from '@angular/material/paginator'; import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; import { MatSelectModule } from '@angular/material/select'; +import { MatSlideToggleModule } from '@angular/material/slide-toggle'; import { MatSortModule } from '@angular/material/sort'; import { MatTableModule } from '@angular/material/table'; import { MatTooltipModule } from '@angular/material/tooltip'; @@ -70,6 +71,8 @@ import { TableDialogComponent } from './tables/table-dialog/table-dialog.compone MatDialogModule, MatTooltipModule, MatDividerModule, + MatSlideToggleModule, + MatInputModule, RouterModule, FormsModule ], @@ -87,7 +90,10 @@ import { TableDialogComponent } from './tables/table-dialog/table-dialog.compone HashlistsTableComponent ], providers: [ - { provide: MAT_SNACK_BAR_DEFAULT_OPTIONS, useValue: { duration: 2500 } } + { + provide: MAT_SNACK_BAR_DEFAULT_OPTIONS, + useValue: { duration: 2500, verticalPosition: 'top' } + } ] }) export class CoreComponentsModule {} From fd253a2be8105edef1096064200aea6ea80c3417 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Sat, 11 Nov 2023 23:11:16 +0100 Subject: [PATCH 233/419] update bulk menu --- .../bulk-action-menu.component.html | 10 ++++- .../bulk-action-menu.component.ts | 43 ++++++++++++------- .../bulk-action-menu.constants.ts | 28 ++++++------ 3 files changed, 51 insertions(+), 30 deletions(-) diff --git a/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.component.html b/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.component.html index cef10e34..1164cade 100644 --- a/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.component.html +++ b/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.component.html @@ -1,2 +1,8 @@ - \ No newline at end of file + diff --git a/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.component.ts b/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.component.ts index 752e8caa..3f683f32 100644 --- a/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.component.ts +++ b/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.component.ts @@ -5,6 +5,7 @@ import { /* eslint-disable @angular-eslint/component-selector */ import { Component, Input, OnInit } from '@angular/core'; +import { ActionMenuItem } from '../action-menu/action-menu.model'; import { BaseMenuComponent } from '../base-menu/base-menu.component'; import { DataType } from '../../tables/ht-table/ht-table.models'; @@ -17,8 +18,13 @@ export class BulkActionMenuComponent implements OnInit { @Input() dataType: DataType; + @Input() isArchived: boolean; ngOnInit(): void { + this.loadMenu(); + } + + private loadMenu(): void { if (this.dataType === 'agents') { this.getAgentMenu(); } else if (this.dataType === 'hashlists') { @@ -27,22 +33,25 @@ export class BulkActionMenuComponent } private getHashlistMenu(): void { - this.actionMenuItems[0] = [ - { - label: BulkActionMenuLabel.ARCHIVE_TASKS, - action: BulkActionMenuAction.ARCHIVE, - icon: 'archive' - } - ]; + const deleteMenuAction: ActionMenuItem = { + label: BulkActionMenuLabel.DELETE_HASHLISTS, + action: BulkActionMenuAction.DELETE, + icon: 'delete', + red: true + }; - this.actionMenuItems[1] = [ - { - label: BulkActionMenuLabel.DELETE_TASKS, - action: BulkActionMenuAction.DELETE, - icon: 'delete', - red: true - } - ]; + if (this.isArchived) { + this.actionMenuItems[0] = [deleteMenuAction]; + } else { + this.actionMenuItems[0] = [ + { + label: BulkActionMenuLabel.ARCHIVE_HASHLISTS, + action: BulkActionMenuAction.ARCHIVE, + icon: 'archive' + } + ]; + this.actionMenuItems[1] = [deleteMenuAction]; + } } private getTaskMenu(): void { @@ -87,4 +96,8 @@ export class BulkActionMenuComponent } ]; } + + reload(): void { + this.loadMenu(); + } } diff --git a/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.constants.ts b/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.constants.ts index b1023d98..d0daf483 100644 --- a/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.constants.ts +++ b/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.constants.ts @@ -1,14 +1,16 @@ -export class BulkActionMenuLabel { - static readonly DELETE_AGENTS = 'Delete Agents'; - static readonly ACTIVATE_AGENTS = 'Activate Agents'; - static readonly DEACTIVATE_AGENTS = 'Deactivate Agents'; - static readonly ARCHIVE_TASKS = 'Archive Tasks'; - static readonly DELETE_TASKS = 'Delete Tasks'; -} +export const BulkActionMenuLabel = { + DELETE_AGENTS: 'Delete Agents', + ACTIVATE_AGENTS: 'Activate Agents', + DEACTIVATE_AGENTS: 'Deactivate Agents', + ARCHIVE_TASKS: 'Archive Tasks', + DELETE_TASKS: 'Delete Tasks', + DELETE_HASHLISTS: 'Delete Hashlists', + ARCHIVE_HASHLISTS: 'Archive Hashlists' +}; -export class BulkActionMenuAction { - static readonly DELETE = 'bulk-delete'; - static readonly ACTIVATE = 'bulk-activate'; - static readonly DEACTIVATE = 'bulk-deactivate'; - static readonly ARCHIVE = 'bulk-archive'; -} \ No newline at end of file +export const BulkActionMenuAction = { + DELETE: 'bulk-delete', + ACTIVATE: 'bulk-activate', + DEACTIVATE: 'bulk-deactivate', + ARCHIVE: 'bulk-archive' +}; From d26085ab3fe739519c8aaaf9296d6de99c1d53bc Mon Sep 17 00:00:00 2001 From: itbf9 Date: Sat, 11 Nov 2023 23:11:46 +0100 Subject: [PATCH 234/419] Update action menu --- .../action-menu/action-menu.component.ts | 76 +++++++++++-------- .../row-action-menu.component.ts | 58 +++++++------- 2 files changed, 78 insertions(+), 56 deletions(-) diff --git a/src/app/core/_components/menus/action-menu/action-menu.component.ts b/src/app/core/_components/menus/action-menu/action-menu.component.ts index 76c67016..aa3d7c80 100644 --- a/src/app/core/_components/menus/action-menu/action-menu.component.ts +++ b/src/app/core/_components/menus/action-menu/action-menu.component.ts @@ -1,19 +1,27 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -/* eslint-disable @angular-eslint/component-selector */ -import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core'; import { ActionMenuEvent, ActionMenuItem } from './action-menu.model'; import { ActivatedRoute, NavigationEnd, Router } from '@angular/router'; +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable @angular-eslint/component-selector */ +import { + Component, + EventEmitter, + Input, + OnDestroy, + OnInit, + Output +} from '@angular/core'; + import { Subscription } from 'rxjs'; /** * Component representing an action menu with a list of menu items. - * + * * Each action menu item can have either an `action` or a `routerLink`. If - * the menu item `action` attribute is set an event is emitted with the - * menu item itself and the `data` privided when clicked. If the `routerLink` - * attribute is set the user will be routed on click. - * - * The actionMenuItems are divided into sections by providing them in separate + * the menu item `action` attribute is set an event is emitted with the + * menu item itself and the `data` privided when clicked. If the `routerLink` + * attribute is set the user will be routed on click. + * + * The actionMenuItems are divided into sections by providing them in separate * arrays like this: `[[A, B, C], [D, E, F]]`. This will generate two sections * separetad by a divider. * @@ -32,11 +40,10 @@ import { Subscription } from 'rxjs'; templateUrl: './action-menu.component.html' }) export class ActionMenuComponent implements OnInit, OnDestroy { - - private subscriptions: Subscription[] = [] + private subscriptions: Subscription[] = []; currentUrl: any[]; - isActive = false + isActive = false; /** Icon to be displayed in the menu button. */ @Input() icon: string; @@ -45,28 +52,34 @@ export class ActionMenuComponent implements OnInit, OnDestroy { /** Determines if the menu button is disabled. */ @Input() disabled = false; /** Custom CSS classes for styling. */ - @Input() cls = '' + @Input() cls = ''; /** Custom data to be associated with the menu. */ @Input() data: any; /** Two-dimensional array of sections / menu items. */ - @Input() actionMenuItems: ActionMenuItem[][] = [] + @Input() actionMenuItems: ActionMenuItem[][] = []; - @Output() menuItemClicked: EventEmitter> = new EventEmitter>(); + @Output() menuItemClicked: EventEmitter> = + new EventEmitter>(); - constructor(private router: Router, private route: ActivatedRoute) { } + constructor( + private router: Router, + private route: ActivatedRoute + ) {} ngOnInit(): void { - this.subscriptions.push(this.router.events.subscribe((event: any) => { - if (event instanceof NavigationEnd) { - this.currentUrl = event.url.split('/').slice(1) - this.checkIsActive() - } - })) + this.subscriptions.push( + this.router.events.subscribe((event: any) => { + if (event instanceof NavigationEnd) { + this.currentUrl = event.url.split('/').slice(1); + this.checkIsActive(); + } + }) + ); } ngOnDestroy(): void { for (const sub of this.subscriptions) { - sub.unsubscribe() + sub.unsubscribe(); } } @@ -74,13 +87,13 @@ export class ActionMenuComponent implements OnInit, OnDestroy { * Checks if a menu item should be considered active based on the current URL. * It sets the 'isActive' property to true if the menu item's 'routerLink' matches * or partially matches the beginning of the current URL. - * + * * The 'actionMenuItems' array should contain sections of menu items, where each * section is an array of menu items with 'routerLink' properties. - * + * */ checkIsActive(): void { - this.isActive = false + this.isActive = false; for (const section of this.actionMenuItems) { if (this.isActive) { break; @@ -89,8 +102,11 @@ export class ActionMenuComponent implements OnInit, OnDestroy { for (const item of section) { if (item.routerLink) { const partial = this.currentUrl.slice(0, item.routerLink.length); - if (item.routerLink && item.routerLink.every((value, index) => value === partial[index])) { - this.isActive = true + if ( + item.routerLink && + item.routerLink.every((value, index) => value === partial[index]) + ) { + this.isActive = true; break; } } @@ -104,7 +120,7 @@ export class ActionMenuComponent implements OnInit, OnDestroy { */ onMenuItemClick(menuItem: ActionMenuItem): void { if (menuItem.routerLink) { - this.router.navigate(menuItem.routerLink) + this.router.navigate(menuItem.routerLink); } else { this.menuItemClicked.emit({ menuItem: menuItem, @@ -112,4 +128,4 @@ export class ActionMenuComponent implements OnInit, OnDestroy { }); } } -} \ No newline at end of file +} diff --git a/src/app/core/_components/menus/row-action-menu/row-action-menu.component.ts b/src/app/core/_components/menus/row-action-menu/row-action-menu.component.ts index c4a1a499..d1bfe940 100644 --- a/src/app/core/_components/menus/row-action-menu/row-action-menu.component.ts +++ b/src/app/core/_components/menus/row-action-menu/row-action-menu.component.ts @@ -5,6 +5,7 @@ import { RowActionMenuLabel } from './row-action-menu.constants'; +import { ActionMenuItem } from '../action-menu/action-menu.model'; import { BaseMenuComponent } from '../base-menu/base-menu.component'; @Component({ @@ -50,33 +51,38 @@ export class RowActionMenuComponent * Get the context menu items for an agent data row. */ private getHashlistMenu(): void { - this.actionMenuItems[0] = [ - { - label: RowActionMenuLabel.EDIT_HASHLIST, - action: RowActionMenuAction.EDIT, - icon: 'edit' - }, - { - label: RowActionMenuLabel.IMPORT_HASHLIST, - action: RowActionMenuAction.IMPORT, - icon: 'arrow_upwards' - }, - { - label: RowActionMenuLabel.EXPORT_HASHLIST, - action: RowActionMenuAction.EXPORT, - icon: 'arrow_downward' - } - ]; - this.actionMenuItems[1] = [ - { - label: RowActionMenuLabel.DELETE_HASHLIST, - action: RowActionMenuAction.DELETE, - icon: 'delete', - red: true - } - ]; - } + this.actionMenuItems[0] = []; + + const deleteMenuItem: ActionMenuItem = { + label: RowActionMenuLabel.DELETE_HASHLIST, + action: RowActionMenuAction.DELETE, + icon: 'delete', + red: true + }; + if (this.data['isArchived']) { + this.actionMenuItems[0].push(deleteMenuItem); + } else { + this.actionMenuItems[0] = [ + { + label: RowActionMenuLabel.EDIT_HASHLIST, + action: RowActionMenuAction.EDIT, + icon: 'edit' + }, + { + label: RowActionMenuLabel.IMPORT_HASHLIST, + action: RowActionMenuAction.IMPORT, + icon: 'arrow_upwards' + }, + { + label: RowActionMenuLabel.EXPORT_HASHLIST, + action: RowActionMenuAction.EXPORT, + icon: 'arrow_downward' + } + ]; + this.actionMenuItems[1] = [deleteMenuItem]; + } + } /** * Get the context menu items for a task data row. */ From e6782c8c005e273c7318730c50a1d541f8e6aa32 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Sat, 11 Nov 2023 23:12:50 +0100 Subject: [PATCH 235/419] Add table reference --- .../agents-table/agents-table.component.html | 19 +++++++++++++++---- .../chunks-table/chunks-table.component.html | 1 + 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/app/core/_components/tables/agents-table/agents-table.component.html b/src/app/core/_components/tables/agents-table/agents-table.component.html index d7201cb4..7ed3ffba 100644 --- a/src/app/core/_components/tables/agents-table/agents-table.component.html +++ b/src/app/core/_components/tables/agents-table/agents-table.component.html @@ -1,4 +1,15 @@ - \ No newline at end of file + diff --git a/src/app/core/_components/tables/chunks-table/chunks-table.component.html b/src/app/core/_components/tables/chunks-table/chunks-table.component.html index 400c48b8..996821f3 100644 --- a/src/app/core/_components/tables/chunks-table/chunks-table.component.html +++ b/src/app/core/_components/tables/chunks-table/chunks-table.component.html @@ -1,4 +1,5 @@ Date: Sat, 11 Nov 2023 23:13:57 +0100 Subject: [PATCH 236/419] update ht-table --- .../tables/ht-table/ht-table.component.html | 20 ++++++++++++------- .../tables/ht-table/ht-table.component.ts | 9 ++++++++- 2 files changed, 21 insertions(+), 8 deletions(-) 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 1cd873f4..4a5d24fc 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 @@ -18,7 +18,9 @@
- + + filter_list + +
diff --git a/src/app/core/_components/tables/ht-table/ht-table.component.ts b/src/app/core/_components/tables/ht-table/ht-table.component.ts index 0920f768..c36f91b1 100644 --- a/src/app/core/_components/tables/ht-table/ht-table.component.ts +++ b/src/app/core/_components/tables/ht-table/ht-table.component.ts @@ -16,6 +16,7 @@ import { MatPaginator, PageEvent } from '@angular/material/paginator'; import { ActionMenuEvent } from '../../menus/action-menu/action-menu.model'; import { BaseDataSource } from 'src/app/core/_datasources/base.datasource'; +import { BulkActionMenuComponent } from '../../menus/bulk-action-menu/bulk-action-menu.component'; import { ColumnSelectionDialogComponent } from '../column-selection-dialog/column-selection-dialog.component'; import { LocalStorageService } from 'src/app/core/_services/storage/local-storage.service'; import { MatDialog } from '@angular/material/dialog'; @@ -109,6 +110,9 @@ export class HTTableComponent implements OnInit, AfterViewInit { /** Flag to enable or disable filtering. */ @Input() isFilterable = false; + /** Flag show archived data. */ + @Input() isArchived = false; + /** The list of table columns and their configurations. */ @Input() tableColumns: HTTableColumn[] = []; @@ -136,6 +140,8 @@ export class HTTableComponent implements OnInit, AfterViewInit { /** Fetches user customizations */ private uiSettings: UISettingsUtilityClass; + @ViewChild('bulkMenu') bulkMenu: BulkActionMenuComponent; + constructor( public dialog: MatDialog, private cd: ChangeDetectorRef, @@ -275,10 +281,11 @@ export class HTTableComponent implements OnInit, AfterViewInit { } /** - * Reloads the data in the table. + * Reloads the data in the table and the bulk menu. */ reload(): void { this.dataSource.reload(); + this.bulkMenu.reload(); } /** From c38f4314c7ae408978bf50fe63144540d870497d Mon Sep 17 00:00:00 2001 From: itbf9 Date: Sat, 11 Nov 2023 23:15:34 +0100 Subject: [PATCH 237/419] Add reference to table and reload function --- .../tables/base-table/base-table.component.ts | 47 ++++++++++++------- 1 file changed, 29 insertions(+), 18 deletions(-) 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 fafc461b..7f839ca5 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 @@ -1,26 +1,32 @@ /* eslint-disable @angular-eslint/component-selector */ -import { Component, Renderer2 } from '@angular/core'; -import { Router } from '@angular/router'; +import { Component, Input, Renderer2, ViewChild } from '@angular/core'; import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; -import { MatSnackBar } from '@angular/material/snack-bar'; -import { MatDialog } from '@angular/material/dialog'; -import { UISettingsUtilityClass } from 'src/app/shared/utils/config'; -import { Subscription } from 'rxjs'; +import { + UIConfig, + uiConfigDefault +} from 'src/app/core/_models/config-ui.model'; + +import { ExportService } from 'src/app/core/_services/export/export.service'; import { GlobalService } from 'src/app/core/_services/main.service'; +import { HTTableComponent } from '../ht-table/ht-table.component'; import { LocalStorageService } from 'src/app/core/_services/storage/local-storage.service'; -import { UIConfig, uiConfigDefault } from 'src/app/core/_models/config-ui.model'; +import { MatDialog } from '@angular/material/dialog'; +import { MatSnackBar } from '@angular/material/snack-bar'; +import { Router } from '@angular/router'; +import { Subscription } from 'rxjs'; import { UIConfigService } from 'src/app/core/_services/shared/storage.service'; -import { ExportService } from 'src/app/core/_services/export/export.service'; +import { UISettingsUtilityClass } from 'src/app/shared/utils/config'; @Component({ selector: 'base-table', template: '' }) export class BaseTableComponent { + protected uiSettings: UISettingsUtilityClass; + protected dateFormat: string; + protected subscriptions: Subscription[] = []; - protected uiSettings: UISettingsUtilityClass - protected dateFormat: string - protected subscriptions: Subscription[] = [] + @ViewChild('table') table: HTTableComponent; constructor( protected gs: GlobalService, @@ -31,10 +37,10 @@ export class BaseTableComponent { protected snackBar: MatSnackBar, protected uiService: UIConfigService, protected exportService: ExportService, - public dialog: MatDialog, + public dialog: MatDialog ) { - this.uiSettings = new UISettingsUtilityClass(settingsService) - this.dateFormat = this.getDateFormat() + this.uiSettings = new UISettingsUtilityClass(settingsService); + this.dateFormat = this.getDateFormat(); } /** @@ -42,9 +48,9 @@ export class BaseTableComponent { * @returns The date format string. */ private getDateFormat(): string { - const fmt = this.uiSettings.getSetting('timefmt') + const fmt = this.uiSettings.getSetting('timefmt'); - return fmt ? fmt : uiConfigDefault.timefmt + return fmt ? fmt : uiConfigDefault.timefmt; } /** @@ -53,7 +59,12 @@ export class BaseTableComponent { * @returns A SafeHtml object that represents the sanitized HTML. */ protected sanitize(html: string): SafeHtml { - return this.sanitizer.bypassSecurityTrustHtml(html) + return this.sanitizer.bypassSecurityTrustHtml(html); } -} \ No newline at end of file + reload(): void { + if (this.table) { + this.table.reload(); + } + } +} From 2a2944a000ed204b89c07823fe34cadad53f5a57 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Sat, 11 Nov 2023 23:16:29 +0100 Subject: [PATCH 238/419] Add isArchived and enable reload --- src/app/core/_datasources/hashlists.datasource.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/app/core/_datasources/hashlists.datasource.ts b/src/app/core/_datasources/hashlists.datasource.ts index 8deb0d28..fb1c1a3f 100644 --- a/src/app/core/_datasources/hashlists.datasource.ts +++ b/src/app/core/_datasources/hashlists.datasource.ts @@ -7,13 +7,19 @@ import { ListResponseWrapper } from '../_models/response.model'; import { SERV } from '../_services/main.config'; export class HashlistsDataSource extends BaseDataSource { - loadAll(isArchived: boolean): void { + private isArchived = false; + + setIsArchived(isArchived: boolean): void { + this.isArchived = isArchived; + } + + loadAll(): void { this.loadingSubject.next(true); const params = { maxResults: this.maxResults, expand: 'hashType,accessGroup', - filter: `isArchived=${isArchived}` + filter: `isArchived=${this.isArchived}` }; const hashLists$ = this.service.getAll(SERV.HASHLISTS, params); @@ -48,6 +54,6 @@ export class HashlistsDataSource extends BaseDataSource { reload(): void { this.reset(); - //this.loadAll(); + this.loadAll(); } } From 07296bbf7a14fe4fd66bc3346556623acf78baf2 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Sat, 11 Nov 2023 23:17:51 +0100 Subject: [PATCH 239/419] Add actions + pass isArchived to datasource --- .../hashlists-table.component.html | 2 + .../hashlists-table.component.ts | 69 ++++++++++--------- 2 files changed, 39 insertions(+), 32 deletions(-) diff --git a/src/app/core/_components/tables/hashlists-table/hashlists-table.component.html b/src/app/core/_components/tables/hashlists-table/hashlists-table.component.html index ee8f1c5f..cf9312a2 100644 --- a/src/app/core/_components/tables/hashlists-table/hashlists-table.component.html +++ b/src/app/core/_components/tables/hashlists-table/hashlists-table.component.html @@ -1,4 +1,5 @@ ): void { switch (event.menuItem.action) { - case BulkActionMenuAction.ACTIVATE: + case BulkActionMenuAction.ARCHIVE: this.openDialog({ rows: event.data, - title: `Activating ${event.data.length} hashlists ...`, + title: `Archiving ${event.data.length} hashlists ...`, icon: 'info', - listAttribute: 'hashlistName', - action: event.menuItem.action - }); - break; - case BulkActionMenuAction.DEACTIVATE: - this.openDialog({ - rows: event.data, - title: `Deactivating ${event.data.length} hashlists ...`, - icon: 'info', - listAttribute: 'hashlistName', + listAttribute: 'name', action: event.menuItem.action }); break; @@ -253,7 +243,7 @@ export class HashlistsTableComponent icon: 'warning', body: `Are you sure you want to delete the above hashlists? Note that this action cannot be undone.`, warn: true, - listAttribute: 'hashlistName', + listAttribute: 'name', action: event.menuItem.action }); break; @@ -263,18 +253,20 @@ export class HashlistsTableComponent /** * @todo Implement error handling. */ - private bulkActionActivate(hashlists: Hashlist[], isActive: boolean): void { + private bulkActionArchive(hashlists: Hashlist[], isArchived: boolean): void { const requests = hashlists.map((hashlist: Hashlist) => { - return this.gs.update(SERV.AGENTS, hashlist._id, { isActive: isActive }); + return this.gs.update(SERV.HASHLISTS, hashlist._id, { + isArchived: isArchived + }); }); - const action = isActive ? 'activated' : 'deactivated'; + const action = isArchived ? 'archived' : 'unarchived'; this.subscriptions.push( forkJoin(requests) .pipe( catchError((error) => { - console.error('Error during activation:', error); + console.error('Error during archiving:', error); return []; }) ) @@ -283,7 +275,7 @@ export class HashlistsTableComponent `Successfully ${action} ${results.length} hashlists!`, 'Close' ); - this.dataSource.reload(); + this.reload(); }) ); } @@ -293,7 +285,7 @@ export class HashlistsTableComponent */ private bulkActionDelete(hashlists: Hashlist[]): void { const requests = hashlists.map((hashlist: Hashlist) => { - return this.gs.delete(SERV.AGENTS, hashlist._id); + return this.gs.delete(SERV.HASHLISTS, hashlist._id); }); this.subscriptions.push( @@ -309,7 +301,7 @@ export class HashlistsTableComponent `Successfully deleted ${results.length} hashlists!`, 'Close' ); - this.dataSource.reload(); + this.reload(); }) ); } @@ -317,12 +309,20 @@ export class HashlistsTableComponent /** * @todo Implement error handling. */ - private rowActionDelete(hashlist: Hashlist): void { + private rowActionDelete(hashlists: Hashlist[]): void { this.subscriptions.push( - this.gs.delete(SERV.HASHLISTS, hashlist._id).subscribe(() => { - this.snackBar.open('Successfully deleted hashlist!', 'Close'); - this.dataSource.reload(); - }) + this.gs + .delete(SERV.HASHLISTS, hashlists[0]._id) + .pipe( + catchError((error) => { + console.error('Error during deletion:', error); + return []; + }) + ) + .subscribe(() => { + this.snackBar.open('Successfully deleted hashlist!', 'Close'); + this.reload(); + }) ); } @@ -343,4 +343,9 @@ export class HashlistsTableComponent private rowActionImport(hashlist: Hashlist): void { this.router.navigate(['/hashlists', hashlist._id, 'copy']); } + + setIsArchived(isArchived: boolean): void { + this.isArchived = isArchived; + this.dataSource.setIsArchived(isArchived); + } } From 74814c6480612a9c93cffac5e07ef10aaf048fb0 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Sat, 11 Nov 2023 23:18:48 +0100 Subject: [PATCH 240/419] Add slidetoggle --- .../hashlists/hashlist/hashlist.component.html | 8 ++++++-- .../hashlists/hashlist/hashlist.component.ts | 17 +++++++++++++++-- src/app/hashlists/hashlists.module.ts | 2 ++ 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/app/hashlists/hashlist/hashlist.component.html b/src/app/hashlists/hashlist/hashlist.component.html index 72f70519..030d1853 100644 --- a/src/app/hashlists/hashlist/hashlist.component.html +++ b/src/app/hashlists/hashlist/hashlist.component.html @@ -1,9 +1,13 @@ - + + Show Archived + +

+
diff --git a/src/app/hashlists/hashlist/hashlist.component.ts b/src/app/hashlists/hashlist/hashlist.component.ts index 22b64351..3172394a 100644 --- a/src/app/hashlists/hashlist/hashlist.component.ts +++ b/src/app/hashlists/hashlist/hashlist.component.ts @@ -1,7 +1,20 @@ -import { Component } from '@angular/core'; +import { Component, ViewChild } from '@angular/core'; + +import { HashlistsTableComponent } from 'src/app/core/_components/tables/hashlists-table/hashlists-table.component'; +import { MatSlideToggleChange } from '@angular/material/slide-toggle'; @Component({ selector: 'app-hashlist', templateUrl: './hashlist.component.html' }) -export class HashlistComponent {} +export class HashlistComponent { + @ViewChild('table') table: HashlistsTableComponent; + + pageTitle = 'Hashlists'; + + toggleIsArchived(event: MatSlideToggleChange): void { + this.table.setIsArchived(event.checked); + this.pageTitle = event.checked ? 'Hashlists (archived)' : 'Hashlists'; + this.table.reload(); + } +} diff --git a/src/app/hashlists/hashlists.module.ts b/src/app/hashlists/hashlists.module.ts index 8279d026..40a6302b 100644 --- a/src/app/hashlists/hashlists.module.ts +++ b/src/app/hashlists/hashlists.module.ts @@ -10,6 +10,7 @@ import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; import { HashesComponent } from './hashes/hashes.component'; import { HashlistComponent } from './hashlist/hashlist.component'; import { HashlistRoutingModule } from './hashlists-routing.module'; +import { MatSlideToggleModule } from '@angular/material/slide-toggle'; import { NewHashlistComponent } from './new-hashlist/new-hashlist.component'; import { NewSuperhashlistComponent } from './new-superhashlist/new-superhashlist.component'; import { NgModule } from '@angular/core'; @@ -37,6 +38,7 @@ import { SuperhashlistComponent } from './superhashlist/superhashlist.component' CoreComponentsModule, FontAwesomeModule, DataTablesModule, + MatSlideToggleModule, DirectivesModule, ComponentsModule, CommonModule, From d7ae78ae1c85f988ce05def3de6993b8077789ab Mon Sep 17 00:00:00 2001 From: itbf9 Date: Sun, 12 Nov 2023 00:26:01 +0100 Subject: [PATCH 241/419] update style --- src/styles/base/_base.scss | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/styles/base/_base.scss b/src/styles/base/_base.scss index e7b2471a..c741488d 100644 --- a/src/styles/base/_base.scss +++ b/src/styles/base/_base.scss @@ -11,16 +11,32 @@ a { color: $accent-800 !important; } -.text-ok { - color: $color-green !important; +.light-theme { + .text-ok { + color: $color-green !important; + } +} + +.dark-theme { + .text-ok { + color: $color-green-dark !important; + } } .text-warning { color: $color-orange !important; } -.text-critical { - color: $warn-600 !important; +.light-theme { + .text-critical { + color: $warn-600 !important; + } +} + +.dark-theme { + .text-critical { + color: $warn-300 !important; + } } .text-inactive { From 90782083c1e4f38ec2f4922a0e58df29caff3d81 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Sun, 12 Nov 2023 00:27:32 +0100 Subject: [PATCH 242/419] fix tables with no filters --- .../_components/core-components.module.ts | 1 - .../action-menu/action-menu.component.html | 23 +++++++++++++++---- .../tables/ht-table/ht-table.component.html | 3 +-- src/app/tasks/tasks.module.ts | 4 ++++ 4 files changed, 23 insertions(+), 8 deletions(-) diff --git a/src/app/core/_components/core-components.module.ts b/src/app/core/_components/core-components.module.ts index 84d060bf..978a5d7f 100644 --- a/src/app/core/_components/core-components.module.ts +++ b/src/app/core/_components/core-components.module.ts @@ -72,7 +72,6 @@ import { TableDialogComponent } from './tables/table-dialog/table-dialog.compone MatTooltipModule, MatDividerModule, MatSlideToggleModule, - MatInputModule, RouterModule, FormsModule ], diff --git a/src/app/core/_components/menus/action-menu/action-menu.component.html b/src/app/core/_components/menus/action-menu/action-menu.component.html index 64028692..39403187 100644 --- a/src/app/core/_components/menus/action-menu/action-menu.component.html +++ b/src/app/core/_components/menus/action-menu/action-menu.component.html @@ -1,5 +1,11 @@ - - - \ No newline at end of file + 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 4a5d24fc..1be0bd70 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 @@ -52,12 +52,11 @@
- + filter_list Date: Sun, 12 Nov 2023 10:04:37 +0100 Subject: [PATCH 243/419] update style --- src/styles/base/_colors.scss | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/styles/base/_colors.scss b/src/styles/base/_colors.scss index 2226700e..35036f80 100644 --- a/src/styles/base/_colors.scss +++ b/src/styles/base/_colors.scss @@ -1,21 +1,5 @@ @use '@angular/material' as mat; -$color-white: #ffffff; -$color-grey-light: #cccccc; -$color-grey-border: #999999; -$color-grey: #666666; -$color-grey-metal: #4B5563; -$color-grey-dark: #444444; -$color-black: #111111; -$color-orange: #ff9900; -$color-orange-dark: #b36e06; -$color-green: #009933; -$color-green-dark: #1c6d37; -$color-red: #ff0000; -$color-red-dark: #800000; -$color-blue-light: #42d4f4; -$color-blue: #0b83b3; - $primary-50: mat.get-color-from-palette($hashtopolis-primary-palette, 50); $primary-100: mat.get-color-from-palette($hashtopolis-primary-palette, 100); $primary-200: mat.get-color-from-palette($hashtopolis-primary-palette, 200); @@ -47,4 +31,20 @@ $warn-500: mat.get-color-from-palette($hashtopolis-warn-palette, 500); $warn-600: mat.get-color-from-palette($hashtopolis-warn-palette, 600); $warn-700: mat.get-color-from-palette($hashtopolis-warn-palette, 700); $warn-800: mat.get-color-from-palette($hashtopolis-warn-palette, 800); -$warn-900: mat.get-color-from-palette($hashtopolis-warn-palette, 900); \ No newline at end of file +$warn-900: mat.get-color-from-palette($hashtopolis-warn-palette, 900); + +$color-white: #ffffff; +$color-grey-light: #cccccc; +$color-grey-border: #999999; +$color-grey: #666666; +$color-grey-metal: #4b5563; +$color-grey-dark: #444444; +$color-black: #111111; +$color-orange: #ff9900; +$color-orange-dark: #b36e06; +$color-green: #009933; +$color-green-dark: #1c6d37; +$color-red: #ff0000; +$color-red-dark: #800000; +$color-blue-light: #42d4f4; +$color-blue: #0b83b3; From cd958cb52ca9ad2e4eabd62fb63bf60ef7361286 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Sun, 12 Nov 2023 10:04:53 +0100 Subject: [PATCH 244/419] Add hashtypes datasource! --- .../core/_datasources/hashtypes.datasource.ts | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 src/app/core/_datasources/hashtypes.datasource.ts diff --git a/src/app/core/_datasources/hashtypes.datasource.ts b/src/app/core/_datasources/hashtypes.datasource.ts new file mode 100644 index 00000000..2a2dbbac --- /dev/null +++ b/src/app/core/_datasources/hashtypes.datasource.ts @@ -0,0 +1,36 @@ +import { catchError, finalize, of } from 'rxjs'; + +import { BaseDataSource } from './base.datasource'; +import { Hashtype } from '../_models/hashtype.model'; +import { ListResponseWrapper } from '../_models/response.model'; +import { SERV } from '../_services/main.config'; + +export class HashtypesDataSource extends BaseDataSource { + loadAll(): void { + this.loadingSubject.next(true); + + const params = { maxResults: this.maxResults }; + const hashtypes$ = this.service.getAll(SERV.HASHTYPES, params); + + this.subscriptions.push( + hashtypes$ + .pipe( + catchError(() => of([])), + finalize(() => this.loadingSubject.next(false)) + ) + .subscribe((response: ListResponseWrapper) => { + this.setPaginationConfig( + this.pageSize, + this.currentPage, + response.total + ); + this.setData(response.values); + }) + ); + } + + reload(): void { + this.reset(); + this.loadAll(); + } +} From 060899f02a2a9b34511d087121dd1cbcbaf0e23f Mon Sep 17 00:00:00 2001 From: itbf9 Date: Sun, 12 Nov 2023 13:11:37 +0100 Subject: [PATCH 245/419] Add hashtypes table + bugfixes --- src/app/config/config.module.ts | 56 ++-- .../config/hashtypes/hashtypes.component.html | 41 +-- .../config/hashtypes/hashtypes.component.ts | 201 +------------- .../_components/core-components.module.ts | 5 +- .../agents-table/agents-table.component.ts | 2 +- .../tables/base-table/base-table.component.ts | 9 +- .../chunks-table/chunks-table.component.ts | 2 +- .../hashlists-table.component.ts | 6 +- .../hashtypes-table.component.html | 15 + .../hashtypes-table.component.ts | 261 ++++++++++++++++++ .../hashtypes-table.constants.ts | 6 + .../tables/ht-table/ht-table.component.html | 3 +- .../tables/ht-table/ht-table.component.ts | 6 +- .../tables/ht-table/ht-table.models.ts | 2 +- .../core/_datasources/agents.datasource.ts | 4 +- src/app/core/_datasources/base.datasource.ts | 25 +- .../core/_datasources/chunks.datasource.ts | 4 +- .../core/_datasources/hashlists.datasource.ts | 4 +- .../core/_datasources/hashtypes.datasource.ts | 6 +- src/app/core/_models/config-ui.model.ts | 7 + 20 files changed, 385 insertions(+), 280 deletions(-) create mode 100644 src/app/core/_components/tables/hashtypes-table/hashtypes-table.component.html create mode 100644 src/app/core/_components/tables/hashtypes-table/hashtypes-table.component.ts create mode 100644 src/app/core/_components/tables/hashtypes-table/hashtypes-table.constants.ts diff --git a/src/app/config/config.module.ts b/src/app/config/config.module.ts index cf3a9a45..4e518670 100644 --- a/src/app/config/config.module.ts +++ b/src/app/config/config.module.ts @@ -1,34 +1,35 @@ -import { FontAwesomeModule } from "@fortawesome/angular-fontawesome"; -import { FormsModule, ReactiveFormsModule } from "@angular/forms"; -import { NgbModule } from "@ng-bootstrap/ng-bootstrap"; -import { DataTablesModule } from "angular-datatables"; -import { CommonModule } from "@angular/common"; -import { RouterModule } from "@angular/router"; -import { NgModule } from "@angular/core"; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; -import { NewAgentBinariesComponent } from "./engine/agent-binaries/agent-binary/new-agent-binaries.component"; -import { NewPreprocessorComponent } from './engine/preprocessors/preprocessor/new-preprocessor.component'; -import { EditHealthChecksComponent } from './health-checks/edit-health-check/edit-health-checks.component'; -import { NewHealthChecksComponent } from './health-checks/new-health-check/new-health-checks.component'; +import { AgentBinariesComponent } from './engine/agent-binaries/agent-binaries.component'; +import { CommonModule } from '@angular/common'; +import { ComponentsModule } from '../shared/components.module'; +import { ConfigRoutingModule } from './config-routing.module'; +import { CoreComponentsModule } from '../core/_components/core-components.module'; +import { CrackersComponent } from './engine/crackers/crackers.component'; +import { DataTablesModule } from 'angular-datatables'; import { EditCrackersComponent } from './engine/crackers/edit-version/edit-crackers.component'; -import { NewCrackersComponent } from './engine/crackers/new-version/new-crackers.component'; -import { NewCrackerComponent } from './engine/crackers/new-cracker/new-cracker.component'; -import { AgentBinariesComponent } from "./engine/agent-binaries/agent-binaries.component"; -import { PreprocessorsComponent } from "./engine/preprocessors/preprocessors.component"; -import { HealthChecksComponent } from "./health-checks/health-checks.component"; +import { EditHealthChecksComponent } from './health-checks/edit-health-check/edit-health-checks.component'; +import { EngineMenuComponent } from './engine/engine-menu'; +import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; import { HashtypeComponent } from './hashtypes/hashtype/hashtype.component'; -import { CrackersComponent } from "./engine/crackers/crackers.component"; -import { HashtypesComponent } from "./hashtypes/hashtypes.component"; -import { ComponentsModule } from "../shared/components.module"; -import { SettingsMenuComponent } from "./server/settings-menu"; -import { ConfigRoutingModule } from "./config-routing.module"; -import { ServerComponent } from "./server/server.component"; -import { EngineMenuComponent } from "./engine/engine-menu"; -import { PipesModule } from "../shared/pipes.module"; -import { LogComponent } from "./log/log.component"; +import { HashtypesComponent } from './hashtypes/hashtypes.component'; +import { HealthChecksComponent } from './health-checks/health-checks.component'; +import { LogComponent } from './log/log.component'; +import { NewAgentBinariesComponent } from './engine/agent-binaries/agent-binary/new-agent-binaries.component'; +import { NewCrackerComponent } from './engine/crackers/new-cracker/new-cracker.component'; +import { NewCrackersComponent } from './engine/crackers/new-version/new-crackers.component'; +import { NewHealthChecksComponent } from './health-checks/new-health-check/new-health-checks.component'; +import { NewPreprocessorComponent } from './engine/preprocessors/preprocessor/new-preprocessor.component'; +import { NgModule } from '@angular/core'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { PipesModule } from '../shared/pipes.module'; +import { PreprocessorsComponent } from './engine/preprocessors/preprocessors.component'; +import { RouterModule } from '@angular/router'; +import { ServerComponent } from './server/server.component'; +import { SettingsMenuComponent } from './server/settings-menu'; @NgModule({ - declarations:[ + declarations: [ NewAgentBinariesComponent, EditHealthChecksComponent, NewPreprocessorComponent, @@ -47,12 +48,13 @@ import { LogComponent } from "./log/log.component"; ServerComponent, LogComponent ], - imports:[ + imports: [ ReactiveFormsModule, ConfigRoutingModule, FontAwesomeModule, DataTablesModule, ComponentsModule, + CoreComponentsModule, CommonModule, RouterModule, FormsModule, diff --git a/src/app/config/hashtypes/hashtypes.component.html b/src/app/config/hashtypes/hashtypes.component.html index a455b605..e40940cd 100644 --- a/src/app/config/hashtypes/hashtypes.component.html +++ b/src/app/config/hashtypes/hashtypes.component.html @@ -1,36 +1,9 @@ - - - - - - - - - - - - - - - - - - - - - -
Hashtype (Hashcat -m)DescriptionSaltedSlow HashActions
{{ h.hashTypeId }}{{ h.description }}{{ h.isSalted === 0 ? "Yes" : "No" }}{{ h.isSlowHash === 0 ? "Yes" : "No" }} -
- - - - - -
-
+ +
diff --git a/src/app/config/hashtypes/hashtypes.component.ts b/src/app/config/hashtypes/hashtypes.component.ts index d36e22e1..01d38b5c 100644 --- a/src/app/config/hashtypes/hashtypes.component.ts +++ b/src/app/config/hashtypes/hashtypes.component.ts @@ -1,203 +1,12 @@ -import { faHomeAlt, faPlus, faTrash, faEdit, faSave, faCancel, faInfoCircle} from '@fortawesome/free-solid-svg-icons'; -import { Component, OnInit, ViewChild } from '@angular/core'; -import { DataTableDirective } from 'angular-datatables'; -import Swal from 'sweetalert2/dist/sweetalert2.js'; -import { Subject } from 'rxjs'; +import { AutoTitleService } from 'src/app/core/_services/shared/autotitle.service'; +import { Component } from '@angular/core'; -import { AlertService } from 'src/app/core/_services/shared/alert.service'; -import { BulkService } from 'src/app/core/_services/shared/bulk.service'; -import { GlobalService } from 'src/app/core/_services/main.service'; -import { environment } from './../../../environments/environment'; -import { PageTitle } from 'src/app/core/_decorators/autotitle'; -import { SERV } from '../../core/_services/main.config'; - -declare let $:any; @Component({ selector: 'app-hashtypes', templateUrl: './hashtypes.component.html' }) -@PageTitle(['Show Hashtypes']) -export class HashtypesComponent implements OnInit { - - faInfoCircle=faInfoCircle; - faCancel=faCancel; - faHome=faHomeAlt; - faTrash=faTrash; - faPlus=faPlus; - faEdit=faEdit; - faSave=faSave; - - private maxResults = environment.config.prodApiMaxResults; - - @ViewChild(DataTableDirective) - dtElement: DataTableDirective; - - dtTrigger: Subject = new Subject(); - dtOptions: any = {}; - - constructor( - private alert: AlertService, - private bulk: BulkService, - private gs: GlobalService - ) { } - - public htypes: any; - - ngOnInit(): void { - - const params = {'maxResults': this.maxResults}; - - this.gs.getAll(SERV.HASHTYPES,params).subscribe((htypes: any) => { - this.htypes = htypes.values; - this.dtTrigger.next(void 0); - }); - const self = this; - this.dtOptions = { - dom: 'Bfrtip', - scrollX: true, - pageLength: 25, - lengthMenu: [ - [10, 25, 50, 100, 250, -1], - [10, 25, 50, 100, 250, 'All'] - ], - processing: true, // Error loading - deferRender: true, - destroy:true, - select: { - style: 'multi', - }, - buttons: { - dom: { - button: { - className: 'dt-button buttons-collection btn btn-sm-dt btn-outline-gray-600-dt', - } - }, - buttons: [ - { - text: '↻', - autoClose: true, - action: function (e, dt, node, config) { - self.onRefresh(); - } - }, - { - extend: 'collection', - text: 'Export', - buttons: [ - { - extend: 'excelHtml5', - exportOptions: { - columns: [0, 1, 2, 3] - }, - }, - { - extend: 'print', - exportOptions: { - columns: [0, 1, 2, 3] - }, - customize: function ( win ) { - $(win.document.body) - .css( 'font-size', '10pt' ) - $(win.document.body).find( 'table' ) - .addClass( 'compact' ) - .css( 'font-size', 'inherit' ); - } - }, - { - extend: 'csvHtml5', - exportOptions: {modifier: {selected: true}}, - select: true, - customize: function (dt, csv) { - let data = ""; - for (let i = 0; i < dt.length; i++) { - data = "Hashtypes\n\n"+ dt; - } - return data; - } - }, - 'copy' - ] - }, - { - extend: 'collection', - text: 'Bulk Actions', - className: 'dt-button buttons-collection btn btn-sm-dt btn-outline-gray-600-dt', - buttons: [ - { - text: 'Delete Hashtypes', - autoClose: true, - action: function (e, dt, node, config) { - self.onDeleteBulk(); - } - } - ] - }, - ], - } - }; - +export class HashtypesComponent { + constructor(private titleService: AutoTitleService) { + titleService.set(['Show Hashtypes']); } - - onRefresh(){ - this.rerender(); - this.ngOnInit(); - } - - rerender(): void { - this.dtElement.dtInstance.then((dtInstance: DataTables.Api) => { - // Destroy the table first - dtInstance.destroy(); - // Call the dtTrigger to rerender again - setTimeout(() => { - this.dtTrigger['new'].next(); - }); - }); - } - - onDelete(id: number, name: string){ - this.alert.deleteConfirmation(name,'Hashtypes').then((confirmed) => { - if (confirmed) { - // Deletion - this.gs.delete(SERV.HASHTYPES, id).subscribe(() => { - // Successful deletion - this.alert.okAlert(`Deleted Hashtype ${name}`, ''); - this.onRefreshTable(); // Refresh the table - }); - } else { - // Handle cancellation - this.alert.okAlert(`Hashtype ${name} is safe!`,''); - } - }); - } - - onSelectedHashtypes(){ - $(".dt-button-background").trigger("click"); - const selection = $($(this.dtElement).DataTable.tables()).DataTable().rows({ selected: true } ).data().pluck(0).toArray(); - if(selection.length == 0) { - this.alert.okAlert('You have not selected any Hashtype',''); - return; - } - const selectionnum = selection.map(i=>Number(i)); - - return selectionnum; - } - - async onDeleteBulk() { - const HashtypesIds = this.onSelectedHashtypes(); - this.alert.bulkDeleteAlert(HashtypesIds,'Hashtypes',SERV.HASHTYPES); - this.onRefreshTable(); - } - - onRefreshTable(){ - setTimeout(() => { - this.ngOnInit(); - this.rerender(); // rerender datatables - },2000); - } - - // Add unsubscribe to detect changes - ngOnDestroy(){ - this.dtTrigger.unsubscribe(); - } - } diff --git a/src/app/core/_components/core-components.module.ts b/src/app/core/_components/core-components.module.ts index 978a5d7f..9a9be9e4 100644 --- a/src/app/core/_components/core-components.module.ts +++ b/src/app/core/_components/core-components.module.ts @@ -15,6 +15,7 @@ import { CommonModule } from '@angular/common'; import { ExportMenuComponent } from './menus/export-menu/export-menu.component'; import { HTTableComponent } from './tables/ht-table/ht-table.component'; import { HashlistsTableComponent } from './tables/hashlists-table/hashlists-table.component'; +import { HashtypesTableComponent } from './tables/hashtypes-table/hashtypes-table.component'; import { MatButtonModule } from '@angular/material/button'; import { MatCheckboxModule } from '@angular/material/checkbox'; import { MatDialogModule } from '@angular/material/dialog'; @@ -49,6 +50,7 @@ import { TableDialogComponent } from './tables/table-dialog/table-dialog.compone ColumnSelectionDialogComponent, AgentsTableComponent, ChunksTableComponent, + HashtypesTableComponent, HashlistsTableComponent ], imports: [ @@ -86,7 +88,8 @@ import { TableDialogComponent } from './tables/table-dialog/table-dialog.compone ExportMenuComponent, AgentsTableComponent, ChunksTableComponent, - HashlistsTableComponent + HashlistsTableComponent, + HashtypesTableComponent ], providers: [ { diff --git a/src/app/core/_components/tables/agents-table/agents-table.component.ts b/src/app/core/_components/tables/agents-table/agents-table.component.ts index dc8d0fde..ec5d142c 100644 --- a/src/app/core/_components/tables/agents-table/agents-table.component.ts +++ b/src/app/core/_components/tables/agents-table/agents-table.component.ts @@ -40,7 +40,7 @@ export class AgentsTableComponent ngOnInit(): void { this.tableColumns = this.getColumns(); - this.dataSource = new AgentsDataSource(this.gs, this.uiService); + this.dataSource = new AgentsDataSource(this.cdr, this.gs, this.uiService); this.dataSource.setColumns(this.tableColumns); this.dataSource.loadAll(); } 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 7f839ca5..304bc244 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 @@ -1,5 +1,11 @@ /* eslint-disable @angular-eslint/component-selector */ -import { Component, Input, Renderer2, ViewChild } from '@angular/core'; +import { + ChangeDetectorRef, + Component, + Input, + Renderer2, + ViewChild +} from '@angular/core'; import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; import { UIConfig, @@ -37,6 +43,7 @@ export class BaseTableComponent { protected snackBar: MatSnackBar, protected uiService: UIConfigService, protected exportService: ExportService, + protected cdr: ChangeDetectorRef, public dialog: MatDialog ) { this.uiSettings = new UISettingsUtilityClass(settingsService); 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 e989eabf..06cc19ed 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 @@ -33,7 +33,7 @@ export class ChunksTableComponent extends BaseTableComponent implements OnInit { ngOnInit(): void { this.tableColumns = this.getColumns(); - this.dataSource = new ChunksDataSource(this.gs, this.uiService); + this.dataSource = new ChunksDataSource(this.cdr, this.gs, this.uiService); this.dataSource.setColumns(this.tableColumns); this.dataSource.loadAll(); } diff --git a/src/app/core/_components/tables/hashlists-table/hashlists-table.component.ts b/src/app/core/_components/tables/hashlists-table/hashlists-table.component.ts index 5151a857..2f292325 100644 --- a/src/app/core/_components/tables/hashlists-table/hashlists-table.component.ts +++ b/src/app/core/_components/tables/hashlists-table/hashlists-table.component.ts @@ -32,7 +32,11 @@ export class HashlistsTableComponent ngOnInit(): void { this.tableColumns = this.getColumns(); - this.dataSource = new HashlistsDataSource(this.gs, this.uiService); + this.dataSource = new HashlistsDataSource( + this.cdr, + this.gs, + this.uiService + ); this.dataSource.setColumns(this.tableColumns); this.dataSource.setIsArchived(this.isArchived); this.dataSource.loadAll(); diff --git a/src/app/core/_components/tables/hashtypes-table/hashtypes-table.component.html b/src/app/core/_components/tables/hashtypes-table/hashtypes-table.component.html new file mode 100644 index 00000000..9f9b5067 --- /dev/null +++ b/src/app/core/_components/tables/hashtypes-table/hashtypes-table.component.html @@ -0,0 +1,15 @@ + diff --git a/src/app/core/_components/tables/hashtypes-table/hashtypes-table.component.ts b/src/app/core/_components/tables/hashtypes-table/hashtypes-table.component.ts new file mode 100644 index 00000000..3b4ff4b9 --- /dev/null +++ b/src/app/core/_components/tables/hashtypes-table/hashtypes-table.component.ts @@ -0,0 +1,261 @@ +/* eslint-disable @angular-eslint/component-selector */ +import { + AfterViewInit, + ChangeDetectionStrategy, + Component, + OnInit +} from '@angular/core'; +import { + HTTableColumn, + HTTableIcon +} from '../../tables/ht-table/ht-table.models'; +import { catchError, forkJoin } from 'rxjs'; + +import { ActionMenuEvent } from '../../menus/action-menu/action-menu.model'; +import { BaseTableComponent } from '../base-table/base-table.component'; +import { BulkActionMenuAction } from '../../menus/bulk-action-menu/bulk-action-menu.constants'; +import { Cacheable } from 'src/app/core/_decorators/cacheable'; +import { DialogData } from '../table-dialog/table-dialog.model'; +import { ExportMenuAction } from '../../menus/export-menu/export-menu.constants'; +import { Hashtype } from '../../../_models/hashtype.model'; +import { HashtypesDataSource } from '../../../_datasources/hashtypes.datasource'; +import { HashtypesTableColumnLabel } from './hashtypes-table.constants'; +import { RowActionMenuAction } from '../../menus/row-action-menu/row-action-menu.constants'; +import { SERV } from 'src/app/core/_services/main.config'; +import { TableDialogComponent } from '../table-dialog/table-dialog.component'; + +@Component({ + selector: 'hashtypes-table', + templateUrl: './hashtypes-table.component.html', + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class HashtypesTableComponent + extends BaseTableComponent + implements OnInit, AfterViewInit +{ + tableColumns: HTTableColumn[] = []; + dataSource: HashtypesDataSource; + + ngOnInit(): void { + this.tableColumns = this.getColumns(); + this.dataSource = new HashtypesDataSource( + this.cdr, + this.gs, + this.uiService + ); + this.dataSource.setColumns(this.tableColumns); + } + + ngAfterViewInit(): void { + // Wait until paginator is defined + this.dataSource.loadAll(); + } + + getColumns(): HTTableColumn[] { + const tableColumns = [ + { + name: HashtypesTableColumnLabel.HASHTYPE, + dataKey: 'hashTypeId', + isSortable: true, + export: async (hashtype: Hashtype) => hashtype.hashTypeId + '' + }, + { + name: HashtypesTableColumnLabel.DESCRIPTION, + dataKey: 'description', + isSortable: true, + export: async (hashtype: Hashtype) => hashtype.description + }, + { + name: HashtypesTableColumnLabel.SALTED, + dataKey: 'isSalted', + icons: (hashtype: Hashtype) => this.renderIsSaltedIcon(hashtype), + isSortable: true, + export: async (hashtype: Hashtype) => (hashtype.isSalted ? 'Yes' : 'No') + }, + { + name: HashtypesTableColumnLabel.SLOW_HASH, + dataKey: 'isSlowHash', + icons: (hashtype: Hashtype) => this.renderIsSlowIcon(hashtype), + isSortable: true, + export: async (hashtype: Hashtype) => + hashtype.isSlowHash ? 'Yes' : 'No' + } + ]; + + return tableColumns; + } + + filter(item: Hashtype, filterValue: string): boolean { + if ( + item.hashTypeId.toString().toLowerCase().includes(filterValue) || + item.description.toLowerCase().includes(filterValue) + ) { + return true; + } + + return false; + } + + openDialog(data: DialogData) { + const dialogRef = this.dialog.open(TableDialogComponent, { + data: data, + width: '450px' + }); + + this.subscriptions.push( + dialogRef.afterClosed().subscribe((result) => { + if (result && result.action) { + switch (result.action) { + case RowActionMenuAction.DELETE: + this.rowActionDelete(result.data); + break; + case BulkActionMenuAction.DELETE: + this.bulkActionDelete(result.data); + break; + } + } + }) + ); + } + + rowActionClicked(event: ActionMenuEvent): void { + switch (event.menuItem.action) { + case RowActionMenuAction.EDIT: + this.rowActionEdit(event.data); + break; + case RowActionMenuAction.DELETE: + this.openDialog({ + rows: [event.data], + title: `Deleting hashtype ${event.data.hashTypeId} (${event.data.description}) ...`, + icon: 'warning', + body: `Are you sure you want to delete it? Note that this action cannot be undone.`, + warn: true, + action: event.menuItem.action + }); + break; + } + } + + bulkActionClicked(event: ActionMenuEvent): void { + switch (event.menuItem.action) { + case BulkActionMenuAction.DELETE: + this.openDialog({ + rows: event.data, + title: `Deleting ${event.data.length} hashtypes ...`, + icon: 'warning', + body: `Are you sure you want to delete the above hashtypes? Note that this action cannot be undone.`, + warn: true, + listAttribute: 'name', + action: event.menuItem.action + }); + break; + } + } + + /** + * @todo Implement error handling. + */ + private bulkActionDelete(hashtypes: Hashtype[]): void { + const requests = hashtypes.map((hashtype: Hashtype) => { + return this.gs.delete(SERV.HASHTYPES, hashtype.hashTypeId); + }); + + this.subscriptions.push( + forkJoin(requests) + .pipe( + catchError((error) => { + console.error('Error during deletion:', error); + return []; + }) + ) + .subscribe((results) => { + this.snackBar.open( + `Successfully deleted ${results.length} hashtypes!`, + 'Close' + ); + this.reload(); + }) + ); + } + + /** + * @todo Implement error handling. + */ + private rowActionDelete(hashtypes: Hashtype[]): void { + this.subscriptions.push( + this.gs + .delete(SERV.HASHTYPES, hashtypes[0].hashTypeId) + .pipe( + catchError((error) => { + console.error('Error during deletion:', error); + return []; + }) + ) + .subscribe(() => { + this.snackBar.open('Successfully deleted hashtype!', 'Close'); + this.reload(); + }) + ); + } + + private rowActionEdit(hashtype: Hashtype): void { + this.router.navigate(['/config', 'hashtypes', hashtype.hashTypeId, 'edit']); + } + + exportActionClicked(event: ActionMenuEvent): void { + switch (event.menuItem.action) { + case ExportMenuAction.EXCEL: + this.exportService.toExcel( + 'hashtopolis-hashtypes', + this.tableColumns, + event.data + ); + break; + case ExportMenuAction.CSV: + this.exportService.toCsv( + 'hashtopolis-hashtypes', + this.tableColumns, + event.data + ); + break; + case ExportMenuAction.COPY: + this.exportService + .toClipboard(this.tableColumns, event.data) + .then(() => { + this.snackBar.open( + 'The selected rows are copied to the clipboard', + 'Close' + ); + }); + break; + } + } + + @Cacheable(['hashTypeId', 'isSalted']) + async renderIsSaltedIcon(hashtype: Hashtype): Promise { + const icons: HTTableIcon[] = []; + if (hashtype.isSalted) { + icons.push({ + name: 'check_circle', + tooltip: 'Salted Hash', + cls: 'text-ok' + }); + } + + return icons; + } + + @Cacheable(['hashTypeId', 'isSlowHash']) + async renderIsSlowIcon(hashtype: Hashtype): Promise { + const icons: HTTableIcon[] = []; + if (hashtype.isSlowHash) { + icons.push({ + name: 'check_circle', + tooltip: 'Slow Hash', + cls: 'text-ok' + }); + } + + return icons; + } +} diff --git a/src/app/core/_components/tables/hashtypes-table/hashtypes-table.constants.ts b/src/app/core/_components/tables/hashtypes-table/hashtypes-table.constants.ts new file mode 100644 index 00000000..c75679d7 --- /dev/null +++ b/src/app/core/_components/tables/hashtypes-table/hashtypes-table.constants.ts @@ -0,0 +1,6 @@ +export const HashtypesTableColumnLabel = { + HASHTYPE: 'Hashtype (Hashcat -m)', + DESCRIPTION: 'Description', + SALTED: 'Salted', + SLOW_HASH: 'Slow Hash' +}; 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 1be0bd70..3de5dff1 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 @@ -70,7 +70,6 @@
-
@@ -192,7 +191,7 @@ -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
IDNameStatusHash typeCrackedHashlistsPre-crackedActions
{{ list.hashlistId }} - {{ list.name | shortenString:35 }} - - - - {{ list.hashType.description }} -
- - {{ list.cracked / list.hashCount | percent:'1.2-2' }} - - ( - {{ list.cracked }} - / - {{ list.hashCount }} - ) -
-
-
- {{h.name}} -
-
-
- - - - - - - - - - - - -
+ +
- diff --git a/src/app/hashlists/superhashlist/superhashlist.component.ts b/src/app/hashlists/superhashlist/superhashlist.component.ts index f18698a8..eb6e998c 100644 --- a/src/app/hashlists/superhashlist/superhashlist.component.ts +++ b/src/app/hashlists/superhashlist/superhashlist.component.ts @@ -1,163 +1,12 @@ -import { faEdit, faTrash,faFileImport, faFileExport, faPlus, faLock, faCheckCircle } from '@fortawesome/free-solid-svg-icons'; -import { Component, OnInit, ViewChild } from '@angular/core'; -import { DataTableDirective } from 'angular-datatables'; -import Swal from 'sweetalert2/dist/sweetalert2.js'; -import { Subject } from 'rxjs'; - -import { AlertService } from 'src/app/core/_services/shared/alert.service'; -import { GlobalService } from 'src/app/core/_services/main.service'; -import { environment } from './../../../environments/environment'; -import { PageTitle } from 'src/app/core/_decorators/autotitle'; -import { SERV } from '../../core/_services/main.config'; +import { AutoTitleService } from 'src/app/core/_services/shared/autotitle.service'; +import { Component } from '@angular/core'; @Component({ selector: 'app-superhashlist', templateUrl: './superhashlist.component.html' }) -@PageTitle(['Show SuperHashlist']) -export class SuperhashlistComponent implements OnInit { - - faCheckCircle=faCheckCircle; - faFileImport=faFileImport; - faFileExport=faFileExport; - faTrash=faTrash; - faEdit=faEdit; - faLock=faLock; - faPlus=faPlus; - - @ViewChild(DataTableDirective, {static: false}) - dtElement: DataTableDirective; - - dtTrigger: Subject = new Subject(); - dtOptions: any = {}; - - constructor( - private alert: AlertService, - private gs: GlobalService, - ) { } - - allsuperhashlisth: any - private maxResults = environment.config.prodApiMaxResults - - ngOnInit(): void { - - this.gs.getAll(SERV.HASHLISTS,{'maxResults': this.maxResults, 'filter': 'format=3', 'expand': 'hashType,hashlists'}).subscribe((sh: any) => { - this.allsuperhashlisth = sh.values; - console.log(sh.values); - this.dtTrigger.next(void 0); - }); - const self = this; - this.dtOptions = { - dom: 'Bfrtip', - scrollX: true, - pageLength: 25, - lengthMenu: [ - [10, 25, 50, 100, 250, -1], - [10, 25, 50, 100, 250, 'All'] - ], - stateSave: true, - select: true, - buttons: { - dom: { - button: { - className: 'dt-button buttons-collection btn btn-sm-dt btn-outline-gray-600-dt', - } - }, - buttons: [ - { - text: '↻', - autoClose: true, - action: function (e, dt, node, config) { - self.onRefresh(); - } - }, - { - extend: 'collection', - text: 'Export', - buttons: [ - { - extend: 'excelHtml5', - exportOptions: { - columns: [0, 1, 2, 3, 4] - }, - }, - { - extend: 'print', - exportOptions: { - columns: [0, 1, 2, 3, 4] - }, - customize: function ( win ) { - $(win.document.body) - .css( 'font-size', '10pt' ) - $(win.document.body).find( 'table' ) - .addClass( 'compact' ) - .css( 'font-size', 'inherit' ); - } - }, - { - extend: 'csvHtml5', - exportOptions: {modifier: {selected: true}}, - select: true, - customize: function (dt, csv) { - let data = ""; - for (let i = 0; i < dt.length; i++) { - data = "SuperHashlist\n\n"+ dt; - } - return data; - } - }, - 'copy' - ] - } - ], - } - }; - - } - - onRefresh(){ - this.rerender(); - this.ngOnInit(); +export class SuperhashlistComponent { + constructor(private titleService: AutoTitleService) { + this.titleService.set(['Show SuperHashlist']); } - - rerender(): void { - this.dtElement.dtInstance.then((dtInstance: DataTables.Api) => { - // Destroy the table first - dtInstance.destroy(); - // Call the dtTrigger to rerender again - setTimeout(() => { - this.dtTrigger['new'].next(); - }); - }); - } - - onDelete(id: number, name: string){ - this.alert.deleteConfirmation(name,'Superhashlists').then((confirmed) => { - if (confirmed) { - // Deletion - this.gs.delete(SERV.HASHLISTS, id).subscribe(() => { - // Successful deletion - this.alert.okAlert(`Deleted Superhashlist ${name}`, ''); - this.onRefreshTable(); // Refresh the table - }); - } else { - // Handle cancellation - this.alert.okAlert(`Superhashlist ${name} is safe!`,''); - } - }); - } - - onRefreshTable(){ - setTimeout(() => { - this.ngOnInit(); - this.rerender(); // rerender datatables - },2000); - } - - // Add unsubscribe to detect changes - ngOnDestroy(): void { - this.dtTrigger.unsubscribe(); - } - - } From 0b6f9ef51af5762229439cd67454c4c865a9b688 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Mon, 13 Nov 2023 12:11:58 +0000 Subject: [PATCH 250/419] Save --- STYLE_GUIDE.md | 7 +- src/app/account/account-routing.module.ts | 131 ++-- src/app/agents/agents-routing.module.ts | 71 +- src/app/app-routing.module.ts | 146 ++-- src/app/auth/auth.component.html | 50 ++ src/app/auth/auth.component.ts | 99 +-- src/app/config/config-routing.module.ts | 486 +++++++------ src/app/core/_models/routes.model.ts | 16 + src/app/core/_services/unsubscribe.service.ts | 2 +- src/app/files/files-routing.module.ts | 129 ++-- src/app/hashlists/hashlists-routing.module.ts | 175 +++-- src/app/home/home-routing.module.ts | 34 +- .../edit-project/edit-project.component.html | 1 - .../edit-project/edit-project.component.ts | 14 - src/app/projects/projects-routing.module.ts | 36 - src/app/projects/projects.component.html | 104 --- src/app/projects/projects.component.ts | 669 ------------------ src/app/projects/projects.module.ts | 29 - src/app/projects/report.ts | 19 - .../shared/form/dynamicform.component.html | 113 +++ src/app/shared/form/dynamicform.component.ts | 156 +--- src/app/shared/form/dynamicform.module.ts | 15 +- .../form/dynamicformlayout.component.ts | 369 ---------- src/app/shared/form/form.component.html | 12 + src/app/shared/form/form.component.ts | 151 ++-- .../shared/form/formuisettings.component.ts | 139 ---- .../shared/grid-containers/grid-autocol.ts | 61 +- .../navigation/horizontalnav.component.html | 33 + .../navigation/horizontalnav.component.ts | 36 +- src/app/tasks/tasks-routing.module.ts | 440 ++++++------ src/app/users/users-routing.module.ts | 206 +++--- src/styles/base/_base.scss | 13 - src/styles/base/_form.scss | 20 - src/styles/components/_form.scss | 335 +++------ 34 files changed, 1476 insertions(+), 2841 deletions(-) create mode 100644 src/app/auth/auth.component.html create mode 100644 src/app/core/_models/routes.model.ts delete mode 100644 src/app/projects/edit-project/edit-project.component.html delete mode 100644 src/app/projects/edit-project/edit-project.component.ts delete mode 100644 src/app/projects/projects-routing.module.ts delete mode 100644 src/app/projects/projects.component.html delete mode 100644 src/app/projects/projects.component.ts delete mode 100644 src/app/projects/projects.module.ts delete mode 100644 src/app/projects/report.ts create mode 100644 src/app/shared/form/dynamicform.component.html delete mode 100644 src/app/shared/form/dynamicformlayout.component.ts create mode 100644 src/app/shared/form/form.component.html delete mode 100644 src/app/shared/form/formuisettings.component.ts create mode 100644 src/app/shared/navigation/horizontalnav.component.html diff --git a/STYLE_GUIDE.md b/STYLE_GUIDE.md index 88afd575..3cb4292f 100644 --- a/STYLE_GUIDE.md +++ b/STYLE_GUIDE.md @@ -22,7 +22,9 @@ This document outlines the coding and documentation conventions for our project. ### Variable Names: -Choose variable names that are descriptive, clear, and contextually relevant, avoiding ambiguity, generic terms, and "magic" values, and strive for consistency and single responsibility to enhance code readability and maintainability. +Choose variable names that are descriptive, clear, and contextually relevant, avoiding ambiguity, generic terms, and "magic" values, and strive for consistency and single responsibility to enhance code readability and maintainability. Below are some guidelines for variable naming. + +- Boolean Variables: consider using prefixes like is (e.g., isVisible, isFinished) or has (e.g., hasPermission, hasErrors) ### Constants: @@ -80,7 +82,6 @@ Choose variable names that are descriptive, clear, and contextually relevant, av - Use kebab-case for route paths (e.g., /user-profile, /dashboard). - Use PascalCase for route components (e.g., UserProfileComponent, DashboardComponent). - ## 3. Comments - Use comments sparingly; prefer self-explanatory code. @@ -142,4 +143,4 @@ const addNumbers = (a: number, b: number): number => a + b; ## 7. Angular-Specific Guidelines -- Follow the official [Angular Style Guide](https://angular.io/guide/styleguide). \ No newline at end of file +- Follow the official [Angular Style Guide](https://angular.io/guide/styleguide). diff --git a/src/app/account/account-routing.module.ts b/src/app/account/account-routing.module.ts index 6c0ead68..6f6fe41d 100644 --- a/src/app/account/account-routing.module.ts +++ b/src/app/account/account-routing.module.ts @@ -1,73 +1,84 @@ -import { IsAuth } from "../core/_guards/auth.guard"; -import { Routes, RouterModule } from '@angular/router'; -import { NgModule } from "@angular/core"; +import { IsAuth } from '../core/_guards/auth.guard'; +import { RouterModule, Routes } from '@angular/router'; +import { NgModule } from '@angular/core'; -import { AccountSettingsComponent } from "./settings/acc-settings/acc-settings.component"; -import { UiSettingsComponent } from "./settings/ui-settings/ui-settings.component"; -import { NewNotificationComponent } from "./notifications/notification/new-notification.component"; -import { FormUIsettingsComponent } from "../shared/form/formuisettings.component"; -import { NotificationsComponent } from "./notifications/notifications.component"; -import { AccountComponent } from "./account.component"; +import { AccountSettingsComponent } from './settings/acc-settings/acc-settings.component'; +import { UiSettingsComponent } from './settings/ui-settings/ui-settings.component'; +import { NewNotificationComponent } from './notifications/notification/new-notification.component'; +import { NotificationsComponent } from './notifications/notifications.component'; +import { AccountComponent } from './account.component'; import { SERV } from '../core/_services/main.config'; -import { FormComponent } from "../shared/form/form.component"; +import { FormComponent } from '../shared/form/form.component'; +import { MyRoute, RouteData } from '../core/_models/routes.model'; -const routes: Routes = [ +const routes: MyRoute[] = [ { path: '', children: [ - { - path: '', component: AccountComponent, - data: { - kind: 'account', - breadcrumb: '' - }, - canActivate: [IsAuth]}, - { - path: 'acc-settings', component: AccountSettingsComponent, - data: { - kind: 'acc-settings', - breadcrumb: 'Account Settings' - }, - canActivate: [IsAuth]}, - { - path: 'ui-settings', component: UiSettingsComponent, - data: { - kind: 'uisettings', - type: 'edit', - path: SERV.CONFIGS, - breadcrumb: 'UI Settings' - }, - canActivate: [IsAuth]}, - { - path: 'notifications', component: NotificationsComponent, - data: { - kind: 'notifications', - breadcrumb: 'Notifications' - }, - canActivate: [IsAuth]}, - { - path: 'notifications/:id/edit', component: FormComponent, - data: { - kind: 'editnotif', - type: 'edit', - path: SERV.NOTIFICATIONS, - breadcrumb: 'Edit Notification' - }, - canActivate: [IsAuth]}, - { - path: 'notifications/new-notification', component: NewNotificationComponent, - data: { - kind: 'new-notifications', - breadcrumb: 'New Notification' - }, - canActivate: [IsAuth]}, - ] + { + path: '', + component: AccountComponent, + data: { + kind: 'account', + breadcrumb: '' + }, + canActivate: [IsAuth] + }, + { + path: 'acc-settings', + component: AccountSettingsComponent, + data: { + kind: 'acc-settings', + breadcrumb: 'Account Settings' + }, + canActivate: [IsAuth] + }, + { + path: 'ui-settings', + component: UiSettingsComponent, + data: { + kind: 'uisettings', + type: 'edit', + path: SERV.CONFIGS, + breadcrumb: 'UI Settings' + }, + canActivate: [IsAuth] + }, + { + path: 'notifications', + component: NotificationsComponent, + data: { + kind: 'notifications', + breadcrumb: 'Notifications' + }, + canActivate: [IsAuth] + }, + { + path: 'notifications/:id/edit', + component: FormComponent, + data: { + kind: 'editnotif', + type: 'edit', + path: SERV.NOTIFICATIONS, + breadcrumb: 'Edit Notification' + }, + canActivate: [IsAuth] + }, + { + path: 'notifications/new-notification', + component: NewNotificationComponent, + data: { + kind: 'new-notifications', + breadcrumb: 'New Notification' + }, + canActivate: [IsAuth] } - ] + ] + } +]; @NgModule({ imports: [RouterModule.forChild(routes)], exports: [RouterModule] - }) export class AccountRoutingModule {} diff --git a/src/app/agents/agents-routing.module.ts b/src/app/agents/agents-routing.module.ts index e401d91d..18f1e676 100644 --- a/src/app/agents/agents-routing.module.ts +++ b/src/app/agents/agents-routing.module.ts @@ -1,59 +1,66 @@ -import { CheckPerm } from "../core/_guards/permission.guard"; -import { IsAuth } from "../core/_guards/auth.guard"; -import { NgModule } from "@angular/core"; -import { Routes, RouterModule } from '@angular/router'; +import { CheckPerm } from '../core/_guards/permission.guard'; +import { IsAuth } from '../core/_guards/auth.guard'; +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; -import { AgentStatusComponent } from "./agent-status/agent-status.component"; -import { PendingChangesGuard } from "../core/_guards/pendingchanges.guard"; -import { ShowAgentsComponent } from "./show-agents/show-agents.component"; -import { EditAgentComponent } from "./edit-agent/edit-agent.component"; -import { NewAgentComponent } from "./new-agent/new-agent.component"; +import { AgentStatusComponent } from './agent-status/agent-status.component'; +import { PendingChangesGuard } from '../core/_guards/pendingchanges.guard'; +import { ShowAgentsComponent } from './show-agents/show-agents.component'; +import { EditAgentComponent } from './edit-agent/edit-agent.component'; +import { NewAgentComponent } from './new-agent/new-agent.component'; +import { MyRoute, RouteData } from '../core/_models/routes.model'; -const routes: Routes = [ +const routes: MyRoute[] = [ { path: '', canActivate: [IsAuth], children: [ { - path: 'agent-status', component: AgentStatusComponent, + path: 'agent-status', + component: AgentStatusComponent, data: { - kind: 'agent-status', - breadcrumb: 'Agent Status', - permission: 'Agent' //ToDo this one has Agent read and Agent Stats read + kind: 'agent-status', + breadcrumb: 'Agent Status', + permission: 'Agent' //ToDo this one has Agent read and Agent Stats read }, canActivate: [CheckPerm] }, { - path: 'new-agent', component: NewAgentComponent, + path: 'new-agent', + component: NewAgentComponent, data: { - kind: 'new-agent', - breadcrumb: 'New Agent', - permission: 'Agent' + kind: 'new-agent', + breadcrumb: 'New Agent', + permission: 'Agent' }, - canActivate: [CheckPerm]}, + canActivate: [CheckPerm] + }, { - path: 'show-agents', component: ShowAgentsComponent, + path: 'show-agents', + component: ShowAgentsComponent, data: { - kind: 'show-agents', - breadcrumb: 'Show Agent', - permission: 'Agent' + kind: 'show-agents', + breadcrumb: 'Show Agent', + permission: 'Agent' }, - canActivate: [CheckPerm]}, + canActivate: [CheckPerm] + }, { - path: 'show-agents/:id/edit', component: EditAgentComponent, + path: 'show-agents/:id/edit', + component: EditAgentComponent, data: { - kind: 'edit-agent', - breadcrumb: 'Edit Agent', - permission: 'Agent' + kind: 'edit-agent', + breadcrumb: 'Edit Agent', + permission: 'Agent' }, - canActivate: [CheckPerm]}, - ] - }, + canActivate: [CheckPerm] + } + ] + } ]; @NgModule({ imports: [RouterModule.forChild(routes)], exports: [RouterModule] - }) export class AgentsRoutingModule {} diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 685461a6..b9f24e2a 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -1,5 +1,5 @@ import { AppPreloadingStrategy } from './core/app_preloading_strategy'; -import { Routes, RouterModule } from '@angular/router'; +import { RouterModule, Routes } from '@angular/router'; import { NgModule } from '@angular/core'; import { PageNotFoundComponent } from './layout/page-not-found/page-not-found.component'; @@ -7,72 +7,84 @@ import { ErrorPageComponent } from './layout/error-page/error-page.component'; import { IsAuth } from './core/_guards/auth.guard'; const appRoutes: Routes = [ - {path: '', - loadChildren: () => import('./home/home.module').then(m => m.HomeModule), - data: { preload: true, delay: false } - }, - { - path: 'projects', - loadChildren: () => import('./projects/projects.module').then(m => m.ProjectsModule), - data: { preload: true, delay: false } - }, - { - path: 'agents', - loadChildren: () => import('./agents/agent.module').then(m => m.AgentsModule), - data: { preload: true, delay: false } - }, - { - path: 'tasks', - loadChildren: () => import('./tasks/tasks.module').then(m => m.TasksModule), - data: { preload: true, delay: false } - }, - { - path: 'hashlists', - loadChildren: () => import('./hashlists/hashlists.module').then(m => m.HashlistModule), - data: { preload: true, delay: true } - }, - { - path: 'files', - loadChildren: () => import('./files/files.module').then(m => m.FilesModule), - data: { preload: false, delay: false } - }, - { - path: 'account', - loadChildren: () => import('./account/account.module').then(m => m.AccountModule), - data: { preload: false, delay: false } - }, - { - path: 'users', - loadChildren: () => import('./users/users.module').then(m => m.UsersModule), - data: { preload: false, delay: false } - }, - { - path: 'config', - loadChildren: () => import('./config/config.module').then(m => m.ConfigModule), - data: { preload: false, delay: false } - }, - {path: 'error', component: ErrorPageComponent, data:{message: 'Page Not Found!'} ,canActivate: [IsAuth] }, - {path: 'access-denied', component: ErrorPageComponent, data:{message: 'Sorry, You are not allowed to access this page!'} ,canActivate: [IsAuth] }, - {path: 'not-found', component: PageNotFoundComponent ,canActivate: [IsAuth] }, - {path: '**', redirectTo: 'not-found'} // Note: Always the last route. Don't change position. - ]; + { + path: '', + loadChildren: () => import('./home/home.module').then((m) => m.HomeModule), + data: { preload: true, delay: false } + }, + { + path: 'agents', + loadChildren: () => + import('./agents/agent.module').then((m) => m.AgentsModule), + data: { preload: true, delay: false } + }, + { + path: 'tasks', + loadChildren: () => + import('./tasks/tasks.module').then((m) => m.TasksModule), + data: { preload: true, delay: false } + }, + { + path: 'hashlists', + loadChildren: () => + import('./hashlists/hashlists.module').then((m) => m.HashlistModule), + data: { preload: true, delay: true } + }, + { + path: 'files', + loadChildren: () => + import('./files/files.module').then((m) => m.FilesModule), + data: { preload: false, delay: false } + }, + { + path: 'account', + loadChildren: () => + import('./account/account.module').then((m) => m.AccountModule), + data: { preload: false, delay: false } + }, + { + path: 'users', + loadChildren: () => + import('./users/users.module').then((m) => m.UsersModule), + data: { preload: false, delay: false } + }, + { + path: 'config', + loadChildren: () => + import('./config/config.module').then((m) => m.ConfigModule), + data: { preload: false, delay: false } + }, + { + path: 'error', + component: ErrorPageComponent, + data: { message: 'Page Not Found!' }, + canActivate: [IsAuth] + }, + { + path: 'access-denied', + component: ErrorPageComponent, + data: { message: 'Sorry, You are not allowed to access this page!' }, + canActivate: [IsAuth] + }, + { + path: 'not-found', + component: PageNotFoundComponent, + canActivate: [IsAuth] + }, + { path: '**', redirectTo: 'not-found' } // Note: Always the last route. Don't change position. +]; @NgModule({ - imports: [ - RouterModule.forRoot( - appRoutes, - { - // enableTracing: false, // <-- Debugging purposes only - preloadingStrategy: AppPreloadingStrategy, - // When useHash is set to true, Angular uses the # symbol to append the route to the base URL. - // If it is set false, the route is appended to the URL as a path. This would require server-side configuration. - // For direct access to URLs, the server needs to be configured to return the index.html file for all requested URLs. - useHash: true + imports: [ + RouterModule.forRoot(appRoutes, { + // enableTracing: false, // <-- Debugging purposes only + preloadingStrategy: AppPreloadingStrategy, + // When useHash is set to true, Angular uses the # symbol to append the route to the base URL. + // If it is set false, the route is appended to the URL as a path. This would require server-side configuration. + // For direct access to URLs, the server needs to be configured to return the index.html file for all requested URLs. + useHash: true + }) + ], + exports: [RouterModule] }) - ], - exports:[ - RouterModule - ] -}) - -export class AppRoutingModule{} +export class AppRoutingModule {} diff --git a/src/app/auth/auth.component.html b/src/app/auth/auth.component.html new file mode 100644 index 00000000..8acd1413 --- /dev/null +++ b/src/app/auth/auth.component.html @@ -0,0 +1,50 @@ + diff --git a/src/app/auth/auth.component.ts b/src/app/auth/auth.component.ts index ef00e872..ee6572da 100644 --- a/src/app/auth/auth.component.ts +++ b/src/app/auth/auth.component.ts @@ -2,12 +2,6 @@ import { AuthResponseData, AuthService } from '../core/_services/access/auth.service'; -import { - faEye, - faEyeSlash, - faLock, - faUser -} from '@fortawesome/free-solid-svg-icons'; import { environment } from './../../environments/environment'; import { Component, Inject, OnInit } from '@angular/core'; import { Router } from '@angular/router'; @@ -17,101 +11,12 @@ import { ConfigService } from '../core/_services/shared/config.service'; @Component({ selector: 'app-login', - template: ` - - `, - styles: [ - ` - .login-container { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - height: 100vh; - } - - .login-card { - width: 400px; - margin-bottom: 16px; - } - - mat-form-field { - width: 100%; - margin-bottom: 16px; - } - - .mat-raised-button { - width: 100%; - } - - .forgot-password-link { - margin-top: 16px; - text-align: center; - } - - .forgot-password-link a { - color: #2196f3; /* Use the color you prefer */ - text-decoration: none; - } - - .mat-card-alert { - background-color: #ffebee; /* Use the alert color you prefer */ - margin-top: 16px; - } - ` - ] + templateUrl: './auth.component.html' }) export class AuthComponent implements OnInit { errorRes: string | null; - public hide = true; + public isVisible = true; headerConfig: any; constructor( diff --git a/src/app/config/config-routing.module.ts b/src/app/config/config-routing.module.ts index bd8d49c5..9e3660a6 100644 --- a/src/app/config/config-routing.module.ts +++ b/src/app/config/config-routing.module.ts @@ -1,236 +1,280 @@ -import { CheckPerm } from "../core/_guards/permission.guard"; -import { Routes, RouterModule } from '@angular/router'; -import { IsAuth } from "../core/_guards/auth.guard"; -import { NgModule } from "@angular/core"; +import { CheckPerm } from '../core/_guards/permission.guard'; +import { RouterModule, Routes } from '@angular/router'; +import { IsAuth } from '../core/_guards/auth.guard'; +import { NgModule } from '@angular/core'; -import { EditHealthChecksComponent } from "./health-checks/edit-health-check/edit-health-checks.component"; -import { NewHealthChecksComponent } from "./health-checks/new-health-check/new-health-checks.component"; -import { AgentBinariesComponent } from "./engine/agent-binaries/agent-binaries.component"; -import { PreprocessorsComponent } from "./engine/preprocessors/preprocessors.component"; -import { HealthChecksComponent } from "./health-checks/health-checks.component"; -import { FormConfigComponent } from "../shared/form/formconfig.component"; -import { CrackersComponent } from "./engine/crackers/crackers.component"; -import { HashtypesComponent } from "./hashtypes/hashtypes.component"; -import { FormComponent } from "../shared/form/form.component"; +import { EditHealthChecksComponent } from './health-checks/edit-health-check/edit-health-checks.component'; +import { NewHealthChecksComponent } from './health-checks/new-health-check/new-health-checks.component'; +import { AgentBinariesComponent } from './engine/agent-binaries/agent-binaries.component'; +import { PreprocessorsComponent } from './engine/preprocessors/preprocessors.component'; +import { HealthChecksComponent } from './health-checks/health-checks.component'; +import { FormConfigComponent } from '../shared/form/formconfig.component'; +import { CrackersComponent } from './engine/crackers/crackers.component'; +import { HashtypesComponent } from './hashtypes/hashtypes.component'; +import { MyRoute, RouteData } from '../core/_models/routes.model'; +import { FormComponent } from '../shared/form/form.component'; import { SERV } from '../core/_services/main.config'; -import { LogComponent } from "./log/log.component"; +import { LogComponent } from './log/log.component'; -const routes: Routes = [ +const routes: MyRoute[] = [ { path: '', canActivate: [IsAuth], children: [ - { - path: 'agent', component: FormConfigComponent, - data: { - kind: 'serveragent', - type: 'edit', - path: SERV.CONFIGS, - breadcrumb: 'Agent Settings', - permission: 'Config' - }, - canActivate: [CheckPerm]}, - { - path: 'task-chunk', component: FormConfigComponent, - data: { - kind: 'servertaskchunk', - type: 'edit', - path: SERV.CONFIGS, - breadcrumb: 'Task Chunk Settings', - permission: 'Config' - }, - canActivate: [CheckPerm]}, - { - path: 'hch', component: FormConfigComponent, - data: { - kind: 'serverhch', - type: 'edit', - path: SERV.CONFIGS, - breadcrumb: 'Hashes/Cracks/Hashlist Settings', - permission: 'Config' - }, - canActivate: [CheckPerm]}, - { - path: 'notifications', component: FormConfigComponent, - data: { - kind: 'servernotif', - type: 'edit', - path: SERV.CONFIGS, - breadcrumb: 'Notifications', - permission: 'Notif' - }, - canActivate: [CheckPerm]}, - { - path: 'general-settings', component: FormConfigComponent, - data: { - kind: 'servergs', - type: 'edit', - path: SERV.CONFIGS, - breadcrumb: 'General Settings', - permission: 'Config' - }, - canActivate: [CheckPerm]}, - { - path: 'hashtypes', component: HashtypesComponent, - data: { - kind: 'hashtypes', - breadcrumb: 'Hashtypes', - permission: 'Hashtype' - }, - canActivate: [CheckPerm]}, - { - path: 'hashtypes/new', component: FormComponent, - data: { - kind: 'newhashtype', - type: 'create', - path: SERV.HASHTYPES, - breadcrumb: 'New Hashtype', - permission: 'Hashtype' - }, - canActivate: [CheckPerm]}, - { - path: 'hashtypes/:id/edit', component: FormComponent, - data: { - kind: 'edithashtype', - type: 'edit', - path: SERV.HASHTYPES, - breadcrumb: 'Edit Hashtype', - permission: 'Hashtype' - }, - canActivate: [CheckPerm]}, - { - path: 'log', component: LogComponent, - data: { - kind: 'log', - breadcrumb: 'Logs', - permission: 'Logs' - }, - canActivate: [CheckPerm]}, - { - path: 'health-checks', component: HealthChecksComponent, - data: { - kind: 'health-checks', - breadcrumb: 'Health Checks', - permission: 'HealthCheck' - }, - canActivate: [CheckPerm]}, - { - path: 'health-checks/new', component: NewHealthChecksComponent, - data: { - kind: 'new-health-checks', - breadcrumb: 'New Health Checks', - permission: 'HealthCheck' - }, - canActivate: [CheckPerm]}, - { - path: 'health-checks/:id/edit', component: EditHealthChecksComponent, - data: { - kind: 'edit-health-checks', - breadcrumb: 'Edit Health Checks', - permission: 'HealthCheck' - }, - canActivate: [CheckPerm]}, - { - path: 'engine/agent-binaries', component: AgentBinariesComponent, - data: { - kind: 'agent-binaries', - breadcrumb: 'Engine > Agent-binaries', - permission: 'AgentBinary' - }, - canActivate: [CheckPerm]}, - { - path: 'engine/agent-binaries/new-agent-binary', component: FormComponent, - data: { - kind: 'newagentbinary', - type: 'create', - path: SERV.AGENT_BINARY, - breadcrumb: 'Engine > New Agent binary', - permission: 'AgentBinary' - }, - canActivate: [CheckPerm]}, - { - path: 'engine/agent-binaries/:id/edit', component: FormComponent, - data: { - kind: 'editagentbinary', - type: 'edit', - path: SERV.AGENT_BINARY, - breadcrumb: 'Engine > Edit Agent binary', - permission: 'AgentBinary' - }, - canActivate: [CheckPerm]}, - { - path: 'engine/crackers', component: CrackersComponent, - data: { - kind: 'crackers', - breadcrumb: 'Engine > Crackers', - permission: 'CrackerBinary' - }, - canActivate: [CheckPerm]}, - { - path: 'engine/crackers/new', component: FormComponent, - data: { - kind: 'newcracker', - type: 'create', - path: SERV.CRACKERS_TYPES, - breadcrumb: 'Engine > New Cracker', - permission: 'CrackerBinary' - }, - canActivate: [CheckPerm]}, - { - path: 'engine/crackers/:id/new', component: FormComponent, - data: { - kind: 'newcrackerversion', - type: 'create', - path: SERV.CRACKERS, - breadcrumb: 'Engine > New Cracker Version/Binary', - permission: 'CrackerBinary' - }, - canActivate: [CheckPerm]}, - { - path: 'engine/crackers/:id/edit', component: FormComponent, - data: { - kind: 'editcrackerversion', - type: 'edit', - path: SERV.CRACKERS, - breadcrumb: 'Engine > Edit Cracker Version/Binary', - permission: 'CrackerBinaryType' - }, - canActivate: [CheckPerm]}, - { - path: 'engine/preprocessors', component: PreprocessorsComponent, - data: { - kind: 'preprocessors', - breadcrumb: 'Engine > Preprocessors', - permission: 'Prepro' - }, - canActivate: [CheckPerm]}, - { - path: 'engine/preprocessors/new-preprocessor', component: FormComponent, - data: { - kind: 'newpreprocessor', - type: 'create', - path: SERV.PREPROCESSORS, - breadcrumb: 'Engine > New Preprocessor', - permission: 'Prepro' - }, - canActivate: [CheckPerm]}, - { - path: 'engine/preprocessors/:id/edit', component: FormComponent, - data: { - kind: 'editpreprocessor', - type: 'edit', - path: SERV.PREPROCESSORS, - breadcrumb: 'Engine > Edit Preprocessor', - permission: 'Prepro' - }, - canActivate: [CheckPerm]}, + { + path: 'agent', + component: FormConfigComponent, + data: { + kind: 'serveragent', + type: 'edit', + path: SERV.CONFIGS, + breadcrumb: 'Agent Settings', + permission: 'Config' + }, + canActivate: [CheckPerm] + }, + { + path: 'task-chunk', + component: FormConfigComponent, + data: { + kind: 'servertaskchunk', + type: 'edit', + path: SERV.CONFIGS, + breadcrumb: 'Task Chunk Settings', + permission: 'Config' + }, + canActivate: [CheckPerm] + }, + { + path: 'hch', + component: FormConfigComponent, + data: { + kind: 'serverhch', + type: 'edit', + path: SERV.CONFIGS, + breadcrumb: 'Hashes/Cracks/Hashlist Settings', + permission: 'Config' + }, + canActivate: [CheckPerm] + }, + { + path: 'notifications', + component: FormConfigComponent, + data: { + kind: 'servernotif', + type: 'edit', + path: SERV.CONFIGS, + breadcrumb: 'Notifications', + permission: 'Notif' + }, + canActivate: [CheckPerm] + }, + { + path: 'general-settings', + component: FormConfigComponent, + data: { + kind: 'servergs', + type: 'edit', + path: SERV.CONFIGS, + breadcrumb: 'General Settings', + permission: 'Config' + }, + canActivate: [CheckPerm] + }, + { + path: 'hashtypes', + component: HashtypesComponent, + data: { + kind: 'hashtypes', + breadcrumb: 'Hashtypes', + permission: 'Hashtype' + }, + canActivate: [CheckPerm] + }, + { + path: 'hashtypes/new', + component: FormComponent, + data: { + kind: 'newhashtype', + type: 'create', + path: SERV.HASHTYPES, + breadcrumb: 'New Hashtype', + permission: 'Hashtype' + }, + canActivate: [CheckPerm] + }, + { + path: 'hashtypes/:id/edit', + component: FormComponent, + data: { + kind: 'edithashtype', + type: 'edit', + path: SERV.HASHTYPES, + breadcrumb: 'Edit Hashtype', + permission: 'Hashtype' + }, + canActivate: [CheckPerm] + }, + { + path: 'log', + component: LogComponent, + data: { + kind: 'log', + breadcrumb: 'Logs', + permission: 'Logs' + }, + canActivate: [CheckPerm] + }, + { + path: 'health-checks', + component: HealthChecksComponent, + data: { + kind: 'health-checks', + breadcrumb: 'Health Checks', + permission: 'HealthCheck' + }, + canActivate: [CheckPerm] + }, + { + path: 'health-checks/new', + component: NewHealthChecksComponent, + data: { + kind: 'new-health-checks', + breadcrumb: 'New Health Checks', + permission: 'HealthCheck' + }, + canActivate: [CheckPerm] + }, + { + path: 'health-checks/:id/edit', + component: EditHealthChecksComponent, + data: { + kind: 'edit-health-checks', + breadcrumb: 'Edit Health Checks', + permission: 'HealthCheck' + }, + canActivate: [CheckPerm] + }, + { + path: 'engine/agent-binaries', + component: AgentBinariesComponent, + data: { + kind: 'agent-binaries', + breadcrumb: 'Engine > Agent-binaries', + permission: 'AgentBinary' + }, + canActivate: [CheckPerm] + }, + { + path: 'engine/agent-binaries/new-agent-binary', + component: FormComponent, + data: { + kind: 'newagentbinary', + type: 'create', + path: SERV.AGENT_BINARY, + breadcrumb: 'Engine > New Agent binary', + permission: 'AgentBinary' + }, + canActivate: [CheckPerm] + }, + { + path: 'engine/agent-binaries/:id/edit', + component: FormComponent, + data: { + kind: 'editagentbinary', + type: 'edit', + path: SERV.AGENT_BINARY, + breadcrumb: 'Engine > Edit Agent binary', + permission: 'AgentBinary' + }, + canActivate: [CheckPerm] + }, + { + path: 'engine/crackers', + component: CrackersComponent, + data: { + kind: 'crackers', + breadcrumb: 'Engine > Crackers', + permission: 'CrackerBinary' + }, + canActivate: [CheckPerm] + }, + { + path: 'engine/crackers/new', + component: FormComponent, + data: { + kind: 'newcracker', + type: 'create', + path: SERV.CRACKERS_TYPES, + breadcrumb: 'Engine > New Cracker', + permission: 'CrackerBinary' + }, + canActivate: [CheckPerm] + }, + { + path: 'engine/crackers/:id/new', + component: FormComponent, + data: { + kind: 'newcrackerversion', + type: 'create', + path: SERV.CRACKERS, + breadcrumb: 'Engine > New Cracker Version/Binary', + permission: 'CrackerBinary' + }, + canActivate: [CheckPerm] + }, + { + path: 'engine/crackers/:id/edit', + component: FormComponent, + data: { + kind: 'editcrackerversion', + type: 'edit', + path: SERV.CRACKERS, + breadcrumb: 'Engine > Edit Cracker Version/Binary', + permission: 'CrackerBinaryType' + }, + canActivate: [CheckPerm] + }, + { + path: 'engine/preprocessors', + component: PreprocessorsComponent, + data: { + kind: 'preprocessors', + breadcrumb: 'Engine > Preprocessors', + permission: 'Prepro' + }, + canActivate: [CheckPerm] + }, + { + path: 'engine/preprocessors/new-preprocessor', + component: FormComponent, + data: { + kind: 'newpreprocessor', + type: 'create', + path: SERV.PREPROCESSORS, + breadcrumb: 'Engine > New Preprocessor', + permission: 'Prepro' + }, + canActivate: [CheckPerm] + }, + { + path: 'engine/preprocessors/:id/edit', + component: FormComponent, + data: { + kind: 'editpreprocessor', + type: 'edit', + path: SERV.PREPROCESSORS, + breadcrumb: 'Engine > Edit Preprocessor', + permission: 'Prepro' + }, + canActivate: [CheckPerm] + } ] -}, + } ]; @NgModule({ imports: [RouterModule.forChild(routes)], exports: [RouterModule] - }) export class ConfigRoutingModule {} diff --git a/src/app/core/_models/routes.model.ts b/src/app/core/_models/routes.model.ts new file mode 100644 index 00000000..8a870db0 --- /dev/null +++ b/src/app/core/_models/routes.model.ts @@ -0,0 +1,16 @@ +export interface RouteData { + kind?: string; + type?: string; + path?: any; + permission?: string; + breadcrumb?: string; +} + +export interface MyRoute { + path: string; + component?: any; // Option as first path is empty + data?: RouteData; + canActivate?: any[]; // Replace 'any[]' with the actual canActivate guards + canDeactivate?: any[]; // Replace 'any[]' with the actual canDeactivate guards + children?: MyRoute[]; +} diff --git a/src/app/core/_services/unsubscribe.service.ts b/src/app/core/_services/unsubscribe.service.ts index 0d7d101f..c1086f7b 100644 --- a/src/app/core/_services/unsubscribe.service.ts +++ b/src/app/core/_services/unsubscribe.service.ts @@ -5,7 +5,7 @@ import { Subscription } from 'rxjs'; * Service for managing and unsubscribing from subscriptions in Angular components. */ @Injectable({ - providedIn: 'root', + providedIn: 'root' }) export class UnsubscribeService { private subscriptions: Subscription[] = []; diff --git a/src/app/files/files-routing.module.ts b/src/app/files/files-routing.module.ts index 9b83dda2..e3ee723b 100644 --- a/src/app/files/files-routing.module.ts +++ b/src/app/files/files-routing.module.ts @@ -1,95 +1,114 @@ -import { CheckPerm } from "../core/_guards/permission.guard"; -import { Routes, RouterModule } from '@angular/router'; -import { IsAuth } from "../core/_guards/auth.guard"; -import { NgModule } from "@angular/core"; +import { CheckPerm } from '../core/_guards/permission.guard'; +import { RouterModule, Routes } from '@angular/router'; +import { IsAuth } from '../core/_guards/auth.guard'; +import { NgModule } from '@angular/core'; -import { FilesEditComponent } from "./files-edit/files-edit.component"; -import { NewFilesComponent } from "./new-files/new-files.component"; -import { FilesComponent } from "./files.component"; +import { FilesEditComponent } from './files-edit/files-edit.component'; +import { NewFilesComponent } from './new-files/new-files.component'; +import { MyRoute, RouteData } from '../core/_models/routes.model'; +import { FilesComponent } from './files.component'; -const routes: Routes = [ +const routes: MyRoute[] = [ { path: '', + canActivate: [IsAuth], children: [ { - path: 'wordlist', component: FilesComponent, + path: 'wordlist', + component: FilesComponent, data: { - kind: 'wordlist', - breadcrumb: 'Wordlist', - permission: 'File' + kind: 'wordlist', + breadcrumb: 'Wordlist', + permission: 'File' }, - canActivate: [IsAuth,CheckPerm]}, + canActivate: [CheckPerm] + }, { - path: 'wordlist/new-wordlist', component: NewFilesComponent, + path: 'wordlist/new-wordlist', + component: NewFilesComponent, data: { - kind: 'wordlist-new', - breadcrumb: 'Wordlist New', - permission: 'File' + kind: 'wordlist-new', + breadcrumb: 'Wordlist New', + permission: 'File' }, - canActivate: [IsAuth,CheckPerm]}, + canActivate: [CheckPerm] + }, { - path: ':id/wordlist-edit', component: FilesEditComponent, + path: ':id/wordlist-edit', + component: FilesEditComponent, data: { - kind: 'wordlist-edit', - breadcrumb: 'Wordlist Edit', - permission: 'File' + kind: 'wordlist-edit', + breadcrumb: 'Wordlist Edit', + permission: 'File' }, - canActivate: [IsAuth,CheckPerm]}, + canActivate: [CheckPerm] + }, { - path: 'rules', component: FilesComponent, + path: 'rules', + component: FilesComponent, data: { - kind: 'rules', - breadcrumb: 'Rules', - permission: 'File' + kind: 'rules', + breadcrumb: 'Rules', + permission: 'File' }, - canActivate: [IsAuth,CheckPerm]}, + canActivate: [CheckPerm] + }, { - path: 'rules/new-rule', component: NewFilesComponent, + path: 'rules/new-rule', + component: NewFilesComponent, data: { - kind: 'rule-new', - breadcrumb: 'Rule New', - permission: 'File' + kind: 'rule-new', + breadcrumb: 'Rule New', + permission: 'File' }, - canActivate: [IsAuth,CheckPerm]}, + canActivate: [CheckPerm] + }, { - path: ':id/rules-edit', component: FilesEditComponent, + path: ':id/rules-edit', + component: FilesEditComponent, data: { - kind: 'rules-edit', - breadcrumb: 'Rules Edit', - permission: 'File' + kind: 'rules-edit', + breadcrumb: 'Rules Edit', + permission: 'File' }, - canActivate: [IsAuth,CheckPerm]}, + canActivate: [CheckPerm] + }, { - path: 'other', component: FilesComponent, + path: 'other', + component: FilesComponent, data: { - kind: 'other', - breadcrumb: 'Other', - permission: 'File' + kind: 'other', + breadcrumb: 'Other', + permission: 'File' }, - canActivate: [IsAuth,CheckPerm]}, + canActivate: [CheckPerm] + }, { - path: 'other/new-other', component: NewFilesComponent, + path: 'other/new-other', + component: NewFilesComponent, data: { - kind: 'other-new', - breadcrumb: 'Other New', - permission: 'File' + kind: 'other-new', + breadcrumb: 'Other New', + permission: 'File' }, - canActivate: [IsAuth,CheckPerm]}, + canActivate: [CheckPerm] + }, { - path: ':id/other-edit', component: FilesEditComponent, + path: ':id/other-edit', + component: FilesEditComponent, data: { - kind: 'other-edit', - breadcrumb: 'Other Edit', - permission: 'File' + kind: 'other-edit', + breadcrumb: 'Other Edit', + permission: 'File' }, - canActivate: [IsAuth,CheckPerm]}, + canActivate: [CheckPerm] + } ] } -] +]; @NgModule({ imports: [RouterModule.forChild(routes)], exports: [RouterModule] - }) export class FilesRoutingModule {} diff --git a/src/app/hashlists/hashlists-routing.module.ts b/src/app/hashlists/hashlists-routing.module.ts index cefff87f..84a441ee 100644 --- a/src/app/hashlists/hashlists-routing.module.ts +++ b/src/app/hashlists/hashlists-routing.module.ts @@ -1,129 +1,152 @@ -import { CheckPerm } from "../core/_guards/permission.guard"; -import { Routes, RouterModule } from '@angular/router'; -import { IsAuth } from "../core/_guards/auth.guard"; -import { NgModule } from "@angular/core"; +import { CheckPerm } from '../core/_guards/permission.guard'; +import { RouterModule, Routes } from '@angular/router'; +import { IsAuth } from '../core/_guards/auth.guard'; +import { NgModule } from '@angular/core'; -import { NewSuperhashlistComponent } from "./new-superhashlist/new-superhashlist.component"; -import { SuperhashlistComponent } from "./superhashlist/superhashlist.component"; -import { EditHashlistComponent } from "./edit-hashlist/edit-hashlist.component"; -import { NewHashlistComponent } from "./new-hashlist/new-hashlist.component"; -import { PendingChangesGuard } from "../core/_guards/pendingchanges.guard"; -import { SearchHashComponent } from "./search-hash/search-hash.component"; -import { ShowCracksComponent } from "./show-cracks/show-cracks.component"; -import { HashlistComponent } from "./hashlist/hashlist.component"; -import { HashesComponent } from "./hashes/hashes.component"; +import { NewSuperhashlistComponent } from './new-superhashlist/new-superhashlist.component'; +import { SuperhashlistComponent } from './superhashlist/superhashlist.component'; +import { EditHashlistComponent } from './edit-hashlist/edit-hashlist.component'; +import { NewHashlistComponent } from './new-hashlist/new-hashlist.component'; +import { PendingChangesGuard } from '../core/_guards/pendingchanges.guard'; +import { SearchHashComponent } from './search-hash/search-hash.component'; +import { ShowCracksComponent } from './show-cracks/show-cracks.component'; +import { MyRoute, RouteData } from '../core/_models/routes.model'; +import { HashlistComponent } from './hashlist/hashlist.component'; +import { HashesComponent } from './hashes/hashes.component'; -const routes: Routes = [ +const routes: MyRoute[] = [ { path: '', + canActivate: [IsAuth], children: [ { - path: 'hashlist', component: HashlistComponent, + path: 'hashlist', + component: HashlistComponent, data: { - kind: 'hashlist', - breadcrumb: 'Hashlist', - permission: 'Hashlist' + kind: 'hashlist', + breadcrumb: 'Hashlist', + permission: 'Hashlist' }, - canActivate: [IsAuth,CheckPerm]}, + canActivate: [CheckPerm] + }, { - path: 'archived', component: HashlistComponent, + path: 'archived', + component: HashlistComponent, data: { - kind: 'archived', - breadcrumb: 'Hashlist Archived', - permission: 'Hashlist' + kind: 'archived', + breadcrumb: 'Hashlist Archived', + permission: 'Hashlist' }, - canActivate: [IsAuth,CheckPerm]}, + canActivate: [CheckPerm] + }, { - path: 'hashlist/:id/edit', component: EditHashlistComponent, + path: 'hashlist/:id/edit', + component: EditHashlistComponent, data: { - kind: 'edit-hashlist', - breadcrumb: 'Edit Hashlist', - permission: 'Hashlist' + kind: 'edit-hashlist', + breadcrumb: 'Edit Hashlist', + permission: 'Hashlist' }, - canActivate: [IsAuth,CheckPerm], + canActivate: [CheckPerm] // canDeactivate: [PendingChangesGuard] }, { - path: 'new-hashlist', component: NewHashlistComponent, + path: 'new-hashlist', + component: NewHashlistComponent, data: { - kind: 'new-hashlist', - breadcrumb: 'New Hashlist', - permission: 'SuperHashlist' + kind: 'new-hashlist', + breadcrumb: 'New Hashlist', + permission: 'SuperHashlist' }, - canActivate: [IsAuth,CheckPerm], + canActivate: [CheckPerm] // canDeactivate: [PendingChangesGuard] }, { - path: 'superhashlist', component: SuperhashlistComponent, + path: 'superhashlist', + component: SuperhashlistComponent, data: { - kind: 'super-hashlist', - breadcrumb: 'Super Hashlist', - permission: 'SuperHashlist' + kind: 'super-hashlist', + breadcrumb: 'Super Hashlist', + permission: 'SuperHashlist' }, - canActivate: [IsAuth,CheckPerm]}, + canActivate: [CheckPerm] + }, { - path: 'superhashlist/:id/edit', component: EditHashlistComponent, + path: 'superhashlist/:id/edit', + component: EditHashlistComponent, data: { - kind: 'edit-super-hashlist', - breadcrumb: 'Edit Super Hashlist', - permission: 'SuperHashlist' + kind: 'edit-super-hashlist', + breadcrumb: 'Edit Super Hashlist', + permission: 'SuperHashlist' }, - canActivate: [IsAuth,CheckPerm]}, + canActivate: [CheckPerm] + }, { - path: 'new-superhashlist', component: NewSuperhashlistComponent, + path: 'new-superhashlist', + component: NewSuperhashlistComponent, data: { - kind: 'new-superhashlist', - breadcrumb: 'New Super Hashlist', - permission: 'SuperHashlist' + kind: 'new-superhashlist', + breadcrumb: 'New Super Hashlist', + permission: 'SuperHashlist' }, - canActivate: [IsAuth,CheckPerm]}, + canActivate: [CheckPerm] + }, { - path: 'hashes/tasks/:id', component: HashesComponent, + path: 'hashes/tasks/:id', + component: HashesComponent, data: { - kind: 'taskhas', - breadcrumb: 'Task Hashes', - permission: 'Hash' + kind: 'taskhas', + breadcrumb: 'Task Hashes', + permission: 'Hash' }, - canActivate: [IsAuth,CheckPerm]}, + canActivate: [CheckPerm] + }, { - path: 'hashes/hashlists/:id', component: HashesComponent, + path: 'hashes/hashlists/:id', + component: HashesComponent, data: { - kind: 'hashlisthash', - breadcrumb: 'Hashlist Hashes', - permission: 'Hash' + kind: 'hashlisthash', + breadcrumb: 'Hashlist Hashes', + permission: 'Hash' }, - canActivate: [IsAuth,CheckPerm]}, + canActivate: [CheckPerm] + }, { - path: 'hashes/chunks/:id', component: HashesComponent, + path: 'hashes/chunks/:id', + component: HashesComponent, data: { - kind: 'chunkshash', - breadcrumb: 'Chunks Hashes', - permission: 'Hash' + kind: 'chunkshash', + breadcrumb: 'Chunks Hashes', + permission: 'Hash' }, - canActivate: [IsAuth,CheckPerm]}, + canActivate: [CheckPerm] + }, { - path: 'search-hash', component: SearchHashComponent, + path: 'search-hash', + component: SearchHashComponent, data: { - kind: 'search-hash', - breadcrumb: 'Search-hash', - permission: 'Hash' + kind: 'search-hash', + breadcrumb: 'Search-hash', + permission: 'Hash' }, - canActivate: [IsAuth,CheckPerm]}, + canActivate: [CheckPerm] + }, { - path: 'show-cracks', component: ShowCracksComponent, + path: 'show-cracks', + component: ShowCracksComponent, data: { - kind: 'show-cracks', - breadcrumb: 'Show Cracks', - permission: 'Hash' + kind: 'show-cracks', + breadcrumb: 'Show Cracks', + permission: 'Hash' }, - canActivate: [IsAuth,CheckPerm]}, + canActivate: [CheckPerm] + } ] } -] +]; @NgModule({ imports: [RouterModule.forChild(routes)], exports: [RouterModule] - }) export class HashlistRoutingModule {} diff --git a/src/app/home/home-routing.module.ts b/src/app/home/home-routing.module.ts index 37244e88..34a4dc15 100644 --- a/src/app/home/home-routing.module.ts +++ b/src/app/home/home-routing.module.ts @@ -1,27 +1,29 @@ -import { IsAuth } from "../core/_guards/auth.guard"; -import { Routes, RouterModule } from '@angular/router'; -import { NgModule } from "@angular/core"; +import { IsAuth } from '../core/_guards/auth.guard'; +import { RouterModule, Routes } from '@angular/router'; +import { NgModule } from '@angular/core'; -import { HomeComponent } from "./home.component"; +import { MyRoute, RouteData } from '../core/_models/routes.model'; +import { HomeComponent } from './home.component'; -const routes: Routes = [ +const routes: MyRoute[] = [ { path: '', + canActivate: [IsAuth], children: [ - { - path: '', component: HomeComponent, - data: { - kind: 'Home', - breadcrumb: 'Home' - }, - canActivate: [IsAuth]}, - ] - } - ] + { + path: '', + component: HomeComponent, + data: { + kind: 'Home', + breadcrumb: 'Home' + } + } + ] + } +]; @NgModule({ imports: [RouterModule.forChild(routes)], exports: [RouterModule] - }) export class HomeRoutingModule {} diff --git a/src/app/projects/edit-project/edit-project.component.html b/src/app/projects/edit-project/edit-project.component.html deleted file mode 100644 index 240a92b2..00000000 --- a/src/app/projects/edit-project/edit-project.component.html +++ /dev/null @@ -1 +0,0 @@ -

edit-project works!

diff --git a/src/app/projects/edit-project/edit-project.component.ts b/src/app/projects/edit-project/edit-project.component.ts deleted file mode 100644 index 43ab36e4..00000000 --- a/src/app/projects/edit-project/edit-project.component.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Component, OnInit } from '@angular/core'; - -@Component({ - selector: 'app-edit-project', - templateUrl: './edit-project.component.html' -}) -export class EditProjectComponent implements OnInit { - - constructor() { } - - ngOnInit(): void { - } - -} diff --git a/src/app/projects/projects-routing.module.ts b/src/app/projects/projects-routing.module.ts deleted file mode 100644 index 5da8a65e..00000000 --- a/src/app/projects/projects-routing.module.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { IsAuth } from "../core/_guards/auth.guard"; -import { NgModule } from "@angular/core"; -import { Routes, RouterModule } from '@angular/router'; - -import { ProjectsComponent } from "./projects.component"; -import { EditProjectComponent } from "./edit-project/edit-project.component"; - -const routes: Routes = [ - { - path: '', - children: [ - { - path: '', component: ProjectsComponent, - data: { - kind: 'projects', - breadcrumb: 'Projects' - }, - canActivate: [IsAuth]}, - { - path: ':id/edit-project', component: EditProjectComponent, - data: { - kind: 'edit-project', - breadcrumb: 'Edit Project' - }, - canActivate: [IsAuth]}, - ] - } - ] - - -@NgModule({ - imports: [RouterModule.forChild(routes)], - exports: [RouterModule] - -}) -export class ProjectsRoutingModule {} diff --git a/src/app/projects/projects.component.html b/src/app/projects/projects.component.html deleted file mode 100644 index 0abdd428..00000000 --- a/src/app/projects/projects.component.html +++ /dev/null @@ -1,104 +0,0 @@ -
-
-

Projects

-
- -
- - -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - -
IDProject TitleProject CodeRolePerformanceStart DateDeadlineStatusOptions
{{p.taskId}}{{p.taskName}}{{p.attackCmd}} - - {{p.attackCmd}} - - {{p.statusTimer}}{{p.statusTimer}} - {{p.crackerBinaryTypeId}} - - - - - - {{getStatus(p.crackerBinaryId)}} - - -
- -
- - - - - - -
-
-
-
-
-
- - - - - - - - - - diff --git a/src/app/projects/projects.component.ts b/src/app/projects/projects.component.ts deleted file mode 100644 index b25e3e44..00000000 --- a/src/app/projects/projects.component.ts +++ /dev/null @@ -1,669 +0,0 @@ -import { faHomeAlt, faPlus, faTrash, faEdit, faFilePdf} from '@fortawesome/free-solid-svg-icons'; -import { NgbModal, ModalDismissReasons } from '@ng-bootstrap/ng-bootstrap'; -import Swal from 'sweetalert2/dist/sweetalert2.js'; -import { Component, OnInit } from '@angular/core'; -import pdfFonts from 'pdfmake/build/vfs_fonts'; -import { InputFiles, Report } from './report'; -import pdfMake from 'pdfmake/build/pdfmake'; -import { Subject } from 'rxjs'; - -import { GlobalService } from 'src/app/core/_services/main.service'; -import { PageTitle } from 'src/app/core/_decorators/autotitle'; -import { SERV } from '../core/_services/main.config'; - -// import { ReportConfig } from '../shared/defines/logobase64'; -pdfMake.vfs = pdfFonts.pdfMake.vfs; - -@Component({ - selector: 'app-projects', - templateUrl: './projects.component.html' -}) -@PageTitle(['Show Projects']) -export class ProjectsComponent implements OnInit { - public isCollapsed = true; - faHome=faHomeAlt; - faPlus=faPlus; - faTrash=faTrash; - faEdit=faEdit; - faFilePdf=faFilePdf; - - dtTrigger: Subject = new Subject(); - dtOptions: any = {}; - - // public projects: {preprocessorId: number}[] = []; - public projects: any[] = []; - - public project: any[] = []; - - constructor( - private modalService: NgbModal, - private gs: GlobalService, - ) { } - - ngOnInit(): void { - this.gs.getAll(SERV.PROJECTS).subscribe((proj: any) => { - this.projects = proj.values; - this.dtTrigger.next(void 0); - }); - const self = this; - this.dtOptions = { - dom: 'Bfrtip', - scrollX: true, - pageLength: 25, - lengthMenu: [ - [10, 25, 50, 100, 250, -1], - [10, 25, 50, 100, 250, 'All'] - ], - bStateSave:true, - destroy: true, - select: { - style: 'multi' - }, - buttons: { - dom: { - button: { - className: 'dt-button buttons-collection btn btn-sm-dt btn-outline-gray-600-dt', - } - }, - buttons: [ - { - text: '↻', - autoClose: true, - action: function (e, dt, node, config) { - self.onRefresh(); - } - }, - { - extend: 'collection', - text: 'Export', - buttons: [ - { - extend: 'excelHtml5', - exportOptions: { - columns: [0, 1, 2, 3, 4, 5, 6, 7] - }, - }, - { - extend: 'print', - exportOptions: { - columns: [0, 1, 2, 3, 4, 5, 6, 7] - }, - customize: function ( win ) { - $(win.document.body) - .css( 'font-size', '10pt' ) - $(win.document.body).find( 'table' ) - .addClass( 'compact' ) - .css( 'font-size', 'inherit' ); - } - }, - { - extend: 'csvHtml5', - exportOptions: {modifier: {selected: true}}, - select: true, - customize: function (dt, csv) { - let data = ""; - for (let i = 0; i < dt.length; i++) { - data = "Show Projects\n\n"+ dt; - } - return data; - } - }, - { - extend: 'copy', - } - ] - }, - { - extend: 'collection', - text: 'Bulk Actions', - drawCallback: function() { - const hasRows = this.api().rows({ filter: 'applied' }).data().length > 0; - $('.buttons-excel')[0].style.visibility = hasRows ? 'visible' : 'hidden' - }, - buttons: [ - { - text: 'Delete Projects(s)', - autoClose: true, - action: function (e, dt, node, config) { - } - } - ] - }, - { - extend: 'colvis', - text: 'Column View', - columns: [ 1, 2, 3, 4, 5, 6, 7 ], - }, - { - extend: "pageLength", - className: "btn-sm" - } - ], - } - }; - } - - onRefresh(){ - // this.rerender(); - this.ngOnInit(); - } - - getStatus(status: string): string{ - if(status == '0') - return 'Live'; - else if (status == '1') - return 'Completed'; - else if (status == '2') - return 'Archived'; - else - return 'Cancelled'; - } - - // Start Render PDF - - report = new Report(); - - public confreport: any[] = []; - - async renderPDF(id: number){ - this.gs.get(SERV.PROJECTS,id).subscribe((proj: any) => { - this.project = proj.values; - }); - - const isHeaderAlt = 0; // Vairaible for log; O use alternative logo, 1 use hashtopolis logo - - const project = { - info: { - title: 'Hashtopolis Report', - author: 'xbenyx', - subject: 'Password Recovery Processes', - }, - pageSize: 'A4', - pageMargins: [40, 80, 40, 60], - userPassword: 555, - ownerPassword: 'hashtoadmin', - permissions: { - printing: 'highResolution', //'lowResolution' - modifying: false, - copying: false, - annotating: true, - fillingForms: true, - contentAccessibility: true, - documentAssembly: true - }, - // header: function(page) { - // if (page != 1 && isHeaderAlt == 1){ - // return { columns: [{ - // image: ReportConfig.LOGORED - // ,width: 130 - // ,margin: [25, 15 , 0, 0] - // }]} - // } - // else if (page != 1 && isHeaderAlt == 0){ - // return { columns: [{ - // image: ReportConfig.LOGOALT - // ,width: 180 - // ,margin: [25, 15 , 0, 0] - // }]} - // } - // else return false; - // }, - footer: function(currentPage, pageCount) { - return { - margin:10, - columns: [ - { - fontSize: 9, - italic: true, - text:[ - { - text: 'Page ' + currentPage.toString() + ' of ' + pageCount, - } - ], - alignment: 'center' - } - ] - }; - }, - // background: { - // image: await this.getBase64ImageFromURL("../../assets/img/backgroung.png"), width: 600, margin: [0, 520 , 0, 0] - // }, - content: [ - { - image: await this.getBase64ImageFromURL("../../assets/img/letterhead.png"), - // image: await this.getBase64ImageFromURL("../../assets/img/header_2.png"), - width: 600, - alignment: 'center', - margin: [0, -100 , 0, 0], - }, - '\n\n\n', - { - columns: [ - { - text: this.confreport[0].title_report, - color: '#00275b', - bold: true, - fontSize: 26, - alignment: 'left', - margin: [0, 0, 0, 10], - } - ], - }, - { - columns: [ - { - text: 'this.confreport[0].info_cover_body_1', - color: '#00275b', - bold: true, - fontSize: 14, - alignment: 'left', - margin: [0, 0, 0, 3], - } - ], - }, - { - columns: [ - { - text: 'this.confreport[0].info_cover_body_2', - color: '#00275b', - bold: true, - fontSize: 14, - alignment: 'left', - margin: [0, 0, 0, 3], - } - ], - }, - { - columns: [ - { - text: 'this.project[0].reference', - color: '#00275b', - bold: true, - fontSize: 14, - alignment: 'left', - margin: [0, 0, 0, 3], - } - ], - }, - { - columns: [ - { - text: 'this.confreport[0].info_cover_body_3', - color: '#00275b', - bold: true, - fontSize: 14, - alignment: 'left', - margin: [0, 0, 0, 3], - } - ], - }, - '\n\n\n\n\n\n', - { - columns: [ - { - text: 'this.confreport[0].info_cover_body_4', - color: '#00275b', - bold: true, - fontSize: 14, - alignment: 'left', - margin: [0, 0, 0, 3], - } - ], - }, - { - columns: [ - { - text: 'this.confreport[0].info_cover_body_5', - color: '#00275b', - bold: true, - fontSize: 14, - alignment: 'left', - margin: [0, 0, 0, 3], - } - ], - }, - { - columns: [ - { - text: 'The Hague, '+new Date().toDateString(), - color: '#00275b', - bold: true, - fontSize: 14, - alignment: 'left', - margin: [0, 0, 0, 3], - } - ], - }, - '\n\n\n\n\n\n\n\n\n\n\n\n\n\n', - {table: { - // widths: ['*'], - body: [ - [ - { - text: 'this.confreport[0].info_cover_footer_1', - color: '#aaaaab', - border: [true, true, false, true], - margin: [30, 10, 10, 5], - fontSize: 9, - alignment: 'center', - }, - { - text: 'this.confreport[0].info_cover_footer_2', - border: [false, true, false, true], - color: '#aaaaab', - margin: [70, 10, 10, 5], - fontSize: 9, - alignment: 'center', - }, - { - text: 'this.confreport[0].info_cover_footer_3', - border: [false, true, true, true], - color: '#aaaaab', - margin: [70, 10, 10, 5], - fontSize: 9, - alignment: 'center', - }, - ], - ] - } - }, - // '\n\n', - {columns: [ - { - text: 'Project Description', - color: '#00275b', - bold: true, - fontSize: 14, - alignment: 'left', - margin: [0, 0, 0, 3], - pageBreak: 'before' - } - ], - }, - '\n', - // this.getInputFilesObject(this.resume.educations), - {columns: [ - { - text: this.project[0].project_description, - color: '#000000', - bold: true, - fontSize: 12, - alignment: 'left', - margin: [0, 0, 0, 3], - } - ], - }, - '\n\n', - {columns: [ - { - text: 'Input Files', - color: '#00275b', - bold: true, - fontSize: 14, - alignment: 'left', - margin: [0, 0, 0, 3], - } - ], - }, - '\n', - { - layout: { - defaultBorder: false, - hLineWidth: function(i, node) { - return 1; - }, - vLineWidth: function(i, node) { - return 1; - }, - hLineColor: function(i, node) { - if (i === 1 || i === 0) { - return '#bfdde8'; - } - return '#eaeaea'; - }, - vLineColor: function(i, node) { - return '#eaeaea'; - }, - hLineStyle: function(i, node) { - // if (i === 0 || i === node.table.body.length) { - return null; - //} - }, - // vLineStyle: function (i, node) { return {dash: { length: 10, space: 4 }}; }, - paddingLeft: function(i, node) { - return 10; - }, - paddingRight: function(i, node) { - return 10; - }, - paddingTop: function(i, node) { - return 2; - }, - paddingBottom: function(i, node) { - return 2; - }, - fillColor: function(rowIndex, node, columnIndex) { - return '#fff'; - }, - }, - table: { - headerRows: 1, - body: [ - [ - { - text: 'Reference Name', - fillColor: '#eaf2f5', - border: [false, true, false, true], - margin: [0, 5, 0, 5], - textTransform: 'uppercase', - }, - { - text: 'Hash Mode', - border: [false, true, false, true], - alignment: 'right', - fillColor: '#eaf2f5', - margin: [0, 5, 0, 5], - textTransform: 'uppercase', - }, - { - text: 'Hash Count', - border: [false, true, false, true], - alignment: 'right', - fillColor: '#eaf2f5', - margin: [0, 5, 0, 5], - textTransform: 'uppercase', - }, - { - text: 'Retrieved', - border: [false, true, false, true], - alignment: 'right', - fillColor: '#eaf2f5', - margin: [0, 5, 0, 5], - textTransform: 'uppercase', - }, - { - text: 'Keyspace explored', - border: [false, true, false, true], - alignment: 'right', - fillColor: '#eaf2f5', - margin: [0, 5, 0, 5], - textTransform: 'uppercase', - }, - ], - [ - { - text: 'cyrborg_robocot', - border: [false, false, false, true], - margin: [0, 5, 0, 5], - alignment: 'center', - }, - { - border: [false, false, false, true], - text: '3200', - fillColor: '#f5f5f5', - alignment: 'right', - margin: [0, 5, 0, 5], - }, - { - border: [false, false, false, true], - text: '1', - fillColor: '#f5f5f5', - alignment: 'right', - margin: [0, 5, 0, 5], - }, - { - border: [false, false, false, true], - text: '0', - fillColor: '#f5f5f5', - alignment: 'right', - margin: [0, 5, 0, 5], - }, - { - border: [false, false, false, true], - text: '17,071,868,064', - fillColor: '#f5f5f5', - alignment: 'right', - margin: [0, 5, 0, 5], - }, - ], - ], - }, - }, - '\n', - {columns: [ - { - text: 'Process Performed', - color: '#00275b', - bold: true, - fontSize: 14, - alignment: 'left', - margin: [0, 0, 0, 3], - } - ], - }, - '\n', - ], - styles: { - notesTitle: { - fontSize: 10, - bold: true, - margin: [0, 50, 0, 3], - }, - notesText: { - fontSize: 10, - }, - }, - defaultStyle: { - columnGap: 20, - }, - }; - - pdfMake.createPdf(project).open(); - } - - getInputFilesObject(inpfiles: InputFiles[]) { - return { - table: { - headerRows: 1, - body: [ - [ - { - text: 'Reference Name', - fillColor: '#eaf2f5', - border: [false, true, false, true], - margin: [0, 5, 0, 5], - textTransform: 'uppercase', - }, - { - text: 'Hash Mode', - border: [false, true, false, true], - alignment: 'right', - fillColor: '#eaf2f5', - margin: [0, 5, 0, 5], - textTransform: 'uppercase', - }, - { - text: 'Hash Count', - border: [false, true, false, true], - alignment: 'right', - fillColor: '#eaf2f5', - margin: [0, 5, 0, 5], - textTransform: 'uppercase', - }, - { - text: 'Retrieved', - border: [false, true, false, true], - alignment: 'right', - fillColor: '#eaf2f5', - margin: [0, 5, 0, 5], - textTransform: 'uppercase', - }, - { - text: 'Keyspace explored', - border: [false, true, false, true], - alignment: 'right', - fillColor: '#eaf2f5', - margin: [0, 5, 0, 5], - textTransform: 'uppercase', - } - ], - ...inpfiles.map(ed => { - return [ed.name, ed.hashtypeId, ed.hashCount, ed.cracked, ed.dispatched_keyspace]; - }) - ] - } - }; - } - - // Function creates converts the image in base64, so can be used in the report - getBase64ImageFromURL(url: string) { - return new Promise((resolve, reject) => { - const img = new Image(); - img.setAttribute("crossOrigin", "anonymous"); - - img.onload = () => { - const canvas = document.createElement("canvas"); - canvas.width = img.width; - canvas.height = img.height; - - const ctx = canvas.getContext("2d"); - ctx.drawImage(img, 0, 0); - - const dataURL = canvas.toDataURL("image/png"); - - resolve(dataURL); - }; - - img.onerror = error => { - reject(error); - }; - - img.src = url; - - });} - // End Render PDF - - // Modal Information - closeResult = ''; - open(content) { - this.modalService.open(content, { size: 'xl' }).result.then( - (result) => { - this.closeResult = `Closed with: ${result}`; - }, - (reason) => { - this.closeResult = `Dismissed ${this.getDismissReason(reason)}`; - }, - ); - } - - private getDismissReason(reason: any): string { - if (reason === ModalDismissReasons.ESC) { - return 'by pressing ESC'; - } else if (reason === ModalDismissReasons.BACKDROP_CLICK) { - return 'by clicking on a backdrop'; - } else { - return `with: ${reason}`; - } - } - -} diff --git a/src/app/projects/projects.module.ts b/src/app/projects/projects.module.ts deleted file mode 100644 index 321da0b5..00000000 --- a/src/app/projects/projects.module.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { CommonModule } from "@angular/common"; -import { NgModule } from "@angular/core"; -import { FormsModule } from "@angular/forms"; -import { RouterModule } from "@angular/router"; -import { FontAwesomeModule } from "@fortawesome/angular-fontawesome"; -import { NgbModule } from "@ng-bootstrap/ng-bootstrap"; -import { DataTablesModule } from "angular-datatables"; - -import { EditProjectComponent } from "./edit-project/edit-project.component"; -import { ProjectsRoutingModule } from "./projects-routing.module"; -import { ProjectsComponent } from "./projects.component"; - - -@NgModule({ - declarations:[ - ProjectsComponent, - EditProjectComponent - ], - imports:[ - CommonModule, - RouterModule, - FormsModule, - DataTablesModule, - FontAwesomeModule, - NgbModule, - ProjectsRoutingModule - ] -}) -export class ProjectsModule {} diff --git a/src/app/projects/report.ts b/src/app/projects/report.ts deleted file mode 100644 index 9d0b2c8c..00000000 --- a/src/app/projects/report.ts +++ /dev/null @@ -1,19 +0,0 @@ -export class Report { - project_id: number; - project_reference: string; - project_description: string; - project_created_by: string; - inputfiles: InputFiles[] = []; - - constructor() { - this.inputfiles.push(new InputFiles()); - } -} - -export class InputFiles { - name: string; - hashtypeId: number; - hashCount: number; - cracked: string; - dispatched_keyspace: number; -} diff --git a/src/app/shared/form/dynamicform.component.html b/src/app/shared/form/dynamicform.component.html new file mode 100644 index 00000000..bc221bbb --- /dev/null +++ b/src/app/shared/form/dynamicform.component.html @@ -0,0 +1,113 @@ + + +
+ +
+
+ +
{{ field.label }}
+
+ +
+ + + {{ field.label }} + + + + + + + + + + + + + + + + + + + + + {{ option.label }} + + + + + Please Select an Option + {{ option.name }} + + + + +
+
+ {{ + field.label + }} +
+
+
+
+ + Delete + +
+
+
diff --git a/src/app/shared/form/dynamicform.component.ts b/src/app/shared/form/dynamicform.component.ts index feab2eac..c5a84d3d 100644 --- a/src/app/shared/form/dynamicform.component.ts +++ b/src/app/shared/form/dynamicform.component.ts @@ -14,7 +14,6 @@ import { OnInit, Output } from '@angular/core'; -import { faInfoCircle } from '@fortawesome/free-solid-svg-icons'; import { Router } from '@angular/router'; import { Observable, @@ -35,128 +34,9 @@ import { ChangeDetectorRef } from '@angular/core'; */ @Component({ selector: 'app-dynamic-form', - template: ` - - -
- -
-
- -
{{ field.label }}
-
- -
- - - {{ field.label }} - - - - - - - - - - - - - - - - - - - - - {{ option.label }} - - - - - Please Select an Option - {{ option.name }} - - - - -
-
- {{ - field.label - }} -
-
-
-
- - Delete - -
-
-
- ` + templateUrl: 'dynamicform.component.html' }) export class DynamicFormComponent implements OnInit, AfterViewInit, OnDestroy { - /** - * FontAwesome icon for providing additional information in form fields. - */ - faInfoCircle = faInfoCircle; - /** * The subtitle to display. * @type {string} @@ -168,11 +48,6 @@ export class DynamicFormComponent implements OnInit, AfterViewInit, OnDestroy { */ @Input() formMetadata: any[] = []; - /** - * Additional CSS class for labels. - */ - @Input() labelclass?: any; - /** * Initial values for form fields (optional). If not provided, an empty object is used as the default. */ @@ -226,6 +101,18 @@ export class DynamicFormComponent implements OnInit, AfterViewInit, OnDestroy { */ private destroy$: Subject = new Subject(); + /** + * A subscription to handle dynamic select options data retrieval. + * This subscription is used to fetch and update select field options with dynamic data. + */ + private selectOptionsSubscription: Subscription; + + /** + * Indicates whether the dynamic select options are currently being loaded. + * When true, it represents that options are being fetched; when false, loading is complete. + */ + isLoadingSelect = true; + /** * Constructor for the DynamicFormComponent. * @param fb - The Angular FormBuilder for creating form controls and groups. @@ -296,18 +183,6 @@ export class DynamicFormComponent implements OnInit, AfterViewInit, OnDestroy { this.form = this.fb.group(controlsConfig); } - /** - * A subscription to handle dynamic select options data retrieval. - * This subscription is used to fetch and update select field options with dynamic data. - */ - private selectOptionsSubscription: Subscription; - - /** - * Indicates whether the dynamic select options are currently being loaded. - * When true, it represents that options are being fetched; when false, loading is complete. - */ - isLoadingSelect = true; - /** * Angular lifecycle hook: ngAfterViewInit * Performs initialization and logic for select fields with dynamic options. @@ -404,8 +279,9 @@ export class DynamicFormComponent implements OnInit, AfterViewInit, OnDestroy { */ ngOnDestroy() { // Unsubscribe from the selectOptionsSubscription - // this.selectOptionsSubscription.unsubscribe(); - + if (this.selectOptionsSubscription) { + this.selectOptionsSubscription.unsubscribe(); + } // Complete and close the destroy$ subject to prevent memory leaks this.destroy$.next(); this.destroy$.complete(); diff --git a/src/app/shared/form/dynamicform.module.ts b/src/app/shared/form/dynamicform.module.ts index 2af33ffa..a59df6c7 100644 --- a/src/app/shared/form/dynamicform.module.ts +++ b/src/app/shared/form/dynamicform.module.ts @@ -6,7 +6,6 @@ import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; import { FormComponent } from './form.component'; import { FormConfigComponent } from './formconfig.component'; -import { FormUIsettingsComponent } from './formuisettings.component'; import { GridModule } from '../grid-containers/grid.module'; import { HorizontalNavModule } from '../navigation/navigation.module'; import { MatDividerModule } from '@angular/material/divider'; @@ -24,12 +23,7 @@ import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { PageTitleModule } from '../page-headers/page-title.module'; @NgModule({ - declarations: [ - FormUIsettingsComponent, - DynamicFormComponent, - FormConfigComponent, - FormComponent - ], + declarations: [DynamicFormComponent, FormConfigComponent, FormComponent], imports: [ ReactiveFormsModule, HorizontalNavModule, @@ -49,12 +43,7 @@ import { PageTitleModule } from '../page-headers/page-title.module'; MatIconModule, MatDividerModule ], - exports: [ - FormUIsettingsComponent, - DynamicFormComponent, - FormConfigComponent, - FormComponent - ], + exports: [DynamicFormComponent, FormConfigComponent, FormComponent], providers: [ { provide: MAT_FORM_FIELD_DEFAULT_OPTIONS, diff --git a/src/app/shared/form/dynamicformlayout.component.ts b/src/app/shared/form/dynamicformlayout.component.ts deleted file mode 100644 index f7c89793..00000000 --- a/src/app/shared/form/dynamicformlayout.component.ts +++ /dev/null @@ -1,369 +0,0 @@ -import { FormBuilder, FormGroup, FormControl, ValidatorFn, Validators } from '@angular/forms'; -import { Component, Input, OnInit, Output, EventEmitter, AfterViewInit, OnDestroy } from '@angular/core'; -import { faInfoCircle } from '@fortawesome/free-solid-svg-icons'; -import { Router } from '@angular/router'; -import { Observable, Subject, Subscription, combineLatest, forkJoin, map, switchMap, takeUntil } from 'rxjs'; -import { MetadataService } from 'src/app/core/_services/metadata.service'; -import { GlobalService } from 'src/app/core/_services/main.service'; -import { ChangeDetectorRef } from '@angular/core'; - -/** - * This component renders a dynamic form based on the provided form metadata. - */ -@Component({ - selector: 'app-dynamic-form', - template: ` - - -
- -
-
- -
-
-
- -
{{ field.label }}
-
- -
-
- - - -
- - - - -
- -
- - - - - - - - diff --git a/src/app/files/files-edit/files-edit.component.ts b/src/app/files/files-edit/files-edit.component.ts deleted file mode 100644 index 0c32946d..00000000 --- a/src/app/files/files-edit/files-edit.component.ts +++ /dev/null @@ -1,137 +0,0 @@ -import { FormControl, FormGroup, Validators } from '@angular/forms'; -import { ActivatedRoute, Params } from '@angular/router'; -import { Component, OnInit } from '@angular/core'; -import { Router } from '@angular/router'; - -import { AlertService } from 'src/app/core/_services/shared/alert.service'; -import { GlobalService } from 'src/app/core/_services/main.service'; -import { PageTitle } from 'src/app/core/_decorators/autotitle'; -import { SERV } from '../../core/_services/main.config'; -import { Filetype } from '../../core/_models/file.model'; - -@Component({ - selector: 'app-files-edit', - templateUrl: './files-edit.component.html' -}) -@PageTitle(['Edit File']) -export class FilesEditComponent implements OnInit { - - editMode = false; - editedFileIndex: number; - editedFile: any // Change to Model - - filterType: number - whichView: string; - - // accessgroup: AccessGroup; //Use models when data structure is reliable - updateForm: FormGroup; - accessgroup: any[] - allfiles: any[] - filetype: any[] - - constructor( - private route: ActivatedRoute, - private alert: AlertService, - private gs: GlobalService, - private router: Router - ) { } - - ngOnInit(): void { - - this.route.params - .subscribe( - (params: Params) => { - this.editedFileIndex = +params['id']; - this.editMode = params['id'] != null; - this.initForm(); - } - ); - - this.updateForm = new FormGroup({ - 'fileId': new FormControl({ value: '', disabled: true }), - 'updateData': new FormGroup({ - 'filename': new FormControl('', [Validators.required, Validators.minLength(1)]), - 'fileType': new FormControl(null), - 'accessGroupId': new FormControl(null), - 'isSecret': new FormControl(null), - }) - }); - - this.route.data.subscribe(data => { - switch (data['kind']) { - - case 'wordlist': - this.whichView = 'wordlist-edit'; - break; - - case 'rules': - this.whichView = 'rules-edit'; - break; - - case 'other': - this.whichView = 'other-edit'; - break; - - } - - this.filetype = [{ fileType: 0, fileName: 'Wordlist' }, { fileType: 1, fileName: 'Rules' }, { fileType: 2, fileName: 'Other' }]; - - this.gs.getAll(SERV.ACCESS_GROUPS).subscribe((agroups: any) => { - this.accessgroup = agroups.values; - }); - - this.gs.get(SERV.FILES, this.editedFileIndex).subscribe((files: any) => { - this.allfiles = files; - }); - - }); - } - - onSubmit(): void { - this.gs.update(SERV.FILES, this.editedFileIndex, this.updateForm.value['updateData']).subscribe(() => { - this.alert.okAlert('File saved!', ''); - this.route.data.subscribe(data => { - switch (data['kind']) { - - case 'wordlist-edit': - this.whichView = 'wordlist'; - break; - - case 'rules-edit': - this.whichView = 'rules'; - break; - - case 'other-edit': - this.whichView = 'other'; - break; - - } - this.router.navigate(['../files/' + this.whichView + '']); - }) - }, - errorMessage => { - // check error status code is 500, if so, do some action - this.alert.okAlert('File was not updated, please try again!', '', 'warning'); - this.ngOnInit(); - } - ); - this.updateForm.reset(); // success, we reset form - } - - private initForm() { - if (this.editMode) { - this.gs.get(SERV.FILES, this.editedFileIndex).subscribe((result) => { - this.updateForm = new FormGroup({ - 'fileId': new FormControl({ value: result['fileId'], disabled: true }), - 'updateData': new FormGroup({ - 'filename': new FormControl(result['filename'], Validators.required), - 'fileType': new FormControl(result['fileType'], Validators.required), - 'accessGroupId': new FormControl(result['accessGroupId'], Validators.required), - 'isSecret': new FormControl(result['isSecret']), - }) - }); - }); - } - } - -} diff --git a/src/app/files/files-routing.module.ts b/src/app/files/files-routing.module.ts index 42a54338..5cc6ffe8 100644 --- a/src/app/files/files-routing.module.ts +++ b/src/app/files/files-routing.module.ts @@ -3,7 +3,6 @@ import { RouterModule, Routes } from '@angular/router'; import { IsAuth } from '../core/_guards/auth.guard'; import { NgModule } from '@angular/core'; -import { FilesEditComponent } from './files-edit/files-edit.component'; import { NewFilesComponent } from './new-files/new-files.component'; import { MyRoute, RouteData } from '../core/_models/routes.model'; import { FormComponent } from '../shared/form/form.component'; diff --git a/src/app/files/files.module.ts b/src/app/files/files.module.ts index eb314a8e..145d7113 100644 --- a/src/app/files/files.module.ts +++ b/src/app/files/files.module.ts @@ -1,25 +1,20 @@ -import { FontAwesomeModule } from "@fortawesome/angular-fontawesome"; -import { FormsModule, ReactiveFormsModule } from "@angular/forms"; -import { NgbModule } from "@ng-bootstrap/ng-bootstrap"; -import { DataTablesModule } from "angular-datatables"; -import { RouterModule } from "@angular/router"; -import { CommonModule } from "@angular/common"; -import { NgModule } from "@angular/core"; +import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { DataTablesModule } from 'angular-datatables'; +import { RouterModule } from '@angular/router'; +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; -import { FilesEditComponent } from "./files-edit/files-edit.component"; import { NewFilesComponent } from './new-files/new-files.component'; -import { ComponentsModule } from "../shared/components.module"; -import { FilesRoutingModule } from "./files-routing.module"; -import { PipesModule } from "../shared/pipes.module"; -import { FilesComponent } from "./files.component"; +import { ComponentsModule } from '../shared/components.module'; +import { FilesRoutingModule } from './files-routing.module'; +import { PipesModule } from '../shared/pipes.module'; +import { FilesComponent } from './files.component'; @NgModule({ - declarations:[ - FilesEditComponent, - FilesComponent, - NewFilesComponent - ], - imports:[ + declarations: [FilesComponent, NewFilesComponent], + imports: [ ReactiveFormsModule, FilesRoutingModule, FontAwesomeModule, From 555a35d97d06fe5720736a989fccdb1598901e85 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Tue, 14 Nov 2023 09:43:19 +0100 Subject: [PATCH 259/419] Change logo in dark mode --- src/app/layout/header/header.component.html | 26 +++++++++++++++++---- src/app/layout/header/header.component.ts | 12 +++++++++- src/app/layout/header/header.model.ts | 12 +++++----- src/styles/layout/_header.scss | 20 ++++++++++++---- 4 files changed, 53 insertions(+), 17 deletions(-) diff --git a/src/app/layout/header/header.component.html b/src/app/layout/header/header.component.html index b866db68..e08adc55 100644 --- a/src/app/layout/header/header.component.html +++ b/src/app/layout/header/header.component.html @@ -1,10 +1,26 @@ - Hashtopolis +
+ Hashtopolis +
+ {{ this.headerConfig.brand.name }} - + -
\ No newline at end of file + diff --git a/src/app/layout/header/header.component.ts b/src/app/layout/header/header.component.ts index 8ce40be9..163195e3 100644 --- a/src/app/layout/header/header.component.ts +++ b/src/app/layout/header/header.component.ts @@ -3,8 +3,11 @@ import { HeaderMenuAction, HeaderMenuLabel } from './header.constants'; import { ActionMenuEvent } from 'src/app/core/_components/menus/action-menu/action-menu.model'; import { AuthService } from '../../core/_services/access/auth.service'; +import { LocalStorageService } from 'src/app/core/_services/storage/local-storage.service'; import { MainMenuItem } from './header.model'; import { Subscription } from 'rxjs'; +import { UIConfig } from 'src/app/core/_models/config-ui.model'; +import { UISettingsUtilityClass } from 'src/app/shared/utils/config'; import { User } from 'src/app/core/_models/auth-user.model'; import { environment } from './../../../environments/environment'; @@ -14,13 +17,20 @@ import { environment } from './../../../environments/environment'; }) export class HeaderComponent implements OnInit, OnDestroy { private subscriptions: Subscription[] = []; + protected uiSettings: UISettingsUtilityClass; private username = ''; headerConfig = environment.config.header; mainMenu: MainMenuItem[] = []; + isDarkMode = false; - constructor(private authService: AuthService) { + constructor( + private authService: AuthService, + private storage: LocalStorageService + ) { this.rebuildMenu(); + this.uiSettings = new UISettingsUtilityClass(this.storage); + this.isDarkMode = this.uiSettings.getSetting('theme') === 'dark'; } ngOnInit(): void { diff --git a/src/app/layout/header/header.model.ts b/src/app/layout/header/header.model.ts index 7c474a38..19a64201 100644 --- a/src/app/layout/header/header.model.ts +++ b/src/app/layout/header/header.model.ts @@ -1,8 +1,8 @@ -import { ActionMenuItem } from "src/app/core/_components/menus/action-menu/action-menu.model" +import { ActionMenuItem } from 'src/app/core/_components/menus/action-menu/action-menu.model'; export interface MainMenuItem { - label?: string - icon?: string - actions: ActionMenuItem[][] - display: boolean -} \ No newline at end of file + label?: string; + icon?: string; + actions: ActionMenuItem[][]; + display: boolean; +} diff --git a/src/styles/layout/_header.scss b/src/styles/layout/_header.scss index 9fd9e61b..cc94dcac 100644 --- a/src/styles/layout/_header.scss +++ b/src/styles/layout/_header.scss @@ -19,10 +19,23 @@ .hashtopolis-toolbar { border-bottom: 5px solid $primary-900; + .logo-container { + position: relative; + display: inline-block; + + a { + position: relative; + display: inline-block; + width: 100%; + height: auto; + z-index: 2 !important; + } + } + .mdc-button { margin-left: 8px; - &>.mat-icon { + & > .mat-icon { margin-left: 0; margin-right: 4px; } @@ -42,13 +55,10 @@ .mat-icon { color: #ffffff !important; } - } } - - } .light-theme { --mat-toolbar-container-background-color: #ffffff; -} \ No newline at end of file +} From d7eb2cee7dbc9df40f9319ca470ecec02851d6f0 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Tue, 14 Nov 2023 09:44:34 +0100 Subject: [PATCH 260/419] Add files datasource --- src/app/core/_datasources/files.datasource.ts | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 src/app/core/_datasources/files.datasource.ts diff --git a/src/app/core/_datasources/files.datasource.ts b/src/app/core/_datasources/files.datasource.ts new file mode 100644 index 00000000..755575f5 --- /dev/null +++ b/src/app/core/_datasources/files.datasource.ts @@ -0,0 +1,60 @@ +import { File, FileType } from '../_models/file.model'; +import { catchError, finalize, of } from 'rxjs'; + +import { BaseDataSource } from './base.datasource'; +import { ListResponseWrapper } from '../_models/response.model'; +import { SERV } from '../_services/main.config'; + +export class FilesDataSource extends BaseDataSource { + private fileType: FileType = 0; + + setFileType(fileType: FileType): void { + this.fileType = fileType; + } + + getFileType(): FileType { + return this.fileType; + } + + loadAll(): void { + this.loading = true; + + const params = { + maxResults: this.pageSize, + expand: 'accessGroup', + filter: `fileType=${this.fileType}` + }; + + const files$ = this.service.getAll(SERV.FILES, params); + + this.subscriptions.push( + files$ + .pipe( + catchError(() => of([])), + finalize(() => (this.loading = false)) + ) + .subscribe((response: ListResponseWrapper) => { + const files: File[] = response.values; + + files.map((file: File) => { + if (file.accessGroup) { + file.accessGroupId = file.accessGroup.accessGroupId; + file.accessGroupName = file.accessGroup.groupName; + } + }); + + this.setPaginationConfig( + this.pageSize, + this.currentPage, + response.total + ); + this.setData(response.values); + }) + ); + } + + reload(): void { + this.reset(); + this.loadAll(); + } +} From d30f86a84a51126148b68ac33640e5dcfbab6e41 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Tue, 14 Nov 2023 09:45:05 +0100 Subject: [PATCH 261/419] Add files menu items --- .../menus/base-menu/base-menu.component.ts | 18 ++++++++++--- .../bulk-action-menu.component.ts | 13 ++++++++++ .../bulk-action-menu.constants.ts | 9 ++++--- .../row-action-menu.component.ts | 23 ++++++++++++++++ .../row-action-menu.constants.ts | 26 ++++++++++--------- 5 files changed, 70 insertions(+), 19 deletions(-) diff --git a/src/app/core/_components/menus/base-menu/base-menu.component.ts b/src/app/core/_components/menus/base-menu/base-menu.component.ts index 94d6282e..d436882a 100644 --- a/src/app/core/_components/menus/base-menu/base-menu.component.ts +++ b/src/app/core/_components/menus/base-menu/base-menu.component.ts @@ -21,7 +21,7 @@ export class BaseMenuComponent { actionMenuItems: ActionMenuItem[][] = []; /** - * Check if the data row is of type "Agent." + * Check if the data row is of type "Agent". * @returns `true` if the data row is an agent; otherwise, `false`. */ protected isAgent(): boolean { @@ -33,7 +33,7 @@ export class BaseMenuComponent { } /** - * Check if the data row is of type "Task." + * Check if the data row is of type "Task". * @returns `true` if the data row is a task; otherwise, `false`. */ protected isTask(): boolean { @@ -45,7 +45,19 @@ export class BaseMenuComponent { } /** - * Check if the data row is of type "Hashlist." + * Check if the data row is of type "File". + * @returns `true` if the data row is a task; otherwise, `false`. + */ + protected isFile(): boolean { + try { + return this.data['_id'] === this.data['fileId']; + } catch (error) { + return false; + } + } + + /** + * Check if the data row is of type "Hashlist". * @returns `true` if the data row is a hashlist; otherwise, `false`. */ protected isHashlist(): boolean { diff --git a/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.component.ts b/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.component.ts index b83f1c56..8153c74d 100644 --- a/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.component.ts +++ b/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.component.ts @@ -31,6 +31,8 @@ export class BulkActionMenuComponent this.getHashlistMenu(); } else if (this.dataType === 'hashtypes') { this.getHashtypesMenu(); + } else if (this.dataType === 'files') { + this.getFilesMenu(); } } @@ -67,6 +69,17 @@ export class BulkActionMenuComponent this.actionMenuItems[0] = [deleteMenuAction]; } + private getFilesMenu(): void { + const deleteMenuAction: ActionMenuItem = { + label: BulkActionMenuLabel.DELETE_FILES, + action: BulkActionMenuAction.DELETE, + icon: 'delete', + red: true + }; + + this.actionMenuItems[0] = [deleteMenuAction]; + } + private getTaskMenu(): void { this.actionMenuItems[0] = [ { diff --git a/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.constants.ts b/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.constants.ts index e6e27144..376d1855 100644 --- a/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.constants.ts +++ b/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.constants.ts @@ -1,12 +1,13 @@ export const BulkActionMenuLabel = { DELETE_AGENTS: 'Delete Agents', + DELETE_TASKS: 'Delete Tasks', + DELETE_HASHLISTS: 'Delete Hashlists', + DELETE_HASHTYPES: 'Delete Hashtypes', + DELETE_FILES: 'Delete Files', ACTIVATE_AGENTS: 'Activate Agents', DEACTIVATE_AGENTS: 'Deactivate Agents', ARCHIVE_TASKS: 'Archive Tasks', - DELETE_TASKS: 'Delete Tasks', - DELETE_HASHLISTS: 'Delete Hashlists', - ARCHIVE_HASHLISTS: 'Archive Hashlists', - DELETE_HASHTYPES: 'Delete Hashtypes' + ARCHIVE_HASHLISTS: 'Archive Hashlists' }; export const BulkActionMenuAction = { diff --git a/src/app/core/_components/menus/row-action-menu/row-action-menu.component.ts b/src/app/core/_components/menus/row-action-menu/row-action-menu.component.ts index 63934bf5..73356426 100644 --- a/src/app/core/_components/menus/row-action-menu/row-action-menu.component.ts +++ b/src/app/core/_components/menus/row-action-menu/row-action-menu.component.ts @@ -27,6 +27,8 @@ export class RowActionMenuComponent this.getSuperHashlistMenu(); } else if (this.isHashtype()) { this.getHashtypeMenu(); + } else if (this.isFile()) { + this.getFileMenu(); } } @@ -51,6 +53,27 @@ export class RowActionMenuComponent ]; } + /** + * Get the context menu items for a file data row. + */ + private getFileMenu(): void { + this.actionMenuItems[0] = [ + { + label: RowActionMenuLabel.EDIT_FILE, + action: RowActionMenuAction.EDIT, + icon: 'edit' + } + ]; + this.actionMenuItems[1] = [ + { + label: RowActionMenuLabel.DELETE_FILE, + action: RowActionMenuAction.DELETE, + icon: 'delete', + red: true + } + ]; + } + /** * Get the context menu items for an agent data row. */ 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 c7fbad0d..7ad503da 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 @@ -1,19 +1,21 @@ export const RowActionMenuLabel = { - EDIT_AGENT: 'Edit', - DELETE_AGENT: 'Delete', - EDIT_TASK: 'Edit', + EDIT_AGENT: 'Edit Agent', + EDIT_TASK: 'Edit Task', + EDIT_SUBTASKS: 'Edit Subtasks', + EDIT_HASHLIST: 'Edit Hashlist', + EDIT_SUPERHASHLIST: 'Edit Superhashlist', + EDIT_FILE: 'Edit File', + DELETE_AGENT: 'Delete Agent', + DELETE_TASK: 'Delete Task', + DELETE_HASHLIST: 'Delete Hashlist', + DELETE_HASHTYPE: 'Delete Hashtype', + DELETE_SUPERHASHLIST: 'Delete Superhashlist', + DELETE_FILE: 'Delete File', COPY_TO_TASK: 'Copy to Task', COPY_TO_PRETASK: 'Copy to Pretask', - EDIT_SUBTASKS: 'Edit Subtasks', - ARCHIVE_TASK: 'Archive', - DELETE_TASK: 'Delete', - EDIT_HASHLIST: 'Edit', - DELETE_HASHLIST: 'Delete', + ARCHIVE_TASK: 'Archive Task', IMPORT_HASHLIST: 'Import Hashlist', - EXPORT_HASHLIST: 'Export Hashlist', - DELETE_HASHTYPE: 'Delete', - EDIT_SUPERHASHLIST: 'Edit', - DELETE_SUPERHASHLIST: 'Delete' + EXPORT_HASHLIST: 'Export Hashlist' }; export const RowActionMenuAction = { From d1f298bdc1ebf607f3bafee86595936a43e12c49 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Tue, 14 Nov 2023 09:46:22 +0100 Subject: [PATCH 262/419] Add file size util function --- src/app/shared/utils/util.ts | 54 ++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/src/app/shared/utils/util.ts b/src/app/shared/utils/util.ts index cf2bf356..e80233f2 100644 --- a/src/app/shared/utils/util.ts +++ b/src/app/shared/utils/util.ts @@ -73,3 +73,57 @@ export const formatPercentage = (value: number, total: number): string => { return `${formattedPercentage}%`; }; + +/** + * Formats a file size in bytes into a human-readable string. + * + * @param sizeInBytes - The size in bytes to be formatted. + * @param useLongForm - If true, use long-form units (e.g., "Kilobytes" instead of "KB"). + * @param baseSize - The base for size conversion (default is 1024). + * @param threshold - The threshold for switching to the next unit (default is 1024). + * + * @returns A human-readable string representing the file size. + */ +export const formatFileSize = ( + sizeInBytes: number, + suffix: 'short' | 'long' | 'none', + baseSize = 1024, + threshold = 1024 +): string => { + const fileSizeUnits = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; + const fileSizeUnitsLong = [ + 'Bytes', + 'Kilobytes', + 'Megabytes', + 'Gigabytes', + 'Pettabytes', + 'Exabytes', + 'Zettabytes', + 'Yottabytes' + ]; + let units: string[] = []; + + if (suffix === 'short') { + units = fileSizeUnits; + } else if (suffix === 'long') { + units = fileSizeUnitsLong; + } + + let formattedSize: number | string = 0; + + if (sizeInBytes < 1) { + return '0'; + } + + const scale = sizeInBytes > threshold ? sizeInBytes / threshold : sizeInBytes; + const power = Math.min( + Math.round(Math.log(scale) / Math.log(baseSize)), + units.length - 1 + ); + const size = sizeInBytes / Math.pow(baseSize, power); + const unit = units ? units[power] : ''; + + formattedSize = Math.round(size * 100) / 100; + + return `${formattedSize} ${unit}`; +}; From cf1bd409bfe52e5be567b1da75fd71b0462f88fd Mon Sep 17 00:00:00 2001 From: itbf9 Date: Tue, 14 Nov 2023 09:46:46 +0100 Subject: [PATCH 263/419] Add files table --- .../files-table/files-table.component.html | 15 ++ .../files-table/files-table.component.ts | 254 ++++++++++++++++++ .../files-table/files-table.constants.ts | 7 + 3 files changed, 276 insertions(+) create mode 100644 src/app/core/_components/tables/files-table/files-table.component.html create mode 100644 src/app/core/_components/tables/files-table/files-table.component.ts create mode 100644 src/app/core/_components/tables/files-table/files-table.constants.ts diff --git a/src/app/core/_components/tables/files-table/files-table.component.html b/src/app/core/_components/tables/files-table/files-table.component.html new file mode 100644 index 00000000..28ccd98f --- /dev/null +++ b/src/app/core/_components/tables/files-table/files-table.component.html @@ -0,0 +1,15 @@ + diff --git a/src/app/core/_components/tables/files-table/files-table.component.ts b/src/app/core/_components/tables/files-table/files-table.component.ts new file mode 100644 index 00000000..a4f9c219 --- /dev/null +++ b/src/app/core/_components/tables/files-table/files-table.component.ts @@ -0,0 +1,254 @@ +/* eslint-disable @angular-eslint/component-selector */ +import { Component, Input, OnDestroy, OnInit } from '@angular/core'; +import { File, FileType } from 'src/app/core/_models/file.model'; +import { HTTableColumn, HTTableIcon } from '../ht-table/ht-table.models'; +import { catchError, forkJoin } from 'rxjs'; + +import { ActionMenuEvent } from '../../menus/action-menu/action-menu.model'; +import { BaseTableComponent } from '../base-table/base-table.component'; +import { BulkActionMenuAction } from '../../menus/bulk-action-menu/bulk-action-menu.constants'; +import { Cacheable } from 'src/app/core/_decorators/cacheable'; +import { DialogData } from '../table-dialog/table-dialog.model'; +import { ExportMenuAction } from '../../menus/export-menu/export-menu.constants'; +import { FilesDataSource } from 'src/app/core/_datasources/files.datasource'; +import { FilesTableColumnLabel } from './files-table.constants'; +import { RowActionMenuAction } from '../../menus/row-action-menu/row-action-menu.constants'; +import { SERV } from 'src/app/core/_services/main.config'; +import { TableDialogComponent } from '../table-dialog/table-dialog.component'; +import { formatFileSize } from 'src/app/shared/utils/util'; + +@Component({ + selector: 'files-table', + templateUrl: './files-table.component.html' +}) +export class FilesTableComponent + extends BaseTableComponent + implements OnInit, OnDestroy +{ + @Input() fileType: FileType = 0; + + tableColumns: HTTableColumn[] = []; + dataSource: FilesDataSource; + editPath = ''; + + ngOnInit(): void { + this.editPath = + this.fileType === FileType.WORDLIST ? 'wordlist-edit' : 'rules-edit'; + + this.tableColumns = this.getColumns(); + this.dataSource = new FilesDataSource(this.cdr, this.gs, this.uiService); + this.dataSource.setColumns(this.tableColumns); + this.dataSource.setFileType(this.fileType); + this.dataSource.loadAll(); + } + + ngOnDestroy(): void { + for (const sub of this.subscriptions) { + sub.unsubscribe(); + } + } + + filter(item: File, filterValue: string): boolean { + if (item.filename.toLowerCase().includes(filterValue)) { + return true; + } + + return false; + } + + getColumns(): HTTableColumn[] { + const tableColumns = [ + { + name: FilesTableColumnLabel.ID, + dataKey: '_id', + isSortable: true, + export: async (file: File) => file._id + '' + }, + { + name: FilesTableColumnLabel.NAME, + dataKey: 'filename', + icons: (file: File) => this.renderSecretIcon(file), + routerLink: (file: File) => [ + { + routerLink: ['/files', file._id, this.editPath] + } + ], + isSortable: true, + export: async (file: File) => file.filename + }, + { + name: FilesTableColumnLabel.SIZE, + dataKey: 'size', + render: (file: File) => formatFileSize(file.size, 'short'), + isSortable: true, + export: async (file: File) => formatFileSize(file.size, 'short') + }, + { + name: FilesTableColumnLabel.LINE_COUNT, + dataKey: 'lineCount', + isSortable: true, + export: async (file: File) => file.lineCount + '' + }, + { + name: FilesTableColumnLabel.ACCESS_GROUP, + dataKey: 'accessGroupName', + isSortable: true, + export: async (file: File) => file.accessGroupName + } + ]; + + return tableColumns; + } + + openDialog(data: DialogData) { + const dialogRef = this.dialog.open(TableDialogComponent, { + data: data, + width: '450px' + }); + + this.subscriptions.push( + dialogRef.afterClosed().subscribe((result) => { + if (result && result.action) { + switch (result.action) { + case RowActionMenuAction.DELETE: + this.rowActionDelete(result.data); + break; + case BulkActionMenuAction.DELETE: + this.bulkActionDelete(result.data); + break; + } + } + }) + ); + } + + // --- Render functions --- + + @Cacheable(['_id', 'isSecret']) + async renderSecretIcon(file: File): Promise { + const icons: HTTableIcon[] = []; + if (file.isSecret) { + icons.push({ + name: 'lock', + tooltip: 'Secret' + }); + } + + return icons; + } + + // --- Action functions --- + + exportActionClicked(event: ActionMenuEvent): void { + switch (event.menuItem.action) { + case ExportMenuAction.EXCEL: + this.exportService.toExcel( + 'hashtopolis-files', + this.tableColumns, + event.data + ); + break; + case ExportMenuAction.CSV: + this.exportService.toCsv( + 'hashtopolis-files', + this.tableColumns, + event.data + ); + break; + case ExportMenuAction.COPY: + this.exportService + .toClipboard(this.tableColumns, event.data) + .then(() => { + this.snackBar.open( + 'The selected rows are copied to the clipboard', + 'Close' + ); + }); + break; + } + } + + rowActionClicked(event: ActionMenuEvent): void { + switch (event.menuItem.action) { + case RowActionMenuAction.EDIT: + this.rowActionEdit(event.data); + break; + case RowActionMenuAction.DELETE: + this.openDialog({ + rows: [event.data], + title: `Deleting file ${event.data.filename} ...`, + icon: 'warning', + body: `Are you sure you want to delete it? Note that this action cannot be undone.`, + warn: true, + action: event.menuItem.action + }); + break; + } + } + + bulkActionClicked(event: ActionMenuEvent): void { + switch (event.menuItem.action) { + case BulkActionMenuAction.DELETE: + this.openDialog({ + rows: event.data, + title: `Deleting ${event.data.length} files ...`, + icon: 'warning', + body: `Are you sure you want to delete the above files? Note that this action cannot be undone.`, + warn: true, + listAttribute: 'filename', + action: event.menuItem.action + }); + break; + } + } + + /** + * @todo Implement error handling. + */ + private bulkActionDelete(files: File[]): void { + const requests = files.map((file: File) => { + return this.gs.delete(SERV.HASHLISTS, file._id); + }); + + this.subscriptions.push( + forkJoin(requests) + .pipe( + catchError((error) => { + console.error('Error during deletion:', error); + return []; + }) + ) + .subscribe((results) => { + this.snackBar.open( + `Successfully deleted ${results.length} files!`, + 'Close' + ); + this.reload(); + }) + ); + } + + /** + * @todo Implement error handling. + */ + private rowActionDelete(files: File[]): void { + this.subscriptions.push( + this.gs + .delete(SERV.HASHLISTS, files[0]._id) + .pipe( + catchError((error) => { + console.error('Error during deletion:', error); + return []; + }) + ) + .subscribe(() => { + this.snackBar.open('Successfully deleted file!', 'Close'); + this.reload(); + }) + ); + } + + private rowActionEdit(file: File): void { + this.router.navigate(['/files', file._id, this.editPath]); + } +} diff --git a/src/app/core/_components/tables/files-table/files-table.constants.ts b/src/app/core/_components/tables/files-table/files-table.constants.ts new file mode 100644 index 00000000..67fa304c --- /dev/null +++ b/src/app/core/_components/tables/files-table/files-table.constants.ts @@ -0,0 +1,7 @@ +export const FilesTableColumnLabel = { + ID: 'ID', + NAME: 'Name', + SIZE: 'Size', + LINE_COUNT: 'Line Count', + ACCESS_GROUP: 'Access Group' +}; From 1e719fb61e64c83fabc8491d0498e717b11acde7 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Tue, 14 Nov 2023 09:47:09 +0100 Subject: [PATCH 264/419] implement files table --- .../_components/core-components.module.ts | 7 +- .../tables/ht-table/ht-table.component.html | 37 +-- .../tables/ht-table/ht-table.models.ts | 1 + src/app/core/_models/access-group.model.ts | 4 +- src/app/core/_models/config-ui.model.ts | 8 + src/app/core/_models/file.model.ts | 60 +++- .../files-edit/files-edit.component.html | 90 +++--- src/app/files/files.component.html | 73 ++--- src/app/files/files.component.ts | 300 +----------------- src/app/files/files.module.ts | 34 +- 10 files changed, 179 insertions(+), 435 deletions(-) diff --git a/src/app/core/_components/core-components.module.ts b/src/app/core/_components/core-components.module.ts index 8902d0a2..e0e94514 100644 --- a/src/app/core/_components/core-components.module.ts +++ b/src/app/core/_components/core-components.module.ts @@ -13,6 +13,7 @@ import { ChunksTableComponent } from './tables/chunks-table/chunks-table.compone import { ColumnSelectionDialogComponent } from './tables/column-selection-dialog/column-selection-dialog.component'; import { CommonModule } from '@angular/common'; import { ExportMenuComponent } from './menus/export-menu/export-menu.component'; +import { FilesTableComponent } from './tables/files-table/files-table.component'; import { HTTableComponent } from './tables/ht-table/ht-table.component'; import { HashlistsTableComponent } from './tables/hashlists-table/hashlists-table.component'; import { HashtypesTableComponent } from './tables/hashtypes-table/hashtypes-table.component'; @@ -53,7 +54,8 @@ import { TableDialogComponent } from './tables/table-dialog/table-dialog.compone ChunksTableComponent, HashtypesTableComponent, HashlistsTableComponent, - SuperHashlistsTableComponent + SuperHashlistsTableComponent, + FilesTableComponent ], imports: [ ReactiveFormsModule, @@ -92,7 +94,8 @@ import { TableDialogComponent } from './tables/table-dialog/table-dialog.compone ChunksTableComponent, HashlistsTableComponent, HashtypesTableComponent, - SuperHashlistsTableComponent + SuperHashlistsTableComponent, + FilesTableComponent ], providers: [ { 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 fdeb9a72..2efe1937 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 @@ -138,24 +138,6 @@ [class.truncate]="tableColumn.routerLink" [class.icons]="tableColumn.icons" > - - - - {{ icon.name }} - - {{ - icon.name - }} - - - @@ -188,6 +170,25 @@ {{ element[tableColumn.dataKey] }} + + + + + {{ icon.name }} + + {{ + icon.name + }} + + +
-
+
+ + + + + + + + + +
+
+ -
- - - - - + /> +
- - - - - - - - - - - - - - - - - - - - - - -
IDNameStatusSizeLine CountAccess GroupActions
{{ f.fileId }}{{ f.filename | shortenString:35 }}{{ f.size | fileSize:false }}{{ f.lineCount | number: '2.' }}{{ f.accessGroup.groupName }} - - - - - -
+ + + + + + + + + + + + diff --git a/src/app/files/files.component.ts b/src/app/files/files.component.ts index 3d18ce26..74214d22 100644 --- a/src/app/files/files.component.ts +++ b/src/app/files/files.component.ts @@ -1,317 +1,43 @@ -import { faEdit, faTrash, faPlus, faLock } from '@fortawesome/free-solid-svg-icons'; -import { Component, OnInit, ViewChild } from '@angular/core'; -import { ActivatedRoute, Router } from '@angular/router'; -import { DataTableDirective } from 'angular-datatables'; -import Swal from 'sweetalert2/dist/sweetalert2.js'; -import { HttpClient } from '@angular/common/http'; -import { Subject } from 'rxjs'; +import { Component, OnInit } from '@angular/core'; -import { AlertService } from '../core/_services/shared/alert.service'; -import { GlobalService } from 'src/app/core/_services/main.service'; -import { environment } from './../../environments/environment'; -import { PageTitle } from '../core/_decorators/autotitle'; -import { SERV } from '../core/_services/main.config'; -import { Filetype } from '../core/_models/file.model'; - -declare let $: any; +import { ActivatedRoute } from '@angular/router'; +import { AutoTitleService } from '../core/_services/shared/autotitle.service'; +import { FileType } from '../core/_models/file.model'; @Component({ selector: 'app-files', templateUrl: './files.component.html' }) -@PageTitle(['Show Files']) export class FilesComponent implements OnInit { - - faTrash = faTrash; - faPlus = faPlus; - faLock = faLock; - faEdit = faEdit; - - public allfiles: { - fileId: number, - filename: string, - size: number, - isSecret: boolean, - fileType: number, - accessGroupId: number, - lineCount: number - accessGroup: { - accessGroupId: number, - groupName: string - } - }[] = []; - - private maxResults = environment.config.prodApiMaxResults; + fileType: FileType = 0; + FileType = FileType; constructor( private route: ActivatedRoute, - private alert: AlertService, - private gs: GlobalService, - private router: Router - ) { } - - @ViewChild(DataTableDirective) - dtElement: DataTableDirective; - - dtTrigger: Subject = new Subject(); - dtOptions: any = {}; - - ngOnDestroy() { - this.dtTrigger.unsubscribe(); + private titleService: AutoTitleService + ) { + titleService.set(['Show Files']); } - filterType: number - whichView: string; - navEdit: string; - ngOnInit(): void { - this.loadFiles(); - } loadFiles() { - this.route.data.subscribe(data => { + this.route.data.subscribe((data) => { switch (data['kind']) { - case 'wordlist': - this.filterType = 0; - this.whichView = 'wordlist'; - this.navEdit = 'wordlist-edit'; + this.fileType = FileType.WORDLIST; break; case 'rules': - this.filterType = 1; - this.whichView = 'rules'; - this.navEdit = 'rules-edit'; + this.fileType = FileType.RULES; break; case 'other': - this.filterType = 2; - this.whichView = 'other'; - this.navEdit = 'rules-edit'; + this.fileType = FileType.OTHER; break; - - } - const params = { 'maxResults': this.maxResults, 'expand': 'accessGroup', 'filter': 'fileType=' + this.filterType + '' }; - - this.gs.getAll(SERV.FILES, params).subscribe((files: any) => { - this.allfiles = files.values; - this.dtTrigger.next(void 0); - }); - - const self = this; - this.dtOptions = { - dom: 'Bfrtip', - scrollX: true, - pageLength: 25, - lengthMenu: [ - [10, 25, 50, 100, 250, -1], - [10, 25, 50, 100, 250, 'All'] - ], - scrollY: true, - stateSave: true, - destroy: true, - select: { - style: 'multi', - }, - order: [[0, 'desc']], - buttons: { - dom: { - button: { - className: 'dt-button buttons-collection btn btn-sm-dt btn-outline-gray-600-dt', - } - }, - buttons: [ - { - text: '↻', - autoClose: true, - action: function (e, dt, node, config) { - self.onRefresh(); - } - }, - { - extend: 'collection', - text: 'Export', - buttons: [ - { - extend: 'excelHtml5', - exportOptions: { - columns: [0, 1, 2, 3, 4, 5] - }, - }, - { - extend: 'print', - exportOptions: { - columns: [0, 1, 2, 3, 4, 5] - }, - customize: function (win) { - $(win.document.body) - .css('font-size', '10pt') - $(win.document.body).find('table') - .addClass('compact') - .css('font-size', 'inherit'); - } - }, - { - extend: 'csvHtml5', - exportOptions: { modifier: { selected: true } }, - select: true, - customize: function (dt, csv) { - let data = ""; - for (let i = 0; i < dt.length; i++) { - data = "Agents\n\n" + dt; - } - return data; - } - }, - { - extend: 'copy', - } - ] - }, - { - extend: 'collection', - text: 'Bulk Actions', - buttons: [ - { - text: 'Delete Files', - autoClose: true, - action: function (e, dt, node, config) { - self.onDeleteBulk(); - } - }, - { - text: 'Change Type', - autoClose: true, - action: function (e, dt, node, config) { - const title = 'Edit File Type' - self.onModalEditType(title) - } - }, - { - text: 'Line Count (Missing API Call)', - autoClose: true, - action: function (e, dt, node, config) { - } - } - ] - }, - { - extend: 'colvis', - text: 'Column View', - columns: [0, 1, 2, 3, 4, 5], - }, - { - extend: "pageLength", - className: "btn-sm" - } - ], - } - }; - - }); - } - - onRefresh() { - this.rerender(); - this.ngOnInit(); - } - - rerender(): void { - this.dtElement.dtInstance.then((dtInstance: DataTables.Api) => { - // Destroy the table first - dtInstance.destroy(); - // Call the dtTrigger to rerender again - setTimeout(() => { - this.dtTrigger['new'].next(); - }); - }); - } - - deleteFile(id: number, name: string) { - this.alert.deleteConfirmation(name, 'Files').then((confirmed) => { - if (confirmed) { - // Deletion - this.gs.delete(SERV.FILES, id).subscribe(() => { - // Successful deletion - this.alert.okAlert(`Deleted File ${name}`, ''); - this.onRefreshTable(); // Refresh the table - }); - } else { - // Handle cancellation - this.alert.okAlert(`File ${name} is safe!`, ''); } }); } - - onRefreshTable() { - setTimeout(() => { - this.ngOnInit(); - this.rerender(); // rerender datatables - }, 2000); - } - - // Bulk Actions - - onSelectedFiles() { - $(".dt-button-background").trigger("click"); - const selection = $($(this.dtElement).DataTable.tables()).DataTable().rows({ selected: true }).data().pluck(0).toArray(); - if (selection.length == 0) { - this.alert.okAlert('You have not selected any File', ''); - return; - } - const selectionnum = selection.map(i => Number(i)); - - return selectionnum; - } - - async onDeleteBulk() { - const FilesIds = this.onSelectedFiles(); - this.alert.bulkDeleteAlert(FilesIds, 'Files', SERV.FILES); - this.onRefreshTable(); - } - - async onUpdateBulk(value: any) { - const FilesIds = this.onSelectedFiles(); - this.alert.bulkUpdateAlert(FilesIds, value, 'Files', SERV.FILES); - this.onRefreshTable(); - } - - onEdit(id: number) { - this.router.navigate(['/files', id, this.navEdit]); - } - - onModalEditType(title: string) { - (async () => { - - $(".dt-button-background").trigger("click"); - const selection = $($(this.dtElement).DataTable.tables()).DataTable().rows({ selected: true }).data().pluck(0).toArray(); - if (selection.length == 0) { - this.alert.okAlert('You have not selected any Group', ''); - return; - } - - const { value: formValues } = await Swal.fire({ - title: title, - html: - '', - focusConfirm: false, - confirmButtonColor: '#4B5563', - preConfirm: () => { - return [ - (document.getElementById('filetype')).value, - ] - } - }) - - if (formValues) { - const edit = { fileType: +formValues }; - this.onUpdateBulk(edit); - } - - })() - } - - } diff --git a/src/app/files/files.module.ts b/src/app/files/files.module.ts index eb314a8e..d1c2877f 100644 --- a/src/app/files/files.module.ts +++ b/src/app/files/files.module.ts @@ -1,31 +1,29 @@ -import { FontAwesomeModule } from "@fortawesome/angular-fontawesome"; -import { FormsModule, ReactiveFormsModule } from "@angular/forms"; -import { NgbModule } from "@ng-bootstrap/ng-bootstrap"; -import { DataTablesModule } from "angular-datatables"; -import { RouterModule } from "@angular/router"; -import { CommonModule } from "@angular/common"; -import { NgModule } from "@angular/core"; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; -import { FilesEditComponent } from "./files-edit/files-edit.component"; +import { CommonModule } from '@angular/common'; +import { ComponentsModule } from '../shared/components.module'; +import { CoreComponentsModule } from '../core/_components/core-components.module'; +import { DataTablesModule } from 'angular-datatables'; +import { FilesComponent } from './files.component'; +import { FilesEditComponent } from './files-edit/files-edit.component'; +import { FilesRoutingModule } from './files-routing.module'; +import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; import { NewFilesComponent } from './new-files/new-files.component'; -import { ComponentsModule } from "../shared/components.module"; -import { FilesRoutingModule } from "./files-routing.module"; -import { PipesModule } from "../shared/pipes.module"; -import { FilesComponent } from "./files.component"; +import { NgModule } from '@angular/core'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { PipesModule } from '../shared/pipes.module'; +import { RouterModule } from '@angular/router'; @NgModule({ - declarations:[ - FilesEditComponent, - FilesComponent, - NewFilesComponent - ], - imports:[ + declarations: [FilesEditComponent, FilesComponent, NewFilesComponent], + imports: [ ReactiveFormsModule, FilesRoutingModule, FontAwesomeModule, DataTablesModule, ComponentsModule, CommonModule, + CoreComponentsModule, RouterModule, FormsModule, PipesModule, From 652000bd64f6cba8b8ffffe0a17080dcb94c643b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Tue, 14 Nov 2023 08:48:24 +0000 Subject: [PATCH 265/419] Superhaslist MatChip --- src/app/hashlists/hashlists.module.ts | 25 ++++ .../new-superhashlist.component.html | 64 +++++---- .../new-superhashlist.component.ts | 135 ++++++++++++------ .../shared/form/dynamicform.component.html | 4 +- src/app/shared/form/dynamicform.module.ts | 37 +++-- .../select-field/select-field.component.html | 11 ++ .../select-field/select-field.component.ts | 11 ++ 7 files changed, 197 insertions(+), 90 deletions(-) create mode 100644 src/app/shared/form/select-field/select-field.component.html create mode 100644 src/app/shared/form/select-field/select-field.component.ts diff --git a/src/app/hashlists/hashlists.module.ts b/src/app/hashlists/hashlists.module.ts index 40a6302b..d49aff09 100644 --- a/src/app/hashlists/hashlists.module.ts +++ b/src/app/hashlists/hashlists.module.ts @@ -20,6 +20,19 @@ import { RouterModule } from '@angular/router'; import { SearchHashComponent } from './search-hash/search-hash.component'; import { ShowCracksComponent } from './show-cracks/show-cracks.component'; import { SuperhashlistComponent } from './superhashlist/superhashlist.component'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatAutocompleteModule } from '@angular/material/autocomplete'; +import { MatOptionModule } from '@angular/material/core'; +import { MatIconModule } from '@angular/material/icon'; +import { MatInputModule } from '@angular/material/input'; +import { MatSelectModule } from '@angular/material/select'; +import { MatDividerModule } from '@angular/material/divider'; +import { MatTooltipModule } from '@angular/material/tooltip'; +import { MatButtonModule } from '@angular/material/button'; +import { MatCheckboxModule } from '@angular/material/checkbox'; +import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; + +import { MatChipsModule } from '@angular/material/chips'; @NgModule({ declarations: [ @@ -39,6 +52,18 @@ import { SuperhashlistComponent } from './superhashlist/superhashlist.component' FontAwesomeModule, DataTablesModule, MatSlideToggleModule, + MatAutocompleteModule, + MatOptionModule, + MatButtonModule, + MatCheckboxModule, + MatDividerModule, + MatIconModule, + MatInputModule, + MatProgressSpinnerModule, + MatChipsModule, + MatSelectModule, + MatTooltipModule, + MatFormFieldModule, DirectivesModule, ComponentsModule, CommonModule, diff --git a/src/app/hashlists/new-superhashlist/new-superhashlist.component.html b/src/app/hashlists/new-superhashlist/new-superhashlist.component.html index c4086827..8260ff78 100644 --- a/src/app/hashlists/new-superhashlist/new-superhashlist.component.html +++ b/src/app/hashlists/new-superhashlist/new-superhashlist.component.html @@ -1,38 +1,48 @@ - +
- -
- - - + + + Name + + + + + + Select or search Hashlists: + + + {{ selectedHashlist.name }} + cancel + -
-
- -
- - - - -
-
+ + + + {{ hashlist.name }} + + + +
+ {{ createForm.value | json }}
+ + diff --git a/src/app/hashlists/new-superhashlist/new-superhashlist.component.ts b/src/app/hashlists/new-superhashlist/new-superhashlist.component.ts index 1339dcd5..4aa0f081 100644 --- a/src/app/hashlists/new-superhashlist/new-superhashlist.component.ts +++ b/src/app/hashlists/new-superhashlist/new-superhashlist.component.ts @@ -1,11 +1,15 @@ -import { Component, OnInit, ChangeDetectionStrategy ,ChangeDetectorRef } from '@angular/core'; -import { faFile, faMagnifyingGlass } from '@fortawesome/free-solid-svg-icons'; -import { FormControl, FormGroup, Validators, FormArray } from '@angular/forms'; +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + OnInit +} from '@angular/core'; +import { FormArray, FormControl, FormGroup, Validators } from '@angular/forms'; import { Router } from '@angular/router'; import { AlertService } from 'src/app/core/_services/shared/alert.service'; import { GlobalService } from 'src/app/core/_services/main.service'; -import { environment } from './../../../environments/environment' +import { environment } from './../../../environments/environment'; import { PageTitle } from 'src/app/core/_decorators/autotitle'; import { SERV } from '../../core/_services/main.config'; @@ -16,67 +20,73 @@ import { SERV } from '../../core/_services/main.config'; }) @PageTitle(['New SuperHashlist']) export class NewSuperhashlistComponent implements OnInit { - - faFile=faFile; - faMagnifyingGlass=faMagnifyingGlass; - constructor( private _changeDetectorRef: ChangeDetectorRef, private alert: AlertService, private gs: GlobalService, private router: Router - ) { } + ) {} createForm: FormGroup; - private maxResults = environment.config.prodApiMaxResults + private maxResults = environment.config.prodApiMaxResults; formArr: FormArray; + hashlists: any; ngOnInit(): void { - this.createForm = new FormGroup({ name: new FormControl(''), - hashlistIds: new FormControl(''), + hashlistIds: new FormControl('') }); - this.gs.getAll(SERV.HASHLISTS,{'maxResults': this.maxResults, 'filter': 'isArchived=false,format=0'}).subscribe((tasks: any) => { - const self = this; - const response = tasks.values; - ($("#hashlistIds") as any).selectize({ - maxItems: null, - plugins: ["restore_on_backspace"], - valueField: "hashlistId", - placeholder: "Search hashlist...", - labelField: "name", - searchField: ["name"], - loadingClass: 'Loading..', - highlight: true, - onChange: function (value) { + this.gs + .getAll(SERV.HASHLISTS, { + maxResults: this.maxResults, + filter: 'isArchived=false,format=0' + }) + .subscribe((tasks: any) => { + const self = this; + const response = tasks.values; + this.hashlists = response; + ($('#hashlistIds') as any).selectize({ + maxItems: null, + plugins: ['restore_on_backspace'], + valueField: 'hashlistId', + placeholder: 'Search hashlist...', + labelField: 'name', + searchField: ['name'], + loadingClass: 'Loading..', + highlight: true, + onChange: function (value) { self.OnChangeValue(value); // We need to overide DOM event, Angular vs Jquery - }, - render: { - option: function (item, escape) { - return '
' + escape(item.hashlistId) + ' - ' + escape(item.name) + '
'; }, - }, - onInitialize: function(){ - const selectize = this; + render: { + option: function (item, escape) { + return ( + '
' + + escape(item.hashlistId) + + ' - ' + + escape(item.name) + + '
' + ); + } + }, + onInitialize: function () { + const selectize = this; selectize.addOption(response); // This is will add to option const selected_items = []; - $.each(response, function( i, obj) { - selected_items.push(obj.id); + $.each(response, function (i, obj) { + selected_items.push(obj.id); }); selectize.setValue(selected_items); //this will set option values as default } - }); }); + }); } - OnChangeValue(value){ + OnChangeValue(value) { const formArr = new FormArray([]); for (const val of value) { - formArr.push( - new FormControl(+val) - ); + formArr.push(new FormControl(+val)); } const cname = this.createForm.get('name').value; this.createForm = new FormGroup({ @@ -86,16 +96,49 @@ export class NewSuperhashlistComponent implements OnInit { this._changeDetectorRef.detectChanges(); } - onSubmit(){ + onSubmit() { if (this.createForm.valid) { console.log(this.createForm.value); - this.gs.chelper(SERV.HELPER,'createSuperHashlist',this.createForm.value).subscribe(() => { - this.alert.okAlert('New SuperHashList created!',''); - this.createForm.reset(); // success, we reset form - this.router.navigate(['hashlists/superhashlist']); - } - ); + this.gs + .chelper(SERV.HELPER, 'createSuperHashlist', this.createForm.value) + .subscribe(() => { + this.alert.okAlert('New SuperHashList created!', ''); + this.createForm.reset(); // success, we reset form + this.router.navigate(['hashlists/superhashlist']); + }); } } + hashlistIds = new FormControl(); + selectedHashlistDisplay: any[] = []; // Array for displaying selected hashlists + selectedHashlistValues: any[] = []; // Array for storing selected hashlist values + + addItem(event: any): void { + const value = (event.value || '').trim(); + + if (value && !this.selectedHashlistValues.includes(value)) { + this.selectedHashlistValues.push(value); + this.selectedHashlistDisplay.push(value); // Add to display array + this.hashlistIds.setValue(''); + } + } + + removeItem(hashlist: any): void { + const index = this.selectedHashlistValues.indexOf(hashlist); + + if (index >= 0) { + this.selectedHashlistValues.splice(index, 1); + this.selectedHashlistDisplay.splice(index, 1); // Remove from display array + } + } + + selectedItem(event: any): void { + const selectedValue = event.option.value; + + if (!this.selectedHashlistValues.includes(selectedValue)) { + this.selectedHashlistValues.push(selectedValue); + this.selectedHashlistDisplay.push(selectedValue); // Add to display array + this.hashlistIds.setValue(''); + } + } } diff --git a/src/app/shared/form/dynamicform.component.html b/src/app/shared/form/dynamicform.component.html index bc221bbb..e3be6a73 100644 --- a/src/app/shared/form/dynamicform.component.html +++ b/src/app/shared/form/dynamicform.component.html @@ -69,15 +69,13 @@
{{ field.label }}
- Please Select an Option {{ option.name }} + diff --git a/src/app/shared/form/dynamicform.module.ts b/src/app/shared/form/dynamicform.module.ts index a59df6c7..e86a2c79 100644 --- a/src/app/shared/form/dynamicform.module.ts +++ b/src/app/shared/form/dynamicform.module.ts @@ -12,6 +12,7 @@ import { MatDividerModule } from '@angular/material/divider'; import { MatTooltipModule } from '@angular/material/tooltip'; import { MatButtonModule } from '@angular/material/button'; import { MatCheckboxModule } from '@angular/material/checkbox'; +import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; import { MAT_FORM_FIELD_DEFAULT_OPTIONS, MatFormFieldModule @@ -21,27 +22,35 @@ import { MatInputModule } from '@angular/material/input'; import { MatSelectModule } from '@angular/material/select'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { PageTitleModule } from '../page-headers/page-title.module'; +import { SelectFieldComponent } from './select-field/select-field.component'; @NgModule({ - declarations: [DynamicFormComponent, FormConfigComponent, FormComponent], + declarations: [ + DynamicFormComponent, + FormConfigComponent, + FormComponent, + SelectFieldComponent + ], imports: [ - ReactiveFormsModule, - HorizontalNavModule, + MatButtonModule, + MatCheckboxModule, + MatDividerModule, + MatFormFieldModule, + MatIconModule, + MatInputModule, + MatProgressSpinnerModule, + MatSelectModule, + MatTooltipModule, + CommonModule, FontAwesomeModule, - PageTitleModule, - ButtonsModule, FormsModule, GridModule, NgbModule, - CommonModule, - MatCheckboxModule, - MatTooltipModule, - MatFormFieldModule, - MatSelectModule, - MatButtonModule, - MatInputModule, - MatIconModule, - MatDividerModule + PageTitleModule, + ReactiveFormsModule, + + HorizontalNavModule, + ButtonsModule ], exports: [DynamicFormComponent, FormConfigComponent, FormComponent], providers: [ diff --git a/src/app/shared/form/select-field/select-field.component.html b/src/app/shared/form/select-field/select-field.component.html new file mode 100644 index 00000000..d9e4abc0 --- /dev/null +++ b/src/app/shared/form/select-field/select-field.component.html @@ -0,0 +1,11 @@ + + + {{ option.name }} + + + + + diff --git a/src/app/shared/form/select-field/select-field.component.ts b/src/app/shared/form/select-field/select-field.component.ts new file mode 100644 index 00000000..ec0abbce --- /dev/null +++ b/src/app/shared/form/select-field/select-field.component.ts @@ -0,0 +1,11 @@ +import { Component, EventEmitter, Input, Output } from '@angular/core'; + +@Component({ + selector: 'app-select-field', + templateUrl: 'select-field.component.html' +}) +export class SelectFieldComponent { + @Input() fieldName: string; + @Input() selectOptions: any[]; + @Input() isLoading: boolean; +} From 29d8bd2a74c70be5022703ba5110ad662a58c213 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Tue, 14 Nov 2023 14:41:08 +0100 Subject: [PATCH 266/419] Update cracker binary models --- src/app/core/_models/cracker-binary.model.ts | 27 ++++++++++---------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/src/app/core/_models/cracker-binary.model.ts b/src/app/core/_models/cracker-binary.model.ts index d5a249f0..64fe6303 100644 --- a/src/app/core/_models/cracker-binary.model.ts +++ b/src/app/core/_models/cracker-binary.model.ts @@ -1,17 +1,18 @@ export interface CrackerBinary { - _id: number - _self: string - binaryName: string - crackerBinaryId: number - crackerBinaryTypeId: number - downloadUrl: string - version: string + _id: number; + _self: string; + binaryName: string; + crackerBinaryId: number; + crackerBinaryTypeId: number; + downloadUrl: string; + version: string; } export interface CrackerBinaryType { - crackerBinaryTypeId: number - isChunkingAvailable: boolean - typeName: string - _id: number - _self: string -} \ No newline at end of file + _id: number; + _self: string; + crackerBinaryTypeId: number; + crackerVersions: CrackerBinary[]; + isChunkingAvailable: boolean; + typeName: string; +} From 8c9dc59fdc7c4a5d0306f0192009c451faae6cd4 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Tue, 14 Nov 2023 14:41:31 +0100 Subject: [PATCH 267/419] create crackers datasource --- .../core/_datasources/crackers.datasource.ts | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 src/app/core/_datasources/crackers.datasource.ts diff --git a/src/app/core/_datasources/crackers.datasource.ts b/src/app/core/_datasources/crackers.datasource.ts new file mode 100644 index 00000000..646d9257 --- /dev/null +++ b/src/app/core/_datasources/crackers.datasource.ts @@ -0,0 +1,42 @@ +import { catchError, finalize, of } from 'rxjs'; + +import { BaseDataSource } from './base.datasource'; +import { CrackerBinaryType } from '../_models/cracker-binary.model'; +import { ListResponseWrapper } from '../_models/response.model'; +import { SERV } from '../_services/main.config'; + +export class CrackersDataSource extends BaseDataSource { + loadAll(): void { + this.loading = true; + + const params = { + maxResults: this.pageSize, + expand: 'crackerVersions' + }; + + const crackers$ = this.service.getAll(SERV.CRACKERS_TYPES, params); + + this.subscriptions.push( + crackers$ + .pipe( + catchError(() => of([])), + finalize(() => (this.loading = false)) + ) + .subscribe((response: ListResponseWrapper) => { + const crackers: CrackerBinaryType[] = response.values; + + this.setPaginationConfig( + this.pageSize, + this.currentPage, + response.total + ); + this.setData(crackers); + }) + ); + } + + reload(): void { + this.reset(); + this.loadAll(); + } +} From 36fc578f6706a70cc9d96a0ad0384beaf575318f Mon Sep 17 00:00:00 2001 From: itbf9 Date: Tue, 14 Nov 2023 15:50:32 +0100 Subject: [PATCH 268/419] Add crackers table --- .../engine/crackers/crackers.component.html | 41 +-- .../_components/core-components.module.ts | 7 +- .../menus/base-menu/base-menu.component.ts | 14 +- .../bulk-action-menu.component.ts | 25 +- .../bulk-action-menu.constants.ts | 1 + .../row-action-menu.component.ts | 23 ++ .../row-action-menu.constants.ts | 5 +- .../crackers-table.component.html | 15 ++ .../crackers-table.component.ts | 233 ++++++++++++++++++ .../crackers-table.constants.ts | 5 + .../tables/ht-table/ht-table.models.ts | 1 + 11 files changed, 317 insertions(+), 53 deletions(-) create mode 100644 src/app/core/_components/tables/crackers-table/crackers-table.component.html create mode 100644 src/app/core/_components/tables/crackers-table/crackers-table.component.ts create mode 100644 src/app/core/_components/tables/crackers-table/crackers-table.constants.ts diff --git a/src/app/config/engine/crackers/crackers.component.html b/src/app/config/engine/crackers/crackers.component.html index 5a7b6cf2..f531ff72 100644 --- a/src/app/config/engine/crackers/crackers.component.html +++ b/src/app/config/engine/crackers/crackers.component.html @@ -1,37 +1,10 @@ - - - - - - - - - - - - - - - - - - -
IDNameAvailable VersionsActions
{{ type.crackerBinaryTypeId }}{{ type.typeName }} - - - - - - - -
+ +
- diff --git a/src/app/core/_components/core-components.module.ts b/src/app/core/_components/core-components.module.ts index e0e94514..048e0504 100644 --- a/src/app/core/_components/core-components.module.ts +++ b/src/app/core/_components/core-components.module.ts @@ -12,6 +12,7 @@ import { BulkActionMenuComponent } from './menus/bulk-action-menu/bulk-action-me import { ChunksTableComponent } from './tables/chunks-table/chunks-table.component'; import { ColumnSelectionDialogComponent } from './tables/column-selection-dialog/column-selection-dialog.component'; import { CommonModule } from '@angular/common'; +import { CrackersTableComponent } from './tables/crackers-table/crackers-table.component'; import { ExportMenuComponent } from './menus/export-menu/export-menu.component'; import { FilesTableComponent } from './tables/files-table/files-table.component'; import { HTTableComponent } from './tables/ht-table/ht-table.component'; @@ -55,7 +56,8 @@ import { TableDialogComponent } from './tables/table-dialog/table-dialog.compone HashtypesTableComponent, HashlistsTableComponent, SuperHashlistsTableComponent, - FilesTableComponent + FilesTableComponent, + CrackersTableComponent ], imports: [ ReactiveFormsModule, @@ -95,7 +97,8 @@ import { TableDialogComponent } from './tables/table-dialog/table-dialog.compone HashlistsTableComponent, HashtypesTableComponent, SuperHashlistsTableComponent, - FilesTableComponent + FilesTableComponent, + CrackersTableComponent ], providers: [ { diff --git a/src/app/core/_components/menus/base-menu/base-menu.component.ts b/src/app/core/_components/menus/base-menu/base-menu.component.ts index d436882a..12e0ad89 100644 --- a/src/app/core/_components/menus/base-menu/base-menu.component.ts +++ b/src/app/core/_components/menus/base-menu/base-menu.component.ts @@ -32,6 +32,18 @@ export class BaseMenuComponent { } } + /** + * Check if the data row is of type "CrackerBinaryType". + * @returns `true` if the data row is a cracker; otherwise, `false`. + */ + protected isCrackerBinaryType(): boolean { + try { + return this.data['_id'] === this.data['crackerBinaryTypeId']; + } catch (error) { + return false; + } + } + /** * Check if the data row is of type "Task". * @returns `true` if the data row is a task; otherwise, `false`. @@ -46,7 +58,7 @@ export class BaseMenuComponent { /** * Check if the data row is of type "File". - * @returns `true` if the data row is a task; otherwise, `false`. + * @returns `true` if the data row is a file; otherwise, `false`. */ protected isFile(): boolean { try { diff --git a/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.component.ts b/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.component.ts index 8153c74d..68fc8a91 100644 --- a/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.component.ts +++ b/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.component.ts @@ -30,9 +30,11 @@ export class BulkActionMenuComponent } else if (this.dataType === 'hashlists') { this.getHashlistMenu(); } else if (this.dataType === 'hashtypes') { - this.getHashtypesMenu(); + this.getDeleteMenu(BulkActionMenuLabel.DELETE_HASHTYPES); } else if (this.dataType === 'files') { - this.getFilesMenu(); + this.getDeleteMenu(BulkActionMenuLabel.DELETE_FILES); + } else if (this.dataType === 'crackers') { + this.getDeleteMenu(BulkActionMenuLabel.DELETE_CRACKERS); } } @@ -58,20 +60,13 @@ export class BulkActionMenuComponent } } - private getHashtypesMenu(): void { + /** + * Generates a bulk menu with only a delete option. + * @param label Delete action label + */ + private getDeleteMenu(label: string): void { const deleteMenuAction: ActionMenuItem = { - label: BulkActionMenuLabel.DELETE_HASHTYPES, - action: BulkActionMenuAction.DELETE, - icon: 'delete', - red: true - }; - - this.actionMenuItems[0] = [deleteMenuAction]; - } - - private getFilesMenu(): void { - const deleteMenuAction: ActionMenuItem = { - label: BulkActionMenuLabel.DELETE_FILES, + label: label, action: BulkActionMenuAction.DELETE, icon: 'delete', red: true diff --git a/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.constants.ts b/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.constants.ts index 376d1855..f2334153 100644 --- a/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.constants.ts +++ b/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.constants.ts @@ -4,6 +4,7 @@ export const BulkActionMenuLabel = { DELETE_HASHLISTS: 'Delete Hashlists', DELETE_HASHTYPES: 'Delete Hashtypes', DELETE_FILES: 'Delete Files', + DELETE_CRACKERS: 'Delete Crackers', ACTIVATE_AGENTS: 'Activate Agents', DEACTIVATE_AGENTS: 'Deactivate Agents', ARCHIVE_TASKS: 'Archive Tasks', diff --git a/src/app/core/_components/menus/row-action-menu/row-action-menu.component.ts b/src/app/core/_components/menus/row-action-menu/row-action-menu.component.ts index 73356426..a4d00f2b 100644 --- a/src/app/core/_components/menus/row-action-menu/row-action-menu.component.ts +++ b/src/app/core/_components/menus/row-action-menu/row-action-menu.component.ts @@ -29,6 +29,8 @@ export class RowActionMenuComponent this.getHashtypeMenu(); } else if (this.isFile()) { this.getFileMenu(); + } else if (this.isCrackerBinaryType()) { + this.getCrackerBinaryTypeMenu(); } } @@ -53,6 +55,27 @@ export class RowActionMenuComponent ]; } + /** + * Get the context menu items for a cracker data row. + */ + private getCrackerBinaryTypeMenu(): void { + this.actionMenuItems[0] = [ + { + label: RowActionMenuLabel.NEW_VERSION, + action: RowActionMenuAction.NEW, + icon: 'add' + } + ]; + this.actionMenuItems[1] = [ + { + label: RowActionMenuLabel.DELETE_CRACKER, + action: RowActionMenuAction.DELETE, + icon: 'delete', + red: true + } + ]; + } + /** * Get the context menu items for a file data row. */ 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 7ad503da..d191d438 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 @@ -6,6 +6,7 @@ export const RowActionMenuLabel = { EDIT_SUPERHASHLIST: 'Edit Superhashlist', EDIT_FILE: 'Edit File', DELETE_AGENT: 'Delete Agent', + DELETE_CRACKER: 'Delete Cracker', DELETE_TASK: 'Delete Task', DELETE_HASHLIST: 'Delete Hashlist', DELETE_HASHTYPE: 'Delete Hashtype', @@ -15,7 +16,8 @@ export const RowActionMenuLabel = { COPY_TO_PRETASK: 'Copy to Pretask', ARCHIVE_TASK: 'Archive Task', IMPORT_HASHLIST: 'Import Hashlist', - EXPORT_HASHLIST: 'Export Hashlist' + EXPORT_HASHLIST: 'Export Hashlist', + NEW_VERSION: 'Add Version' }; export const RowActionMenuAction = { @@ -25,6 +27,7 @@ export const RowActionMenuAction = { COPY_TO_TASK: 'copy-to-task', COPY_TO_PRETASK: 'copy-to-pretask', EDIT_SUBTASKS: 'edit-subtasks', + NEW: 'new', IMPORT: 'import', EXPORT: 'export' }; diff --git a/src/app/core/_components/tables/crackers-table/crackers-table.component.html b/src/app/core/_components/tables/crackers-table/crackers-table.component.html new file mode 100644 index 00000000..df2718b4 --- /dev/null +++ b/src/app/core/_components/tables/crackers-table/crackers-table.component.html @@ -0,0 +1,15 @@ + diff --git a/src/app/core/_components/tables/crackers-table/crackers-table.component.ts b/src/app/core/_components/tables/crackers-table/crackers-table.component.ts new file mode 100644 index 00000000..1bf6c2df --- /dev/null +++ b/src/app/core/_components/tables/crackers-table/crackers-table.component.ts @@ -0,0 +1,233 @@ +/* eslint-disable @angular-eslint/component-selector */ +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { + CrackerBinary, + CrackerBinaryType +} from 'src/app/core/_models/cracker-binary.model'; +import { HTTableColumn, HTTableRouterLink } from '../ht-table/ht-table.models'; +import { catchError, forkJoin } from 'rxjs'; + +import { ActionMenuEvent } from '../../menus/action-menu/action-menu.model'; +import { BaseTableComponent } from '../base-table/base-table.component'; +import { BulkActionMenuAction } from '../../menus/bulk-action-menu/bulk-action-menu.constants'; +import { CrackersDataSource } from 'src/app/core/_datasources/crackers.datasource'; +import { CrackersTableColumnLabel } from './crackers-table.constants'; +import { DialogData } from '../table-dialog/table-dialog.model'; +import { ExportMenuAction } from '../../menus/export-menu/export-menu.constants'; +import { RowActionMenuAction } from '../../menus/row-action-menu/row-action-menu.constants'; +import { SERV } from 'src/app/core/_services/main.config'; +import { TableDialogComponent } from '../table-dialog/table-dialog.component'; + +@Component({ + selector: 'crackers-table', + templateUrl: './crackers-table.component.html' +}) +export class CrackersTableComponent + extends BaseTableComponent + implements OnInit, OnDestroy +{ + tableColumns: HTTableColumn[] = []; + dataSource: CrackersDataSource; + + ngOnInit(): void { + this.tableColumns = this.getColumns(); + this.dataSource = new CrackersDataSource(this.cdr, this.gs, this.uiService); + this.dataSource.setColumns(this.tableColumns); + this.dataSource.loadAll(); + } + + ngOnDestroy(): void { + for (const sub of this.subscriptions) { + sub.unsubscribe(); + } + } + + filter(item: CrackerBinaryType, filterValue: string): boolean { + if (item.typeName.toLowerCase().includes(filterValue)) { + return true; + } + + return false; + } + + getColumns(): HTTableColumn[] { + const tableColumns = [ + { + name: CrackersTableColumnLabel.ID, + dataKey: '_id', + isSortable: true, + export: async (cracker: CrackerBinaryType) => cracker._id + '' + }, + { + name: CrackersTableColumnLabel.NAME, + dataKey: 'typeName', + isSortable: true, + export: async (cracker: CrackerBinaryType) => cracker.typeName + }, + { + name: CrackersTableColumnLabel.VERSIONS, + dataKey: 'crackerVersions', + routerLink: (cracker: CrackerBinaryType) => { + const links: HTTableRouterLink[] = []; + for (const link of cracker.crackerVersions) { + links.push({ + label: link.version, + routerLink: ['/config', 'engine', 'crackers', link._id, 'edit'] + }); + } + return links; + }, + isSortable: false, + export: async (cracker: CrackerBinaryType) => + cracker.crackerVersions + .map((bin: CrackerBinary) => bin.version) + .join(', ') + } + ]; + + return tableColumns; + } + + openDialog(data: DialogData) { + const dialogRef = this.dialog.open(TableDialogComponent, { + data: data, + width: '450px' + }); + + this.subscriptions.push( + dialogRef.afterClosed().subscribe((result) => { + if (result && result.action) { + switch (result.action) { + case RowActionMenuAction.DELETE: + this.rowActionDelete(result.data); + break; + case BulkActionMenuAction.DELETE: + this.bulkActionDelete(result.data); + break; + } + } + }) + ); + } + + // --- Action functions --- + + exportActionClicked(event: ActionMenuEvent): void { + switch (event.menuItem.action) { + case ExportMenuAction.EXCEL: + this.exportService.toExcel( + 'hashtopolis-crackers', + this.tableColumns, + event.data + ); + break; + case ExportMenuAction.CSV: + this.exportService.toCsv( + 'hashtopolis-crackers', + this.tableColumns, + event.data + ); + break; + case ExportMenuAction.COPY: + this.exportService + .toClipboard(this.tableColumns, event.data) + .then(() => { + this.snackBar.open( + 'The selected rows are copied to the clipboard', + 'Close' + ); + }); + break; + } + } + + rowActionClicked(event: ActionMenuEvent): void { + switch (event.menuItem.action) { + case RowActionMenuAction.DELETE: + this.openDialog({ + rows: [event.data], + title: `Deleting cracker ${event.data.typeName} ...`, + icon: 'warning', + body: `Are you sure you want to delete it? Note that this action cannot be undone.`, + warn: true, + action: event.menuItem.action + }); + break; + case RowActionMenuAction.NEW: + this.rowActionAddVersion(event.data); + break; + } + } + + bulkActionClicked(event: ActionMenuEvent): void { + switch (event.menuItem.action) { + case BulkActionMenuAction.DELETE: + this.openDialog({ + rows: event.data, + title: `Deleting ${event.data.length} crackers ...`, + icon: 'warning', + body: `Are you sure you want to delete the above crackers? Note that this action cannot be undone.`, + warn: true, + listAttribute: 'crackername', + action: event.menuItem.action + }); + break; + } + } + + /** + * @todo Implement error handling. + */ + private bulkActionDelete(crackers: CrackerBinaryType[]): void { + const requests = crackers.map((cracker: CrackerBinaryType) => { + return this.gs.delete(SERV.CRACKERS_TYPES, cracker._id); + }); + + this.subscriptions.push( + forkJoin(requests) + .pipe( + catchError((error) => { + console.error('Error during deletion:', error); + return []; + }) + ) + .subscribe((results) => { + this.snackBar.open( + `Successfully deleted ${results.length} crackers!`, + 'Close' + ); + this.reload(); + }) + ); + } + + /** + * @todo Implement error handling. + */ + private rowActionDelete(crackers: CrackerBinaryType[]): void { + this.subscriptions.push( + this.gs + .delete(SERV.CRACKERS_TYPES, crackers[0]._id) + .pipe( + catchError((error) => { + console.error('Error during deletion:', error); + return []; + }) + ) + .subscribe(() => { + this.snackBar.open('Successfully deleted cracker!', 'Close'); + this.reload(); + }) + ); + } + + private rowActionAddVersion(cracker: CrackerBinaryType): void { + this.router.navigate([ + '/config', + 'engine', + 'crackers', + cracker.crackerBinaryTypeId, + 'new' + ]); + } +} diff --git a/src/app/core/_components/tables/crackers-table/crackers-table.constants.ts b/src/app/core/_components/tables/crackers-table/crackers-table.constants.ts new file mode 100644 index 00000000..0e07762f --- /dev/null +++ b/src/app/core/_components/tables/crackers-table/crackers-table.constants.ts @@ -0,0 +1,5 @@ +export const CrackersTableColumnLabel = { + ID: 'ID', + NAME: 'Name', + VERSIONS: 'Available Versions' +}; diff --git a/src/app/core/_components/tables/ht-table/ht-table.models.ts b/src/app/core/_components/tables/ht-table/ht-table.models.ts index 85f334bc..211c1ad4 100644 --- a/src/app/core/_components/tables/ht-table/ht-table.models.ts +++ b/src/app/core/_components/tables/ht-table/ht-table.models.ts @@ -7,6 +7,7 @@ export type DataType = | 'chunks' | 'hashtypes' | 'files' + | 'crackers' | 'superhashlists'; export interface HTTableIcon { From 6d8983fba7f61098df2bf9840ba6a3360298ed06 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Tue, 14 Nov 2023 16:19:15 +0100 Subject: [PATCH 269/419] Add default table columns for crackers table --- src/app/core/_models/config-ui.model.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/app/core/_models/config-ui.model.ts b/src/app/core/_models/config-ui.model.ts index 2a710480..2a49e18b 100644 --- a/src/app/core/_models/config-ui.model.ts +++ b/src/app/core/_models/config-ui.model.ts @@ -1,5 +1,6 @@ import { AgentsTableColumnLabel } from '../_components/tables/agents-table/agents-table.constants'; import { ChunksTableColumnLabel } from '../_components/tables/chunks-table/chunks-table.constants'; +import { CrackersTableColumnLabel } from '../_components/tables/crackers-table/crackers-table.constants'; import { FilesTableColumnLabel } from '../_components/tables/files-table/files-table.constants'; import { HashlistsTableColumnLabel } from '../_components/tables/hashlists-table/hashlists-table.constants'; import { HashtypesTableColumnLabel } from '../_components/tables/hashtypes-table/hashtypes-table.constants'; @@ -74,6 +75,11 @@ export const uiConfigDefault: UIConfig = { FilesTableColumnLabel.SIZE, FilesTableColumnLabel.LINE_COUNT, FilesTableColumnLabel.ACCESS_GROUP + ], + crackersTable: [ + CrackersTableColumnLabel.ID, + CrackersTableColumnLabel.NAME, + CrackersTableColumnLabel.VERSIONS ] }, refreshPage: false, From a4d0fdeb94d2bf79a93debbdb9412fd77a733d73 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Wed, 15 Nov 2023 08:28:17 +0100 Subject: [PATCH 270/419] Implement preprocessors table --- .../preprocessors.component.html | 37 +-- .../preprocessors/preprocessors.component.ts | 157 +------------ .../_components/core-components.module.ts | 7 +- .../menus/base-menu/base-menu.component.ts | 40 ++-- .../bulk-action-menu.component.ts | 21 +- .../bulk-action-menu.constants.ts | 1 + .../export-menu/export-menu.component.ts | 16 +- .../row-action-menu.component.ts | 74 ++---- .../row-action-menu.constants.ts | 2 + .../tables/ht-table/ht-table.models.ts | 1 + .../preprocessors-table.component.html | 15 ++ .../preprocessors-table.component.ts | 215 ++++++++++++++++++ .../preprocessors-table.constants.ts | 4 + .../_datasources/preprocessors.datasource.ts | 41 ++++ src/app/core/_models/config-ui.model.ts | 5 + src/app/core/_models/preprocessor.model.ts | 16 +- 16 files changed, 364 insertions(+), 288 deletions(-) create mode 100644 src/app/core/_components/tables/preprocessors-table/preprocessors-table.component.html create mode 100644 src/app/core/_components/tables/preprocessors-table/preprocessors-table.component.ts create mode 100644 src/app/core/_components/tables/preprocessors-table/preprocessors-table.constants.ts create mode 100644 src/app/core/_datasources/preprocessors.datasource.ts diff --git a/src/app/config/engine/preprocessors/preprocessors.component.html b/src/app/config/engine/preprocessors/preprocessors.component.html index ec051938..23393fc6 100644 --- a/src/app/config/engine/preprocessors/preprocessors.component.html +++ b/src/app/config/engine/preprocessors/preprocessors.component.html @@ -1,32 +1,9 @@ - - - - - - - - - - - - - - - - - -
IDNameActions
{{ p.preprocessorId }}{{ p.name }} - - - - - -
+ +
- - diff --git a/src/app/config/engine/preprocessors/preprocessors.component.ts b/src/app/config/engine/preprocessors/preprocessors.component.ts index bd29b71f..f90147c0 100644 --- a/src/app/config/engine/preprocessors/preprocessors.component.ts +++ b/src/app/config/engine/preprocessors/preprocessors.component.ts @@ -1,157 +1,12 @@ -import { faHomeAlt, faPlus, faTrash, faEdit} from '@fortawesome/free-solid-svg-icons'; -import { Component, OnInit, ViewChild } from '@angular/core'; -import { ActivatedRoute, Params } from '@angular/router'; -import { DataTableDirective } from 'angular-datatables'; -import Swal from 'sweetalert2/dist/sweetalert2.js'; -import { Router } from '@angular/router'; -import { Subject } from 'rxjs'; - -import { AlertService } from 'src/app/core/_services/shared/alert.service'; -import { environment } from './../../../../environments/environment'; -import { GlobalService } from 'src/app/core/_services/main.service'; -import { PageTitle } from 'src/app/core/_decorators/autotitle'; -import { SERV } from '../../../core/_services/main.config'; +import { AutoTitleService } from 'src/app/core/_services/shared/autotitle.service'; +import { Component } from '@angular/core'; @Component({ selector: 'app-preprocessors', templateUrl: './preprocessors.component.html' }) -@PageTitle(['Show Preprocessors']) -export class PreprocessorsComponent implements OnInit { - public isCollapsed = true; - faHome=faHomeAlt; - faPlus=faPlus; - faTrash=faTrash; - faEdit=faEdit; - - @ViewChild(DataTableDirective, {static: false}) - dtElement: DataTableDirective; - - dtTrigger: Subject = new Subject(); - dtOptions: any = {}; - - public preproc: {preprocessorId: number, name: string, url: string, binaryName: string, keyspaceCommand: string, skipCommand: string, limitCommand: string}[] = []; - - constructor( - private alert: AlertService, - private gs: GlobalService, - ) { } - - private maxResults = environment.config.prodApiMaxResults - - - ngOnInit(): void { - const params = {'maxResults': this.maxResults } - this.gs.getAll(SERV.PREPROCESSORS,params).subscribe((pre: any) => { - this.preproc = pre.values; - this.dtTrigger.next(void 0); - }); - const self = this; - this.dtOptions = { - dom: 'Bfrtip', - scrollX: true, - lengthMenu: [ - [10, 25, 50, 100, 250, -1], - [10, 25, 50, 100, 250, 'All'] - ], - pageLength: 25, - stateSave: true, - select: true, - buttons: { - dom: { - button: { - className: 'dt-button buttons-collection btn btn-sm-dt btn-outline-gray-600-dt', - } - }, - buttons: [ - { - text: '↻', - autoClose: true, - action: function (e, dt, node, config) { - self.onRefresh(); - } - }, - { - extend: 'collection', - text: 'Export', - buttons: [ - { - extend: 'excelHtml5', - exportOptions: { - columns: [0, 1] - }, - }, - { - extend: 'print', - exportOptions: { - columns: [0, 1] - }, - customize: function ( win ) { - $(win.document.body) - .css( 'font-size', '10pt' ) - $(win.document.body).find( 'table' ) - .addClass( 'compact' ) - .css( 'font-size', 'inherit' ); - } - }, - { - extend: 'csvHtml5', - exportOptions: {modifier: {selected: true}}, - select: true, - customize: function (dt, csv) { - let data = ""; - for (let i = 0; i < dt.length; i++) { - data = "Preprocessors\n\n"+ dt; - } - return data; - } - }, - 'copy' - ] - } - ], - } - }; - - } - - onRefresh(){ - this.rerender(); - this.ngOnInit(); - } - - rerender(): void { - this.dtElement.dtInstance.then((dtInstance: DataTables.Api) => { - // Destroy the table first - dtInstance.destroy(); - // Call the dtTrigger to rerender again - setTimeout(() => { - this.dtTrigger['new'].next(); - }); - }); - } - - onDelete(id: number, name: string){ - this.alert.deleteConfirmation(name,'Preprocessors').then((confirmed) => { - if (confirmed) { - // Deletion - this.gs.delete(SERV.PREPROCESSORS, id).subscribe(() => { - // Successful deletion - this.alert.okAlert(`Deleted Preprocessor ${name}`, ''); - this.onRefreshTable(); // Refresh the table - }); - } else { - // Handle cancellation - this.alert.okAlert(`Preprocessor ${name} is safe!`,''); - } - }); - } - - onRefreshTable(){ - setTimeout(() => { - this.ngOnInit(); - this.rerender(); // rerender datatables - },2000); - } - +export class PreprocessorsComponent { + constructor(private titleService: AutoTitleService) { + this.titleService.set(['Show Preprocessors']); + } } diff --git a/src/app/core/_components/core-components.module.ts b/src/app/core/_components/core-components.module.ts index 048e0504..7313ff7c 100644 --- a/src/app/core/_components/core-components.module.ts +++ b/src/app/core/_components/core-components.module.ts @@ -35,6 +35,7 @@ import { MatSortModule } from '@angular/material/sort'; import { MatTableModule } from '@angular/material/table'; import { MatTooltipModule } from '@angular/material/tooltip'; import { NgModule } from '@angular/core'; +import { PreprocessorsTableComponent } from './tables/preprocessors-table/preprocessors-table.component'; import { RouterModule } from '@angular/router'; import { RowActionMenuComponent } from './menus/row-action-menu/row-action-menu.component'; import { SuperHashlistsTableComponent } from './tables/super-hashlists-table/super-hashlists-table.component'; @@ -57,7 +58,8 @@ import { TableDialogComponent } from './tables/table-dialog/table-dialog.compone HashlistsTableComponent, SuperHashlistsTableComponent, FilesTableComponent, - CrackersTableComponent + CrackersTableComponent, + PreprocessorsTableComponent ], imports: [ ReactiveFormsModule, @@ -98,7 +100,8 @@ import { TableDialogComponent } from './tables/table-dialog/table-dialog.compone HashtypesTableComponent, SuperHashlistsTableComponent, FilesTableComponent, - CrackersTableComponent + CrackersTableComponent, + PreprocessorsTableComponent ], providers: [ { diff --git a/src/app/core/_components/menus/base-menu/base-menu.component.ts b/src/app/core/_components/menus/base-menu/base-menu.component.ts index 12e0ad89..a8805f7b 100644 --- a/src/app/core/_components/menus/base-menu/base-menu.component.ts +++ b/src/app/core/_components/menus/base-menu/base-menu.component.ts @@ -20,16 +20,28 @@ export class BaseMenuComponent { actionMenuItems: ActionMenuItem[][] = []; + private checkId(attribute: string): boolean { + try { + return this.data['_id'] === this.data[attribute]; + } catch (error) { + return false; + } + } + /** * Check if the data row is of type "Agent". * @returns `true` if the data row is an agent; otherwise, `false`. */ protected isAgent(): boolean { - try { - return this.data['_id'] === this.data['agentId']; - } catch (error) { - return false; - } + return this.checkId('agentId'); + } + + /** + * Check if the data row is of type "Preprocessor". + * @returns `true` if the data row is an preprocessor; otherwise, `false`. + */ + protected isPreprocessor(): boolean { + return this.checkId('preprocessorId'); } /** @@ -37,11 +49,7 @@ export class BaseMenuComponent { * @returns `true` if the data row is a cracker; otherwise, `false`. */ protected isCrackerBinaryType(): boolean { - try { - return this.data['_id'] === this.data['crackerBinaryTypeId']; - } catch (error) { - return false; - } + return this.checkId('crackerBinaryTypeId'); } /** @@ -49,11 +57,7 @@ export class BaseMenuComponent { * @returns `true` if the data row is a task; otherwise, `false`. */ protected isTask(): boolean { - try { - return this.data['_id'] === this.data['taskId']; - } catch (error) { - return false; - } + return this.checkId('taskId'); } /** @@ -61,11 +65,7 @@ export class BaseMenuComponent { * @returns `true` if the data row is a file; otherwise, `false`. */ protected isFile(): boolean { - try { - return this.data['_id'] === this.data['fileId']; - } catch (error) { - return false; - } + return this.checkId('fileId'); } /** diff --git a/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.component.ts b/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.component.ts index 68fc8a91..62db27cf 100644 --- a/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.component.ts +++ b/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.component.ts @@ -35,6 +35,8 @@ export class BulkActionMenuComponent this.getDeleteMenu(BulkActionMenuLabel.DELETE_FILES); } else if (this.dataType === 'crackers') { this.getDeleteMenu(BulkActionMenuLabel.DELETE_CRACKERS); + } else if (this.dataType === 'preprocessors') { + this.getDeleteMenu(BulkActionMenuLabel.DELETE_PREPROCESSORS); } } @@ -75,25 +77,6 @@ export class BulkActionMenuComponent this.actionMenuItems[0] = [deleteMenuAction]; } - private getTaskMenu(): void { - this.actionMenuItems[0] = [ - { - label: BulkActionMenuLabel.ARCHIVE_TASKS, - action: BulkActionMenuAction.ARCHIVE, - icon: 'archive' - } - ]; - - this.actionMenuItems[1] = [ - { - label: BulkActionMenuLabel.DELETE_TASKS, - action: BulkActionMenuAction.DELETE, - icon: 'delete', - red: true - } - ]; - } - private getAgentMenu(): void { this.actionMenuItems[0] = [ { diff --git a/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.constants.ts b/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.constants.ts index f2334153..73d3c9ac 100644 --- a/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.constants.ts +++ b/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.constants.ts @@ -5,6 +5,7 @@ export const BulkActionMenuLabel = { DELETE_HASHTYPES: 'Delete Hashtypes', DELETE_FILES: 'Delete Files', DELETE_CRACKERS: 'Delete Crackers', + DELETE_PREPROCESSORS: 'Delete Preprocessors', ACTIVATE_AGENTS: 'Activate Agents', DEACTIVATE_AGENTS: 'Deactivate Agents', ARCHIVE_TASKS: 'Archive Tasks', diff --git a/src/app/core/_components/menus/export-menu/export-menu.component.ts b/src/app/core/_components/menus/export-menu/export-menu.component.ts index e5c6d2bf..dccf1bcc 100644 --- a/src/app/core/_components/menus/export-menu/export-menu.component.ts +++ b/src/app/core/_components/menus/export-menu/export-menu.component.ts @@ -1,8 +1,8 @@ /* eslint-disable @angular-eslint/component-selector */ -import { Component, OnInit, } from '@angular/core'; -import { BaseMenuComponent } from '../base-menu/base-menu.component'; +import { Component, OnInit } from '@angular/core'; import { ExportMenuAction, ExportMenuLabel } from './export-menu.constants'; +import { BaseMenuComponent } from '../base-menu/base-menu.component'; @Component({ selector: 'export-menu', @@ -14,14 +14,14 @@ export class ExportMenuComponent extends BaseMenuComponent implements OnInit { { label: ExportMenuLabel.EXCEL, action: ExportMenuAction.EXCEL, - icon: 'file_download', + icon: 'file_download' }, { label: ExportMenuLabel.CSV, action: ExportMenuAction.CSV, - icon: 'file_download', + icon: 'file_download' }, - // Not yet implemented + // @todo implement print export //{ // label: ExportMenuLabel.PRINT, // action: ExportMenuAction.PRINT, @@ -30,8 +30,8 @@ export class ExportMenuComponent extends BaseMenuComponent implements OnInit { { label: ExportMenuLabel.COPY, action: ExportMenuAction.COPY, - icon: 'content_copy', - }, + icon: 'content_copy' + } ]; } -} \ No newline at end of file +} diff --git a/src/app/core/_components/menus/row-action-menu/row-action-menu.component.ts b/src/app/core/_components/menus/row-action-menu/row-action-menu.component.ts index a4d00f2b..6509e741 100644 --- a/src/app/core/_components/menus/row-action-menu/row-action-menu.component.ts +++ b/src/app/core/_components/menus/row-action-menu/row-action-menu.component.ts @@ -18,43 +18,36 @@ export class RowActionMenuComponent { ngOnInit(): void { if (this.isAgent()) { - this.getAgentMenu(); + this.getEditDeleteMenu( + RowActionMenuLabel.EDIT_AGENT, + RowActionMenuLabel.DELETE_AGENT + ); + } else if (this.isSuperHashlist()) { + this.getEditDeleteMenu( + RowActionMenuLabel.EDIT_SUPERHASHLIST, + RowActionMenuLabel.DELETE_SUPERHASHLIST + ); + } else if (this.isFile()) { + this.getEditDeleteMenu( + RowActionMenuLabel.EDIT_FILE, + RowActionMenuLabel.DELETE_FILE + ); + } else if (this.isPreprocessor()) { + this.getEditDeleteMenu( + RowActionMenuLabel.EDIT_PREPROCESSOR, + RowActionMenuLabel.DELETE_PREPROCESSOR + ); } else if (this.isTask()) { this.getTaskMenu(); } else if (this.isHashlist()) { this.getHashlistMenu(); - } else if (this.isSuperHashlist()) { - this.getSuperHashlistMenu(); } else if (this.isHashtype()) { this.getHashtypeMenu(); - } else if (this.isFile()) { - this.getFileMenu(); } else if (this.isCrackerBinaryType()) { this.getCrackerBinaryTypeMenu(); } } - /** - * Get the context menu items for an agent data row. - */ - private getAgentMenu(): void { - this.actionMenuItems[0] = [ - { - label: RowActionMenuLabel.EDIT_AGENT, - action: RowActionMenuAction.EDIT, - icon: 'edit' - } - ]; - this.actionMenuItems[1] = [ - { - label: RowActionMenuLabel.DELETE_AGENT, - action: RowActionMenuAction.DELETE, - icon: 'delete', - red: true - } - ]; - } - /** * Get the context menu items for a cracker data row. */ @@ -77,19 +70,19 @@ export class RowActionMenuComponent } /** - * Get the context menu items for a file data row. + * Get context menu with edit and delete action. */ - private getFileMenu(): void { + private getEditDeleteMenu(editLabel: string, deleteLabel: string): void { this.actionMenuItems[0] = [ { - label: RowActionMenuLabel.EDIT_FILE, + label: editLabel, action: RowActionMenuAction.EDIT, icon: 'edit' } ]; this.actionMenuItems[1] = [ { - label: RowActionMenuLabel.DELETE_FILE, + label: deleteLabel, action: RowActionMenuAction.DELETE, icon: 'delete', red: true @@ -134,27 +127,6 @@ export class RowActionMenuComponent } } - /** - * Get the context menu items for an agent data row. - */ - private getSuperHashlistMenu(): void { - this.actionMenuItems[0] = [ - { - label: RowActionMenuLabel.EDIT_SUPERHASHLIST, - action: RowActionMenuAction.EDIT, - icon: 'edit' - } - ]; - this.actionMenuItems[1] = [ - { - label: RowActionMenuLabel.DELETE_SUPERHASHLIST, - action: RowActionMenuAction.DELETE, - icon: 'delete', - red: true - } - ]; - } - /** * Get the context menu items for a task data row. */ 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 d191d438..891e0418 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 @@ -5,6 +5,7 @@ export const RowActionMenuLabel = { EDIT_HASHLIST: 'Edit Hashlist', EDIT_SUPERHASHLIST: 'Edit Superhashlist', EDIT_FILE: 'Edit File', + EDIT_PREPROCESSOR: 'Edit Preprocessor', DELETE_AGENT: 'Delete Agent', DELETE_CRACKER: 'Delete Cracker', DELETE_TASK: 'Delete Task', @@ -12,6 +13,7 @@ export const RowActionMenuLabel = { DELETE_HASHTYPE: 'Delete Hashtype', DELETE_SUPERHASHLIST: 'Delete Superhashlist', DELETE_FILE: 'Delete File', + DELETE_PREPROCESSOR: 'Delete Preprocessor', COPY_TO_TASK: 'Copy to Task', COPY_TO_PRETASK: 'Copy to Pretask', ARCHIVE_TASK: 'Archive Task', diff --git a/src/app/core/_components/tables/ht-table/ht-table.models.ts b/src/app/core/_components/tables/ht-table/ht-table.models.ts index 211c1ad4..ff0cda55 100644 --- a/src/app/core/_components/tables/ht-table/ht-table.models.ts +++ b/src/app/core/_components/tables/ht-table/ht-table.models.ts @@ -8,6 +8,7 @@ export type DataType = | 'hashtypes' | 'files' | 'crackers' + | 'preprocessors' | 'superhashlists'; export interface HTTableIcon { diff --git a/src/app/core/_components/tables/preprocessors-table/preprocessors-table.component.html b/src/app/core/_components/tables/preprocessors-table/preprocessors-table.component.html new file mode 100644 index 00000000..daa8877b --- /dev/null +++ b/src/app/core/_components/tables/preprocessors-table/preprocessors-table.component.html @@ -0,0 +1,15 @@ + diff --git a/src/app/core/_components/tables/preprocessors-table/preprocessors-table.component.ts b/src/app/core/_components/tables/preprocessors-table/preprocessors-table.component.ts new file mode 100644 index 00000000..45511f3e --- /dev/null +++ b/src/app/core/_components/tables/preprocessors-table/preprocessors-table.component.ts @@ -0,0 +1,215 @@ +/* eslint-disable @angular-eslint/component-selector */ +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { catchError, forkJoin } from 'rxjs'; + +import { ActionMenuEvent } from '../../menus/action-menu/action-menu.model'; +import { BaseTableComponent } from '../base-table/base-table.component'; +import { BulkActionMenuAction } from '../../menus/bulk-action-menu/bulk-action-menu.constants'; +import { DialogData } from '../table-dialog/table-dialog.model'; +import { ExportMenuAction } from '../../menus/export-menu/export-menu.constants'; +import { HTTableColumn } from '../ht-table/ht-table.models'; +import { Preprocessor } from 'src/app/core/_models/preprocessor.model'; +import { PreprocessorsDataSource } from 'src/app/core/_datasources/preprocessors.datasource'; +import { PreprocessorsTableColumnLabel } from './preprocessors-table.constants'; +import { RowActionMenuAction } from '../../menus/row-action-menu/row-action-menu.constants'; +import { SERV } from 'src/app/core/_services/main.config'; +import { TableDialogComponent } from '../table-dialog/table-dialog.component'; + +@Component({ + selector: 'preprocessors-table', + templateUrl: './preprocessors-table.component.html' +}) +export class PreprocessorsTableComponent + extends BaseTableComponent + implements OnInit, OnDestroy +{ + tableColumns: HTTableColumn[] = []; + dataSource: PreprocessorsDataSource; + + ngOnInit(): void { + this.tableColumns = this.getColumns(); + this.dataSource = new PreprocessorsDataSource( + this.cdr, + this.gs, + this.uiService + ); + this.dataSource.setColumns(this.tableColumns); + this.dataSource.loadAll(); + } + + ngOnDestroy(): void { + for (const sub of this.subscriptions) { + sub.unsubscribe(); + } + } + + filter(item: Preprocessor, filterValue: string): boolean { + if (item.name.toLowerCase().includes(filterValue)) { + return true; + } + + return false; + } + + getColumns(): HTTableColumn[] { + const tableColumns = [ + { + name: PreprocessorsTableColumnLabel.ID, + dataKey: '_id', + isSortable: true, + export: async (preprocessor: Preprocessor) => preprocessor._id + '' + }, + { + name: PreprocessorsTableColumnLabel.NAME, + dataKey: 'name', + isSortable: true, + export: async (preprocessor: Preprocessor) => preprocessor.name + } + ]; + + return tableColumns; + } + + openDialog(data: DialogData) { + const dialogRef = this.dialog.open(TableDialogComponent, { + data: data, + width: '450px' + }); + + this.subscriptions.push( + dialogRef.afterClosed().subscribe((result) => { + if (result && result.action) { + switch (result.action) { + case RowActionMenuAction.DELETE: + this.rowActionDelete(result.data); + break; + case BulkActionMenuAction.DELETE: + this.bulkActionDelete(result.data); + break; + } + } + }) + ); + } + + // --- Action functions --- + + exportActionClicked(event: ActionMenuEvent): void { + switch (event.menuItem.action) { + case ExportMenuAction.EXCEL: + this.exportService.toExcel( + 'hashtopolis-preprocessors', + this.tableColumns, + event.data + ); + break; + case ExportMenuAction.CSV: + this.exportService.toCsv( + 'hashtopolis-preprocessors', + this.tableColumns, + event.data + ); + break; + case ExportMenuAction.COPY: + this.exportService + .toClipboard(this.tableColumns, event.data) + .then(() => { + this.snackBar.open( + 'The selected rows are copied to the clipboard', + 'Close' + ); + }); + break; + } + } + + rowActionClicked(event: ActionMenuEvent): void { + switch (event.menuItem.action) { + case RowActionMenuAction.DELETE: + this.openDialog({ + rows: [event.data], + title: `Deleting preprocessor ${event.data.name} ...`, + icon: 'warning', + body: `Are you sure you want to delete it? Note that this action cannot be undone.`, + warn: true, + action: event.menuItem.action + }); + break; + case RowActionMenuAction.EDIT: + this.rowActionEdit(event.data); + break; + } + } + + bulkActionClicked(event: ActionMenuEvent): void { + switch (event.menuItem.action) { + case BulkActionMenuAction.DELETE: + this.openDialog({ + rows: event.data, + title: `Deleting ${event.data.length} preprocessors ...`, + icon: 'warning', + body: `Are you sure you want to delete the above preprocessors? Note that this action cannot be undone.`, + warn: true, + listAttribute: 'name', + action: event.menuItem.action + }); + break; + } + } + + /** + * @todo Implement error handling. + */ + private bulkActionDelete(preprocessors: Preprocessor[]): void { + const requests = preprocessors.map((preprocessor: Preprocessor) => { + return this.gs.delete(SERV.CRACKERS_TYPES, preprocessor._id); + }); + + this.subscriptions.push( + forkJoin(requests) + .pipe( + catchError((error) => { + console.error('Error during deletion:', error); + return []; + }) + ) + .subscribe((results) => { + this.snackBar.open( + `Successfully deleted ${results.length} preprocessors!`, + 'Close' + ); + this.reload(); + }) + ); + } + + /** + * @todo Implement error handling. + */ + private rowActionDelete(preprocessors: Preprocessor[]): void { + this.subscriptions.push( + this.gs + .delete(SERV.CRACKERS_TYPES, preprocessors[0]._id) + .pipe( + catchError((error) => { + console.error('Error during deletion:', error); + return []; + }) + ) + .subscribe(() => { + this.snackBar.open('Successfully deleted preprocessor!', 'Close'); + this.reload(); + }) + ); + } + + private rowActionEdit(preprocessor: Preprocessor): void { + this.router.navigate([ + '/config', + 'engine', + 'preprocessors', + preprocessor._id, + 'edit' + ]); + } +} diff --git a/src/app/core/_components/tables/preprocessors-table/preprocessors-table.constants.ts b/src/app/core/_components/tables/preprocessors-table/preprocessors-table.constants.ts new file mode 100644 index 00000000..8a064c90 --- /dev/null +++ b/src/app/core/_components/tables/preprocessors-table/preprocessors-table.constants.ts @@ -0,0 +1,4 @@ +export const PreprocessorsTableColumnLabel = { + ID: 'ID', + NAME: 'Name' +}; diff --git a/src/app/core/_datasources/preprocessors.datasource.ts b/src/app/core/_datasources/preprocessors.datasource.ts new file mode 100644 index 00000000..51b8ed75 --- /dev/null +++ b/src/app/core/_datasources/preprocessors.datasource.ts @@ -0,0 +1,41 @@ +import { catchError, finalize, of } from 'rxjs'; + +import { BaseDataSource } from './base.datasource'; +import { ListResponseWrapper } from '../_models/response.model'; +import { Preprocessor } from '../_models/preprocessor.model'; +import { SERV } from '../_services/main.config'; + +export class PreprocessorsDataSource extends BaseDataSource { + loadAll(): void { + this.loading = true; + + const params = { + maxResults: this.pageSize + }; + + const preprocessors$ = this.service.getAll(SERV.PREPROCESSORS, params); + + this.subscriptions.push( + preprocessors$ + .pipe( + catchError(() => of([])), + finalize(() => (this.loading = false)) + ) + .subscribe((response: ListResponseWrapper) => { + const preprocessors: Preprocessor[] = response.values; + + this.setPaginationConfig( + this.pageSize, + this.currentPage, + response.total + ); + this.setData(preprocessors); + }) + ); + } + + reload(): void { + this.reset(); + this.loadAll(); + } +} diff --git a/src/app/core/_models/config-ui.model.ts b/src/app/core/_models/config-ui.model.ts index 2a49e18b..2a4d1928 100644 --- a/src/app/core/_models/config-ui.model.ts +++ b/src/app/core/_models/config-ui.model.ts @@ -4,6 +4,7 @@ import { CrackersTableColumnLabel } from '../_components/tables/crackers-table/c import { FilesTableColumnLabel } from '../_components/tables/files-table/files-table.constants'; import { HashlistsTableColumnLabel } from '../_components/tables/hashlists-table/hashlists-table.constants'; import { HashtypesTableColumnLabel } from '../_components/tables/hashtypes-table/hashtypes-table.constants'; +import { PreprocessorsTableColumnLabel } from '../_components/tables/preprocessors-table/preprocessors-table.constants'; import { SuperHashlistsTableColumnLabel } from '../_components/tables/super-hashlists-table/super-hashlists-table.constants'; export type Layout = 'full' | 'fixed'; @@ -80,6 +81,10 @@ export const uiConfigDefault: UIConfig = { CrackersTableColumnLabel.ID, CrackersTableColumnLabel.NAME, CrackersTableColumnLabel.VERSIONS + ], + preprocessorsTable: [ + PreprocessorsTableColumnLabel.ID, + PreprocessorsTableColumnLabel.NAME ] }, refreshPage: false, diff --git a/src/app/core/_models/preprocessor.model.ts b/src/app/core/_models/preprocessor.model.ts index 3f612173..ae8cd67e 100644 --- a/src/app/core/_models/preprocessor.model.ts +++ b/src/app/core/_models/preprocessor.model.ts @@ -1,9 +1,11 @@ export interface Preprocessor { - preprocessorId: number - name: string - url: string - binaryName: string - keyspaceCommand: string - skipCommand: string - limitCommand: string + _id: number; + _self: string; + preprocessorId: number; + name: string; + url: string; + binaryName: string; + keyspaceCommand: string; + skipCommand: string; + limitCommand: string; } From 8238b1a9e71e467ea33eda41607ec432164a2ebb Mon Sep 17 00:00:00 2001 From: itbf9 Date: Wed, 15 Nov 2023 16:23:04 +0100 Subject: [PATCH 271/419] Add agent binaries table --- .../agent-binaries.component.html | 43 +--- .../agent-binaries.component.ts | 158 +----------- .../_components/core-components.module.ts | 7 +- .../menus/base-menu/base-menu.component.ts | 8 + .../bulk-action-menu.component.ts | 2 + .../bulk-action-menu.constants.ts | 1 + .../row-action-menu.component.ts | 5 + .../row-action-menu.constants.ts | 2 + .../agent-binaries-table.component.html | 15 ++ .../agent-binaries-table.component.ts | 239 ++++++++++++++++++ .../agent-binaries-table.constants.ts | 8 + .../tables/ht-table/ht-table.models.ts | 1 + .../_datasources/agent-binaries.datasource.ts | 41 +++ src/app/core/_models/agent-binary.model.ts | 11 + src/app/core/_models/config-ui.model.ts | 9 + 15 files changed, 359 insertions(+), 191 deletions(-) create mode 100644 src/app/core/_components/tables/agent-binaries-table/agent-binaries-table.component.html create mode 100644 src/app/core/_components/tables/agent-binaries-table/agent-binaries-table.component.ts create mode 100644 src/app/core/_components/tables/agent-binaries-table/agent-binaries-table.constants.ts create mode 100644 src/app/core/_datasources/agent-binaries.datasource.ts create mode 100644 src/app/core/_models/agent-binary.model.ts diff --git a/src/app/config/engine/agent-binaries/agent-binaries.component.html b/src/app/config/engine/agent-binaries/agent-binaries.component.html index f1802c0b..5e688f71 100644 --- a/src/app/config/engine/agent-binaries/agent-binaries.component.html +++ b/src/app/config/engine/agent-binaries/agent-binaries.component.html @@ -1,38 +1,9 @@ - - - - - - - - - - - - - - - - - - - - - - - - - -
IDTypeOSFilenameCurrent VersionUpdate TrackActions
{{ b.agentBinaryId }}{{ b.type }}{{ b.operatingSystems }}{{ b.filename }}{{ b.version }}{{ b.updateTrack }} - - - - - -
+ +
diff --git a/src/app/config/engine/agent-binaries/agent-binaries.component.ts b/src/app/config/engine/agent-binaries/agent-binaries.component.ts index 6ca7218e..44854b19 100644 --- a/src/app/config/engine/agent-binaries/agent-binaries.component.ts +++ b/src/app/config/engine/agent-binaries/agent-binaries.component.ts @@ -1,160 +1,12 @@ -import { faHomeAlt, faPlus, faTrash, faEdit } from '@fortawesome/free-solid-svg-icons'; -import { Component, OnInit, ViewChild } from '@angular/core'; -import { DataTableDirective } from 'angular-datatables'; -import Swal from 'sweetalert2/dist/sweetalert2.js'; -import { Subject } from 'rxjs'; - -import { AlertService } from 'src/app/core/_services/shared/alert.service'; -import { environment } from './../../../../environments/environment'; -import { GlobalService } from 'src/app/core/_services/main.service'; -import { PageTitle } from 'src/app/core/_decorators/autotitle'; -import { SERV } from '../../../core/_services/main.config'; +import { AutoTitleService } from 'src/app/core/_services/shared/autotitle.service'; +import { Component } from '@angular/core'; @Component({ selector: 'app-agent-binaries', templateUrl: './agent-binaries.component.html' }) -@PageTitle(['Show Agent Binaries']) -export class AgentBinariesComponent implements OnInit { - - public isCollapsed = true; - faHome=faHomeAlt; - faPlus=faPlus; - faTrash=faTrash; - faEdit=faEdit; - - @ViewChild(DataTableDirective, {static: false}) - dtElement: DataTableDirective; - - dtTrigger: Subject = new Subject(); - dtOptions: any = {}; - - public binaries: {agentBinaryId: number, type: string, version: string, operatingSystems: string, filename: string, updateTrack: string, updateAvailable: string}[] = []; - - private maxResults = environment.config.prodApiMaxResults; - - constructor( - private alert: AlertService, - private gs: GlobalService, - ) { } - - ngOnInit(): void { - - const params = {'maxResults': this.maxResults} - this.gs.getAll(SERV.AGENT_BINARY,params).subscribe((bin: any) => { - this.binaries = bin.values; - this.dtTrigger.next(void 0); - }); - const self = this; - this.dtOptions = { - dom: 'Bfrtip', - scrollX: true, - pageLength: 25, - lengthMenu: [ - [10, 25, 50, 100, 250, -1], - [10, 25, 50, 100, 250, 'All'] - ], - stateSave: true, - select: true, - buttons: { - dom: { - button: { - className: 'dt-button buttons-collection btn btn-sm-dt btn-outline-gray-600-dt', - } - }, - buttons: [ - { - text: '↻', - autoClose: true, - action: function (e, dt, node, config) { - self.onRefresh(); - } - }, - { - extend: 'collection', - text: 'Export', - buttons: [ - { - extend: 'excelHtml5', - exportOptions: { - columns: [0, 1] - }, - }, - { - extend: 'print', - exportOptions: { - columns: [0, 1] - }, - customize: function ( win ) { - $(win.document.body) - .css( 'font-size', '10pt' ) - $(win.document.body).find( 'table' ) - .addClass( 'compact' ) - .css( 'font-size', 'inherit' ); - } - }, - { - extend: 'csvHtml5', - exportOptions: {modifier: {selected: true}}, - select: true, - customize: function (dt, csv) { - let data = ""; - for (let i = 0; i < dt.length; i++) { - data = "Agent Binaries\n\n"+ dt; - } - return data; - } - }, - 'copy' - ] - } - ], - } - }; - +export class AgentBinariesComponent { + constructor(private titleService: AutoTitleService) { + this.titleService.set(['Show Agent Binaries']); } - - onRefresh(){ - this.rerender(); - this.ngOnInit(); - } - - onSubmit(){ - this.alert.okAlert('New Binary created!',''); - } - - rerender(): void { - this.dtElement.dtInstance.then((dtInstance: DataTables.Api) => { - // Destroy the table first - dtInstance.destroy(); - // Call the dtTrigger to rerender again - setTimeout(() => { - this.dtTrigger['new'].next(); - }); - }); - } - - onDelete(id: number, name: string){ - this.alert.deleteConfirmation(name,'Binaries').then((confirmed) => { - if (confirmed) { - // Deletion - this.gs.delete(SERV.AGENT_BINARY, id).subscribe(() => { - // Successful deletion - this.alert.okAlert(`Deleted Binary ${name}`, ''); - this.onRefreshTable(); // Refresh the table - }); - } else { - // Handle cancellation - this.alert.okAlert(`Binary ${name} is safe!`,''); - } - }); - } - - onRefreshTable(){ - setTimeout(() => { - this.ngOnInit(); - this.rerender(); // rerender datatables - },2000); - } - } diff --git a/src/app/core/_components/core-components.module.ts b/src/app/core/_components/core-components.module.ts index 7313ff7c..426e1162 100644 --- a/src/app/core/_components/core-components.module.ts +++ b/src/app/core/_components/core-components.module.ts @@ -5,6 +5,7 @@ import { } from '@angular/material/snack-bar'; import { ActionMenuComponent } from './menus/action-menu/action-menu.component'; +import { AgentBinariesTableComponent } from './tables/agent-binaries-table/agent-binaries-table.component'; import { AgentsTableComponent } from './tables/agents-table/agents-table.component'; import { BaseMenuComponent } from './menus/base-menu/base-menu.component'; import { BaseTableComponent } from './tables/base-table/base-table.component'; @@ -59,7 +60,8 @@ import { TableDialogComponent } from './tables/table-dialog/table-dialog.compone SuperHashlistsTableComponent, FilesTableComponent, CrackersTableComponent, - PreprocessorsTableComponent + PreprocessorsTableComponent, + AgentBinariesTableComponent ], imports: [ ReactiveFormsModule, @@ -101,7 +103,8 @@ import { TableDialogComponent } from './tables/table-dialog/table-dialog.compone SuperHashlistsTableComponent, FilesTableComponent, CrackersTableComponent, - PreprocessorsTableComponent + PreprocessorsTableComponent, + AgentBinariesTableComponent ], providers: [ { diff --git a/src/app/core/_components/menus/base-menu/base-menu.component.ts b/src/app/core/_components/menus/base-menu/base-menu.component.ts index a8805f7b..335edb45 100644 --- a/src/app/core/_components/menus/base-menu/base-menu.component.ts +++ b/src/app/core/_components/menus/base-menu/base-menu.component.ts @@ -36,6 +36,14 @@ export class BaseMenuComponent { return this.checkId('agentId'); } + /** + * Check if the data row is of type "AgentBinary". + * @returns `true` if the data row is an agent binary; otherwise, `false`. + */ + protected isAgentBinary(): boolean { + return this.checkId('agentBinaryId'); + } + /** * Check if the data row is of type "Preprocessor". * @returns `true` if the data row is an preprocessor; otherwise, `false`. diff --git a/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.component.ts b/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.component.ts index 62db27cf..c6f0287e 100644 --- a/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.component.ts +++ b/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.component.ts @@ -31,6 +31,8 @@ export class BulkActionMenuComponent this.getHashlistMenu(); } else if (this.dataType === 'hashtypes') { this.getDeleteMenu(BulkActionMenuLabel.DELETE_HASHTYPES); + } else if (this.dataType === 'agent-binaries') { + this.getDeleteMenu(BulkActionMenuLabel.DELETE_AGENTBINARIES); } else if (this.dataType === 'files') { this.getDeleteMenu(BulkActionMenuLabel.DELETE_FILES); } else if (this.dataType === 'crackers') { diff --git a/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.constants.ts b/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.constants.ts index 73d3c9ac..394e8898 100644 --- a/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.constants.ts +++ b/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.constants.ts @@ -6,6 +6,7 @@ export const BulkActionMenuLabel = { DELETE_FILES: 'Delete Files', DELETE_CRACKERS: 'Delete Crackers', DELETE_PREPROCESSORS: 'Delete Preprocessors', + DELETE_AGENTBINARIES: 'Delete Agent Binaries', ACTIVATE_AGENTS: 'Activate Agents', DEACTIVATE_AGENTS: 'Deactivate Agents', ARCHIVE_TASKS: 'Archive Tasks', diff --git a/src/app/core/_components/menus/row-action-menu/row-action-menu.component.ts b/src/app/core/_components/menus/row-action-menu/row-action-menu.component.ts index 6509e741..d3e9786f 100644 --- a/src/app/core/_components/menus/row-action-menu/row-action-menu.component.ts +++ b/src/app/core/_components/menus/row-action-menu/row-action-menu.component.ts @@ -27,6 +27,11 @@ export class RowActionMenuComponent RowActionMenuLabel.EDIT_SUPERHASHLIST, RowActionMenuLabel.DELETE_SUPERHASHLIST ); + } else if (this.isAgentBinary()) { + this.getEditDeleteMenu( + RowActionMenuLabel.EDIT_AGENTBINARY, + RowActionMenuLabel.DELETE_AGENTBINARY + ); } else if (this.isFile()) { this.getEditDeleteMenu( RowActionMenuLabel.EDIT_FILE, 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 891e0418..00bc72b4 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 @@ -6,6 +6,7 @@ export const RowActionMenuLabel = { EDIT_SUPERHASHLIST: 'Edit Superhashlist', EDIT_FILE: 'Edit File', EDIT_PREPROCESSOR: 'Edit Preprocessor', + EDIT_AGENTBINARY: 'Edit Agent Binary', DELETE_AGENT: 'Delete Agent', DELETE_CRACKER: 'Delete Cracker', DELETE_TASK: 'Delete Task', @@ -14,6 +15,7 @@ export const RowActionMenuLabel = { DELETE_SUPERHASHLIST: 'Delete Superhashlist', DELETE_FILE: 'Delete File', DELETE_PREPROCESSOR: 'Delete Preprocessor', + DELETE_AGENTBINARY: 'Delete Agent Binary', COPY_TO_TASK: 'Copy to Task', COPY_TO_PRETASK: 'Copy to Pretask', ARCHIVE_TASK: 'Archive Task', diff --git a/src/app/core/_components/tables/agent-binaries-table/agent-binaries-table.component.html b/src/app/core/_components/tables/agent-binaries-table/agent-binaries-table.component.html new file mode 100644 index 00000000..e6452f4c --- /dev/null +++ b/src/app/core/_components/tables/agent-binaries-table/agent-binaries-table.component.html @@ -0,0 +1,15 @@ + diff --git a/src/app/core/_components/tables/agent-binaries-table/agent-binaries-table.component.ts b/src/app/core/_components/tables/agent-binaries-table/agent-binaries-table.component.ts new file mode 100644 index 00000000..197de3d3 --- /dev/null +++ b/src/app/core/_components/tables/agent-binaries-table/agent-binaries-table.component.ts @@ -0,0 +1,239 @@ +/* eslint-disable @angular-eslint/component-selector */ +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { catchError, forkJoin } from 'rxjs'; + +import { ActionMenuEvent } from '../../menus/action-menu/action-menu.model'; +import { AgentBinariesDataSource } from 'src/app/core/_datasources/agent-binaries.datasource'; +import { AgentBinariesTableColumnLabel } from './agent-binaries-table.constants'; +import { AgentBinary } from 'src/app/core/_models/agent-binary.model'; +import { BaseTableComponent } from '../base-table/base-table.component'; +import { BulkActionMenuAction } from '../../menus/bulk-action-menu/bulk-action-menu.constants'; +import { DialogData } from '../table-dialog/table-dialog.model'; +import { ExportMenuAction } from '../../menus/export-menu/export-menu.constants'; +import { HTTableColumn } from '../ht-table/ht-table.models'; +import { RowActionMenuAction } from '../../menus/row-action-menu/row-action-menu.constants'; +import { SERV } from 'src/app/core/_services/main.config'; +import { TableDialogComponent } from '../table-dialog/table-dialog.component'; + +@Component({ + selector: 'agent-binaries-table', + templateUrl: './agent-binaries-table.component.html' +}) +export class AgentBinariesTableComponent + extends BaseTableComponent + implements OnInit, OnDestroy +{ + tableColumns: HTTableColumn[] = []; + dataSource: AgentBinariesDataSource; + + ngOnInit(): void { + this.tableColumns = this.getColumns(); + this.dataSource = new AgentBinariesDataSource( + this.cdr, + this.gs, + this.uiService + ); + this.dataSource.setColumns(this.tableColumns); + this.dataSource.loadAll(); + } + + ngOnDestroy(): void { + for (const sub of this.subscriptions) { + sub.unsubscribe(); + } + } + + filter(item: AgentBinary, filterValue: string): boolean { + if (item.filename.toLowerCase().includes(filterValue)) { + return true; + } + + return false; + } + + getColumns(): HTTableColumn[] { + const tableColumns = [ + { + name: AgentBinariesTableColumnLabel.ID, + dataKey: '_id', + isSortable: true, + export: async (agentBinary: AgentBinary) => agentBinary._id + '' + }, + { + name: AgentBinariesTableColumnLabel.TYPE, + dataKey: 'type', + isSortable: true, + export: async (agentBinary: AgentBinary) => agentBinary.type + }, + { + name: AgentBinariesTableColumnLabel.OS, + dataKey: 'operatingSystems', + isSortable: true, + export: async (agentBinary: AgentBinary) => agentBinary.operatingSystems + }, + { + name: AgentBinariesTableColumnLabel.FILENAME, + dataKey: 'filename', + isSortable: true, + export: async (agentBinary: AgentBinary) => agentBinary.filename + }, + { + name: AgentBinariesTableColumnLabel.VERSION, + dataKey: 'version', + isSortable: true, + export: async (agentBinary: AgentBinary) => agentBinary.version + }, + { + name: AgentBinariesTableColumnLabel.UPDATE_TRACK, + dataKey: 'updateTrack', + isSortable: true, + export: async (agentBinary: AgentBinary) => agentBinary.updateTrack + } + ]; + + return tableColumns; + } + + openDialog(data: DialogData) { + const dialogRef = this.dialog.open(TableDialogComponent, { + data: data, + width: '450px' + }); + + this.subscriptions.push( + dialogRef.afterClosed().subscribe((result) => { + if (result && result.action) { + switch (result.action) { + case RowActionMenuAction.DELETE: + this.rowActionDelete(result.data); + break; + case BulkActionMenuAction.DELETE: + this.bulkActionDelete(result.data); + break; + } + } + }) + ); + } + + // --- Action functions --- + + exportActionClicked(event: ActionMenuEvent): void { + switch (event.menuItem.action) { + case ExportMenuAction.EXCEL: + this.exportService.toExcel( + 'hashtopolis-agent-binaries', + this.tableColumns, + event.data + ); + break; + case ExportMenuAction.CSV: + this.exportService.toCsv( + 'hashtopolis-agent-binaries', + this.tableColumns, + event.data + ); + break; + case ExportMenuAction.COPY: + this.exportService + .toClipboard(this.tableColumns, event.data) + .then(() => { + this.snackBar.open( + 'The selected rows are copied to the clipboard', + 'Close' + ); + }); + break; + } + } + + rowActionClicked(event: ActionMenuEvent): void { + switch (event.menuItem.action) { + case RowActionMenuAction.DELETE: + this.openDialog({ + rows: [event.data], + title: `Deleting agentBinary ${event.data.filename} ...`, + icon: 'warning', + body: `Are you sure you want to delete it? Note that this action cannot be undone.`, + warn: true, + action: event.menuItem.action + }); + break; + case RowActionMenuAction.EDIT: + this.rowActionEdit(event.data); + break; + } + } + + bulkActionClicked(event: ActionMenuEvent): void { + switch (event.menuItem.action) { + case BulkActionMenuAction.DELETE: + this.openDialog({ + rows: event.data, + title: `Deleting ${event.data.length} agent binaries ...`, + icon: 'warning', + body: `Are you sure you want to delete the above agent binaries? Note that this action cannot be undone.`, + warn: true, + listAttribute: 'filename', + action: event.menuItem.action + }); + break; + } + } + + /** + * @todo Implement error handling. + */ + private bulkActionDelete(agentBinaries: AgentBinary[]): void { + const requests = agentBinaries.map((agentBinary: AgentBinary) => { + return this.gs.delete(SERV.AGENT_BINARY, agentBinary._id); + }); + + this.subscriptions.push( + forkJoin(requests) + .pipe( + catchError((error) => { + console.error('Error during deletion:', error); + return []; + }) + ) + .subscribe((results) => { + this.snackBar.open( + `Successfully deleted ${results.length} agentBinaries!`, + 'Close' + ); + this.reload(); + }) + ); + } + + /** + * @todo Implement error handling. + */ + private rowActionDelete(agentBinaries: AgentBinary[]): void { + this.subscriptions.push( + this.gs + .delete(SERV.AGENT_BINARY, agentBinaries[0]._id) + .pipe( + catchError((error) => { + console.error('Error during deletion:', error); + return []; + }) + ) + .subscribe(() => { + this.snackBar.open('Successfully deleted agent binary!', 'Close'); + this.reload(); + }) + ); + } + + private rowActionEdit(agentBinary: AgentBinary): void { + this.router.navigate([ + '/config', + 'engine', + 'agent-binaries', + agentBinary._id, + 'edit' + ]); + } +} diff --git a/src/app/core/_components/tables/agent-binaries-table/agent-binaries-table.constants.ts b/src/app/core/_components/tables/agent-binaries-table/agent-binaries-table.constants.ts new file mode 100644 index 00000000..02208a02 --- /dev/null +++ b/src/app/core/_components/tables/agent-binaries-table/agent-binaries-table.constants.ts @@ -0,0 +1,8 @@ +export const AgentBinariesTableColumnLabel = { + ID: 'ID', + TYPE: 'Type', + OS: 'OS', + FILENAME: 'Filename', + VERSION: 'Current Version', + UPDATE_TRACK: 'Update Track' +}; diff --git a/src/app/core/_components/tables/ht-table/ht-table.models.ts b/src/app/core/_components/tables/ht-table/ht-table.models.ts index ff0cda55..ffa7ed4d 100644 --- a/src/app/core/_components/tables/ht-table/ht-table.models.ts +++ b/src/app/core/_components/tables/ht-table/ht-table.models.ts @@ -9,6 +9,7 @@ export type DataType = | 'files' | 'crackers' | 'preprocessors' + | 'agent-binaries' | 'superhashlists'; export interface HTTableIcon { diff --git a/src/app/core/_datasources/agent-binaries.datasource.ts b/src/app/core/_datasources/agent-binaries.datasource.ts new file mode 100644 index 00000000..17ad1231 --- /dev/null +++ b/src/app/core/_datasources/agent-binaries.datasource.ts @@ -0,0 +1,41 @@ +import { catchError, finalize, of } from 'rxjs'; + +import { AgentBinary } from '../_models/agent-binary.model'; +import { BaseDataSource } from './base.datasource'; +import { ListResponseWrapper } from '../_models/response.model'; +import { SERV } from '../_services/main.config'; + +export class AgentBinariesDataSource extends BaseDataSource { + loadAll(): void { + this.loading = true; + + const params = { + maxResults: this.pageSize + }; + + const agentBinaries$ = this.service.getAll(SERV.AGENT_BINARY, params); + + this.subscriptions.push( + agentBinaries$ + .pipe( + catchError(() => of([])), + finalize(() => (this.loading = false)) + ) + .subscribe((response: ListResponseWrapper) => { + const agentBinaries: AgentBinary[] = response.values; + + this.setPaginationConfig( + this.pageSize, + this.currentPage, + response.total + ); + this.setData(agentBinaries); + }) + ); + } + + reload(): void { + this.reset(); + this.loadAll(); + } +} diff --git a/src/app/core/_models/agent-binary.model.ts b/src/app/core/_models/agent-binary.model.ts new file mode 100644 index 00000000..69fbe6da --- /dev/null +++ b/src/app/core/_models/agent-binary.model.ts @@ -0,0 +1,11 @@ +export interface AgentBinary { + _id: 1; + _self: string; + agentBinaryId: number; + filename: string; + operatingSystems: string; + type: string; + updateAvailable: string; + updateTrack: string; + version: string; +} diff --git a/src/app/core/_models/config-ui.model.ts b/src/app/core/_models/config-ui.model.ts index 2a4d1928..1279ac0a 100644 --- a/src/app/core/_models/config-ui.model.ts +++ b/src/app/core/_models/config-ui.model.ts @@ -1,3 +1,4 @@ +import { AgentBinariesTableColumnLabel } from '../_components/tables/agent-binaries-table/agent-binaries-table.constants'; import { AgentsTableColumnLabel } from '../_components/tables/agents-table/agents-table.constants'; import { ChunksTableColumnLabel } from '../_components/tables/chunks-table/chunks-table.constants'; import { CrackersTableColumnLabel } from '../_components/tables/crackers-table/crackers-table.constants'; @@ -85,6 +86,14 @@ export const uiConfigDefault: UIConfig = { preprocessorsTable: [ PreprocessorsTableColumnLabel.ID, PreprocessorsTableColumnLabel.NAME + ], + agentBinariesTable: [ + AgentBinariesTableColumnLabel.ID, + AgentBinariesTableColumnLabel.FILENAME, + AgentBinariesTableColumnLabel.OS, + AgentBinariesTableColumnLabel.TYPE, + AgentBinariesTableColumnLabel.UPDATE_TRACK, + AgentBinariesTableColumnLabel.VERSION ] }, refreshPage: false, From 8b1899f13682373eb7bc0bffb0e78176553e1d8f Mon Sep 17 00:00:00 2001 From: itbf9 Date: Thu, 16 Nov 2023 12:52:52 +0100 Subject: [PATCH 272/419] Add health checks table --- .../health-checks.component.html | 39 +-- .../health-checks/health-checks.component.ts | 188 +------------ .../_components/core-components.module.ts | 7 +- .../menus/base-menu/base-menu.component.ts | 26 ++ .../bulk-action-menu.component.ts | 144 ++++++---- .../bulk-action-menu.constants.ts | 8 + .../row-action-menu.component.ts | 253 ++++++++++-------- .../row-action-menu.constants.ts | 12 + .../health-checks-table.component.html | 15 ++ .../health-checks-table.component.ts | 244 +++++++++++++++++ .../health-checks-table.constants.ts | 14 + .../tables/ht-table/ht-table.models.ts | 1 + .../_datasources/health-checks.datasource.ts | 61 +++++ src/app/core/_models/config-ui.model.ts | 7 + src/app/core/_models/hashtype.model.ts | 2 + src/app/core/_models/health-check.model.ts | 31 +++ 16 files changed, 678 insertions(+), 374 deletions(-) create mode 100644 src/app/core/_components/tables/health-checks-table/health-checks-table.component.html create mode 100644 src/app/core/_components/tables/health-checks-table/health-checks-table.component.ts create mode 100644 src/app/core/_components/tables/health-checks-table/health-checks-table.constants.ts create mode 100644 src/app/core/_datasources/health-checks.datasource.ts create mode 100644 src/app/core/_models/health-check.model.ts diff --git a/src/app/config/health-checks/health-checks.component.html b/src/app/config/health-checks/health-checks.component.html index 8a21010e..61d08a0a 100644 --- a/src/app/config/health-checks/health-checks.component.html +++ b/src/app/config/health-checks/health-checks.component.html @@ -1,34 +1,9 @@ - - - - - - - - - - - - - - - - - - - - - -
IDCreatedTypeStatusActions
{{ h.healthCheckId }}{{ h.time | uiDate }}Brute-Force ({{ h.description }}){{ h.status | HCstatus | lowercase | titlecase }} - - - - - -
+ +
diff --git a/src/app/config/health-checks/health-checks.component.ts b/src/app/config/health-checks/health-checks.component.ts index 07e23cf4..c87e7645 100644 --- a/src/app/config/health-checks/health-checks.component.ts +++ b/src/app/config/health-checks/health-checks.component.ts @@ -1,188 +1,12 @@ -import { faHomeAlt, faPlus, faEdit, faTrash, faEyeDropper} from '@fortawesome/free-solid-svg-icons'; -import { Component, OnInit, ViewChild} from '@angular/core'; -import { DataTableDirective } from 'angular-datatables'; -import Swal from 'sweetalert2/dist/sweetalert2.js'; -import { ActivatedRoute } from '@angular/router'; -import { Router } from '@angular/router'; -import { Subject } from 'rxjs'; - -import { UIConfigService } from 'src/app/core/_services/shared/storage.service'; -import { AlertService } from 'src/app/core/_services/shared/alert.service'; -import { GlobalService } from 'src/app/core/_services/main.service'; -import { environment } from './../../../environments/environment'; -import { PageTitle } from 'src/app/core/_decorators/autotitle'; -import { SERV } from '../../core/_services/main.config'; +import { AutoTitleService } from 'src/app/core/_services/shared/autotitle.service'; +import { Component } from '@angular/core'; @Component({ selector: 'app-health-checks', - templateUrl: './health-checks.component.html', - // changeDetection: ChangeDetectionStrategy.OnPush + templateUrl: './health-checks.component.html' }) -@PageTitle(['Show Health Checks']) -export class HealthChecksComponent implements OnInit { - - // Form attributtes - faHome=faHomeAlt; - faPlus=faPlus; - faTrash=faTrash; - faEdit=faEdit; - faEyeDropper=faEyeDropper; - - @ViewChild(DataTableDirective, {static: false}) - dtElement: DataTableDirective; - - dtTrigger: Subject = new Subject(); - dtOptions: any = {}; - - constructor( - private uiService: UIConfigService, - private alert: AlertService, - private gs: GlobalService, - ) { } - - public healthc: { - attackCmd: string, - checkType: number, - crackerBinaryId: number, - expectedCracks: number, - hashtypeId: number, - hashtypeName: string, - healthCheckId: number, - status: number, - time: number - }[] = []; - - public htypes: { - hashTypeId: number, - description: string, - isSalted: number, - isSlowHash: number, - }[] = []; - - private maxResults = environment.config.prodApiMaxResults; - - public mergedObjects: any - - ngOnInit(): void { - - const params = {'maxResults': this.maxResults}; - - this.gs.getAll(SERV.HEALTH_CHECKS,params).subscribe((check: any) => { - this.gs.getAll(SERV.HASHTYPES,params).subscribe((hasht: any) => { - this.mergedObjects = check.values.map(mainObject => { - const matchObject = hasht.values.find(element => element.hashTypeId === mainObject.hashtypeId) - return { ...mainObject, ...matchObject } - }) - this.dtTrigger.next(void 0); - }); - }); - - const self = this; - this.dtOptions = { - dom: 'Bfrtip', - scrollX: true, - pageLength: 25, - stateSave: true, - select: true, - buttons: { - dom: { - button: { - className: 'dt-button buttons-collection btn btn-sm-dt btn-outline-gray-600-dt', - } - }, - buttons: [ - { - text: '↻', - autoClose: true, - action: function (e, dt, node, config) { - self.onRefresh(); - } - }, - { - extend: 'collection', - text: 'Export', - buttons: [ - { - extend: 'excelHtml5', - exportOptions: { - columns: [0, 1, 2, 3] - }, - }, - { - extend: 'print', - exportOptions: { - columns: [0, 1, 2, 3] - }, - customize: function ( win ) { - $(win.document.body) - .css( 'font-size', '10pt' ) - $(win.document.body).find( 'table' ) - .addClass( 'compact' ) - .css( 'font-size', 'inherit' ); - } - }, - { - extend: 'csvHtml5', - exportOptions: {modifier: {selected: true}}, - select: true, - customize: function (dt, csv) { - let data = ""; - for (let i = 0; i < dt.length; i++) { - data = "Health Checks\n\n"+ dt; - } - return data; - } - }, - 'copy' - ] - }, - { - extend: "pageLength", - className: "btn-sm" - } - ], - } - }; - - } - - onRefresh(){ - this.rerender(); - this.ngOnInit(); - } - - rerender(): void { - this.dtElement.dtInstance.then((dtInstance: DataTables.Api) => { - // Destroy the table first - dtInstance.destroy(); - // Call the dtTrigger to rerender again - setTimeout(() => { - this.dtTrigger['new'].next(); - }); - }); +export class HealthChecksComponent { + constructor(private titleService: AutoTitleService) { + this.titleService.set(['Show Health Checks']); } - - onDelete(id: number, name: string){ - this.alert.deleteConfirmation(name,'Health Checks').then((confirmed) => { - if (confirmed) { - // Deletion - this.gs.delete(SERV.HEALTH_CHECKS, id).subscribe(() => { - // Successful deletion - this.alert.okAlert(`Deleted Health Check ${name}`, ''); - this.onRefreshTable(); // Refresh the table - }); - } else { - // Handle cancellation - this.alert.okAlert(`Health Check ${name} is safe!`,''); - } - }); - } - - onRefreshTable(){ - setTimeout(() => { - this.ngOnInit(); - this.rerender(); // rerender datatables - },2000); - } - } diff --git a/src/app/core/_components/core-components.module.ts b/src/app/core/_components/core-components.module.ts index 426e1162..1f429e6d 100644 --- a/src/app/core/_components/core-components.module.ts +++ b/src/app/core/_components/core-components.module.ts @@ -19,6 +19,7 @@ import { FilesTableComponent } from './tables/files-table/files-table.component' import { HTTableComponent } from './tables/ht-table/ht-table.component'; import { HashlistsTableComponent } from './tables/hashlists-table/hashlists-table.component'; import { HashtypesTableComponent } from './tables/hashtypes-table/hashtypes-table.component'; +import { HealthChecksTableComponent } from './tables/health-checks-table/health-checks-table.component'; import { MatButtonModule } from '@angular/material/button'; import { MatCheckboxModule } from '@angular/material/checkbox'; import { MatDialogModule } from '@angular/material/dialog'; @@ -61,7 +62,8 @@ import { TableDialogComponent } from './tables/table-dialog/table-dialog.compone FilesTableComponent, CrackersTableComponent, PreprocessorsTableComponent, - AgentBinariesTableComponent + AgentBinariesTableComponent, + HealthChecksTableComponent ], imports: [ ReactiveFormsModule, @@ -104,7 +106,8 @@ import { TableDialogComponent } from './tables/table-dialog/table-dialog.compone FilesTableComponent, CrackersTableComponent, PreprocessorsTableComponent, - AgentBinariesTableComponent + AgentBinariesTableComponent, + HealthChecksTableComponent ], providers: [ { diff --git a/src/app/core/_components/menus/base-menu/base-menu.component.ts b/src/app/core/_components/menus/base-menu/base-menu.component.ts index 335edb45..4627aa00 100644 --- a/src/app/core/_components/menus/base-menu/base-menu.component.ts +++ b/src/app/core/_components/menus/base-menu/base-menu.component.ts @@ -68,6 +68,14 @@ export class BaseMenuComponent { return this.checkId('taskId'); } + /** + * Check if the data row is of type "HealthCheck". + * @returns `true` if the data row is a health check; otherwise, `false`. + */ + protected isHealthCheck(): boolean { + return this.checkId('healthCheckId'); + } + /** * Check if the data row is of type "File". * @returns `true` if the data row is a file; otherwise, `false`. @@ -110,6 +118,24 @@ export class BaseMenuComponent { return 'hashTypeId' in this.data; } + /** + * Sets action menu items at the specified index. + * @param index The index to set action menu items. + * @param items The array of ActionMenuItem to set. + */ + protected setActionMenuItems(index: number, items: ActionMenuItem[]): void { + this.actionMenuItems[index] = items; + } + + /** + * Adds an action menu item at the specified index. + * @param index The index to add the action menu item. + * @param item The ActionMenuItem to add. + */ + protected addActionMenuItem(index: number, item: ActionMenuItem): void { + this.actionMenuItems[index].push(item); + } + onMenuItemClick(event: ActionMenuEvent): void { this.menuItemClicked.emit(event); } diff --git a/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.component.ts b/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.component.ts index c6f0287e..05596e09 100644 --- a/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.component.ts +++ b/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.component.ts @@ -1,5 +1,6 @@ import { BulkActionMenuAction, + BulkActionMenuIcon, BulkActionMenuLabel } from './bulk-action-menu.constants'; /* eslint-disable @angular-eslint/component-selector */ @@ -9,6 +10,9 @@ import { ActionMenuItem } from '../action-menu/action-menu.model'; import { BaseMenuComponent } from '../base-menu/base-menu.component'; import { DataType } from '../../tables/ht-table/ht-table.models'; +/** + * Component representing the bulk action menu for various data types. + */ @Component({ selector: 'bulk-action-menu', templateUrl: './bulk-action-menu.component.html' @@ -17,90 +21,128 @@ export class BulkActionMenuComponent extends BaseMenuComponent implements OnInit { + /** The type of data for which the bulk action menu is displayed. */ @Input() dataType: DataType; + /** Flag indicating whether the data is archived. */ @Input() isArchived: boolean; ngOnInit(): void { this.loadMenu(); } + /** + * Loads the appropriate menu based on the data type. + */ private loadMenu(): void { if (this.dataType === 'agents') { - this.getAgentMenu(); + this.setAgentMenu(); } else if (this.dataType === 'hashlists') { - this.getHashlistMenu(); + this.setHashlistMenu(); } else if (this.dataType === 'hashtypes') { - this.getDeleteMenu(BulkActionMenuLabel.DELETE_HASHTYPES); + this.setDeleteMenu(BulkActionMenuLabel.DELETE_HASHTYPES); } else if (this.dataType === 'agent-binaries') { - this.getDeleteMenu(BulkActionMenuLabel.DELETE_AGENTBINARIES); + this.setDeleteMenu(BulkActionMenuLabel.DELETE_AGENTBINARIES); } else if (this.dataType === 'files') { - this.getDeleteMenu(BulkActionMenuLabel.DELETE_FILES); + this.setDeleteMenu(BulkActionMenuLabel.DELETE_FILES); } else if (this.dataType === 'crackers') { - this.getDeleteMenu(BulkActionMenuLabel.DELETE_CRACKERS); + this.setDeleteMenu(BulkActionMenuLabel.DELETE_CRACKERS); } else if (this.dataType === 'preprocessors') { - this.getDeleteMenu(BulkActionMenuLabel.DELETE_PREPROCESSORS); + this.setDeleteMenu(BulkActionMenuLabel.DELETE_PREPROCESSORS); + } else if (this.dataType === 'health-checks') { + this.setDeleteMenu(BulkActionMenuLabel.DELETE_HEALTHCHECKS); } } - private getHashlistMenu(): void { - const deleteMenuAction: ActionMenuItem = { - label: BulkActionMenuLabel.DELETE_HASHLISTS, - action: BulkActionMenuAction.DELETE, - icon: 'delete', - red: true - }; - + /** + * Sets the context menu items for a hashlist data type. + */ + private setHashlistMenu(): void { if (this.isArchived) { - this.actionMenuItems[0] = [deleteMenuAction]; + this.setActionMenuItems(0, [ + this.getDeleteMenuItem(BulkActionMenuLabel.DELETE_HASHLISTS) + ]); } else { - this.actionMenuItems[0] = [ - { - label: BulkActionMenuLabel.ARCHIVE_HASHLISTS, - action: BulkActionMenuAction.ARCHIVE, - icon: 'archive' - } - ]; - this.actionMenuItems[1] = [deleteMenuAction]; + this.setActionMenuItems(0, [ + this.getArchiveMenuItem(BulkActionMenuLabel.ARCHIVE_HASHLISTS) + ]); + this.setActionMenuItems(1, [ + this.getDeleteMenuItem(BulkActionMenuLabel.DELETE_HASHLISTS) + ]); } } /** - * Generates a bulk menu with only a delete option. - * @param label Delete action label + * Sets the bulk menu items for a data type with only a delete option. + * @param label Delete action label. + */ + private setDeleteMenu(label: string): void { + this.setActionMenuItems(0, [this.getDeleteMenuItem(label)]); + } + + /** + * Sets the bulk menu items for an agent data type. + */ + private setAgentMenu(): void { + this.setActionMenuItems(0, [ + this.getActivateMenuItem(BulkActionMenuLabel.ACTIVATE_AGENTS), + this.getDeactivateMenuItem(BulkActionMenuLabel.DEACTIVATE_AGENTS) + ]); + this.setActionMenuItems(1, [ + this.getDeleteMenuItem(BulkActionMenuLabel.DELETE_AGENTS) + ]); + } + + /** + * Creates an ActionMenuItem with bulk delete action. + * @param label The label for the menu item. + * @returns The ActionMenuItem with bulk delete action. */ - private getDeleteMenu(label: string): void { - const deleteMenuAction: ActionMenuItem = { + private getDeleteMenuItem(label: string): ActionMenuItem { + return { label: label, action: BulkActionMenuAction.DELETE, - icon: 'delete', + icon: BulkActionMenuIcon.DELETE, red: true }; + } - this.actionMenuItems[0] = [deleteMenuAction]; + /** + * Creates an ActionMenuItem with bulk archive action. + * @param label The label for the menu item. + * @returns The ActionMenuItem with bulk archive action. + */ + private getArchiveMenuItem(label: string): ActionMenuItem { + return { + label: label, + action: BulkActionMenuAction.ARCHIVE, + icon: BulkActionMenuIcon.ARCHIVE + }; } - private getAgentMenu(): void { - this.actionMenuItems[0] = [ - { - label: BulkActionMenuLabel.ACTIVATE_AGENTS, - action: BulkActionMenuAction.ACTIVATE, - icon: 'radio_button_checked' - }, - { - label: BulkActionMenuLabel.DEACTIVATE_AGENTS, - action: BulkActionMenuAction.DEACTIVATE, - icon: 'radio_button_unchecked' - } - ]; + /** + * Creates an ActionMenuItem with bulk activate action. + * @param label The label for the menu item. + * @returns The ActionMenuItem with bulk activate action. + */ + private getActivateMenuItem(label: string): ActionMenuItem { + return { + label: label, + action: BulkActionMenuAction.ACTIVATE, + icon: BulkActionMenuIcon.ACTIVATE + }; + } - this.actionMenuItems[1] = [ - { - label: BulkActionMenuLabel.DELETE_AGENTS, - action: BulkActionMenuAction.DELETE, - icon: 'delete', - red: true - } - ]; + /** + * Creates an ActionMenuItem with bulk deactivate action. + * @param label The label for the menu item. + * @returns The ActionMenuItem with bulk deactivate action. + */ + private getDeactivateMenuItem(label: string): ActionMenuItem { + return { + label: label, + action: BulkActionMenuAction.DEACTIVATE, + icon: BulkActionMenuIcon.DEACTIVATE + }; } reload(): void { diff --git a/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.constants.ts b/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.constants.ts index 394e8898..ba91f8b4 100644 --- a/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.constants.ts +++ b/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.constants.ts @@ -7,6 +7,7 @@ export const BulkActionMenuLabel = { DELETE_CRACKERS: 'Delete Crackers', DELETE_PREPROCESSORS: 'Delete Preprocessors', DELETE_AGENTBINARIES: 'Delete Agent Binaries', + DELETE_HEALTHCHECKS: 'Delete Health Checks', ACTIVATE_AGENTS: 'Activate Agents', DEACTIVATE_AGENTS: 'Deactivate Agents', ARCHIVE_TASKS: 'Archive Tasks', @@ -19,3 +20,10 @@ export const BulkActionMenuAction = { DEACTIVATE: 'bulk-deactivate', ARCHIVE: 'bulk-archive' }; + +export const BulkActionMenuIcon = { + DELETE: 'delete', + ARCHIVE: 'archive', + ACTIVATE: 'radio_button_checked', + DEACTIVATE: 'radio_button_unchecked' +}; diff --git a/src/app/core/_components/menus/row-action-menu/row-action-menu.component.ts b/src/app/core/_components/menus/row-action-menu/row-action-menu.component.ts index d3e9786f..3c697139 100644 --- a/src/app/core/_components/menus/row-action-menu/row-action-menu.component.ts +++ b/src/app/core/_components/menus/row-action-menu/row-action-menu.component.ts @@ -2,12 +2,16 @@ import { Component, OnInit } from '@angular/core'; import { RowActionMenuAction, + RowActionMenuIcon, RowActionMenuLabel } from './row-action-menu.constants'; import { ActionMenuItem } from '../action-menu/action-menu.model'; import { BaseMenuComponent } from '../base-menu/base-menu.component'; +/** + * Component representing the row action menu for various data types. + */ @Component({ selector: 'row-action-menu', templateUrl: './row-action-menu.component.html' @@ -18,177 +22,212 @@ export class RowActionMenuComponent { ngOnInit(): void { if (this.isAgent()) { - this.getEditDeleteMenu( + this.setEditDeleteMenuItems( RowActionMenuLabel.EDIT_AGENT, RowActionMenuLabel.DELETE_AGENT ); } else if (this.isSuperHashlist()) { - this.getEditDeleteMenu( + this.setEditDeleteMenuItems( RowActionMenuLabel.EDIT_SUPERHASHLIST, RowActionMenuLabel.DELETE_SUPERHASHLIST ); } else if (this.isAgentBinary()) { - this.getEditDeleteMenu( + this.setEditDeleteMenuItems( RowActionMenuLabel.EDIT_AGENTBINARY, RowActionMenuLabel.DELETE_AGENTBINARY ); } else if (this.isFile()) { - this.getEditDeleteMenu( + this.setEditDeleteMenuItems( RowActionMenuLabel.EDIT_FILE, RowActionMenuLabel.DELETE_FILE ); } else if (this.isPreprocessor()) { - this.getEditDeleteMenu( + this.setEditDeleteMenuItems( RowActionMenuLabel.EDIT_PREPROCESSOR, RowActionMenuLabel.DELETE_PREPROCESSOR ); + } else if (this.isHealthCheck()) { + this.setEditDeleteMenuItems( + RowActionMenuLabel.EDIT_HEALTHCHECK, + RowActionMenuLabel.DELETE_HEALTHCHECK + ); } else if (this.isTask()) { - this.getTaskMenu(); + this.setTaskMenu(); } else if (this.isHashlist()) { - this.getHashlistMenu(); + this.setHashlistMenu(); } else if (this.isHashtype()) { - this.getHashtypeMenu(); + this.setHashtypeMenu(); } else if (this.isCrackerBinaryType()) { - this.getCrackerBinaryTypeMenu(); + this.setCrackerBinaryTypeMenu(); } } /** - * Get the context menu items for a cracker data row. + * Sets the context menu items for a cracker data row. */ - private getCrackerBinaryTypeMenu(): void { - this.actionMenuItems[0] = [ - { - label: RowActionMenuLabel.NEW_VERSION, - action: RowActionMenuAction.NEW, - icon: 'add' - } - ]; - this.actionMenuItems[1] = [ - { - label: RowActionMenuLabel.DELETE_CRACKER, - action: RowActionMenuAction.DELETE, - icon: 'delete', - red: true - } - ]; + private setCrackerBinaryTypeMenu(): void { + this.setActionMenuItems(0, [ + this.getNewMenuItem(RowActionMenuLabel.NEW_VERSION) + ]); + this.setActionMenuItems(1, [ + this.getDeleteMenuItem(RowActionMenuLabel.DELETE_CRACKER) + ]); } /** - * Get context menu with edit and delete action. + * Sets context menu with edit and delete action. + * @param editLabel The label for the edit action. + * @param deleteLabel The label for the delete action. */ - private getEditDeleteMenu(editLabel: string, deleteLabel: string): void { - this.actionMenuItems[0] = [ - { - label: editLabel, - action: RowActionMenuAction.EDIT, - icon: 'edit' - } - ]; - this.actionMenuItems[1] = [ - { - label: deleteLabel, - action: RowActionMenuAction.DELETE, - icon: 'delete', - red: true - } - ]; + private setEditDeleteMenuItems(editLabel: string, deleteLabel: string): void { + this.setActionMenuItems(0, [this.getEditMenuItem(editLabel)]); + this.setActionMenuItems(1, [this.getDeleteMenuItem(deleteLabel)]); } /** - * Get the context menu items for an agent data row. + * Sets the context menu items for an agent data row. */ - private getHashlistMenu(): void { - this.actionMenuItems[0] = []; - - const deleteMenuItem: ActionMenuItem = { - label: RowActionMenuLabel.DELETE_HASHLIST, - action: RowActionMenuAction.DELETE, - icon: 'delete', - red: true - }; + private setHashlistMenu(): void { + this.setActionMenuItems(0, []); if (this.data['isArchived']) { - this.actionMenuItems[0].push(deleteMenuItem); + this.setActionMenuItems(0, [ + this.getDeleteMenuItem(RowActionMenuLabel.DELETE_HASHLIST) + ]); } else { - this.actionMenuItems[0] = [ - { - label: RowActionMenuLabel.EDIT_HASHLIST, - action: RowActionMenuAction.EDIT, - icon: 'edit' - }, - { - label: RowActionMenuLabel.IMPORT_HASHLIST, - action: RowActionMenuAction.IMPORT, - icon: 'arrow_upwards' - }, - { - label: RowActionMenuLabel.EXPORT_HASHLIST, - action: RowActionMenuAction.EXPORT, - icon: 'arrow_downward' - } - ]; - this.actionMenuItems[1] = [deleteMenuItem]; + this.setActionMenuItems(0, [ + this.getEditMenuItem(RowActionMenuLabel.EDIT_HASHLIST), + this.getImportMenuItem(RowActionMenuLabel.IMPORT_HASHLIST), + this.getExportMenuItem(RowActionMenuLabel.EXPORT_HASHLIST) + ]); + this.setActionMenuItems(1, [ + this.getDeleteMenuItem(RowActionMenuLabel.DELETE_HASHLIST) + ]); } } /** - * Get the context menu items for a task data row. + * Sets the context menu items for a task data row. */ - private getTaskMenu(): void { - this.actionMenuItems[0] = [ - { - label: RowActionMenuLabel.EDIT_TASK, - action: RowActionMenuAction.EDIT, - icon: 'edit' - } - ]; - - this.actionMenuItems[1] = [ - { - label: RowActionMenuLabel.DELETE_TASK, - action: RowActionMenuAction.DELETE, - icon: 'delete', - red: true - } - ]; + private setTaskMenu(): void { + this.setActionMenuItems(0, [ + this.getEditMenuItem(RowActionMenuLabel.EDIT_TASK) + ]); + + this.setActionMenuItems(1, [ + this.getDeleteMenuItem(RowActionMenuLabel.DELETE_TASK) + ]); if (this.data.taskType === 0) { - this.actionMenuItems[0].push({ + this.addActionMenuItem(0, { label: RowActionMenuLabel.COPY_TO_TASK, action: RowActionMenuAction.COPY_TO_TASK, - icon: 'content_copy' + icon: RowActionMenuIcon.COPY }); - this.actionMenuItems[0].push({ + this.addActionMenuItem(0, { label: RowActionMenuLabel.COPY_TO_PRETASK, action: RowActionMenuAction.COPY_TO_PRETASK, - icon: 'content_copy' + icon: RowActionMenuIcon.COPY }); } else if (this.data.taskType === 1) { - this.actionMenuItems[0].push({ + this.addActionMenuItem(0, { label: RowActionMenuLabel.EDIT_SUBTASKS, action: RowActionMenuAction.EDIT_SUBTASKS, - icon: 'edit' + icon: RowActionMenuIcon.EDIT }); } - this.actionMenuItems[0].push({ - label: RowActionMenuLabel.ARCHIVE_TASK, - action: RowActionMenuAction.ARCHIVE, - icon: 'archive' - }); + this.addActionMenuItem( + 0, + this.getArchiveMenuItem(RowActionMenuLabel.ARCHIVE_TASK) + ); } - private getHashtypeMenu(): void { - this.actionMenuItems[0] = []; + /** + * Sets the context menu items for a hashtype data row. + */ + private setHashtypeMenu(): void { + this.setActionMenuItems(0, [ + this.getDeleteMenuItem(RowActionMenuLabel.DELETE_HASHTYPE) + ]); + } - const deleteMenuItem: ActionMenuItem = { - label: RowActionMenuLabel.DELETE_HASHTYPE, + /** + * Creates an ActionMenuItem with delete action. + * @param label The label for the menu item. + * @returns The ActionMenuItem with delete action. + */ + private getDeleteMenuItem(label: string): ActionMenuItem { + return { + label: label, action: RowActionMenuAction.DELETE, - icon: 'delete', + icon: RowActionMenuIcon.DELETE, red: true }; + } - this.actionMenuItems[0].push(deleteMenuItem); + /** + * Creates an ActionMenuItem with edit action. + * @param label The label for the menu item. + * @returns The ActionMenuItem with edit action. + */ + private getEditMenuItem(label: string): ActionMenuItem { + return { + label: label, + action: RowActionMenuAction.EDIT, + icon: RowActionMenuIcon.EDIT + }; + } + + /** + * Creates an ActionMenuItem with import action. + * @param label The label for the menu item. + * @returns The ActionMenuItem with import action. + */ + private getImportMenuItem(label: string): ActionMenuItem { + return { + label: label, + action: RowActionMenuAction.IMPORT, + icon: RowActionMenuIcon.IMPORT + }; + } + + /** + * Creates an ActionMenuItem with export action. + * @param label The label for the menu item. + * @returns The ActionMenuItem with export action. + */ + private getExportMenuItem(label: string): ActionMenuItem { + return { + label: label, + action: RowActionMenuAction.EXPORT, + icon: RowActionMenuIcon.EXPORT + }; + } + + /** + * Creates an ActionMenuItem with new action. + * @param label The label for the menu item. + * @returns The ActionMenuItem with new action. + */ + private getNewMenuItem(label: string): ActionMenuItem { + return { + label: label, + action: RowActionMenuAction.NEW, + icon: RowActionMenuIcon.NEW + }; + } + + /** + * Creates an ActionMenuItem with archive action. + * @param label The label for the menu item. + * @returns The ActionMenuItem with archive action. + */ + private getArchiveMenuItem(label: string): ActionMenuItem { + return { + label: label, + action: RowActionMenuAction.ARCHIVE, + icon: RowActionMenuIcon.ARCHIVE + }; } } 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 00bc72b4..0f74ef23 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 @@ -7,6 +7,7 @@ export const RowActionMenuLabel = { EDIT_FILE: 'Edit File', EDIT_PREPROCESSOR: 'Edit Preprocessor', EDIT_AGENTBINARY: 'Edit Agent Binary', + EDIT_HEALTHCHECK: 'Edit Health Check', DELETE_AGENT: 'Delete Agent', DELETE_CRACKER: 'Delete Cracker', DELETE_TASK: 'Delete Task', @@ -16,6 +17,7 @@ export const RowActionMenuLabel = { DELETE_FILE: 'Delete File', DELETE_PREPROCESSOR: 'Delete Preprocessor', DELETE_AGENTBINARY: 'Delete Agent Binary', + DELETE_HEALTHCHECK: 'Delete Health Check', COPY_TO_TASK: 'Copy to Task', COPY_TO_PRETASK: 'Copy to Pretask', ARCHIVE_TASK: 'Archive Task', @@ -35,3 +37,13 @@ export const RowActionMenuAction = { IMPORT: 'import', EXPORT: 'export' }; + +export const RowActionMenuIcon = { + EDIT: 'edit', + DELETE: 'delete', + NEW: 'add', + IMPORT: 'arrow_upwards', + EXPORT: 'arrow_downward', + COPY: 'content_copy', + ARCHIVE: 'archive' +}; diff --git a/src/app/core/_components/tables/health-checks-table/health-checks-table.component.html b/src/app/core/_components/tables/health-checks-table/health-checks-table.component.html new file mode 100644 index 00000000..12ec17c7 --- /dev/null +++ b/src/app/core/_components/tables/health-checks-table/health-checks-table.component.html @@ -0,0 +1,15 @@ + diff --git a/src/app/core/_components/tables/health-checks-table/health-checks-table.component.ts b/src/app/core/_components/tables/health-checks-table/health-checks-table.component.ts new file mode 100644 index 00000000..6d5d46c0 --- /dev/null +++ b/src/app/core/_components/tables/health-checks-table/health-checks-table.component.ts @@ -0,0 +1,244 @@ +/* eslint-disable @angular-eslint/component-selector */ +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { + HealthChecksTableColumnLabel, + HealthChecksTableStatusLabel +} from './health-checks-table.constants'; +import { catchError, forkJoin } from 'rxjs'; + +import { ActionMenuEvent } from '../../menus/action-menu/action-menu.model'; +import { BaseTableComponent } from '../base-table/base-table.component'; +import { BulkActionMenuAction } from '../../menus/bulk-action-menu/bulk-action-menu.constants'; +import { DialogData } from '../table-dialog/table-dialog.model'; +import { ExportMenuAction } from '../../menus/export-menu/export-menu.constants'; +import { HTTableColumn } from '../ht-table/ht-table.models'; +import { HealthCheck } from 'src/app/core/_models/health-check.model'; +import { HealthChecksDataSource } from 'src/app/core/_datasources/health-checks.datasource'; +import { RowActionMenuAction } from '../../menus/row-action-menu/row-action-menu.constants'; +import { SERV } from 'src/app/core/_services/main.config'; +import { TableDialogComponent } from '../table-dialog/table-dialog.component'; +import { formatUnixTimestamp } from 'src/app/shared/utils/datetime'; + +@Component({ + selector: 'health-checks-table', + templateUrl: './health-checks-table.component.html' +}) +export class HealthChecksTableComponent + extends BaseTableComponent + implements OnInit, OnDestroy +{ + tableColumns: HTTableColumn[] = []; + dataSource: HealthChecksDataSource; + + ngOnInit(): void { + this.tableColumns = this.getColumns(); + this.dataSource = new HealthChecksDataSource( + this.cdr, + this.gs, + this.uiService + ); + this.dataSource.setColumns(this.tableColumns); + this.dataSource.loadAll(); + } + + ngOnDestroy(): void { + for (const sub of this.subscriptions) { + sub.unsubscribe(); + } + } + + filter(item: HealthCheck, filterValue: string): boolean { + if (item.attackCmd.toLowerCase().includes(filterValue)) { + return true; + } + + return false; + } + + getColumns(): HTTableColumn[] { + const tableColumns = [ + { + name: HealthChecksTableColumnLabel.ID, + dataKey: '_id', + isSortable: true, + export: async (healthCheck: HealthCheck) => healthCheck._id + '' + }, + { + name: HealthChecksTableColumnLabel.CREATED, + dataKey: 'created', + isSortable: true, + render: (healthCheck: HealthCheck) => + formatUnixTimestamp(healthCheck.time, this.dateFormat), + export: async (healthCheck: HealthCheck) => + formatUnixTimestamp(healthCheck.time, this.dateFormat) + }, + { + name: HealthChecksTableColumnLabel.TYPE, + dataKey: 'hashtypeDescription', + render: (healthCheck: HealthCheck) => + healthCheck.hashtype + ? `Brute Force (${healthCheck.hashtypeDescription})` + : '', + isSortable: true, + export: async (healthCheck: HealthCheck) => + healthCheck.hashtype + ? `Brute Force (${healthCheck.hashtypeDescription})` + : '' + }, + { + name: HealthChecksTableColumnLabel.STATUS, + dataKey: 'status', + render: (healthCheck: HealthCheck) => + HealthChecksTableStatusLabel[healthCheck.status], + isSortable: true, + export: async (healthCheck: HealthCheck) => + HealthChecksTableStatusLabel[healthCheck.status] + } + ]; + + return tableColumns; + } + + openDialog(data: DialogData) { + const dialogRef = this.dialog.open(TableDialogComponent, { + data: data, + width: '450px' + }); + + this.subscriptions.push( + dialogRef.afterClosed().subscribe((result) => { + if (result && result.action) { + switch (result.action) { + case RowActionMenuAction.DELETE: + this.rowActionDelete(result.data); + break; + case BulkActionMenuAction.DELETE: + this.bulkActionDelete(result.data); + break; + } + } + }) + ); + } + + // --- Action functions --- + + exportActionClicked(event: ActionMenuEvent): void { + switch (event.menuItem.action) { + case ExportMenuAction.EXCEL: + this.exportService.toExcel( + 'hashtopolis-health-checks', + this.tableColumns, + event.data + ); + break; + case ExportMenuAction.CSV: + this.exportService.toCsv( + 'hashtopolis-health-checks', + this.tableColumns, + event.data + ); + break; + case ExportMenuAction.COPY: + this.exportService + .toClipboard(this.tableColumns, event.data) + .then(() => { + this.snackBar.open( + 'The selected rows are copied to the clipboard', + 'Close' + ); + }); + break; + } + } + + rowActionClicked(event: ActionMenuEvent): void { + switch (event.menuItem.action) { + case RowActionMenuAction.DELETE: + this.openDialog({ + rows: [event.data], + title: `Deleting health check Brute Force (${event.data.hashtype.description}) ...`, + icon: 'warning', + body: `Are you sure you want to delete it? Note that this action cannot be undone.`, + warn: true, + action: event.menuItem.action + }); + break; + case RowActionMenuAction.EDIT: + this.rowActionEdit(event.data); + break; + } + } + + bulkActionClicked(event: ActionMenuEvent): void { + switch (event.menuItem.action) { + case BulkActionMenuAction.DELETE: + this.openDialog({ + rows: event.data, + title: `Deleting ${event.data.length} health checks ...`, + icon: 'warning', + body: `Are you sure you want to delete the above health checks? Note that this action cannot be undone.`, + warn: true, + listAttribute: 'hashtypeDescription', + action: event.menuItem.action + }); + break; + } + } + + /** + * @todo Implement error handling. + */ + private bulkActionDelete(healthChecks: HealthCheck[]): void { + const requests = healthChecks.map((healthCheck: HealthCheck) => { + return this.gs.delete(SERV.CRACKERS_TYPES, healthCheck._id); + }); + + this.subscriptions.push( + forkJoin(requests) + .pipe( + catchError((error) => { + console.error('Error during deletion:', error); + return []; + }) + ) + .subscribe((results) => { + this.snackBar.open( + `Successfully deleted ${results.length} healthChecks!`, + 'Close' + ); + this.reload(); + }) + ); + } + + /** + * @todo Implement error handling. + */ + private rowActionDelete(healthChecks: HealthCheck[]): void { + this.subscriptions.push( + this.gs + .delete(SERV.CRACKERS_TYPES, healthChecks[0]._id) + .pipe( + catchError((error) => { + console.error('Error during deletion:', error); + return []; + }) + ) + .subscribe(() => { + this.snackBar.open('Successfully deleted health check!', 'Close'); + this.reload(); + }) + ); + } + + private rowActionEdit(healthCheck: HealthCheck): void { + this.router.navigate([ + '/config', + 'engine', + 'health-checks', + healthCheck._id, + 'edit' + ]); + } +} diff --git a/src/app/core/_components/tables/health-checks-table/health-checks-table.constants.ts b/src/app/core/_components/tables/health-checks-table/health-checks-table.constants.ts new file mode 100644 index 00000000..b18a55af --- /dev/null +++ b/src/app/core/_components/tables/health-checks-table/health-checks-table.constants.ts @@ -0,0 +1,14 @@ +import { HealthCheckStatus } from 'src/app/core/_models/health-check.model'; + +export const HealthChecksTableColumnLabel = { + ID: 'ID', + CREATED: 'Created', + TYPE: 'Type', + STATUS: 'Status' +}; + +export const HealthChecksTableStatusLabel = { + [HealthCheckStatus.RUNNING]: 'Running', + [HealthCheckStatus.ABORTED]: 'Aborted', + [HealthCheckStatus.COMPLETED]: 'Completed' +}; diff --git a/src/app/core/_components/tables/ht-table/ht-table.models.ts b/src/app/core/_components/tables/ht-table/ht-table.models.ts index ffa7ed4d..af0da090 100644 --- a/src/app/core/_components/tables/ht-table/ht-table.models.ts +++ b/src/app/core/_components/tables/ht-table/ht-table.models.ts @@ -10,6 +10,7 @@ export type DataType = | 'crackers' | 'preprocessors' | 'agent-binaries' + | 'health-checks' | 'superhashlists'; export interface HTTableIcon { diff --git a/src/app/core/_datasources/health-checks.datasource.ts b/src/app/core/_datasources/health-checks.datasource.ts new file mode 100644 index 00000000..d373be17 --- /dev/null +++ b/src/app/core/_datasources/health-checks.datasource.ts @@ -0,0 +1,61 @@ +import { catchError, finalize, forkJoin, of } from 'rxjs'; + +import { BaseDataSource } from './base.datasource'; +import { Hashtype } from '../_models/hashtype.model'; +import { HealthCheck } from '../_models/health-check.model'; +import { ListResponseWrapper } from '../_models/response.model'; +import { SERV } from '../_services/main.config'; + +export class HealthChecksDataSource extends BaseDataSource { + loadAll(): void { + this.loading = true; + + /** + * @todo Extend health checks api response with hashtype + */ + const healthChecks$ = this.service.getAll(SERV.HEALTH_CHECKS, { + maxResults: this.pageSize + }); + + const hashTypes$ = this.service.getAll(SERV.HASHTYPES, { + maxResults: this.maxResults + }); + + this.subscriptions.push( + forkJoin([healthChecks$, hashTypes$]) + .pipe( + catchError(() => of([])), + finalize(() => (this.loading = false)) + ) + .subscribe( + ([healthCheckResponse, hashTypesResponse]: [ + ListResponseWrapper, + ListResponseWrapper + ]) => { + const healthChecks: HealthCheck[] = healthCheckResponse.values; + const hashTypes: Hashtype[] = hashTypesResponse.values; + + healthChecks.map((healthCheck: HealthCheck) => { + healthCheck.hashtype = hashTypes.find( + (el: Hashtype) => el._id === healthCheck.hashtypeId + ); + healthCheck.hashtypeDescription = + healthCheck.hashtype.description; + }); + + this.setPaginationConfig( + this.pageSize, + this.currentPage, + healthCheckResponse.total + ); + this.setData(healthChecks); + } + ) + ); + } + + reload(): void { + this.reset(); + this.loadAll(); + } +} diff --git a/src/app/core/_models/config-ui.model.ts b/src/app/core/_models/config-ui.model.ts index 1279ac0a..f5ccdad5 100644 --- a/src/app/core/_models/config-ui.model.ts +++ b/src/app/core/_models/config-ui.model.ts @@ -5,6 +5,7 @@ import { CrackersTableColumnLabel } from '../_components/tables/crackers-table/c import { FilesTableColumnLabel } from '../_components/tables/files-table/files-table.constants'; import { HashlistsTableColumnLabel } from '../_components/tables/hashlists-table/hashlists-table.constants'; import { HashtypesTableColumnLabel } from '../_components/tables/hashtypes-table/hashtypes-table.constants'; +import { HealthChecksTableColumnLabel } from '../_components/tables/health-checks-table/health-checks-table.constants'; import { PreprocessorsTableColumnLabel } from '../_components/tables/preprocessors-table/preprocessors-table.constants'; import { SuperHashlistsTableColumnLabel } from '../_components/tables/super-hashlists-table/super-hashlists-table.constants'; @@ -94,6 +95,12 @@ export const uiConfigDefault: UIConfig = { AgentBinariesTableColumnLabel.TYPE, AgentBinariesTableColumnLabel.UPDATE_TRACK, AgentBinariesTableColumnLabel.VERSION + ], + healthChecksTable: [ + HealthChecksTableColumnLabel.ID, + HealthChecksTableColumnLabel.CREATED, + HealthChecksTableColumnLabel.STATUS, + HealthChecksTableColumnLabel.TYPE ] }, refreshPage: false, diff --git a/src/app/core/_models/hashtype.model.ts b/src/app/core/_models/hashtype.model.ts index bee55f6d..eac6e579 100644 --- a/src/app/core/_models/hashtype.model.ts +++ b/src/app/core/_models/hashtype.model.ts @@ -1,4 +1,6 @@ export interface Hashtype { + _id: number; + _self: string; hashTypeId: number; description: string; isSalted: boolean; diff --git a/src/app/core/_models/health-check.model.ts b/src/app/core/_models/health-check.model.ts new file mode 100644 index 00000000..53a55f0d --- /dev/null +++ b/src/app/core/_models/health-check.model.ts @@ -0,0 +1,31 @@ +import { Hashtype } from './hashtype.model'; + +export enum HealthCheckType { + ONE, + TWO, + THREE +} + +/** + * We cannot have negtive values in an enum + */ +export const HealthCheckStatus = { + ABORTED: -1, + RUNNING: 0, + COMPLETED: 1 +}; + +export interface HealthCheck { + _id: number; + _self: string; + attackCmd: string; + checkType: HealthCheckType; + crackerBinaryId: number; + expectedCracks: number; + hashtypeId: number; + hashtype?: Hashtype; + hashtypeDescription?: string; + healthCheckId: number; + status: number; + time: number; +} From 310160a1daa08776d95c623c80222dfc7d9b85ca Mon Sep 17 00:00:00 2001 From: itbf9 Date: Thu, 16 Nov 2023 12:59:51 +0100 Subject: [PATCH 273/419] Update health check type enum --- src/app/core/_models/health-check.model.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/app/core/_models/health-check.model.ts b/src/app/core/_models/health-check.model.ts index 53a55f0d..cfe38886 100644 --- a/src/app/core/_models/health-check.model.ts +++ b/src/app/core/_models/health-check.model.ts @@ -1,9 +1,7 @@ import { Hashtype } from './hashtype.model'; export enum HealthCheckType { - ONE, - TWO, - THREE + BRUTE_FORCE } /** From f5f3b1d976efba6764d20b588bad5f71ea3a5013 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Thu, 16 Nov 2023 20:15:54 +0100 Subject: [PATCH 274/419] Add logs-table component --- src/app/config/log/log.component.html | 31 +---- .../_components/core-components.module.ts | 7 +- .../tables/ht-table/ht-table.component.ts | 1 + .../tables/ht-table/ht-table.models.ts | 1 + .../logs-table/logs-table.component.html | 14 +++ .../tables/logs-table/logs-table.component.ts | 118 ++++++++++++++++++ .../tables/logs-table/logs-table.constants.ts | 7 ++ .../_datasources/agent-binaries.datasource.ts | 1 - .../core/_datasources/agents.datasource.ts | 1 - src/app/core/_datasources/base.datasource.ts | 4 +- .../core/_datasources/chunks.datasource.ts | 1 - .../core/_datasources/crackers.datasource.ts | 1 - src/app/core/_datasources/files.datasource.ts | 1 - .../core/_datasources/hashlists.datasource.ts | 1 - .../core/_datasources/hashtypes.datasource.ts | 1 - src/app/core/_datasources/logs.datasource.ts | 42 +++++++ .../_datasources/preprocessors.datasource.ts | 1 - .../super-hashlists.datasource.ts | 1 - src/app/core/_models/log.model.ts | 13 ++ 19 files changed, 205 insertions(+), 42 deletions(-) create mode 100644 src/app/core/_components/tables/logs-table/logs-table.component.html create mode 100644 src/app/core/_components/tables/logs-table/logs-table.component.ts create mode 100644 src/app/core/_components/tables/logs-table/logs-table.constants.ts create mode 100644 src/app/core/_datasources/logs.datasource.ts create mode 100644 src/app/core/_models/log.model.ts diff --git a/src/app/config/log/log.component.html b/src/app/config/log/log.component.html index 4b6a090d..5633a999 100644 --- a/src/app/config/log/log.component.html +++ b/src/app/config/log/log.component.html @@ -1,31 +1,4 @@ - - - - - - - - - - - - - - - - - - - - - - - -
IDTimeLevelIssuerMessage
{{ l.logEntryId }}{{ l.time | uiDate }}{{ l.level | lowercase | titlecase }}{{ l.issuer }}-ID-{{ l.issuerId }}{{ l.message }}
- + +
diff --git a/src/app/core/_components/core-components.module.ts b/src/app/core/_components/core-components.module.ts index 426e1162..e5497ed9 100644 --- a/src/app/core/_components/core-components.module.ts +++ b/src/app/core/_components/core-components.module.ts @@ -19,6 +19,7 @@ import { FilesTableComponent } from './tables/files-table/files-table.component' import { HTTableComponent } from './tables/ht-table/ht-table.component'; import { HashlistsTableComponent } from './tables/hashlists-table/hashlists-table.component'; import { HashtypesTableComponent } from './tables/hashtypes-table/hashtypes-table.component'; +import { LogsTableComponent } from './tables/logs-table/logs-table.component'; import { MatButtonModule } from '@angular/material/button'; import { MatCheckboxModule } from '@angular/material/checkbox'; import { MatDialogModule } from '@angular/material/dialog'; @@ -61,7 +62,8 @@ import { TableDialogComponent } from './tables/table-dialog/table-dialog.compone FilesTableComponent, CrackersTableComponent, PreprocessorsTableComponent, - AgentBinariesTableComponent + AgentBinariesTableComponent, + LogsTableComponent ], imports: [ ReactiveFormsModule, @@ -104,7 +106,8 @@ import { TableDialogComponent } from './tables/table-dialog/table-dialog.compone FilesTableComponent, CrackersTableComponent, PreprocessorsTableComponent, - AgentBinariesTableComponent + AgentBinariesTableComponent, + LogsTableComponent ], providers: [ { diff --git a/src/app/core/_components/tables/ht-table/ht-table.component.ts b/src/app/core/_components/tables/ht-table/ht-table.component.ts index 09d5f9f5..015feba9 100644 --- a/src/app/core/_components/tables/ht-table/ht-table.component.ts +++ b/src/app/core/_components/tables/ht-table/ht-table.component.ts @@ -289,6 +289,7 @@ export class HTTableComponent implements OnInit, AfterViewInit { * Reloads the data in the table and the bulk menu. */ reload(): void { + this.dataSource.reset(true); this.dataSource.reload(); if (this.bulkMenu) { this.bulkMenu.reload(); diff --git a/src/app/core/_components/tables/ht-table/ht-table.models.ts b/src/app/core/_components/tables/ht-table/ht-table.models.ts index ffa7ed4d..85ba8de9 100644 --- a/src/app/core/_components/tables/ht-table/ht-table.models.ts +++ b/src/app/core/_components/tables/ht-table/ht-table.models.ts @@ -10,6 +10,7 @@ export type DataType = | 'crackers' | 'preprocessors' | 'agent-binaries' + | 'logs' | 'superhashlists'; export interface HTTableIcon { diff --git a/src/app/core/_components/tables/logs-table/logs-table.component.html b/src/app/core/_components/tables/logs-table/logs-table.component.html new file mode 100644 index 00000000..9f5e31a4 --- /dev/null +++ b/src/app/core/_components/tables/logs-table/logs-table.component.html @@ -0,0 +1,14 @@ + diff --git a/src/app/core/_components/tables/logs-table/logs-table.component.ts b/src/app/core/_components/tables/logs-table/logs-table.component.ts new file mode 100644 index 00000000..d37b16da --- /dev/null +++ b/src/app/core/_components/tables/logs-table/logs-table.component.ts @@ -0,0 +1,118 @@ +/* eslint-disable @angular-eslint/component-selector */ +import { Component, OnDestroy, OnInit } from '@angular/core'; + +import { ActionMenuEvent } from '../../menus/action-menu/action-menu.model'; +import { BaseTableComponent } from '../base-table/base-table.component'; +import { ExportMenuAction } from '../../menus/export-menu/export-menu.constants'; +import { HTTableColumn } from '../ht-table/ht-table.models'; +import { Log } from 'src/app/core/_models/log.model'; +import { LogsDataSource } from 'src/app/core/_datasources/logs.datasource'; +import { LogsTableColumnLabel } from './logs-table.constants'; +import { formatUnixTimestamp } from 'src/app/shared/utils/datetime'; + +@Component({ + selector: 'logs-table', + templateUrl: './logs-table.component.html' +}) +export class LogsTableComponent + extends BaseTableComponent + implements OnInit, OnDestroy +{ + tableColumns: HTTableColumn[] = []; + dataSource: LogsDataSource; + + ngOnInit(): void { + this.tableColumns = this.getColumns(); + this.dataSource = new LogsDataSource(this.cdr, this.gs, this.uiService); + this.dataSource.setColumns(this.tableColumns); + this.dataSource.loadAll(); + } + + ngOnDestroy(): void { + for (const sub of this.subscriptions) { + sub.unsubscribe(); + } + } + + filter(item: Log, filterValue: string): boolean { + if (item.message.toLowerCase().includes(filterValue)) { + return true; + } + + return false; + } + + getColumns(): HTTableColumn[] { + const tableColumns = [ + { + name: LogsTableColumnLabel.ID, + dataKey: '_id', + isSortable: true, + export: async (log: Log) => log._id + '' + }, + { + name: LogsTableColumnLabel.TIME, + dataKey: 'time', + isSortable: true, + render: (log: Log) => formatUnixTimestamp(log.time, this.dateFormat), + export: async (log: Log) => + formatUnixTimestamp(log.time, this.dateFormat) + }, + { + name: LogsTableColumnLabel.LEVEL, + dataKey: 'level', + isSortable: true, + render: (log: Log) => + log.level.charAt(0).toUpperCase() + log.level.slice(1).toLowerCase(), + export: async (log: Log) => + log.level.charAt(0).toUpperCase() + log.level.slice(1).toLowerCase() + }, + { + name: LogsTableColumnLabel.ISSUER, + dataKey: 'issuer', + isSortable: true, + render: (log: Log) => `${log.issuer}-ID-${log.issuerId}`, + export: async (log: Log) => `${log.issuer}-ID-${log.issuerId}` + }, + { + name: LogsTableColumnLabel.MESSAGE, + dataKey: 'message', + isSortable: true, + export: async (log: Log) => log.message + } + ]; + + return tableColumns; + } + + // --- Action functions --- + + exportActionClicked(event: ActionMenuEvent): void { + switch (event.menuItem.action) { + case ExportMenuAction.EXCEL: + this.exportService.toExcel( + 'hashtopolis-logs', + this.tableColumns, + event.data + ); + break; + case ExportMenuAction.CSV: + this.exportService.toCsv( + 'hashtopolis-logs', + this.tableColumns, + event.data + ); + break; + case ExportMenuAction.COPY: + this.exportService + .toClipboard(this.tableColumns, event.data) + .then(() => { + this.snackBar.open( + 'The selected rows are copied to the clipboard', + 'Close' + ); + }); + break; + } + } +} diff --git a/src/app/core/_components/tables/logs-table/logs-table.constants.ts b/src/app/core/_components/tables/logs-table/logs-table.constants.ts new file mode 100644 index 00000000..e1c1f1d5 --- /dev/null +++ b/src/app/core/_components/tables/logs-table/logs-table.constants.ts @@ -0,0 +1,7 @@ +export const LogsTableColumnLabel = { + ID: 'ID', + TIME: 'Time', + LEVEL: 'Level', + ISSUER: 'Issuer', + MESSAGE: 'Message' +}; diff --git a/src/app/core/_datasources/agent-binaries.datasource.ts b/src/app/core/_datasources/agent-binaries.datasource.ts index 17ad1231..9014ef65 100644 --- a/src/app/core/_datasources/agent-binaries.datasource.ts +++ b/src/app/core/_datasources/agent-binaries.datasource.ts @@ -35,7 +35,6 @@ export class AgentBinariesDataSource extends BaseDataSource { } reload(): void { - this.reset(); this.loadAll(); } } diff --git a/src/app/core/_datasources/agents.datasource.ts b/src/app/core/_datasources/agents.datasource.ts index 9ea5969a..94681dcc 100644 --- a/src/app/core/_datasources/agents.datasource.ts +++ b/src/app/core/_datasources/agents.datasource.ts @@ -103,7 +103,6 @@ export class AgentsDataSource extends BaseDataSource { } reload(): void { - this.reset(); this.loadAll(); } } diff --git a/src/app/core/_datasources/base.datasource.ts b/src/app/core/_datasources/base.datasource.ts index 119de593..cb409ca5 100644 --- a/src/app/core/_datasources/base.datasource.ts +++ b/src/app/core/_datasources/base.datasource.ts @@ -295,7 +295,7 @@ export abstract class BaseDataSource< /** * Resets the data source by clearing filters, deselecting all rows, and returning to page 1 (if using pagination). */ - reset(): void { + reset(firstPage: boolean): void { // Clear any applied filters this.filter = ''; this.dataSubject.next(this.originalData); @@ -304,7 +304,7 @@ export abstract class BaseDataSource< this.selection.clear(); // Return to page 1 if using pagination - if (this.paginator) { + if (firstPage && this.paginator) { this.paginator.firstPage(); } } diff --git a/src/app/core/_datasources/chunks.datasource.ts b/src/app/core/_datasources/chunks.datasource.ts index 7c25c475..504ee03b 100644 --- a/src/app/core/_datasources/chunks.datasource.ts +++ b/src/app/core/_datasources/chunks.datasource.ts @@ -43,7 +43,6 @@ export class ChunksDataSource extends BaseDataSource { } reload(): void { - this.reset(); this.loadAll(); } } diff --git a/src/app/core/_datasources/crackers.datasource.ts b/src/app/core/_datasources/crackers.datasource.ts index 646d9257..559d897e 100644 --- a/src/app/core/_datasources/crackers.datasource.ts +++ b/src/app/core/_datasources/crackers.datasource.ts @@ -36,7 +36,6 @@ export class CrackersDataSource extends BaseDataSource { } reload(): void { - this.reset(); this.loadAll(); } } diff --git a/src/app/core/_datasources/files.datasource.ts b/src/app/core/_datasources/files.datasource.ts index 755575f5..a99bcd80 100644 --- a/src/app/core/_datasources/files.datasource.ts +++ b/src/app/core/_datasources/files.datasource.ts @@ -54,7 +54,6 @@ export class FilesDataSource extends BaseDataSource { } reload(): void { - this.reset(); this.loadAll(); } } diff --git a/src/app/core/_datasources/hashlists.datasource.ts b/src/app/core/_datasources/hashlists.datasource.ts index 15a021a2..c156e87c 100644 --- a/src/app/core/_datasources/hashlists.datasource.ts +++ b/src/app/core/_datasources/hashlists.datasource.ts @@ -53,7 +53,6 @@ export class HashlistsDataSource extends BaseDataSource { } reload(): void { - this.reset(); this.loadAll(); } } diff --git a/src/app/core/_datasources/hashtypes.datasource.ts b/src/app/core/_datasources/hashtypes.datasource.ts index 2357b812..17518565 100644 --- a/src/app/core/_datasources/hashtypes.datasource.ts +++ b/src/app/core/_datasources/hashtypes.datasource.ts @@ -30,7 +30,6 @@ export class HashtypesDataSource extends BaseDataSource { } reload(): void { - this.reset(); this.loadAll(); } } diff --git a/src/app/core/_datasources/logs.datasource.ts b/src/app/core/_datasources/logs.datasource.ts new file mode 100644 index 00000000..64093956 --- /dev/null +++ b/src/app/core/_datasources/logs.datasource.ts @@ -0,0 +1,42 @@ +import { catchError, finalize, of } from 'rxjs'; + +import { BaseDataSource } from './base.datasource'; +import { ListResponseWrapper } from '../_models/response.model'; +import { Log } from '../_models/log.model'; +import { SERV } from '../_services/main.config'; + +export class LogsDataSource extends BaseDataSource { + loadAll(): void { + this.loading = true; + + const startAt = this.currentPage * this.pageSize; + const params = { + maxResults: this.pageSize, + startAt: startAt + }; + + const logs$ = this.service.getAll(SERV.LOGS, params); + + this.subscriptions.push( + logs$ + .pipe( + catchError(() => of([])), + finalize(() => (this.loading = false)) + ) + .subscribe((response: ListResponseWrapper) => { + const logs: Log[] = response.values; + + this.setPaginationConfig( + this.pageSize, + this.currentPage, + response.total + ); + this.setData(logs); + }) + ); + } + + reload(): void { + this.loadAll(); + } +} diff --git a/src/app/core/_datasources/preprocessors.datasource.ts b/src/app/core/_datasources/preprocessors.datasource.ts index 51b8ed75..ea219ba0 100644 --- a/src/app/core/_datasources/preprocessors.datasource.ts +++ b/src/app/core/_datasources/preprocessors.datasource.ts @@ -35,7 +35,6 @@ export class PreprocessorsDataSource extends BaseDataSource { } reload(): void { - this.reset(); this.loadAll(); } } diff --git a/src/app/core/_datasources/super-hashlists.datasource.ts b/src/app/core/_datasources/super-hashlists.datasource.ts index e711f355..4c8b4401 100644 --- a/src/app/core/_datasources/super-hashlists.datasource.ts +++ b/src/app/core/_datasources/super-hashlists.datasource.ts @@ -49,7 +49,6 @@ export class SuperHashlistsDataSource extends BaseDataSource { } reload(): void { - this.reset(); this.loadAll(); } } diff --git a/src/app/core/_models/log.model.ts b/src/app/core/_models/log.model.ts new file mode 100644 index 00000000..1a80a7d9 --- /dev/null +++ b/src/app/core/_models/log.model.ts @@ -0,0 +1,13 @@ +export type LogLevel = 'warning' | 'error' | 'fatal error' | 'information'; +export type Issuer = 'API' | 'User'; + +export interface Log { + _id: number; + _self: string; + issuer: Issuer; + issuerId: string; + level: LogLevel; + logEntryId: number; + message: string; + time: number; +} From 1f321aebc6811bda2fbeb2fce26a4a655ef3fbf1 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Thu, 16 Nov 2023 20:42:54 +0100 Subject: [PATCH 275/419] Add pagination params --- src/app/core/_datasources/agent-binaries.datasource.ts | 4 +++- src/app/core/_datasources/agents.datasource.ts | 10 ++++++++-- src/app/core/_datasources/chunks.datasource.ts | 9 +++++++-- src/app/core/_datasources/crackers.datasource.ts | 2 ++ src/app/core/_datasources/files.datasource.ts | 2 ++ src/app/core/_datasources/hashlists.datasource.ts | 4 +++- src/app/core/_datasources/hashtypes.datasource.ts | 7 ++++++- src/app/core/_datasources/preprocessors.datasource.ts | 4 +++- .../core/_datasources/super-hashlists.datasource.ts | 2 ++ 9 files changed, 36 insertions(+), 8 deletions(-) diff --git a/src/app/core/_datasources/agent-binaries.datasource.ts b/src/app/core/_datasources/agent-binaries.datasource.ts index 9014ef65..a712b3de 100644 --- a/src/app/core/_datasources/agent-binaries.datasource.ts +++ b/src/app/core/_datasources/agent-binaries.datasource.ts @@ -9,8 +9,10 @@ export class AgentBinariesDataSource extends BaseDataSource { loadAll(): void { this.loading = true; + const startAt = this.currentPage * this.pageSize; const params = { - maxResults: this.pageSize + maxResults: this.pageSize, + startAt: startAt }; const agentBinaries$ = this.service.getAll(SERV.AGENT_BINARY, params); diff --git a/src/app/core/_datasources/agents.datasource.ts b/src/app/core/_datasources/agents.datasource.ts index 94681dcc..49ae64ed 100644 --- a/src/app/core/_datasources/agents.datasource.ts +++ b/src/app/core/_datasources/agents.datasource.ts @@ -13,8 +13,14 @@ export class AgentsDataSource extends BaseDataSource { loadAll(): void { this.loading = true; - const agentParams = { maxResults: 999999, expand: 'accessGroups' }; - const params = { maxResults: 999999 }; + const startAt = this.currentPage * this.pageSize; + const agentParams = { + maxResults: this.pageSize, + startAt: startAt, + expand: 'accessGroups' + }; + + const params = { maxResults: this.maxResults }; const agents$ = this.service.getAll(SERV.AGENTS, agentParams); const users$ = this.service.getAll(SERV.USERS, params); const agentAssign$ = this.service.getAll(SERV.AGENT_ASSIGN, params); diff --git a/src/app/core/_datasources/chunks.datasource.ts b/src/app/core/_datasources/chunks.datasource.ts index 504ee03b..2c9be9e6 100644 --- a/src/app/core/_datasources/chunks.datasource.ts +++ b/src/app/core/_datasources/chunks.datasource.ts @@ -10,8 +10,13 @@ export class ChunksDataSource extends BaseDataSource { loadAll(): void { this.loading = true; - const agentParams = { maxResults: 999999 }; - const chunkParams = { maxResults: 1000, expand: 'task' }; + const startAt = this.currentPage * this.pageSize; + const chunkParams = { + maxResults: this.pageSize, + startAt: startAt, + expand: 'task' + }; + const agentParams = { maxResults: this.maxResults }; const chunks$ = this.service.getAll(SERV.CHUNKS, chunkParams); const agents$ = this.service.getAll(SERV.AGENTS, agentParams); diff --git a/src/app/core/_datasources/crackers.datasource.ts b/src/app/core/_datasources/crackers.datasource.ts index 559d897e..eaf7a135 100644 --- a/src/app/core/_datasources/crackers.datasource.ts +++ b/src/app/core/_datasources/crackers.datasource.ts @@ -9,8 +9,10 @@ export class CrackersDataSource extends BaseDataSource { loadAll(): void { this.loading = true; + const startAt = this.currentPage * this.pageSize; const params = { maxResults: this.pageSize, + startAt: startAt, expand: 'crackerVersions' }; diff --git a/src/app/core/_datasources/files.datasource.ts b/src/app/core/_datasources/files.datasource.ts index a99bcd80..ebfa568a 100644 --- a/src/app/core/_datasources/files.datasource.ts +++ b/src/app/core/_datasources/files.datasource.ts @@ -19,8 +19,10 @@ export class FilesDataSource extends BaseDataSource { loadAll(): void { this.loading = true; + const startAt = this.currentPage * this.pageSize; const params = { maxResults: this.pageSize, + startAt: startAt, expand: 'accessGroup', filter: `fileType=${this.fileType}` }; diff --git a/src/app/core/_datasources/hashlists.datasource.ts b/src/app/core/_datasources/hashlists.datasource.ts index c156e87c..0e7877ea 100644 --- a/src/app/core/_datasources/hashlists.datasource.ts +++ b/src/app/core/_datasources/hashlists.datasource.ts @@ -16,8 +16,10 @@ export class HashlistsDataSource extends BaseDataSource { loadAll(): void { this.loading = true; + const startAt = this.currentPage * this.pageSize; const params = { - maxResults: this.maxResults, + maxResults: this.pageSize, + startAt: startAt, expand: 'hashType,accessGroup', filter: `isArchived=${this.isArchived}` }; diff --git a/src/app/core/_datasources/hashtypes.datasource.ts b/src/app/core/_datasources/hashtypes.datasource.ts index 17518565..12fa8ca0 100644 --- a/src/app/core/_datasources/hashtypes.datasource.ts +++ b/src/app/core/_datasources/hashtypes.datasource.ts @@ -9,7 +9,12 @@ export class HashtypesDataSource extends BaseDataSource { loadAll(): void { this.loading = true; - const params = { maxResults: this.paginator.pageSize }; + const startAt = this.currentPage * this.pageSize; + const params = { + maxResults: this.pageSize, + startAt: startAt + }; + const hashtypes$ = this.service.getAll(SERV.HASHTYPES, params); this.subscriptions.push( diff --git a/src/app/core/_datasources/preprocessors.datasource.ts b/src/app/core/_datasources/preprocessors.datasource.ts index ea219ba0..c93dcafa 100644 --- a/src/app/core/_datasources/preprocessors.datasource.ts +++ b/src/app/core/_datasources/preprocessors.datasource.ts @@ -9,8 +9,10 @@ export class PreprocessorsDataSource extends BaseDataSource { loadAll(): void { this.loading = true; + const startAt = this.currentPage * this.pageSize; const params = { - maxResults: this.pageSize + maxResults: this.pageSize, + startAt: startAt }; const preprocessors$ = this.service.getAll(SERV.PREPROCESSORS, params); diff --git a/src/app/core/_datasources/super-hashlists.datasource.ts b/src/app/core/_datasources/super-hashlists.datasource.ts index 4c8b4401..16051513 100644 --- a/src/app/core/_datasources/super-hashlists.datasource.ts +++ b/src/app/core/_datasources/super-hashlists.datasource.ts @@ -16,8 +16,10 @@ export class SuperHashlistsDataSource extends BaseDataSource { loadAll(): void { this.loading = true; + const startAt = this.currentPage * this.pageSize; const params = { maxResults: this.pageSize, + startAt: startAt, expand: 'hashType,hashlists', filter: `format=${HashListFormat.SUPERHASHLIST}` }; From 8e575e1361ef6954663d7098022181daf30a01b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Fri, 17 Nov 2023 13:38:06 +0000 Subject: [PATCH 276/419] New input shared --- .../_directives/validate-number.directive.ts | 30 --- .../new-superhashlist.component.html | 57 ++-- .../new-superhashlist.component.ts | 217 ++++++++------- src/app/shared/components.module.ts | 33 +-- src/app/shared/directives.module.ts | 20 +- src/app/shared/form/dynamicform.module.ts | 17 +- .../mat-autocomplete.component.html | 10 - .../mat-autocomplete.component.ts | 47 ---- src/app/shared/input/abstract-input.ts | 66 +++++ .../shared/input/check/check.component.html | 3 + src/app/shared/input/check/check.component.ts | 32 +++ .../shared/input/color/color.component.html | 16 ++ src/app/shared/input/color/color.component.ts | 63 +++++ src/app/shared/input/date/date.component.html | 9 + src/app/shared/input/date/date.component.ts | 33 +++ src/app/shared/input/input.module.ts | 64 +++++ .../multiselect/multiselect.component.html | 38 +++ .../multiselect/multiselect.component.ts | 248 ++++++++++++++++++ .../shared/input/number/number.component.html | 8 + .../shared/input/number/number.component.ts | 32 +++ .../select}/select-field.component.html | 0 .../select}/select-field.component.ts | 0 src/app/shared/input/text/text.component.html | 24 ++ src/app/shared/input/text/text.component.ts | 34 +++ src/app/shared/pipes.module.ts | 57 ++-- src/app/shared/utils/forms.ts | 31 ++- src/styles/components/_form.scss | 30 +++ 27 files changed, 924 insertions(+), 295 deletions(-) delete mode 100644 src/app/core/_directives/validate-number.directive.ts delete mode 100644 src/app/shared/form/mat-autocomplete/mat-autocomplete.component.html delete mode 100644 src/app/shared/form/mat-autocomplete/mat-autocomplete.component.ts create mode 100644 src/app/shared/input/abstract-input.ts create mode 100644 src/app/shared/input/check/check.component.html create mode 100644 src/app/shared/input/check/check.component.ts create mode 100644 src/app/shared/input/color/color.component.html create mode 100644 src/app/shared/input/color/color.component.ts create mode 100644 src/app/shared/input/date/date.component.html create mode 100644 src/app/shared/input/date/date.component.ts create mode 100644 src/app/shared/input/input.module.ts create mode 100644 src/app/shared/input/multiselect/multiselect.component.html create mode 100644 src/app/shared/input/multiselect/multiselect.component.ts create mode 100644 src/app/shared/input/number/number.component.html create mode 100644 src/app/shared/input/number/number.component.ts rename src/app/shared/{form/select-field => input/select}/select-field.component.html (100%) rename src/app/shared/{form/select-field => input/select}/select-field.component.ts (100%) create mode 100644 src/app/shared/input/text/text.component.html create mode 100644 src/app/shared/input/text/text.component.ts diff --git a/src/app/core/_directives/validate-number.directive.ts b/src/app/core/_directives/validate-number.directive.ts deleted file mode 100644 index 03e167da..00000000 --- a/src/app/core/_directives/validate-number.directive.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { - Directive, - Input } from '@angular/core'; -import { - NG_VALIDATORS, - Validator, - AbstractControl } from '@angular/forms'; - -/** - * Validate Input Number only allows to type a number, it is important for validation - * Usage: - * value | validateInputNumber - * Example: - * validateInputNumber > - * @returns 1KB - * output is: dont let you type any number -**/ - -@Directive({ - selector: '[validateInputNumber]', - providers: [{provide: NG_VALIDATORS, useExisting: InputNumberValidator, multi: true}] -}) -export class InputNumberValidator implements Validator { - - @Input('validateInputNumber') length: number; - - validate(control: AbstractControl): {[key: string]: any} | null { - return control.value.toString().length < this.length ? null : { validateFieldNumber: { NotEqual: true }}; - } -} diff --git a/src/app/hashlists/new-superhashlist/new-superhashlist.component.html b/src/app/hashlists/new-superhashlist/new-superhashlist.component.html index 8260ff78..64b5fb18 100644 --- a/src/app/hashlists/new-superhashlist/new-superhashlist.component.html +++ b/src/app/hashlists/new-superhashlist/new-superhashlist.component.html @@ -1,48 +1,27 @@ - -
+ - - Name - - + - - - Select or search Hashlists: - - - {{ selectedHashlist.name }} - cancel - - - - - - {{ hashlist.name }} - - - + + + + + + - - - -
- {{ createForm.value | json }} + {{ form.value | json }}
+ diff --git a/src/app/hashlists/new-superhashlist/new-superhashlist.component.ts b/src/app/hashlists/new-superhashlist/new-superhashlist.component.ts index 4aa0f081..61ade061 100644 --- a/src/app/hashlists/new-superhashlist/new-superhashlist.component.ts +++ b/src/app/hashlists/new-superhashlist/new-superhashlist.component.ts @@ -2,143 +2,162 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, + Input, + OnDestroy, OnInit } from '@angular/core'; -import { FormArray, FormControl, FormGroup, Validators } from '@angular/forms'; +import { + AbstractControl, + FormBuilder, + FormControl, + FormGroup, + Validators +} from '@angular/forms'; import { Router } from '@angular/router'; import { AlertService } from 'src/app/core/_services/shared/alert.service'; import { GlobalService } from 'src/app/core/_services/main.service'; +import { UnsubscribeService } from 'src/app/core/_services/unsubscribe.service'; +import { AutoTitleService } from 'src/app/core/_services/shared/autotitle.service'; import { environment } from './../../../environments/environment'; -import { PageTitle } from 'src/app/core/_decorators/autotitle'; +import { Hashlist } from 'src/app/core/_models/hashlist.model'; import { SERV } from '../../core/_services/main.config'; +import { extractIds } from '../../shared/utils/forms'; + +interface SelectField { + _id: string; + name: string; +} +/** + * Represents the NewSuperhashlistComponent responsible for creating a new SuperHashlist. + */ @Component({ selector: 'app-new-superhashlist', templateUrl: './new-superhashlist.component.html', changeDetection: ChangeDetectionStrategy.OnPush }) -@PageTitle(['New SuperHashlist']) -export class NewSuperhashlistComponent implements OnInit { +export class NewSuperhashlistComponent implements OnInit, OnDestroy { + /** Flag indicating whether data is still loading. */ + isLoading = true; + + /** Form group for the new SuperHashlist. */ + form: FormGroup; + + /** Maximum results for API requests. */ + private maxResults = environment.config.prodApiMaxResults; + + /** List of hashlists. */ + hashlists: any; + + // Util functions + /** Utility function for extracting IDs from a list of items. */ + extractIds = extractIds; + + /** + * Constructor of the NewSuperhashlistComponent. + * + * @param unsubscribeService - The service responsible for managing subscriptions. + * @param changeDetectorRef - Reference to the change detector to manually trigger change detection. + * @param titleService - Service for managing the title of the page. + * @param alert - Service for displaying alerts. + * @param globalService - Service for making global API requests. + * @param formBuilder - FormBuilder service for creating reactive forms. + * @param router - Angular Router service for navigation. + */ constructor( - private _changeDetectorRef: ChangeDetectorRef, + private unsubscribeService: UnsubscribeService, + private changeDetectorRef: ChangeDetectorRef, + private titleService: AutoTitleService, private alert: AlertService, - private gs: GlobalService, + private globalService: GlobalService, + private formBuilder: FormBuilder, private router: Router - ) {} + ) { + this.buildForm(); + titleService.set(['New SuperHashlist']); + } - createForm: FormGroup; - private maxResults = environment.config.prodApiMaxResults; - formArr: FormArray; - hashlists: any; + @Input() + error; + /** + * Lifecycle hook called after component initialization. + */ ngOnInit(): void { - this.createForm = new FormGroup({ - name: new FormControl(''), - hashlistIds: new FormControl('') + this.loadData(); + } + + /** + * Lifecycle hook called before the component is destroyed. + * Unsubscribes from all subscriptions to prevent memory leaks. + */ + ngOnDestroy(): void { + this.unsubscribeService.unsubscribeAll(); + } + + /** + * Builds the form for creating a new SuperHashlist. + */ + buildForm(): void { + this.form = this.formBuilder.group({ + name: ['', Validators.required], + hashlistIds: [null, Validators.required] }); + } - this.gs + /** + * Loads data, specifically hashlists, for the component. + */ + loadData(): void { + this.globalService .getAll(SERV.HASHLISTS, { maxResults: this.maxResults, filter: 'isArchived=false,format=0' }) - .subscribe((tasks: any) => { - const self = this; - const response = tasks.values; - this.hashlists = response; - ($('#hashlistIds') as any).selectize({ - maxItems: null, - plugins: ['restore_on_backspace'], - valueField: 'hashlistId', - placeholder: 'Search hashlist...', - labelField: 'name', - searchField: ['name'], - loadingClass: 'Loading..', - highlight: true, - onChange: function (value) { - self.OnChangeValue(value); // We need to overide DOM event, Angular vs Jquery - }, - render: { - option: function (item, escape) { - return ( - '
' + - escape(item.hashlistId) + - ' - ' + - escape(item.name) + - '
' - ); - } - }, - onInitialize: function () { - const selectize = this; - selectize.addOption(response); // This is will add to option - const selected_items = []; - $.each(response, function (i, obj) { - selected_items.push(obj.id); - }); - selectize.setValue(selected_items); //this will set option values as default - } - }); + .subscribe((response: any) => { + this.hashlists = response.values; + this.isLoading = false; + this.changeDetectorRef.detectChanges(); }); } - OnChangeValue(value) { - const formArr = new FormArray([]); - for (const val of value) { - formArr.push(new FormControl(+val)); - } - const cname = this.createForm.get('name').value; - this.createForm = new FormGroup({ - name: new FormControl(cname), - hashlistIds: formArr - }); - this._changeDetectorRef.detectChanges(); - } - - onSubmit() { - if (this.createForm.valid) { - console.log(this.createForm.value); - this.gs - .chelper(SERV.HELPER, 'createSuperHashlist', this.createForm.value) + /** + * Handles form submission, creating a new SuperHashlist. + * If the form is valid, it makes an API request and navigates to the SuperHashlist page. + */ + onSubmit(): void { + if (this.form.valid) { + const createSubscription$ = this.globalService + .chelper(SERV.HELPER, 'createSuperHashlist', this.form.value) .subscribe(() => { this.alert.okAlert('New SuperHashList created!', ''); - this.createForm.reset(); // success, we reset form + this.form.reset(); this.router.navigate(['hashlists/superhashlist']); }); - } - } - - hashlistIds = new FormControl(); - selectedHashlistDisplay: any[] = []; // Array for displaying selected hashlists - selectedHashlistValues: any[] = []; // Array for storing selected hashlist values - addItem(event: any): void { - const value = (event.value || '').trim(); - - if (value && !this.selectedHashlistValues.includes(value)) { - this.selectedHashlistValues.push(value); - this.selectedHashlistDisplay.push(value); // Add to display array - this.hashlistIds.setValue(''); + this.unsubscribeService.add(createSubscription$); } } - removeItem(hashlist: any): void { - const index = this.selectedHashlistValues.indexOf(hashlist); - - if (index >= 0) { - this.selectedHashlistValues.splice(index, 1); - this.selectedHashlistDisplay.splice(index, 1); // Remove from display array - } + /** + * Checks if a given control is an instance of FormControl. + * + * @param control - The control to check. + * @returns True if the control is a FormControl, false otherwise. + */ + isFormControl(control: AbstractControl | null): control is FormControl { + return control instanceof FormControl; } - selectedItem(event: any): void { - const selectedValue = event.option.value; - - if (!this.selectedHashlistValues.includes(selectedValue)) { - this.selectedHashlistValues.push(selectedValue); - this.selectedHashlistDisplay.push(selectedValue); // Add to display array - this.hashlistIds.setValue(''); - } + /** + * Handles the selection of items in the UI. + * Extracts the IDs from the selected items and sets them in the form. + * + * @param selectedItems - The items that are selected. + */ + handleSelectedItems(selectedItems: SelectField[]): void { + const extractedIds = this.extractIds(selectedItems, '_id'); + this.form.get('hashlistIds').setValue(extractedIds); } } diff --git a/src/app/shared/components.module.ts b/src/app/shared/components.module.ts index f6cf3ec9..d92e62e9 100644 --- a/src/app/shared/components.module.ts +++ b/src/app/shared/components.module.ts @@ -1,35 +1,36 @@ import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { CommonModule } from '@angular/common'; -import { NgModule } from "@angular/core"; +import { NgModule } from '@angular/core'; import { LoadingSpinnerComponent } from '../shared/loading-spinner/loading-spinner.component'; -import { HashtypeDetectorComponent } from "./hashtype-detector/hashtype-detector.component"; +import { HashtypeDetectorComponent } from './hashtype-detector/hashtype-detector.component'; import { ActiveSpinnerComponent } from './loading-spinner/loading-spinner-active.component'; import { PassStrenghtComponent } from './password/pass-strenght/pass-strenght.component'; import { ButtonTruncateTextComponent } from './table/button-truncate-text.component'; -import { HexconvertorComponent } from "./utils/hexconvertor/hexconvertor.component"; +import { HexconvertorComponent } from './utils/hexconvertor/hexconvertor.component'; import { TimeoutDialogComponent } from './dialog/timeout/timeout-dialog.component'; import { PassMatchComponent } from './password/pass-match/pass-match.component'; -import { CheatsheetComponent } from "./alert/cheatsheet/cheatsheet.component"; -import { FilterTextboxModule } from "./filter-textbox/filter-textbox.module"; -import { SwitchThemeModule } from "./switch-theme/switch-theme.module"; +import { CheatsheetComponent } from './alert/cheatsheet/cheatsheet.component'; +import { FilterTextboxModule } from './filter-textbox/filter-textbox.module'; +import { SwitchThemeModule } from './switch-theme/switch-theme.module'; import { MatProgressBarModule } from '@angular/material/progress-bar'; -import { TimeoutComponent } from "./alert/timeout/timeout.component"; +import { TimeoutComponent } from './alert/timeout/timeout.component'; import { HorizontalNavModule } from './navigation/navigation.module'; -import { PageTitleModule } from "./page-headers/page-title.module"; -import { PaginationModule } from "./pagination/pagination.module"; +import { PageTitleModule } from './page-headers/page-title.module'; +import { PaginationModule } from './pagination/pagination.module'; import { DynamicFormModule } from './form/dynamicform.module'; import { MatDialogModule } from '@angular/material/dialog'; import { MatButtonModule } from '@angular/material/button'; -import { GridModule } from "./grid-containers/grid.module"; -import { TableModule } from "./table/table-actions.module"; -import { AlertComponent } from "./alert/alert.component"; -import { ButtonsModule } from "./buttons/buttons.module"; +import { GridModule } from './grid-containers/grid.module'; +import { TableModule } from './table/table-actions.module'; +import { AlertComponent } from './alert/alert.component'; +import { ButtonsModule } from './buttons/buttons.module'; import { LottiesModule } from './lottie/lottie.module'; import { MatIconModule } from '@angular/material/icon'; -import { GraphsModule } from "./graphs/graphs.module"; +import { GraphsModule } from './graphs/graphs.module'; import { ColorPickerModule } from 'ngx-color-picker'; -import { FormsModule } from "@angular/forms"; +import { InputModule } from './input/input.module'; +import { FormsModule } from '@angular/forms'; @NgModule({ declarations: [ @@ -64,6 +65,7 @@ import { FormsModule } from "@angular/forms"; CommonModule, FormsModule, TableModule, + InputModule, GridModule, NgbModule ], @@ -90,6 +92,7 @@ import { FormsModule } from "@angular/forms"; LottiesModule, GraphsModule, CommonModule, + InputModule, TableModule, GridModule ] diff --git a/src/app/shared/directives.module.ts b/src/app/shared/directives.module.ts index f046fd70..07068656 100644 --- a/src/app/shared/directives.module.ts +++ b/src/app/shared/directives.module.ts @@ -1,12 +1,12 @@ -import { CommonModule } from "@angular/common"; -import { NgModule } from "@angular/core"; +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; -import { StartsWithActiveDirective } from "../core/_directives/nav-startswith.directive"; -import { HoverDashedDirective } from "../core/_directives/hover-dashed.directive"; -import { FileSelectDirective } from "../core/_directives/file-select.directive"; -import { CopyButtonDirective } from "../core/_directives/copy-button.directive"; -import { UnderlineDirective } from "../core/_directives/underline.directive"; -import { FileDropDirective } from "../core/_directives/file-drop.directive"; +import { StartsWithActiveDirective } from '../core/_directives/nav-startswith.directive'; +import { HoverDashedDirective } from '../core/_directives/hover-dashed.directive'; +import { FileSelectDirective } from '../core/_directives/file-select.directive'; +import { CopyButtonDirective } from '../core/_directives/copy-button.directive'; +import { UnderlineDirective } from '../core/_directives/underline.directive'; +import { FileDropDirective } from '../core/_directives/file-drop.directive'; @NgModule({ declarations: [ @@ -28,7 +28,3 @@ import { FileDropDirective } from "../core/_directives/file-drop.directive"; ] }) export class DirectivesModule {} - - - - diff --git a/src/app/shared/form/dynamicform.module.ts b/src/app/shared/form/dynamicform.module.ts index e86a2c79..871174b5 100644 --- a/src/app/shared/form/dynamicform.module.ts +++ b/src/app/shared/form/dynamicform.module.ts @@ -22,15 +22,13 @@ import { MatInputModule } from '@angular/material/input'; import { MatSelectModule } from '@angular/material/select'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { PageTitleModule } from '../page-headers/page-title.module'; -import { SelectFieldComponent } from './select-field/select-field.component'; +import { MatChipsModule } from '@angular/material/chips'; +import { MatAutocompleteModule } from '@angular/material/autocomplete'; +import { MatOptionModule } from '@angular/material/core'; +import { PipesModule } from '../pipes.module'; @NgModule({ - declarations: [ - DynamicFormComponent, - FormConfigComponent, - FormComponent, - SelectFieldComponent - ], + declarations: [DynamicFormComponent, FormConfigComponent, FormComponent], imports: [ MatButtonModule, MatCheckboxModule, @@ -41,14 +39,17 @@ import { SelectFieldComponent } from './select-field/select-field.component'; MatProgressSpinnerModule, MatSelectModule, MatTooltipModule, + MatChipsModule, + MatOptionModule, CommonModule, + MatAutocompleteModule, FontAwesomeModule, FormsModule, + PipesModule, GridModule, NgbModule, PageTitleModule, ReactiveFormsModule, - HorizontalNavModule, ButtonsModule ], diff --git a/src/app/shared/form/mat-autocomplete/mat-autocomplete.component.html b/src/app/shared/form/mat-autocomplete/mat-autocomplete.component.html deleted file mode 100644 index 3ab3c621..00000000 --- a/src/app/shared/form/mat-autocomplete/mat-autocomplete.component.html +++ /dev/null @@ -1,10 +0,0 @@ -
- - - - - {{ option.taskName }} - - - -
diff --git a/src/app/shared/form/mat-autocomplete/mat-autocomplete.component.ts b/src/app/shared/form/mat-autocomplete/mat-autocomplete.component.ts deleted file mode 100644 index 561bdd64..00000000 --- a/src/app/shared/form/mat-autocomplete/mat-autocomplete.component.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; -import { FormControl } from '@angular/forms'; -import { Observable } from 'rxjs'; - -@Component({ - selector: 'app-mat-autocomplete', - templateUrl: 'mat-autocomplete.component.html' -}) -export class MatAutocompleteComponent implements OnInit { - @Input() options$: Observable; // An Observable emitting the options - @Input() placeholder = 'Select or search'; - @Output() optionSelected = new EventEmitter(); - - selectedOption = new FormControl(); - filteredOptions: Observable | undefined; - - constructor() {} - - /** - * Angular lifecycle hook: ngOnInit - * Initializes the component after Angular has initialized its data-bound properties. - * Subscribes to changes in the selected option value and emits the selected option using the 'optionSelected' event. - * Sets up the filteredOptions if an Observable of options ('options$') is provided. - */ - ngOnInit(): void { - // Subscribe to changes in the selected option value - this.selectedOption.valueChanges.subscribe((option) => { - // Emit the selected option using the 'optionSelected' event - this.optionSelected.emit(option); - }); - - // Check if an Observable of options is provided - if (this.options$) { - // If options$ is available, bind the filteredOptions to the selectedOption value changes - this.filteredOptions = this.selectedOption.valueChanges; - } - } - - /** - * Custom display function to determine the display text for the selected option. - * @param option - The option for which the display text is generated. - * @returns The display text for the selected option, or an empty string if the option is falsy. - */ - displayFn(option: any): string { - return option && option.taskName ? option.taskName : ''; - } -} diff --git a/src/app/shared/input/abstract-input.ts b/src/app/shared/input/abstract-input.ts new file mode 100644 index 00000000..124c15b6 --- /dev/null +++ b/src/app/shared/input/abstract-input.ts @@ -0,0 +1,66 @@ +import { Directive, ElementRef, Input, OnInit, ViewChild } from '@angular/core'; +import { ControlValueAccessor } from '@angular/forms'; + +@Directive() +export class AbstractInputComponent implements OnInit, ControlValueAccessor { + @ViewChild('inputField') + inputField: ElementRef; + + constructor() {} + + onChange = (newValue: T) => {}; + + onTouched = () => {}; + + writeValue(newValue: any): void { + this.value = newValue; + } + + registerOnChange(fn: any): void { + this.onChange = fn; + } + + registerOnTouched(fn: any): void { + this.onTouched = fn; + } + + setDisabledState?(isDisabled: boolean): void { + this.disabled = isDisabled; + } + + focus() { + if (this.inputField && this.inputField.nativeElement) { + this.inputField.nativeElement.focus(); + } + } + + @Input() + title: string; + + @Input() + disabled = false; + + @Input() + error: string; + + value: T; + + @Input() + inputId: string; + + @Input() + hint: string; + + @Input() + tooltip: string; + + ngOnInit(): void { + if (!this.inputId) { + this.inputId = 'input_' + Math.random().toString(36).substr(2, 9); + console.warn( + 'Input ID not provided. Generated a unique ID:', + this.inputId + ); + } + } +} diff --git a/src/app/shared/input/check/check.component.html b/src/app/shared/input/check/check.component.html new file mode 100644 index 00000000..09acf128 --- /dev/null +++ b/src/app/shared/input/check/check.component.html @@ -0,0 +1,3 @@ + + {{ title }} + diff --git a/src/app/shared/input/check/check.component.ts b/src/app/shared/input/check/check.component.ts new file mode 100644 index 00000000..93dcd1fb --- /dev/null +++ b/src/app/shared/input/check/check.component.ts @@ -0,0 +1,32 @@ +import { AbstractInputComponent } from '../abstract-input'; +import { Component, forwardRef } from '@angular/core'; +import { NG_VALUE_ACCESSOR } from '@angular/forms'; + +/** + * Custom Input Check Component. + * + * Usage Example: + * ```html + * + * ``` + */ +@Component({ + selector: 'app-input-check', + templateUrl: './check.component.html', + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => InputCheckComponent), + multi: true + } + ] +}) +export class InputCheckComponent extends AbstractInputComponent { + constructor() { + super(); + } +} diff --git a/src/app/shared/input/color/color.component.html b/src/app/shared/input/color/color.component.html new file mode 100644 index 00000000..fdf8ccbe --- /dev/null +++ b/src/app/shared/input/color/color.component.html @@ -0,0 +1,16 @@ + + {{ title }} + + + diff --git a/src/app/shared/input/color/color.component.ts b/src/app/shared/input/color/color.component.ts new file mode 100644 index 00000000..c75b1175 --- /dev/null +++ b/src/app/shared/input/color/color.component.ts @@ -0,0 +1,63 @@ +import { AbstractControl, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { + Component, + EventEmitter, + Input, + OnInit, + Output, + forwardRef +} from '@angular/core'; +import { AbstractInputComponent } from '../abstract-input'; + +/** + * Custom Input Color Picker Component. + * + * This component provides an input field with an integrated color picker. + * Users can select a color either through the input or the color picker. + * + * Usage Example: + * ```html + * + * ``` + */ +@Component({ + selector: 'app-input-color', + templateUrl: './color.component.html', + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => InputColorComponent), + multi: true + } + ] +}) +export class InputColorComponent extends AbstractInputComponent { + constructor() { + super(); + } + + @Input() defaultColor = '#FFFFFF'; + + /** + * List of preset colors for the color picker. + */ + presetColors = [ + '#D41A29', //Red + '#00A5ff', //Blue + '#D000A4', //Magenta + '#FF9000', //Orange + '#F32E6E', //Pink + '#D35F00', //Brown + '#7ad54d' //Green + ]; + + onChangeValue(value) { + this.value = value; + this.onChange(value); + } +} diff --git a/src/app/shared/input/date/date.component.html b/src/app/shared/input/date/date.component.html new file mode 100644 index 00000000..db72476d --- /dev/null +++ b/src/app/shared/input/date/date.component.html @@ -0,0 +1,9 @@ + + {{ title }} + + {{ hint }} + + +
Invalid date.
+
diff --git a/src/app/shared/input/date/date.component.ts b/src/app/shared/input/date/date.component.ts new file mode 100644 index 00000000..94bf9030 --- /dev/null +++ b/src/app/shared/input/date/date.component.ts @@ -0,0 +1,33 @@ +import { AbstractInputComponent } from '../abstract-input'; +import { Component, forwardRef } from '@angular/core'; +import { NG_VALUE_ACCESSOR } from '@angular/forms'; + +/** + * Custom Input Date Component. + * + * Usage Example: + * ```html + + * ``` + */ +@Component({ + selector: 'app-input-date', + templateUrl: './date.component.html', + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => InputDateComponent), + multi: true + } + ] +}) +export class InputDateComponent extends AbstractInputComponent { + constructor() { + super(); + } +} diff --git a/src/app/shared/input/input.module.ts b/src/app/shared/input/input.module.ts new file mode 100644 index 00000000..e6d14811 --- /dev/null +++ b/src/app/shared/input/input.module.ts @@ -0,0 +1,64 @@ +import { CommonModule } from '@angular/common'; +import { ColorPickerModule } from 'ngx-color-picker'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { MatButtonModule } from '@angular/material/button'; +import { MatAutocompleteModule } from '@angular/material/autocomplete'; +import { MatCheckboxModule } from '@angular/material/checkbox'; +import { MatChipsModule } from '@angular/material/chips'; +import { MatDividerModule } from '@angular/material/divider'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatIconModule } from '@angular/material/icon'; +import { MatInputModule } from '@angular/material/input'; +import { MatOptionModule } from '@angular/material/core'; +import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; +import { MatSelectModule } from '@angular/material/select'; +import { MatTooltipModule } from '@angular/material/tooltip'; +import { MatDatepickerModule } from '@angular/material/datepicker'; +import { MatNativeDateModule } from '@angular/material/core'; +import { NgModule } from '@angular/core'; +import { InputMultiSelectComponent } from './multiselect/multiselect.component'; +import { InputColorComponent } from './color/color.component'; +import { InputCheckComponent } from './check/check.component'; +import { InputTextComponent } from './text/text.component'; +import { InputDateComponent } from './date/date.component'; +import { InputNumberComponent } from './number/number.component'; + +@NgModule({ + imports: [ + ColorPickerModule, + CommonModule, + FormsModule, + MatButtonModule, + MatAutocompleteModule, + MatCheckboxModule, + MatChipsModule, + MatDividerModule, + MatFormFieldModule, + MatIconModule, + MatInputModule, + MatOptionModule, + MatProgressSpinnerModule, + MatSelectModule, + MatTooltipModule, + MatDatepickerModule, + MatNativeDateModule, + ReactiveFormsModule + ], + exports: [ + InputCheckComponent, + InputColorComponent, + InputDateComponent, + InputMultiSelectComponent, + InputNumberComponent, + InputTextComponent + ], + declarations: [ + InputCheckComponent, + InputColorComponent, + InputDateComponent, + InputMultiSelectComponent, + InputNumberComponent, + InputTextComponent + ] +}) +export class InputModule {} diff --git a/src/app/shared/input/multiselect/multiselect.component.html b/src/app/shared/input/multiselect/multiselect.component.html new file mode 100644 index 00000000..0dace6cc --- /dev/null +++ b/src/app/shared/input/multiselect/multiselect.component.html @@ -0,0 +1,38 @@ +
+ + + {{label}} + +
+ + + + {{label}} + + + {{ item._id }} + ({{ item.name }}) + + + + + + + + + + + + + diff --git a/src/app/shared/input/multiselect/multiselect.component.ts b/src/app/shared/input/multiselect/multiselect.component.ts new file mode 100644 index 00000000..7c60f45c --- /dev/null +++ b/src/app/shared/input/multiselect/multiselect.component.ts @@ -0,0 +1,248 @@ +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + ElementRef, + EventEmitter, + Input, + OnInit, + Output, + ViewChild, + forwardRef +} from '@angular/core'; +import { COMMA, ENTER } from '@angular/cdk/keycodes'; +import { + AbstractControl, + FormControl, + NG_VALUE_ACCESSOR, + Validators +} from '@angular/forms'; +import { MatChipInputEvent } from '@angular/material/chips'; +import { Observable } from 'rxjs'; +import { MatAutocompleteSelectedEvent } from '@angular/material/autocomplete'; +import { map, startWith } from 'rxjs/operators'; +import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; + +interface SelectField { + _id: string; + name: string; +} +/** + * InputMultiSelectComponent for selecting or searching items from an array of objects. + * Supports dynamic filtering, highlighting, and emits selection changes. + */ +@Component({ + selector: 'app-input-multiselect', + templateUrl: 'multiselect.component.html', + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => InputMultiSelectComponent), + multi: true + } + ], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class InputMultiSelectComponent implements OnInit { + @Input() arrayOfObjects: SelectField[] = []; + @Input() label = 'Select or search:'; + @Input() placeholder = 'Select or search'; + @Input() isLoading = false; + @Input() mergeIdAndName = false; + @Input() externalControl: AbstractControl; + @Output() selectionChanged = new EventEmitter(); + + itemCtrl: AbstractControl; + highlightedValue: SafeHtml = ''; + readonly separatorKeysCodes: number[] = [ENTER, COMMA]; + filteredItems: Observable; + items: SelectField[] = []; + + @ViewChild('selectInput') selectInput: ElementRef; + + /** + * Constructs the MatAutocompleteComponent. + * @param cdr - ChangeDetectorRef for triggering change detection + * @param sanitizer - DomSanitizer for sanitizing HTML content + */ + constructor( + private cdr: ChangeDetectorRef, + private sanitizer: DomSanitizer + ) {} + + /** + * Initializes the MatAutocompleteComponent and sets up the observable for filtering items + */ + ngOnInit(): void { + this.itemCtrl = this.externalControl || this.createFormControl(); + + this.setupFilteredItemsObservable(); + + this.itemCtrl.valueChanges.subscribe((value: string) => { + this.updateHighlightedValue(value, this.itemCtrl.value); + this.cdr.markForCheck(); + }); + } + + private createFormControl(): FormControl { + return new FormControl('', Validators.required); + } + + /** + * Checks if a control is an instance of FormControl. + * @param control - The control to check. + * @returns True if the control is a FormControl, otherwise false. + */ + isFormControl(control: AbstractControl): control is FormControl { + return control instanceof FormControl; + } + + /** + * Handles the addition of a new item. + * @param event + */ + public addItem(event: MatChipInputEvent): void { + const value = (event.value || '').trim(); + + event.chipInput!.clear(); + + this.itemCtrl.setValue(null); + + this.selectionChanged.emit(this.items); + } + + /** + * Handles the removal of a selected item. + * @param item - The SelectField item to be removed. + */ + public remove(item: SelectField): void { + const index = this.items.indexOf(item); + + if (index >= 0) { + this.items.splice(index, 1); + + this.selectionChanged.emit(this.items); + + this.setupFilteredItemsObservable(); + } + } + + /** + * Handles the selection of an option If an option is selected, it adds the selected item to the 'items' array + * If no option is selected, it sets the selection error to true. + * @param event - The MatAutocompleteSelectedEvent representing the selected option. + */ + public selected(event: MatAutocompleteSelectedEvent): void { + this.items.push(event.option.value); + this.selectInput.nativeElement.value = ''; + this.setupFilteredItemsObservable(); + this.highlightedValue = ''; + this.selectionChanged.emit(this.items); + this.onChange(this.items); + this.onTouched(); + } + + private onChange: any = () => {}; + private onTouched: any = () => {}; + + writeValue(value: any): void { + if (value !== undefined) { + this.items = value; + this.cdr.detectChanges(); + } + } + + registerOnChange(fn: any): void { + this.onChange = fn; + } + + registerOnTouched(fn: any): void { + this.onTouched = fn; + } + + /** + * Sets up an observable for filtering items based on user input. + * + */ + private setupFilteredItemsObservable(): void { + this.filteredItems = this.itemCtrl.valueChanges.pipe( + startWith(null), + map((inputValue: string | null) => + inputValue ? this._filter(inputValue) : this.getUnselectedItems() + ) + ); + } + + /** + * Gets the unselected items from the array of objects. + * @returns An array of unselected items. + */ + private getUnselectedItems(): SelectField[] { + return this.arrayOfObjects.filter((item) => !this.items.includes(item)); + } + + /** + * Filters an array of SelectField items based on a search value. + * @param value - The search value used for filtering. + * @returns An array of SelectField items that match the search criteria. + */ + private _filter(value: string): SelectField[] { + const filterValue = value.toLowerCase(); + return this.arrayOfObjects.filter((item: SelectField) => { + const nameToSearch = this.mergeIdAndName + ? `${item._id} ${item.name}`.toLowerCase() + : item.name.toLowerCase(); + return nameToSearch.includes(filterValue); + }); + } + + /** + * Checks if a specific error is present in the FormControl. + * @param errorName - The name of the error to check. + * @returns True if the specified error is present, otherwise false. + */ + public hasError = (errorName: string): boolean => { + return this.itemCtrl.hasError(errorName); + }; + + /** + * Gets the error message for the selection based on the current FormControl state. + * @returns The error message or an empty string if no error is present. + */ + public getSelectionError(): string { + if ( + this.hasError('required') && + (this.itemCtrl.dirty || this.itemCtrl.touched) + ) { + return 'Please make a selection'; + } + + return ''; + } + + /** + * Updates the HTML representation of a value with highlighted characters. + * @param value - The original string value. + * @param term - The search term used for highlighting. + * @returns A SafeHtml object representing the HTML with highlighted characters. + */ + public updateHighlightedValue(value: string, term: string): SafeHtml { + if (typeof term === 'string' && typeof value === 'string') { + const pattern = term + .replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&') + .split(' ') + .filter((t) => t.length > 0) + .join('|'); + const regex = new RegExp(`(${pattern})`, 'gi'); + + const highlightedValue = value.replace( + regex, + (match) => `${match}` + ); + + return this.sanitizer.bypassSecurityTrustHtml(highlightedValue); + } else { + return this.sanitizer.bypassSecurityTrustHtml(value); + } + } +} diff --git a/src/app/shared/input/number/number.component.html b/src/app/shared/input/number/number.component.html new file mode 100644 index 00000000..69a0aa12 --- /dev/null +++ b/src/app/shared/input/number/number.component.html @@ -0,0 +1,8 @@ + + {{ title }} +
+ +
+
{{ error }}
+ {{ hint }} +
diff --git a/src/app/shared/input/number/number.component.ts b/src/app/shared/input/number/number.component.ts new file mode 100644 index 00000000..b8d891e5 --- /dev/null +++ b/src/app/shared/input/number/number.component.ts @@ -0,0 +1,32 @@ +import { AbstractInputComponent } from '../abstract-input'; +import { Component, forwardRef } from '@angular/core'; +import { NG_VALUE_ACCESSOR } from '@angular/forms'; + +/** + * Custom Input Number Component. + * + * Usage Example: + * ```html + * + * ``` + */ +@Component({ + selector: 'app-input-number', + templateUrl: './number.component.html', + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => InputNumberComponent), + multi: true + } + ] +}) +export class InputNumberComponent extends AbstractInputComponent { + constructor() { + super(); + } +} diff --git a/src/app/shared/form/select-field/select-field.component.html b/src/app/shared/input/select/select-field.component.html similarity index 100% rename from src/app/shared/form/select-field/select-field.component.html rename to src/app/shared/input/select/select-field.component.html diff --git a/src/app/shared/form/select-field/select-field.component.ts b/src/app/shared/input/select/select-field.component.ts similarity index 100% rename from src/app/shared/form/select-field/select-field.component.ts rename to src/app/shared/input/select/select-field.component.ts diff --git a/src/app/shared/input/text/text.component.html b/src/app/shared/input/text/text.component.html new file mode 100644 index 00000000..0121aaae --- /dev/null +++ b/src/app/shared/input/text/text.component.html @@ -0,0 +1,24 @@ + + {{ title }} + + + + {{ hint }} + {{ error }} + diff --git a/src/app/shared/input/text/text.component.ts b/src/app/shared/input/text/text.component.ts new file mode 100644 index 00000000..17ad571a --- /dev/null +++ b/src/app/shared/input/text/text.component.ts @@ -0,0 +1,34 @@ +import { AbstractInputComponent } from '../abstract-input'; +import { Component, forwardRef } from '@angular/core'; +import { NG_VALUE_ACCESSOR } from '@angular/forms'; + +/** + * Custom Input Text Component. + * + * Usage Example: + * ```html + * + * ``` + */ +@Component({ + selector: 'app-input-text', + templateUrl: './text.component.html', + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => InputTextComponent), + multi: true + } + ] +}) +export class InputTextComponent extends AbstractInputComponent { + constructor() { + super(); + } +} diff --git a/src/app/shared/pipes.module.ts b/src/app/shared/pipes.module.ts index 0b703c15..29e4dd74 100644 --- a/src/app/shared/pipes.module.ts +++ b/src/app/shared/pipes.module.ts @@ -1,31 +1,31 @@ -import { CommonModule } from "@angular/common"; -import { NgModule } from "@angular/core"; +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; -import { HealthCheckStatusPipe } from "../core/_pipes/healthcheck-status.pipe"; -import { TaskDispatchedPipe } from "../core/_pipes/task-dispatched.pipe"; -import { TaskTimeSpentPipe } from "../core/_pipes/task-timespent.pipe"; -import { ReplaceStringPipe } from "../core/_pipes/replace-string.pipe"; -import { ShortenStringPipe } from "../core/_pipes/shorten-string.pipe"; -import { SecondsToTimePipe } from "../core/_pipes/secondsto-time.pipe"; -import { AgentSColorPipe } from "../core/_pipes/agentstat-color.pipe"; -import { WarningColorPipe } from "../core/_pipes/warning-color.pipe"; -import { TaskSearchedPipe } from "../core/_pipes/task-searched.pipe"; -import { HashesFilterPipe } from "../core/_pipes/hashes-filter.pipe"; -import { KeyspaceCalcPipe } from "../core/_pipes/keyspace-calc.pipe"; -import { StaticArrayPipe } from "../core/_pipes/static-array.pipe"; -import { MaximizePipe } from "../core/_pipes/maximize-object.pipe"; -import { TaskCrackedPipe } from "../core/_pipes/task-cracked.pipe"; -import { AgentsSpeedPipe } from "../core/_pipes/agents-speed.pipe"; -import { ArraySortPipe } from "../core/_pipes/orderby-item.pipe"; -import { AveragePipe } from "../core/_pipes/average-object.pipe"; -import { FilterItemPipe } from "../core/_pipes/filter-item.pipe"; -import { SearchPipe } from "../core/_pipes/filter-search.pipe"; -import { FileSizePipe } from "../core/_pipes/file-size.pipe"; -import { FileTypePipe } from "../core/_pipes/file-type.pipe"; -import { GroupByPipe } from "../core/_pipes/groupby.pipe"; -import { SumPipe } from "../core/_pipes/sum-object.pipe"; -import { SplitPipe } from "../core/_pipes/split.pipe"; -import { uiDatePipe } from "../core/_pipes/date.pipe"; +import { HealthCheckStatusPipe } from '../core/_pipes/healthcheck-status.pipe'; +import { TaskDispatchedPipe } from '../core/_pipes/task-dispatched.pipe'; +import { TaskTimeSpentPipe } from '../core/_pipes/task-timespent.pipe'; +import { ReplaceStringPipe } from '../core/_pipes/replace-string.pipe'; +import { ShortenStringPipe } from '../core/_pipes/shorten-string.pipe'; +import { SecondsToTimePipe } from '../core/_pipes/secondsto-time.pipe'; +import { AgentSColorPipe } from '../core/_pipes/agentstat-color.pipe'; +import { WarningColorPipe } from '../core/_pipes/warning-color.pipe'; +import { TaskSearchedPipe } from '../core/_pipes/task-searched.pipe'; +import { HashesFilterPipe } from '../core/_pipes/hashes-filter.pipe'; +import { KeyspaceCalcPipe } from '../core/_pipes/keyspace-calc.pipe'; +import { StaticArrayPipe } from '../core/_pipes/static-array.pipe'; +import { MaximizePipe } from '../core/_pipes/maximize-object.pipe'; +import { TaskCrackedPipe } from '../core/_pipes/task-cracked.pipe'; +import { AgentsSpeedPipe } from '../core/_pipes/agents-speed.pipe'; +import { ArraySortPipe } from '../core/_pipes/orderby-item.pipe'; +import { AveragePipe } from '../core/_pipes/average-object.pipe'; +import { FilterItemPipe } from '../core/_pipes/filter-item.pipe'; +import { SearchPipe } from '../core/_pipes/filter-search.pipe'; +import { FileSizePipe } from '../core/_pipes/file-size.pipe'; +import { FileTypePipe } from '../core/_pipes/file-type.pipe'; +import { GroupByPipe } from '../core/_pipes/groupby.pipe'; +import { SumPipe } from '../core/_pipes/sum-object.pipe'; +import { SplitPipe } from '../core/_pipes/split.pipe'; +import { uiDatePipe } from '../core/_pipes/date.pipe'; @NgModule({ declarations: [ @@ -85,6 +85,3 @@ import { uiDatePipe } from "../core/_pipes/date.pipe"; ] }) export class PipesModule {} - - - diff --git a/src/app/shared/utils/forms.ts b/src/app/shared/utils/forms.ts index 75e47320..e2d8c56f 100644 --- a/src/app/shared/utils/forms.ts +++ b/src/app/shared/utils/forms.ts @@ -5,7 +5,6 @@ * Comments use: https://tsdoc.org/ */ - /** * Show / Hide elements in the form * Used in; New Hashlist @@ -17,9 +16,9 @@ */ export function ShowHideTypeFile(checkbox: string): void { - const pasteObject = document.getElementById("pasteLine"); - const uploadObject = document.getElementById("uploadLine"); - const urlObject = document.getElementById("urlLine"); + const pasteObject = document.getElementById('pasteLine'); + const uploadObject = document.getElementById('uploadLine'); + const urlObject = document.getElementById('urlLine'); switch (checkbox) { case 'paste': pasteObject.style.display = ''; @@ -38,6 +37,28 @@ export function ShowHideTypeFile(checkbox: string): void { uploadObject.style.display = 'none'; urlObject.style.display = ''; break; - } } + +/** + * Extract Ids + * Used extract ids after mat-autcomplete component + * + * @param dataArray + * @param idKey + * @returns Values [1,2,3] + * ``` + * @public + */ + +export function extractIds(dataArray: any[], idKey: string): number[] { + return dataArray + .map((item) => { + let id = null; + if (Object.prototype.hasOwnProperty.call(item, idKey)) { + id = item[idKey]; + } + return id; + }) + .filter((id) => id !== null) as number[]; +} diff --git a/src/styles/components/_form.scss b/src/styles/components/_form.scss index 27f63c32..76fe7fc1 100644 --- a/src/styles/components/_form.scss +++ b/src/styles/components/_form.scss @@ -13,6 +13,8 @@ $btn-text-color: #ffffff; $btn-bg-color: #1986b1; $btn-border-color: #1986b1; +$search-text-highlight-color: #96dcf7; + /* 01- Highlight validation in red */ @@ -273,6 +275,34 @@ progress::-webkit-progress-value { 07- End */ +/* + 08- Mat Autocomplete +*/ + +.custom-chip-grid { + width: 100%; +} + +.highlight-text { + background-color: $search-text-highlight-color; +} + + +/* + 08- End +*/ + +/* + 09- Shared/Input +*/ + + +/* + 09 +*/ + + + $custom-form-min-width: 150px; $custom-form-max-width: 500px; From 4fd2fe9995bbe18e8b559471f6069ab933010396 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Fri, 17 Nov 2023 16:12:43 +0000 Subject: [PATCH 277/419] Change Folder Struture and update paths --- src/app/account/account-routing.module.ts | 2 +- src/app/auth/auth.module.ts | 3 +- src/app/config/config-routing.module.ts | 4 +- .../forms/simple-forms}/form.component.html | 0 .../forms/simple-forms}/form.component.ts | 0 .../simple-forms/formconfig.component.html | 11 ++++ .../simple-forms}/formconfig.component.ts | 16 +----- src/app/files/files-routing.module.ts | 3 +- .../new-superhashlist.component.html | 1 - src/app/shared/components.module.ts | 2 +- .../dynamicform.component.html | 0 .../dynamicform.component.ts | 0 .../dynamicform.module.ts | 4 +- .../shared/input/color/color.component.html | 39 +++++++++------ src/app/shared/input/color/color.component.ts | 33 +++++++----- src/app/shared/input/input.module.ts | 7 ++- .../input/text-area/text-area.component.html | 26 ++++++++++ .../input/text-area/text-area.component.ts | 34 +++++++++++++ src/app/shared/utils/forms.ts | 50 +++++++++++++++++++ src/app/users/users-routing.module.ts | 2 +- src/styles/components/_form.scss | 17 ++++++- 21 files changed, 196 insertions(+), 58 deletions(-) rename src/app/{shared/form => core/_components/forms/simple-forms}/form.component.html (100%) rename src/app/{shared/form => core/_components/forms/simple-forms}/form.component.ts (100%) create mode 100644 src/app/core/_components/forms/simple-forms/formconfig.component.html rename src/app/{shared/form => core/_components/forms/simple-forms}/formconfig.component.ts (94%) rename src/app/shared/{form => dynamic-form-builder}/dynamicform.component.html (100%) rename src/app/shared/{form => dynamic-form-builder}/dynamicform.component.ts (100%) rename src/app/shared/{form => dynamic-form-builder}/dynamicform.module.ts (92%) create mode 100644 src/app/shared/input/text-area/text-area.component.html create mode 100644 src/app/shared/input/text-area/text-area.component.ts diff --git a/src/app/account/account-routing.module.ts b/src/app/account/account-routing.module.ts index 6f6fe41d..cef28045 100644 --- a/src/app/account/account-routing.module.ts +++ b/src/app/account/account-routing.module.ts @@ -6,9 +6,9 @@ import { AccountSettingsComponent } from './settings/acc-settings/acc-settings.c import { UiSettingsComponent } from './settings/ui-settings/ui-settings.component'; import { NewNotificationComponent } from './notifications/notification/new-notification.component'; import { NotificationsComponent } from './notifications/notifications.component'; +import { FormComponent } from '../core/_components/forms/simple-forms/form.component'; import { AccountComponent } from './account.component'; import { SERV } from '../core/_services/main.config'; -import { FormComponent } from '../shared/form/form.component'; import { MyRoute, RouteData } from '../core/_models/routes.model'; const routes: MyRoute[] = [ diff --git a/src/app/auth/auth.module.ts b/src/app/auth/auth.module.ts index 21a19a75..c3c4bf7c 100644 --- a/src/app/auth/auth.module.ts +++ b/src/app/auth/auth.module.ts @@ -1,3 +1,4 @@ +import { FormComponent } from '../core/_components/forms/simple-forms/form.component'; import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { IsAuth } from '../core/_guards/auth.guard'; @@ -14,10 +15,8 @@ import { MatInputModule } from '@angular/material/input'; import { MatSelectModule } from '@angular/material/select'; import { MatToolbarModule } from '@angular/material/toolbar'; import { NgModule } from '@angular/core'; - import { ComponentsModule } from '../shared/components.module'; import { AuthComponent } from './auth.component'; -import { FormComponent } from '../shared/form/form.component'; @NgModule({ declarations: [AuthComponent], diff --git a/src/app/config/config-routing.module.ts b/src/app/config/config-routing.module.ts index 9e3660a6..3e4b6fad 100644 --- a/src/app/config/config-routing.module.ts +++ b/src/app/config/config-routing.module.ts @@ -8,11 +8,11 @@ import { NewHealthChecksComponent } from './health-checks/new-health-check/new-h import { AgentBinariesComponent } from './engine/agent-binaries/agent-binaries.component'; import { PreprocessorsComponent } from './engine/preprocessors/preprocessors.component'; import { HealthChecksComponent } from './health-checks/health-checks.component'; -import { FormConfigComponent } from '../shared/form/formconfig.component'; +import { FormConfigComponent } from '../core/_components/forms/simple-forms/formconfig.component'; +import { FormComponent } from '../core/_components/forms/simple-forms/form.component'; import { CrackersComponent } from './engine/crackers/crackers.component'; import { HashtypesComponent } from './hashtypes/hashtypes.component'; import { MyRoute, RouteData } from '../core/_models/routes.model'; -import { FormComponent } from '../shared/form/form.component'; import { SERV } from '../core/_services/main.config'; import { LogComponent } from './log/log.component'; diff --git a/src/app/shared/form/form.component.html b/src/app/core/_components/forms/simple-forms/form.component.html similarity index 100% rename from src/app/shared/form/form.component.html rename to src/app/core/_components/forms/simple-forms/form.component.html diff --git a/src/app/shared/form/form.component.ts b/src/app/core/_components/forms/simple-forms/form.component.ts similarity index 100% rename from src/app/shared/form/form.component.ts rename to src/app/core/_components/forms/simple-forms/form.component.ts diff --git a/src/app/core/_components/forms/simple-forms/formconfig.component.html b/src/app/core/_components/forms/simple-forms/formconfig.component.html new file mode 100644 index 00000000..68fb9d10 --- /dev/null +++ b/src/app/core/_components/forms/simple-forms/formconfig.component.html @@ -0,0 +1,11 @@ + + diff --git a/src/app/shared/form/formconfig.component.ts b/src/app/core/_components/forms/simple-forms/formconfig.component.ts similarity index 94% rename from src/app/shared/form/formconfig.component.ts rename to src/app/core/_components/forms/simple-forms/formconfig.component.ts index b42f753e..286e67f2 100644 --- a/src/app/shared/form/formconfig.component.ts +++ b/src/app/core/_components/forms/simple-forms/formconfig.component.ts @@ -9,24 +9,12 @@ import { AlertService } from 'src/app/core/_services/shared/alert.service'; import { MetadataService } from 'src/app/core/_services/metadata.service'; import { HorizontalNav } from 'src/app/core/_models/horizontalnav.model'; import { GlobalService } from 'src/app/core/_services/main.service'; -import { SERV } from '../../core/_services/main.config'; +import { SERV } from '../../../_services/main.config'; import { Subscription } from 'rxjs'; @Component({ selector: 'app-form', - template: ` - - - ` + templateUrl: 'formconfig.component.html' }) /** * Component for managing forms, supporting both create and edit modes. diff --git a/src/app/files/files-routing.module.ts b/src/app/files/files-routing.module.ts index 5cc6ffe8..0e6a306b 100644 --- a/src/app/files/files-routing.module.ts +++ b/src/app/files/files-routing.module.ts @@ -2,10 +2,9 @@ import { CheckPerm } from '../core/_guards/permission.guard'; import { RouterModule, Routes } from '@angular/router'; import { IsAuth } from '../core/_guards/auth.guard'; import { NgModule } from '@angular/core'; - +import { FormComponent } from '../core/_components/forms/simple-forms/form.component'; import { NewFilesComponent } from './new-files/new-files.component'; import { MyRoute, RouteData } from '../core/_models/routes.model'; -import { FormComponent } from '../shared/form/form.component'; import { SERV } from '../core/_services/main.config'; import { FilesComponent } from './files.component'; diff --git a/src/app/hashlists/new-superhashlist/new-superhashlist.component.html b/src/app/hashlists/new-superhashlist/new-superhashlist.component.html index 64b5fb18..813e3bfb 100644 --- a/src/app/hashlists/new-superhashlist/new-superhashlist.component.html +++ b/src/app/hashlists/new-superhashlist/new-superhashlist.component.html @@ -24,4 +24,3 @@
- diff --git a/src/app/shared/components.module.ts b/src/app/shared/components.module.ts index d92e62e9..b870a6dc 100644 --- a/src/app/shared/components.module.ts +++ b/src/app/shared/components.module.ts @@ -18,7 +18,7 @@ import { TimeoutComponent } from './alert/timeout/timeout.component'; import { HorizontalNavModule } from './navigation/navigation.module'; import { PageTitleModule } from './page-headers/page-title.module'; import { PaginationModule } from './pagination/pagination.module'; -import { DynamicFormModule } from './form/dynamicform.module'; +import { DynamicFormModule } from './dynamic-form-builder/dynamicform.module'; import { MatDialogModule } from '@angular/material/dialog'; import { MatButtonModule } from '@angular/material/button'; import { GridModule } from './grid-containers/grid.module'; diff --git a/src/app/shared/form/dynamicform.component.html b/src/app/shared/dynamic-form-builder/dynamicform.component.html similarity index 100% rename from src/app/shared/form/dynamicform.component.html rename to src/app/shared/dynamic-form-builder/dynamicform.component.html diff --git a/src/app/shared/form/dynamicform.component.ts b/src/app/shared/dynamic-form-builder/dynamicform.component.ts similarity index 100% rename from src/app/shared/form/dynamicform.component.ts rename to src/app/shared/dynamic-form-builder/dynamicform.component.ts diff --git a/src/app/shared/form/dynamicform.module.ts b/src/app/shared/dynamic-form-builder/dynamicform.module.ts similarity index 92% rename from src/app/shared/form/dynamicform.module.ts rename to src/app/shared/dynamic-form-builder/dynamicform.module.ts index 871174b5..83064fde 100644 --- a/src/app/shared/form/dynamicform.module.ts +++ b/src/app/shared/dynamic-form-builder/dynamicform.module.ts @@ -4,8 +4,8 @@ import { DynamicFormComponent } from './dynamicform.component'; import { NgModule } from '@angular/core'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; -import { FormComponent } from './form.component'; -import { FormConfigComponent } from './formconfig.component'; +import { FormComponent } from 'src/app/core/_components/forms/simple-forms/form.component'; +import { FormConfigComponent } from '../../core/_components/forms/simple-forms/formconfig.component'; import { GridModule } from '../grid-containers/grid.module'; import { HorizontalNavModule } from '../navigation/navigation.module'; import { MatDividerModule } from '@angular/material/divider'; diff --git a/src/app/shared/input/color/color.component.html b/src/app/shared/input/color/color.component.html index fdf8ccbe..262ea3bc 100644 --- a/src/app/shared/input/color/color.component.html +++ b/src/app/shared/input/color/color.component.html @@ -1,16 +1,25 @@ - - {{ title }} - - +
+ + {{ title }} + + +
+ +
+
diff --git a/src/app/shared/input/color/color.component.ts b/src/app/shared/input/color/color.component.ts index c75b1175..79817d65 100644 --- a/src/app/shared/input/color/color.component.ts +++ b/src/app/shared/input/color/color.component.ts @@ -1,13 +1,14 @@ -import { AbstractControl, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { NG_VALUE_ACCESSOR } from '@angular/forms'; import { Component, - EventEmitter, + ElementRef, Input, - OnInit, - Output, + ViewChild, forwardRef } from '@angular/core'; +import { randomColor } from '../../../shared/utils/forms'; import { AbstractInputComponent } from '../abstract-input'; +import { ChangeDetectorRef } from '@angular/core'; /** * Custom Input Color Picker Component. @@ -17,12 +18,10 @@ import { AbstractInputComponent } from '../abstract-input'; * * Usage Example: * ```html - * + * ``` */ @Component({ @@ -37,11 +36,11 @@ import { AbstractInputComponent } from '../abstract-input'; ] }) export class InputColorComponent extends AbstractInputComponent { - constructor() { + constructor(private cdr: ChangeDetectorRef) { super(); } - - @Input() defaultColor = '#FFFFFF'; + @ViewChild('colorInput') colorInput: ElementRef; + @Input() defaultColor = ''; /** * List of preset colors for the color picker. @@ -56,8 +55,16 @@ export class InputColorComponent extends AbstractInputComponent { '#7ad54d' //Green ]; + generateRandomColor() { + this.onChangeValue(randomColor()); + this.cdr.detectChanges(); + } + onChangeValue(value) { this.value = value; this.onChange(value); + // When using generateRandomColor() dom needs to be update to reflect color change + const inputElement = this.colorInput.nativeElement; + inputElement.style.background = this.value; } } diff --git a/src/app/shared/input/input.module.ts b/src/app/shared/input/input.module.ts index e6d14811..b19ee564 100644 --- a/src/app/shared/input/input.module.ts +++ b/src/app/shared/input/input.module.ts @@ -22,6 +22,7 @@ import { InputCheckComponent } from './check/check.component'; import { InputTextComponent } from './text/text.component'; import { InputDateComponent } from './date/date.component'; import { InputNumberComponent } from './number/number.component'; +import { InputTextAreaComponent } from './text-area/text-area.component'; @NgModule({ imports: [ @@ -50,7 +51,8 @@ import { InputNumberComponent } from './number/number.component'; InputDateComponent, InputMultiSelectComponent, InputNumberComponent, - InputTextComponent + InputTextComponent, + InputTextAreaComponent ], declarations: [ InputCheckComponent, @@ -58,7 +60,8 @@ import { InputNumberComponent } from './number/number.component'; InputDateComponent, InputMultiSelectComponent, InputNumberComponent, - InputTextComponent + InputTextComponent, + InputTextAreaComponent ] }) export class InputModule {} diff --git a/src/app/shared/input/text-area/text-area.component.html b/src/app/shared/input/text-area/text-area.component.html new file mode 100644 index 00000000..a0be9e97 --- /dev/null +++ b/src/app/shared/input/text-area/text-area.component.html @@ -0,0 +1,26 @@ + + {{ title }} + + + + {{ hint }} + {{ error }} + diff --git a/src/app/shared/input/text-area/text-area.component.ts b/src/app/shared/input/text-area/text-area.component.ts new file mode 100644 index 00000000..3b7a9861 --- /dev/null +++ b/src/app/shared/input/text-area/text-area.component.ts @@ -0,0 +1,34 @@ +import { AbstractInputComponent } from '../abstract-input'; +import { Component, forwardRef } from '@angular/core'; +import { NG_VALUE_ACCESSOR } from '@angular/forms'; + +/** + * Custom Input Text Component. + * + * Usage Example: + * ```html + * + * ``` + */ +@Component({ + selector: 'app-input-text-area-area', + templateUrl: './text-area.component.html', + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => InputTextAreaComponent), + multi: true + } + ] +}) +export class InputTextAreaComponent extends AbstractInputComponent { + constructor() { + super(); + } +} diff --git a/src/app/shared/utils/forms.ts b/src/app/shared/utils/forms.ts index e2d8c56f..5dd0b1a6 100644 --- a/src/app/shared/utils/forms.ts +++ b/src/app/shared/utils/forms.ts @@ -62,3 +62,53 @@ export function extractIds(dataArray: any[], idKey: string): number[] { }) .filter((id) => id !== null) as number[]; } + +/** + * https://axonflux.com/handy-rgb-to-hsl-and-rgb-to-hsv-color-model-c + * + * Converts an HSL color value to RGB. Conversion formula + * adapted from http://en.wikipedia.org/wiki/HSL_color_space. + * Assumes h, s, and l are contained in the set [0, 1] and + * returns r, g, and b in the set [0, 255]. + * + * @param Number h The hue + * @param Number s The saturation + * @param Number l The lightness + * @return Array The RGB representation + */ +function hslToRgb(h, s, l) { + let r, g, b; + + if (s == 0) { + r = g = b = l; // achromatic + } else { + function hue2rgb(p, q, t) { + if (t < 0) t += 1; + if (t > 1) t -= 1; + if (t < 1 / 6) return p + (q - p) * 6 * t; + if (t < 1 / 2) return q; + if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6; + return p; + } + + const q = l < 0.5 ? l * (1 + s) : l + s - l * s; + const p = 2 * l - q; + r = hue2rgb(p, q, h + 1 / 3); + g = hue2rgb(p, q, h); + b = hue2rgb(p, q, h - 1 / 3); + } + + return [r * 255, g * 255, b * 255]; +} + +function componentToHex(c) { + const hex = Math.floor(c).toString(16); + return hex.length == 1 ? '0' + hex : hex; +} + +export function randomColor() { + const rgb = hslToRgb(Math.random(), 0.6, Math.random() * 0.4 + 0.4); + return `#${componentToHex(rgb[0])}${componentToHex(rgb[1])}${componentToHex( + rgb[2] + )}`; +} diff --git a/src/app/users/users-routing.module.ts b/src/app/users/users-routing.module.ts index f1d27d8b..70a462f2 100644 --- a/src/app/users/users-routing.module.ts +++ b/src/app/users/users-routing.module.ts @@ -5,10 +5,10 @@ import { NgModule } from '@angular/core'; import { EditGlobalpermissionsgroupsComponent } from './globalpermissionsgroups/edit-globalpermissionsgroups/edit-globalpermissionsgroups.component'; import { GlobalpermissionsgroupsComponent } from './globalpermissionsgroups/globalpermissionsgroups.component'; +import { FormComponent } from '../core/_components/forms/simple-forms/form.component'; import { EditUsersComponent } from './edit-users/edit-users.component'; import { AllUsersComponent } from './all-users/all-users.component'; import { MyRoute, RouteData } from '../core/_models/routes.model'; -import { FormComponent } from '../shared/form/form.component'; import { GroupsComponent } from './groups/groups.component'; import { SERV } from '../core/_services/main.config'; diff --git a/src/styles/components/_form.scss b/src/styles/components/_form.scss index 76fe7fc1..d3a07c0e 100644 --- a/src/styles/components/_form.scss +++ b/src/styles/components/_form.scss @@ -296,13 +296,26 @@ progress::-webkit-progress-value { 09- Shared/Input */ +.color-picker-container { + display: flex; + align-items: center; /* Align items vertically */ +} + +.color-picker-field { + flex: 1; /* Allow the mat-form-field to grow and take available space */ + margin-right: 16px; /* Adjust the margin between the field and the button */ +} + +.custom-textarea { + width: 100%; + min-width: 300px; /* Set your desired minimum width */ + max-width: 600px; /* Set your desired maximum width */ +} /* 09 */ - - $custom-form-min-width: 150px; $custom-form-max-width: 500px; From 6973bca18baef3feeb1600f568e064141456503f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Fri, 17 Nov 2023 16:41:41 +0000 Subject: [PATCH 278/419] Creating a Select component --- src/app/shared/input/input.module.ts | 3 ++ .../input/select/select-field.component.html | 11 ----- .../input/select/select-field.component.ts | 11 ----- .../shared/input/select/select.component.html | 21 +++++++++ .../shared/input/select/select.component.ts | 43 +++++++++++++++++++ 5 files changed, 67 insertions(+), 22 deletions(-) delete mode 100644 src/app/shared/input/select/select-field.component.html delete mode 100644 src/app/shared/input/select/select-field.component.ts create mode 100644 src/app/shared/input/select/select.component.html create mode 100644 src/app/shared/input/select/select.component.ts diff --git a/src/app/shared/input/input.module.ts b/src/app/shared/input/input.module.ts index b19ee564..1bb0d92f 100644 --- a/src/app/shared/input/input.module.ts +++ b/src/app/shared/input/input.module.ts @@ -23,6 +23,7 @@ import { InputTextComponent } from './text/text.component'; import { InputDateComponent } from './date/date.component'; import { InputNumberComponent } from './number/number.component'; import { InputTextAreaComponent } from './text-area/text-area.component'; +import { SelectComponent } from './select/select.component'; @NgModule({ imports: [ @@ -51,6 +52,7 @@ import { InputTextAreaComponent } from './text-area/text-area.component'; InputDateComponent, InputMultiSelectComponent, InputNumberComponent, + SelectComponent, InputTextComponent, InputTextAreaComponent ], @@ -60,6 +62,7 @@ import { InputTextAreaComponent } from './text-area/text-area.component'; InputDateComponent, InputMultiSelectComponent, InputNumberComponent, + SelectComponent, InputTextComponent, InputTextAreaComponent ] diff --git a/src/app/shared/input/select/select-field.component.html b/src/app/shared/input/select/select-field.component.html deleted file mode 100644 index d9e4abc0..00000000 --- a/src/app/shared/input/select/select-field.component.html +++ /dev/null @@ -1,11 +0,0 @@ - - - {{ option.name }} - - - - - diff --git a/src/app/shared/input/select/select-field.component.ts b/src/app/shared/input/select/select-field.component.ts deleted file mode 100644 index ec0abbce..00000000 --- a/src/app/shared/input/select/select-field.component.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Component, EventEmitter, Input, Output } from '@angular/core'; - -@Component({ - selector: 'app-select-field', - templateUrl: 'select-field.component.html' -}) -export class SelectFieldComponent { - @Input() fieldName: string; - @Input() selectOptions: any[]; - @Input() isLoading: boolean; -} diff --git a/src/app/shared/input/select/select.component.html b/src/app/shared/input/select/select.component.html new file mode 100644 index 00000000..7eb12494 --- /dev/null +++ b/src/app/shared/input/select/select.component.html @@ -0,0 +1,21 @@ + + {{ title }} + + + + + {{ option.name }} + + + {{ hint }} + {{ error }} + diff --git a/src/app/shared/input/select/select.component.ts b/src/app/shared/input/select/select.component.ts new file mode 100644 index 00000000..c525fe51 --- /dev/null +++ b/src/app/shared/input/select/select.component.ts @@ -0,0 +1,43 @@ +import { Component, Input } from '@angular/core'; +import { AbstractInputComponent } from '../abstract-input'; + +@Component({ + selector: 'app-input-select', + templateUrl: './select.component.html' +}) +export class SelectComponent extends AbstractInputComponent { + @Input() items: any[]; + @Input() allowNull = false; + @Input() suggestions: number[]; + + // Add a property to hold the sorting order + sortOrder: 'asc' | 'desc' = 'asc'; + + // Function to sort options based on the current sortOrder + sortedOptions(): any[] { + if (!this.items) { + return []; + } + + // Clone the array to avoid modifying the original array + const sortedItems = [...this.items]; + + sortedItems.sort((a, b) => { + const nameA = a.name.toUpperCase(); + const nameB = b.name.toUpperCase(); + + if (this.sortOrder === 'asc') { + return nameA.localeCompare(nameB); + } else { + return nameB.localeCompare(nameA); + } + }); + + return sortedItems; + } + + // Function to toggle the sorting order + toggleSortOrder() { + this.sortOrder = this.sortOrder === 'asc' ? 'desc' : 'asc'; + } +} From bbdff40327fae081cb4e730b4d1ee010969a6ba7 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Fri, 17 Nov 2023 19:59:40 +0100 Subject: [PATCH 279/419] Suppert trigger menu on mouse over + implement in header --- .../action-menu/action-menu.component.html | 98 ++++++++++----- .../action-menu/action-menu.component.ts | 119 ++++++++++++++++++ src/app/layout/header/header.component.html | 1 + src/app/layout/header/header.component.ts | 1 + src/styles/layout/_header.scss | 6 +- 5 files changed, 195 insertions(+), 30 deletions(-) diff --git a/src/app/core/_components/menus/action-menu/action-menu.component.html b/src/app/core/_components/menus/action-menu/action-menu.component.html index 39403187..6fd05417 100644 --- a/src/app/core/_components/menus/action-menu/action-menu.component.html +++ b/src/app/core/_components/menus/action-menu/action-menu.component.html @@ -1,29 +1,69 @@ - - - - - - - + + + +
+ + + + +
+
+
+ + + + + + + + + diff --git a/src/app/core/_components/menus/action-menu/action-menu.component.ts b/src/app/core/_components/menus/action-menu/action-menu.component.ts index aa3d7c80..2741ef46 100644 --- a/src/app/core/_components/menus/action-menu/action-menu.component.ts +++ b/src/app/core/_components/menus/action-menu/action-menu.component.ts @@ -11,6 +11,7 @@ import { Output } from '@angular/core'; +import { MatMenuTrigger } from '@angular/material/menu'; import { Subscription } from 'rxjs'; /** @@ -44,6 +45,7 @@ export class ActionMenuComponent implements OnInit, OnDestroy { currentUrl: any[]; isActive = false; + isOpen = false; /** Icon to be displayed in the menu button. */ @Input() icon: string; @@ -58,9 +60,16 @@ export class ActionMenuComponent implements OnInit, OnDestroy { /** Two-dimensional array of sections / menu items. */ @Input() actionMenuItems: ActionMenuItem[][] = []; + @Input() openOnMouseEnter = false; + @Output() menuItemClicked: EventEmitter> = new EventEmitter>(); + timedOutCloser: NodeJS.Timeout; + timedOutOpener: NodeJS.Timeout; + + openSubmenus: MatMenuTrigger[] = []; + constructor( private router: Router, private route: ActivatedRoute @@ -128,4 +137,114 @@ export class ActionMenuComponent implements OnInit, OnDestroy { }); } } + + /** + * Handle mouse enter event for the menu. + * @param trigger - The MatMenuTrigger associated with the menu. + */ + menuEnter(trigger: MatMenuTrigger): void { + if (this.timedOutCloser) { + clearTimeout(this.timedOutCloser); + } + + if (!trigger.menuOpen) { + this.timedOutOpener = setTimeout(() => { + this.openSubmenus.push(trigger); + trigger.openMenu(); + }, 50); + } + } + + /** + * Handle mouse leave event for the menu. + * @param trigger - The MatMenuTrigger associated with the menu. + */ + menuLeave(trigger: MatMenuTrigger): void { + if (this.timedOutOpener) { + clearTimeout(this.timedOutOpener); + } + + if (trigger.menuOpen) { + this.timedOutCloser = setTimeout(() => { + this.closeAllSubmenus(); + }, 100); + } + } + + /** + * Handle mouse enter event for a submenu. + */ + subMenuEnter(trigger: MatMenuTrigger): void { + if (this.timedOutCloser) { + clearTimeout(this.timedOutCloser); + } + + this.timedOutOpener = setTimeout(() => { + this.openSubmenus.push(trigger); + trigger.openMenu(); + }, 50); + } + + /** + * Handle mouse leave event for a submenu. + */ + subMenuLeave(): void { + if (this.timedOutOpener) { + clearTimeout(this.timedOutOpener); + } + + this.timedOutCloser = setTimeout(() => { + this.closeAllSubmenus(); + }, 100); + } + + /** + * Check if the related target is inside the menu panel. + * @param event - The mouse event. + * @param trigger - The MatMenuTrigger associated with the menu. + * @returns True if the related target is inside the menu panel, false otherwise. + */ + isRelatedTargetInPanel(event: MouseEvent, trigger: MatMenuTrigger): boolean { + const panelId = trigger.menu?.panelId; + + if (panelId && event.relatedTarget instanceof Element) { + const relatedTargetInPanel = event.relatedTarget.closest(`#${panelId}`); + const isAnotherMenuContent = event.relatedTarget.classList.contains( + 'mat-mdc-menu-content' + ); + + return relatedTargetInPanel && !isAnotherMenuContent; + } + + return false; + } + + /** + * Prevent focus on the button. + * @param event - The focus event. + */ + preventFocus(event: FocusEvent): void { + // Prevent the focus and blur the button immediately + event.preventDefault(); + (event.target as HTMLElement).blur(); + } + + /** + * Close all open submenus. + */ + closeAllSubmenus(): void { + this.openSubmenus.forEach((trigger) => trigger.closeMenu()); + this.openSubmenus = []; + } + + /** + * Navigate to the first menu item. + * @param event - The mouse event. + */ + navigateToFirst(event: MouseEvent): void { + event.stopPropagation(); + if (this.actionMenuItems[0][0].routerLink) { + this.router.navigate(this.actionMenuItems[0][0].routerLink); + } + } } diff --git a/src/app/layout/header/header.component.html b/src/app/layout/header/header.component.html index e08adc55..e4032590 100644 --- a/src/app/layout/header/header.component.html +++ b/src/app/layout/header/header.component.html @@ -20,6 +20,7 @@ [label]="menuItem.label" [icon]="menuItem.icon" [actionMenuItems]="menuItem.actions" + [openOnMouseEnter]="true" (menuItemClicked)="menuItemClicked($event)" > diff --git a/src/app/layout/header/header.component.ts b/src/app/layout/header/header.component.ts index 163195e3..415fc67b 100644 --- a/src/app/layout/header/header.component.ts +++ b/src/app/layout/header/header.component.ts @@ -23,6 +23,7 @@ export class HeaderComponent implements OnInit, OnDestroy { headerConfig = environment.config.header; mainMenu: MainMenuItem[] = []; isDarkMode = false; + timedOutCloser: any; constructor( private authService: AuthService, diff --git a/src/styles/layout/_header.scss b/src/styles/layout/_header.scss index cc94dcac..4df04fd7 100644 --- a/src/styles/layout/_header.scss +++ b/src/styles/layout/_header.scss @@ -44,8 +44,12 @@ font-weight: 400; } + &.cdk-focused, + &.cdk-program-focused { + background-color: transparent !important; + } &.active { - background-color: $primary-900; + background-color: $primary-900 !important; .mdc-button__label { font-weight: 400; From 210af4585b123c1c3e52db8880e1a73f1e159215 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Mon, 20 Nov 2023 10:00:53 +0000 Subject: [PATCH 280/419] Styling --- .../_interceptors/http-res.interceptor.ts | 139 ++++++++++-------- .../new-hashlist/new-hashlist.component.html | 1 + .../new-superhashlist.component.html | 1 - .../shared/alert/error/error.component.html | 20 +-- src/app/shared/alert/error/error.component.ts | 22 +-- .../dynamicform.component.html | 2 +- .../shared/input/color/color.component.html | 4 +- src/app/shared/input/color/color.component.ts | 1 + src/app/shared/input/input.module.ts | 6 +- .../shared/input/select/select.component.html | 18 ++- .../shared/input/select/select.component.ts | 34 ++++- 11 files changed, 158 insertions(+), 90 deletions(-) diff --git a/src/app/core/_interceptors/http-res.interceptor.ts b/src/app/core/_interceptors/http-res.interceptor.ts index 2fad3e63..aff04f02 100644 --- a/src/app/core/_interceptors/http-res.interceptor.ts +++ b/src/app/core/_interceptors/http-res.interceptor.ts @@ -1,5 +1,18 @@ -import { HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http'; -import { Observable, retry, throwError, catchError, finalize, BehaviorSubject } from 'rxjs'; +import { + HttpErrorResponse, + HttpEvent, + HttpHandler, + HttpInterceptor, + HttpRequest +} from '@angular/common/http'; +import { + BehaviorSubject, + Observable, + catchError, + finalize, + retry, + throwError +} from 'rxjs'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { Injectable } from '@angular/core'; import { Router } from '@angular/router'; @@ -7,85 +20,95 @@ import { Router } from '@angular/router'; import { ErrorModalComponent } from '../../shared/alert/error/error.component'; import { LoadingService } from '../_services/shared/loading.service'; import { AuthService } from '../_services/access/auth.service'; +import { MatDialog } from '@angular/material/dialog'; @Injectable() export class HttpResInterceptor implements HttpInterceptor { - private isRefreshing = false; - private refreshTokenSubject: BehaviorSubject = new BehaviorSubject(null); + private refreshTokenSubject: BehaviorSubject = new BehaviorSubject( + null + ); constructor( public authService: AuthService, - private modalService: NgbModal, + private modalService: NgbModal, // Keep NgbModal for now + private dialog: MatDialog, public ls: LoadingService, private router: Router - ) { } + ) {} modalRef = null; - intercept(req: HttpRequest, next: HttpHandler): - Observable> { + intercept( + req: HttpRequest, + next: HttpHandler + ): Observable> { this.ls.handleRequest('plus'); - return next.handle(req) - .pipe( - retry(1), - finalize(this.finalize.bind(this)), - catchError((error: HttpErrorResponse) => { - let errmsg = ''; - if (error.error instanceof ErrorEvent) { - const err = error?.error.message || 'Unknown API error'; - errmsg = `Client Side Error: ${err}`; + return next.handle(req).pipe( + retry(1), + finalize(this.finalize.bind(this)), + catchError((error: HttpErrorResponse) => { + let errmsg = ''; + if (error.error instanceof ErrorEvent) { + const err = error?.error.message || 'Unknown API error'; + errmsg = `Client Side Error: ${err}`; + } else { + if (error.status === 0) { + alert(`Unable to Connect to the Server: ` + error.message); } - else { - if (error.status === 0) { - alert(`Unable to Connect to the Server: ` + error.message); - } - if (error.status === 401) { - if (!req.url.includes('/auth')) { - const token = this.authService.token; - const userData: { _expires: string } = JSON.parse(localStorage.getItem('userData')); - if (token !== 'notoken' && (new Date(userData._expires) < new Date(Date.now() - 60000))) { - this.isRefresh(req); - } else { - errmsg = `${error.error.title}`; - } + if (error.status === 401) { + if (!req.url.includes('/auth')) { + const token = this.authService.token; + const userData: { _expires: string } = JSON.parse( + localStorage.getItem('userData') + ); + if ( + token !== 'notoken' && + new Date(userData._expires) < new Date(Date.now() - 60000) + ) { + this.isRefresh(req); } else { errmsg = `${error.error.title}`; } + } else { + errmsg = `${error.error.title}`; } - if (error.status === 403) { - errmsg = `You don't have permissions. Please contact your Administrator.`; - } - if (error.status === 404 && !req.url.includes('config.json')) { - errmsg = `The requested URL was not found.`; - } - // if(error.status !== 404 && error.status !== 403 && error.status !== 401 && error.status >= 300){ - // this.router.navigate(['error']); - // } - else { - errmsg = error.error.exception[0].message; - } } - this.modalRef = this.modalService.open(ErrorModalComponent); - this.modalRef.componentInstance.status = error?.status; - this.modalRef.componentInstance.message = errmsg; - return throwError(() => errmsg); - }) - ) + if (error.status === 403) { + errmsg = `You don't have permissions. Please contact your Administrator.`; + } + if (error.status === 404 && !req.url.includes('config.json')) { + errmsg = `The requested URL was not found.`; + } + // if(error.status !== 404 && error.status !== 403 && error.status !== 401 && error.status >= 300){ + // this.router.navigate(['error']); + // } + else { + errmsg = error.error.exception[0].message; + } + } + this.dialog.open(ErrorModalComponent, { + data: { status: error?.status, message: errmsg } + }); + return throwError(() => errmsg); + }) + ); } finalize = (): void => this.ls.handleRequest(); isNetworkError(errorObject) { - return errorObject.message === "net::ERR_INTERNET_DISCONNECTED" || - errorObject.message === "net::ERR_PROXY_CONNECTION_FAILED" || - errorObject.message === "net::ERR_PROXY_CONNECTION_FAILED" || - errorObject.message === "net::ERR_CONNECTION_TIMED_OUT" || - errorObject.message === "net::ERR_CONNECTION_RESET" || - errorObject.message === "net::ERR_CONNECTION_CLOSE" || - errorObject.message === "net::ERR_UNKNOWN_PROTOCOL" || - errorObject.message === "net::ERR_SLOW_CONNECTION" || - errorObject.message === "net::ERR_FAILED" || - errorObject.message === "net::ERR_NAME_NOT_RESOLVED"; + return ( + errorObject.message === 'net::ERR_INTERNET_DISCONNECTED' || + errorObject.message === 'net::ERR_PROXY_CONNECTION_FAILED' || + errorObject.message === 'net::ERR_PROXY_CONNECTION_FAILED' || + errorObject.message === 'net::ERR_CONNECTION_TIMED_OUT' || + errorObject.message === 'net::ERR_CONNECTION_RESET' || + errorObject.message === 'net::ERR_CONNECTION_CLOSE' || + errorObject.message === 'net::ERR_UNKNOWN_PROTOCOL' || + errorObject.message === 'net::ERR_SLOW_CONNECTION' || + errorObject.message === 'net::ERR_FAILED' || + errorObject.message === 'net::ERR_NAME_NOT_RESOLVED' + ); } isRefresh(request: HttpRequest) { diff --git a/src/app/hashlists/new-hashlist/new-hashlist.component.html b/src/app/hashlists/new-hashlist/new-hashlist.component.html index 431accae..2286f9cb 100644 --- a/src/app/hashlists/new-hashlist/new-hashlist.component.html +++ b/src/app/hashlists/new-hashlist/new-hashlist.component.html @@ -212,6 +212,7 @@
Hashcat Brain Enabled
    + diff --git a/src/app/hashlists/new-superhashlist/new-superhashlist.component.html b/src/app/hashlists/new-superhashlist/new-superhashlist.component.html index 813e3bfb..4e0a72ef 100644 --- a/src/app/hashlists/new-superhashlist/new-superhashlist.component.html +++ b/src/app/hashlists/new-superhashlist/new-superhashlist.component.html @@ -20,7 +20,6 @@ - {{ form.value | json }} diff --git a/src/app/shared/alert/error/error.component.html b/src/app/shared/alert/error/error.component.html index 65588db5..b5416c48 100644 --- a/src/app/shared/alert/error/error.component.html +++ b/src/app/shared/alert/error/error.component.html @@ -1,12 +1,12 @@ -

- - - -
- -
- - - -
- -
- + +
+
+ +
+ - - - -
- -
- - - -
- -
- +
+ +
+ + + - {{ cprogress / tkeyspace | percent:'1.2-2' }} + {{ cprogress / tkeyspace | percent: '1.2-2' }} - N/A - N/A - {{ editedTaskIndex | ttimespent:false:true | async | sectotime }} - + {{ + editedTaskIndex + | ttimespent: false : true + | async + | sectotime + }} + - {{ (ctimespent/(cprogress/tkeyspace)-ctimespent) | sectotime }} - + {{ + ctimespent / (cprogress / tkeyspace) - ctimespent + | sectotime + }} + - --- - --- - {{ currenspeed | fileSize:false }}H/s - {{ currenspeed | fileSize: false }}H/s - {{ hashlistinform.name }} / {{ hashlistDescrip }} - + {{ hashlistinform.name }} / {{ hashlistDescrip }} +
+
- - - - + + + + + + + + + + + + - - - - - - - -
IDNameTypeSizeIDNameTypeSize
{{ file.fileId }}{{ file.filename | shortenString: 15 }}{{ file.fileType | fileType }}{{ file.size | fileSize: false }}
{{ file.fileId }}{{ file.filename | shortenString:15 }}{{ file.fileType | fileType }}{{ file.size | fileSize:false }}
@@ -327,18 +373,26 @@
- - - + + + + + Assing @@ -347,73 +401,132 @@
+ + - +
- +
- - - - - - - - - - - + + + + + + + + + + + - - + + - - + - + + + + + + + diff --git a/src/app/files/files-edit/files-edit.component.ts b/src/app/files/files-edit/files-edit.component.ts new file mode 100644 index 00000000..2c27482a --- /dev/null +++ b/src/app/files/files-edit/files-edit.component.ts @@ -0,0 +1,147 @@ +import { FormControl, FormGroup, Validators } from '@angular/forms'; +import { ActivatedRoute, Params } from '@angular/router'; +import { Component, OnInit } from '@angular/core'; +import { Router } from '@angular/router'; + +import { AlertService } from 'src/app/core/_services/shared/alert.service'; +import { GlobalService } from 'src/app/core/_services/main.service'; +import { PageTitle } from 'src/app/core/_decorators/autotitle'; +import { SERV } from '../../core/_services/main.config'; +import { Filetype } from '../../core/_models/file.model'; + +@Component({ + selector: 'app-files-edit', + templateUrl: './files-edit.component.html' +}) +@PageTitle(['Edit File']) +export class FilesEditComponent implements OnInit { + editMode = false; + editedFileIndex: number; + editedFile: any; // Change to Model + + filterType: number; + whichView: string; + + // accessgroup: AccessGroup; //Use models when data structure is reliable + updateForm: FormGroup; + accessgroup: any[]; + allfiles: any[]; + filetype: any[]; + + constructor( + private route: ActivatedRoute, + private alert: AlertService, + private gs: GlobalService, + private router: Router + ) {} + + ngOnInit(): void { + this.route.params.subscribe((params: Params) => { + this.editedFileIndex = +params['id']; + this.editMode = params['id'] != null; + this.initForm(); + }); + + this.updateForm = new FormGroup({ + fileId: new FormControl({ value: '', disabled: true }), + updateData: new FormGroup({ + filename: new FormControl('', [ + Validators.required, + Validators.minLength(1) + ]), + fileType: new FormControl(null), + accessGroupId: new FormControl(null), + isSecret: new FormControl(null) + }) + }); + + this.route.data.subscribe((data) => { + switch (data['kind']) { + case 'wordlist': + this.whichView = 'wordlist-edit'; + break; + + case 'rules': + this.whichView = 'rules-edit'; + break; + + case 'other': + this.whichView = 'other-edit'; + break; + } + + this.filetype = [ + { fileType: 0, fileName: 'Wordlist' }, + { fileType: 1, fileName: 'Rules' }, + { fileType: 2, fileName: 'Other' } + ]; + + this.gs.getAll(SERV.ACCESS_GROUPS).subscribe((agroups: any) => { + this.accessgroup = agroups.values; + }); + + this.gs.get(SERV.FILES, this.editedFileIndex).subscribe((files: any) => { + this.allfiles = files; + }); + }); + } + + onSubmit(): void { + this.gs + .update( + SERV.FILES, + this.editedFileIndex, + this.updateForm.value['updateData'] + ) + .subscribe( + () => { + this.alert.okAlert('File saved!', ''); + this.route.data.subscribe((data) => { + switch (data['kind']) { + case 'wordlist-edit': + this.whichView = 'wordlist'; + break; + + case 'rules-edit': + this.whichView = 'rules'; + break; + + case 'other-edit': + this.whichView = 'other'; + break; + } + this.router.navigate(['../files/' + this.whichView + '']); + }); + }, + (errorMessage) => { + // check error status code is 500, if so, do some action + this.alert.okAlert( + 'File was not updated, please try again!', + '', + 'warning' + ); + this.ngOnInit(); + } + ); + this.updateForm.reset(); // success, we reset form + } + + private initForm() { + if (this.editMode) { + this.gs.get(SERV.FILES, this.editedFileIndex).subscribe((result) => { + this.updateForm = new FormGroup({ + fileId: new FormControl({ value: result['fileId'], disabled: true }), + updateData: new FormGroup({ + filename: new FormControl(result['filename'], Validators.required), + fileType: new FormControl(result['fileType'], Validators.required), + accessGroupId: new FormControl( + result['accessGroupId'], + Validators.required + ), + isSecret: new FormControl(result['isSecret']) + }) + }); + }); + } + } +} From 59bad0171f6a142c65c87dd49c38afc679b5023f Mon Sep 17 00:00:00 2001 From: itbf9 Date: Tue, 21 Nov 2023 10:45:19 +0100 Subject: [PATCH 293/419] Add cracks table --- .../_components/core-components.module.ts | 10 +- .../tables/base-table/base-table.component.ts | 50 ++-- .../cracks-table/cracks-table.component.html | 16 ++ .../cracks-table/cracks-table.component.ts | 247 ++++++++++++++++++ .../cracks-table/cracks-table.constants.ts | 12 + .../tables/ht-table/ht-table.component.html | 67 +++-- .../tables/ht-table/ht-table.models.ts | 2 + .../table-truncate.component.html | 7 + .../table-truncate.component.scss | 17 ++ .../table-truncate.component.ts | 30 +++ .../core/_datasources/cracks.datasource.ts | 52 ++++ src/app/core/_models/config-ui.model.ts | 10 + src/app/core/_models/hash.model.ts | 31 ++- src/app/hashlists/hashlists.module.ts | 8 +- .../show-cracks/show-cracks.component.html | 41 +-- .../show-cracks/show-cracks.component.ts | 183 +------------ src/app/shared/components.module.ts | 64 +++-- .../table/button-truncate-text.component.ts | 59 ----- src/styles/components/_table.scss | 7 + 19 files changed, 535 insertions(+), 378 deletions(-) create mode 100644 src/app/core/_components/tables/cracks-table/cracks-table.component.html create mode 100644 src/app/core/_components/tables/cracks-table/cracks-table.component.ts create mode 100644 src/app/core/_components/tables/cracks-table/cracks-table.constants.ts create mode 100644 src/app/core/_components/tables/table-truncate/table-truncate.component.html create mode 100644 src/app/core/_components/tables/table-truncate/table-truncate.component.scss create mode 100644 src/app/core/_components/tables/table-truncate/table-truncate.component.ts create mode 100644 src/app/core/_datasources/cracks.datasource.ts delete mode 100644 src/app/shared/table/button-truncate-text.component.ts diff --git a/src/app/core/_components/core-components.module.ts b/src/app/core/_components/core-components.module.ts index 95dc12d4..0c07a4f8 100644 --- a/src/app/core/_components/core-components.module.ts +++ b/src/app/core/_components/core-components.module.ts @@ -14,6 +14,7 @@ import { ChunksTableComponent } from './tables/chunks-table/chunks-table.compone import { ColumnSelectionDialogComponent } from './tables/column-selection-dialog/column-selection-dialog.component'; import { CommonModule } from '@angular/common'; import { CrackersTableComponent } from './tables/crackers-table/crackers-table.component'; +import { CracksTableComponent } from './tables/cracks-table/cracks-table.component'; import { ExportMenuComponent } from './menus/export-menu/export-menu.component'; import { FilesTableComponent } from './tables/files-table/files-table.component'; import { HTTableComponent } from './tables/ht-table/ht-table.component'; @@ -43,11 +44,13 @@ import { RouterModule } from '@angular/router'; import { RowActionMenuComponent } from './menus/row-action-menu/row-action-menu.component'; import { SuperHashlistsTableComponent } from './tables/super-hashlists-table/super-hashlists-table.component'; import { TableDialogComponent } from './tables/table-dialog/table-dialog.component'; +import { TableTruncateComponent } from './tables/table-truncate/table-truncate.component'; import { UsersTableComponent } from './tables/users-table/users-table.component'; @NgModule({ declarations: [ TableDialogComponent, + TableTruncateComponent, BaseTableComponent, HTTableComponent, BaseMenuComponent, @@ -67,7 +70,8 @@ import { UsersTableComponent } from './tables/users-table/users-table.component' AgentBinariesTableComponent, HealthChecksTableComponent, LogsTableComponent, - UsersTableComponent + UsersTableComponent, + CracksTableComponent ], imports: [ ReactiveFormsModule, @@ -95,6 +99,7 @@ import { UsersTableComponent } from './tables/users-table/users-table.component' ], exports: [ BaseTableComponent, + TableTruncateComponent, HTTableComponent, ColumnSelectionDialogComponent, BaseMenuComponent, @@ -113,7 +118,8 @@ import { UsersTableComponent } from './tables/users-table/users-table.component' AgentBinariesTableComponent, HealthChecksTableComponent, LogsTableComponent, - UsersTableComponent + UsersTableComponent, + CracksTableComponent ], providers: [ { 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 50d367a0..8f87caab 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 @@ -93,20 +93,23 @@ export class BaseTableComponent { async renderTaskLink(obj: unknown): Promise { return [ { - routerLink: obj['taskId'] - ? ['/tasks', 'show-tasks', obj['taskId'], 'edit'] - : [] + routerLink: + obj && obj['taskId'] + ? ['/tasks', 'show-tasks', obj['taskId'], 'edit'] + : [] } ]; } @Cacheable(['agentId']) async renderAgentLink(obj: unknown): Promise { + console.log('obj', obj); return [ { - routerLink: obj['agentId'] - ? ['/agents', 'show-agents', obj['agentId'], 'edit'] - : [] + routerLink: + obj && obj['agentId'] + ? ['/agents', 'show-agents', obj['agentId'], 'edit'] + : [] } ]; } @@ -115,9 +118,10 @@ export class BaseTableComponent { async renderCrackedLink(obj: unknown): Promise { return [ { - routerLink: obj['taskId'] - ? ['/hashlists', 'hashes', 'tasks', obj['taskId']] - : [] + routerLink: + obj && obj['taskId'] + ? ['/hashlists', 'hashes', 'tasks', obj['taskId']] + : [] } ]; } @@ -126,7 +130,8 @@ export class BaseTableComponent { async renderUserLink(obj: unknown): Promise { return [ { - routerLink: obj['userId'] ? ['/users', obj['userId'], 'edit'] : [] + routerLink: + obj && obj['userId'] ? ['/users', obj['userId'], 'edit'] : [] } ]; } @@ -135,9 +140,10 @@ export class BaseTableComponent { async renderChunkLink(obj: unknown): Promise { return [ { - routerLink: obj['chunkId'] - ? ['/tasks', 'chunks', obj['chunkId'], 'view'] - : [] + routerLink: + obj && obj['chunkId'] + ? ['/tasks', 'chunks', obj['chunkId'], 'view'] + : [] } ]; } @@ -146,9 +152,10 @@ export class BaseTableComponent { async renderHashlistLink(obj: unknown): Promise { return [ { - routerLink: obj['hashlistId'] - ? ['/hashlists', 'hashlist', obj['hashlistId'], 'edit'] - : [] + routerLink: + obj && obj['hashlistId'] + ? ['/hashlists', 'hashlist', obj['hashlistId'], 'edit'] + : [] } ]; } @@ -157,7 +164,7 @@ export class BaseTableComponent { async renderHashlistLinks(obj: unknown): Promise { const links: HTTableRouterLink[] = []; - if (obj['hashlists'] && obj['hashlists'].length) { + if (obj && obj['hashlists'] && obj['hashlists'].length) { for (const hashlist of obj['hashlists']) { links.push({ label: hashlist.name, @@ -173,9 +180,10 @@ export class BaseTableComponent { async renderHashCountLink(obj: unknown): Promise { return [ { - routerLink: obj['hashlistId'] - ? ['/hashlists', 'hashes', 'hashlists', obj['hashlistId']] - : [] + routerLink: + obj && obj['hashlistId'] + ? ['/hashlists', 'hashes', 'hashlists', obj['hashlistId']] + : [] } ]; } @@ -184,7 +192,7 @@ export class BaseTableComponent { async renderAccessGroupLinks(obj: unknown): Promise { let links: HTTableRouterLink[] = []; - if (obj['accessGroups'] && obj['accessGroups'].length) { + if (obj && obj['accessGroups'] && obj['accessGroups'].length) { links = obj['accessGroups'].map((accessGroup: AccessGroup) => { return { routerLink: [ diff --git a/src/app/core/_components/tables/cracks-table/cracks-table.component.html b/src/app/core/_components/tables/cracks-table/cracks-table.component.html new file mode 100644 index 00000000..cec6184c --- /dev/null +++ b/src/app/core/_components/tables/cracks-table/cracks-table.component.html @@ -0,0 +1,16 @@ + diff --git a/src/app/core/_components/tables/cracks-table/cracks-table.component.ts b/src/app/core/_components/tables/cracks-table/cracks-table.component.ts new file mode 100644 index 00000000..cf66563b --- /dev/null +++ b/src/app/core/_components/tables/cracks-table/cracks-table.component.ts @@ -0,0 +1,247 @@ +/* eslint-disable @angular-eslint/component-selector */ +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { catchError, forkJoin } from 'rxjs'; + +import { ActionMenuEvent } from '../../menus/action-menu/action-menu.model'; +import { BaseTableComponent } from '../base-table/base-table.component'; +import { BulkActionMenuAction } from '../../menus/bulk-action-menu/bulk-action-menu.constants'; +import { CracksDataSource } from 'src/app/core/_datasources/cracks.datasource'; +import { CracksTableColumnLabel } from './cracks-table.constants'; +import { DialogData } from '../table-dialog/table-dialog.model'; +import { ExportMenuAction } from '../../menus/export-menu/export-menu.constants'; +import { HTTableColumn } from '../ht-table/ht-table.models'; +import { Hash } from 'src/app/core/_models/hash.model'; +import { HashListFormatLabel } from 'src/app/core/_constants/hashlist.config'; +import { RowActionMenuAction } from '../../menus/row-action-menu/row-action-menu.constants'; +import { SERV } from 'src/app/core/_services/main.config'; +import { TableDialogComponent } from '../table-dialog/table-dialog.component'; +import { formatUnixTimestamp } from 'src/app/shared/utils/datetime'; + +@Component({ + selector: 'cracks-table', + templateUrl: './cracks-table.component.html' +}) +export class CracksTableComponent + extends BaseTableComponent + implements OnInit, OnDestroy +{ + tableColumns: HTTableColumn[] = []; + dataSource: CracksDataSource; + + ngOnInit(): void { + this.tableColumns = this.getColumns(); + this.dataSource = new CracksDataSource(this.cdr, this.gs, this.uiService); + this.dataSource.setColumns(this.tableColumns); + this.dataSource.loadAll(); + } + + ngOnDestroy(): void { + for (const sub of this.subscriptions) { + sub.unsubscribe(); + } + } + + filter(item: Hash, filterValue: string): boolean { + if (item.hash.toLowerCase().includes(filterValue)) { + return true; + } + + return false; + } + + getColumns(): HTTableColumn[] { + const tableColumns = [ + { + name: CracksTableColumnLabel.FOUND, + dataKey: 'timeCracked', + render: (crack: Hash) => + formatUnixTimestamp(crack.timeCracked, this.dateFormat), + isSortable: true, + export: async (crack: Hash) => + formatUnixTimestamp(crack.timeCracked, this.dateFormat) + }, + { + name: CracksTableColumnLabel.PLAINTEXT, + dataKey: 'plaintext', + isSortable: true, + export: async (crack: Hash) => crack.plaintext + }, + { + name: CracksTableColumnLabel.HASH, + dataKey: 'hash', + isSortable: true, + truncate: true, + export: async (crack: Hash) => crack.hash + }, + { + name: CracksTableColumnLabel.AGENT, + dataKey: 'agentId', + isSortable: true, + routerLink: (crack: Hash) => this.renderAgentLink(crack), + export: async (crack: Hash) => crack.agentId + '' + }, + { + name: CracksTableColumnLabel.TASK, + dataKey: 'taskId', + isSortable: true, + routerLink: (crack: Hash) => this.renderTaskLink(crack), + export: async (crack: Hash) => crack.taskId + '' + }, + { + name: CracksTableColumnLabel.CHUNK, + dataKey: 'chunkId', + isSortable: true, + routerLink: (crack: Hash) => this.renderChunkLink(crack), + export: async (crack: Hash) => crack.chunkId + '' + }, + { + name: CracksTableColumnLabel.TYPE, + dataKey: 'hashlistId', + isSortable: true, + render: (crack: Hash) => + crack.hashlist ? HashListFormatLabel[crack.hashlist.format] : '', + export: async (crack: Hash) => + crack.hashlist ? HashListFormatLabel[crack.hashlist.format] : '' + } + ]; + + return tableColumns; + } + + openDialog(data: DialogData) { + const dialogRef = this.dialog.open(TableDialogComponent, { + data: data, + width: '450px' + }); + + this.subscriptions.push( + dialogRef.afterClosed().subscribe((result) => { + if (result && result.action) { + switch (result.action) { + case RowActionMenuAction.DELETE: + this.rowActionDelete(result.data); + break; + case BulkActionMenuAction.DELETE: + this.bulkActionDelete(result.data); + break; + } + } + }) + ); + } + + // --- Action functions --- + + exportActionClicked(event: ActionMenuEvent): void { + switch (event.menuItem.action) { + case ExportMenuAction.EXCEL: + this.exportService.toExcel( + 'hashtopolis-cracks', + this.tableColumns, + event.data + ); + break; + case ExportMenuAction.CSV: + this.exportService.toCsv( + 'hashtopolis-cracks', + this.tableColumns, + event.data + ); + break; + case ExportMenuAction.COPY: + this.exportService + .toClipboard(this.tableColumns, event.data) + .then(() => { + this.snackBar.open( + 'The selected rows are copied to the clipboard', + 'Close' + ); + }); + break; + } + } + + rowActionClicked(event: ActionMenuEvent): void { + switch (event.menuItem.action) { + case RowActionMenuAction.DELETE: + this.openDialog({ + rows: [event.data], + title: `Deleting crack ${event.data._id} ...`, + icon: 'warning', + body: `Are you sure you want to delete it? Note that this action cannot be undone.`, + warn: true, + action: event.menuItem.action + }); + break; + case RowActionMenuAction.EDIT: + this.rowActionEdit(event.data); + break; + } + } + + bulkActionClicked(event: ActionMenuEvent): void { + switch (event.menuItem.action) { + case BulkActionMenuAction.DELETE: + this.openDialog({ + rows: event.data, + title: `Deleting ${event.data.length} cracks ...`, + icon: 'warning', + body: `Are you sure you want to delete the above cracks? Note that this action cannot be undone.`, + warn: true, + listAttribute: 'name', + action: event.menuItem.action + }); + break; + } + } + + /** + * @todo Implement error handling. + */ + private bulkActionDelete(cracks: Hash[]): void { + const requests = cracks.map((crack: Hash) => { + return this.gs.delete(SERV.CRACKERS_TYPES, crack._id); + }); + + this.subscriptions.push( + forkJoin(requests) + .pipe( + catchError((error) => { + console.error('Error during deletion:', error); + return []; + }) + ) + .subscribe((results) => { + this.snackBar.open( + `Successfully deleted ${results.length} cracks!`, + 'Close' + ); + this.reload(); + }) + ); + } + + /** + * @todo Implement error handling. + */ + private rowActionDelete(cracks: Hash[]): void { + this.subscriptions.push( + this.gs + .delete(SERV.CRACKERS_TYPES, cracks[0]._id) + .pipe( + catchError((error) => { + console.error('Error during deletion:', error); + return []; + }) + ) + .subscribe(() => { + this.snackBar.open('Successfully deleted crack!', 'Close'); + this.reload(); + }) + ); + } + + private rowActionEdit(crack: Hash): void { + this.router.navigate(['/config', 'engine', 'cracks', crack._id, 'edit']); + } +} diff --git a/src/app/core/_components/tables/cracks-table/cracks-table.constants.ts b/src/app/core/_components/tables/cracks-table/cracks-table.constants.ts new file mode 100644 index 00000000..54c1e38f --- /dev/null +++ b/src/app/core/_components/tables/cracks-table/cracks-table.constants.ts @@ -0,0 +1,12 @@ +export const CracksTableColumnLabel = { + ID: 'ID', + FOUND: 'Time Found', + PLAINTEXT: 'Plaintext', + HASH: 'Hash', + HASHLIST: 'Hashlist', + AGENT: 'Agent', + TASK: 'Task', + CHUNK: 'Chunk', + TYPE: 'Type', + SALT: 'Salt' +}; 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 197f5bf8..0079ea9c 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 @@ -138,36 +138,47 @@ [class.truncate]="tableColumn.routerLink" [class.icons]="tableColumn.icons" > - - - - - {{ link.label }} - - - {{ element[tableColumn.dataKey] }} - - - + + - - - -
-
- - -
+ + + + + + {{ link.label }} + + + {{ element[tableColumn.dataKey] }} + + + - - - {{ element[tableColumn.dataKey] }} + + + +
+
+ + +
+
+ + + {{ element[tableColumn.dataKey] }} +
diff --git a/src/app/core/_components/tables/ht-table/ht-table.models.ts b/src/app/core/_components/tables/ht-table/ht-table.models.ts index a92f45a8..73ae86b6 100644 --- a/src/app/core/_components/tables/ht-table/ht-table.models.ts +++ b/src/app/core/_components/tables/ht-table/ht-table.models.ts @@ -13,6 +13,7 @@ export type DataType = | 'agent-binaries' | 'health-checks' | 'logs' + | 'cracks' | 'superhashlists'; export interface HTTableIcon { @@ -36,4 +37,5 @@ export interface HTTableColumn { async?: (data: any) => Promise; routerLink?: (data: any) => Promise; export?: (data: any) => Promise; + truncate?: boolean; } diff --git a/src/app/core/_components/tables/table-truncate/table-truncate.component.html b/src/app/core/_components/tables/table-truncate/table-truncate.component.html new file mode 100644 index 00000000..a5073d3d --- /dev/null +++ b/src/app/core/_components/tables/table-truncate/table-truncate.component.html @@ -0,0 +1,7 @@ +
+
{{ abbr }}
+
{{ text }}
+ +
diff --git a/src/app/core/_components/tables/table-truncate/table-truncate.component.scss b/src/app/core/_components/tables/table-truncate/table-truncate.component.scss new file mode 100644 index 00000000..e9a540a1 --- /dev/null +++ b/src/app/core/_components/tables/table-truncate/table-truncate.component.scss @@ -0,0 +1,17 @@ +.text-container { + display: inline-block; + position: relative; + max-width: 200px; + word-break: break-word; + margin: 0; + padding: 0; + + button { + display: inline-block; + background: none; + border: none; + cursor: pointer; + margin: 0; + padding: 0; + } +} diff --git a/src/app/core/_components/tables/table-truncate/table-truncate.component.ts b/src/app/core/_components/tables/table-truncate/table-truncate.component.ts new file mode 100644 index 00000000..d10fee33 --- /dev/null +++ b/src/app/core/_components/tables/table-truncate/table-truncate.component.ts @@ -0,0 +1,30 @@ +import { Component, Input, OnInit } from '@angular/core'; + +/** + * Component for truncating and expanding text with a "More/Less" button. + */ +@Component({ + selector: 'table-truncate', + templateUrl: './table-truncate.component.html', + styleUrls: ['./table-truncate.component.scss'] +}) +export class TableTruncateComponent implements OnInit { + @Input() text: string; + @Input() maxLength = 40; + + expanded = false; + abbr: string; + + ngOnInit(): void { + if (this.text.length > this.maxLength) { + this.abbr = `${this.text.substring(0, this.maxLength)} [...]`; + } else { + this.abbr = this.text; + } + } + + toggleExpansion(event: MouseEvent): void { + event.stopPropagation(); + this.expanded = !this.expanded; + } +} diff --git a/src/app/core/_datasources/cracks.datasource.ts b/src/app/core/_datasources/cracks.datasource.ts new file mode 100644 index 00000000..74cc02cf --- /dev/null +++ b/src/app/core/_datasources/cracks.datasource.ts @@ -0,0 +1,52 @@ +import { catchError, finalize, of } from 'rxjs'; + +import { BaseDataSource } from './base.datasource'; +import { Hash } from '../_models/hash.model'; +import { ListResponseWrapper } from '../_models/response.model'; +import { SERV } from '../_services/main.config'; + +export class CracksDataSource extends BaseDataSource { + loadAll(): void { + this.loading = true; + + const startAt = this.currentPage * this.pageSize; + const params = { + maxResults: this.pageSize, + startAt: startAt, + filter: 'isCracked=1', + expand: 'hashlist,chunk' + }; + + const cracks$ = this.service.getAll(SERV.HASHES, params); + + this.subscriptions.push( + cracks$ + .pipe( + catchError(() => of([])), + finalize(() => (this.loading = false)) + ) + .subscribe((response: ListResponseWrapper) => { + const cracks: Hash[] = response.values; + + cracks.map((crack: Hash) => { + if (crack.chunk) { + crack.agentId = crack.chunk.agentId; + crack.taskId = crack.chunk.taskId; + } + }); + + this.setPaginationConfig( + this.pageSize, + this.currentPage, + response.total + ); + this.setData(cracks); + }) + ); + } + + reload(): void { + this.clearSelection(); + this.loadAll(); + } +} diff --git a/src/app/core/_models/config-ui.model.ts b/src/app/core/_models/config-ui.model.ts index 4329e6d7..562aa50d 100644 --- a/src/app/core/_models/config-ui.model.ts +++ b/src/app/core/_models/config-ui.model.ts @@ -2,6 +2,7 @@ import { AgentBinariesTableColumnLabel } from '../_components/tables/agent-binar import { AgentsTableColumnLabel } from '../_components/tables/agents-table/agents-table.constants'; import { ChunksTableColumnLabel } from '../_components/tables/chunks-table/chunks-table.constants'; import { CrackersTableColumnLabel } from '../_components/tables/crackers-table/crackers-table.constants'; +import { CracksTableColumnLabel } from '../_components/tables/cracks-table/cracks-table.constants'; import { FilesTableColumnLabel } from '../_components/tables/files-table/files-table.constants'; import { HashlistsTableColumnLabel } from '../_components/tables/hashlists-table/hashlists-table.constants'; import { HashtypesTableColumnLabel } from '../_components/tables/hashtypes-table/hashtypes-table.constants'; @@ -30,6 +31,15 @@ export const uiConfigDefault: UIConfig = { theme: 'light', timefmt: 'dd/MM/yyyy h:mm:ss', tableSettings: { + cracksTable: [ + CracksTableColumnLabel.FOUND, + CracksTableColumnLabel.PLAINTEXT, + CracksTableColumnLabel.HASH, + CracksTableColumnLabel.AGENT, + CracksTableColumnLabel.TASK, + CracksTableColumnLabel.CHUNK, + CracksTableColumnLabel.TYPE + ], agentsTable: [ AgentsTableColumnLabel.ID, AgentsTableColumnLabel.NAME, diff --git a/src/app/core/_models/hash.model.ts b/src/app/core/_models/hash.model.ts index 2f8469b2..d5015cf0 100644 --- a/src/app/core/_models/hash.model.ts +++ b/src/app/core/_models/hash.model.ts @@ -1,13 +1,20 @@ +import { Chunk } from './chunk.model'; +import { Hashlist } from './hashlist.model'; + export interface Hash { - _id: number - _self: string - chunkId: number - crackPos: number - hash: string - hashId: number - hashlistId: number - isCracked: boolean - plaintext: string - salt: string - timeCracked: number -} \ No newline at end of file + _id: number; + _self: string; + chunkId: number; + agentId?: number; + taskId?: number; + chunk?: Chunk; + crackPos: number; + hash: string; + hashId: number; + hashlistId: number; + hashlist?: Hashlist; + isCracked: boolean; + plaintext: string; + salt: string; + timeCracked: number; +} diff --git a/src/app/hashlists/hashlists.module.ts b/src/app/hashlists/hashlists.module.ts index 40a6302b..b04c4542 100644 --- a/src/app/hashlists/hashlists.module.ts +++ b/src/app/hashlists/hashlists.module.ts @@ -33,6 +33,10 @@ import { SuperhashlistComponent } from './superhashlist/superhashlist.component' HashesComponent ], imports: [ + CommonModule, + RouterModule, + FormsModule, + ComponentsModule, HashlistRoutingModule, ReactiveFormsModule, CoreComponentsModule, @@ -40,10 +44,6 @@ import { SuperhashlistComponent } from './superhashlist/superhashlist.component' DataTablesModule, MatSlideToggleModule, DirectivesModule, - ComponentsModule, - CommonModule, - RouterModule, - FormsModule, PipesModule, NgbModule ] diff --git a/src/app/hashlists/show-cracks/show-cracks.component.html b/src/app/hashlists/show-cracks/show-cracks.component.html index c32230f1..ad1ad95d 100644 --- a/src/app/hashlists/show-cracks/show-cracks.component.html +++ b/src/app/hashlists/show-cracks/show-cracks.component.html @@ -1,41 +1,4 @@ - - -
IDStatusNameAgent StatusBenchmarkSpeedKeyspace searchedTime spentCrackedLast activityActionIDStatusNameAgent StatusBenchmarkSpeedKeyspace searchedTime spentCrackedLast activityAction
{{ a.assignmentId }} - + - - {{ a.agentName | shortenString:30 }} + + + {{ a.agentName | shortenString: 30 }} - + - + Active - + Inactive - {{ a.benchmark | shortenString:20 }} - + + {{ + a.benchmark | shortenString: 20 + }} + + + {{ a.agentId | aspeed: true | async | fileSize: false : 1000 }} H/s {{ a.agentId | aspeed:true | async | fileSize:false:1000 }} H/s - {{ a.agentId | tdsearched:tkeyspace:true | async | percent:'1.2-2'}} + {{ + a.agentId | tdsearched: tkeyspace : true | async | percent: '1.2-2' + }} - {{ a.agentId | ttimespent:false:true | async | sectotime }} + {{ a.agentId | ttimespent: false : true | async | sectotime }} - {{ a.agentId | tdcracked | async }} + {{ a.agentId | tdcracked | async }} {{ a.lastTime | uiDate }} - @@ -424,16 +537,26 @@
- - -
-
- - - + + +
+
+ + + -
- +
+
@@ -449,37 +572,56 @@
- - + + - - - + + - - + +
IDCracked Action
{{ gc.chunkId }} + + {{ gc.chunkId }} {{ gc.skip }} {{ gc.length }}{{ gc.checkpoint }} ({{ (gc.checkpoint-gc.skip)/gc.length | percent:'1.2-2' }}){{ gc.progress/100 }} % + {{ gc.checkpoint }} ({{ + (gc.checkpoint - gc.skip) / gc.length | percent: '1.2-2' + }}) + {{ gc.progress / 100 }} % N/A - {{ gc.agentName | shortenString:15 }} + + {{ gc.agentName | shortenString: 15 }} {{ gc.dispatchTime | uiDate }} (No acitivity) {{ gc.solveTime | uiDate }} {{ (gc.solveTime - gc.dispatchTime) | sectotime }}{{ gc.solveTime | uiDate }}{{ gc.solveTime - gc.dispatchTime | sectotime }} - - {{ gc.state | staticArray:'states' }} + + {{ gc.state | staticArray: 'states' }} {{ gc.cracked }} -
-
-
+
+ + diff --git a/src/styles/components/_table.scss b/src/styles/components/_table.scss index 3bc2deff..f842f049 100644 --- a/src/styles/components/_table.scss +++ b/src/styles/components/_table.scss @@ -213,6 +213,18 @@ input.dt-button { 02- End */ +@keyframes pulse { + 0% { + opacity: 1; + } + 50% { + opacity: 0; + } + 100% { + opacity: 1; + } +} + table.hashtopolis-table { .mat-mdc-header-cell { background-color: $primary-800; @@ -228,6 +240,11 @@ table.hashtopolis-table { } .mat-mdc-cell { + .pulsing-progress { + color: $warn-900; + animation: pulse 1s infinite; + } + .row-action { .mat-icon { margin-right: 0; From 10160822a700cc32c00f14086a46b7b3db0247d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Mon, 20 Nov 2023 14:19:46 +0000 Subject: [PATCH 284/419] Move utils, interfaces --- .../new-superhashlist.component.html | 6 +- .../new-superhashlist.component.ts | 23 ++- .../new-supertasks.component.html | 25 +++ .../new-supertasks.component.ts | 148 ++++++++++++++++++ src/app/core/_models/input.model.ts | 4 + src/app/hashlists/hashlists-routing.module.ts | 2 +- src/app/hashlists/hashlists.module.ts | 2 +- .../dynamicform.component.ts | 31 +--- src/app/shared/utils/forms.ts | 27 ++++ .../new-supertasks.component.html | 38 ----- .../new-supertasks.component.ts | 102 ------------ src/app/tasks/tasks-routing.module.ts | 2 +- src/app/tasks/tasks.module.ts | 2 +- 13 files changed, 226 insertions(+), 186 deletions(-) rename src/app/{hashlists => core/_components/forms/custom-forms/superhashlist}/new-superhashlist/new-superhashlist.component.html (84%) rename src/app/{hashlists => core/_components/forms/custom-forms/superhashlist}/new-superhashlist/new-superhashlist.component.ts (90%) create mode 100644 src/app/core/_components/forms/custom-forms/task/new-supertasks/new-supertasks.component.html create mode 100644 src/app/core/_components/forms/custom-forms/task/new-supertasks/new-supertasks.component.ts create mode 100644 src/app/core/_models/input.model.ts delete mode 100644 src/app/tasks/new-supertasks/new-supertasks.component.html delete mode 100644 src/app/tasks/new-supertasks/new-supertasks.component.ts diff --git a/src/app/hashlists/new-superhashlist/new-superhashlist.component.html b/src/app/core/_components/forms/custom-forms/superhashlist/new-superhashlist/new-superhashlist.component.html similarity index 84% rename from src/app/hashlists/new-superhashlist/new-superhashlist.component.html rename to src/app/core/_components/forms/custom-forms/superhashlist/new-superhashlist/new-superhashlist.component.html index 4e0a72ef..a753af9c 100644 --- a/src/app/hashlists/new-superhashlist/new-superhashlist.component.html +++ b/src/app/core/_components/forms/custom-forms/superhashlist/new-superhashlist/new-superhashlist.component.html @@ -1,5 +1,7 @@ - + + +
@@ -7,7 +9,7 @@ { - this.hashlists = response.values; + .subscribe((response: ListResponseWrapper) => { + this.selectHashlists = response.values; this.isLoading = false; this.changeDetectorRef.detectChanges(); }); diff --git a/src/app/core/_components/forms/custom-forms/task/new-supertasks/new-supertasks.component.html b/src/app/core/_components/forms/custom-forms/task/new-supertasks/new-supertasks.component.html new file mode 100644 index 00000000..cf4931bc --- /dev/null +++ b/src/app/core/_components/forms/custom-forms/task/new-supertasks/new-supertasks.component.html @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + diff --git a/src/app/core/_components/forms/custom-forms/task/new-supertasks/new-supertasks.component.ts b/src/app/core/_components/forms/custom-forms/task/new-supertasks/new-supertasks.component.ts new file mode 100644 index 00000000..35ee88c8 --- /dev/null +++ b/src/app/core/_components/forms/custom-forms/task/new-supertasks/new-supertasks.component.ts @@ -0,0 +1,148 @@ +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + Input, + OnDestroy, + OnInit +} from '@angular/core'; +import { + FormArray, + FormBuilder, + FormControl, + FormGroup, + Validators +} from '@angular/forms'; +import { Router } from '@angular/router'; + +import { AlertService } from 'src/app/core/_services/shared/alert.service'; +import { GlobalService } from 'src/app/core/_services/main.service'; +import { environment } from '../../../../../../../environments/environment'; +import { + extractIds, + transformSelectOptions +} from '../../../../../../shared/utils/forms'; +import { AutoTitleService } from 'src/app/core/_services/shared/autotitle.service'; +import { UnsubscribeService } from 'src/app/core/_services/unsubscribe.service'; +import { ListResponseWrapper } from 'src/app/core/_models/response.model'; +import { SelectField } from 'src/app/core/_models/input.model'; +import { SERV } from '../../../../../_services/main.config'; + +/** + * Represents the NewSupertasksComponent responsible for creating a new SuperTask. + */ +@Component({ + selector: 'app-new-supertasks', + templateUrl: './new-supertasks.component.html', + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class NewSupertasksComponent implements OnInit, OnDestroy { + /** Flag indicating whether data is still loading. */ + isLoading = true; + + /** Form group for the new SuperTask. */ + form: FormGroup; + + /** Maximum results for API requests. */ + private maxResults = environment.config.prodApiMaxResults; + + /** List of PreTasks. */ + selectPretasks: any[]; + + // Util functions + extractIds = extractIds; + transformSelectOptions = transformSelectOptions; + + constructor( + private unsubscribeService: UnsubscribeService, + private changeDetectorRef: ChangeDetectorRef, + private titleService: AutoTitleService, + private formBuilder: FormBuilder, + private alert: AlertService, + private gs: GlobalService, + private router: Router + ) { + this.buildForm(); + titleService.set(['New SuperTask']); + } + + @Input() + error; + + /** + * Lifecycle hook called after component initialization. + */ + ngOnInit(): void { + this.loadData(); + } + + /** + * Lifecycle hook called before the component is destroyed. + * Unsubscribes from all subscriptions to prevent memory leaks. + */ + ngOnDestroy(): void { + this.unsubscribeService.unsubscribeAll(); + } + + /** + * Builds the form for creating a new SuperHashlist. + */ + buildForm(): void { + this.form = this.formBuilder.group({ + supertaskName: ['', Validators.required], + pretasks: [null, Validators.required] + }); + } + + /** + * Loads data, specifically hashlists, for the component. + */ + loadData(): void { + const field = { + fieldMapping: { + name: 'taskName', + _id: '_id' + } + }; + this.gs + .getAll(SERV.PRETASKS, { maxResults: this.maxResults }) + .subscribe((response: ListResponseWrapper) => { + const transformedOptions = this.transformSelectOptions( + response.values, + field + ); + this.selectPretasks = transformedOptions; + this.isLoading = false; + this.changeDetectorRef.detectChanges(); + }); + } + + /** + * Handles form submission, creating a new SuperTask. + * If the form is valid, it makes an API request and navigates to the Supertaks page. + */ + onSubmit() { + if (this.form.valid) { + const createSubscription$ = this.gs + .create(SERV.SUPER_TASKS, this.form.value) + .subscribe(() => { + this.alert.okAlert('New SuperTask created!', ''); + this.form.reset(); + this.router.navigate(['tasks/supertasks']); + }); + + this.unsubscribeService.add(createSubscription$); + } + } + + /** + * Handles the selection of items in the UI. + * Extracts the IDs from the selected items and sets them in the form. + * + * @param selectedItems - The items that are selected. + */ + handleSelectedItems(selectedItems: SelectField[]): void { + const extractedIds = this.extractIds(selectedItems, '_id'); + this.form.get('hashlistIds').setValue(extractedIds); + } +} diff --git a/src/app/core/_models/input.model.ts b/src/app/core/_models/input.model.ts new file mode 100644 index 00000000..f24850fc --- /dev/null +++ b/src/app/core/_models/input.model.ts @@ -0,0 +1,4 @@ +export interface SelectField { + _id: string; + name: string; +} diff --git a/src/app/hashlists/hashlists-routing.module.ts b/src/app/hashlists/hashlists-routing.module.ts index 84a441ee..b5d6478b 100644 --- a/src/app/hashlists/hashlists-routing.module.ts +++ b/src/app/hashlists/hashlists-routing.module.ts @@ -3,7 +3,7 @@ import { RouterModule, Routes } from '@angular/router'; import { IsAuth } from '../core/_guards/auth.guard'; import { NgModule } from '@angular/core'; -import { NewSuperhashlistComponent } from './new-superhashlist/new-superhashlist.component'; +import { NewSuperhashlistComponent } from '../core/_components/forms/custom-forms/superhashlist/new-superhashlist/new-superhashlist.component'; import { SuperhashlistComponent } from './superhashlist/superhashlist.component'; import { EditHashlistComponent } from './edit-hashlist/edit-hashlist.component'; import { NewHashlistComponent } from './new-hashlist/new-hashlist.component'; diff --git a/src/app/hashlists/hashlists.module.ts b/src/app/hashlists/hashlists.module.ts index d49aff09..9c281525 100644 --- a/src/app/hashlists/hashlists.module.ts +++ b/src/app/hashlists/hashlists.module.ts @@ -12,7 +12,7 @@ import { HashlistComponent } from './hashlist/hashlist.component'; import { HashlistRoutingModule } from './hashlists-routing.module'; import { MatSlideToggleModule } from '@angular/material/slide-toggle'; import { NewHashlistComponent } from './new-hashlist/new-hashlist.component'; -import { NewSuperhashlistComponent } from './new-superhashlist/new-superhashlist.component'; +import { NewSuperhashlistComponent } from '../core/_components/forms/custom-forms/superhashlist/new-superhashlist/new-superhashlist.component'; import { NgModule } from '@angular/core'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { PipesModule } from '../shared/pipes.module'; diff --git a/src/app/shared/dynamic-form-builder/dynamicform.component.ts b/src/app/shared/dynamic-form-builder/dynamicform.component.ts index c5a84d3d..b6f794df 100644 --- a/src/app/shared/dynamic-form-builder/dynamicform.component.ts +++ b/src/app/shared/dynamic-form-builder/dynamicform.component.ts @@ -27,6 +27,7 @@ import { } from 'rxjs'; import { MetadataService } from 'src/app/core/_services/metadata.service'; import { GlobalService } from 'src/app/core/_services/main.service'; +import { transformSelectOptions } from '../../shared/utils/forms'; import { ChangeDetectorRef } from '@angular/core'; /** @@ -125,6 +126,9 @@ export class DynamicFormComponent implements OnInit, AfterViewInit, OnDestroy { private cd: ChangeDetectorRef ) {} + // Util functions + transformSelectOptions = transformSelectOptions; + /** * Initializes the dynamic form by creating form controls and setting their initial values. * This method is called when the dynamic form component is initialized. @@ -286,31 +290,4 @@ export class DynamicFormComponent implements OnInit, AfterViewInit, OnDestroy { this.destroy$.next(); this.destroy$.complete(); } - - /** - * Transforms API response options based on a field mapping configuration. - * - * @param apiOptions - The options received from an API response. - * @param field - The field configuration that contains the mapping between form fields and API fields. - * - * @returns An array of transformed select options to be used in the form. - */ - transformSelectOptions(apiOptions: any[], field: any): any[] { - return apiOptions.map((apiOption: any) => { - const transformedOption: any = {}; - - for (const formField of Object.keys(field.fieldMapping)) { - const apiField = field.fieldMapping[formField]; - - if (Object.prototype.hasOwnProperty.call(apiOption, apiField)) { - transformedOption[formField] = apiOption[apiField]; - } else { - // Handle the case where the API field doesn't exist in the response - transformedOption[formField] = null; // or set a default value - } - } - - return transformedOption; - }); - } } diff --git a/src/app/shared/utils/forms.ts b/src/app/shared/utils/forms.ts index 5dd0b1a6..0239ec24 100644 --- a/src/app/shared/utils/forms.ts +++ b/src/app/shared/utils/forms.ts @@ -63,6 +63,33 @@ export function extractIds(dataArray: any[], idKey: string): number[] { .filter((id) => id !== null) as number[]; } +/** + * Transforms API response options based on a field mapping configuration. + * + * @param apiOptions - The options received from an API response. + * @param field - The field configuration that contains the mapping between form fields and API fields. + * + * @returns An array of transformed select options to be used in the form. + */ +export function transformSelectOptions(apiOptions: any[], field: any): any[] { + return apiOptions.map((apiOption: any) => { + const transformedOption: any = {}; + + for (const formField of Object.keys(field.fieldMapping)) { + const apiField = field.fieldMapping[formField]; + + if (Object.prototype.hasOwnProperty.call(apiOption, apiField)) { + transformedOption[formField] = apiOption[apiField]; + } else { + // Handle the case where the API field doesn't exist in the response + transformedOption[formField] = null; // or set a default value + } + } + + return transformedOption; + }); +} + /** * https://axonflux.com/handy-rgb-to-hsl-and-rgb-to-hsv-color-model-c * diff --git a/src/app/tasks/new-supertasks/new-supertasks.component.html b/src/app/tasks/new-supertasks/new-supertasks.component.html deleted file mode 100644 index 0a9cbbe3..00000000 --- a/src/app/tasks/new-supertasks/new-supertasks.component.html +++ /dev/null @@ -1,38 +0,0 @@ - - - -
- -
- - - - -
-
- -
- - - - -
-
- - - - -
-
diff --git a/src/app/tasks/new-supertasks/new-supertasks.component.ts b/src/app/tasks/new-supertasks/new-supertasks.component.ts deleted file mode 100644 index fec5c672..00000000 --- a/src/app/tasks/new-supertasks/new-supertasks.component.ts +++ /dev/null @@ -1,102 +0,0 @@ -import { Component, OnInit, ChangeDetectionStrategy ,ChangeDetectorRef } from '@angular/core'; -import { FormControl, FormGroup, Validators, FormArray } from '@angular/forms'; -import { faFile, faMagnifyingGlass } from '@fortawesome/free-solid-svg-icons'; -import { Router } from '@angular/router'; - -import { AlertService } from 'src/app/core/_services/shared/alert.service'; -import { GlobalService } from 'src/app/core/_services/main.service'; -import { environment } from './../../../environments/environment'; -import { PageTitle } from 'src/app/core/_decorators/autotitle'; -import { SERV } from '../../core/_services/main.config'; - -@Component({ - selector: 'app-new-supertasks', - templateUrl: './new-supertasks.component.html', - changeDetection: ChangeDetectionStrategy.OnPush -}) -@PageTitle(['New SuperTask']) -export class NewSupertasksComponent implements OnInit { - - faMagnifyingGlass=faMagnifyingGlass; - faFile=faFile; - - constructor( - private _changeDetectorRef: ChangeDetectorRef, - private alert: AlertService, - private gs: GlobalService, - private router: Router - ) { } - - createForm: FormGroup; - private maxResults = environment.config.prodApiMaxResults - formArr: FormArray; - - ngOnInit(): void { - - this.createForm = new FormGroup({ - supertaskName: new FormControl(''), - pretasks: new FormControl(''), - }); - - const params = {'maxResults': this.maxResults} - - this.gs.getAll(SERV.PRETASKS,params).subscribe((tasks: any) => { - const self = this; - const response = tasks.values; - ($("#preTasks") as any).selectize({ - maxItems: null, - plugins: ["restore_on_backspace"], - valueField: "pretaskId", - placeholder: "Search task...", - labelField: "taskName", - searchField: ["taskName"], - loadingClass: 'Loading..', - highlight: true, - onChange: function (value) { - self.OnChangeValue(value); // We need to overide DOM event, Angular vs Jquery - }, - render: { - option: function (item, escape) { - return '
' + escape(item.pretaskId) + ' - ' + escape(item.taskName) + '
'; - }, - }, - onInitialize: function(){ - const selectize = this; - selectize.addOption(response); // This is will add to option - const selected_items = []; - $.each(response, function( i, obj) { - selected_items.push(obj.id); - }); - selectize.setValue(selected_items); //this will set option values as default - } - }); - }); - } - - OnChangeValue(value){ - const formArr = new FormArray([]); - for (const val of value) { - formArr.push( - new FormControl(+val) - ); - } - const cname = this.createForm.get('supertaskName').value; - this.createForm = new FormGroup({ - supertaskName: new FormControl(cname), - pretasks: formArr - }); - this._changeDetectorRef.detectChanges(); - } - - onSubmit(){ - if (this.createForm.valid) { - - this.gs.create(SERV.SUPER_TASKS,this.createForm.value).subscribe(() => { - this.alert.okAlert('New SuperTask created!',''); - this.createForm.reset(); // success, we reset form - this.router.navigate(['tasks/supertasks']); - } - ); - } - } -} diff --git a/src/app/tasks/tasks-routing.module.ts b/src/app/tasks/tasks-routing.module.ts index 577e49e3..cfa2a982 100644 --- a/src/app/tasks/tasks-routing.module.ts +++ b/src/app/tasks/tasks-routing.module.ts @@ -7,7 +7,7 @@ import { EditPreconfiguredTasksComponent } from './edit-preconfigured-tasks/edit import { NewPreconfiguredTasksComponent } from './new-preconfigured-tasks/new-preconfigured-tasks.component'; import { PreconfiguredTasksComponent } from './preconfigured-tasks/preconfigured-tasks.component'; import { EditSupertasksComponent } from './edit-supertasks/edit-supertasks.component'; -import { NewSupertasksComponent } from './new-supertasks/new-supertasks.component'; +import { NewSupertasksComponent } from '../core/_components/forms/custom-forms/task/new-supertasks/new-supertasks.component'; import { ApplyHashlistComponent } from './supertasks/applyhashlist.component'; import { WrbulkComponent } from './import-supertasks/wrbulk/wrbulk.component'; import { PendingChangesGuard } from '../core/_guards/pendingchanges.guard'; diff --git a/src/app/tasks/tasks.module.ts b/src/app/tasks/tasks.module.ts index 3f8ca7a5..26500d62 100644 --- a/src/app/tasks/tasks.module.ts +++ b/src/app/tasks/tasks.module.ts @@ -16,7 +16,7 @@ import { MatInputModule } from '@angular/material/input'; import { ModalPretasksComponent } from './supertasks/modal-pretasks/modal-pretasks.component'; import { ModalSubtasksComponent } from './show-tasks/modal-subtasks/modal-subtasks.component'; import { NewPreconfiguredTasksComponent } from './new-preconfigured-tasks/new-preconfigured-tasks.component'; -import { NewSupertasksComponent } from './new-supertasks/new-supertasks.component'; +import { NewSupertasksComponent } from '../core/_components/forms/custom-forms/task/new-supertasks/new-supertasks.component'; import { NewTasksComponent } from './new-tasks/new-tasks.component'; import { NgModule } from '@angular/core'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; From e9b02877da88bd5260c3ba442ffd706b9e01aa78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Mon, 20 Nov 2023 17:50:05 +0000 Subject: [PATCH 285/419] Maxresults --- src/app/core/_services/buildparams.ts | 23 ++- src/app/core/_services/main.service.ts | 223 ++++++++++++++++--------- 2 files changed, 162 insertions(+), 84 deletions(-) diff --git a/src/app/core/_services/buildparams.ts b/src/app/core/_services/buildparams.ts index e68bbc3a..0a4bb95e 100644 --- a/src/app/core/_services/buildparams.ts +++ b/src/app/core/_services/buildparams.ts @@ -7,17 +7,24 @@ * @public */ -import { HttpParams } from "@angular/common/http"; -import { Params } from "@angular/router"; +import { HttpParams } from '@angular/common/http'; +import { Params } from '@angular/router'; -export function setParameter(routerParams: Params): HttpParams { +export function setParameter( + routerParams: Params, + maxResults?: string | number +): HttpParams { let queryParams = new HttpParams(); for (const key in routerParams) { - if (routerParams.hasOwnProperty(key)) { - queryParams = queryParams.set(key, routerParams[key]); - } + if (routerParams.hasOwnProperty(key)) { + queryParams = queryParams.set(key, routerParams[key]); + } } - return queryParams; -} + // If maxResults is not present, add it to queryParams + if (!queryParams.has('maxResults')) { + queryParams = queryParams.set('maxResults', maxResults); + } + return queryParams; +} diff --git a/src/app/core/_services/main.service.ts b/src/app/core/_services/main.service.ts index 54011377..ae4e5cec 100644 --- a/src/app/core/_services/main.service.ts +++ b/src/app/core/_services/main.service.ts @@ -1,8 +1,20 @@ -import { Observable, tap, retryWhen, delay, take, debounceTime } from 'rxjs'; +import { + Observable, + catchError, + debounceTime, + delay, + forkJoin, + map, + of, + retryWhen, + switchMap, + take, + tap +} from 'rxjs'; import { environment } from './../../../environments/environment'; import { Inject, Injectable, PLATFORM_ID } from '@angular/core'; import { AuthService } from './access/auth.service'; -import { HttpClient} from '@angular/common/http'; +import { HttpClient } from '@angular/common/http'; import { setParameter } from './buildparams'; import { Params } from '@angular/router'; import { ConfigService } from './shared/config.service'; @@ -11,66 +23,120 @@ import { ConfigService } from './shared/config.service'; providedIn: 'root' }) export class GlobalService { - constructor( private http: HttpClient, - @Inject(PLATFORM_ID) private platformId: Object, - private as:AuthService, - private cs:ConfigService, - ) { } + private as: AuthService, + private cs: ConfigService + ) {} -/** - * Get logged user id - * @returns id -**/ + /** + * Get logged user id + * @returns id + **/ - get userId(){ + get userId() { return this.as.userId; } + /** + * Gets the maximum number of results from the environment configuration. + * @returns {number} The maximum number of results. + */ + get maxResults(): number { + return Number(environment.config.prodApiMaxResults); + } -/** - * Returns all - * @param routerParams - to include multiple options such as Max number of results or filtering - * @returns Object -**/ - getAll(methodUrl: string, routerParams?: Params):Observable { + /** + * Service method to retrieve data from the API. + * If a value is specified for maxResults, it will be utilized; otherwise, the system will default to the maxResults defined in the configuration and load the data in chunks of the specified maxResults. + * @param methodUrl - The API endpoint URL. + * @param routerParams - Parameters for the API request, including options such as max number of results or filtering. + * @returns An observable that emits the API response. + */ + getAll(methodUrl: string, routerParams?: Params): Observable { let queryParams: Params = {}; + let fixedMaxResults: boolean; + if (routerParams) { - queryParams = setParameter(routerParams); + if (!('maxResults' in routerParams)) { + fixedMaxResults = true; + } + queryParams = setParameter(routerParams, this.maxResults); } - return this.http.get(this.cs.getEndpoint() + methodUrl, {params: queryParams}) + + return this.http + .get(this.cs.getEndpoint() + methodUrl, { params: queryParams }) + .pipe( + switchMap((response: any) => { + const total = response.total || 0; + const maxResults = this.maxResults; + + if (total > maxResults && fixedMaxResults) { + const requests: Observable[] = []; + const numRequests = Math.ceil(total / maxResults); + + for (let i = 0; i < numRequests; i++) { + const startsAt = i * maxResults; + const partialParams = setParameter( + { ...queryParams, startsAt }, + maxResults + ); + requests.push( + this.http.get(this.cs.getEndpoint() + methodUrl, { + params: partialParams + }) + ); + } + + return forkJoin([of(response), ...requests]).pipe( + catchError((error) => { + console.error('Error in forkJoin:', error); + return of(response); + }) + ); + } else { + return of(response); + } + }), + catchError((error) => { + console.error('Error in switchMap:', error); + return of({ values: [] }); + }) + ); } -/** - * Returns an specific element - * @param id - element id - * @returns Object -**/ - get(methodUrl:string, id: number, routerParams?: Params):Observable { + /** + * Returns an specific element + * @param id - element id + * @returns Object + **/ + get(methodUrl: string, id: number, routerParams?: Params): Observable { let queryParams: Params = {}; if (routerParams) { - queryParams = setParameter(routerParams); + queryParams = setParameter(routerParams); } - return this.http.get(`${this.cs.getEndpoint() + methodUrl}/${id}`,{params: routerParams}) + return this.http.get(`${this.cs.getEndpoint() + methodUrl}/${id}`, { + params: routerParams + }); } -/** - * Create - * @param item - fields - * @returns Object -**/ - create(methodUrl:string, item: any): Observable { - return this.http.post(this.cs.getEndpoint() + methodUrl, item) + /** + * Create + * @param item - fields + * @returns Object + **/ + create(methodUrl: string, item: any): Observable { + return this.http.post(this.cs.getEndpoint() + methodUrl, item); } -/** - * Deletes a element - * @param id - element id - * @returns Object -**/ + /** + * Deletes a element + * @param id - element id + * @returns Object + **/ delete(methodUrl: string, id: number): Observable { - return this.http.delete(this.cs.getEndpoint() + methodUrl +'/'+ id) + return this.http.delete(this.cs.getEndpoint() + methodUrl + '/' + id); + /* .pipe( tap(data => console.log(JSON.stringify(data))), retryWhen(errors => { @@ -82,52 +148,57 @@ export class GlobalService { ); } ) ); + */ } -/** - * Update element information - * @param id - element id - * @param arr - fields to be updated - * @returns Object -**/ + /** + * Update element information + * @param id - element id + * @param arr - fields to be updated + * @returns Object + **/ update(methodUrl: string, id: number, arr: any): Observable { - return this.http.patch(this.cs.getEndpoint() + methodUrl + '/' + id, arr) - .pipe( - debounceTime(2000) - ); + return this.http + .patch(this.cs.getEndpoint() + methodUrl + '/' + id, arr) + .pipe(debounceTime(2000)); } -/** - * Update agent information - * @param id - agent id - * @param arr - fields to be updated - * @returns Object -**/ + /** + * Update agent information + * @param id - agent id + * @param arr - fields to be updated + * @returns Object + **/ archive(methodUrl: string, id: number): Observable { - return this.http.patch(this.cs.getEndpoint() + methodUrl + '/' + id, {isArchived: true}) + return this.http.patch( + this.cs.getEndpoint() + methodUrl + '/' + id, + { isArchived: true } + ); } -/** - * Helper Create function - * @param option - method used. ie. /abort /reset /importFile - * @param arr - fields to be updated - * @returns Object -**/ + /** + * Helper Create function + * @param option - method used. ie. /abort /reset /importFile + * @param arr - fields to be updated + * @returns Object + **/ chelper(methodUrl: string, option: string, arr: any): Observable { - return this.http.post(this.cs.getEndpoint() + methodUrl + '/' + option, arr) + return this.http.post( + this.cs.getEndpoint() + methodUrl + '/' + option, + arr + ); } -/** - * Helper Update function - * @param option - method used. ie. /abort /reset /importFile - * @param arr - fields to be updated - * @returns Object -**/ + /** + * Helper Update function + * @param option - method used. ie. /abort /reset /importFile + * @param arr - fields to be updated + * @returns Object + **/ uhelper(methodUrl: string, option: string, arr: any): Observable { - return this.http.patch(this.cs.getEndpoint() + methodUrl + '/' + option, arr) + return this.http.patch( + this.cs.getEndpoint() + methodUrl + '/' + option, + arr + ); } - - - - } From baeb1cad97d0abfb46411e37834e96d259ec8564 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Mon, 20 Nov 2023 21:44:15 +0100 Subject: [PATCH 286/419] Update style on progress icon in dark mode --- src/styles/components/_table.scss | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/styles/components/_table.scss b/src/styles/components/_table.scss index f842f049..13e74a07 100644 --- a/src/styles/components/_table.scss +++ b/src/styles/components/_table.scss @@ -324,6 +324,12 @@ div.table-actions { .mat-mdc-row:hover { background-color: $primary-900; } + + .mat-mdc-cell { + .pulsing-progress { + color: $warn-300; + } + } } .mat-mdc-paginator { From 3cfbeb3a86905376982fb2f3a382fbc926f6233e Mon Sep 17 00:00:00 2001 From: itbf9 Date: Mon, 20 Nov 2023 21:45:41 +0100 Subject: [PATCH 287/419] Add taskId to agents --- src/app/core/_datasources/agents.datasource.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/core/_datasources/agents.datasource.ts b/src/app/core/_datasources/agents.datasource.ts index 65cead69..fb839d85 100644 --- a/src/app/core/_datasources/agents.datasource.ts +++ b/src/app/core/_datasources/agents.datasource.ts @@ -90,7 +90,6 @@ export class AgentsDataSource extends BaseDataSource { const agentAssign$ = this.service.getAll(SERV.AGENT_ASSIGN, assignParams); const chunks$ = this.service.getAll(SERV.CHUNKS, params); const users$ = this.service.getAll(SERV.USERS, params); - //const accessGroups$ = this.service.getAll(SERV.ACCESS_GROUPS) forkJoin([users$, agentAssign$, chunks$]) .pipe( @@ -115,6 +114,7 @@ export class AgentsDataSource extends BaseDataSource { agent.task = task; agent.user = users.find((e: User) => e._id === agent.userId); agent.taskName = agent.task.taskName; + agent.taskId = agent.task._id; agent.chunk = chunks.find((e) => e.agentId === agent.agentId); if (agent.chunk) { agent.chunkId = agent.chunk._id; From 6203cc956c0d2c9083a3c3aacc9b7207711070a2 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Mon, 20 Nov 2023 21:46:05 +0100 Subject: [PATCH 288/419] remove unused var --- .../core/_components/menus/action-menu/action-menu.component.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/app/core/_components/menus/action-menu/action-menu.component.ts b/src/app/core/_components/menus/action-menu/action-menu.component.ts index 2741ef46..4a825902 100644 --- a/src/app/core/_components/menus/action-menu/action-menu.component.ts +++ b/src/app/core/_components/menus/action-menu/action-menu.component.ts @@ -45,7 +45,6 @@ export class ActionMenuComponent implements OnInit, OnDestroy { currentUrl: any[]; isActive = false; - isOpen = false; /** Icon to be displayed in the menu button. */ @Input() icon: string; From 5daa4c35a773f13c9da06790d3ff725f5847db9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Tue, 21 Nov 2023 09:02:56 +0000 Subject: [PATCH 289/419] Review changes --- .../new-superhashlist.component.html | 5 +- .../new-superhashlist.component.ts | 26 ++------- .../new-supertasks.component.html | 3 +- .../new-supertasks.component.ts | 17 +++--- src/app/shared/input/abstract-input.ts | 56 +++++++++---------- .../multiselect/multiselect.component.html | 1 - .../multiselect/multiselect.component.ts | 5 +- src/styles/base/_colors.scss | 10 ++++ src/styles/components/_form.scss | 13 ----- 9 files changed, 55 insertions(+), 81 deletions(-) diff --git a/src/app/core/_components/forms/custom-forms/superhashlist/new-superhashlist/new-superhashlist.component.html b/src/app/core/_components/forms/custom-forms/superhashlist/new-superhashlist/new-superhashlist.component.html index a753af9c..2d0ea26d 100644 --- a/src/app/core/_components/forms/custom-forms/superhashlist/new-superhashlist/new-superhashlist.component.html +++ b/src/app/core/_components/forms/custom-forms/superhashlist/new-superhashlist/new-superhashlist.component.html @@ -1,6 +1,6 @@ - +
@@ -8,11 +8,10 @@ diff --git a/src/app/core/_components/forms/custom-forms/superhashlist/new-superhashlist/new-superhashlist.component.ts b/src/app/core/_components/forms/custom-forms/superhashlist/new-superhashlist/new-superhashlist.component.ts index 4faf089d..30600b08 100644 --- a/src/app/core/_components/forms/custom-forms/superhashlist/new-superhashlist/new-superhashlist.component.ts +++ b/src/app/core/_components/forms/custom-forms/superhashlist/new-superhashlist/new-superhashlist.component.ts @@ -7,7 +7,6 @@ import { OnInit } from '@angular/core'; import { - AbstractControl, FormBuilder, FormControl, FormGroup, @@ -41,16 +40,15 @@ export class NewSuperhashlistComponent implements OnInit, OnDestroy { /** Form group for the new SuperHashlist. */ form: FormGroup; + @Input() + error; + /** Maximum results for API requests. */ private maxResults = environment.config.prodApiMaxResults; /** List of hashlists. */ selectHashlists: any; - // Util functions - /** Utility function for extracting IDs from a list of items. */ - extractIds = extractIds; - /** * Constructor of the NewSuperhashlistComponent. * @@ -75,9 +73,6 @@ export class NewSuperhashlistComponent implements OnInit, OnDestroy { titleService.set(['New SuperHashlist']); } - @Input() - error; - /** * Lifecycle hook called after component initialization. */ @@ -107,7 +102,7 @@ export class NewSuperhashlistComponent implements OnInit, OnDestroy { * Loads data, specifically hashlists, for the component. */ loadData(): void { - this.globalService + const loadSubscription$ = this.globalService .getAll(SERV.HASHLISTS, { maxResults: this.maxResults, filter: 'isArchived=false,format=0' @@ -117,6 +112,7 @@ export class NewSuperhashlistComponent implements OnInit, OnDestroy { this.isLoading = false; this.changeDetectorRef.detectChanges(); }); + this.unsubscribeService.add(loadSubscription$); } /** @@ -137,16 +133,6 @@ export class NewSuperhashlistComponent implements OnInit, OnDestroy { } } - /** - * Checks if a given control is an instance of FormControl. - * - * @param control - The control to check. - * @returns True if the control is a FormControl, false otherwise. - */ - isFormControl(control: AbstractControl | null): control is FormControl { - return control instanceof FormControl; - } - /** * Handles the selection of items in the UI. * Extracts the IDs from the selected items and sets them in the form. @@ -154,7 +140,7 @@ export class NewSuperhashlistComponent implements OnInit, OnDestroy { * @param selectedItems - The items that are selected. */ handleSelectedItems(selectedItems: SelectField[]): void { - const extractedIds = this.extractIds(selectedItems, '_id'); + const extractedIds = extractIds(selectedItems, '_id'); this.form.get('hashlistIds').setValue(extractedIds); } } diff --git a/src/app/core/_components/forms/custom-forms/task/new-supertasks/new-supertasks.component.html b/src/app/core/_components/forms/custom-forms/task/new-supertasks/new-supertasks.component.html index cf4931bc..a63fdb4a 100644 --- a/src/app/core/_components/forms/custom-forms/task/new-supertasks/new-supertasks.component.html +++ b/src/app/core/_components/forms/custom-forms/task/new-supertasks/new-supertasks.component.html @@ -1,6 +1,6 @@ - + @@ -12,7 +12,6 @@ [arrayOfObjects]="selectPretasks" [mergeIdAndName]="true" [label]="'Select or search Tasks:'" - [externalControl]="form.get('pretasks')" (selectionChanged)="handleSelectedItems($event)" > diff --git a/src/app/core/_components/forms/custom-forms/task/new-supertasks/new-supertasks.component.ts b/src/app/core/_components/forms/custom-forms/task/new-supertasks/new-supertasks.component.ts index 35ee88c8..0836e998 100644 --- a/src/app/core/_components/forms/custom-forms/task/new-supertasks/new-supertasks.component.ts +++ b/src/app/core/_components/forms/custom-forms/task/new-supertasks/new-supertasks.component.ts @@ -43,16 +43,15 @@ export class NewSupertasksComponent implements OnInit, OnDestroy { /** Form group for the new SuperTask. */ form: FormGroup; + @Input() + error; + /** Maximum results for API requests. */ private maxResults = environment.config.prodApiMaxResults; /** List of PreTasks. */ selectPretasks: any[]; - // Util functions - extractIds = extractIds; - transformSelectOptions = transformSelectOptions; - constructor( private unsubscribeService: UnsubscribeService, private changeDetectorRef: ChangeDetectorRef, @@ -66,9 +65,6 @@ export class NewSupertasksComponent implements OnInit, OnDestroy { titleService.set(['New SuperTask']); } - @Input() - error; - /** * Lifecycle hook called after component initialization. */ @@ -104,10 +100,10 @@ export class NewSupertasksComponent implements OnInit, OnDestroy { _id: '_id' } }; - this.gs + const loadSubscription$ = this.gs .getAll(SERV.PRETASKS, { maxResults: this.maxResults }) .subscribe((response: ListResponseWrapper) => { - const transformedOptions = this.transformSelectOptions( + const transformedOptions = transformSelectOptions( response.values, field ); @@ -115,6 +111,7 @@ export class NewSupertasksComponent implements OnInit, OnDestroy { this.isLoading = false; this.changeDetectorRef.detectChanges(); }); + this.unsubscribeService.add(loadSubscription$); } /** @@ -142,7 +139,7 @@ export class NewSupertasksComponent implements OnInit, OnDestroy { * @param selectedItems - The items that are selected. */ handleSelectedItems(selectedItems: SelectField[]): void { - const extractedIds = this.extractIds(selectedItems, '_id'); + const extractedIds = extractIds(selectedItems, '_id'); this.form.get('hashlistIds').setValue(extractedIds); } } diff --git a/src/app/shared/input/abstract-input.ts b/src/app/shared/input/abstract-input.ts index 124c15b6..cf627857 100644 --- a/src/app/shared/input/abstract-input.ts +++ b/src/app/shared/input/abstract-input.ts @@ -6,34 +6,6 @@ export class AbstractInputComponent implements OnInit, ControlValueAccessor { @ViewChild('inputField') inputField: ElementRef; - constructor() {} - - onChange = (newValue: T) => {}; - - onTouched = () => {}; - - writeValue(newValue: any): void { - this.value = newValue; - } - - registerOnChange(fn: any): void { - this.onChange = fn; - } - - registerOnTouched(fn: any): void { - this.onTouched = fn; - } - - setDisabledState?(isDisabled: boolean): void { - this.disabled = isDisabled; - } - - focus() { - if (this.inputField && this.inputField.nativeElement) { - this.inputField.nativeElement.focus(); - } - } - @Input() title: string; @@ -54,6 +26,8 @@ export class AbstractInputComponent implements OnInit, ControlValueAccessor { @Input() tooltip: string; + constructor() {} + ngOnInit(): void { if (!this.inputId) { this.inputId = 'input_' + Math.random().toString(36).substr(2, 9); @@ -63,4 +37,30 @@ export class AbstractInputComponent implements OnInit, ControlValueAccessor { ); } } + + onChange = (newValue: T) => {}; + + onTouched = () => {}; + + writeValue(newValue: any): void { + this.value = newValue; + } + + registerOnChange(fn: any): void { + this.onChange = fn; + } + + registerOnTouched(fn: any): void { + this.onTouched = fn; + } + + setDisabledState?(isDisabled: boolean): void { + this.disabled = isDisabled; + } + + focus() { + if (this.inputField && this.inputField.nativeElement) { + this.inputField.nativeElement.focus(); + } + } } diff --git a/src/app/shared/input/multiselect/multiselect.component.html b/src/app/shared/input/multiselect/multiselect.component.html index 0dace6cc..5427b802 100644 --- a/src/app/shared/input/multiselect/multiselect.component.html +++ b/src/app/shared/input/multiselect/multiselect.component.html @@ -20,7 +20,6 @@ Date: Tue, 21 Nov 2023 09:07:46 +0000 Subject: [PATCH 290/419] Conflict File Module --- src/app/files/files.module.ts | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/app/files/files.module.ts b/src/app/files/files.module.ts index 145d7113..a4597ccb 100644 --- a/src/app/files/files.module.ts +++ b/src/app/files/files.module.ts @@ -1,16 +1,17 @@ -import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; -import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; -import { DataTablesModule } from 'angular-datatables'; -import { RouterModule } from '@angular/router'; -import { CommonModule } from '@angular/common'; -import { NgModule } from '@angular/core'; -import { NewFilesComponent } from './new-files/new-files.component'; +import { CommonModule } from '@angular/common'; import { ComponentsModule } from '../shared/components.module'; +import { CoreComponentsModule } from '../core/_components/core-components.module'; +import { DataTablesModule } from 'angular-datatables'; +import { FilesComponent } from './files.component'; import { FilesRoutingModule } from './files-routing.module'; +import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; +import { NewFilesComponent } from './new-files/new-files.component'; +import { NgModule } from '@angular/core'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { PipesModule } from '../shared/pipes.module'; -import { FilesComponent } from './files.component'; +import { RouterModule } from '@angular/router'; @NgModule({ declarations: [FilesComponent, NewFilesComponent], @@ -21,6 +22,7 @@ import { FilesComponent } from './files.component'; DataTablesModule, ComponentsModule, CommonModule, + CoreComponentsModule, RouterModule, FormsModule, PipesModule, From 5614e2c8327ce10baf216b2b18ade77d35eb2b28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Tue, 21 Nov 2023 09:20:13 +0000 Subject: [PATCH 291/419] Change selector name --- .../new-superhashlist/new-superhashlist.component.html | 6 +++--- .../task/new-supertasks/new-supertasks.component.html | 6 +++--- src/app/shared/input/check/check.component.ts | 6 +++--- src/app/shared/input/color/color.component.ts | 6 +++--- src/app/shared/input/date/date.component.ts | 6 +++--- src/app/shared/input/multiselect/multiselect.component.ts | 2 +- src/app/shared/input/number/number.component.ts | 6 +++--- src/app/shared/input/select/select.component.ts | 6 +++--- src/app/shared/input/text/text.component.ts | 6 +++--- 9 files changed, 25 insertions(+), 25 deletions(-) diff --git a/src/app/core/_components/forms/custom-forms/superhashlist/new-superhashlist/new-superhashlist.component.html b/src/app/core/_components/forms/custom-forms/superhashlist/new-superhashlist/new-superhashlist.component.html index 2d0ea26d..09a2ed3e 100644 --- a/src/app/core/_components/forms/custom-forms/superhashlist/new-superhashlist/new-superhashlist.component.html +++ b/src/app/core/_components/forms/custom-forms/superhashlist/new-superhashlist/new-superhashlist.component.html @@ -4,16 +4,16 @@ - + - + > diff --git a/src/app/core/_components/forms/custom-forms/task/new-supertasks/new-supertasks.component.html b/src/app/core/_components/forms/custom-forms/task/new-supertasks/new-supertasks.component.html index a63fdb4a..7ffcdd5e 100644 --- a/src/app/core/_components/forms/custom-forms/task/new-supertasks/new-supertasks.component.html +++ b/src/app/core/_components/forms/custom-forms/task/new-supertasks/new-supertasks.component.html @@ -4,16 +4,16 @@ - + - + > diff --git a/src/app/shared/input/check/check.component.ts b/src/app/shared/input/check/check.component.ts index 93dcd1fb..dcc28209 100644 --- a/src/app/shared/input/check/check.component.ts +++ b/src/app/shared/input/check/check.component.ts @@ -7,15 +7,15 @@ import { NG_VALUE_ACCESSOR } from '@angular/forms'; * * Usage Example: * ```html - * + * > * ``` */ @Component({ - selector: 'app-input-check', + selector: 'input-check', templateUrl: './check.component.html', providers: [ { diff --git a/src/app/shared/input/color/color.component.ts b/src/app/shared/input/color/color.component.ts index dd9bc29d..84eb0db2 100644 --- a/src/app/shared/input/color/color.component.ts +++ b/src/app/shared/input/color/color.component.ts @@ -18,14 +18,14 @@ import { ChangeDetectorRef } from '@angular/core'; * * Usage Example: * ```html - + > * ``` */ @Component({ - selector: 'app-input-color', + selector: 'input-color', templateUrl: './color.component.html', providers: [ { diff --git a/src/app/shared/input/date/date.component.ts b/src/app/shared/input/date/date.component.ts index 94bf9030..a50e9057 100644 --- a/src/app/shared/input/date/date.component.ts +++ b/src/app/shared/input/date/date.component.ts @@ -7,16 +7,16 @@ import { NG_VALUE_ACCESSOR } from '@angular/forms'; * * Usage Example: * ```html - + > * ``` */ @Component({ - selector: 'app-input-date', + selector: 'input-date', templateUrl: './date.component.html', providers: [ { diff --git a/src/app/shared/input/multiselect/multiselect.component.ts b/src/app/shared/input/multiselect/multiselect.component.ts index ee45201a..6c8a4ece 100644 --- a/src/app/shared/input/multiselect/multiselect.component.ts +++ b/src/app/shared/input/multiselect/multiselect.component.ts @@ -29,7 +29,7 @@ import { SelectField } from 'src/app/core/_models/input.model'; * Supports dynamic filtering, highlighting, and emits selection changes. */ @Component({ - selector: 'app-input-multiselect', + selector: 'input-multiselect', templateUrl: 'multiselect.component.html', providers: [ { diff --git a/src/app/shared/input/number/number.component.ts b/src/app/shared/input/number/number.component.ts index b8d891e5..a5c9d617 100644 --- a/src/app/shared/input/number/number.component.ts +++ b/src/app/shared/input/number/number.component.ts @@ -7,15 +7,15 @@ import { NG_VALUE_ACCESSOR } from '@angular/forms'; * * Usage Example: * ```html - * + * > * ``` */ @Component({ - selector: 'app-input-number', + selector: 'input-number', templateUrl: './number.component.html', providers: [ { diff --git a/src/app/shared/input/select/select.component.ts b/src/app/shared/input/select/select.component.ts index 204b0a70..a592aac7 100644 --- a/src/app/shared/input/select/select.component.ts +++ b/src/app/shared/input/select/select.component.ts @@ -6,17 +6,17 @@ import { AbstractInputComponent } from '../abstract-input'; * * Usage Example: * ```html - * + * > * ``` */ @Component({ - selector: 'app-input-select', + selector: 'input-select', templateUrl: './select.component.html' }) export class InputSelectComponent extends AbstractInputComponent< diff --git a/src/app/shared/input/text/text.component.ts b/src/app/shared/input/text/text.component.ts index 17ad571a..0d534e28 100644 --- a/src/app/shared/input/text/text.component.ts +++ b/src/app/shared/input/text/text.component.ts @@ -7,17 +7,17 @@ import { NG_VALUE_ACCESSOR } from '@angular/forms'; * * Usage Example: * ```html - * + * > * ``` */ @Component({ - selector: 'app-input-text', + selector: 'input-text', templateUrl: './text.component.html', providers: [ { From 51fc0c9dc973461cec9f5e23b2efa6d4095c76e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Tue, 21 Nov 2023 09:22:50 +0000 Subject: [PATCH 292/419] Conflic file-edit --- .../files-edit/files-edit.component.html | 59 +++++++ .../files/files-edit/files-edit.component.ts | 147 ++++++++++++++++++ 2 files changed, 206 insertions(+) create mode 100644 src/app/files/files-edit/files-edit.component.html create mode 100644 src/app/files/files-edit/files-edit.component.ts diff --git a/src/app/files/files-edit/files-edit.component.html b/src/app/files/files-edit/files-edit.component.html new file mode 100644 index 00000000..a7d8aef0 --- /dev/null +++ b/src/app/files/files-edit/files-edit.component.html @@ -0,0 +1,59 @@ + + + + +
+ + + +
+ + + + + + + + + + +
+
+ +
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Time foundPlaintextHashHashlistAgentTaskChunkTypeSalt
{{ hash.timeCracked | uiDate }}{{ hash.plaintext }} - - - {{ hash.hashlist.name | shortenString:30 }} - - {{ hash.chunk['agentId'] }} - - {{ hash.chunk['taskId'] }} - {{ hash.chunkId }}{{ hash.hashlist['format'] }}{{ hash.salt }}
+ + - diff --git a/src/app/hashlists/show-cracks/show-cracks.component.ts b/src/app/hashlists/show-cracks/show-cracks.component.ts index 7ebab750..1ec3d83f 100644 --- a/src/app/hashlists/show-cracks/show-cracks.component.ts +++ b/src/app/hashlists/show-cracks/show-cracks.component.ts @@ -1,187 +1,12 @@ -import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core'; -import { environment } from './../../../environments/environment'; -import { faPlus} from '@fortawesome/free-solid-svg-icons'; -import { DataTableDirective } from 'angular-datatables'; -import { Subject, Subscription } from 'rxjs'; - import { AutoTitleService } from 'src/app/core/_services/shared/autotitle.service'; -import { GlobalService } from 'src/app/core/_services/main.service'; -import { SERV } from '../../core/_services/main.config'; +import { Component } from '@angular/core'; @Component({ selector: 'app-show-cracks', templateUrl: './show-cracks.component.html' }) -/** - * ShowCracksComponent is a component that manages and displays all groups data. - * - * It uses DataTables to display and interact with the groups data, including exporting, deleting, bulk actions - * and refreshing the table. -*/ -export class ShowCracksComponent implements OnInit, OnDestroy { - - // Font Awesome icons - faPlus=faPlus; - - // ViewChild reference to the DataTableDirective - @ViewChild(DataTableDirective, {static: false}) - dtElement: DataTableDirective; - - dtTrigger: Subject = new Subject(); - dtOptions: any = {}; - - // List of hashes ToDo. Change Interface - allhashes: any = []; - - // Subscriptions to unsubscribe on component destruction - subscriptions: Subscription[] = [] - - private maxResults = environment.config.prodApiMaxResults; - - constructor( - private titleService: AutoTitleService, - private gs: GlobalService, - ) { - titleService.set(['Show Cracks']) - } - - /** - * Initializes DataTable and retrieves groups. - */ - ngOnInit(): void { - this.loadCracks(); - this.setupTable(); - } - - /** - * Unsubscribes from active subscriptions and DataTable. - */ - ngOnDestroy(): void { - this.dtTrigger.unsubscribe(); - for (const sub of this.subscriptions) { - sub.unsubscribe(); - } - } - - /** - * Refreshes the data and the DataTable. - */ - onRefresh() { - this.rerender(); - this.ngOnInit(); - } - - /** - * Rerenders the DataTable instance. - * Destroys and recreates the DataTable to reflect changes. - */ - rerender(): void { - this.dtElement.dtInstance.then((dtInstance: DataTables.Api) => { - // Destroy the table first - dtInstance.destroy(); - // Call the dtTrigger to rerender again - setTimeout(() => { - this.dtTrigger['new'].next(); - }); - }); - } - - /** - * Fetches Hashes from the server. - * Subscribes to the API response and updates the Groups list. - */ - loadCracks() { - - const params = {'maxResults': this.maxResults, 'filter': 'isCracked=1', 'expand':'hashlist,chunk'} - - this.gs.getAll(SERV.HASHES,params).subscribe((response: any) => { - this.allhashes = response.values; - this.dtTrigger.next(void 0); - }); - - } - - /** - * Sets up the DataTable options and buttons. - * Customizes DataTable appearance and behavior. - */ - setupTable(): void { - // DataTables options - const self = this; - this.dtOptions = { - dom: 'Bfrtip', - scrollX: true, - pageLength: 25, - lengthMenu: [ - [10, 25, 50, 100, 250, -1], - [10, 25, 50, 100, 250, 'All'] - ], - stateSave: true, - select: true, - order: [[0, 'desc']], - buttons: { - dom: { - button: { - className: 'dt-button buttons-collection btn btn-sm-dt btn-outline-gray-600-dt', - } - }, - buttons: [ - { - text: '↻', - autoClose: true, - action: function (e, dt, node, config) { - self.onRefresh(); - } - }, - { - extend: 'collection', - text: 'Export', - buttons: [ - { - extend: 'excelHtml5', - exportOptions: { - columns: [0, 1, 2, 3, 4, 5, 6, 7, 8] - }, - }, - { - extend: 'print', - exportOptions: { - columns: [0, 1, 2, 3, 4, 5, 6, 7, 8] - }, - customize: function ( win ) { - $(win.document.body) - .css( 'font-size', '10pt' ) - $(win.document.body).find( 'table' ) - .addClass( 'compact' ) - .css( 'font-size', 'inherit' ); - } - }, - { - extend: 'csvHtml5', - exportOptions: {modifier: {selected: true}}, - select: true, - customize: function (dt, csv) { - let data = ""; - for (let i = 0; i < dt.length; i++) { - data = "Logs\n\n"+ dt; - } - return data; - } - }, - 'copy' - ] - }, - { - extend: 'colvis', - text: 'Column View', - columns: [0, 1, 2, 3, 4, 5, 6, 7, 8] - }, - { - extend: "pageLength", - className: "btn-sm" - }, - ], - } - }; +export class ShowCracksComponent { + constructor(private titleService: AutoTitleService) { + titleService.set(['Show Cracks']); } } diff --git a/src/app/shared/components.module.ts b/src/app/shared/components.module.ts index f6cf3ec9..714031df 100644 --- a/src/app/shared/components.module.ts +++ b/src/app/shared/components.module.ts @@ -1,39 +1,36 @@ -import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; -import { CommonModule } from '@angular/common'; -import { NgModule } from "@angular/core"; - -import { LoadingSpinnerComponent } from '../shared/loading-spinner/loading-spinner.component'; -import { HashtypeDetectorComponent } from "./hashtype-detector/hashtype-detector.component"; import { ActiveSpinnerComponent } from './loading-spinner/loading-spinner-active.component'; -import { PassStrenghtComponent } from './password/pass-strenght/pass-strenght.component'; -import { ButtonTruncateTextComponent } from './table/button-truncate-text.component'; -import { HexconvertorComponent } from "./utils/hexconvertor/hexconvertor.component"; -import { TimeoutDialogComponent } from './dialog/timeout/timeout-dialog.component'; -import { PassMatchComponent } from './password/pass-match/pass-match.component'; -import { CheatsheetComponent } from "./alert/cheatsheet/cheatsheet.component"; -import { FilterTextboxModule } from "./filter-textbox/filter-textbox.module"; -import { SwitchThemeModule } from "./switch-theme/switch-theme.module"; -import { MatProgressBarModule } from '@angular/material/progress-bar'; -import { TimeoutComponent } from "./alert/timeout/timeout.component"; -import { HorizontalNavModule } from './navigation/navigation.module'; -import { PageTitleModule } from "./page-headers/page-title.module"; -import { PaginationModule } from "./pagination/pagination.module"; +import { AlertComponent } from './alert/alert.component'; +import { ButtonsModule } from './buttons/buttons.module'; +import { CheatsheetComponent } from './alert/cheatsheet/cheatsheet.component'; +import { ColorPickerModule } from 'ngx-color-picker'; +import { CommonModule } from '@angular/common'; import { DynamicFormModule } from './form/dynamicform.module'; -import { MatDialogModule } from '@angular/material/dialog'; -import { MatButtonModule } from '@angular/material/button'; -import { GridModule } from "./grid-containers/grid.module"; -import { TableModule } from "./table/table-actions.module"; -import { AlertComponent } from "./alert/alert.component"; -import { ButtonsModule } from "./buttons/buttons.module"; +import { FilterTextboxModule } from './filter-textbox/filter-textbox.module'; +import { FormsModule } from '@angular/forms'; +import { GraphsModule } from './graphs/graphs.module'; +import { GridModule } from './grid-containers/grid.module'; +import { HashtypeDetectorComponent } from './hashtype-detector/hashtype-detector.component'; +import { HexconvertorComponent } from './utils/hexconvertor/hexconvertor.component'; +import { HorizontalNavModule } from './navigation/navigation.module'; +import { LoadingSpinnerComponent } from '../shared/loading-spinner/loading-spinner.component'; import { LottiesModule } from './lottie/lottie.module'; +import { MatButtonModule } from '@angular/material/button'; +import { MatDialogModule } from '@angular/material/dialog'; import { MatIconModule } from '@angular/material/icon'; -import { GraphsModule } from "./graphs/graphs.module"; -import { ColorPickerModule } from 'ngx-color-picker'; -import { FormsModule } from "@angular/forms"; +import { MatProgressBarModule } from '@angular/material/progress-bar'; +import { NgModule } from '@angular/core'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { PageTitleModule } from './page-headers/page-title.module'; +import { PaginationModule } from './pagination/pagination.module'; +import { PassMatchComponent } from './password/pass-match/pass-match.component'; +import { PassStrenghtComponent } from './password/pass-strenght/pass-strenght.component'; +import { SwitchThemeModule } from './switch-theme/switch-theme.module'; +import { TableModule } from './table/table-actions.module'; +import { TimeoutComponent } from './alert/timeout/timeout.component'; +import { TimeoutDialogComponent } from './dialog/timeout/timeout-dialog.component'; @NgModule({ declarations: [ - ButtonTruncateTextComponent, HashtypeDetectorComponent, LoadingSpinnerComponent, TimeoutDialogComponent, @@ -46,6 +43,9 @@ import { FormsModule } from "@angular/forms"; AlertComponent ], imports: [ + CommonModule, + FormsModule, + NgbModule, MatProgressBarModule, FilterTextboxModule, HorizontalNavModule, @@ -61,14 +61,10 @@ import { FormsModule } from "@angular/forms"; MatIconModule, MatIconModule, GraphsModule, - CommonModule, - FormsModule, TableModule, - GridModule, - NgbModule + GridModule ], exports: [ - ButtonTruncateTextComponent, HashtypeDetectorComponent, LoadingSpinnerComponent, TimeoutDialogComponent, diff --git a/src/app/shared/table/button-truncate-text.component.ts b/src/app/shared/table/button-truncate-text.component.ts deleted file mode 100644 index 17657444..00000000 --- a/src/app/shared/table/button-truncate-text.component.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { Component, Input } from '@angular/core'; - -/** - * Component for truncating and expanding text with a "More/Less" button. - */ -@Component({ - selector: 'btn-truncate-text', - template: ` -
-
{{ text | slice:0:maxLength }}{{ text.length > maxLength ? '...' : '' }}
-
{{ text }}
- -
- `, - styles: [` - .text-container { - position: relative; - max-width: 200px; /* Customize as needed */ - overflow: hidden; - margin: 0; /* Add this to remove margin */ - padding: 0; /* Add this to remove padding */ - } - - .text-container button { - display: inline; - background: none; - border: none; - color: blue; - cursor: pointer; - margin: 0; /* Add this to remove margin */ - padding: 0; /* Add this to remove padding */ - } -`] -}) -export class ButtonTruncateTextComponent { - /** - * The text to be truncated and expanded. - */ - @Input() text: string; - - /** - * The maximum length of the text to display before truncation. - */ - @Input() maxLength: number = 40; // Default maximum length - - /** - * Flag to determine if the text is expanded or truncated. - */ - expanded: boolean = false; - - /** - * Toggles the expansion of the text. - */ - toggleExpansion() { - this.expanded = !this.expanded; - } -} diff --git a/src/styles/components/_table.scss b/src/styles/components/_table.scss index 13e74a07..7e0a98de 100644 --- a/src/styles/components/_table.scss +++ b/src/styles/components/_table.scss @@ -245,6 +245,10 @@ table.hashtopolis-table { animation: pulse 1s infinite; } + .text-container button { + color: $accent-900; + } + .row-action { .mat-icon { margin-right: 0; @@ -329,6 +333,9 @@ div.table-actions { .pulsing-progress { color: $warn-300; } + .text-container button { + color: $accent-600; + } } } From 0451ff74ac55740a2225b2bf1075afeedccbc783 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Tue, 21 Nov 2023 09:50:56 +0000 Subject: [PATCH 294/419] Error in Merge --- src/app/core/_services/main.service.ts | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/app/core/_services/main.service.ts b/src/app/core/_services/main.service.ts index 12aaf31e..ae4e5cec 100644 --- a/src/app/core/_services/main.service.ts +++ b/src/app/core/_services/main.service.ts @@ -1,4 +1,3 @@ - import { Observable, catchError, @@ -12,9 +11,8 @@ import { take, tap } from 'rxjs'; -import { Observable, debounceTime, delay, retryWhen, take, tap } from 'rxjs'; import { environment } from './../../../environments/environment'; -import { Inject, Injectable } from '@angular/core'; +import { Inject, Injectable, PLATFORM_ID } from '@angular/core'; import { AuthService } from './access/auth.service'; import { HttpClient } from '@angular/common/http'; import { setParameter } from './buildparams'; @@ -105,11 +103,6 @@ export class GlobalService { return of({ values: [] }); }) ); - queryParams = setParameter(routerParams); - } - return this.http.get(this.cs.getEndpoint() + methodUrl, { - params: queryParams - }); } /** From 89c73edddd7bd3d22517f8fa82787deebb76baf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Tue, 21 Nov 2023 10:00:13 +0000 Subject: [PATCH 295/419] Conflict --- src/app/core/_services/main.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/core/_services/main.service.ts b/src/app/core/_services/main.service.ts index ae4e5cec..188d1835 100644 --- a/src/app/core/_services/main.service.ts +++ b/src/app/core/_services/main.service.ts @@ -50,7 +50,7 @@ export class GlobalService { * Service method to retrieve data from the API. * If a value is specified for maxResults, it will be utilized; otherwise, the system will default to the maxResults defined in the configuration and load the data in chunks of the specified maxResults. * @param methodUrl - The API endpoint URL. - * @param routerParams - Parameters for the API request, including options such as max number of results or filtering. + * @param routerParams - Parameters for the API request, including options such as Max number of results or filtering. * @returns An observable that emits the API response. */ getAll(methodUrl: string, routerParams?: Params): Observable { From 177acb9dc5d18f08d59011d5fe307e1ef5eca4da Mon Sep 17 00:00:00 2001 From: itbf9 Date: Tue, 21 Nov 2023 11:57:23 +0100 Subject: [PATCH 296/419] Update filter --- .../core/_components/tables/base-table/base-table.component.ts | 1 - .../_components/tables/cracks-table/cracks-table.component.ts | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) 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 8f87caab..e0601f21 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 @@ -103,7 +103,6 @@ export class BaseTableComponent { @Cacheable(['agentId']) async renderAgentLink(obj: unknown): Promise { - console.log('obj', obj); return [ { routerLink: diff --git a/src/app/core/_components/tables/cracks-table/cracks-table.component.ts b/src/app/core/_components/tables/cracks-table/cracks-table.component.ts index cf66563b..4fe02db3 100644 --- a/src/app/core/_components/tables/cracks-table/cracks-table.component.ts +++ b/src/app/core/_components/tables/cracks-table/cracks-table.component.ts @@ -42,7 +42,7 @@ export class CracksTableComponent } filter(item: Hash, filterValue: string): boolean { - if (item.hash.toLowerCase().includes(filterValue)) { + if (item.plaintext.toLowerCase().includes(filterValue)) { return true; } From 66fcbf6f47627d034d435afa8132948d65f35aca Mon Sep 17 00:00:00 2001 From: itbf9 Date: Tue, 21 Nov 2023 12:46:30 +0100 Subject: [PATCH 297/419] Add permissions table --- .../_components/core-components.module.ts | 7 +- .../menus/base-menu/base-menu.component.ts | 12 + .../bulk-action-menu.component.ts | 2 + .../bulk-action-menu.constants.ts | 1 + .../row-action-menu.component.ts | 16 ++ .../row-action-menu.constants.ts | 2 + .../tables/base-table/base-table.component.ts | 12 + .../tables/ht-table/ht-table.models.ts | 1 + .../permissions-table.component.html | 16 ++ .../permissions-table.component.ts | 223 +++++++++++++++ .../permissions-table.constants.ts | 5 + .../_datasources/permissions.datasource.ts | 47 ++++ src/app/core/_models/config-ui.model.ts | 6 + .../_models/global-permission-group.model.ts | 3 + .../globalpermissionsgroups.component.html | 50 +--- .../globalpermissionsgroups.component.ts | 266 +----------------- 16 files changed, 362 insertions(+), 307 deletions(-) create mode 100644 src/app/core/_components/tables/permissions-table/permissions-table.component.html create mode 100644 src/app/core/_components/tables/permissions-table/permissions-table.component.ts create mode 100644 src/app/core/_components/tables/permissions-table/permissions-table.constants.ts create mode 100644 src/app/core/_datasources/permissions.datasource.ts diff --git a/src/app/core/_components/core-components.module.ts b/src/app/core/_components/core-components.module.ts index 95dc12d4..8f717260 100644 --- a/src/app/core/_components/core-components.module.ts +++ b/src/app/core/_components/core-components.module.ts @@ -38,6 +38,7 @@ import { MatSortModule } from '@angular/material/sort'; import { MatTableModule } from '@angular/material/table'; import { MatTooltipModule } from '@angular/material/tooltip'; import { NgModule } from '@angular/core'; +import { PermissionsTableComponent } from './tables/permissions-table/permissions-table.component'; import { PreprocessorsTableComponent } from './tables/preprocessors-table/preprocessors-table.component'; import { RouterModule } from '@angular/router'; import { RowActionMenuComponent } from './menus/row-action-menu/row-action-menu.component'; @@ -67,7 +68,8 @@ import { UsersTableComponent } from './tables/users-table/users-table.component' AgentBinariesTableComponent, HealthChecksTableComponent, LogsTableComponent, - UsersTableComponent + UsersTableComponent, + PermissionsTableComponent ], imports: [ ReactiveFormsModule, @@ -113,7 +115,8 @@ import { UsersTableComponent } from './tables/users-table/users-table.component' AgentBinariesTableComponent, HealthChecksTableComponent, LogsTableComponent, - UsersTableComponent + UsersTableComponent, + PermissionsTableComponent ], providers: [ { diff --git a/src/app/core/_components/menus/base-menu/base-menu.component.ts b/src/app/core/_components/menus/base-menu/base-menu.component.ts index 9f122e71..725f9e64 100644 --- a/src/app/core/_components/menus/base-menu/base-menu.component.ts +++ b/src/app/core/_components/menus/base-menu/base-menu.component.ts @@ -80,6 +80,18 @@ export class BaseMenuComponent { } } + /** + * Check if the data row is of type "GlobalPermissionGroup". + * @returns `true` if the data row is a permission; otherwise, `false`. + */ + protected isPermission(): boolean { + try { + return this.data['_id'] === this.data['id'] && 'permissions' in this.data; + } catch (error) { + return false; + } + } + /** * Check if the data row is of type "HealthCheck". * @returns `true` if the data row is a health check; otherwise, `false`. diff --git a/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.component.ts b/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.component.ts index 1d7a7744..a4e922ef 100644 --- a/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.component.ts +++ b/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.component.ts @@ -48,6 +48,8 @@ export class BulkActionMenuComponent ); } else if (this.dataType === 'hashlists') { this.setHashlistMenu(); + } else if (this.dataType === 'permissions') { + this.setDeleteMenu(BulkActionMenuLabel.DELETE_PERMISSIONS); } else if (this.dataType === 'hashtypes') { this.setDeleteMenu(BulkActionMenuLabel.DELETE_HASHTYPES); } else if (this.dataType === 'agent-binaries') { diff --git a/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.constants.ts b/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.constants.ts index 0161c198..97f8a43b 100644 --- a/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.constants.ts +++ b/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.constants.ts @@ -9,6 +9,7 @@ export const BulkActionMenuLabel = { DELETE_AGENTBINARIES: 'Delete Agent Binaries', DELETE_HEALTHCHECKS: 'Delete Health Checks', DELETE_USERS: 'Delete Users', + DELETE_PERMISSIONS: 'Delete Permission Groups', ACTIVATE_AGENTS: 'Activate Agents', ACTIVATE_USERS: 'Activate Users', DEACTIVATE_AGENTS: 'Deactivate Agents', diff --git a/src/app/core/_components/menus/row-action-menu/row-action-menu.component.ts b/src/app/core/_components/menus/row-action-menu/row-action-menu.component.ts index f8a63d63..89e15f26 100644 --- a/src/app/core/_components/menus/row-action-menu/row-action-menu.component.ts +++ b/src/app/core/_components/menus/row-action-menu/row-action-menu.component.ts @@ -48,6 +48,8 @@ export class RowActionMenuComponent RowActionMenuLabel.EDIT_HEALTHCHECK, RowActionMenuLabel.DELETE_HEALTHCHECK ); + } else if (this.isPermission()) { + this.setPermissionMenu(); } else if (this.isUser()) { this.setUserMenu(); } else if (this.isTask()) { @@ -61,6 +63,20 @@ export class RowActionMenuComponent } } + /** + * Sets the context menu items for a permission data row. + */ + private setPermissionMenu(): void { + this.setActionMenuItems(0, [ + this.getEditMenuItem(RowActionMenuLabel.EDIT_PERMISSION) + ]); + if (!this.data.user || this.data.user.length === 0) { + this.setActionMenuItems(1, [ + this.getDeleteMenuItem(RowActionMenuLabel.DELETE_PERMISSION) + ]); + } + } + /** * Sets the context menu items for a cracker data row. */ 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 c74bed2d..65f58f3a 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 @@ -13,6 +13,7 @@ export const RowActionMenuLabel = { EDIT_AGENTBINARY: 'Edit Agent Binary', EDIT_HEALTHCHECK: 'Edit Health Check', EDIT_USER: 'Edit User', + EDIT_PERMISSION: 'Edit Permission Group', DELETE_AGENT: 'Delete Agent', DELETE_CRACKER: 'Delete Cracker', DELETE_TASK: 'Delete Task', @@ -24,6 +25,7 @@ export const RowActionMenuLabel = { DELETE_AGENTBINARY: 'Delete Agent Binary', DELETE_HEALTHCHECK: 'Delete Health Check', DELETE_USER: 'Delete User', + DELETE_PERMISSION: 'Delete Permission Group', COPY_TO_TASK: 'Copy to Task', COPY_TO_PRETASK: 'Copy to Pretask', ARCHIVE_TASK: 'Archive Task', 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 50d367a0..39a82017 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 @@ -180,6 +180,18 @@ export class BaseTableComponent { ]; } + @Cacheable(['id']) + async renderPermissionLink(obj: unknown): Promise { + return [ + { + routerLink: + obj && obj['id'] + ? ['/users', 'global-permissions-groups', obj['id'], 'edit'] + : [] + } + ]; + } + @Cacheable(['accessGroups']) async renderAccessGroupLinks(obj: unknown): Promise { let links: HTTableRouterLink[] = []; diff --git a/src/app/core/_components/tables/ht-table/ht-table.models.ts b/src/app/core/_components/tables/ht-table/ht-table.models.ts index a92f45a8..311ad2b5 100644 --- a/src/app/core/_components/tables/ht-table/ht-table.models.ts +++ b/src/app/core/_components/tables/ht-table/ht-table.models.ts @@ -13,6 +13,7 @@ export type DataType = | 'agent-binaries' | 'health-checks' | 'logs' + | 'permissions' | 'superhashlists'; export interface HTTableIcon { diff --git a/src/app/core/_components/tables/permissions-table/permissions-table.component.html b/src/app/core/_components/tables/permissions-table/permissions-table.component.html new file mode 100644 index 00000000..311172b7 --- /dev/null +++ b/src/app/core/_components/tables/permissions-table/permissions-table.component.html @@ -0,0 +1,16 @@ + diff --git a/src/app/core/_components/tables/permissions-table/permissions-table.component.ts b/src/app/core/_components/tables/permissions-table/permissions-table.component.ts new file mode 100644 index 00000000..bf424cf9 --- /dev/null +++ b/src/app/core/_components/tables/permissions-table/permissions-table.component.ts @@ -0,0 +1,223 @@ +/* eslint-disable @angular-eslint/component-selector */ +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { catchError, forkJoin } from 'rxjs'; + +import { ActionMenuEvent } from '../../menus/action-menu/action-menu.model'; +import { BaseTableComponent } from '../base-table/base-table.component'; +import { BulkActionMenuAction } from '../../menus/bulk-action-menu/bulk-action-menu.constants'; +import { DialogData } from '../table-dialog/table-dialog.model'; +import { ExportMenuAction } from '../../menus/export-menu/export-menu.constants'; +import { GlobalPermissionGroup } from 'src/app/core/_models/global-permission-group.model'; +import { HTTableColumn } from '../ht-table/ht-table.models'; +import { PermissionsDataSource } from 'src/app/core/_datasources/permissions.datasource'; +import { PermissionsTableColumnLabel } from './permissions-table.constants'; +import { RowActionMenuAction } from '../../menus/row-action-menu/row-action-menu.constants'; +import { SERV } from 'src/app/core/_services/main.config'; +import { TableDialogComponent } from '../table-dialog/table-dialog.component'; + +@Component({ + selector: 'permissions-table', + templateUrl: './permissions-table.component.html' +}) +export class PermissionsTableComponent + extends BaseTableComponent + implements OnInit, OnDestroy +{ + tableColumns: HTTableColumn[] = []; + dataSource: PermissionsDataSource; + + ngOnInit(): void { + this.tableColumns = this.getColumns(); + this.dataSource = new PermissionsDataSource( + this.cdr, + this.gs, + this.uiService + ); + this.dataSource.setColumns(this.tableColumns); + this.dataSource.loadAll(); + } + + ngOnDestroy(): void { + for (const sub of this.subscriptions) { + sub.unsubscribe(); + } + } + + filter(item: GlobalPermissionGroup, filterValue: string): boolean { + if (item.name.toLowerCase().includes(filterValue)) { + return true; + } + + return false; + } + + getColumns(): HTTableColumn[] { + const tableColumns = [ + { + name: PermissionsTableColumnLabel.ID, + dataKey: '_id', + isSortable: true, + export: async (permission: GlobalPermissionGroup) => permission._id + '' + }, + { + name: PermissionsTableColumnLabel.NAME, + dataKey: 'name', + routerLink: (permission: GlobalPermissionGroup) => + this.renderPermissionLink(permission), + isSortable: true, + export: async (permission: GlobalPermissionGroup) => permission.name + }, + { + name: PermissionsTableColumnLabel.MEMBERS, + dataKey: 'numUsers', + isSortable: true, + render: (permission: GlobalPermissionGroup) => permission.user.length, + export: async (permission: GlobalPermissionGroup) => permission._id + '' + } + ]; + + return tableColumns; + } + + openDialog(data: DialogData) { + const dialogRef = this.dialog.open(TableDialogComponent, { + data: data, + width: '450px' + }); + + this.subscriptions.push( + dialogRef.afterClosed().subscribe((result) => { + if (result && result.action) { + switch (result.action) { + case RowActionMenuAction.DELETE: + this.rowActionDelete(result.data); + break; + case BulkActionMenuAction.DELETE: + this.bulkActionDelete(result.data); + break; + } + } + }) + ); + } + + // --- Action functions --- + + exportActionClicked(event: ActionMenuEvent): void { + switch (event.menuItem.action) { + case ExportMenuAction.EXCEL: + this.exportService.toExcel( + 'hashtopolis-permissions', + this.tableColumns, + event.data + ); + break; + case ExportMenuAction.CSV: + this.exportService.toCsv( + 'hashtopolis-permissions', + this.tableColumns, + event.data + ); + break; + case ExportMenuAction.COPY: + this.exportService + .toClipboard(this.tableColumns, event.data) + .then(() => { + this.snackBar.open( + 'The selected rows are copied to the clipboard', + 'Close' + ); + }); + break; + } + } + + rowActionClicked(event: ActionMenuEvent): void { + switch (event.menuItem.action) { + case RowActionMenuAction.DELETE: + this.openDialog({ + rows: [event.data], + title: `Deleting permission ${event.data.name} ...`, + icon: 'warning', + body: `Are you sure you want to delete it? Note that this action cannot be undone.`, + warn: true, + action: event.menuItem.action + }); + break; + case RowActionMenuAction.EDIT: + this.rowActionEdit(event.data); + break; + } + } + + bulkActionClicked(event: ActionMenuEvent): void { + switch (event.menuItem.action) { + case BulkActionMenuAction.DELETE: + this.openDialog({ + rows: event.data, + title: `Deleting ${event.data.length} permissions ...`, + icon: 'warning', + body: `Are you sure you want to delete the above permissions? Note that this action cannot be undone.`, + warn: true, + listAttribute: 'name', + action: event.menuItem.action + }); + break; + } + } + + /** + * @todo Implement error handling. + */ + private bulkActionDelete(permissions: GlobalPermissionGroup[]): void { + const requests = permissions.map((permission: GlobalPermissionGroup) => { + return this.gs.delete(SERV.ACCESS_PERMISSIONS_GROUPS, permission._id); + }); + + this.subscriptions.push( + forkJoin(requests) + .pipe( + catchError((error) => { + console.error('Error during deletion:', error); + return []; + }) + ) + .subscribe((results) => { + this.snackBar.open( + `Successfully deleted ${results.length} permissions!`, + 'Close' + ); + this.reload(); + }) + ); + } + + /** + * @todo Implement error handling. + */ + private rowActionDelete(permissions: GlobalPermissionGroup[]): void { + this.subscriptions.push( + this.gs + .delete(SERV.ACCESS_PERMISSIONS_GROUPS, permissions[0]._id) + .pipe( + catchError((error) => { + console.error('Error during deletion:', error); + return []; + }) + ) + .subscribe(() => { + this.snackBar.open('Successfully deleted permission group!', 'Close'); + this.reload(); + }) + ); + } + + private rowActionEdit(permission: GlobalPermissionGroup): void { + this.router.navigate([ + '/users', + 'global-permissions-groups', + permission._id, + 'edit' + ]); + } +} diff --git a/src/app/core/_components/tables/permissions-table/permissions-table.constants.ts b/src/app/core/_components/tables/permissions-table/permissions-table.constants.ts new file mode 100644 index 00000000..68e11b42 --- /dev/null +++ b/src/app/core/_components/tables/permissions-table/permissions-table.constants.ts @@ -0,0 +1,5 @@ +export const PermissionsTableColumnLabel = { + ID: 'ID', + NAME: 'Name', + MEMBERS: 'Members' +}; diff --git a/src/app/core/_datasources/permissions.datasource.ts b/src/app/core/_datasources/permissions.datasource.ts new file mode 100644 index 00000000..b732e0d2 --- /dev/null +++ b/src/app/core/_datasources/permissions.datasource.ts @@ -0,0 +1,47 @@ +import { catchError, finalize, of } from 'rxjs'; + +import { BaseDataSource } from './base.datasource'; +import { GlobalPermissionGroup } from '../_models/global-permission-group.model'; +import { ListResponseWrapper } from '../_models/response.model'; +import { SERV } from '../_services/main.config'; + +export class PermissionsDataSource extends BaseDataSource { + loadAll(): void { + this.loading = true; + + const startAt = this.currentPage * this.pageSize; + const params = { + maxResults: this.pageSize, + startAt: startAt, + expand: 'user' + }; + + const permissions$ = this.service.getAll( + SERV.ACCESS_PERMISSIONS_GROUPS, + params + ); + + this.subscriptions.push( + permissions$ + .pipe( + catchError(() => of([])), + finalize(() => (this.loading = false)) + ) + .subscribe((response: ListResponseWrapper) => { + const permissions: GlobalPermissionGroup[] = response.values; + + this.setPaginationConfig( + this.pageSize, + this.currentPage, + response.total + ); + this.setData(permissions); + }) + ); + } + + reload(): void { + this.clearSelection(); + this.loadAll(); + } +} diff --git a/src/app/core/_models/config-ui.model.ts b/src/app/core/_models/config-ui.model.ts index 4329e6d7..707bc25b 100644 --- a/src/app/core/_models/config-ui.model.ts +++ b/src/app/core/_models/config-ui.model.ts @@ -6,6 +6,7 @@ import { FilesTableColumnLabel } from '../_components/tables/files-table/files-t import { HashlistsTableColumnLabel } from '../_components/tables/hashlists-table/hashlists-table.constants'; import { HashtypesTableColumnLabel } from '../_components/tables/hashtypes-table/hashtypes-table.constants'; import { HealthChecksTableColumnLabel } from '../_components/tables/health-checks-table/health-checks-table.constants'; +import { PermissionsTableColumnLabel } from '../_components/tables/permissions-table/permissions-table.constants'; import { PreprocessorsTableColumnLabel } from '../_components/tables/preprocessors-table/preprocessors-table.constants'; import { SuperHashlistsTableColumnLabel } from '../_components/tables/super-hashlists-table/super-hashlists-table.constants'; @@ -30,6 +31,11 @@ export const uiConfigDefault: UIConfig = { theme: 'light', timefmt: 'dd/MM/yyyy h:mm:ss', tableSettings: { + permissionsTable: [ + PermissionsTableColumnLabel.ID, + PermissionsTableColumnLabel.NAME, + PermissionsTableColumnLabel.MEMBERS + ], agentsTable: [ AgentsTableColumnLabel.ID, AgentsTableColumnLabel.NAME, diff --git a/src/app/core/_models/global-permission-group.model.ts b/src/app/core/_models/global-permission-group.model.ts index 91e827e5..cff46dd7 100644 --- a/src/app/core/_models/global-permission-group.model.ts +++ b/src/app/core/_models/global-permission-group.model.ts @@ -1,3 +1,5 @@ +import { User } from 'src/app/users/user.model'; + export interface Permission { [key: string]: boolean; } @@ -8,4 +10,5 @@ export interface GlobalPermissionGroup { id: number; name: string; permissions: Permission; + user: User[]; } diff --git a/src/app/users/globalpermissionsgroups/globalpermissionsgroups.component.html b/src/app/users/globalpermissionsgroups/globalpermissionsgroups.component.html index d3aed636..56670507 100644 --- a/src/app/users/globalpermissionsgroups/globalpermissionsgroups.component.html +++ b/src/app/users/globalpermissionsgroups/globalpermissionsgroups.component.html @@ -1,45 +1,9 @@ - - - - - - - - - - - - - - - - - - - -
IDName# of UsersActions - -
{{ a.id }} - - {{ a.name| shortenString:40 }} - - {{ a.user.length }} - - - - - -
+ +
diff --git a/src/app/users/globalpermissionsgroups/globalpermissionsgroups.component.ts b/src/app/users/globalpermissionsgroups/globalpermissionsgroups.component.ts index fda362fe..4da4d4f9 100644 --- a/src/app/users/globalpermissionsgroups/globalpermissionsgroups.component.ts +++ b/src/app/users/globalpermissionsgroups/globalpermissionsgroups.component.ts @@ -1,270 +1,12 @@ -import { faHomeAlt, faPlus, faTrash, faEdit, faInfoCircle } from '@fortawesome/free-solid-svg-icons'; -import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core'; -import { DataTableDirective } from 'angular-datatables'; -import { Subject, Subscription } from 'rxjs'; -import { Router } from '@angular/router'; - import { AutoTitleService } from 'src/app/core/_services/shared/autotitle.service'; -import { AlertService } from 'src/app/core/_services/shared/alert.service'; -import { GlobalService } from 'src/app/core/_services/main.service'; -import { environment } from 'src/environments/environment'; -import { SERV } from '../../core/_services/main.config'; - -declare let $:any; +import { Component } from '@angular/core'; @Component({ selector: 'app-globalpermissionsgroups', templateUrl: './globalpermissionsgroups.component.html' }) -/** - * GlobalpermissionsgroupsComponent is a component that manages and displays Global Permissions data. - * - * It uses DataTables to display and interact with the Global Permissions data, including exporting, deleting, bulk actions - * and refreshing the table. - */ -export class GlobalpermissionsgroupsComponent implements OnInit, OnDestroy { - - // Font Awesome icons - faInfoCircle=faInfoCircle; - faHome=faHomeAlt; - faPlus=faPlus; - faEdit=faEdit; - faTrash=faTrash; - - // ViewChild reference to the DataTableDirective - @ViewChild(DataTableDirective, {static: false}) - dtElement: DataTableDirective; - - dtTrigger: Subject = new Subject(); - dtOptions: any = {}; - - // List of global permission groups - public Allgpg:any = []; - - // Subscriptions to unsubscribe on component destruction - subscriptions: Subscription[] = [] - - private maxResults = environment.config.prodApiMaxResults; - - constructor( - private titleService: AutoTitleService, - private alert: AlertService, - private gs: GlobalService, - private router: Router - ) { - titleService.set(['Show Global Permissions']) - } - - /** - * Initializes DataTable and retrieves pretasks. - */ - ngOnInit(): void { - this.getGlobalPermissions(); - this.setupTable(); - } - - /** - * Unsubscribes from active subscriptions. - */ - ngOnDestroy(): void { - this.dtTrigger.unsubscribe(); - for (const sub of this.subscriptions) { - sub.unsubscribe(); - } - } - - // Refresh the data and the DataTable - onRefresh() { - this.rerender(); - this.ngOnInit(); - } - - /** - * Rerenders the DataTable instance. - * Destroys and recreates the DataTable to reflect changes. - */ - rerender(): void { - this.dtElement.dtInstance.then((dtInstance: DataTables.Api) => { - // Destroy the table first - dtInstance.destroy(); - // Call the dtTrigger to rerender again - setTimeout(() => { - if (this.dtTrigger['new']) { - this.dtTrigger['new'].next(); - } - }); - }); - } - - /** - * Fetches Global Permissions Groups from the server. - * Subscribes to the API response and updates the Global Permissions Groups list. - */ - getGlobalPermissions(): void { - // Set parameters for the API request - const params = {'maxResults': this.maxResults , 'expand': 'user'}; - // Make an API call to get permissions data - this.subscriptions.push(this.gs.getAll(SERV.ACCESS_PERMISSIONS_GROUPS,params).subscribe((response: any) => { - this.Allgpg = response.values; - this.dtTrigger.next(void 0); - })); - } - - /** - * Sets up the DataTable options and buttons. - * Customizes DataTable appearance and behavior. - */ - setupTable(): void { - // DataTables options - const self = this; - this.dtOptions = { - dom: 'Bfrtip', - scrollX: true, - pageLength: 25, - lengthMenu: [ - [10, 25, 50, 100, 250, -1], - [10, 25, 50, 100, 250, 'All'] - ], - processing: true, // Error loading - deferRender: true, - destroy:true, - select: { - style: 'multi', - }, - buttons: { - dom: { - button: { - className: 'dt-button buttons-collection btn btn-sm-dt btn-outline-gray-600-dt', - } - }, - buttons: [ - { - text: '↻', - autoClose: true, - action: function (e, dt, node, config) { - self.onRefresh(); - } - }, - { - extend: 'collection', - text: 'Export', - buttons: [ - { - extend: 'excelHtml5', - exportOptions: { - columns: [0, 1] - }, - }, - { - extend: 'print', - exportOptions: { - columns: [0,1] - }, - customize: function ( win ) { - $(win.document.body) - .css( 'font-size', '10pt' ) - $(win.document.body).find( 'table' ) - .addClass( 'compact' ) - .css( 'font-size', 'inherit' ); - } - }, - { - extend: 'csvHtml5', - exportOptions: {modifier: {selected: true}}, - select: true, - customize: function (dt, csv) { - let data = ""; - for (let i = 0; i < dt.length; i++) { - data = "Agents\n\n"+ dt; - } - return data; - } - }, - 'copy' - ] - }, - { - extend: 'collection', - text: 'Bulk Actions', - className: 'dt-button buttons-collection btn btn-sm-dt btn-outline-gray-600-dt', - buttons: [ - { - text: 'Delete Groups', - autoClose: true, - action: function (e, dt, node, config) { - self.onDeleteBulk(); - } - } - ] - }, - ], - } - }; - - } - - // Refresh the table after a delete operation - onRefreshTable() { - setTimeout(() => { - this.ngOnInit(); - this.rerender(); // Rerender the DataTable - }, 2000); - } - - /** - * Handles Global Permission Group deletion. - * Displays a confirmation dialog and deletes the Global Permission Group if confirmed. - * - * @param {number} id - The ID of the Global Permission Group to delete. - * @param {string} name - The name of the Global Permission Group. - */ - onDelete(id: number, name: string){ - this.alert.deleteConfirmation(name,'Global permissions').then((confirmed) => { - if (confirmed) { - // Deletion - this.subscriptions.push(this.gs.delete(SERV.ACCESS_PERMISSIONS_GROUPS, id).subscribe(() => { - // Successful deletion - this.alert.okAlert(`Deleted Global permission ${name}`, ''); - this.onRefreshTable(); // Refresh the table - })); - } else { - // Handle cancellation - this.alert.okAlert(`Global permission ${name} is safe!`,''); - } - }); - } - - /** - * BULK ACTIONS - * - */ - - /** - * Handles Global Permission Group selection. - * On multi select grabs the ids to be used for bulk action - * - */ - onSelectedGroups(){ - $(".dt-button-background").trigger("click"); - const selection = $($(this.dtElement).DataTable.tables()).DataTable().rows({ selected: true } ).data().pluck(0).toArray(); - if(selection.length == 0) { - this.alert.okAlert('You have not selected any Group',''); - return; - } - const selectionnum = selection.map(i=>Number(i)); - - return selectionnum; - } - - /** - * Handles bulk deletion - * Delete the Global Permission Group showing a progress bar - * - */ - async onDeleteBulk() { - const GlobalIds = this.onSelectedGroups(); - this.alert.bulkDeleteAlert(GlobalIds,'Global Group Permissions',SERV.ACCESS_PERMISSIONS_GROUPS); - this.onRefreshTable(); +export class GlobalpermissionsgroupsComponent { + constructor(private titleService: AutoTitleService) { + titleService.set(['Show Global Permissions']); } - } From 104d096b3452b0e02eaa14f7be9e4aefb7298908 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Tue, 21 Nov 2023 13:36:46 +0100 Subject: [PATCH 298/419] access groups table --- .../_components/core-components.module.ts | 7 +- .../menus/base-menu/base-menu.component.ts | 18 +- .../bulk-action-menu.component.ts | 2 + .../bulk-action-menu.constants.ts | 1 + .../row-action-menu.component.ts | 5 + .../row-action-menu.constants.ts | 2 + .../access-groups-table.component.html | 16 ++ .../access-groups-table.component.ts | 215 +++++++++++++++ .../access-groups-table.constants.ts | 4 + .../agents-table/agents-table.component.ts | 4 +- .../tables/base-table/base-table.component.ts | 12 + .../files-table/files-table.component.ts | 4 +- .../hashlists-table.component.ts | 10 +- .../tables/ht-table/ht-table.models.ts | 1 + .../users-table/users-table.component.ts | 10 +- .../_datasources/access-groups.datasource.ts | 43 +++ src/app/core/_models/access-group.model.ts | 2 + src/app/users/groups/groups.component.html | 40 +-- src/app/users/groups/groups.component.ts | 260 +----------------- 19 files changed, 354 insertions(+), 302 deletions(-) create mode 100644 src/app/core/_components/tables/access-groups-table/access-groups-table.component.html create mode 100644 src/app/core/_components/tables/access-groups-table/access-groups-table.component.ts create mode 100644 src/app/core/_components/tables/access-groups-table/access-groups-table.constants.ts create mode 100644 src/app/core/_datasources/access-groups.datasource.ts diff --git a/src/app/core/_components/core-components.module.ts b/src/app/core/_components/core-components.module.ts index 95dc12d4..b9ac248e 100644 --- a/src/app/core/_components/core-components.module.ts +++ b/src/app/core/_components/core-components.module.ts @@ -4,6 +4,7 @@ import { MatSnackBarModule } from '@angular/material/snack-bar'; +import { AccessGroupsTableComponent } from './tables/access-groups-table/access-groups-table.component'; import { ActionMenuComponent } from './menus/action-menu/action-menu.component'; import { AgentBinariesTableComponent } from './tables/agent-binaries-table/agent-binaries-table.component'; import { AgentsTableComponent } from './tables/agents-table/agents-table.component'; @@ -67,7 +68,8 @@ import { UsersTableComponent } from './tables/users-table/users-table.component' AgentBinariesTableComponent, HealthChecksTableComponent, LogsTableComponent, - UsersTableComponent + UsersTableComponent, + AccessGroupsTableComponent ], imports: [ ReactiveFormsModule, @@ -113,7 +115,8 @@ import { UsersTableComponent } from './tables/users-table/users-table.component' AgentBinariesTableComponent, HealthChecksTableComponent, LogsTableComponent, - UsersTableComponent + UsersTableComponent, + AccessGroupsTableComponent ], providers: [ { diff --git a/src/app/core/_components/menus/base-menu/base-menu.component.ts b/src/app/core/_components/menus/base-menu/base-menu.component.ts index 9f122e71..5c1b263b 100644 --- a/src/app/core/_components/menus/base-menu/base-menu.component.ts +++ b/src/app/core/_components/menus/base-menu/base-menu.component.ts @@ -33,7 +33,15 @@ export class BaseMenuComponent { * @returns `true` if the data row is an agent; otherwise, `false`. */ protected isAgent(): boolean { - return this.checkId('agentId'); + return this.checkId('agentId') && 'agentName' in this.data; + } + + /** + * Check if the data row is of type "AccessGroup". + * @returns `true` if the data row is an access group; otherwise, `false`. + */ + protected isAccessGroup(): boolean { + return this.checkId('accessGroupId') && 'groupName' in this.data; } /** @@ -41,7 +49,7 @@ export class BaseMenuComponent { * @returns `true` if the data row is an agent binary; otherwise, `false`. */ protected isAgentBinary(): boolean { - return this.checkId('agentBinaryId'); + return this.checkId('agentBinaryId') && 'filename' in this.data; } /** @@ -49,7 +57,7 @@ export class BaseMenuComponent { * @returns `true` if the data row is an preprocessor; otherwise, `false`. */ protected isPreprocessor(): boolean { - return this.checkId('preprocessorId'); + return this.checkId('preprocessorId') && 'binaryName' in this.data; } /** @@ -57,7 +65,7 @@ export class BaseMenuComponent { * @returns `true` if the data row is a cracker; otherwise, `false`. */ protected isCrackerBinaryType(): boolean { - return this.checkId('crackerBinaryTypeId'); + return this.checkId('crackerBinaryTypeId') && 'typeName' in this.data; } /** @@ -65,7 +73,7 @@ export class BaseMenuComponent { * @returns `true` if the data row is a task; otherwise, `false`. */ protected isTask(): boolean { - return this.checkId('taskId'); + return this.checkId('taskId') && 'taskName' in this.data; } /** diff --git a/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.component.ts b/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.component.ts index 1d7a7744..2f250c52 100644 --- a/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.component.ts +++ b/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.component.ts @@ -48,6 +48,8 @@ export class BulkActionMenuComponent ); } else if (this.dataType === 'hashlists') { this.setHashlistMenu(); + } else if (this.dataType === 'access-groups') { + this.setDeleteMenu(BulkActionMenuLabel.DELETE_ACCESSGROUPS); } else if (this.dataType === 'hashtypes') { this.setDeleteMenu(BulkActionMenuLabel.DELETE_HASHTYPES); } else if (this.dataType === 'agent-binaries') { diff --git a/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.constants.ts b/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.constants.ts index 0161c198..beb3e122 100644 --- a/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.constants.ts +++ b/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.constants.ts @@ -9,6 +9,7 @@ export const BulkActionMenuLabel = { DELETE_AGENTBINARIES: 'Delete Agent Binaries', DELETE_HEALTHCHECKS: 'Delete Health Checks', DELETE_USERS: 'Delete Users', + DELETE_ACCESSGROUPS: 'Delete Access Groups', ACTIVATE_AGENTS: 'Activate Agents', ACTIVATE_USERS: 'Activate Users', DEACTIVATE_AGENTS: 'Deactivate Agents', diff --git a/src/app/core/_components/menus/row-action-menu/row-action-menu.component.ts b/src/app/core/_components/menus/row-action-menu/row-action-menu.component.ts index f8a63d63..60a710d5 100644 --- a/src/app/core/_components/menus/row-action-menu/row-action-menu.component.ts +++ b/src/app/core/_components/menus/row-action-menu/row-action-menu.component.ts @@ -23,6 +23,11 @@ export class RowActionMenuComponent ngOnInit(): void { if (this.isAgent()) { this.setAgentMenu(); + } else if (this.isAccessGroup()) { + this.setEditDeleteMenuItems( + RowActionMenuLabel.EDIT_ACCESSGROUP, + RowActionMenuLabel.DELETE_ACCESSGROUP + ); } else if (this.isSuperHashlist()) { this.setEditDeleteMenuItems( RowActionMenuLabel.EDIT_SUPERHASHLIST, 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 c74bed2d..40cd1ff4 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 @@ -13,6 +13,7 @@ export const RowActionMenuLabel = { EDIT_AGENTBINARY: 'Edit Agent Binary', EDIT_HEALTHCHECK: 'Edit Health Check', EDIT_USER: 'Edit User', + EDIT_ACCESSGROUP: 'Edit Access Group', DELETE_AGENT: 'Delete Agent', DELETE_CRACKER: 'Delete Cracker', DELETE_TASK: 'Delete Task', @@ -24,6 +25,7 @@ export const RowActionMenuLabel = { DELETE_AGENTBINARY: 'Delete Agent Binary', DELETE_HEALTHCHECK: 'Delete Health Check', DELETE_USER: 'Delete User', + DELETE_ACCESSGROUP: 'Delete Access Group', COPY_TO_TASK: 'Copy to Task', COPY_TO_PRETASK: 'Copy to Pretask', ARCHIVE_TASK: 'Archive Task', diff --git a/src/app/core/_components/tables/access-groups-table/access-groups-table.component.html b/src/app/core/_components/tables/access-groups-table/access-groups-table.component.html new file mode 100644 index 00000000..43cd40ed --- /dev/null +++ b/src/app/core/_components/tables/access-groups-table/access-groups-table.component.html @@ -0,0 +1,16 @@ + diff --git a/src/app/core/_components/tables/access-groups-table/access-groups-table.component.ts b/src/app/core/_components/tables/access-groups-table/access-groups-table.component.ts new file mode 100644 index 00000000..b0df713a --- /dev/null +++ b/src/app/core/_components/tables/access-groups-table/access-groups-table.component.ts @@ -0,0 +1,215 @@ +/* eslint-disable @angular-eslint/component-selector */ +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { HTTableColumn, HTTableRouterLink } from '../ht-table/ht-table.models'; +import { catchError, forkJoin } from 'rxjs'; + +import { AccessGroup } from 'src/app/core/_models/access-group.model'; +import { AccessGroupsDataSource } from 'src/app/core/_datasources/access-groups.datasource'; +import { AccessGroupsTableColumnLabel } from './access-groups-table.constants'; +import { ActionMenuEvent } from '../../menus/action-menu/action-menu.model'; +import { BaseTableComponent } from '../base-table/base-table.component'; +import { BulkActionMenuAction } from '../../menus/bulk-action-menu/bulk-action-menu.constants'; +import { DialogData } from '../table-dialog/table-dialog.model'; +import { ExportMenuAction } from '../../menus/export-menu/export-menu.constants'; +import { RowActionMenuAction } from '../../menus/row-action-menu/row-action-menu.constants'; +import { SERV } from 'src/app/core/_services/main.config'; +import { TableDialogComponent } from '../table-dialog/table-dialog.component'; + +@Component({ + selector: 'access-groups-table', + templateUrl: './access-groups-table.component.html' +}) +export class AccessGroupsTableComponent + extends BaseTableComponent + implements OnInit, OnDestroy +{ + tableColumns: HTTableColumn[] = []; + dataSource: AccessGroupsDataSource; + + ngOnInit(): void { + this.tableColumns = this.getColumns(); + this.dataSource = new AccessGroupsDataSource( + this.cdr, + this.gs, + this.uiService + ); + this.dataSource.setColumns(this.tableColumns); + this.dataSource.loadAll(); + } + + ngOnDestroy(): void { + for (const sub of this.subscriptions) { + sub.unsubscribe(); + } + } + + filter(item: AccessGroup, filterValue: string): boolean { + if (item.groupName.toLowerCase().includes(filterValue)) { + return true; + } + + return false; + } + + getColumns(): HTTableColumn[] { + const tableColumns = [ + { + name: AccessGroupsTableColumnLabel.ID, + dataKey: '_id', + isSortable: true, + export: async (accessGroup: AccessGroup) => accessGroup._id + '' + }, + { + name: AccessGroupsTableColumnLabel.NAME, + dataKey: 'groupName', + routerLink: (accessGroup: AccessGroup) => + this.renderAccessGroupLink(accessGroup), + isSortable: true, + export: async (accessGroup: AccessGroup) => accessGroup.groupName + } + ]; + + return tableColumns; + } + + openDialog(data: DialogData) { + const dialogRef = this.dialog.open(TableDialogComponent, { + data: data, + width: '450px' + }); + + this.subscriptions.push( + dialogRef.afterClosed().subscribe((result) => { + if (result && result.action) { + switch (result.action) { + case RowActionMenuAction.DELETE: + this.rowActionDelete(result.data); + break; + case BulkActionMenuAction.DELETE: + this.bulkActionDelete(result.data); + break; + } + } + }) + ); + } + + // --- Action functions --- + + exportActionClicked(event: ActionMenuEvent): void { + switch (event.menuItem.action) { + case ExportMenuAction.EXCEL: + this.exportService.toExcel( + 'hashtopolis-access-groups', + this.tableColumns, + event.data + ); + break; + case ExportMenuAction.CSV: + this.exportService.toCsv( + 'hashtopolis-access-groups', + this.tableColumns, + event.data + ); + break; + case ExportMenuAction.COPY: + this.exportService + .toClipboard(this.tableColumns, event.data) + .then(() => { + this.snackBar.open( + 'The selected rows are copied to the clipboard', + 'Close' + ); + }); + break; + } + } + + rowActionClicked(event: ActionMenuEvent): void { + switch (event.menuItem.action) { + case RowActionMenuAction.DELETE: + this.openDialog({ + rows: [event.data], + title: `Deleting access group ${event.data.groupName} ...`, + icon: 'warning', + body: `Are you sure you want to delete it? Note that this action cannot be undone.`, + warn: true, + action: event.menuItem.action + }); + break; + case RowActionMenuAction.EDIT: + this.rowActionEdit(event.data); + break; + } + } + + bulkActionClicked(event: ActionMenuEvent): void { + switch (event.menuItem.action) { + case BulkActionMenuAction.DELETE: + this.openDialog({ + rows: event.data, + title: `Deleting ${event.data.length} access groups ...`, + icon: 'warning', + body: `Are you sure you want to delete the above access groups? Note that this action cannot be undone.`, + warn: true, + listAttribute: 'groupName', + action: event.menuItem.action + }); + break; + } + } + + /** + * @todo Implement error handling. + */ + private bulkActionDelete(accessGroups: AccessGroup[]): void { + const requests = accessGroups.map((accessGroup: AccessGroup) => { + return this.gs.delete(SERV.CRACKERS_TYPES, accessGroup._id); + }); + + this.subscriptions.push( + forkJoin(requests) + .pipe( + catchError((error) => { + console.error('Error during deletion:', error); + return []; + }) + ) + .subscribe((results) => { + this.snackBar.open( + `Successfully deleted ${results.length} access groups!`, + 'Close' + ); + this.reload(); + }) + ); + } + + /** + * @todo Implement error handling. + */ + private rowActionDelete(accessGroups: AccessGroup[]): void { + this.subscriptions.push( + this.gs + .delete(SERV.CRACKERS_TYPES, accessGroups[0]._id) + .pipe( + catchError((error) => { + console.error('Error during deletion:', error); + return []; + }) + ) + .subscribe(() => { + this.snackBar.open('Successfully deleted accessGroup!', 'Close'); + this.reload(); + }) + ); + } + + private rowActionEdit(accessGroup: AccessGroup): void { + this.renderAccessGroupLink(accessGroup).then( + (links: HTTableRouterLink[]) => { + this.router.navigate(links[0].routerLink); + } + ); + } +} diff --git a/src/app/core/_components/tables/access-groups-table/access-groups-table.constants.ts b/src/app/core/_components/tables/access-groups-table/access-groups-table.constants.ts new file mode 100644 index 00000000..ed3e7f54 --- /dev/null +++ b/src/app/core/_components/tables/access-groups-table/access-groups-table.constants.ts @@ -0,0 +1,4 @@ +export const AccessGroupsTableColumnLabel = { + ID: 'ID', + NAME: 'Name' +}; diff --git a/src/app/core/_components/tables/agents-table/agents-table.component.ts b/src/app/core/_components/tables/agents-table/agents-table.component.ts index 15abd81e..9b21bffc 100644 --- a/src/app/core/_components/tables/agents-table/agents-table.component.ts +++ b/src/app/core/_components/tables/agents-table/agents-table.component.ts @@ -543,6 +543,8 @@ export class AgentsTableComponent } private rowActionEdit(agent: Agent): void { - this.router.navigate(['agents', 'show-agents', agent._id, 'edit']); + this.renderAgentLink(agent).then((links: HTTableRouterLink[]) => { + this.router.navigate(links[0].routerLink); + }); } } 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 50d367a0..4e7ff486 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 @@ -180,6 +180,18 @@ export class BaseTableComponent { ]; } + @Cacheable(['accessGroupId']) + async renderAccessGroupLink(obj: unknown): Promise { + return [ + { + routerLink: + obj && obj['accessGroupId'] + ? ['/users', 'access-groups', obj['accessGroupId'], 'edit'] + : [] + } + ]; + } + @Cacheable(['accessGroups']) async renderAccessGroupLinks(obj: unknown): Promise { let links: HTTableRouterLink[] = []; diff --git a/src/app/core/_components/tables/files-table/files-table.component.ts b/src/app/core/_components/tables/files-table/files-table.component.ts index 7e04a037..fa7bc73f 100644 --- a/src/app/core/_components/tables/files-table/files-table.component.ts +++ b/src/app/core/_components/tables/files-table/files-table.component.ts @@ -258,6 +258,8 @@ export class FilesTableComponent } private rowActionEdit(file: File): void { - this.router.navigate(['/files', file._id, this.editPath]); + this.renderFileLink(file).then((links: HTTableRouterLink[]) => { + this.router.navigate(links[0].routerLink); + }); } } diff --git a/src/app/core/_components/tables/hashlists-table/hashlists-table.component.ts b/src/app/core/_components/tables/hashlists-table/hashlists-table.component.ts index 1666e4e3..4b906443 100644 --- a/src/app/core/_components/tables/hashlists-table/hashlists-table.component.ts +++ b/src/app/core/_components/tables/hashlists-table/hashlists-table.component.ts @@ -1,6 +1,10 @@ /* eslint-disable @angular-eslint/component-selector */ import { Component, Input, OnDestroy, OnInit } from '@angular/core'; -import { HTTableColumn, HTTableIcon } from '../ht-table/ht-table.models'; +import { + HTTableColumn, + HTTableIcon, + HTTableRouterLink +} from '../ht-table/ht-table.models'; import { catchError, forkJoin } from 'rxjs'; import { ActionMenuEvent } from '../../menus/action-menu/action-menu.model'; @@ -323,7 +327,9 @@ export class HashlistsTableComponent } private rowActionEdit(hashlist: Hashlist): void { - this.router.navigate(['/hashlists', 'hashlist', hashlist._id, 'edit']); + this.renderHashlistLink(hashlist).then((links: HTTableRouterLink[]) => { + this.router.navigate(links[0].routerLink); + }); } /** diff --git a/src/app/core/_components/tables/ht-table/ht-table.models.ts b/src/app/core/_components/tables/ht-table/ht-table.models.ts index a92f45a8..705c763c 100644 --- a/src/app/core/_components/tables/ht-table/ht-table.models.ts +++ b/src/app/core/_components/tables/ht-table/ht-table.models.ts @@ -10,6 +10,7 @@ export type DataType = | 'crackers' | 'preprocessors' | 'users' + | 'access-groups' | 'agent-binaries' | 'health-checks' | 'logs' diff --git a/src/app/core/_components/tables/users-table/users-table.component.ts b/src/app/core/_components/tables/users-table/users-table.component.ts index 9409ede1..e5a8c2ab 100644 --- a/src/app/core/_components/tables/users-table/users-table.component.ts +++ b/src/app/core/_components/tables/users-table/users-table.component.ts @@ -1,6 +1,10 @@ /* eslint-disable @angular-eslint/component-selector */ import { Component, OnDestroy, OnInit } from '@angular/core'; -import { HTTableColumn, HTTableIcon } from '../ht-table/ht-table.models'; +import { + HTTableColumn, + HTTableIcon, + HTTableRouterLink +} from '../ht-table/ht-table.models'; import { UsersTableColumnLabel, UsersTableStatus @@ -336,6 +340,8 @@ export class UsersTableComponent } private rowActionEdit(user: User): void { - this.router.navigate(['/users', user._id, 'edit']); + this.renderUserLink(user).then((links: HTTableRouterLink[]) => { + this.router.navigate(links[0].routerLink); + }); } } diff --git a/src/app/core/_datasources/access-groups.datasource.ts b/src/app/core/_datasources/access-groups.datasource.ts new file mode 100644 index 00000000..d74feb5b --- /dev/null +++ b/src/app/core/_datasources/access-groups.datasource.ts @@ -0,0 +1,43 @@ +import { catchError, finalize, of } from 'rxjs'; + +import { AccessGroup } from '../_models/access-group.model'; +import { BaseDataSource } from './base.datasource'; +import { ListResponseWrapper } from '../_models/response.model'; +import { SERV } from '../_services/main.config'; + +export class AccessGroupsDataSource extends BaseDataSource { + loadAll(): void { + this.loading = true; + + const startAt = this.currentPage * this.pageSize; + const params = { + maxResults: this.pageSize, + startAt: startAt + }; + + const accessGroups$ = this.service.getAll(SERV.ACCESS_GROUPS, params); + + this.subscriptions.push( + accessGroups$ + .pipe( + catchError(() => of([])), + finalize(() => (this.loading = false)) + ) + .subscribe((response: ListResponseWrapper) => { + const accessGroups: AccessGroup[] = response.values; + + this.setPaginationConfig( + this.pageSize, + this.currentPage, + response.total + ); + this.setData(accessGroups); + }) + ); + } + + reload(): void { + this.clearSelection(); + this.loadAll(); + } +} diff --git a/src/app/core/_models/access-group.model.ts b/src/app/core/_models/access-group.model.ts index eae9a6fd..252ee1e8 100644 --- a/src/app/core/_models/access-group.model.ts +++ b/src/app/core/_models/access-group.model.ts @@ -1,4 +1,6 @@ export interface AccessGroup { + _id: number; + _self: string; accessGroupId: number; groupName: string; } diff --git a/src/app/users/groups/groups.component.html b/src/app/users/groups/groups.component.html index 58ca902f..df6153b9 100644 --- a/src/app/users/groups/groups.component.html +++ b/src/app/users/groups/groups.component.html @@ -1,35 +1,9 @@ - - - - - - - - - - - - - - - - - -
Access Group IDGroup NameActions
{{ a.accessGroupId }} - - {{ a.groupName | shortenString:40 }} - - - - - - - -
+ +
- diff --git a/src/app/users/groups/groups.component.ts b/src/app/users/groups/groups.component.ts index 289653e5..954aac12 100644 --- a/src/app/users/groups/groups.component.ts +++ b/src/app/users/groups/groups.component.ts @@ -1,264 +1,12 @@ -import { faHomeAlt, faPlus, faTrash, faEdit, faSave, faCancel } from '@fortawesome/free-solid-svg-icons'; -import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core'; -import { environment } from 'src/environments/environment'; -import { DataTableDirective } from 'angular-datatables'; -import { Subject, Subscription } from 'rxjs'; - import { AutoTitleService } from 'src/app/core/_services/shared/autotitle.service'; -import { AlertService } from 'src/app/core/_services/shared/alert.service'; -import { GlobalService } from 'src/app/core/_services/main.service'; -import { PageTitle } from 'src/app/core/_decorators/autotitle'; -import { SERV } from '../../core/_services/main.config'; - -declare let $:any; +import { Component } from '@angular/core'; @Component({ selector: 'app-groups', templateUrl: './groups.component.html' }) -/** - * GroupsComponent is a component that manages and displays all groups data. - * - * It uses DataTables to display and interact with the groups data, including exporting, deleting, bulk actions - * and refreshing the table. - */ -export class GroupsComponent implements OnInit, OnDestroy { - - // Font Awesome icons - faHome = faHomeAlt; - faPlus = faPlus; - faEdit = faEdit; - faTrash = faTrash; - faSave = faSave; - faCancel = faCancel; - - // ViewChild reference to the DataTableDirective - @ViewChild(DataTableDirective, { static: false }) - dtElement: DataTableDirective; - - dtTrigger: Subject = new Subject(); - dtOptions: any = {}; - - // List of groups ToDo. Change Interface - public agroups: { accessGroupId: number, groupName: string, isEdit: false }[] = []; - - // Subscriptions to unsubscribe on component destruction - subscriptions: Subscription[] = [] - - private maxResults = environment.config.prodApiMaxResults; - - constructor( - private titleService: AutoTitleService, - private alert: AlertService, - private gs: GlobalService, - ) { - titleService.set(['Show Groups']) - } - - /** - * Initializes DataTable and retrieves groups. - */ - ngOnInit(): void { - this.loadAccessGroups(); - this.setupTable(); - } - - /** - * Unsubscribes from active subscriptions and DataTable. - */ - ngOnDestroy(): void { - this.dtTrigger.unsubscribe(); - for (const sub of this.subscriptions) { - sub.unsubscribe(); - } - } - - /** - * Refreshes the data and the DataTable. - */ - onRefresh() { - this.rerender(); - this.ngOnInit(); +export class GroupsComponent { + constructor(private titleService: AutoTitleService) { + titleService.set(['Show Groups']); } - - /** - * Rerenders the DataTable instance. - * Destroys and recreates the DataTable to reflect changes. - */ - rerender(): void { - this.dtElement.dtInstance.then((dtInstance: DataTables.Api) => { - // Destroy the table first - dtInstance.destroy(); - // Call the dtTrigger to rerender again - setTimeout(() => { - this.dtTrigger['new'].next(); - }); - }); - } - - /** - * Fetches Groups from the server. - * Subscribes to the API response and updates the Groups list. - */ - loadAccessGroups() { - // Set parameters for the API request - const params = { 'maxResults': this.maxResults }; - // Make an API call to get groups data - this.subscriptions.push(this.gs.getAll(SERV.ACCESS_GROUPS, params).subscribe((response: any) => { - this.agroups = response.values; - this.dtTrigger.next(void 0); - })); - } - - /** - * Sets up the DataTable options and buttons. - * Customizes DataTable appearance and behavior. - */ - setupTable(): void { - // DataTables options - const self = this; - this.dtOptions = { - dom: 'Bfrtip', - scrollX: true, - pageLength: 25, - lengthMenu: [ - [10, 25, 50, 100, 250, -1], - [10, 25, 50, 100, 250, 'All'] - ], - processing: true, // Error loading - deferRender: true, - destroy: true, - select: { - style: 'multi', - }, - buttons: { - dom: { - button: { - className: 'dt-button buttons-collection btn btn-sm-dt btn-outline-gray-600-dt', - } - }, - buttons: [ - { - text: '↻', - autoClose: true, - action: function (e, dt, node, config) { - self.onRefresh(); - } - }, - { - extend: 'collection', - text: 'Export', - buttons: [ - { - extend: 'excelHtml5', - exportOptions: { - columns: [0, 1] - }, - }, - { - extend: 'print', - exportOptions: { - columns: [0, 1] - }, - customize: function (win) { - $(win.document.body) - .css('font-size', '10pt'); - $(win.document.body).find('table') - .addClass('compact') - .css('font-size', 'inherit'); - } - }, - { - extend: 'csvHtml5', - exportOptions: { modifier: { selected: true } }, - select: true, - customize: function (dt, csv) { - let data = ''; - for (let i = 0; i < dt.length; i++) { - data = 'Agents\n\n' + dt; - } - return data; - } - }, - 'copy' - ] - }, - { - extend: 'collection', - text: 'Bulk Actions', - className: 'dt-button buttons-collection btn btn-sm-dt btn-outline-gray-600-dt', - buttons: [ - { - text: 'Delete Groups', - autoClose: true, - action: function (e, dt, node, config) { - self.onDeleteBulk(); - } - } - ] - } - ], - } - }; - } - - // Refresh the table after a delete operation - onRefreshTable() { - setTimeout(() => { - this.ngOnInit(); - this.rerender(); // Rerender the DataTable - }, 2000); - } - - /** - * Handles Group deletion. - * Displays a confirmation dialog and deletes the Group if confirmed. - * - * @param {number} id - The ID of the Group to delete. - * @param {string} name - The name of the Group. - */ - onDelete(id: number, name: string) { - this.alert.deleteConfirmation(name, 'Groups').then((confirmed) => { - if (confirmed) { - // Deletion - this.subscriptions.push(this.gs.delete(SERV.ACCESS_GROUPS, id).subscribe(() => { - // Successful deletion - this.alert.okAlert(`Deleted Group ${name}`, ''); - this.onRefreshTable(); // Refresh the table - })); - } else { - // Handle cancellation - this.alert.okAlert(`Group ${name} is safe!`, ''); - } - }); - } - - /** - * Handles Group selection for bulk actions. - * On multi-select, retrieves the IDs to be used for bulk actions. - * - * @returns An array of selected Group IDs. - */ - onSelectedGroups() { - $(".dt-button-background").trigger("click"); - const selection = $($(this.dtElement).DataTable.tables()).DataTable().rows({ selected: true }).data().pluck(0).toArray(); - if (selection.length == 0) { - this.alert.okAlert('You have not selected any Group', ''); - return; - } - const selectionnum = selection.map(i => Number(i)); - - return selectionnum; - } - - /** - * Handles bulk deletion of selected Groups. - * Deletes the selected Groups and shows a progress bar. - */ - async onDeleteBulk() { - const GroupsIds = this.onSelectedGroups(); - this.alert.bulkDeleteAlert(GroupsIds, 'Groups', SERV.ACCESS_GROUPS); - this.onRefreshTable(); - } - } From a177b118756f4789b308a963341391aeba63e4d2 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Tue, 21 Nov 2023 20:49:59 +0100 Subject: [PATCH 299/419] Add notifications table --- src/app/account/account.module.ts | 50 ++- .../edit-notification.component.ts | 97 +++-- .../notifications.component.html | 60 +-- .../notifications.component.spec.ts | 182 --------- .../notifications/notifications.component.ts | 309 +-------------- .../_components/core-components.module.ts | 7 +- .../menus/base-menu/base-menu.component.ts | 8 + .../bulk-action-menu.component.ts | 6 + .../bulk-action-menu.constants.ts | 3 + .../row-action-menu.component.ts | 26 ++ .../row-action-menu.constants.ts | 4 + .../agents-table/agents-table.component.ts | 17 - .../tables/base-table/base-table.component.ts | 23 +- .../hashlists-table.component.ts | 4 +- .../tables/ht-table/ht-table.models.ts | 1 + .../notifications-table.component.html | 16 + .../notifications-table.component.ts | 356 ++++++++++++++++++ .../notifications-table.constants.ts | 8 + .../super-hashlists-table.component.ts | 6 +- .../table-dialog/table-dialog.component.html | 22 +- .../users-table/users-table.component.ts | 4 +- .../_datasources/notifications.datasource.ts | 43 +++ src/app/core/_models/config-ui.model.ts | 9 + 23 files changed, 629 insertions(+), 632 deletions(-) delete mode 100644 src/app/account/notifications/notifications.component.spec.ts create mode 100644 src/app/core/_components/tables/notifications-table/notifications-table.component.html create mode 100644 src/app/core/_components/tables/notifications-table/notifications-table.component.ts create mode 100644 src/app/core/_components/tables/notifications-table/notifications-table.constants.ts create mode 100644 src/app/core/_datasources/notifications.datasource.ts diff --git a/src/app/account/account.module.ts b/src/app/account/account.module.ts index e9caa954..80679006 100644 --- a/src/app/account/account.module.ts +++ b/src/app/account/account.module.ts @@ -1,29 +1,26 @@ -import { FontAwesomeModule } from "@fortawesome/angular-fontawesome"; -import { FormsModule, ReactiveFormsModule } from "@angular/forms"; -import { AccountRoutingModule } from "./account-routing.module"; -import { ComponentsModule } from "../shared/components.module"; -import { DataTablesModule } from "angular-datatables"; -import { PipesModule } from "../shared/pipes.module"; -import { CommonModule } from "@angular/common"; -import { RouterModule } from "@angular/router"; -import { NgModule } from "@angular/core"; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; -import { EditNotificationComponent } from "./notifications/notification/edit-notification.component"; +import { AccountComponent } from './account.component'; +import { AccountRoutingModule } from './account-routing.module'; +import { AccountSettingsComponent } from './settings/acc-settings/acc-settings.component'; +import { CommonModule } from '@angular/common'; +import { ComponentsModule } from '../shared/components.module'; +import { CoreComponentsModule } from '../core/_components/core-components.module'; +import { DataTablesModule } from 'angular-datatables'; +import { EditNotificationComponent } from './notifications/notification/edit-notification.component'; +import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; +import { MatButtonModule } from '@angular/material/button'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatIconModule } from '@angular/material/icon'; +import { MatInputModule } from '@angular/material/input'; +import { MatSelectModule } from '@angular/material/select'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; import { NewNotificationComponent } from './notifications/notification/new-notification.component'; -import { NotificationsComponent } from "./notifications/notifications.component"; -import { AccountComponent } from "./account.component"; +import { NgModule } from '@angular/core'; +import { NotificationsComponent } from './notifications/notifications.component'; +import { PipesModule } from '../shared/pipes.module'; +import { RouterModule } from '@angular/router'; import { UiSettingsComponent } from './settings/ui-settings/ui-settings.component'; -import { AccountSettingsComponent } from "./settings/acc-settings/acc-settings.component"; - - -import { MatFormFieldModule } from "@angular/material/form-field"; -import { MatInputModule } from "@angular/material/input"; -import { MatIconModule } from "@angular/material/icon"; -import { MatSelectModule } from "@angular/material/select"; -import { MatButtonModule } from "@angular/material/button"; -import { MatSnackBarModule } from "@angular/material/snack-bar"; - - @NgModule({ declarations: [ @@ -40,6 +37,7 @@ import { MatSnackBarModule } from "@angular/material/snack-bar"; FontAwesomeModule, DataTablesModule, ComponentsModule, + CoreComponentsModule, RouterModule, CommonModule, PipesModule, @@ -49,7 +47,7 @@ import { MatSnackBarModule } from "@angular/material/snack-bar"; MatSelectModule, MatIconModule, MatButtonModule, - MatSnackBarModule, - ], + MatSnackBarModule + ] }) -export class AccountModule { } +export class AccountModule {} diff --git a/src/app/account/notifications/notification/edit-notification.component.ts b/src/app/account/notifications/notification/edit-notification.component.ts index 4238f91f..2890dc1b 100644 --- a/src/app/account/notifications/notification/edit-notification.component.ts +++ b/src/app/account/notifications/notification/edit-notification.component.ts @@ -1,42 +1,44 @@ +import { + ACTIONARRAY, + NOTIFARRAY +} from '../../../core/_constants/notifications.config'; +import { ActivatedRoute, Params, Router } from '@angular/router'; import { Component, OnDestroy, OnInit } from '@angular/core'; import { FormControl, FormGroup } from '@angular/forms'; -import { Subscription } from 'rxjs'; -import { ACTIONARRAY, NOTIFARRAY } from '../../../core/_constants/notifications.config'; -import { AutoTitleService } from 'src/app/core/_services/shared/autotitle.service'; import { AlertService } from 'src/app/core/_services/shared/alert.service'; +import { AutoTitleService } from 'src/app/core/_services/shared/autotitle.service'; +import { Filter } from '../notifications.component'; import { GlobalService } from 'src/app/core/_services/main.service'; -import { ActivatedRoute, Params, Router } from '@angular/router'; import { Notification } from 'src/app/core/_models/notification.model'; import { SERV } from '../../../core/_services/main.config'; -import { Filter } from '../notifications.component'; +import { Subscription } from 'rxjs'; @Component({ selector: 'app-edit-notification', templateUrl: './new-notification.component.html' }) export class EditNotificationComponent implements OnInit, OnDestroy { - - static readonly SUBMITLABEL = 'Save Changes' - static readonly SUBTITLE = 'Edit Notification' + static readonly SUBMITLABEL = 'Save Changes'; + static readonly SUBTITLE = 'Edit Notification'; editedIndex: number; editView = true; - subscriptions: Subscription[] = [] + subscriptions: Subscription[] = []; filters: Filter[]; active = true; allowedActions = ACTIONARRAY; notifications = NOTIFARRAY; - oldValue: boolean - subTitle = EditNotificationComponent.SUBTITLE - submitLabel = EditNotificationComponent.SUBMITLABEL + oldValue: boolean; + subTitle = EditNotificationComponent.SUBTITLE; + submitLabel = EditNotificationComponent.SUBMITLABEL; form = new FormGroup({ - 'action': new FormControl({ value: '', disabled: true }), - 'actionFilter': new FormControl({ value: '', disabled: true }), - 'notification': new FormControl({ value: '', disabled: true }), - 'receiver': new FormControl({ value: '', disabled: true }), - 'isActive': new FormControl(), + action: new FormControl({ value: '', disabled: true }), + actionFilter: new FormControl({ value: '', disabled: true }), + notification: new FormControl({ value: '', disabled: true }), + receiver: new FormControl({ value: '', disabled: true }), + isActive: new FormControl() }); constructor( @@ -46,7 +48,7 @@ export class EditNotificationComponent implements OnInit, OnDestroy { private gs: GlobalService, private router: Router ) { - titleService.set(['Edit Notification']) + titleService.set(['Edit Notification']); } /** @@ -64,13 +66,12 @@ export class EditNotificationComponent implements OnInit, OnDestroy { */ ngOnDestroy(): void { for (const sub of this.subscriptions) { - sub.unsubscribe() + sub.unsubscribe(); } } // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function - changeAction(_action: string): void { } - + changeAction(_action: string): void {} /** * Checks whether the form is valid for submission. @@ -78,7 +79,10 @@ export class EditNotificationComponent implements OnInit, OnDestroy { * @returns {boolean} True if there is a change in the 'isActive' value; otherwise, false. */ formIsValid(): boolean { - return Boolean(this.oldValue).valueOf() !== Boolean(this.form.value.isActive).valueOf(); + return ( + Boolean(this.oldValue).valueOf() !== + Boolean(this.form.value.isActive).valueOf() + ); } /** @@ -86,17 +90,30 @@ export class EditNotificationComponent implements OnInit, OnDestroy { * Subscribes to a service to fetch notification data and populate the form. */ private createForm(): void { - this.subscriptions.push(this.gs.get(SERV.NOTIFICATIONS, this.editedIndex).subscribe((result: Notification) => { - const isActive = result.isActive - this.oldValue = isActive - this.form = new FormGroup({ - 'action': new FormControl({ value: result.action, disabled: true }), - 'actionFilter': new FormControl({ value: result.objectId + '', disabled: true }), - 'notification': new FormControl({ value: result.notification, disabled: true }), - 'receiver': new FormControl({ value: result.receiver, disabled: true }), - 'isActive': new FormControl(isActive), - }); - })); + this.subscriptions.push( + this.gs + .get(SERV.NOTIFICATIONS, this.editedIndex) + .subscribe((result: Notification) => { + const isActive = result.isActive; + this.oldValue = isActive; + this.form = new FormGroup({ + action: new FormControl({ value: result.action, disabled: true }), + actionFilter: new FormControl({ + value: result.objectId + '', + disabled: true + }), + notification: new FormControl({ + value: result.notification, + disabled: true + }), + receiver: new FormControl({ + value: result.receiver, + disabled: true + }), + isActive: new FormControl(isActive) + }); + }) + ); } /** @@ -105,10 +122,16 @@ export class EditNotificationComponent implements OnInit, OnDestroy { */ onSubmit(): void { if (this.form.valid) { - this.subscriptions.push(this.gs.update(SERV.NOTIFICATIONS, this.editedIndex, { 'isActive': this.form.value['isActive'] }).subscribe(() => { - this.alert.okAlert('Notification saved!', ''); - this.router.navigate(['/account/notifications']); - })); + this.subscriptions.push( + this.gs + .update(SERV.NOTIFICATIONS, this.editedIndex, { + isActive: this.form.value['isActive'] + }) + .subscribe(() => { + this.alert.okAlert('Notification saved!', ''); + this.router.navigate(['/account/notifications']); + }) + ); } } } diff --git a/src/app/account/notifications/notifications.component.html b/src/app/account/notifications/notifications.component.html index 99437bc7..51fc2d52 100644 --- a/src/app/account/notifications/notifications.component.html +++ b/src/app/account/notifications/notifications.component.html @@ -1,53 +1,9 @@ - - - - - - - - - - - - - - - - - - - - - - - - - -
IDStatusTrigger actionApplied ToCalled NotificationReceiverAction
{{ n.notificationSettingId }} - - {{ n.isActive === true ? "Active" : "Invalid/Not activated" }} - - {{ n.action }} - {{checkPath(n.action, false)}} {{ n.objectId }} - - {{checkPath(n.action, - false)}} {{ n.objectId }} - {{ n.notification }}{{ n.receiver }} - - - - - -
-
\ No newline at end of file + + + diff --git a/src/app/account/notifications/notifications.component.spec.ts b/src/app/account/notifications/notifications.component.spec.ts deleted file mode 100644 index 1df17c25..00000000 --- a/src/app/account/notifications/notifications.component.spec.ts +++ /dev/null @@ -1,182 +0,0 @@ -import { ComponentFixture, TestBed, tick, fakeAsync, flush } from '@angular/core/testing'; -import { NotificationsComponent } from './notifications.component'; -import { CommonModule } from '@angular/common'; -import { DataTablesModule } from 'angular-datatables'; -import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; -import { GlobalService } from 'src/app/core/_services/main.service'; -import { Observable, of } from 'rxjs'; -import { SERV } from '../../core/_services/main.config'; -import { NotificationListResponse, Notification } from 'src/app/core/_models/notification.model'; -import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; -import { RouterTestingModule } from '@angular/router/testing'; -import Swal from 'sweetalert2/dist/sweetalert2.js'; -import { FormsModule, ReactiveFormsModule } from '@angular/forms'; -import { HttpClientModule } from '@angular/common/http'; -import { RouterModule } from '@angular/router'; -import { ComponentsModule } from 'src/app/shared/components.module'; -import { PipesModule } from 'src/app/shared/pipes.module'; -import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; - - -describe('NotificationsComponent', () => { - let component: NotificationsComponent; - let fixture: ComponentFixture; - - // Sample notifications data - const notifications: Notification[] = [ - { - _id: 1, - _self: 'http://example.com/notifications/1', - action: 'Action 1', - isActive: true, - notification: 'Notification 1', - receiver: 'Receiver 1', - userId: 1, - notificationSettingId: 101, - objectId: 201, - }, - { - _id: 2, - _self: 'http://example.com/notifications/2', - action: 'Action 2', - isActive: true, - notification: 'Notification 2', - receiver: 'Receiver 2', - userId: 2, - notificationSettingId: 102, - objectId: 202, - }, - { - _id: 3, - _self: 'http://example.com/notifications/3', - action: 'Action 3', - isActive: true, - notification: 'Notification 3', - receiver: 'Receiver 3', - userId: 3, - notificationSettingId: 103, - objectId: 203, - }, - ]; - - // Mock GlobalService with required methods - const mockService: Partial = { - // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any - getAll(_methodUrl: string, params: any): Observable { - if (_methodUrl === SERV.NOTIFICATIONS) { - const response: NotificationListResponse = { - _expandable: '', - startAt: 0, - maxResults: notifications.length, - total: notifications.length, - isLast: true, - values: notifications, - }; - return of(response); - } - return of({} as NotificationListResponse); - }, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - delete(_methodUrl: string, id: number): Observable { - if (_methodUrl === SERV.NOTIFICATIONS) { - const index = notifications.findIndex((n) => n._id === id); - if (index !== -1) { - notifications.splice(index, 1); - } - return of({}); - } - return of({}); - }, - }; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - imports: [ - CommonModule, - FormsModule, - ReactiveFormsModule, - HttpClientModule, - FontAwesomeModule, - DataTablesModule, - ComponentsModule, - RouterModule, - PipesModule, - NgbModule, - BrowserAnimationsModule, - RouterTestingModule, - ], - declarations: [NotificationsComponent], - providers: [ - { - provide: GlobalService, - useValue: mockService, - }, - Swal - ], - }).compileComponents(); - - fixture = TestBed.createComponent(NotificationsComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - - }); - - // Check if the component is created successfully - it('should create the component', () => { - expect(component).toBeTruthy(); - }); - - // Ensure that notifications are fetched and stored in the component - it('should fetch notifications on initialization', () => { - expect(component.notifications).toEqual(notifications); - }); - - // Verify that the table in the component's HTML is populated with the correct number of rows - it('should render the table with notifications', () => { - const tableRows = fixture.nativeElement.querySelectorAll('tbody tr'); - expect(tableRows.length).toBe(notifications.length); - }); - - // Simulate the deletion of a notification and confirm the action - // Expect that the notification deletion was called, Swal fire was triggered, - // and the notification is no longer in the component's list - it('should handle notification deletion', fakeAsync(() => { - const deleteSpy = spyOn(mockService, 'delete').and.callThrough(); - const swalFireSpy = spyOn(Swal, 'fire').and.callFake(() => { - return Promise.resolve({ isConfirmed: true }); - }); - - const notificationToDelete = notifications[0]; - component.onDelete(notificationToDelete._id, notificationToDelete.notification); - - // Trigger the confirmation and flush any asynchronous tasks - Swal.clickConfirm(); - flush(); - - expect(deleteSpy).toHaveBeenCalledWith(SERV.NOTIFICATIONS, notificationToDelete._id); - expect(swalFireSpy).toHaveBeenCalled(); - expect(component.notifications).not.toContain(notificationToDelete); - })); - - // Simulate the deletion of a notification and cancel the action - // Expect that the notification deletion was not called, - // Swal fire was triggered, and the notification remains in the component's list - it('should handle notification deletion cancellation', fakeAsync(() => { - const deleteSpy = spyOn(mockService, 'delete').and.callThrough(); - const swalFireSpy = spyOn(Swal, 'fire').and.callFake(() => { - return Promise.resolve({ isConfirmed: false }); - }); - - const notificationToDelete = notifications[0]; - component.onDelete(notificationToDelete._id, notificationToDelete.notification); - - // Trigger the cancellation and flush any asynchronous tasks - Swal.clickCancel() - flush(); - - expect(deleteSpy).not.toHaveBeenCalled(); - expect(swalFireSpy).toHaveBeenCalled(); - expect(component.notifications).toContain(notificationToDelete); - })); - -}); diff --git a/src/app/account/notifications/notifications.component.ts b/src/app/account/notifications/notifications.component.ts index 27a8a502..1cf4e9f0 100644 --- a/src/app/account/notifications/notifications.component.ts +++ b/src/app/account/notifications/notifications.component.ts @@ -1,314 +1,17 @@ -import { faTrash, faPlus, faEye, faEdit } from '@fortawesome/free-solid-svg-icons'; -import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core'; -import { environment } from './../../../environments/environment'; -import { DataTableDirective } from 'angular-datatables'; -import { Subject, Subscription } from 'rxjs'; - import { AutoTitleService } from 'src/app/core/_services/shared/autotitle.service'; -import { NotificationListResponse } from 'src/app/core/_models/notification.model'; -import { AlertService } from 'src/app/core/_services/shared/alert.service'; -import { ACTION } from '../../core/_constants/notifications.config'; -import { GlobalService } from 'src/app/core/_services/main.service'; -import { Notification } from 'src/app/core/_models/notification.model'; -import { SERV } from '../../core/_services/main.config'; +import { Component } from '@angular/core'; export interface Filter { - id: number, - name: string + id: number; + name: string; } -declare let $: any; - @Component({ selector: 'app-notifications', templateUrl: './notifications.component.html' }) -export class NotificationsComponent implements OnInit, OnDestroy { - - faTrash = faTrash; - faPlus = faPlus; - faEdit = faEdit; - faEye = faEye; - - @ViewChild(DataTableDirective, { static: false }) - - // DataTable properties - dtElement: DataTableDirective; - dtTrigger: Subject = new Subject(); - dtOptions: any = {}; - - // List of notifications - notifications: Notification[]; - - // Subscriptions to unsubscribe on component destruction - subscriptions: Subscription[] = [] - - private maxResults = environment.config.prodApiMaxResults; - - constructor( - private titleService: AutoTitleService, - private alert: AlertService, - private gs: GlobalService - ) { - titleService.set(['Notifications']) - } - - /** - * Initializes DataTable and retrieves notifications. - */ - ngOnInit(): void { - this.getNotifications(); - this.setupTable(); - } - - /** - * Unsubscribes from active subscriptions. - */ - ngOnDestroy(): void { - this.dtTrigger.unsubscribe(); - for (const sub of this.subscriptions) { - sub.unsubscribe(); - } - } - - /** - * Refreshes the data table by re-rendering it. - * Fetches notifications and sets up the table again. - */ - onRefresh() { - this.rerender(); - this.getNotifications(); - this.setupTable(); - } - - /** - * Rerenders the DataTable instance. - * Destroys and recreates the DataTable to reflect changes. - */ - rerender(): void { - this.dtElement.dtInstance.then((dtInstance: DataTables.Api) => { - // Destroy the table first - dtInstance.destroy(); - // Call the dtTrigger to rerender again - setTimeout(() => { - if (this.dtTrigger['new']) { - this.dtTrigger['new'].next(); - } - }); - }); - } - - /** - * Fetches notifications from the server. - * Subscribes to the API response and updates the notifications list. - */ - getNotifications(): void { - const params = { 'maxResults': this.maxResults }; - - this.subscriptions.push(this.gs.getAll(SERV.NOTIFICATIONS, params).subscribe((response: NotificationListResponse) => { - this.notifications = response.values; - this.dtTrigger.next(void 0); - })); - } - - /** - * Sets up the DataTable options and buttons. - * Customizes DataTable appearance and behavior. - */ - setupTable(): void { - const self = this; - this.dtOptions = { - dom: 'Bfrtip', - scrollX: true, - pageLength: 25, - lengthMenu: [ - [10, 25, 50, 100, 250, -1], - [10, 25, 50, 100, 250, 'All'] - ], - stateSave: true, - select: true, - buttons: { - dom: { - button: { - className: 'dt-button buttons-collection btn btn-sm-dt btn-outline-gray-600-dt', - } - }, - buttons: [ - { - text: '↻', - autoClose: true, - action: function (e, dt, node, config) { - self.onRefresh(); - } - }, - { - extend: 'collection', - text: 'Export', - buttons: [ - { - extend: 'excelHtml5', - exportOptions: { - columns: [0, 1, 2, 3, 4] - }, - }, - { - extend: 'print', - exportOptions: { - columns: [0, 1, 2, 3, 4] - }, - customize: function (win) { - $(win.document.body) - .css('font-size', '10pt') - $(win.document.body).find('table') - .addClass('compact') - .css('font-size', 'inherit'); - } - }, - { - extend: 'csvHtml5', - exportOptions: { modifier: { selected: true } }, - select: true, - customize: function (dt, csv) { - let data = ''; - for (let i = 0; i < dt.length; i++) { - data = 'Notifications\n\n' + dt; - } - return data; - } - }, - { - extend: 'copy', - } - ] - }, - { - extend: 'collection', - text: 'Bulk Actions', - buttons: [ - { - text: 'Delete Notification(s)', - autoClose: true, - action: function (e, dt, node, config) { - self.onDeleteBulk(); - } - } - ] - }, - { - extend: 'pageLength', - className: 'btn-sm' - } - ], - } - } - } - - // Refresh the table after a delete operation - onRefreshTable() { - setTimeout(() => { - this.ngOnInit(); - this.rerender(); // Rerender the DataTable - }, 2000); - } - - /** - * Handles notification deletion. - * Displays a confirmation dialog and deletes the notification if confirmed. - * - * @param {number} id - The ID of the notification to delete. - * @param {string} name - The name of the notification. - */ - onDelete(id: number, name: string) { - this.alert.deleteConfirmation(name, 'Notifications').then((confirmed) => { - if (confirmed) { - // Deletion - this.subscriptions.push(this.gs.delete(SERV.NOTIFICATIONS, id).subscribe(() => { - // Successful deletion - this.alert.okAlert(`Deleted notification ${name}`, ''); - this.onRefreshTable(); // Refresh the table - })); - } else { - // Handle cancellation - this.alert.okAlert(`Notification ${name} is safe!`, ''); - } - }); - } - - // Bulk actions - - /** - * Handles Notifications selection for bulk actions. - * - * @returns {number[]} - An array of selected hashlist IDs. - */ - onSelectedNotifications() { - $(".dt-button-background").trigger("click"); - const selection = $($(this.dtElement).DataTable.tables()).DataTable().rows({ selected: true }).data().pluck(0).toArray(); - if (selection.length == 0) { - this.alert.okAlert('You have not selected any Notification', ''); - return; - } - const selectionnum = selection.map(i => Number(i)); - - return selectionnum; - } - - /** - * Handles bulk deletion - * Delete the Notifications showing a progress bar - * - */ - async onDeleteBulk() { - const NotifIds = this.onSelectedNotifications(); - this.alert.bulkDeleteAlert(NotifIds, 'Notifications', SERV.NOTIFICATIONS); - this.onRefreshTable(); - } - - /** - * Determines the path or title based on the provided filter. - * - * @param {string} filter - The filter used to determine the path or title. - * @param {boolean} [type] - If true, returns the path; otherwise, returns the title. - * @returns {string} The path or title based on the filter. - */ - checkPath(filter: string, type?: boolean): string { - let path: string; - let title: string; - - switch (filter) { - case ACTION.AGENT_ERROR: - case ACTION.OWN_AGENT_ERROR: - case ACTION.DELETE_AGENT: - title = 'Agent:' - path = '/agents/show-agents/'; - break; - - case ACTION.NEW_TASK: - case ACTION.TASK_COMPLETE: - case ACTION.DELETE_TASK: - title = 'Task:' - path = '/tasks/show-tasks/'; - break; - - case ACTION.DELETE_HASHLIST: - case ACTION.HASHLIST_ALL_CRACKED: - case ACTION.HASHLIST_CRACKED_HASH: - title = 'Hashlist:' - path = '/hashlists/hashlist/'; - break; - - case ACTION.USER_CREATED: - case ACTION.USER_DELETED: - case ACTION.USER_LOGIN_FAILED: - title = 'User:' - path = '/users/'; - break; - - default: - title = '' - path = 'none'; - - } - if (type) { return path; } else { return title; } +export class NotificationsComponent { + constructor(private titleService: AutoTitleService) { + titleService.set(['Notifications']); } } diff --git a/src/app/core/_components/core-components.module.ts b/src/app/core/_components/core-components.module.ts index b9ac248e..2d6e3de0 100644 --- a/src/app/core/_components/core-components.module.ts +++ b/src/app/core/_components/core-components.module.ts @@ -39,6 +39,7 @@ import { MatSortModule } from '@angular/material/sort'; import { MatTableModule } from '@angular/material/table'; import { MatTooltipModule } from '@angular/material/tooltip'; import { NgModule } from '@angular/core'; +import { NotificationsTableComponent } from './tables/notifications-table/notifications-table.component'; import { PreprocessorsTableComponent } from './tables/preprocessors-table/preprocessors-table.component'; import { RouterModule } from '@angular/router'; import { RowActionMenuComponent } from './menus/row-action-menu/row-action-menu.component'; @@ -69,7 +70,8 @@ import { UsersTableComponent } from './tables/users-table/users-table.component' HealthChecksTableComponent, LogsTableComponent, UsersTableComponent, - AccessGroupsTableComponent + AccessGroupsTableComponent, + NotificationsTableComponent ], imports: [ ReactiveFormsModule, @@ -116,7 +118,8 @@ import { UsersTableComponent } from './tables/users-table/users-table.component' HealthChecksTableComponent, LogsTableComponent, UsersTableComponent, - AccessGroupsTableComponent + AccessGroupsTableComponent, + NotificationsTableComponent ], providers: [ { diff --git a/src/app/core/_components/menus/base-menu/base-menu.component.ts b/src/app/core/_components/menus/base-menu/base-menu.component.ts index 5c1b263b..b3474061 100644 --- a/src/app/core/_components/menus/base-menu/base-menu.component.ts +++ b/src/app/core/_components/menus/base-menu/base-menu.component.ts @@ -36,6 +36,14 @@ export class BaseMenuComponent { return this.checkId('agentId') && 'agentName' in this.data; } + /** + * Check if the data row is of type "Notification". + * @returns `true` if the data row is an notification; otherwise, `false`. + */ + protected isNotification(): boolean { + return this.checkId('notificationSettingId'); + } + /** * Check if the data row is of type "AccessGroup". * @returns `true` if the data row is an access group; otherwise, `false`. diff --git a/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.component.ts b/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.component.ts index 2f250c52..b6f0e0ac 100644 --- a/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.component.ts +++ b/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.component.ts @@ -46,6 +46,12 @@ export class BulkActionMenuComponent BulkActionMenuLabel.DEACTIVATE_USERS, BulkActionMenuLabel.DELETE_USERS ); + } else if (this.dataType === 'notifications') { + this.setActivateDeleteMenu( + BulkActionMenuLabel.ACTIVATE_NOTIFICATION, + BulkActionMenuLabel.DEACTIVATE_NOTIFICATIONS, + BulkActionMenuLabel.DELETE_NOTIFICATIONS + ); } else if (this.dataType === 'hashlists') { this.setHashlistMenu(); } else if (this.dataType === 'access-groups') { diff --git a/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.constants.ts b/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.constants.ts index beb3e122..fee3049e 100644 --- a/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.constants.ts +++ b/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.constants.ts @@ -1,5 +1,6 @@ export const BulkActionMenuLabel = { DELETE_AGENTS: 'Delete Agents', + DELETE_NOTIFICATIONS: 'Delete Notification', DELETE_TASKS: 'Delete Tasks', DELETE_HASHLISTS: 'Delete Hashlists', DELETE_HASHTYPES: 'Delete Hashtypes', @@ -12,7 +13,9 @@ export const BulkActionMenuLabel = { DELETE_ACCESSGROUPS: 'Delete Access Groups', ACTIVATE_AGENTS: 'Activate Agents', ACTIVATE_USERS: 'Activate Users', + ACTIVATE_NOTIFICATION: 'Activate Notification', DEACTIVATE_AGENTS: 'Deactivate Agents', + DEACTIVATE_NOTIFICATIONS: 'Deactivate Notification', DEACTIVATE_USERS: 'Deactivate Users', ARCHIVE_TASKS: 'Archive Tasks', ARCHIVE_HASHLISTS: 'Archive Hashlists' diff --git a/src/app/core/_components/menus/row-action-menu/row-action-menu.component.ts b/src/app/core/_components/menus/row-action-menu/row-action-menu.component.ts index 60a710d5..0925bc10 100644 --- a/src/app/core/_components/menus/row-action-menu/row-action-menu.component.ts +++ b/src/app/core/_components/menus/row-action-menu/row-action-menu.component.ts @@ -55,6 +55,8 @@ export class RowActionMenuComponent ); } else if (this.isUser()) { this.setUserMenu(); + } else if (this.isNotification()) { + this.setNotificationMenu(); } else if (this.isTask()) { this.setTaskMenu(); } else if (this.isHashlist()) { @@ -157,6 +159,30 @@ export class RowActionMenuComponent ]); } + /** + * Sets the context menu items for a user data row. + */ + private setNotificationMenu(): void { + if (this.data['isActive']) { + this.setActionMenuItems(0, [ + this.getDeactivateMenuItem(RowActionMenuLabel.DEACTIVATE_NOTIFICATION) + ]); + } else { + this.setActionMenuItems(0, [ + this.getActivateMenuItem(RowActionMenuLabel.ACTIVATE_NOTIFICATION) + ]); + } + + this.addActionMenuItem( + 0, + this.getEditMenuItem(RowActionMenuLabel.EDIT_NOTIFICATION) + ); + + this.setActionMenuItems(1, [ + this.getDeleteMenuItem(RowActionMenuLabel.DELETE_NOTIFICATION) + ]); + } + /** * Sets the context menu items for a task data row. */ 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 40cd1ff4..bbd18af1 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 @@ -1,9 +1,12 @@ export const RowActionMenuLabel = { ACTIVATE_USER: 'Activate User', ACTIVATE_AGENT: 'Activate Agent', + ACTIVATE_NOTIFICATION: 'Activate Notification', DEACTIVATE_USER: 'Deactivate User', DEACTIVATE_AGENT: 'Deactivate Agent', + DEACTIVATE_NOTIFICATION: 'Deactivate Notification', EDIT_AGENT: 'Edit Agent', + EDIT_NOTIFICATION: 'Edit Notification', EDIT_TASK: 'Edit Task', EDIT_SUBTASKS: 'Edit Subtasks', EDIT_HASHLIST: 'Edit Hashlist', @@ -15,6 +18,7 @@ export const RowActionMenuLabel = { EDIT_USER: 'Edit User', EDIT_ACCESSGROUP: 'Edit Access Group', DELETE_AGENT: 'Delete Agent', + DELETE_NOTIFICATION: 'Delete Notification', DELETE_CRACKER: 'Delete Cracker', DELETE_TASK: 'Delete Task', DELETE_HASHLIST: 'Delete Hashlist', diff --git a/src/app/core/_components/tables/agents-table/agents-table.component.ts b/src/app/core/_components/tables/agents-table/agents-table.component.ts index 9b21bffc..85a93435 100644 --- a/src/app/core/_components/tables/agents-table/agents-table.component.ts +++ b/src/app/core/_components/tables/agents-table/agents-table.component.ts @@ -339,23 +339,6 @@ export class AgentsTableComponent return this.sanitize(data); } - @Cacheable(['_id', 'isActive']) - async renderStatusIcon(agent: Agent): Promise { - return agent.isActive - ? [ - { - name: 'check_circle', - cls: 'text-ok' - } - ] - : [ - { - name: 'remove_circle', - cls: 'text-critical' - } - ]; - } - private async getSpeed(agent: Agent): Promise { return this.getChunkDataParam(agent._id, 'speed'); } 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 4e7ff486..4c70c3d2 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 @@ -7,6 +7,7 @@ import { ViewChild } from '@angular/core'; import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; +import { HTTableIcon, HTTableRouterLink } from '../ht-table/ht-table.models'; import { UIConfig, uiConfigDefault @@ -17,7 +18,6 @@ import { Cacheable } from 'src/app/core/_decorators/cacheable'; import { ExportService } from 'src/app/core/_services/export/export.service'; import { GlobalService } from 'src/app/core/_services/main.service'; import { HTTableComponent } from '../ht-table/ht-table.component'; -import { HTTableRouterLink } from '../ht-table/ht-table.models'; import { LocalStorageService } from 'src/app/core/_services/storage/local-storage.service'; import { MatDialog } from '@angular/material/dialog'; import { MatSnackBar } from '@angular/material/snack-bar'; @@ -212,4 +212,25 @@ export class BaseTableComponent { return links; } + + @Cacheable(['isActive']) + async renderStatusIcon(obj: unknown): Promise { + if (obj) { + return obj['isActive'] + ? [ + { + name: 'check_circle', + cls: 'text-ok' + } + ] + : [ + { + name: 'remove_circle', + cls: 'text-critical' + } + ]; + } + + return []; + } } diff --git a/src/app/core/_components/tables/hashlists-table/hashlists-table.component.ts b/src/app/core/_components/tables/hashlists-table/hashlists-table.component.ts index 4b906443..b0889c53 100644 --- a/src/app/core/_components/tables/hashlists-table/hashlists-table.component.ts +++ b/src/app/core/_components/tables/hashlists-table/hashlists-table.component.ts @@ -89,7 +89,7 @@ export class HashlistsTableComponent { name: HashlistsTableColumnLabel.CRACKED, dataKey: 'cracked', - icons: (hashlist: Hashlist) => this.renderStatusIcon(hashlist), + icons: (hashlist: Hashlist) => this.renderCrackedStatusIcon(hashlist), render: (hashlist: Hashlist) => formatPercentage(hashlist.cracked, hashlist.hashCount), isSortable: true, @@ -157,7 +157,7 @@ export class HashlistsTableComponent } @Cacheable(['_id', 'hashCount', 'cracked']) - async renderStatusIcon(hashlist: Hashlist): Promise { + async renderCrackedStatusIcon(hashlist: Hashlist): Promise { const icons: HTTableIcon[] = []; if (hashlist.hashCount === hashlist.cracked) { icons.push({ diff --git a/src/app/core/_components/tables/ht-table/ht-table.models.ts b/src/app/core/_components/tables/ht-table/ht-table.models.ts index 705c763c..6a1839ff 100644 --- a/src/app/core/_components/tables/ht-table/ht-table.models.ts +++ b/src/app/core/_components/tables/ht-table/ht-table.models.ts @@ -11,6 +11,7 @@ export type DataType = | 'preprocessors' | 'users' | 'access-groups' + | 'notifications' | 'agent-binaries' | 'health-checks' | 'logs' diff --git a/src/app/core/_components/tables/notifications-table/notifications-table.component.html b/src/app/core/_components/tables/notifications-table/notifications-table.component.html new file mode 100644 index 00000000..74789a8f --- /dev/null +++ b/src/app/core/_components/tables/notifications-table/notifications-table.component.html @@ -0,0 +1,16 @@ + diff --git a/src/app/core/_components/tables/notifications-table/notifications-table.component.ts b/src/app/core/_components/tables/notifications-table/notifications-table.component.ts new file mode 100644 index 00000000..e9a3ec8a --- /dev/null +++ b/src/app/core/_components/tables/notifications-table/notifications-table.component.ts @@ -0,0 +1,356 @@ +/* eslint-disable @angular-eslint/component-selector */ +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { HTTableColumn, HTTableRouterLink } from '../ht-table/ht-table.models'; +import { catchError, forkJoin } from 'rxjs'; + +import { ACTION } from 'src/app/core/_constants/notifications.config'; +import { ActionMenuEvent } from '../../menus/action-menu/action-menu.model'; +import { BaseTableComponent } from '../base-table/base-table.component'; +import { BulkActionMenuAction } from '../../menus/bulk-action-menu/bulk-action-menu.constants'; +import { Cacheable } from 'src/app/core/_decorators/cacheable'; +import { DialogData } from '../table-dialog/table-dialog.model'; +import { ExportMenuAction } from '../../menus/export-menu/export-menu.constants'; +import { Notification } from 'src/app/core/_models/notification.model'; +import { NotificationsDataSource } from 'src/app/core/_datasources/notifications.datasource'; +import { NotificationsTableColumnLabel } from './notifications-table.constants'; +import { RowActionMenuAction } from '../../menus/row-action-menu/row-action-menu.constants'; +import { SERV } from 'src/app/core/_services/main.config'; +import { TableDialogComponent } from '../table-dialog/table-dialog.component'; + +@Component({ + selector: 'notifications-table', + templateUrl: './notifications-table.component.html' +}) +export class NotificationsTableComponent + extends BaseTableComponent + implements OnInit, OnDestroy +{ + tableColumns: HTTableColumn[] = []; + dataSource: NotificationsDataSource; + + ngOnInit(): void { + this.tableColumns = this.getColumns(); + this.dataSource = new NotificationsDataSource( + this.cdr, + this.gs, + this.uiService + ); + this.dataSource.setColumns(this.tableColumns); + this.dataSource.loadAll(); + } + + ngOnDestroy(): void { + for (const sub of this.subscriptions) { + sub.unsubscribe(); + } + } + + filter(item: Notification, filterValue: string): boolean { + if (item.notification.toLowerCase().includes(filterValue)) { + return true; + } + + return false; + } + + getColumns(): HTTableColumn[] { + const tableColumns = [ + { + name: NotificationsTableColumnLabel.ID, + dataKey: '_id', + isSortable: true, + export: async (notification: Notification) => notification._id + '' + }, + { + name: NotificationsTableColumnLabel.STATUS, + dataKey: 'isActive', + render: (notification: Notification) => + notification.isActive ? 'Active' : 'Inactive', + icons: (notification: Notification) => + this.renderStatusIcon(notification), + isSortable: true, + export: async (notification: Notification) => notification.isActive + '' + }, + { + name: NotificationsTableColumnLabel.ACTION, + dataKey: 'action', + isSortable: true, + export: async (notification: Notification) => notification.action + }, + { + name: NotificationsTableColumnLabel.APPLIED_TO, + dataKey: 'appliedTo', + routerLink: (notification: Notification) => + this.renderAppliedToLink(notification), + isSortable: true, + export: async (notification: Notification) => notification.action + }, + { + name: NotificationsTableColumnLabel.NOTIFICATION, + dataKey: 'notification', + isSortable: true, + export: async (notification: Notification) => notification.notification + }, + { + name: NotificationsTableColumnLabel.RECEIVER, + dataKey: 'receiver', + isSortable: true, + export: async (notification: Notification) => notification.receiver + } + ]; + + return tableColumns; + } + + openDialog(data: DialogData) { + const dialogRef = this.dialog.open(TableDialogComponent, { + data: data, + width: '450px' + }); + + this.subscriptions.push( + dialogRef.afterClosed().subscribe((result) => { + if (result && result.action) { + switch (result.action) { + case BulkActionMenuAction.ACTIVATE: + this.bulkActionActivate(result.data, true); + break; + case BulkActionMenuAction.DEACTIVATE: + this.bulkActionActivate(result.data, false); + break; + case BulkActionMenuAction.DELETE: + this.bulkActionDelete(result.data); + break; + case RowActionMenuAction.DELETE: + this.rowActionDelete(result.data); + break; + } + } + }) + ); + } + + // --- Action functions --- + + exportActionClicked(event: ActionMenuEvent): void { + switch (event.menuItem.action) { + case ExportMenuAction.EXCEL: + this.exportService.toExcel( + 'hashtopolis-notifications', + this.tableColumns, + event.data + ); + break; + case ExportMenuAction.CSV: + this.exportService.toCsv( + 'hashtopolis-notifications', + this.tableColumns, + event.data + ); + break; + case ExportMenuAction.COPY: + this.exportService + .toClipboard(this.tableColumns, event.data) + .then(() => { + this.snackBar.open( + 'The selected rows are copied to the clipboard', + 'Close' + ); + }); + break; + } + } + + rowActionClicked(event: ActionMenuEvent): void { + switch (event.menuItem.action) { + case RowActionMenuAction.DELETE: + this.openDialog({ + rows: [event.data], + title: `Deleting notification ${event.data.action} (${event.data._id}) ...`, + icon: 'warning', + body: `Are you sure you want to delete it? Note that this action cannot be undone.`, + warn: true, + action: event.menuItem.action + }); + break; + case RowActionMenuAction.EDIT: + this.rowActionEdit(event.data); + break; + case RowActionMenuAction.ACTIVATE: + this.bulkActionActivate([event.data], true); + break; + case RowActionMenuAction.DEACTIVATE: + this.bulkActionActivate([event.data], false); + break; + } + } + + bulkActionClicked(event: ActionMenuEvent): void { + switch (event.menuItem.action) { + case BulkActionMenuAction.ACTIVATE: + this.openDialog({ + rows: event.data, + title: `Activating ${event.data.length} notifications ...`, + icon: 'info', + listAttribute: 'action', + action: event.menuItem.action + }); + break; + case BulkActionMenuAction.DEACTIVATE: + this.openDialog({ + rows: event.data, + title: `Deactivating ${event.data.length} notifications ...`, + icon: 'info', + listAttribute: 'action', + action: event.menuItem.action + }); + break; + case BulkActionMenuAction.DELETE: + this.openDialog({ + rows: event.data, + title: `Deleting ${event.data.length} notifications ...`, + icon: 'warning', + body: `Are you sure you want to delete the above notifications? Note that this action cannot be undone.`, + warn: true, + listAttribute: 'action', + action: event.menuItem.action + }); + break; + } + } + + /** + * @todo Implement error handling. + */ + private bulkActionDelete(notifications: Notification[]): void { + const requests = notifications.map((notification: Notification) => { + return this.gs.delete(SERV.CRACKERS_TYPES, notification._id); + }); + + this.subscriptions.push( + forkJoin(requests) + .pipe( + catchError((error) => { + console.error('Error during deletion:', error); + return []; + }) + ) + .subscribe((results) => { + this.snackBar.open( + `Successfully deleted ${results.length} notifications!`, + 'Close' + ); + this.reload(); + }) + ); + } + + /** + * @todo Implement error handling. + */ + private bulkActionActivate( + notifications: Notification[], + isActive: boolean + ): void { + const requests = notifications.map((notification: Notification) => { + return this.gs.update(SERV.NOTIFICATIONS, notification._id, { + isActive: isActive + }); + }); + + const action = isActive ? 'activated' : 'deactivated'; + + this.subscriptions.push( + forkJoin(requests) + .pipe( + catchError((error) => { + console.error('Error during activation:', error); + return []; + }) + ) + .subscribe((results) => { + this.snackBar.open( + `Successfully ${action} ${results.length} notifications!`, + 'Close' + ); + this.dataSource.reload(); + }) + ); + } + + /** + * @todo Implement error handling. + */ + private rowActionDelete(notifications: Notification[]): void { + this.subscriptions.push( + this.gs + .delete(SERV.CRACKERS_TYPES, notifications[0]._id) + .pipe( + catchError((error) => { + console.error('Error during deletion:', error); + return []; + }) + ) + .subscribe(() => { + this.snackBar.open('Successfully deleted notification!', 'Close'); + this.reload(); + }) + ); + } + + private rowActionEdit(notification: Notification): void { + this.router.navigate([ + '/account', + 'notifications', + notification._id, + 'edit' + ]); + } + + @Cacheable(['_id', 'actions', 'objectId']) + async renderAppliedToLink( + notification: Notification + ): Promise { + const links: HTTableRouterLink[] = []; + + switch (notification.action) { + case ACTION.AGENT_ERROR: + case ACTION.OWN_AGENT_ERROR: + case ACTION.DELETE_AGENT: + links.push({ + label: `Agent: ${notification.objectId}`, + routerLink: ['/agents', 'show-agents', notification.objectId, 'edit'] + }); + break; + + case ACTION.NEW_TASK: + case ACTION.TASK_COMPLETE: + case ACTION.DELETE_TASK: + links.push({ + label: `Task: ${notification.objectId}`, + routerLink: ['/tasks', 'show-tasks', notification.objectId, 'edit'] + }); + break; + + case ACTION.DELETE_HASHLIST: + case ACTION.HASHLIST_ALL_CRACKED: + case ACTION.HASHLIST_CRACKED_HASH: + links.push({ + label: `Hashlist: ${notification.objectId}`, + routerLink: ['/hashlists', 'hashlist', notification.objectId, 'edit'] + }); + break; + + case ACTION.USER_CREATED: + case ACTION.USER_DELETED: + case ACTION.USER_LOGIN_FAILED: + links.push({ + label: `User: ${notification.objectId}`, + routerLink: ['/users', notification.objectId] + }); + break; + } + + return links; + } +} diff --git a/src/app/core/_components/tables/notifications-table/notifications-table.constants.ts b/src/app/core/_components/tables/notifications-table/notifications-table.constants.ts new file mode 100644 index 00000000..7d54749f --- /dev/null +++ b/src/app/core/_components/tables/notifications-table/notifications-table.constants.ts @@ -0,0 +1,8 @@ +export const NotificationsTableColumnLabel = { + ID: 'ID', + STATUS: 'Status', + APPLIED_TO: 'Applied To', + ACTION: 'Trigger Action', + NOTIFICATION: 'Called Notification', + RECEIVER: 'Receiver' +}; diff --git a/src/app/core/_components/tables/super-hashlists-table/super-hashlists-table.component.ts b/src/app/core/_components/tables/super-hashlists-table/super-hashlists-table.component.ts index edc8ab0b..dc808283 100644 --- a/src/app/core/_components/tables/super-hashlists-table/super-hashlists-table.component.ts +++ b/src/app/core/_components/tables/super-hashlists-table/super-hashlists-table.component.ts @@ -84,7 +84,7 @@ export class SuperHashlistsTableComponent name: SuperHashlistsTableColumnLabel.CRACKED, dataKey: 'cracked', icons: (superHashlist: Hashlist) => - this.renderStatusIcon(superHashlist), + this.renderCrackedStatusIcon(superHashlist), render: (superHashlist: Hashlist) => formatPercentage(superHashlist.cracked, superHashlist.hashCount), isSortable: true, @@ -153,7 +153,9 @@ export class SuperHashlistsTableComponent } @Cacheable(['_id', 'hashCount', 'cracked']) - async renderStatusIcon(superHashlist: Hashlist): Promise { + async renderCrackedStatusIcon( + superHashlist: Hashlist + ): Promise { const icons: HTTableIcon[] = []; if (superHashlist.hashCount === superHashlist.cracked) { icons.push({ diff --git a/src/app/core/_components/tables/table-dialog/table-dialog.component.html b/src/app/core/_components/tables/table-dialog/table-dialog.component.html index a69d05ac..1c956112 100644 --- a/src/app/core/_components/tables/table-dialog/table-dialog.component.html +++ b/src/app/core/_components/tables/table-dialog/table-dialog.component.html @@ -4,13 +4,23 @@

    -
  • {{ row[data.listAttribute] }}
  • +
  • + {{ row[data.listAttribute] + }} ({{ row['_id'] }}) +

{{ data.body }}

- - -
\ No newline at end of file + + + diff --git a/src/app/core/_components/tables/users-table/users-table.component.ts b/src/app/core/_components/tables/users-table/users-table.component.ts index e5a8c2ab..a4d8f8c7 100644 --- a/src/app/core/_components/tables/users-table/users-table.component.ts +++ b/src/app/core/_components/tables/users-table/users-table.component.ts @@ -105,7 +105,7 @@ export class UsersTableComponent { name: UsersTableColumnLabel.STATUS, dataKey: 'isValid', - icons: (user: User) => this.renderStatusIcon(user), + icons: (user: User) => this.renderIsValidIcon(user), render: (user: User) => user.isValid ? UsersTableStatus.VALID : UsersTableStatus.INVALID, isSortable: true, @@ -160,7 +160,7 @@ export class UsersTableComponent // --- Render functions --- @Cacheable(['_id', 'isValid']) - async renderStatusIcon(user: User): Promise { + async renderIsValidIcon(user: User): Promise { return user.isValid ? [ { diff --git a/src/app/core/_datasources/notifications.datasource.ts b/src/app/core/_datasources/notifications.datasource.ts new file mode 100644 index 00000000..163b8448 --- /dev/null +++ b/src/app/core/_datasources/notifications.datasource.ts @@ -0,0 +1,43 @@ +import { catchError, finalize, of } from 'rxjs'; + +import { BaseDataSource } from './base.datasource'; +import { ListResponseWrapper } from '../_models/response.model'; +import { Notification } from '../_models/notification.model'; +import { SERV } from '../_services/main.config'; + +export class NotificationsDataSource extends BaseDataSource { + loadAll(): void { + this.loading = true; + + const startAt = this.currentPage * this.pageSize; + const params = { + maxResults: this.pageSize, + startAt: startAt + }; + + const notifications$ = this.service.getAll(SERV.NOTIFICATIONS, params); + + this.subscriptions.push( + notifications$ + .pipe( + catchError(() => of([])), + finalize(() => (this.loading = false)) + ) + .subscribe((response: ListResponseWrapper) => { + const notifications: Notification[] = response.values; + + this.setPaginationConfig( + this.pageSize, + this.currentPage, + response.total + ); + this.setData(notifications); + }) + ); + } + + reload(): void { + this.clearSelection(); + this.loadAll(); + } +} diff --git a/src/app/core/_models/config-ui.model.ts b/src/app/core/_models/config-ui.model.ts index 4329e6d7..ee261c44 100644 --- a/src/app/core/_models/config-ui.model.ts +++ b/src/app/core/_models/config-ui.model.ts @@ -6,6 +6,7 @@ import { FilesTableColumnLabel } from '../_components/tables/files-table/files-t import { HashlistsTableColumnLabel } from '../_components/tables/hashlists-table/hashlists-table.constants'; import { HashtypesTableColumnLabel } from '../_components/tables/hashtypes-table/hashtypes-table.constants'; import { HealthChecksTableColumnLabel } from '../_components/tables/health-checks-table/health-checks-table.constants'; +import { NotificationsTableColumnLabel } from '../_components/tables/notifications-table/notifications-table.constants'; import { PreprocessorsTableColumnLabel } from '../_components/tables/preprocessors-table/preprocessors-table.constants'; import { SuperHashlistsTableColumnLabel } from '../_components/tables/super-hashlists-table/super-hashlists-table.constants'; @@ -30,6 +31,14 @@ export const uiConfigDefault: UIConfig = { theme: 'light', timefmt: 'dd/MM/yyyy h:mm:ss', tableSettings: { + notificationsTable: [ + NotificationsTableColumnLabel.ID, + NotificationsTableColumnLabel.STATUS, + NotificationsTableColumnLabel.APPLIED_TO, + NotificationsTableColumnLabel.ACTION, + NotificationsTableColumnLabel.NOTIFICATION, + NotificationsTableColumnLabel.RECEIVER + ], agentsTable: [ AgentsTableColumnLabel.ID, AgentsTableColumnLabel.NAME, From 017ea79d73a89d5b2aceb05b68b1aae3f4972583 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Wed, 22 Nov 2023 15:50:39 +0100 Subject: [PATCH 300/419] Add vouchers table --- src/app/agents/agent.module.ts | 44 ++-- src/app/agents/agents-routing.module.ts | 12 +- .../agents/new-agent/new-agent.component.html | 171 +++++--------- .../agents/new-agent/new-agent.component.ts | 197 ++++------------ src/app/agents/new-agent/new-agent.form.ts | 5 + .../_components/core-components.module.ts | 7 +- .../action-menu/action-menu.component.ts | 2 +- .../menus/base-menu/base-menu.component.ts | 8 + .../bulk-action-menu.component.ts | 2 + .../bulk-action-menu.constants.ts | 1 + .../row-action-menu.component.ts | 56 +++-- .../row-action-menu.constants.ts | 10 +- .../agent-binaries-table.component.html | 7 +- .../agent-binaries-table.component.ts | 13 ++ .../tables/base-table/base-table.component.ts | 2 + .../tables/ht-table/ht-table.component.html | 8 +- .../tables/ht-table/ht-table.models.ts | 1 + .../vouchers-table.component.html | 16 ++ .../vouchers-table.component.ts | 215 ++++++++++++++++++ .../vouchers-table.constants.ts | 4 + .../core/_datasources/vouchers.datasource.ts | 43 ++++ src/app/core/_models/voucher.model.ts | 7 + .../core/_services/export/export.service.ts | 51 +++-- src/styles/base/_base.scss | 38 ++++ src/styles/base/_form.scss | 24 +- src/styles/components/_table.scss | 10 +- src/styles/theme.scss | 4 +- 27 files changed, 617 insertions(+), 341 deletions(-) create mode 100644 src/app/agents/new-agent/new-agent.form.ts create mode 100644 src/app/core/_components/tables/vouchers-table/vouchers-table.component.html create mode 100644 src/app/core/_components/tables/vouchers-table/vouchers-table.component.ts create mode 100644 src/app/core/_components/tables/vouchers-table/vouchers-table.constants.ts create mode 100644 src/app/core/_datasources/vouchers.datasource.ts create mode 100644 src/app/core/_models/voucher.model.ts diff --git a/src/app/agents/agent.module.ts b/src/app/agents/agent.module.ts index 62724ab7..dafc998b 100644 --- a/src/app/agents/agent.module.ts +++ b/src/app/agents/agent.module.ts @@ -1,20 +1,25 @@ -import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; -import { FormsModule, ReactiveFormsModule } from "@angular/forms"; -import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; -import { DataTablesModule } from 'angular-datatables'; -import { CommonModule } from "@angular/common"; -import { RouterModule } from "@angular/router"; -import { NgModule } from "@angular/core"; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; -import { AgentStatusComponent } from "./agent-status/agent-status.component"; -import { ShowAgentsComponent } from "./show-agents/show-agents.component"; -import { EditAgentComponent } from './edit-agent/edit-agent.component'; -import { NewAgentComponent } from "./new-agent/new-agent.component"; -import { DirectivesModule } from "../shared/directives.module"; -import { ComponentsModule } from "../shared/components.module"; -import { AgentsRoutingModule } from "./agents-routing.module"; -import { PipesModule } from "../shared/pipes.module"; +import { AgentStatusComponent } from './agent-status/agent-status.component'; +import { AgentsRoutingModule } from './agents-routing.module'; +import { CommonModule } from '@angular/common'; +import { ComponentsModule } from '../shared/components.module'; import { CoreComponentsModule } from '../core/_components/core-components.module'; +import { DataTablesModule } from 'angular-datatables'; +import { DirectivesModule } from '../shared/directives.module'; +import { EditAgentComponent } from './edit-agent/edit-agent.component'; +import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; +import { MatButtonModule } from '@angular/material/button'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatIconModule } from '@angular/material/icon'; +import { MatInputModule } from '@angular/material/input'; +import { MatStepperModule } from '@angular/material/stepper'; +import { NewAgentComponent } from './new-agent/new-agent.component'; +import { NgModule } from '@angular/core'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { PipesModule } from '../shared/pipes.module'; +import { RouterModule } from '@angular/router'; +import { ShowAgentsComponent } from './show-agents/show-agents.component'; @NgModule({ declarations: [ @@ -25,9 +30,14 @@ import { CoreComponentsModule } from '../core/_components/core-components.module ], imports: [ CoreComponentsModule, + FontAwesomeModule, ReactiveFormsModule, + MatButtonModule, + MatFormFieldModule, + MatInputModule, + MatStepperModule, AgentsRoutingModule, - FontAwesomeModule, + MatIconModule, DataTablesModule, DirectivesModule, ComponentsModule, @@ -38,4 +48,4 @@ import { CoreComponentsModule } from '../core/_components/core-components.module NgbModule ] }) -export class AgentsModule { } +export class AgentsModule {} diff --git a/src/app/agents/agents-routing.module.ts b/src/app/agents/agents-routing.module.ts index 18f1e676..553f9e45 100644 --- a/src/app/agents/agents-routing.module.ts +++ b/src/app/agents/agents-routing.module.ts @@ -1,14 +1,14 @@ -import { CheckPerm } from '../core/_guards/permission.guard'; -import { IsAuth } from '../core/_guards/auth.guard'; -import { NgModule } from '@angular/core'; +import { MyRoute, RouteData } from '../core/_models/routes.model'; import { RouterModule, Routes } from '@angular/router'; import { AgentStatusComponent } from './agent-status/agent-status.component'; -import { PendingChangesGuard } from '../core/_guards/pendingchanges.guard'; -import { ShowAgentsComponent } from './show-agents/show-agents.component'; +import { CheckPerm } from '../core/_guards/permission.guard'; import { EditAgentComponent } from './edit-agent/edit-agent.component'; +import { IsAuth } from '../core/_guards/auth.guard'; import { NewAgentComponent } from './new-agent/new-agent.component'; -import { MyRoute, RouteData } from '../core/_models/routes.model'; +import { NgModule } from '@angular/core'; +import { PendingChangesGuard } from '../core/_guards/pendingchanges.guard'; +import { ShowAgentsComponent } from './show-agents/show-agents.component'; const routes: MyRoute[] = [ { diff --git a/src/app/agents/new-agent/new-agent.component.html b/src/app/agents/new-agent/new-agent.component.html index a260cfa4..a8e220e0 100644 --- a/src/app/agents/new-agent/new-agent.component.html +++ b/src/app/agents/new-agent/new-agent.component.html @@ -1,117 +1,64 @@ - - -
Instructions
-
    -
  1. In clients, download the file and run it
  2. -
  3. Generate voucher to link with agent (Used vouchers are automatically deleted)
  4. -
  5. - Using the URL, link the agent with the app: - {{ agentURL }} - -
  6. -
- - - - - - - - - - - - - - - - - - - - - - - -
IDVersionTypeOperating SystemsFilenameActions
{{ b.agentBinaryId }}{{ b.version }}{{ b.type }}{{ b.operatingSystems }}{{ b.filename }} - - - - -
-
- - - - -
-
-
+ + + + +
+

+ Download the agent binary and execute it on the + client server. You can download an agent binary by clicking on the + action menu (...) in the agent binaries table above. +

-
-
- -
+
+
-
- -
-
- -
- - - - - - - - - - - - - - - - -
KeyCreatedAction
{{ v.voucher }} - + + +
+ + Create a new Voucher + +
{{ v.time | uiDate }} - - - -
+ refresh + + + + +
+

+ Generate a voucher to register the agent. Note that + once the voucher is used it will be automatically deleted +

+
+
+ + +
+
+ + +
+

Register the agent using the above URL

+
+
+ +
+
+
- - diff --git a/src/app/agents/new-agent/new-agent.component.ts b/src/app/agents/new-agent/new-agent.component.ts index 463a733f..b6385cfa 100644 --- a/src/app/agents/new-agent/new-agent.component.ts +++ b/src/app/agents/new-agent/new-agent.component.ts @@ -1,181 +1,82 @@ -import { faTrash, faDownload, faInfoCircle, faCopy } from '@fortawesome/free-solid-svg-icons'; -import { Component, OnInit, OnDestroy, ViewChild, AfterViewInit } from '@angular/core'; -import { FormControl, FormGroup, Validators } from '@angular/forms'; -import { environment } from './../../../environments/environment'; -import { DataTableDirective } from 'angular-datatables'; -import Swal from 'sweetalert2/dist/sweetalert2.js'; -import { Subject } from 'rxjs'; +import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core'; +import { FormControl, FormGroup } from '@angular/forms'; -import { UIConfigService } from 'src/app/core/_services/shared/storage.service'; +import { AutoTitleService } from 'src/app/core/_services/shared/autotitle.service'; +import { Clipboard } from '@angular/cdk/clipboard'; import { ConfigService } from 'src/app/core/_services/shared/config.service'; -import { AlertService } from 'src/app/core/_services/shared/alert.service'; import { GlobalService } from 'src/app/core/_services/main.service'; -import { PageTitle } from 'src/app/core/_decorators/autotitle'; +import { MatSnackBar } from '@angular/material/snack-bar'; import { SERV } from '../../core/_services/main.config'; +import { Subscription } from 'rxjs'; +import { VoucherForm } from './new-agent.form'; +import { VouchersTableComponent } from 'src/app/core/_components/tables/vouchers-table/vouchers-table.component'; +import { environment } from './../../../environments/environment'; @Component({ selector: 'app-new-agent', templateUrl: './new-agent.component.html' }) -@PageTitle(['New Agent']) export class NewAgentComponent implements OnInit, OnDestroy { + form: FormGroup; + agentURL: string; + newVoucherSubscription: Subscription; - // Form attributtes - faInfoCircle=faInfoCircle; - faDownload=faDownload; - faTrash=faTrash; - faCopy=faCopy; - - @ViewChild(DataTableDirective, { static: false }) dtElement: DataTableDirective; - - dtTrigger: Subject = new Subject(); - dtTrigger1: Subject = new Subject(); - dtOptions: any = {}; - dtOptions1: any = {}; - - createForm: FormGroup - binaries: any = []; - vouchers: any = []; - - randomstring:any + @ViewChild('table') table: VouchersTableComponent; constructor( - private uiService: UIConfigService, - private alert: AlertService, - private gs: GlobalService, - private cs:ConfigService - ) { } - - private maxResults = environment.config.prodApiMaxResults; - - public agentdownloadURL: string; - public agentURL: string; - - ngOnInit(): void { - - const path = this.cs.getEndpoint().replace('/api/v2', ''); - - this.agentdownloadURL = path + environment.config.agentdownloadURL; - this.agentURL = path + '/api' +environment.config.agentURL; - - // Generate Voucher - this.randomstring = Math.random().toString(36).slice(-8); - - this.createForm = new FormGroup({ - 'voucher': new FormControl(''), + private titleService: AutoTitleService, + private clipboard: Clipboard, + private snackBar: MatSnackBar, + private cs: ConfigService, + private gs: GlobalService + ) { + this.titleService.set(['New Agent']); + this.form = new FormGroup({ + voucher: new FormControl('', { nonNullable: true }) }); - - const params = {'maxResults': this.maxResults}; - - const self = this; - this.dtOptions = { - dom: 'Bfrtip', - destroy: true, - scrollX: true, - searching: false, - paging: false, - info: false, - pageLength: 25, - processing: true, - lengthMenu: [ - [10, 25, 50, 100, 250, -1], - [10, 25, 50, 100, 250, 'All'] - ], - stateSave: true, - select: true, - buttons: { - dom: { - button: { - className: 'dt-button buttons-collection btn btn-sm-dt btn-outline-gray-600-dt', - } - }, - buttons: [ - { - text: '↻', - autoClose: true, - action: function (e, dt, node, config) { - self.onRefresh(); - } - } - ], - } - }; - - this.gs.getAll(SERV.AGENT_BINARY).subscribe((bin: any) => { - this.binaries = bin.values; - this.dtTrigger.next(void 0); - }); - - this.gs.getAll(SERV.VOUCHER,params).subscribe((vouchers: any) => { - this.vouchers = vouchers.values; - this.dtTrigger1.next(void 0); - }); - } ngOnDestroy(): void { - if (this.dtElement.dtInstance) { - this.dtElement.dtInstance.then((dtInstance: DataTables.Api) => { - // Destroy DataTable when the component is destroyed to avoid memory leaks - dtInstance.destroy(); - }); + if (this.newVoucherSubscription) { + this.newVoucherSubscription.unsubscribe(); } } - onRefresh(){ - this.rerender(); - this.ngOnInit(); + ngOnInit(): void { + const path = this.cs.getEndpoint().replace('/api/v2', ''); + this.agentURL = path + '/api' + environment.config.agentURL; + this.updateVoucher(); } - rerender(): void { - this.dtElement.dtInstance.then((dtInstance: DataTables.Api) => { - // Destroy the table first - dtInstance.destroy(); - // Call the dtTrigger to rerender again - setTimeout(() => { - this.dtTrigger['new'].next(); - // this.dtTrigger1['new'].next(); - }); - }); + updateVoucher(): void { + this.form.setValue({ voucher: this.generateVoucher() }); } - onDelete(id: number, name: string ){ - this.alert.deleteConfirmation(name,'Vouchers').then((confirmed) => { - if (confirmed) { - // Deletion - this.gs.delete(SERV.VOUCHER, id).subscribe(() => { - // Successful deletion - this.alert.okAlert(`Deleted Voucher ${name}`, ''); - this.onRefreshTable(); // Refresh the table - }); - } else { - // Handle cancellation - this.alert.okAlert(`Your Voucher ${name} is safe!`,''); - } - }); + generateVoucher(): string { + return Math.random().toString(36).slice(-8); } - onRefreshTable(){ - setTimeout(() => { - this.ngOnInit(); - this.rerender(); // rerender datatables - },2000); + copyAgentURL(): void { + this.clipboard.copy(this.agentURL); + this.snackBar.open( + 'The agent register URL is copied to the clipboard', + 'Close' + ); } - downloadClient(id) { - window.location.href= this.agentdownloadURL+id; + isValid(): boolean { + return this.form.valid && this.form.get('voucher').value !== ''; } - onSubmit(){ - if (this.createForm.valid) { - - this.gs.create(SERV.VOUCHER,this.createForm.value).subscribe(() => { - this.alert.okAlert('New Voucher created!',''); - this.ngOnInit(); - this.rerender(); // rerender datatables - } - ); + onSubmit() { + if (this.form.valid) { + this.newVoucherSubscription = this.gs + .create(SERV.VOUCHER, this.form.value) + .subscribe(() => { + this.updateVoucher(); + this.snackBar.open('New voucher successfully created!', 'Close'); + this.table.reload(); + }); } } - } diff --git a/src/app/agents/new-agent/new-agent.form.ts b/src/app/agents/new-agent/new-agent.form.ts new file mode 100644 index 00000000..10d23e00 --- /dev/null +++ b/src/app/agents/new-agent/new-agent.form.ts @@ -0,0 +1,5 @@ +import { FormControl } from '@angular/forms'; + +export interface VoucherForm { + voucher: FormControl; +} diff --git a/src/app/core/_components/core-components.module.ts b/src/app/core/_components/core-components.module.ts index b56f7904..5a4d1581 100644 --- a/src/app/core/_components/core-components.module.ts +++ b/src/app/core/_components/core-components.module.ts @@ -48,6 +48,7 @@ import { SuperHashlistsTableComponent } from './tables/super-hashlists-table/sup import { TableDialogComponent } from './tables/table-dialog/table-dialog.component'; import { TableTruncateComponent } from './tables/table-truncate/table-truncate.component'; import { UsersTableComponent } from './tables/users-table/users-table.component'; +import { VouchersTableComponent } from './tables/vouchers-table/vouchers-table.component'; @NgModule({ declarations: [ @@ -75,7 +76,8 @@ import { UsersTableComponent } from './tables/users-table/users-table.component' UsersTableComponent, AccessGroupsTableComponent, PermissionsTableComponent, - CracksTableComponent + CracksTableComponent, + VouchersTableComponent ], imports: [ ReactiveFormsModule, @@ -125,7 +127,8 @@ import { UsersTableComponent } from './tables/users-table/users-table.component' UsersTableComponent, AccessGroupsTableComponent, PermissionsTableComponent, - CracksTableComponent + CracksTableComponent, + VouchersTableComponent ], providers: [ { diff --git a/src/app/core/_components/menus/action-menu/action-menu.component.ts b/src/app/core/_components/menus/action-menu/action-menu.component.ts index 4a825902..771b700d 100644 --- a/src/app/core/_components/menus/action-menu/action-menu.component.ts +++ b/src/app/core/_components/menus/action-menu/action-menu.component.ts @@ -112,7 +112,7 @@ export class ActionMenuComponent implements OnInit, OnDestroy { const partial = this.currentUrl.slice(0, item.routerLink.length); if ( item.routerLink && - item.routerLink.every((value, index) => value === partial[index]) + partial.every((value, index) => value === item.routerLink[index]) ) { this.isActive = true; break; diff --git a/src/app/core/_components/menus/base-menu/base-menu.component.ts b/src/app/core/_components/menus/base-menu/base-menu.component.ts index d032716d..44f13dfa 100644 --- a/src/app/core/_components/menus/base-menu/base-menu.component.ts +++ b/src/app/core/_components/menus/base-menu/base-menu.component.ts @@ -76,6 +76,14 @@ export class BaseMenuComponent { return this.checkId('taskId') && 'taskName' in this.data; } + /** + * Check if the data row is of type "Voucher". + * @returns `true` if the data row is a voucher; otherwise, `false`. + */ + protected isVoucher(): boolean { + return this.checkId('regVoucherId') && 'voucher' in this.data; + } + /** * Check if the data row is of type "User". * @returns `true` if the data row is a user; otherwise, `false`. diff --git a/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.component.ts b/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.component.ts index 4cfc097d..1faade0e 100644 --- a/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.component.ts +++ b/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.component.ts @@ -64,6 +64,8 @@ export class BulkActionMenuComponent this.setDeleteMenu(BulkActionMenuLabel.DELETE_PREPROCESSORS); } else if (this.dataType === 'health-checks') { this.setDeleteMenu(BulkActionMenuLabel.DELETE_HEALTHCHECKS); + } else if (this.dataType === 'vouchers') { + this.setDeleteMenu(BulkActionMenuLabel.DELETE_VOUCHERS); } } diff --git a/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.constants.ts b/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.constants.ts index be9fe1cd..95638907 100644 --- a/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.constants.ts +++ b/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.constants.ts @@ -11,6 +11,7 @@ export const BulkActionMenuLabel = { DELETE_USERS: 'Delete Users', DELETE_ACCESSGROUPS: 'Delete Access Groups', DELETE_PERMISSIONS: 'Delete Permission Groups', + DELETE_VOUCHERS: 'Delete Vouchers', ACTIVATE_AGENTS: 'Activate Agents', ACTIVATE_USERS: 'Activate Users', DEACTIVATE_AGENTS: 'Deactivate Agents', diff --git a/src/app/core/_components/menus/row-action-menu/row-action-menu.component.ts b/src/app/core/_components/menus/row-action-menu/row-action-menu.component.ts index afd377bc..3442c658 100644 --- a/src/app/core/_components/menus/row-action-menu/row-action-menu.component.ts +++ b/src/app/core/_components/menus/row-action-menu/row-action-menu.component.ts @@ -33,11 +33,6 @@ export class RowActionMenuComponent RowActionMenuLabel.EDIT_SUPERHASHLIST, RowActionMenuLabel.DELETE_SUPERHASHLIST ); - } else if (this.isAgentBinary()) { - this.setEditDeleteMenuItems( - RowActionMenuLabel.EDIT_AGENTBINARY, - RowActionMenuLabel.DELETE_AGENTBINARY - ); } else if (this.isFile()) { this.setEditDeleteMenuItems( RowActionMenuLabel.EDIT_FILE, @@ -55,14 +50,18 @@ export class RowActionMenuComponent ); } else if (this.isPermission()) { this.setPermissionMenu(); + } else if (this.isHashtype()) { + this.setDeleteMenuItem(RowActionMenuLabel.DELETE_HASHTYPE); + } else if (this.isVoucher()) { + this.setDeleteMenuItem(RowActionMenuLabel.DELETE_VOUCHER); } else if (this.isUser()) { this.setUserMenu(); + } else if (this.isAgentBinary()) { + this.setAgentBinaryMenu(); } else if (this.isTask()) { this.setTaskMenu(); } else if (this.isHashlist()) { this.setHashlistMenu(); - } else if (this.isHashtype()) { - this.setHashtypeMenu(); } else if (this.isCrackerBinaryType()) { this.setCrackerBinaryTypeMenu(); } @@ -104,6 +103,27 @@ export class RowActionMenuComponent this.setActionMenuItems(1, [this.getDeleteMenuItem(deleteLabel)]); } + /** + * Sets context menu with delete action. + * @param label The label for the delete action. + */ + private setDeleteMenuItem(label: string): void { + this.setActionMenuItems(0, [this.getDeleteMenuItem(label)]); + } + + /** + * Sets the context menu items for an agent binary data row. + */ + private setAgentBinaryMenu(): void { + this.setActionMenuItems(0, [ + this.getEditMenuItem(RowActionMenuLabel.EDIT_AGENTBINARY), + this.getDownloadMenuItem(RowActionMenuLabel.DOWNLOAD_AGENT) + ]); + this.setActionMenuItems(1, [ + this.getDeleteMenuItem(RowActionMenuLabel.DELETE_AGENTBINARY) + ]); + } + /** * Sets the context menu items for an agent data row. */ @@ -210,15 +230,6 @@ export class RowActionMenuComponent ); } - /** - * Sets the context menu items for a hashtype data row. - */ - private setHashtypeMenu(): void { - this.setActionMenuItems(0, [ - this.getDeleteMenuItem(RowActionMenuLabel.DELETE_HASHTYPE) - ]); - } - /** * Creates an ActionMenuItem with delete action. * @param label The label for the menu item. @@ -233,6 +244,19 @@ export class RowActionMenuComponent }; } + /** + * Creates an ActionMenuItem with download action. + * @param label The label for the menu item. + * @returns The ActionMenuItem with download action. + */ + private getDownloadMenuItem(label: string): ActionMenuItem { + return { + label: label, + action: RowActionMenuAction.DOWNLOAD, + icon: RowActionMenuIcon.DOWNLOAD + }; + } + /** * Creates an ActionMenuItem with edit action. * @param label The label for the menu item. 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 1b7f17f1..99bad376 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 @@ -28,12 +28,14 @@ export const RowActionMenuLabel = { DELETE_USER: 'Delete User', DELETE_ACCESSGROUP: 'Delete Access Group', DELETE_PERMISSION: 'Delete Permission Group', + DELETE_VOUCHER: 'Delete Voucher', COPY_TO_TASK: 'Copy to Task', COPY_TO_PRETASK: 'Copy to Pretask', ARCHIVE_TASK: 'Archive Task', IMPORT_HASHLIST: 'Import Hashlist', EXPORT_HASHLIST: 'Export Hashlist', - NEW_VERSION: 'Add Version' + NEW_VERSION: 'Add Version', + DOWNLOAD_AGENT: 'Download Agent Binary' }; export const RowActionMenuAction = { @@ -47,7 +49,8 @@ export const RowActionMenuAction = { IMPORT: 'import', EXPORT: 'export', ACTIVATE: 'activate', - DEACTIVATE: 'deactivate' + DEACTIVATE: 'deactivate', + DOWNLOAD: 'download' }; export const RowActionMenuIcon = { @@ -59,5 +62,6 @@ export const RowActionMenuIcon = { COPY: 'content_copy', ARCHIVE: 'archive', ACTIVATE: 'check_circle', - DEACTIVATE: 'remove_circle' + DEACTIVATE: 'remove_circle', + DOWNLOAD: 'file_download' }; diff --git a/src/app/core/_components/tables/agent-binaries-table/agent-binaries-table.component.html b/src/app/core/_components/tables/agent-binaries-table/agent-binaries-table.component.html index e6452f4c..8efabbbf 100644 --- a/src/app/core/_components/tables/agent-binaries-table/agent-binaries-table.component.html +++ b/src/app/core/_components/tables/agent-binaries-table/agent-binaries-table.component.html @@ -4,9 +4,10 @@ dataType="agent-binaries" [dataSource]="dataSource" [tableColumns]="tableColumns" - [isSelectable]="true" - [hasRowAction]="true" - [isFilterable]="true" + [isSelectable]="isSelectable" + [hasRowAction]="hasRowAction" + [isFilterable]="isFilterable" + [hasBulkActions]="hasBulkActions" [filterFn]="filter" [isPageable]="true" (bulkActionClicked)="bulkActionClicked($event)" diff --git a/src/app/core/_components/tables/agent-binaries-table/agent-binaries-table.component.ts b/src/app/core/_components/tables/agent-binaries-table/agent-binaries-table.component.ts index 197de3d3..dcc33405 100644 --- a/src/app/core/_components/tables/agent-binaries-table/agent-binaries-table.component.ts +++ b/src/app/core/_components/tables/agent-binaries-table/agent-binaries-table.component.ts @@ -14,6 +14,7 @@ import { HTTableColumn } from '../ht-table/ht-table.models'; import { RowActionMenuAction } from '../../menus/row-action-menu/row-action-menu.constants'; import { SERV } from 'src/app/core/_services/main.config'; import { TableDialogComponent } from '../table-dialog/table-dialog.component'; +import { environment } from 'src/environments/environment'; @Component({ selector: 'agent-binaries-table', @@ -26,6 +27,8 @@ export class AgentBinariesTableComponent tableColumns: HTTableColumn[] = []; dataSource: AgentBinariesDataSource; + agentdownloadURL: string; + ngOnInit(): void { this.tableColumns = this.getColumns(); this.dataSource = new AgentBinariesDataSource( @@ -35,6 +38,9 @@ export class AgentBinariesTableComponent ); this.dataSource.setColumns(this.tableColumns); this.dataSource.loadAll(); + + const path = this.cs.getEndpoint().replace('/api/v2', ''); + this.agentdownloadURL = path + environment.config.agentdownloadURL; } ngOnDestroy(): void { @@ -162,6 +168,9 @@ export class AgentBinariesTableComponent case RowActionMenuAction.EDIT: this.rowActionEdit(event.data); break; + case RowActionMenuAction.DOWNLOAD: + this.rowActionDownload(event.data); + break; } } @@ -236,4 +245,8 @@ export class AgentBinariesTableComponent 'edit' ]); } + + private rowActionDownload(agentBinary: AgentBinary): void { + window.location.href = `${this.agentdownloadURL}${agentBinary._id}`; + } } 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 f6b598f0..98acb6b0 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 @@ -14,6 +14,7 @@ import { import { AccessGroup } from 'src/app/core/_models/access-group.model'; import { Cacheable } from 'src/app/core/_decorators/cacheable'; +import { ConfigService } from 'src/app/core/_services/shared/config.service'; import { ExportService } from 'src/app/core/_services/export/export.service'; import { GlobalService } from 'src/app/core/_services/main.service'; import { HTTableComponent } from '../ht-table/ht-table.component'; @@ -50,6 +51,7 @@ export class BaseTableComponent { constructor( protected gs: GlobalService, + protected cs: ConfigService, protected renderer: Renderer2, protected router: Router, protected settingsService: LocalStorageService, 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 0079ea9c..1e354eb9 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 @@ -44,7 +44,7 @@
-
+
+ The table has no data. +
diff --git a/src/app/core/_components/tables/ht-table/ht-table.models.ts b/src/app/core/_components/tables/ht-table/ht-table.models.ts index e07348d1..b9bb62a3 100644 --- a/src/app/core/_components/tables/ht-table/ht-table.models.ts +++ b/src/app/core/_components/tables/ht-table/ht-table.models.ts @@ -16,6 +16,7 @@ export type DataType = | 'logs' | 'permissions' | 'cracks' + | 'vouchers' | 'superhashlists'; export interface HTTableIcon { diff --git a/src/app/core/_components/tables/vouchers-table/vouchers-table.component.html b/src/app/core/_components/tables/vouchers-table/vouchers-table.component.html new file mode 100644 index 00000000..17c55f13 --- /dev/null +++ b/src/app/core/_components/tables/vouchers-table/vouchers-table.component.html @@ -0,0 +1,16 @@ + diff --git a/src/app/core/_components/tables/vouchers-table/vouchers-table.component.ts b/src/app/core/_components/tables/vouchers-table/vouchers-table.component.ts new file mode 100644 index 00000000..7e42d1a1 --- /dev/null +++ b/src/app/core/_components/tables/vouchers-table/vouchers-table.component.ts @@ -0,0 +1,215 @@ +/* eslint-disable @angular-eslint/component-selector */ +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { catchError, forkJoin } from 'rxjs'; + +import { ActionMenuEvent } from '../../menus/action-menu/action-menu.model'; +import { BaseTableComponent } from '../base-table/base-table.component'; +import { BulkActionMenuAction } from '../../menus/bulk-action-menu/bulk-action-menu.constants'; +import { DialogData } from '../table-dialog/table-dialog.model'; +import { ExportMenuAction } from '../../menus/export-menu/export-menu.constants'; +import { HTTableColumn } from '../ht-table/ht-table.models'; +import { RowActionMenuAction } from '../../menus/row-action-menu/row-action-menu.constants'; +import { SERV } from 'src/app/core/_services/main.config'; +import { TableDialogComponent } from '../table-dialog/table-dialog.component'; +import { Voucher } from 'src/app/core/_models/voucher.model'; +import { VouchersDataSource } from 'src/app/core/_datasources/vouchers.datasource'; +import { VouchersTableColumnLabel } from './vouchers-table.constants'; +import { formatUnixTimestamp } from 'src/app/shared/utils/datetime'; + +@Component({ + selector: 'vouchers-table', + templateUrl: './vouchers-table.component.html' +}) +export class VouchersTableComponent + extends BaseTableComponent + implements OnInit, OnDestroy +{ + tableColumns: HTTableColumn[] = []; + dataSource: VouchersDataSource; + + ngOnInit(): void { + this.tableColumns = this.getColumns(); + this.dataSource = new VouchersDataSource(this.cdr, this.gs, this.uiService); + this.dataSource.setColumns(this.tableColumns); + this.dataSource.loadAll(); + } + + ngOnDestroy(): void { + for (const sub of this.subscriptions) { + sub.unsubscribe(); + } + } + + filter(item: Voucher, filterValue: string): boolean { + if (item.voucher.toLowerCase().includes(filterValue)) { + return true; + } + + return false; + } + + getColumns(): HTTableColumn[] { + const tableColumns = [ + { + name: VouchersTableColumnLabel.KEY, + dataKey: 'voucher', + isSortable: true, + export: async (voucher: Voucher) => voucher.voucher + }, + { + name: VouchersTableColumnLabel.CREATED, + dataKey: 'time', + isSortable: true, + render: (voucher: Voucher) => + formatUnixTimestamp(voucher.time, this.dateFormat), + export: async (voucher: Voucher) => + formatUnixTimestamp(voucher.time, this.dateFormat) + } + ]; + + return tableColumns; + } + + openDialog(data: DialogData) { + const dialogRef = this.dialog.open(TableDialogComponent, { + data: data, + width: '450px' + }); + + this.subscriptions.push( + dialogRef.afterClosed().subscribe((result) => { + if (result && result.action) { + switch (result.action) { + case RowActionMenuAction.DELETE: + this.rowActionDelete(result.data); + break; + case BulkActionMenuAction.DELETE: + this.bulkActionDelete(result.data); + break; + } + } + }) + ); + } + + // --- Action functions --- + + exportActionClicked(event: ActionMenuEvent): void { + switch (event.menuItem.action) { + case ExportMenuAction.EXCEL: + this.exportService.toExcel( + 'hashtopolis-vouchers', + this.tableColumns, + event.data + ); + break; + case ExportMenuAction.CSV: + this.exportService.toCsv( + 'hashtopolis-vouchers', + this.tableColumns, + event.data + ); + break; + case ExportMenuAction.COPY: + this.exportService + .toClipboard(this.tableColumns, event.data) + .then(() => { + this.snackBar.open( + 'The selected rows are copied to the clipboard', + 'Close' + ); + }); + break; + } + } + + rowActionClicked(event: ActionMenuEvent): void { + switch (event.menuItem.action) { + case RowActionMenuAction.DELETE: + this.openDialog({ + rows: [event.data], + title: `Deleting voucher ${event.data.voucher} ...`, + icon: 'warning', + body: `Are you sure you want to delete it? Note that this action cannot be undone.`, + warn: true, + action: event.menuItem.action + }); + break; + case RowActionMenuAction.EDIT: + this.rowActionEdit(event.data); + break; + } + } + + bulkActionClicked(event: ActionMenuEvent): void { + switch (event.menuItem.action) { + case BulkActionMenuAction.DELETE: + this.openDialog({ + rows: event.data, + title: `Deleting ${event.data.length} vouchers ...`, + icon: 'warning', + body: `Are you sure you want to delete the above vouchers? Note that this action cannot be undone.`, + warn: true, + listAttribute: 'voucher', + action: event.menuItem.action + }); + break; + } + } + + /** + * @todo Implement error handling. + */ + private bulkActionDelete(vouchers: Voucher[]): void { + const requests = vouchers.map((voucher: Voucher) => { + return this.gs.delete(SERV.VOUCHER, voucher._id); + }); + + this.subscriptions.push( + forkJoin(requests) + .pipe( + catchError((error) => { + console.error('Error during deletion:', error); + return []; + }) + ) + .subscribe((results) => { + this.snackBar.open( + `Successfully deleted ${results.length} vouchers!`, + 'Close' + ); + this.reload(); + }) + ); + } + + /** + * @todo Implement error handling. + */ + private rowActionDelete(vouchers: Voucher[]): void { + this.subscriptions.push( + this.gs + .delete(SERV.VOUCHER, vouchers[0]._id) + .pipe( + catchError((error) => { + console.error('Error during deletion:', error); + return []; + }) + ) + .subscribe(() => { + this.snackBar.open('Successfully deleted voucher!', 'Close'); + this.reload(); + }) + ); + } + + private rowActionEdit(voucher: Voucher): void { + this.router.navigate([ + '/config', + 'engine', + 'vouchers', + voucher._id, + 'edit' + ]); + } +} diff --git a/src/app/core/_components/tables/vouchers-table/vouchers-table.constants.ts b/src/app/core/_components/tables/vouchers-table/vouchers-table.constants.ts new file mode 100644 index 00000000..0f6f88c4 --- /dev/null +++ b/src/app/core/_components/tables/vouchers-table/vouchers-table.constants.ts @@ -0,0 +1,4 @@ +export const VouchersTableColumnLabel = { + KEY: 'Key', + CREATED: 'Created' +}; diff --git a/src/app/core/_datasources/vouchers.datasource.ts b/src/app/core/_datasources/vouchers.datasource.ts new file mode 100644 index 00000000..7f2d40a8 --- /dev/null +++ b/src/app/core/_datasources/vouchers.datasource.ts @@ -0,0 +1,43 @@ +import { catchError, finalize, of } from 'rxjs'; + +import { BaseDataSource } from './base.datasource'; +import { ListResponseWrapper } from '../_models/response.model'; +import { SERV } from '../_services/main.config'; +import { Voucher } from '../_models/voucher.model'; + +export class VouchersDataSource extends BaseDataSource { + loadAll(): void { + this.loading = true; + + const startAt = this.currentPage * this.pageSize; + const params = { + maxResults: this.pageSize, + startAt: startAt + }; + + const vouchers$ = this.service.getAll(SERV.VOUCHER, params); + + this.subscriptions.push( + vouchers$ + .pipe( + catchError(() => of([])), + finalize(() => (this.loading = false)) + ) + .subscribe((response: ListResponseWrapper) => { + const vouchers: Voucher[] = response.values; + + this.setPaginationConfig( + this.pageSize, + this.currentPage, + response.total + ); + this.setData(vouchers); + }) + ); + } + + reload(): void { + this.clearSelection(); + this.loadAll(); + } +} diff --git a/src/app/core/_models/voucher.model.ts b/src/app/core/_models/voucher.model.ts new file mode 100644 index 00000000..f462752d --- /dev/null +++ b/src/app/core/_models/voucher.model.ts @@ -0,0 +1,7 @@ +export interface Voucher { + _id: number; + _self: string; + regVoucherId: number; + time: number; + voucher: string; +} diff --git a/src/app/core/_services/export/export.service.ts b/src/app/core/_services/export/export.service.ts index b8f73bb3..277e90fd 100644 --- a/src/app/core/_services/export/export.service.ts +++ b/src/app/core/_services/export/export.service.ts @@ -1,32 +1,40 @@ +import { Clipboard } from '@angular/cdk/clipboard'; +import { ExcelColumn } from './export.model'; +import { ExportUtil } from './export.util'; +import { HTTableColumn } from '../../_components/tables/ht-table/ht-table.models'; import { Injectable } from '@angular/core'; import { Workbook } from 'exceljs'; import { unparse } from 'papaparse'; -import { ExportUtil } from './export.util'; -import { ExcelColumn } from './export.model'; -import { Clipboard } from '@angular/cdk/clipboard'; -import { HTTableColumn } from '../../_components/tables/ht-table/ht-table.models'; /** * Service for exporting data to Excel, CSV formats and to the clipboard. */ @Injectable({ - providedIn: 'root', + providedIn: 'root' }) export class ExportService { - constructor(private exportUtil: ExportUtil, private clipboard: Clipboard) { } + constructor( + private exportUtil: ExportUtil, + private clipboard: Clipboard + ) {} /** * Exports data to an Excel file and triggers a download. - * + * * @param fileName - The name of the Excel file without ext. * @param tableColumns - Columns configuration for the export. * @param rawData - The data to export. */ - async toExcel(fileName: string, tableColumns: HTTableColumn[], rawData: T[]): Promise { + async toExcel( + fileName: string, + tableColumns: HTTableColumn[], + rawData: T[] + ): Promise { try { const workbook = new Workbook(); const worksheet = workbook.addWorksheet('Sheet 1'); - const columns: ExcelColumn[] = this.exportUtil.toExcelColumns(tableColumns); + const columns: ExcelColumn[] = + this.exportUtil.toExcelColumns(tableColumns); const data = await this.exportUtil.toExcelRows(tableColumns, rawData); @@ -44,12 +52,16 @@ export class ExportService { /** * Exports data to a CSV file and triggers a download. - * + * * @param fileName - The name of the CSV file without ext. * @param tableColumns - Columns configuration for the export. * @param rawData - The data to export. */ - async toCsv(fileName: string, tableColumns: HTTableColumn[], rawData: T[]): Promise { + async toCsv( + fileName: string, + tableColumns: HTTableColumn[], + rawData: T[] + ): Promise { try { const columns: string[] = this.exportUtil.toCsvColumns(tableColumns); const data = await this.exportUtil.toCsvRows(tableColumns, rawData); @@ -65,16 +77,21 @@ export class ExportService { /** * Copies data to the clipboard. - * + * * @param tableColumns - Columns configuration for the export. * @param rawData - The data to export. */ - async toClipboard(tableColumns: HTTableColumn[], rawData: T[]): Promise { + async toClipboard( + tableColumns: HTTableColumn[], + rawData: T[] + ): Promise { try { const columns: string[] = this.exportUtil.toCsvColumns(tableColumns); const data = await this.exportUtil.toCsvRows(tableColumns, rawData); - const textToCopy = [columns, ...data].map((row: string[]) => row.join('\t')).join('\n'); + const textToCopy = [columns, ...data] + .map((row: string[]) => row.join('\t')) + .join('\n'); this.clipboard.copy(textToCopy); } catch (error) { @@ -83,7 +100,11 @@ export class ExportService { } private saveExcelFile(data: ArrayBuffer, fileName: string): void { - this.exportUtil.download(data, `${fileName}.xlsx`, 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); + this.exportUtil.download( + data, + `${fileName}.xlsx`, + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' + ); } private saveCsvFile(data: ArrayBuffer, fileName: string): void { diff --git a/src/styles/base/_base.scss b/src/styles/base/_base.scss index c741488d..3f021164 100644 --- a/src/styles/base/_base.scss +++ b/src/styles/base/_base.scss @@ -71,6 +71,32 @@ a { color: $primary-300 !important; } +.highlight-box { + border-left: 10px solid $accent-300; + background-color: $accent-100; + color: #333; + padding: 0.5em 1em; + margin: 2em 0; + border-radius: 10px; + + strong { + color: $accent-700; + } + + h3 { + margin-top: 0; + } + + ol { + margin-left: 0; + padding-left: 1em; + } + + button { + margin-left: 0; + } +} + .page-title-wrapper { display: flex; justify-content: space-between; @@ -116,9 +142,21 @@ h2 { } .dark-theme { + .mat-stepper-horizontal { + background-color: #212121; + } hr { border-width: 1px; border-style: solid; border-color: var(--mat-table-row-item-outline-color, rgba(0, 0, 0, 0.12)); } + .highlight-box { + border-left-color: $accent-800; + background-color: $accent-900; + color: #fff; + + strong { + color: $accent-200; + } + } } diff --git a/src/styles/base/_form.scss b/src/styles/base/_form.scss index 38adcea9..4b617f95 100644 --- a/src/styles/base/_form.scss +++ b/src/styles/base/_form.scss @@ -15,28 +15,27 @@ } &.m-field { - width: 40% + width: 40%; } &.l-field { - width: 60% + width: 60%; } &.xl-field { - width: 100% + width: 100%; } - .mat-mdc-form-field { - width: 100%; + .mat-form-field-infix { + height: 16px; } - .mat-mdc-text-field-wrapper.mdc-text-field--outlined .mat-mdc-form-field-infix { - padding-top: 8px; - padding-bottom: 8px; + .mat-mdc-form-field { + width: 100%; } - .mat-mdc-form-field-infix { - min-height: 36px; + .mat-icon { + color: $primary-100; } } @@ -55,7 +54,7 @@ } &.l-field { - width: 100% + width: 100%; } } } @@ -75,8 +74,7 @@ } &.l-field { - width: 100% + width: 100%; } } } - diff --git a/src/styles/components/_table.scss b/src/styles/components/_table.scss index 7e0a98de..d76c437d 100644 --- a/src/styles/components/_table.scss +++ b/src/styles/components/_table.scss @@ -226,6 +226,11 @@ input.dt-button { } table.hashtopolis-table { + .no-data { + text-align: center; + color: $primary-900; + padding: 1em; + } .mat-mdc-header-cell { background-color: $primary-800; color: #fff; @@ -337,8 +342,11 @@ div.table-actions { color: $accent-600; } } - } + .no-data { + color: $primary-100; + } + } .mat-mdc-paginator { background-color: var(--mat-toolbar-container-background-color); } diff --git a/src/styles/theme.scss b/src/styles/theme.scss index c0280ecc..58833185 100644 --- a/src/styles/theme.scss +++ b/src/styles/theme.scss @@ -15,7 +15,7 @@ $hashtopolis-light-theme: mat.define-light-theme( warn: $hashtopolis-warn-palette ), typography: mat.define-typography-config(), - density: 0 + density: -1 ) ); @@ -27,7 +27,7 @@ $hashtopolis-dark-theme: mat.define-dark-theme( warn: $hashtopolis-warn-palette ), typography: mat.define-typography-config(), - density: 0 + density: -1 ) ); From 86488710cf90f395a3453af8466705da3bf6221c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Wed, 22 Nov 2023 18:17:16 +0000 Subject: [PATCH 301/419] New Hashlists and improvemtns input abstract --- .../new-superhashlist.component.html | 7 +- .../new-superhashlist.component.ts | 11 - .../new-supertasks.component.html | 8 +- .../new-supertasks.component.ts | 18 +- src/app/core/_constants/hashlist.config.ts | 17 + .../core/_services/files/files_tus.service.ts | 4 +- src/app/core/_services/main.service.ts | 13 +- .../files-edit/files-edit.component.html | 59 --- .../files/files-edit/files-edit.component.ts | 147 ------ .../files/new-files/new-files.component.html | 79 +--- .../files/new-files/new-files.component.ts | 234 ++++++---- src/app/hashlists/hashlists.module.ts | 7 +- .../new-hashlist/new-hashlist.component.html | 322 +++++-------- .../new-hashlist/new-hashlist.component.ts | 435 +++++++++--------- src/app/shared/components.module.ts | 8 + src/app/shared/grid-containers/grid-main.ts | 26 +- .../hashtype-detector.component.html | 62 +-- .../hashtype-detector.component.ts | 67 ++- .../shared/input/color/color.component.html | 2 +- .../multiselect/multiselect.component.html | 27 +- .../multiselect/multiselect.component.ts | 299 ++++++------ .../shared/input/select/select.component.html | 14 +- .../shared/input/select/select.component.ts | 62 +-- .../input/text-area/text-area.component.ts | 6 +- src/app/shared/input/text/text.component.html | 1 + src/app/shared/input/text/text.component.ts | 3 +- src/app/shared/utils/forms.ts | 35 -- src/styles/components/_card.scss | 4 + src/styles/components/_form.scss | 22 +- src/styles/pages/_files.scss | 19 + 30 files changed, 850 insertions(+), 1168 deletions(-) delete mode 100644 src/app/files/files-edit/files-edit.component.html delete mode 100644 src/app/files/files-edit/files-edit.component.ts diff --git a/src/app/core/_components/forms/custom-forms/superhashlist/new-superhashlist/new-superhashlist.component.html b/src/app/core/_components/forms/custom-forms/superhashlist/new-superhashlist/new-superhashlist.component.html index 09a2ed3e..ba46fb48 100644 --- a/src/app/core/_components/forms/custom-forms/superhashlist/new-superhashlist/new-superhashlist.component.html +++ b/src/app/core/_components/forms/custom-forms/superhashlist/new-superhashlist/new-superhashlist.component.html @@ -9,10 +9,11 @@ diff --git a/src/app/core/_components/forms/custom-forms/superhashlist/new-superhashlist/new-superhashlist.component.ts b/src/app/core/_components/forms/custom-forms/superhashlist/new-superhashlist/new-superhashlist.component.ts index 30600b08..f284b85e 100644 --- a/src/app/core/_components/forms/custom-forms/superhashlist/new-superhashlist/new-superhashlist.component.ts +++ b/src/app/core/_components/forms/custom-forms/superhashlist/new-superhashlist/new-superhashlist.component.ts @@ -132,15 +132,4 @@ export class NewSuperhashlistComponent implements OnInit, OnDestroy { this.unsubscribeService.add(createSubscription$); } } - - /** - * Handles the selection of items in the UI. - * Extracts the IDs from the selected items and sets them in the form. - * - * @param selectedItems - The items that are selected. - */ - handleSelectedItems(selectedItems: SelectField[]): void { - const extractedIds = extractIds(selectedItems, '_id'); - this.form.get('hashlistIds').setValue(extractedIds); - } } diff --git a/src/app/core/_components/forms/custom-forms/task/new-supertasks/new-supertasks.component.html b/src/app/core/_components/forms/custom-forms/task/new-supertasks/new-supertasks.component.html index 7ffcdd5e..0d1982bb 100644 --- a/src/app/core/_components/forms/custom-forms/task/new-supertasks/new-supertasks.component.html +++ b/src/app/core/_components/forms/custom-forms/task/new-supertasks/new-supertasks.component.html @@ -9,10 +9,11 @@ @@ -21,4 +22,5 @@ + diff --git a/src/app/core/_components/forms/custom-forms/task/new-supertasks/new-supertasks.component.ts b/src/app/core/_components/forms/custom-forms/task/new-supertasks/new-supertasks.component.ts index 0836e998..d39641b7 100644 --- a/src/app/core/_components/forms/custom-forms/task/new-supertasks/new-supertasks.component.ts +++ b/src/app/core/_components/forms/custom-forms/task/new-supertasks/new-supertasks.component.ts @@ -18,10 +18,7 @@ import { Router } from '@angular/router'; import { AlertService } from 'src/app/core/_services/shared/alert.service'; import { GlobalService } from 'src/app/core/_services/main.service'; import { environment } from '../../../../../../../environments/environment'; -import { - extractIds, - transformSelectOptions -} from '../../../../../../shared/utils/forms'; +import { transformSelectOptions } from '../../../../../../shared/utils/forms'; import { AutoTitleService } from 'src/app/core/_services/shared/autotitle.service'; import { UnsubscribeService } from 'src/app/core/_services/unsubscribe.service'; import { ListResponseWrapper } from 'src/app/core/_models/response.model'; @@ -86,7 +83,7 @@ export class NewSupertasksComponent implements OnInit, OnDestroy { buildForm(): void { this.form = this.formBuilder.group({ supertaskName: ['', Validators.required], - pretasks: [null, Validators.required] + pretasks: ['', Validators.required] }); } @@ -131,15 +128,4 @@ export class NewSupertasksComponent implements OnInit, OnDestroy { this.unsubscribeService.add(createSubscription$); } } - - /** - * Handles the selection of items in the UI. - * Extracts the IDs from the selected items and sets them in the form. - * - * @param selectedItems - The items that are selected. - */ - handleSelectedItems(selectedItems: SelectField[]): void { - const extractedIds = extractIds(selectedItems, '_id'); - this.form.get('hashlistIds').setValue(extractedIds); - } } diff --git a/src/app/core/_constants/hashlist.config.ts b/src/app/core/_constants/hashlist.config.ts index e1ac4c25..a140f0bf 100644 --- a/src/app/core/_constants/hashlist.config.ts +++ b/src/app/core/_constants/hashlist.config.ts @@ -11,3 +11,20 @@ export const HashListFormatLabel = { [HashListFormat.BINARY]: 'Binary', [HashListFormat.SUPERHASHLIST]: 'Superhashlist' }; + +export const hashlistFormat = [ + { _id: 0, name: 'Text' }, + { _id: 1, name: 'HCCAPX / PMKID' }, + { _id: 2, name: 'Binary file (single hash)' } +]; + +export const hashcatbrainFormat = [ + { _id: 1, name: 'Send hashed passwords' }, + { _id: 2, name: 'Send attack positions' }, + { _id: 3, name: 'Send hashed passwords and attack positions' } +]; + +export const hashSource = [ + { _id: 'paste', name: 'Expand Text Area' }, + { _id: 'import', name: 'Upload Input' } +]; diff --git a/src/app/core/_services/files/files_tus.service.ts b/src/app/core/_services/files/files_tus.service.ts index 946ecfa0..56ac9f6a 100644 --- a/src/app/core/_services/files/files_tus.service.ts +++ b/src/app/core/_services/files/files_tus.service.ts @@ -62,7 +62,9 @@ export class UploadTUSService { /** * Retrieves user data from local storage using the STORAGE_KEY and sets the authentication token. */ - const userData: UserData = this.storage.getItem(UploadTUSService.STORAGE_KEY); + const userData: UserData = this.storage.getItem( + UploadTUSService.STORAGE_KEY + ); this._token = userData._token; } diff --git a/src/app/core/_services/main.service.ts b/src/app/core/_services/main.service.ts index 188d1835..32107481 100644 --- a/src/app/core/_services/main.service.ts +++ b/src/app/core/_services/main.service.ts @@ -57,11 +57,17 @@ export class GlobalService { let queryParams: Params = {}; let fixedMaxResults: boolean; + // Check if routerParams exist if (routerParams) { + // Check if 'maxResults' is not present in routerParams if (!('maxResults' in routerParams)) { fixedMaxResults = true; } + // Set queryParams using setParameter utility function queryParams = setParameter(routerParams, this.maxResults); + } else { + fixedMaxResults = true; + queryParams = setParameter({}, this.maxResults); } return this.http @@ -71,10 +77,12 @@ export class GlobalService { const total = response.total || 0; const maxResults = this.maxResults; + // Check if total is greater than maxResults and fixedMaxResults is true if (total > maxResults && fixedMaxResults) { const requests: Observable[] = []; const numRequests = Math.ceil(total / maxResults); + // Create multiple requests based on the total number of items for (let i = 0; i < numRequests; i++) { const startsAt = i * maxResults; const partialParams = setParameter( @@ -88,10 +96,11 @@ export class GlobalService { ); } + // Use forkJoin to combine the original response with additional responses return forkJoin([of(response), ...requests]).pipe( catchError((error) => { console.error('Error in forkJoin:', error); - return of(response); + return of(response); // Return the original response in case of an error }) ); } else { @@ -100,7 +109,7 @@ export class GlobalService { }), catchError((error) => { console.error('Error in switchMap:', error); - return of({ values: [] }); + return of({ values: [] }); // Handle errors in switchMap and return a default response }) ); } diff --git a/src/app/files/files-edit/files-edit.component.html b/src/app/files/files-edit/files-edit.component.html deleted file mode 100644 index a7d8aef0..00000000 --- a/src/app/files/files-edit/files-edit.component.html +++ /dev/null @@ -1,59 +0,0 @@ - - - -
-
- - - -
- - - - - - - - - - -
-
- -
- - - - - Salted hashes, separator: - - - - - - Salt is in hex (only when salted hashes) - - -
-
-
Hashcat Brain Enabled
  -
-
- - -
- -
- Note: When brain is enabled it'll create a network server. If it is used wrongly it could cause bottlenecks or collapse the network server. -
- - -
-   + + + + + + + + + +

+ +
Hashcat Brain Enabled
+ + + + + + Note: When brain is enabled, it'll create a network server. If used wrongly, + it could cause bottlenecks or collapse the network server. You can disable + this setting in Config > Server > General Settings. + + +
-
-
- -
- Paste
- Upload
- -
- -
- - - - - - - - - - - - - - -
HashtypeDescriptionExample
{{ nn.id }}{{ nn.description }}{{ nn.example }}
+ + + + + + {{ column | titlecase }} + {{ row[column] }} + + + + + + + + +
+

Error. No hash type found.

- - + + + + + diff --git a/src/app/shared/hashtype-detector/hashtype-detector.component.ts b/src/app/shared/hashtype-detector/hashtype-detector.component.ts index 2fe0f4b7..d4321155 100644 --- a/src/app/shared/hashtype-detector/hashtype-detector.component.ts +++ b/src/app/shared/hashtype-detector/hashtype-detector.component.ts @@ -1,37 +1,60 @@ +import { + ApplicationRef, + ChangeDetectorRef, + Component, + ViewEncapsulation +} from '@angular/core'; +import { MatTableDataSource } from '@angular/material/table'; +import { MatDialogRef } from '@angular/material/dialog'; import { findHashType } from 'hashtype-detector/dist/lib/es6/index'; -import { Component } from '@angular/core'; +import { BreakpointObserver } from '@angular/cdk/layout'; @Component({ - selector: 'app-hashtype-detector', - templateUrl: './hashtype-detector.component.html' + selector: 'hashtype-detector', + templateUrl: './hashtype-detector.component.html', + encapsulation: ViewEncapsulation.None }) -export class HashtypeDetectorComponent { - - findHashType=findHashType; - +export class HashtypeDetectorComponent { type: any; + displayedColumns: string[] = ['id', 'description', 'example']; + dataSource: MatTableDataSource; - onTest(val){ + constructor( + public dialogRef: MatDialogRef, + private appRef: ApplicationRef, + private breakpointObserver: BreakpointObserver + ) {} - let res = findHashType(val); + onClose(): void { + this.dialogRef.close(); + } - if(res === false){ + onSearch(val: string): void { + const result = findHashType(val); + if (result === false || result === 'No hashes found.') { + this.type = false; + } else { + this.type = result; + this.dataSource.data = this.flattenData(); + this.appRef.tick(); + } + } - res = [{ - "regex": "", - "rAttack": " ", - "options": [ - { - "id": 404, - "description": "Error. No hash type found.", - "example": "" - } - ] - }] + flattenData(): any[] { + const flattenedData: any[] = []; + if (this.type && this.type !== false) { + for (const n of this.type) { + for (const nn of n.options) { + flattenedData.push(nn); + } + } } - this.type = res; + return flattenedData; } + ngOnInit() { + this.dataSource = new MatTableDataSource([]); + } } diff --git a/src/app/shared/input/color/color.component.html b/src/app/shared/input/color/color.component.html index 50dfc5a8..b098008a 100644 --- a/src/app/shared/input/color/color.component.html +++ b/src/app/shared/input/color/color.component.html @@ -1,4 +1,4 @@ -
+
{{ title }} - + {{label}} - + {{ item._id }} ({{ item.name }}) - + {{ item.name }} diff --git a/src/app/shared/input/select/select.component.ts b/src/app/shared/input/select/select.component.ts index a592aac7..7ccb73b8 100644 --- a/src/app/shared/input/select/select.component.ts +++ b/src/app/shared/input/select/select.component.ts @@ -1,5 +1,6 @@ -import { Component, Input } from '@angular/core'; +import { Component, Input, forwardRef } from '@angular/core'; import { AbstractInputComponent } from '../abstract-input'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; /** * Custom Select Component. @@ -17,55 +18,24 @@ import { AbstractInputComponent } from '../abstract-input'; */ @Component({ selector: 'input-select', - templateUrl: './select.component.html' + templateUrl: './select.component.html', + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => InputSelectComponent), + multi: true + } + ] }) -export class InputSelectComponent extends AbstractInputComponent< - number | string -> { +export class InputSelectComponent extends AbstractInputComponent { @Input() items: any[]; - // Add a property to hold the sorting order - sortOrder: 'asc' | 'desc' = 'asc'; - - // Function to sort options based on the current sortOrder - sortedOptions(): any[] { - if (!this.items) { - return []; - } - - // Clone the array to avoid modifying the original array - const sortedItems = [...this.items]; - - sortedItems.sort((a, b) => { - const nameA = a.name.toUpperCase(); - const nameB = b.name.toUpperCase(); - - if (this.sortOrder === 'asc') { - return nameA.localeCompare(nameB); - } else { - return nameB.localeCompare(nameA); - } - }); - - return sortedItems; - } - - // Function to toggle the sorting order - toggleSortOrder() { - this.sortOrder = this.sortOrder === 'asc' ? 'desc' : 'asc'; - } - - clearLastSearchTerm() { - this.items = null; - } - - onSearch($event) { - this.items = $event.term; + constructor() { + super(); } - onBlur() { - setTimeout(() => { - this.clearLastSearchTerm(); - }, 3000); + onChangeValue(value) { + this.value = value; + this.onChange(value); } } diff --git a/src/app/shared/input/text-area/text-area.component.ts b/src/app/shared/input/text-area/text-area.component.ts index 3b7a9861..1de22159 100644 --- a/src/app/shared/input/text-area/text-area.component.ts +++ b/src/app/shared/input/text-area/text-area.component.ts @@ -7,17 +7,17 @@ import { NG_VALUE_ACCESSOR } from '@angular/forms'; * * Usage Example: * ```html - * + * > * ``` */ @Component({ - selector: 'app-input-text-area-area', + selector: 'input-text-area', templateUrl: './text-area.component.html', providers: [ { diff --git a/src/app/shared/input/text/text.component.html b/src/app/shared/input/text/text.component.html index 0121aaae..d9fae4b8 100644 --- a/src/app/shared/input/text/text.component.html +++ b/src/app/shared/input/text/text.component.html @@ -18,6 +18,7 @@ [id]="inputId" [(ngModel)]="value" (ngModelChange)="onChange(value)" + [pattern]="pattern" /> {{ hint }} {{ error }} diff --git a/src/app/shared/input/text/text.component.ts b/src/app/shared/input/text/text.component.ts index 0d534e28..d40d655a 100644 --- a/src/app/shared/input/text/text.component.ts +++ b/src/app/shared/input/text/text.component.ts @@ -1,5 +1,5 @@ import { AbstractInputComponent } from '../abstract-input'; -import { Component, forwardRef } from '@angular/core'; +import { Component, Input, forwardRef } from '@angular/core'; import { NG_VALUE_ACCESSOR } from '@angular/forms'; /** @@ -28,6 +28,7 @@ import { NG_VALUE_ACCESSOR } from '@angular/forms'; ] }) export class InputTextComponent extends AbstractInputComponent { + @Input() pattern: string | RegExp; constructor() { super(); } diff --git a/src/app/shared/utils/forms.ts b/src/app/shared/utils/forms.ts index 0239ec24..693decbb 100644 --- a/src/app/shared/utils/forms.ts +++ b/src/app/shared/utils/forms.ts @@ -5,41 +5,6 @@ * Comments use: https://tsdoc.org/ */ -/** - * Show / Hide elements in the form - * Used in; New Hashlist - * - * @param checkbox - checkbox - * @returns Value - * ``` - * @public - */ - -export function ShowHideTypeFile(checkbox: string): void { - const pasteObject = document.getElementById('pasteLine'); - const uploadObject = document.getElementById('uploadLine'); - const urlObject = document.getElementById('urlLine'); - switch (checkbox) { - case 'paste': - pasteObject.style.display = ''; - uploadObject.style.display = 'none'; - urlObject.style.display = 'none'; - break; - - case 'upload': - pasteObject.style.display = 'none'; - uploadObject.style.display = ''; - urlObject.style.display = 'none'; - break; - - case 'download': - pasteObject.style.display = 'none'; - uploadObject.style.display = 'none'; - urlObject.style.display = ''; - break; - } -} - /** * Extract Ids * Used extract ids after mat-autcomplete component diff --git a/src/styles/components/_card.scss b/src/styles/components/_card.scss index 4e440d13..6d835a3f 100644 --- a/src/styles/components/_card.scss +++ b/src/styles/components/_card.scss @@ -334,3 +334,7 @@ hr.break:after { } } } + +.info-message { + color: $color-red; /* Custom color for additional information */ +} diff --git a/src/styles/components/_form.scss b/src/styles/components/_form.scss index 680052c4..cc683cd9 100644 --- a/src/styles/components/_form.scss +++ b/src/styles/components/_form.scss @@ -283,7 +283,7 @@ progress::-webkit-progress-value { 09- Shared/Input */ -.color-picker-container { +.custom-input-container { display: flex; align-items: center; /* Align items vertically */ } @@ -299,6 +299,18 @@ progress::-webkit-progress-value { max-width: 600px; /* Set your desired maximum width */ } +.custom-input-multiselect { + display: flex; + align-items: center; + margin-right: 20px; +} + +.flex-grow { + flex-grow: 1; + width: 100%; + margin-right: 8px; +} + /* 09 */ @@ -326,3 +338,11 @@ $mat-form-field-max-width: 300px; // Adjust the maximum width as needed .mat-form-field { max-width: $mat-form-field-max-width !important; } + +.mat-chip-list.custom-chip-list { + .mat-chip.custom-chip { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } +} diff --git a/src/styles/pages/_files.scss b/src/styles/pages/_files.scss index 98f09efb..afa50428 100644 --- a/src/styles/pages/_files.scss +++ b/src/styles/pages/_files.scss @@ -90,3 +90,22 @@ font-family:Verdana; font-weight:bold; } + +.file-input-container { + position: relative; + overflow: hidden; + display: inline-block; +} + +input.custom-file-input[type="file"] { + font-size: 100px; + position: absolute; + left: 0; + top: 0; + opacity: 0; +} + +.file-upload-label { + font-size: 16px; + line-height: 24px; +} From 2ea7f8cd00327cb853c7db753bee5a4aecfaff68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Thu, 23 Nov 2023 19:23:24 +0000 Subject: [PATCH 302/419] Updating Forms --- package-lock.json | 17 + package.json | 1 + src/app/app.module.ts | 33 +- .../new-superhashlist.component.ts | 6 +- src/app/core/_constants/general.config.ts | 4 + src/app/core/_guards/pendingchanges.guard.ts | 73 +- .../shared/unsaved-changes.service.ts | 16 + src/app/files/files.module.ts | 11 +- .../files/new-files/new-files.component.html | 69 +- .../files/new-files/new-files.component.ts | 195 +++-- .../edit-hashlist.component.html | 148 +--- .../edit-hashlist/edit-hashlist.component.ts | 351 +++++--- src/app/hashlists/hashlists.module.ts | 37 +- .../new-hashlist/new-hashlist.component.html | 26 +- .../search-hash/search-hash.component.html | 29 +- .../fixed-alert/fixed-alert.component.html | 5 + .../fixed-alert/fixed-alert.component.ts | 9 + src/app/shared/components.module.ts | 24 +- src/app/shared/forms.module.ts | 74 ++ src/app/shared/grid-containers/grid.module.ts | 5 +- .../simulate-form-field.component.html | 10 + .../simulate-form-field.component.ts | 11 + .../shared/input/color/color.component.html | 7 +- src/app/shared/input/color/color.component.ts | 14 +- src/app/shared/input/file/file.component.html | 17 + src/app/shared/input/file/file.component.ts | 57 ++ src/app/shared/input/input.module.ts | 42 +- src/app/shared/input/text/text.component.html | 1 + .../edit-preconfigured-tasks.component.html | 156 +--- .../edit-preconfigured-tasks.component.ts | 270 +++--- .../tasks/edit-tasks/edit-tasks.component.ts | 819 ++++++++++-------- src/styles/components/_alert.scss | 10 +- src/styles/components/_card.scss | 50 +- src/styles/pages/_files.scss | 9 +- 34 files changed, 1448 insertions(+), 1158 deletions(-) create mode 100644 src/app/core/_constants/general.config.ts create mode 100644 src/app/core/_services/shared/unsaved-changes.service.ts create mode 100644 src/app/shared/alert/fixed-alert/fixed-alert.component.html create mode 100644 src/app/shared/alert/fixed-alert/fixed-alert.component.ts create mode 100644 src/app/shared/forms.module.ts create mode 100644 src/app/shared/grid-containers/simulate-form-field/simulate-form-field.component.html create mode 100644 src/app/shared/grid-containers/simulate-form-field/simulate-form-field.component.ts create mode 100644 src/app/shared/input/file/file.component.html create mode 100644 src/app/shared/input/file/file.component.ts diff --git a/package-lock.json b/package-lock.json index aa93e307..b26da6ef 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "@angular/common": "^16.1.4", "@angular/compiler": "^16.1.4", "@angular/core": "^16.1.4", + "@angular/flex-layout": "^15.0.0-beta.42", "@angular/forms": "^16.1.4", "@angular/localize": "^16.1.4", "@angular/material": "^16.2.9", @@ -608,6 +609,22 @@ "zone.js": "~0.13.0" } }, + "node_modules/@angular/flex-layout": { + "version": "15.0.0-beta.42", + "resolved": "https://registry.npmjs.org/@angular/flex-layout/-/flex-layout-15.0.0-beta.42.tgz", + "integrity": "sha512-cTAPVMMxnyIFwpZwdq0PL5mdP9Qh+R8MB7ZBezVaN3Rz2fRrkagzKpLvPX3TFzepXrvHBdpKsU4b8u+NxEC/6g==", + "deprecated": "This package has been deprecated. Please see https://blog.angular.io/modern-css-in-angular-layouts-4a259dca9127", + "dependencies": { + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/cdk": ">=15.0.0", + "@angular/common": ">=15.0.2", + "@angular/core": ">=15.0.2", + "@angular/platform-browser": ">=15.0.2", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, "node_modules/@angular/forms": { "version": "16.2.1", "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-16.2.1.tgz", diff --git a/package.json b/package.json index 7b0a4adc..3ca5ce4d 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "@angular/common": "^16.1.4", "@angular/compiler": "^16.1.4", "@angular/core": "^16.1.4", + "@angular/flex-layout": "^15.0.0-beta.42", "@angular/forms": "^16.1.4", "@angular/localize": "^16.1.4", "@angular/material": "^16.2.9", diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 444a31f0..2af6824e 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -1,9 +1,9 @@ /** * Main Modules * -*/ + */ import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; -import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http'; +import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http'; import { AppPreloadingStrategy } from './core/app_preloading_strategy'; import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; import { BrowserModule, Title } from '@angular/platform-browser'; @@ -19,7 +19,7 @@ import { MomentModule } from 'ngx-moment'; /** * App Pages Components * -*/ + */ import { ScreenSizeDetectorComponent } from './layout/screen-size-detector/screen-size-detector.component'; import { PageNotFoundComponent } from './layout/page-not-found/page-not-found.component'; import { AuthInterceptorService } from './core/_interceptors/auth-interceptor.service'; @@ -35,7 +35,7 @@ import { AppComponent } from './app.component'; /** * App Modules, Reducers * -*/ + */ import { ScrollYTopComponent } from './shared/scrollytop/scrollytop.component'; import { ThemeService } from './core/_services/shared/theme.service'; import { ComponentsModule } from './shared/components.module'; @@ -48,10 +48,13 @@ import { MatIconModule } from '@angular/material/icon'; import { MatMenuModule } from '@angular/material/menu'; import { MatTooltipModule } from '@angular/material/tooltip'; import { MatToolbarModule } from '@angular/material/toolbar'; -import { MatCardModule } from "@angular/material/card"; +import { MatCardModule } from '@angular/material/card'; import { CoreComponentsModule } from './core/_components/core-components.module'; import { MAT_FORM_FIELD_DEFAULT_OPTIONS } from '@angular/material/form-field'; -import { MAT_SNACK_BAR_DEFAULT_OPTIONS, MatSnackBarModule } from '@angular/material/snack-bar'; +import { + MAT_SNACK_BAR_DEFAULT_OPTIONS, + MatSnackBarModule +} from '@angular/material/snack-bar'; @NgModule({ declarations: [ @@ -87,7 +90,7 @@ import { MAT_SNACK_BAR_DEFAULT_OPTIONS, MatSnackBarModule } from '@angular/mater MatSnackBarModule, CoreComponentsModule, NgbModule, - AppRoutingModule, // Main routes for the App + AppRoutingModule, // Main routes for the App NgIdleKeepaliveModule.forRoot(), StoreModule.forRoot({ configList: configReducer }) ], @@ -106,18 +109,20 @@ import { MAT_SNACK_BAR_DEFAULT_OPTIONS, MatSnackBarModule } from '@angular/mater ThemeService, AppPreloadingStrategy, ConfigService, - { provide: MAT_FORM_FIELD_DEFAULT_OPTIONS, useValue: { appearance: 'outline' } }, - { provide: MAT_SNACK_BAR_DEFAULT_OPTIONS, useValue: { duration: 2500, verticalPosition: 'top' } } - + { + provide: MAT_FORM_FIELD_DEFAULT_OPTIONS, + useValue: { appearance: 'outline' } + }, + { + provide: MAT_SNACK_BAR_DEFAULT_OPTIONS, + useValue: { duration: 2500, verticalPosition: 'top' } + } ], bootstrap: [AppComponent] }) export class AppModule { static injector: Injector; - constructor( - injector: Injector - ) { + constructor(injector: Injector) { AppModule.injector = injector; } } - diff --git a/src/app/core/_components/forms/custom-forms/superhashlist/new-superhashlist/new-superhashlist.component.ts b/src/app/core/_components/forms/custom-forms/superhashlist/new-superhashlist/new-superhashlist.component.ts index f284b85e..700fc8da 100644 --- a/src/app/core/_components/forms/custom-forms/superhashlist/new-superhashlist/new-superhashlist.component.ts +++ b/src/app/core/_components/forms/custom-forms/superhashlist/new-superhashlist/new-superhashlist.component.ts @@ -43,10 +43,7 @@ export class NewSuperhashlistComponent implements OnInit, OnDestroy { @Input() error; - /** Maximum results for API requests. */ - private maxResults = environment.config.prodApiMaxResults; - - /** List of hashlists. */ + /** Select List of hashlists. */ selectHashlists: any; /** @@ -104,7 +101,6 @@ export class NewSuperhashlistComponent implements OnInit, OnDestroy { loadData(): void { const loadSubscription$ = this.globalService .getAll(SERV.HASHLISTS, { - maxResults: this.maxResults, filter: 'isArchived=false,format=0' }) .subscribe((response: ListResponseWrapper) => { diff --git a/src/app/core/_constants/general.config.ts b/src/app/core/_constants/general.config.ts new file mode 100644 index 00000000..e96e6e2e --- /dev/null +++ b/src/app/core/_constants/general.config.ts @@ -0,0 +1,4 @@ +export const yesNo = [ + { _id: true, name: 'Yes' }, + { _id: false, name: 'No' } +]; diff --git a/src/app/core/_guards/pendingchanges.guard.ts b/src/app/core/_guards/pendingchanges.guard.ts index 6c932011..fcc4369f 100644 --- a/src/app/core/_guards/pendingchanges.guard.ts +++ b/src/app/core/_guards/pendingchanges.guard.ts @@ -1,37 +1,60 @@ +import { UnsavedChangesService } from '../_services/shared/unsaved-changes.service'; import Swal from 'sweetalert2/dist/sweetalert2.js'; -import { CanDeactivate } from '@angular/router'; +import { CanDeactivate, Router } from '@angular/router'; import { Injectable } from '@angular/core'; import { Observable } from 'rxjs'; - -export interface ComponentCanDeactivate { - canDeactivate: () => boolean | Observable; +export interface CanComponentDeactivate { + canDeactivate: () => Observable | Promise | boolean; } @Injectable({ providedIn: 'root' }) -export class PendingChangesGuard implements CanDeactivate { - - canDeactivate(component: ComponentCanDeactivate): boolean | Observable { - // if there are no pending changes, just allow deactivation; else confirm first - return component.canDeactivate() ? - true : - Swal.fire({ - title: "WARNING", - text: "You have unsaved changes. Press Cancel to go back and save these changes, or OK to lose these changes.", - icon: "warning", - buttons: true, - dangerMode: true, - showCancelButton: true, - }) - .then((result) => { - if (result.isConfirmed) { - return true; - } else { - return false - } - }); +export class PendingChangesGuard + implements CanDeactivate +{ + constructor( + private unsavedChangesService: UnsavedChangesService, + private router: Router + ) {} + + async canDeactivate(component: CanComponentDeactivate): Promise { + const result = component.canDeactivate + ? await this.handleDeactivation(component.canDeactivate()) + : true; + + return result; + } + + private async handleDeactivation( + deactivate: Observable | Promise | boolean + ): Promise { + if (deactivate instanceof Observable) { + return await deactivate.toPromise(); + } else { + return deactivate; + } } + private handleDefault(): boolean { + if (this.unsavedChangesService.hasUnsavedChanges()) { + console.log('here'); + const userConfirmed = window.confirm( + 'You have unsaved changes. Press OK to leave without saving, or Cancel to stay and save.' + ); + + if (userConfirmed) { + // User clicked OK, navigate away + this.unsavedChangesService.setUnsavedChanges(false); + this.router.navigate(['/']); // Adjust this route as needed + return true; + } else { + // User canceled, stay on the current page + return false; + } + } + + return true; + } } diff --git a/src/app/core/_services/shared/unsaved-changes.service.ts b/src/app/core/_services/shared/unsaved-changes.service.ts new file mode 100644 index 00000000..de3327fc --- /dev/null +++ b/src/app/core/_services/shared/unsaved-changes.service.ts @@ -0,0 +1,16 @@ +import { Injectable } from '@angular/core'; + +@Injectable({ + providedIn: 'root' +}) +export class UnsavedChangesService { + private unsavedChanges = false; + + setUnsavedChanges(value: boolean): void { + this.unsavedChanges = value; + } + + hasUnsavedChanges(): boolean { + return this.unsavedChanges; + } +} diff --git a/src/app/files/files.module.ts b/src/app/files/files.module.ts index 81d30dfd..222f8109 100644 --- a/src/app/files/files.module.ts +++ b/src/app/files/files.module.ts @@ -1,33 +1,28 @@ -import { FormsModule, ReactiveFormsModule } from '@angular/forms'; - import { CommonModule } from '@angular/common'; import { ComponentsModule } from '../shared/components.module'; import { CoreComponentsModule } from '../core/_components/core-components.module'; import { DataTablesModule } from 'angular-datatables'; import { FilesComponent } from './files.component'; import { FilesRoutingModule } from './files-routing.module'; -import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; import { NewFilesComponent } from './new-files/new-files.component'; -import { NgModule } from '@angular/core'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { PipesModule } from '../shared/pipes.module'; import { RouterModule } from '@angular/router'; +import { NgModule } from '@angular/core'; +import { CoreFormsModule } from '../shared/forms.module'; @NgModule({ declarations: [FilesComponent, NewFilesComponent], imports: [ - ReactiveFormsModule, + CoreFormsModule, FilesRoutingModule, - FontAwesomeModule, DataTablesModule, ComponentsModule, CommonModule, CoreComponentsModule, RouterModule, - FormsModule, PipesModule, NgbModule ] }) export class FilesModule {} - diff --git a/src/app/files/new-files/new-files.component.html b/src/app/files/new-files/new-files.component.html index 85f1c884..20d1139c 100644 --- a/src/app/files/new-files/new-files.component.html +++ b/src/app/files/new-files/new-files.component.html @@ -1,61 +1,34 @@ - - - + +
    -
  • -
  • + upload + download
-
-

Upload from your computer

-
+

Upload from your computer

+ - + + + + +
+ +
+ +

Upload completed!

+ + + + + -
  + -
-
- -
-
- -
-
-
-
-
- -
-
-

Upload completed!

-
-
- - - - - -
- Do not refresh the page while uploading -
diff --git a/src/app/files/new-files/new-files.component.ts b/src/app/files/new-files/new-files.component.ts index 264dc677..9ac6c15e 100644 --- a/src/app/files/new-files/new-files.component.ts +++ b/src/app/files/new-files/new-files.component.ts @@ -1,10 +1,3 @@ -import { - faDownload, - faFileUpload, - faLink, - faPlus, - faUpload -} from '@fortawesome/free-solid-svg-icons'; import { ChangeDetectorRef, Component, @@ -21,7 +14,6 @@ import { Buffer } from 'buffer'; import { UploadTUSService } from 'src/app/core/_services/files/files_tus.service'; import { AlertService } from 'src/app/core/_services/shared/alert.service'; import { GlobalService } from 'src/app/core/_services/main.service'; -import { FileSizePipe } from 'src/app/core/_pipes/file-size.pipe'; import { environment } from './../../../environments/environment'; import { PageTitle } from 'src/app/core/_decorators/autotitle'; import { validateFileExt } from '../../shared/utils/util'; @@ -33,32 +25,55 @@ import { AutoTitleService } from 'src/app/core/_services/shared/autotitle.servic import { UnsubscribeService } from 'src/app/core/_services/unsubscribe.service'; import { transformSelectOptions } from 'src/app/shared/utils/forms'; +/** + * Represents the NewFilesComponent responsible for creating and uploading files + */ @Component({ selector: 'app-new-files', - templateUrl: './new-files.component.html', - providers: [FileSizePipe] + templateUrl: './new-files.component.html' }) export class NewFilesComponent implements OnInit, OnDestroy { /** Flag indicating whether data is still loading. */ isLoading = true; - faFileUpload = faFileUpload; - faDownload = faDownload; - faUpload = faUpload; - faLink = faLink; - faPlus = faPlus; + /** Form group for the new File. */ + form: FormGroup; + submitted = false; + /** Filtering and views. */ filterType: number; whichView: string; - form: FormGroup; - submitted = false; + viewMode = 'tab1'; + title: string; + redirect: string; // Lists of Selected inputs selectAccessgroup: any[]; - private maxResults = environment.config.prodApiMaxResults; - subscriptions: Subscription[] = []; + // Upload files + selectedFiles: FileList | null = null; + fileName: any; + uploadProgress = 0; + filenames: string[] = []; + selectedFile: ''; + fileToUpload: File | null = null; + + // Unsubcribe files + private fileUnsubscribe = new Subject(); + /** + * Component for handling new files. + * + * @constructor + * @param {UnsubscribeService} unsubscribeService - Service for managing unsubscribing from observables. + * @param {ChangeDetectorRef} changeDetectorRef - Reference to Angular's ChangeDetectorRef for manual change detection. + * @param {UploadTUSService} uploadService - Service for handling file uploads using TUS protocol. + * @param {AutoTitleService} titleService - Service for setting and managing page titles automatically. + * @param {ActivatedRoute} route - Service for accessing the current route information. + * @param {AlertService} alert - Service for displaying alerts to the user. + * @param {GlobalService} gs - Service for accessing global application state. + * @param {Router} router - Angular router service for navigating between views. + */ constructor( private unsubscribeService: UnsubscribeService, private changeDetectorRef: ChangeDetectorRef, @@ -67,7 +82,6 @@ export class NewFilesComponent implements OnInit, OnDestroy { private route: ActivatedRoute, private alert: AlertService, private gs: GlobalService, - private fs: FileSizePipe, private router: Router ) { this.getLocation(); @@ -75,9 +89,10 @@ export class NewFilesComponent implements OnInit, OnDestroy { titleService.set([this.title]); } - // Get Title - public title: string; - public redirect: string; + /** + * Retrieves location information based on route data. + * Sets filterType, title, and redirect properties accordingly. + */ getLocation() { this.route.data.subscribe((data) => { switch (data['kind']) { @@ -115,6 +130,8 @@ export class NewFilesComponent implements OnInit, OnDestroy { */ ngOnDestroy(): void { this.unsubscribeService.unsubscribeAll(); + this.fileUnsubscribe.next(false); + this.fileUnsubscribe.complete(); } /** @@ -129,22 +146,11 @@ export class NewFilesComponent implements OnInit, OnDestroy { sourceType: new FormControl('import' || ''), sourceData: new FormControl('') }); - - //subscribe to changes to handle select salted hashes - // this.form.get('hashTypeId').valueChanges.subscribe((newvalue) => { - // this.handleSelectedItems(newvalue); - // }); } - // ngOnDestroy() { - // this.subs.forEach((s) => s.unsubscribe()); - // for (const sub of this.subscriptions) { - // sub.unsubscribe(); - // } - // this.ngUnsubscribe.next(false); - // this.ngUnsubscribe.complete(); - // } - + /** + * Loads data, specifically access groups, for the component. + */ loadData() { const fieldAccess = { fieldMapping: { @@ -167,53 +173,80 @@ export class NewFilesComponent implements OnInit, OnDestroy { } /** - * Create File - * + * Handles the form submission for creating a new file. + * Checks form validity and submits the form data to create a new file. + * Navigates to the appropriate route upon successful creation. */ onSubmit(): void { if (this.form.valid && this.submitted === false) { - let form = this.onPrep(this.form.value, false); + let form = this.onBeforeSubmit(this.form.value, false); this.submitted = true; if (form.status === false) { - this.subscriptions.push( - this.gs.create(SERV.FILES, form.update).subscribe(() => { - form = this.onPrep(this.form.value, true); + const createSubscription$ = this.gs + .create(SERV.FILES, form.update) + .subscribe(() => { + form = this.onBeforeSubmit(this.form.value, true); this.alert.okAlert('New File created!', ''); this.submitted = false; this.router.navigate(['/files', this.redirect]); - }) - ); + }); + this.unsubscribeService.add(createSubscription$); } } } - onPrep(obj: any, status: boolean) { + /** + * Prepares the form data before submission. + * Determines source data and filename based on the selected source type. + * + * @param {any} form - The form data to be prepared. + * @param {boolean} status - The status indicating the form's validity. + * @returns {{ update: { filename: string, isSecret: boolean, fileType: number, accessGroupId: number, sourceType: string, sourceData: string }, status: boolean }} Prepared form data. + */ + onBeforeSubmit(form: any, status: boolean) { let sourcadata; let fname; - if (obj.sourceType == 'inline') { - fname = obj.filename; - sourcadata = Buffer.from(obj.sourceData).toString('base64'); + + if (form.sourceType === 'inline') { + fname = form.filename; + sourcadata = Buffer.from(form.sourceData).toString('base64'); } else { sourcadata = this.fileName; fname = this.fileName; } + + /** + * Prepared form data for submission. + */ const res = { update: { filename: fname, - isSecret: obj.isSecret, + isSecret: form.isSecret, fileType: this.filterType, - accessGroupId: obj.accessGroupId, - sourceType: obj.sourceType, + accessGroupId: form.accessGroupId, + sourceType: form.sourceType, sourceData: sourcadata }, status: status }; + return res; } - souceType(type: string, view: string) { + /** + * Handles the change of file upload type. + * Updates the view mode and resets form values based on the selected type. + * + * @param {string} type - The selected file upload type. + * @param {string} view - The selected view mode. + */ + onChangeType(type: string, view: string): void { + /** + * Update the view mode based on the selected type. + * Reset form values related to file upload. + */ this.viewMode = view; this.form.patchValue({ filename: '', @@ -223,58 +256,40 @@ export class NewFilesComponent implements OnInit, OnDestroy { }); } - // Uploading file - @ViewChild('file', { static: false }) file: ElementRef; - name = '!!!'; - viewMode = 'tab1'; - uploadProgress = 0; - filenames: string[] = []; - - isHovering: boolean; - - toggleHover(event) { - this.isHovering = event; - } - - validateFileExt = validateFileExt; - - selectedFile: ''; - fileGroup: number; - fileToUpload: File | null = null; - fileSize: any; - fileName: any; + /** + * Handles the selection of files. + * Updates the selected files and extracts the file name. + * + * @param {FileList} files - List of selected files. + */ - handleFileInput(event: any) { - this.fileToUpload = event.target.files[0]; - this.fileSize = this.fileToUpload.size; - this.fileName = this.fileToUpload.name; - $('.fileuploadspan').text(this.fs.transform(this.fileToUpload.size, false)); + onFilesSelected(files: FileList): void { + this.selectedFiles = files; + this.fileName = files[0].name; } - private subs: Subscription[] = []; - private ngUnsubscribe = new Subject(); - - onuploadFile(files: FileList) { - const form = this.onPrep(this.form.value, false); + /** + * Handles the upload of files. + * Prepares form data and initiates the file upload process. + * + * @param {FileList | null} files - List of files to be uploaded. + */ + onuploadFile(files: FileList | null): void { + const form = this.onBeforeSubmit(this.form.value, false); const upload: Array = []; for (let i = 0; i < files.length; i++) { upload.push( this.uploadService - .uploadFile(files[i], files[i].name, SERV.FILES, form.update, [ + .uploadFile(files[0], files[0].name, SERV.FILES, form.update, [ '/files', this.redirect ]) - .pipe(takeUntil(this.ngUnsubscribe)) + .pipe(takeUntil(this.fileUnsubscribe)) .subscribe((progress) => { this.uploadProgress = progress; // console.log(`Upload progress: ${progress}%`); }) ); } - // this.reset(); - } - - reset() { - this.file.nativeElement.value = null; } } diff --git a/src/app/hashlists/edit-hashlist/edit-hashlist.component.html b/src/app/hashlists/edit-hashlist/edit-hashlist.component.html index f99eff7a..c461cce8 100644 --- a/src/app/hashlists/edit-hashlist/edit-hashlist.component.html +++ b/src/app/hashlists/edit-hashlist/edit-hashlist.component.html @@ -1,121 +1,39 @@ - -
- -
- - - -
- - - - - - -
-
- - - - -
-
- - -
- -
-
-
-
-
-
- - - {{ hashtype.hashTypeId }} - {{ hashtype.description }} - - -
-
- - - -
-
- - - -
-
-
-
- - - -
-
- - - -
-
- - - + + + + + + +
+ + + + + + + + +
-
- - - - + + + + + + + + + + + + + + + + +
diff --git a/src/app/hashlists/edit-hashlist/edit-hashlist.component.ts b/src/app/hashlists/edit-hashlist/edit-hashlist.component.ts index 6a7e7949..f7f8eb2e 100644 --- a/src/app/hashlists/edit-hashlist/edit-hashlist.component.ts +++ b/src/app/hashlists/edit-hashlist/edit-hashlist.component.ts @@ -1,6 +1,5 @@ -import { faHomeAlt, faInfoCircle, faEye } from '@fortawesome/free-solid-svg-icons'; import { StaticArrayPipe } from 'src/app/core/_pipes/static-array.pipe'; -import { Component, HostListener, OnInit, ViewChild} from '@angular/core'; +import { Component, HostListener, OnInit, ViewChild } from '@angular/core'; import { ActivatedRoute, Params, Router } from '@angular/router'; import { FormControl, FormGroup } from '@angular/forms'; import { DataTableDirective } from 'angular-datatables'; @@ -11,26 +10,46 @@ import { environment } from './../../../environments/environment'; import { PageTitle } from 'src/app/core/_decorators/autotitle'; import { SERV } from '../../core/_services/main.config'; import { AlertService } from 'src/app/core/_services/shared/alert.service'; +import { transformSelectOptions } from 'src/app/shared/utils/forms'; +import { UnsubscribeService } from 'src/app/core/_services/unsubscribe.service'; +import { ChangeDetectorRef } from '@angular/core'; +import { AutoTitleService } from 'src/app/core/_services/shared/autotitle.service'; +import { + CanComponentDeactivate, + PendingChangesGuard +} from 'src/app/core/_guards/pendingchanges.guard'; +import { OnDestroy } from '@angular/core'; +import { UnsavedChangesService } from 'src/app/core/_services/shared/unsaved-changes.service'; +/** + * Represents the EditHashlistComponent responsible for editing a new hashlists. + */ @Component({ selector: 'app-edit-hashlist', templateUrl: './edit-hashlist.component.html' }) -@PageTitle(['Edit Hashlist']) -export class EditHashlistComponent implements OnInit { +export class EditHashlistComponent + implements OnInit, OnDestroy, CanComponentDeactivate +{ + /** Flag indicating whether data is still loading. */ + isLoading = true; - editMode = false; + /** Form group for the new File. */ + updateForm: FormGroup; + + // Edit variables editedHashlistIndex: number; - editedHashlist: any // Change to Model + editedHashlist: any; // Change to Model hashtype: any; - type: any // Hashlist or SuperHaslist - hashlist: any + type: any; // Hashlist or SuperHaslist + hashlist: any; - faEye=faEye; - faHome=faHomeAlt; - faInfoCircle=faInfoCircle; + // Lists of Selected inputs + selectAccessgroup: any[]; + alltasks: any; //Change to Interface - @ViewChild(DataTableDirective, {static: false}) + // To Remove for use tabes + @ViewChild(DataTableDirective, { static: false }) dtElement: DataTableDirective; dtTrigger: Subject = new Subject(); @@ -38,133 +57,212 @@ export class EditHashlistComponent implements OnInit { dtOptions: any = {}; dtOptions1: any = {}; + /** + * Constructor for the YourComponent class. + * Initializes and injects required services and dependencies. + * Calls necessary methods to set up the component. + * @param {UnsubscribeService} unsubscribeService - Service for managing subscriptions. + * @param {ChangeDetectorRef} changeDetectorRef - Reference to the Angular change detector. + * @param {AutoTitleService} titleService - Service for managing page titles. + * @param {StaticArrayPipe} format - Angular pipe for formatting static arrays. + * @param {ActivatedRoute} route - The activated route, representing the route associated with this component. + * @param {AlertService} alert - Service for displaying alerts. + * @param {GlobalService} gs - Service for global application functionality. + * @param {Router} router - Angular router service for navigation. + */ constructor( + private unsavedChangesService: UnsavedChangesService, + private unsubscribeService: UnsubscribeService, + private changeDetectorRef: ChangeDetectorRef, + private titleService: AutoTitleService, private format: StaticArrayPipe, private route: ActivatedRoute, private alert: AlertService, private gs: GlobalService, private router: Router - ) { } - - updateForm: FormGroup - accessgroup: any //Change to Interface - alltasks: any; //Change to Interface - private maxResults = environment.config.prodApiMaxResults; - - - ngOnInit(): void { - this.route.params - .subscribe( - (params: Params) => { - this.editedHashlistIndex = +params['id']; - this.editMode = params['id'] != null; - this.initForm(); - } - ); + ) { + this.getInitialization(); + this.buildForm(); + titleService.set(['Edit Hashlist']); + } - this.updateForm = new FormGroup({ - 'hashlistId': new FormControl({value: '', disabled: true}), - 'accessGroupId': new FormControl(''), - 'hashTypeId': new FormControl({value: '', disabled: true}), - 'useBrain': new FormControl({value: '', disabled: true}), - 'format': new FormControl({value: '', disabled: true}), - 'hashCount': new FormControl({value: '', disabled: true}), - 'cracked': new FormControl({value: '', disabled: true}), - 'remaining': new FormControl({value: '', disabled: true}), - 'updateData': new FormGroup({ - 'name': new FormControl(''), - 'notes': new FormControl(''), - 'isSecret': new FormControl(''), - 'accessGroupId': new FormControl(''), - }), + /** + * Initializes the form based on route parameters. + */ + getInitialization() { + this.route.params.subscribe((params: Params) => { + this.editedHashlistIndex = +params['id']; + this.updateFormValues(); }); + } - this.gs.get(SERV.HASHLISTS,this.editedHashlistIndex).subscribe((result)=>{ - this.editedHashlist = result; - }); + /** + * Lifecycle hook called after component initialization. + */ + ngOnInit(): void { + this.loadData(); + } - const params = {'maxResults': this.maxResults}; + /** + * Lifecycle hook called before the component is destroyed. + * Unsubscribes from all subscriptions to prevent memory leaks. + */ + ngOnDestroy(): void { + this.unsubscribeService.unsubscribeAll(); + } - this.gs.getAll(SERV.ACCESS_GROUPS, params).subscribe((agroups: any) => { - this.accessgroup = agroups.values; + /** + * Builds the form for creating a new Hashlist. + */ + buildForm(): void { + this.updateForm = new FormGroup({ + hashlistId: new FormControl({ value: '', disabled: true }), + accessGroupId: new FormControl(''), + hashTypeId: new FormControl({ value: '', disabled: true }), + useBrain: new FormControl({ value: '', disabled: true }), + format: new FormControl({ value: '', disabled: true }), + hashCount: new FormControl({ value: '', disabled: true }), + cracked: new FormControl({ value: '', disabled: true }), + remaining: new FormControl({ value: '', disabled: true }), + updateData: new FormGroup({ + name: new FormControl(''), + notes: new FormControl(''), + isSecret: new FormControl(''), + accessGroupId: new FormControl('') + }) }); + } + /** + * Loads data, specifically access groups, for the component. + */ + loadData() { + const fieldAccess = { + fieldMapping: { + name: 'groupName', + _id: '_id' + } + }; + const accedgroupSubscription$ = this.gs + .getAll(SERV.ACCESS_GROUPS) + .subscribe((response: any) => { + const transformedOptions = transformSelectOptions( + response.values, + fieldAccess + ); + this.selectAccessgroup = transformedOptions; + this.isLoading = false; + this.changeDetectorRef.detectChanges(); + }); + this.unsubscribeService.add(accedgroupSubscription$); } - onSubmit(){ + /** + * Handles the form submission. + * If the form is valid, it updates the hashlist using the provided data. + * @returns {void} + */ + onSubmit() { if (this.updateForm.valid) { - - this.gs.update(SERV.HASHLISTS,this.editedHashlistIndex,this.updateForm.value['updateData']).subscribe(() => { - this.alert.okAlert('Hashlist saved!',''); - this.updateForm.reset(); // success, we reset form - const path = this.type === 3 ? '/hashlists/superhashlist':'/hashlists/hashlist'; - this.router.navigate([path]); - } - ); + const createSubscription$ = this.gs + .update( + SERV.HASHLISTS, + this.editedHashlistIndex, + this.updateForm.value['updateData'] + ) + .subscribe(() => { + this.alert.okAlert('Hashlist saved!', ''); + this.updateForm.reset(); // success, we reset form + const path = + this.type === 3 + ? '/hashlists/superhashlist' + : '/hashlists/hashlist'; + this.router.navigate([path]); + }); + this.unsubscribeService.add(createSubscription$); } } - rerender(): void { - this.dtElement.dtInstance.then((dtInstance: DataTables.Api) => { - // Destroy the table first - dtInstance.destroy(); - // Call the dtTrigger to rerender again - setTimeout(() => { - this.dtTrigger['new'].next(); - }); - }); - } - - private initForm() { - if (this.editMode) { - this.gs.get(SERV.HASHLISTS,this.editedHashlistIndex,{'expand':'tasks,hashlists,hashType'}).subscribe((result)=>{ + /** + * Updates the form values based on the hashlist data retrieved from the server. + * Fetches hashlist data, initializes form controls, and updates the form group. + * @returns {void} + */ + private updateFormValues() { + const updateSubscription$ = this.gs + .get(SERV.HASHLISTS, this.editedHashlistIndex, { + expand: 'tasks,hashlists,hashType' + }) + .subscribe((result) => { this.getTasks(); this.editedHashlist = result; this.type = result['format']; this.hashtype = result['hashType']; this.hashlist = result['hashlists']; this.updateForm = new FormGroup({ - 'hashlistId': new FormControl({value: result['hashlistId'], disabled: true}), - 'accessGroupId': new FormControl({value: result['accessGroupId'], disabled: true}), - 'useBrain': new FormControl({value: result['useBrain'] == true ? 'Yes' : 'No', disabled: true}), - 'format': new FormControl({value: this.format.transform(result['format'],'formats'), disabled: true}), - 'hashCount': new FormControl({value: result['hashCount'], disabled: true}), - 'cracked': new FormControl({value: result['cracked'], disabled: true}), - 'remaining': new FormControl({value: result['hashCount'] - result['cracked'], disabled: true}), - 'updateData': new FormGroup({ - 'name': new FormControl(result['name']), - 'notes': new FormControl(result['notes']), - 'isSecret': new FormControl(result['isSecret']), - 'accessGroupId': new FormControl(result['accessGroupId']), + hashlistId: new FormControl({ + value: result['hashlistId'], + disabled: true }), - }); - - this.dtTrigger1.next(null); + accessGroupId: new FormControl({ + value: result['accessGroupId'], + disabled: true + }), + useBrain: new FormControl({ + value: result['useBrain'] == true ? 'Yes' : 'No', + disabled: true + }), + format: new FormControl({ + value: this.format.transform(result['format'], 'formats'), + disabled: true + }), + hashCount: new FormControl({ + value: result['hashCount'], + disabled: true + }), + cracked: new FormControl({ + value: result['cracked'], + disabled: true + }), + remaining: new FormControl({ + value: result['hashCount'] - result['cracked'], + disabled: true + }), + updateData: new FormGroup({ + name: new FormControl(result['name']), + notes: new FormControl(result['notes']), + isSecret: new FormControl(result['isSecret']), + accessGroupId: new FormControl(result['accessGroupId']) + }) + }); - // Display Tasks Expand tasks + this.dtTrigger1.next(null); + }); + this.unsubscribeService.add(updateSubscription$); - }); - } this.dtOptions1 = { dom: 'Bfrtip', scrollX: true, - bStateSave:true, + bStateSave: true, destroy: true, - buttons:[] - } + buttons: [] + }; } - // Remove when expand task is working - getTasks():void { - const params = {'maxResults': this.maxResults, 'expand': 'crackerBinary,crackerBinaryType,hashlist', 'filter': 'isArchived=false'} - const taskh = [] - this.gs.getAll(SERV.TASKS,params).subscribe((tasks: any) => { - for(let i=0; i < tasks.values.length; i++){ - let firtprep = tasks.values[i].hashlist; - for(let i=0; i < firtprep.length; i++){ + // TABLE SECTION BELOW + getTasks(): void { + const params = { + expand: 'crackerBinary,crackerBinaryType,hashlist', + filter: 'isArchived=false' + }; + const taskh = []; + this.gs.getAll(SERV.TASKS, params).subscribe((tasks: any) => { + for (let i = 0; i < tasks.values.length; i++) { + const firtprep = tasks.values[i].hashlist; + for (let i = 0; i < firtprep.length; i++) { const match = firtprep[i].hashlistId == this.editedHashlistIndex; - if(match === true){ - taskh.push(tasks.values[i]) + if (match === true) { + taskh.push(tasks.values[i]); } } } @@ -177,31 +275,36 @@ export class EditHashlistComponent implements OnInit { scrollX: true, pageLength: 25, lengthMenu: [ - [10, 25, 50, 100, 250, -1], - [10, 25, 50, 100, 250, 'All'] + [10, 25, 50, 100, 250, -1], + [10, 25, 50, 100, 250, 'All'] ], - bStateSave:true, + bStateSave: true, destroy: true, - buttons:[] - } - - } - - // @HostListener allows us to also guard against browser refresh, close, etc. - @HostListener('window:beforeunload', ['$event']) - unloadNotification($event: any) { - if (!this.canDeactivate()) { - $event.returnValue = "IE and Edge Message"; - } + buttons: [] + }; } - canDeactivate(): Observable | boolean { - if (this.updateForm.valid) { - return false; - } - return true; + rerender(): void { + this.dtElement.dtInstance.then((dtInstance: DataTables.Api) => { + // Destroy the table first + dtInstance.destroy(); + // Call the dtTrigger to rerender again + setTimeout(() => { + this.dtTrigger['new'].next(); + }); + }); } -} + canDeactivate(): boolean { + const hasUnsavedChanges = this.updateForm.dirty; + if (hasUnsavedChanges) { + console.log('EditTasksComponent - Setting unsaved changes to true'); + this.unsavedChangesService.setUnsavedChanges(true); + } else { + console.log('EditTasksComponent - No unsaved changes'); + } + return !hasUnsavedChanges; + } +} diff --git a/src/app/hashlists/hashlists.module.ts b/src/app/hashlists/hashlists.module.ts index b1b1eb34..056975c1 100644 --- a/src/app/hashlists/hashlists.module.ts +++ b/src/app/hashlists/hashlists.module.ts @@ -10,7 +10,6 @@ import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; import { HashesComponent } from './hashes/hashes.component'; import { HashlistComponent } from './hashlist/hashlist.component'; import { HashlistRoutingModule } from './hashlists-routing.module'; -import { MatSlideToggleModule } from '@angular/material/slide-toggle'; import { NewHashlistComponent } from './new-hashlist/new-hashlist.component'; import { NewSuperhashlistComponent } from '../core/_components/forms/custom-forms/superhashlist/new-superhashlist/new-superhashlist.component'; import { NgModule } from '@angular/core'; @@ -20,21 +19,9 @@ import { RouterModule } from '@angular/router'; import { SearchHashComponent } from './search-hash/search-hash.component'; import { ShowCracksComponent } from './show-cracks/show-cracks.component'; import { SuperhashlistComponent } from './superhashlist/superhashlist.component'; +import { CoreFormsModule } from '../shared/forms.module'; import { MatFormFieldModule } from '@angular/material/form-field'; -import { MatAutocompleteModule } from '@angular/material/autocomplete'; -import { MatOptionModule } from '@angular/material/core'; -import { MatIconModule } from '@angular/material/icon'; -import { MatInputModule } from '@angular/material/input'; -import { MatSelectModule } from '@angular/material/select'; -import { MatDividerModule } from '@angular/material/divider'; -import { MatTooltipModule } from '@angular/material/tooltip'; -import { MatButtonModule } from '@angular/material/button'; -import { MatCheckboxModule } from '@angular/material/checkbox'; -import { MatCardModule } from '@angular/material/card'; -import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; -import { MatDialogModule } from '@angular/material/dialog'; -import { MatChipsModule } from '@angular/material/chips'; -import { MatProgressBarModule } from '@angular/material/progress-bar'; +import { MatInputModule } from '@angular/material/input'; // You may need to import other modules based on your application's requirements. @NgModule({ declarations: [ @@ -48,29 +35,17 @@ import { MatProgressBarModule } from '@angular/material/progress-bar'; HashesComponent ], imports: [ + MatFormFieldModule, + MatInputModule, HashlistRoutingModule, ReactiveFormsModule, + CoreFormsModule, CoreComponentsModule, FontAwesomeModule, DataTablesModule, - MatSlideToggleModule, - MatAutocompleteModule, - MatOptionModule, - MatCardModule, - MatButtonModule, - MatCheckboxModule, - MatDividerModule, - MatIconModule, - MatInputModule, - MatProgressBarModule, - MatProgressSpinnerModule, - MatDialogModule, - MatChipsModule, - MatSelectModule, - MatTooltipModule, - MatFormFieldModule, DirectivesModule, ComponentsModule, + CoreFormsModule, CommonModule, RouterModule, FormsModule, diff --git a/src/app/hashlists/new-hashlist/new-hashlist.component.html b/src/app/hashlists/new-hashlist/new-hashlist.component.html index fce88d7f..612e53c2 100644 --- a/src/app/hashlists/new-hashlist/new-hashlist.component.html +++ b/src/app/hashlists/new-hashlist/new-hashlist.component.html @@ -44,13 +44,11 @@
Hashcat Brain Enabled
- - - Note: When brain is enabled, it'll create a network server. If used wrongly, - it could cause bottlenecks or collapse the network server. You can disable - this setting in Config > Server > General Settings. - - +
@@ -80,23 +78,13 @@
Hashcat Brain Enabled

- - - + >
diff --git a/src/app/hashlists/search-hash/search-hash.component.html b/src/app/hashlists/search-hash/search-hash.component.html index fd7ce27f..89560976 100644 --- a/src/app/hashlists/search-hash/search-hash.component.html +++ b/src/app/hashlists/search-hash/search-hash.component.html @@ -1,25 +1,12 @@ - - - -
-
- -
- - - - -
-
- Minimum 1 hash required!
+ + + + + + + -
+
diff --git a/src/app/shared/alert/fixed-alert/fixed-alert.component.html b/src/app/shared/alert/fixed-alert/fixed-alert.component.html new file mode 100644 index 00000000..2d65f0ec --- /dev/null +++ b/src/app/shared/alert/fixed-alert/fixed-alert.component.html @@ -0,0 +1,5 @@ + + + {{ message }} + + diff --git a/src/app/shared/alert/fixed-alert/fixed-alert.component.ts b/src/app/shared/alert/fixed-alert/fixed-alert.component.ts new file mode 100644 index 00000000..fc133478 --- /dev/null +++ b/src/app/shared/alert/fixed-alert/fixed-alert.component.ts @@ -0,0 +1,9 @@ +import { Component, Input } from '@angular/core'; + +@Component({ + selector: 'fixed-alert', + templateUrl: './fixed-alert.component.html' +}) +export class FixedAlertComponent { + @Input() message = ''; +} diff --git a/src/app/shared/components.module.ts b/src/app/shared/components.module.ts index c84938c4..c04c844e 100644 --- a/src/app/shared/components.module.ts +++ b/src/app/shared/components.module.ts @@ -10,31 +10,26 @@ import { ButtonTruncateTextComponent } from './table/button-truncate-text.compon import { HexconvertorComponent } from './utils/hexconvertor/hexconvertor.component'; import { TimeoutDialogComponent } from './dialog/timeout/timeout-dialog.component'; import { PassMatchComponent } from './password/pass-match/pass-match.component'; +import { FixedAlertComponent } from './alert/fixed-alert/fixed-alert.component'; import { CheatsheetComponent } from './alert/cheatsheet/cheatsheet.component'; import { FilterTextboxModule } from './filter-textbox/filter-textbox.module'; import { SwitchThemeModule } from './switch-theme/switch-theme.module'; -import { MatProgressBarModule } from '@angular/material/progress-bar'; import { TimeoutComponent } from './alert/timeout/timeout.component'; import { HorizontalNavModule } from './navigation/navigation.module'; import { PageTitleModule } from './page-headers/page-title.module'; import { PaginationModule } from './pagination/pagination.module'; import { DynamicFormModule } from './dynamic-form-builder/dynamicform.module'; -import { MatTableModule } from '@angular/material/table'; -import { MatTooltipModule } from '@angular/material/tooltip'; -import { MatDialogModule } from '@angular/material/dialog'; -import { MatFormFieldModule } from '@angular/material/form-field'; -import { MatInputModule } from '@angular/material/input'; -import { MatButtonModule } from '@angular/material/button'; import { GridModule } from './grid-containers/grid.module'; import { TableModule } from './table/table-actions.module'; +import { FlexLayoutModule } from '@angular/flex-layout'; import { AlertComponent } from './alert/alert.component'; import { ButtonsModule } from './buttons/buttons.module'; import { LottiesModule } from './lottie/lottie.module'; -import { MatIconModule } from '@angular/material/icon'; import { GraphsModule } from './graphs/graphs.module'; import { ColorPickerModule } from 'ngx-color-picker'; import { InputModule } from './input/input.module'; import { FormsModule } from '@angular/forms'; +import { CoreFormsModule } from './forms.module'; @NgModule({ declarations: [ @@ -46,29 +41,23 @@ import { FormsModule } from '@angular/forms'; HexconvertorComponent, PassStrenghtComponent, CheatsheetComponent, + FixedAlertComponent, PassMatchComponent, TimeoutComponent, AlertComponent ], imports: [ - MatProgressBarModule, FilterTextboxModule, HorizontalNavModule, DynamicFormModule, SwitchThemeModule, ColorPickerModule, PaginationModule, + CoreFormsModule, PageTitleModule, - MatButtonModule, - MatDialogModule, - MatTableModule, - MatTooltipModule, - MatFormFieldModule, - MatInputModule, + FlexLayoutModule, ButtonsModule, LottiesModule, - MatIconModule, - MatIconModule, GraphsModule, CommonModule, FormsModule, @@ -88,6 +77,7 @@ import { FormsModule } from '@angular/forms'; FilterTextboxModule, HorizontalNavModule, CheatsheetComponent, + FixedAlertComponent, PassMatchComponent, SwitchThemeModule, DynamicFormModule, diff --git a/src/app/shared/forms.module.ts b/src/app/shared/forms.module.ts new file mode 100644 index 00000000..6f38e57f --- /dev/null +++ b/src/app/shared/forms.module.ts @@ -0,0 +1,74 @@ +import { NgModule } from '@angular/core'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { MatTableModule } from '@angular/material/table'; +import { MatTooltipModule } from '@angular/material/tooltip'; +import { MatDialogModule } from '@angular/material/dialog'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatInputModule } from '@angular/material/input'; +import { MatButtonModule } from '@angular/material/button'; +import { FlexLayoutModule } from '@angular/flex-layout'; +import { MatIconModule } from '@angular/material/icon'; +import { MatAutocompleteModule } from '@angular/material/autocomplete'; +import { MatNativeDateModule } from '@angular/material/core'; +import { MatOptionModule } from '@angular/material/core'; +import { MatSelectModule } from '@angular/material/select'; +import { MatDividerModule } from '@angular/material/divider'; +import { MatCheckboxModule } from '@angular/material/checkbox'; +import { MatCardModule } from '@angular/material/card'; +import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; +import { MatChipsModule } from '@angular/material/chips'; +import { MatDatepickerModule } from '@angular/material/datepicker'; +import { MatProgressBarModule } from '@angular/material/progress-bar'; +import { MatSlideToggleModule } from '@angular/material/slide-toggle'; + +@NgModule({ + imports: [ + ReactiveFormsModule, + MatAutocompleteModule, + MatSlideToggleModule, + MatOptionModule, + MatSelectModule, + MatDividerModule, + MatCheckboxModule, + MatCardModule, + MatProgressSpinnerModule, + MatNativeDateModule, + MatDatepickerModule, + MatChipsModule, + MatProgressBarModule, + FormsModule, + MatButtonModule, + MatDialogModule, + MatTableModule, + MatTooltipModule, + MatFormFieldModule, + FlexLayoutModule, + MatInputModule, + MatIconModule + ], + exports: [ + ReactiveFormsModule, + MatAutocompleteModule, + MatSlideToggleModule, + MatOptionModule, + MatSelectModule, + MatDividerModule, + MatCheckboxModule, + MatCardModule, + MatProgressSpinnerModule, + MatNativeDateModule, + MatDatepickerModule, + MatChipsModule, + MatProgressBarModule, + FormsModule, + MatButtonModule, + MatDialogModule, + MatTableModule, + MatTooltipModule, + MatFormFieldModule, + FlexLayoutModule, + MatInputModule, + MatIconModule + ] +}) +export class CoreFormsModule {} diff --git a/src/app/shared/grid-containers/grid.module.ts b/src/app/shared/grid-containers/grid.module.ts index 4980ed72..e213bade 100644 --- a/src/app/shared/grid-containers/grid.module.ts +++ b/src/app/shared/grid-containers/grid.module.ts @@ -1,3 +1,4 @@ +import { SimulateFormFieldComponent } from './simulate-form-field/simulate-form-field.component'; import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; import { GridFormInputComponent } from './grid-formgroup'; import { GridAutoColComponent } from './grid-autocol'; @@ -17,14 +18,16 @@ import { NgModule } from '@angular/core'; NgbModule ], exports: [ + SimulateFormFieldComponent, GridFormInputComponent, GridAutoColComponent, GridMainComponent ], declarations: [ + SimulateFormFieldComponent, GridFormInputComponent, GridAutoColComponent, GridMainComponent ] }) -export class GridModule { } +export class GridModule {} diff --git a/src/app/shared/grid-containers/simulate-form-field/simulate-form-field.component.html b/src/app/shared/grid-containers/simulate-form-field/simulate-form-field.component.html new file mode 100644 index 00000000..196c86ac --- /dev/null +++ b/src/app/shared/grid-containers/simulate-form-field/simulate-form-field.component.html @@ -0,0 +1,10 @@ +
+
+ {{label}} +
+
+
+ {{ message}} +
+
+
diff --git a/src/app/shared/grid-containers/simulate-form-field/simulate-form-field.component.ts b/src/app/shared/grid-containers/simulate-form-field/simulate-form-field.component.ts new file mode 100644 index 00000000..0d2f14c7 --- /dev/null +++ b/src/app/shared/grid-containers/simulate-form-field/simulate-form-field.component.ts @@ -0,0 +1,11 @@ +// simulate-form-field.component.ts +import { Component, Input } from '@angular/core'; + +@Component({ + selector: 'simulate-form-field', + templateUrl: './simulate-form-field.component.html' +}) +export class SimulateFormFieldComponent { + @Input() label: string; + @Input() message: string; +} diff --git a/src/app/shared/input/color/color.component.html b/src/app/shared/input/color/color.component.html index b098008a..05d341ac 100644 --- a/src/app/shared/input/color/color.component.html +++ b/src/app/shared/input/color/color.component.html @@ -2,12 +2,13 @@ {{ title }}
-
diff --git a/src/app/shared/input/color/color.component.ts b/src/app/shared/input/color/color.component.ts index 84eb0db2..63515630 100644 --- a/src/app/shared/input/color/color.component.ts +++ b/src/app/shared/input/color/color.component.ts @@ -9,6 +9,7 @@ import { import { randomColor } from '../../../shared/utils/forms'; import { AbstractInputComponent } from '../abstract-input'; import { ChangeDetectorRef } from '@angular/core'; +import { Renderer2 } from '@angular/core'; /** * Custom Input Color Picker Component. @@ -36,10 +37,13 @@ import { ChangeDetectorRef } from '@angular/core'; ] }) export class InputColorComponent extends AbstractInputComponent { - constructor(private cdr: ChangeDetectorRef) { + constructor( + private cdr: ChangeDetectorRef, + private renderer: Renderer2 + ) { super(); } - @ViewChild('colorInput') colorInput: ElementRef; + @ViewChild('selectInput') colorInput: ElementRef; @Input() defaultColor = ''; @Input() randomColor = true; @@ -58,14 +62,14 @@ export class InputColorComponent extends AbstractInputComponent { generateRandomColor() { this.onChangeValue(randomColor()); - this.cdr.detectChanges(); + // this.cdr.detectChanges(); } onChangeValue(value) { this.value = value; this.onChange(value); - // When using generateRandomColor() dom needs to be update to reflect color change + const inputElement = this.colorInput.nativeElement; - inputElement.style.background = this.value; + this.renderer.setStyle(inputElement, 'background', this.value); } } diff --git a/src/app/shared/input/file/file.component.html b/src/app/shared/input/file/file.component.html new file mode 100644 index 00000000..faeb0def --- /dev/null +++ b/src/app/shared/input/file/file.component.html @@ -0,0 +1,17 @@ +
+ + attach_file + Choose File + + + +
diff --git a/src/app/shared/input/file/file.component.ts b/src/app/shared/input/file/file.component.ts new file mode 100644 index 00000000..e68f957d --- /dev/null +++ b/src/app/shared/input/file/file.component.ts @@ -0,0 +1,57 @@ +import { AbstractInputComponent } from '../abstract-input'; +import { + Component, + EventEmitter, + Input, + Output, + forwardRef +} from '@angular/core'; +import { NG_VALUE_ACCESSOR } from '@angular/forms'; +import { FileSizePipe } from 'src/app/core/_pipes/file-size.pipe'; + +/** + * Custom Input File Component. + * + * Usage Example: + * ```html + * + * ``` + */ +@Component({ + selector: 'input-file', + templateUrl: './file.component.html', + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => InputFileComponent), + multi: true + }, + FileSizePipe + ] +}) +export class InputFileComponent extends AbstractInputComponent { + @Input() accept = ''; + @Input() multiple = false; + @Output() filesSelected = new EventEmitter(); + constructor(private fs: FileSizePipe) { + super(); + } + + onChangeValue(value) { + this.value = value; + this.onChange(value); + } + + handleFileInput(event: any): void { + const fileToUpload = event.target.files[0]; + const fileSize = fileToUpload.size; + const fileName = fileToUpload.name; + $('.fileuploadspan').text( + ' ' + fileName + ' / Size: ' + this.fs.transform(fileSize, false) + ); + const files = event.target.files; + this.filesSelected.emit(files); + } +} diff --git a/src/app/shared/input/input.module.ts b/src/app/shared/input/input.module.ts index ffa19c5a..e0e9b7c0 100644 --- a/src/app/shared/input/input.module.ts +++ b/src/app/shared/input/input.module.ts @@ -1,21 +1,7 @@ +import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { ColorPickerModule } from 'ngx-color-picker'; -import { FormsModule, ReactiveFormsModule } from '@angular/forms'; -import { MatButtonModule } from '@angular/material/button'; -import { MatAutocompleteModule } from '@angular/material/autocomplete'; -import { MatCheckboxModule } from '@angular/material/checkbox'; -import { MatChipsModule } from '@angular/material/chips'; -import { MatDividerModule } from '@angular/material/divider'; -import { MatFormFieldModule } from '@angular/material/form-field'; -import { MatIconModule } from '@angular/material/icon'; -import { MatInputModule } from '@angular/material/input'; -import { MatOptionModule } from '@angular/material/core'; -import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; -import { MatSelectModule } from '@angular/material/select'; -import { MatTooltipModule } from '@angular/material/tooltip'; -import { MatDatepickerModule } from '@angular/material/datepicker'; -import { MatNativeDateModule } from '@angular/material/core'; -import { NgModule } from '@angular/core'; +import { CoreFormsModule } from '../forms.module'; import { InputMultiSelectComponent } from './multiselect/multiselect.component'; import { InputColorComponent } from './color/color.component'; import { InputCheckComponent } from './check/check.component'; @@ -24,32 +10,15 @@ import { InputDateComponent } from './date/date.component'; import { InputNumberComponent } from './number/number.component'; import { InputTextAreaComponent } from './text-area/text-area.component'; import { InputSelectComponent } from './select/select.component'; +import { InputFileComponent } from './file/file.component'; @NgModule({ - imports: [ - ColorPickerModule, - CommonModule, - FormsModule, - MatButtonModule, - MatAutocompleteModule, - MatCheckboxModule, - MatChipsModule, - MatDividerModule, - MatFormFieldModule, - MatIconModule, - MatInputModule, - MatOptionModule, - MatProgressSpinnerModule, - MatSelectModule, - MatTooltipModule, - MatDatepickerModule, - MatNativeDateModule, - ReactiveFormsModule - ], + imports: [CoreFormsModule, ColorPickerModule, CommonModule], exports: [ InputCheckComponent, InputColorComponent, InputDateComponent, + InputFileComponent, InputMultiSelectComponent, InputNumberComponent, InputSelectComponent, @@ -60,6 +29,7 @@ import { InputSelectComponent } from './select/select.component'; InputCheckComponent, InputColorComponent, InputDateComponent, + InputFileComponent, InputMultiSelectComponent, InputNumberComponent, InputSelectComponent, diff --git a/src/app/shared/input/text/text.component.html b/src/app/shared/input/text/text.component.html index d9fae4b8..6690c6b0 100644 --- a/src/app/shared/input/text/text.component.html +++ b/src/app/shared/input/text/text.component.html @@ -14,6 +14,7 @@ - +
- + +
-
-
- - - -
-
-
- - - -
-
-
- - - -
-
+ + +
- - - + + + + + + + + +
- - - + + +
- - - + + +
- - - + + +
- - - + + +
- - - + + +
- - - + + +
- - - + + +
+ +
+
@@ -163,7 +89,7 @@ {{ f.filename | shortenString:35 }} - + {{ f.fileType | fileType }} {{ f.size | fileSize:false }} @@ -173,3 +99,5 @@
+ + diff --git a/src/app/tasks/edit-preconfigured-tasks/edit-preconfigured-tasks.component.ts b/src/app/tasks/edit-preconfigured-tasks/edit-preconfigured-tasks.component.ts index e4f23c52..c5307cc7 100644 --- a/src/app/tasks/edit-preconfigured-tasks/edit-preconfigured-tasks.component.ts +++ b/src/app/tasks/edit-preconfigured-tasks/edit-preconfigured-tasks.component.ts @@ -1,5 +1,4 @@ -import { faHomeAlt, faPlus, faTrash, faInfoCircle, faEye, faLock} from '@fortawesome/free-solid-svg-icons'; -import { Component, OnInit, ViewChild, HostListener } from '@angular/core'; +import { Component, HostListener, OnInit, ViewChild } from '@angular/core'; import { FormControl, FormGroup, Validators } from '@angular/forms'; import { ActivatedRoute, Params, Router } from '@angular/router'; import { DataTableDirective } from 'angular-datatables'; @@ -10,170 +9,217 @@ import { GlobalService } from 'src/app/core/_services/main.service'; import { colorpicker } from '../../core/_constants/settings.config'; import { environment } from './../../../environments/environment'; import { PageTitle } from 'src/app/core/_decorators/autotitle'; +import { yesNo } from '../../core/_constants/general.config'; import { SERV } from '../../core/_services/main.config'; +import { UnsubscribeService } from 'src/app/core/_services/unsubscribe.service'; +import { ChangeDetectorRef } from '@angular/core'; +import { AutoTitleService } from 'src/app/core/_services/shared/autotitle.service'; +/** + * Represents the EditPreconfiguredTasksComponent responsible for editing a Pretask. + */ @Component({ selector: 'app-edit-preconfigured-tasks', templateUrl: './edit-preconfigured-tasks.component.html' }) -@PageTitle(['Edit Preconfigured Tasks']) -export class EditPreconfiguredTasksComponent implements OnInit{ +export class EditPreconfiguredTasksComponent implements OnInit { + /** Flag indicating whether data is still loading. */ + isLoading = true; - editMode = false; + /** Form group for the Pretask. */ + updateForm: FormGroup; + + /** Select Options. */ + selectYesno = yesNo; + + // Edit Options editedPretaskIndex: number; - editedPretask: any // Change to Model + editedPretask: any; // Change to Model - faInfoCircle=faInfoCircle; - faHome=faHomeAlt; - faTrash=faTrash; - faPlus=faPlus; - faLock=faLock; - faEye=faEye; + pretask: any = []; + files: any; //Add Model constructor( - private route:ActivatedRoute, + private unsubscribeService: UnsubscribeService, + private changeDetectorRef: ChangeDetectorRef, + private titleService: AutoTitleService, + private route: ActivatedRoute, private alert: AlertService, private gs: GlobalService, private router: Router - ) { } - - pretask: any = []; - color = ''; - colorpicker=colorpicker; - updateForm: FormGroup - private maxResults = environment.config.prodApiMaxResults - - // Table Files - @ViewChild(DataTableDirective, {static: false}) - dtElement: DataTableDirective; - - dtTrigger: Subject = new Subject(); - dtOptions: any = {}; + ) { + this.getInitialization(); + this.buildForm(); + titleService.set(['Edit Preconfigured Tasks']); + } - files: any //Add Model + /** + * Initializes the form based on route parameters. + */ + getInitialization() { + this.route.params.subscribe((params: Params) => { + this.editedPretaskIndex = +params['id']; + this.updateFormValues(); + }); + } + /** + * Lifecycle hook called after component initialization. + */ ngOnInit(): void { + this.loadData(); + } - this.route.params - .subscribe( - (params: Params) => { - this.editedPretaskIndex = +params['id']; - this.editMode = params['id'] != null; - this.initForm(); - } - ); + /** + * Lifecycle hook called before the component is destroyed. + * Unsubscribes from all subscriptions to prevent memory leaks. + */ + ngOnDestroy(): void { + this.unsubscribeService.unsubscribeAll(); + } + /** + * Builds the form for creating a new SuperHashlist. + */ + buildForm(): void { this.updateForm = new FormGroup({ - 'pretaskId': new FormControl({value: '', disabled: true}), - 'statusTimer': new FormControl({value: '', disabled: true}), - 'useNewBench': new FormControl({value: '', disabled: true}), - 'updateData': new FormGroup({ - 'taskName': new FormControl(''), - 'attackCmd': new FormControl(''), - 'chunkTime': new FormControl(''), - 'color': new FormControl(''), - 'priority': new FormControl(''), - 'maxAgents': new FormControl(''), - 'isCpuTask': new FormControl(''), - 'isSmall': new FormControl(''), - }), + pretaskId: new FormControl({ value: '', disabled: true }), + statusTimer: new FormControl({ value: '', disabled: true }), + useNewBench: new FormControl({ value: '', disabled: true }), + updateData: new FormGroup({ + taskName: new FormControl(''), + attackCmd: new FormControl(''), + chunkTime: new FormControl(''), + color: new FormControl(''), + priority: new FormControl(''), + maxAgents: new FormControl(''), + isCpuTask: new FormControl(''), + isSmall: new FormControl('') + }) }); + } - // Files Table - + /** + * Loads data, specifically Pretasks, for the component. + */ + loadData(): void { const params = { - 'maxResults': this.maxResults, - 'filter': 'pretaskId='+this.editedPretaskIndex+'', - 'expand': 'pretaskFiles' - } + filter: 'pretaskId=' + this.editedPretaskIndex + '', + expand: 'pretaskFiles' + }; + const loadtableSubscription$ = this.gs + .getAll(SERV.PRETASKS, params) + .subscribe((pretasks: any) => { + this.files = pretasks.values; + this.dtTrigger.next(void 0); + }); - this.gs.getAll(SERV.PRETASKS,params).subscribe((pretasks: any) => { - this.files = pretasks.values; - this.dtTrigger.next(void 0); - }); + this.unsubscribeService.add(loadtableSubscription$); this.dtOptions = { dom: 'Bfrtip', scrollX: true, pageLength: 25, lengthMenu: [ - [10, 25, 50, 100, 250, -1], - [10, 25, 50, 100, 250, 'All'] + [10, 25, 50, 100, 250, -1], + [10, 25, 50, 100, 250, 'All'] ], stateSave: true, select: true, - buttons: [ ] + buttons: [] }; - - } - - OnChangeValue(value){ - this.updateForm.patchValue({ - updateData:{color: value} - }); } - onSubmit(){ + /** + * Handles form submission, edit Pretask + * If the form is valid, it makes an API request and navigates to the SuperHashlist page. + */ + onSubmit() { if (this.updateForm.valid) { - - this.gs.update(SERV.PRETASKS,this.editedPretaskIndex,this.updateForm.value['updateData']).subscribe(() => { - this.alert.okAlert('PreTask saved!',''); - this.updateForm.reset(); // success, we reset form - this.router.navigate(['tasks/preconfigured-tasks']); - } - ); + const updateSubscription$ = this.gs + .update( + SERV.PRETASKS, + this.editedPretaskIndex, + this.updateForm.value['updateData'] + ) + .subscribe(() => { + this.alert.okAlert('PreTask saved!', ''); + this.updateForm.reset(); // success, we reset form + this.router.navigate(['tasks/preconfigured-tasks']); + }); + this.unsubscribeService.add(updateSubscription$); } } - private initForm() { - if (this.editMode) { - this.gs.get(SERV.PRETASKS,this.editedPretaskIndex).subscribe((result)=>{ + /** + * Updates the form values based on the data fetched from the server. + * This method retrieves the pre-task data from the server and updates the form controls accordingly. + */ + private updateFormValues() { + this.gs.get(SERV.PRETASKS, this.editedPretaskIndex).subscribe((result) => { this.pretask = result; - this.color = result['color']; this.updateForm = new FormGroup({ - 'pretaskId': new FormControl({value: result['pretaskId'], disabled: true}), - 'statusTimer': new FormControl({value: result['statusTimer'], disabled: true}), - 'useNewBench': new FormControl({value: result['useNewBench'], disabled: true}), - 'updateData': new FormGroup({ - 'taskName': new FormControl(result['taskName'], Validators.required), - 'attackCmd': new FormControl(result['attackCmd'], Validators.required), - 'chunkTime': new FormControl(result['chunkTime']), - 'color': new FormControl(result['color']), - 'priority': new FormControl(result['priority']), - 'maxAgents': new FormControl(result['maxAgents']), - 'isCpuTask': new FormControl(result['isCpuTask'], Validators.required), - 'isSmall': new FormControl(result['isSmall'], Validators.required), + pretaskId: new FormControl({ + value: result['pretaskId'], + disabled: true + }), + statusTimer: new FormControl({ + value: result['statusTimer'], + disabled: true + }), + useNewBench: new FormControl({ + value: result['useNewBench'], + disabled: true }), + updateData: new FormGroup({ + taskName: new FormControl(result['taskName'], Validators.required), + attackCmd: new FormControl(result['attackCmd'], Validators.required), + chunkTime: new FormControl(result['chunkTime']), + color: new FormControl(result['color']), + priority: new FormControl(result['priority']), + maxAgents: new FormControl(result['maxAgents']), + isCpuTask: new FormControl(result['isCpuTask'], Validators.required), + isSmall: new FormControl(result['isSmall'], Validators.required) + }) }); }); - } } - getFileEdit(value:any){ - if(value == 0){ + getFileEdit(value: any) { + if (value == 0) { return 'wordlist-edit'; - } if(value == 1){ + } + if (value == 1) { return 'rules-edit'; - } if(value == 2){ + } + if (value == 2) { return 'other-edit'; - } else{ + } else { return 'error'; } } - // @HostListener allows us to also guard against browser refresh, close, etc. - @HostListener('window:beforeunload', ['$event']) - unloadNotification($event: any) { - if (!this.canDeactivate()) { - $event.returnValue = "IE and Edge Message"; - } - } + // TABLES CODE - canDeactivate(): Observable | boolean { - if (this.updateForm.valid) { - return false; - } - return true; - } + // Table Files + @ViewChild(DataTableDirective, { static: false }) + dtElement: DataTableDirective; + + dtTrigger: Subject = new Subject(); + dtOptions: any = {}; + // // @HostListener allows us to also guard against browser refresh, close, etc. + // @HostListener('window:beforeunload', ['$event']) + // unloadNotification($event: any) { + // if (!this.canDeactivate()) { + // $event.returnValue = 'IE and Edge Message'; + // } + // } + + // canDeactivate(): Observable | boolean { + // if (this.updateForm.valid) { + // return false; + // } + // return true; + // } } diff --git a/src/app/tasks/edit-tasks/edit-tasks.component.ts b/src/app/tasks/edit-tasks/edit-tasks.component.ts index 4f14020a..ee63c76c 100644 --- a/src/app/tasks/edit-tasks/edit-tasks.component.ts +++ b/src/app/tasks/edit-tasks/edit-tasks.component.ts @@ -1,6 +1,28 @@ -import { TitleComponent, TitleComponentOption, ToolboxComponent, ToolboxComponentOption, TooltipComponent, TooltipComponentOption, GridComponent, GridComponentOption, VisualMapComponent, VisualMapComponentOption, DataZoomComponent, DataZoomComponentOption, MarkLineComponent, MarkLineComponentOption } from 'echarts/components'; -import { faHomeAlt, faEye, faEraser, faLock, faTrash, faPencil } from '@fortawesome/free-solid-svg-icons'; -import { Component, OnInit, HostListener, ViewChild } from '@angular/core'; +import { + DataZoomComponent, + DataZoomComponentOption, + GridComponent, + GridComponentOption, + MarkLineComponent, + MarkLineComponentOption, + TitleComponent, + TitleComponentOption, + ToolboxComponent, + ToolboxComponentOption, + TooltipComponent, + TooltipComponentOption, + VisualMapComponent, + VisualMapComponentOption +} from 'echarts/components'; +import { + faEraser, + faEye, + faHomeAlt, + faLock, + faPencil, + faTrash +} from '@fortawesome/free-solid-svg-icons'; +import { Component, HostListener, OnInit, ViewChild } from '@angular/core'; import { environment } from './../../../environments/environment'; import { ActivatedRoute, Params, Router } from '@angular/router'; import { LineChart, LineSeriesOption } from 'echarts/charts'; @@ -27,34 +49,33 @@ import { SERV } from '../../core/_services/main.config'; providers: [FileSizePipe] }) @PageTitle(['Edit Task']) -export class EditTasksComponent implements OnInit,PendingChangesGuard { - +export class EditTasksComponent implements OnInit { editMode = false; editedTaskIndex: number; taskWrapperId: number; - editedTask: any // Change to Model + editedTask: any; // Change to Model - faPencil=faPencil; - faEraser=faEraser; - faHome=faHomeAlt; - faTrash=faTrash; - faLock=faLock; - faEye=faEye; + faPencil = faPencil; + faEraser = faEraser; + faHome = faHomeAlt; + faTrash = faTrash; + faLock = faLock; + faEye = faEye; private maxResults = environment.config.prodApiMaxResults; constructor( - private uiService:UIConfigService, + private uiService: UIConfigService, private route: ActivatedRoute, private alert: AlertService, private gs: GlobalService, - private fs:FileSizePipe, + private fs: FileSizePipe, private router: Router - ) { } + ) {} updateForm: FormGroup; createForm: FormGroup; // Assign Agent - colorpicker=colorpicker; + colorpicker = colorpicker; color = ''; @ViewChild(DataTableDirective) @@ -65,176 +86,207 @@ export class EditTasksComponent implements OnInit,PendingChangesGuard { dtOptions: any = {}; dtOptions1: any = {}; tusepreprocessor: any; - hashlistDescrip:any; - hashlistinform:any; + hashlistDescrip: any; + hashlistinform: any; assigAgents: any; - availAgents:any; - crackerinfo:any; + availAgents: any; + crackerinfo: any; tkeyspace: any; getchunks: any; getFiles: any; ngOnInit() { - - this.route.params - .subscribe( - (params: Params) => { - this.editedTaskIndex = +params['id']; - this.editMode = params['id'] != null; - this.initForm(); - this.assignChunksInit(this.editedTaskIndex); - } - ); + this.route.params.subscribe((params: Params) => { + this.editedTaskIndex = +params['id']; + this.editMode = params['id'] != null; + this.initForm(); + this.assignChunksInit(this.editedTaskIndex); + }); this.updateForm = new FormGroup({ - 'taskId': new FormControl({value: '', disabled: true}), - 'forcePipe': new FormControl({value: '', disabled: true}), - 'skipKeyspace': new FormControl({value: '', disabled: true}), - 'keyspace': new FormControl({value: '', disabled: true}), - 'keyspaceProgress': new FormControl({value: '', disabled: true}), - 'crackerBinaryId': new FormControl({value: '', disabled: true}), - 'chunkSize': new FormControl({value: '', disabled: true}), - 'updateData': new FormGroup({ - 'taskName': new FormControl(''), - 'attackCmd': new FormControl(''), - 'notes': new FormControl(''), - 'color': new FormControl(''), - 'chunkTime': new FormControl(''), - 'statusTimer': new FormControl(''), - 'priority': new FormControl(''), - 'maxAgents': new FormControl(''), - 'isCpuTask': new FormControl(''), - 'isSmall': new FormControl(''), - }), + taskId: new FormControl({ value: '', disabled: true }), + forcePipe: new FormControl({ value: '', disabled: true }), + skipKeyspace: new FormControl({ value: '', disabled: true }), + keyspace: new FormControl({ value: '', disabled: true }), + keyspaceProgress: new FormControl({ value: '', disabled: true }), + crackerBinaryId: new FormControl({ value: '', disabled: true }), + chunkSize: new FormControl({ value: '', disabled: true }), + updateData: new FormGroup({ + taskName: new FormControl(''), + attackCmd: new FormControl(''), + notes: new FormControl(''), + color: new FormControl(''), + chunkTime: new FormControl(''), + statusTimer: new FormControl(''), + priority: new FormControl(''), + maxAgents: new FormControl(''), + isCpuTask: new FormControl(''), + isSmall: new FormControl('') + }) }); this.createForm = new FormGroup({ - 'agentId': new FormControl(), + agentId: new FormControl() }); - } - OnChangeValue(value){ + OnChangeValue(value) { this.updateForm.patchValue({ - updateData:{color: value} + updateData: { color: value } }); } - onSubmit(){ + onSubmit() { if (this.updateForm.valid) { - this.gs.update(SERV.TASKS,this.editedTaskIndex,this.updateForm.value['updateData']).subscribe(() => { - this.alert.okAlert('Task saved!',''); + this.gs + .update( + SERV.TASKS, + this.editedTaskIndex, + this.updateForm.value['updateData'] + ) + .subscribe(() => { + this.alert.okAlert('Task saved!', ''); this.updateForm.reset(); // success, we reset form this.router.navigate(['tasks/show-tasks']); - } - ); + }); } } private initForm() { if (this.editMode) { - this.gs.get(SERV.TASKS,this.editedTaskIndex, {'expand': 'hashlist,speeds,crackerBinary,crackerBinaryType,files'}).subscribe((result)=>{ - this.color = result['color']; - this.getFiles = result.files; - this.crackerinfo = result.crackerBinary; - this.taskWrapperId - result.taskWrapperId; - // Graph Speed - this.initTaskSpeed(result.speeds); - // Assigned Agents init - this.assingAgentInit(); - // Hashlist Description and Type - this.hashlistinform = result.hashlist[0]; - this.gs.getAll(SERV.HASHTYPES,{'filter': 'hashTypeId='+result.hashlist[0]['hashTypeId']+''}).subscribe((htypes: any) => { - this.hashlistDescrip = htypes.values[0].description; - }) - this.tkeyspace = result['keyspace']; - this.tusepreprocessor = result['preprocessorId']; - this.updateForm = new FormGroup({ - 'taskId': new FormControl(result['taskId']), - 'forcePipe': new FormControl({value: result['forcePipe']== true? 'Yes':'No', disabled: true}), - 'skipKeyspace': new FormControl({value: result['skipKeyspace'] > 0?result['skipKeyspace']:'N/A', disabled: true}), - 'keyspace': new FormControl({value: result['keyspace'], disabled: true}), - 'keyspaceProgress': new FormControl({value: result['keyspaceProgress'], disabled: true}), - 'crackerBinaryId': new FormControl(result['crackerBinaryId']), - 'chunkSize': new FormControl({value: result['chunkSize'], disabled: true}), - 'updateData': new FormGroup({ - 'taskName': new FormControl(result['taskName']), - 'attackCmd': new FormControl(result['attackCmd']), - 'notes': new FormControl(result['notes']), - 'color': new FormControl(result['color']), - 'chunkTime': new FormControl(Number(result['chunkTime'])), - 'statusTimer': new FormControl(result['statusTimer']), - 'priority': new FormControl(result['priority']), - 'maxAgents': new FormControl(result['maxAgents']), - 'isCpuTask': new FormControl(result['isCpuTask']), - 'isSmall': new FormControl(result['isSmall']), - }), - }); - }); - } + this.gs + .get(SERV.TASKS, this.editedTaskIndex, { + expand: 'hashlist,speeds,crackerBinary,crackerBinaryType,files' + }) + .subscribe((result) => { + this.color = result['color']; + this.getFiles = result.files; + this.crackerinfo = result.crackerBinary; + this.taskWrapperId - result.taskWrapperId; + // Graph Speed + this.initTaskSpeed(result.speeds); + // Assigned Agents init + this.assingAgentInit(); + // Hashlist Description and Type + this.hashlistinform = result.hashlist[0]; + this.gs + .getAll(SERV.HASHTYPES, { + filter: 'hashTypeId=' + result.hashlist[0]['hashTypeId'] + '' + }) + .subscribe((htypes: any) => { + this.hashlistDescrip = htypes.values[0].description; + }); + this.tkeyspace = result['keyspace']; + this.tusepreprocessor = result['preprocessorId']; + this.updateForm = new FormGroup({ + taskId: new FormControl(result['taskId']), + forcePipe: new FormControl({ + value: result['forcePipe'] == true ? 'Yes' : 'No', + disabled: true + }), + skipKeyspace: new FormControl({ + value: + result['skipKeyspace'] > 0 ? result['skipKeyspace'] : 'N/A', + disabled: true + }), + keyspace: new FormControl({ + value: result['keyspace'], + disabled: true + }), + keyspaceProgress: new FormControl({ + value: result['keyspaceProgress'], + disabled: true + }), + crackerBinaryId: new FormControl(result['crackerBinaryId']), + chunkSize: new FormControl({ + value: result['chunkSize'], + disabled: true + }), + updateData: new FormGroup({ + taskName: new FormControl(result['taskName']), + attackCmd: new FormControl(result['attackCmd']), + notes: new FormControl(result['notes']), + color: new FormControl(result['color']), + chunkTime: new FormControl(Number(result['chunkTime'])), + statusTimer: new FormControl(result['statusTimer']), + priority: new FormControl(result['priority']), + maxAgents: new FormControl(result['maxAgents']), + isCpuTask: new FormControl(result['isCpuTask']), + isSmall: new FormControl(result['isSmall']) + }) + }); + }); + } } -/** - * The below functions are related with assign, manage and delete agents - * -**/ - assingAgentInit(){ - this.gs.getAll(SERV.AGENT_ASSIGN).subscribe((res)=>{ - this.gs.getAll(SERV.AGENTS,{'maxResults': this.maxResults}).subscribe((agents)=>{ - this.availAgents = this.getAvalAgents(res.values,agents.values); - this.assigAgents = res.values.map(mainObject => { - const matchObject = agents.values.find(element => element.agentId === mainObject.agentId) - return { ...mainObject, ...matchObject } - }) - this.dtTrigger1.next(void 0); - }); + /** + * The below functions are related with assign, manage and delete agents + * + **/ + assingAgentInit() { + this.gs.getAll(SERV.AGENT_ASSIGN).subscribe((res) => { + this.gs + .getAll(SERV.AGENTS, { maxResults: this.maxResults }) + .subscribe((agents) => { + this.availAgents = this.getAvalAgents(res.values, agents.values); + this.assigAgents = res.values.map((mainObject) => { + const matchObject = agents.values.find( + (element) => element.agentId === mainObject.agentId + ); + return { ...mainObject, ...matchObject }; + }); + this.dtTrigger1.next(void 0); + }); }); this.dtOptions1 = { dom: 'Bfrtip', - scrollY: "700px", + scrollY: '700px', scrollCollapse: true, paging: false, destroy: true, searching: false, bInfo: false, - buttons:[] - } + buttons: [] + }; } - getAvalAgents(assing: any, agents: any){ - - return agents.filter(u => assing.findIndex(lu => lu.agentId === u.agentId) === -1); - + getAvalAgents(assing: any, agents: any) { + return agents.filter( + (u) => assing.findIndex((lu) => lu.agentId === u.agentId) === -1 + ); } - asignAgents(){ + asignAgents() { if (this.createForm.valid) { - const payload = {"taskId": this.editedTaskIndex, "agentId":this.createForm.value['agentId']}; - this.gs.create(SERV.AGENT_ASSIGN,payload).subscribe(() => { - this.alert.okAlert('Agent assigned!',''); - this.rerender(); // rerender datatables - this.ngOnInit(); - } - ); + const payload = { + taskId: this.editedTaskIndex, + agentId: this.createForm.value['agentId'] + }; + this.gs.create(SERV.AGENT_ASSIGN, payload).subscribe(() => { + this.alert.okAlert('Agent assigned!', ''); + this.rerender(); // rerender datatables + this.ngOnInit(); + }); } } - onDelete(id: number){ - this.gs.delete(SERV.AGENT_ASSIGN,id).subscribe(() => { - this.alert.okAlert('Deleted',''); - this.rerender(); // rerender datatables + onDelete(id: number) { + this.gs.delete(SERV.AGENT_ASSIGN, id).subscribe(() => { + this.alert.okAlert('Deleted', ''); + this.rerender(); // rerender datatables this.ngOnInit(); }); } - onModalUpdate(title: string, id: number, cvalue: any, nameref: string ){ + onModalUpdate(title: string, id: number, cvalue: any, nameref: string) { (async () => { - const { value: formValues } = await Swal.fire({ - title: title + ' - '+ nameref, + title: title + ' - ' + nameref, html: - '', + '', focusConfirm: false, showCancelButton: true, cancelButtonColor: this.alert.cancelButtonColor, @@ -242,46 +294,47 @@ export class EditTasksComponent implements OnInit,PendingChangesGuard { cancelButton: true, preConfirm: () => { return [ - (document.getElementById('project-input')).value, - ] + (document.getElementById('project-input')).value + ]; } - }) + }); if (formValues) { - if(cvalue !== Number(formValues[0])){ - this.gs.update(SERV.AGENT_ASSIGN,id, {benchmark: +formValues}).subscribe(() => { - this.alert.okAlert('Task saved!',''); - this.ngOnInit(); - this.rerender(); // rerender datatables - }); + if (cvalue !== Number(formValues[0])) { + this.gs + .update(SERV.AGENT_ASSIGN, id, { benchmark: +formValues }) + .subscribe(() => { + this.alert.okAlert('Task saved!', ''); + this.ngOnInit(); + this.rerender(); // rerender datatables + }); } } - - })() + })(); } -/** - * This function calculates Keyspace searched, Time Spent and Estimated Time - * -**/ + /** + * This function calculates Keyspace searched, Time Spent and Estimated Time + * + **/ // Keyspace searched cprogress: any; // Time Spent ctimespent: any; - timeCalc(chunks){ - const cprogress = []; - const timespent = []; - const current = 0; - for(let i=0; i < chunks.length; i++){ - cprogress.push(chunks[i].checkpoint - chunks[i].skip); - if(chunks[i].dispatchTime > current){ - timespent.push(chunks[i].solveTime - chunks[i].dispatchTime); - } else if (chunks[i].solveTime > current) { - timespent.push(chunks[i].solveTime- current); - } + timeCalc(chunks) { + const cprogress = []; + const timespent = []; + const current = 0; + for (let i = 0; i < chunks.length; i++) { + cprogress.push(chunks[i].checkpoint - chunks[i].skip); + if (chunks[i].dispatchTime > current) { + timespent.push(chunks[i].solveTime - chunks[i].dispatchTime); + } else if (chunks[i].solveTime > current) { + timespent.push(chunks[i].solveTime - current); } - this.cprogress = cprogress.reduce((a, i) => a + i); - this.ctimespent = timespent.reduce((a, i) => a + i); + } + this.cprogress = cprogress.reduce((a, i) => a + i); + this.ctimespent = timespent.reduce((a, i) => a + i); } // Chunk View @@ -292,28 +345,26 @@ export class EditTasksComponent implements OnInit,PendingChangesGuard { chunkresults: Object; activechunks: Object; - assignChunksInit(id: number){ - this.route.data.subscribe(data => { + assignChunksInit(id: number) { + this.route.data.subscribe((data) => { switch (data['kind']) { - case 'edit-task': this.chunkview = 0; this.chunktitle = 'Live Chunks'; this.chunkresults = this.maxResults; - break; + break; case 'edit-task-c100': this.chunkview = 1; this.chunktitle = 'Latest 100 Chunks'; this.chunkresults = 100; - break; + break; case 'edit-task-cAll': this.chunkview = 2; this.chunktitle = 'All Chunks'; this.chunkresults = 60000; - break; - + break; } }); @@ -323,99 +374,134 @@ export class EditTasksComponent implements OnInit,PendingChangesGuard { scrollX: true, pageLength: 25, lengthMenu: [ - [10, 25, 50, 100, 250, -1], - [10, 25, 50, 100, 250, 'All'] + [10, 25, 50, 100, 250, -1], + [10, 25, 50, 100, 250, 'All'] ], - scrollY: "700px", + scrollY: '700px', scrollCollapse: true, paging: false, destroy: true, buttons: { - dom: { - button: { - className: 'dt-button buttons-collection btn btn-sm-dt btn-outline-gray-600-dt', - } - }, - buttons:[ - { - text: '↻', - autoClose: true, - action: function (e, dt, node, config) { - self.onRefresh(); - } - }, - { - text: self.chunkview === 0 ? 'Show Latest 100':'Show Live', - action: function () { - if(self.chunkview === 0) { - self.router.navigate(['/tasks/show-tasks',id,'edit','show-100-chunks']); - } - if(self.chunkview === 1) { - self.router.navigate(['/tasks/show-tasks',id,'edit']); - } - if(self.chunkview === 2) { - self.router.navigate(['/tasks/show-tasks',id,'edit']); - } + dom: { + button: { + className: + 'dt-button buttons-collection btn btn-sm-dt btn-outline-gray-600-dt' } }, - { - text: self.chunkview === 0 ? 'Show All':'Show Latest 100', - action: function () { - if(self.chunkview === 0) { - console.log(id) - self.router.navigate(['/tasks/show-tasks',id,'edit','show-all-chunks']); + buttons: [ + { + text: '↻', + autoClose: true, + action: function (e, dt, node, config) { + self.onRefresh(); } - if(self.chunkview === 1) { - self.router.navigate(['/tasks/show-tasks',id,'edit','show-all-chunks']); + }, + { + text: self.chunkview === 0 ? 'Show Latest 100' : 'Show Live', + action: function () { + if (self.chunkview === 0) { + self.router.navigate([ + '/tasks/show-tasks', + id, + 'edit', + 'show-100-chunks' + ]); + } + if (self.chunkview === 1) { + self.router.navigate(['/tasks/show-tasks', id, 'edit']); + } + if (self.chunkview === 2) { + self.router.navigate(['/tasks/show-tasks', id, 'edit']); + } } - if(self.chunkview === 2) { - self.router.navigate(['/tasks/show-tasks',id,'edit','show-100-chunks']); + }, + { + text: self.chunkview === 0 ? 'Show All' : 'Show Latest 100', + action: function () { + if (self.chunkview === 0) { + console.log(id); + self.router.navigate([ + '/tasks/show-tasks', + id, + 'edit', + 'show-all-chunks' + ]); + } + if (self.chunkview === 1) { + self.router.navigate([ + '/tasks/show-tasks', + id, + 'edit', + 'show-all-chunks' + ]); + } + if (self.chunkview === 2) { + self.router.navigate([ + '/tasks/show-tasks', + id, + 'edit', + 'show-100-chunks' + ]); + } } + }, + { + extend: 'colvis', + text: 'Column View', + columns: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] } - }, - { - extend: 'colvis', - text: 'Column View', - columns: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], - } - ] + ] } - } + }; - const params = {'maxResults': this.chunkresults}; - this.gs.getAll(SERV.CHUNKS,{'maxResults': this.chunkresults, 'filter': 'taskId='+id+''}).subscribe((result: any)=>{ - this.timeCalc(result.values); - // this.initVisualGraph(result.values, 150, 150); // Get data for visual graph - this.gs.getAll(SERV.AGENTS,params).subscribe((agents: any) => { - this.getchunks = result.values.map(mainObject => { - const matchObject = agents.values.find(element => element.agentId === mainObject.agentId) - return { ...mainObject, ...matchObject } - }) - if(this.chunkview == 0){ - const chunktime = this.uiService.getUIsettings('chunktime').value; - const resultArray = []; - const cspeed = []; - for(let i=0; i < this.getchunks.length; i++){ - if(Date.now()/1000 - Math.max(this.getchunks[i].solveTime, this.getchunks[i].dispatchTime) < chunktime && this.getchunks[i].progress < 10000){ - this.isactive = 1; - cspeed.push(this.getchunks[i].speed); - resultArray.push(this.getchunks[i]); + const params = { maxResults: this.chunkresults }; + this.gs + .getAll(SERV.CHUNKS, { + maxResults: this.chunkresults, + filter: 'taskId=' + id + '' + }) + .subscribe((result: any) => { + this.timeCalc(result.values); + // this.initVisualGraph(result.values, 150, 150); // Get data for visual graph + this.gs.getAll(SERV.AGENTS, params).subscribe((agents: any) => { + this.getchunks = result.values.map((mainObject) => { + const matchObject = agents.values.find( + (element) => element.agentId === mainObject.agentId + ); + return { ...mainObject, ...matchObject }; + }); + if (this.chunkview == 0) { + const chunktime = this.uiService.getUIsettings('chunktime').value; + const resultArray = []; + const cspeed = []; + for (let i = 0; i < this.getchunks.length; i++) { + if ( + Date.now() / 1000 - + Math.max( + this.getchunks[i].solveTime, + this.getchunks[i].dispatchTime + ) < + chunktime && + this.getchunks[i].progress < 10000 + ) { + this.isactive = 1; + cspeed.push(this.getchunks[i].speed); + resultArray.push(this.getchunks[i]); + } + } + if (cspeed.length > 0) { + this.currenspeed = cspeed.reduce((a, i) => a + i); + } + this.getchunks = resultArray; } - } - if(cspeed.length > 0){ - this.currenspeed = cspeed.reduce((a, i) => a + i); - } - this.getchunks = resultArray; - } - this.dtTrigger.next(void 0); + this.dtTrigger.next(void 0); + }); }); - }); - } - onRefresh(){ + onRefresh() { this.ngOnInit(); - this.rerender(); // rerender datatables + this.rerender(); // rerender datatables } rerender(): void { @@ -429,128 +515,130 @@ export class EditTasksComponent implements OnInit,PendingChangesGuard { }); } -/** - * Helper functions - * -**/ + /** + * Helper functions + * + **/ - purgeTask(){ + purgeTask() { const swalWithBootstrapButtons = Swal.mixin({ customClass: { confirmButton: 'btn', cancelButton: 'btn' }, buttonsStyling: false - }) + }); Swal.fire({ - title: "Are you sure?", + title: 'Are you sure?', text: "It'll purge the Task!", - icon: "warning", + icon: 'warning', reverseButtons: true, showCancelButton: true, cancelButtonColor: this.alert.cancelButtonColor, confirmButtonColor: this.alert.confirmButtonColor, confirmButtonText: this.alert.delconfirmText - }) - .then((result) => { + }).then((result) => { if (result.isConfirmed) { - let payload = {"taskId":this.editedTaskIndex}; - this.gs.chelper(SERV.HELPER,'purgeTask',payload).subscribe(() => { - this.alert.okAlert('Deleted '+name+'',''); + const payload = { taskId: this.editedTaskIndex }; + this.gs.chelper(SERV.HELPER, 'purgeTask', payload).subscribe(() => { + this.alert.okAlert('Deleted ' + name + '', ''); this.ngOnInit(); - this.rerender(); // rerender datatables + this.rerender(); // rerender datatables }); } else { swalWithBootstrapButtons.fire({ - title: "Cancelled", - text: "Your Task is safe!", - icon: "error", + title: 'Cancelled', + text: 'Your Task is safe!', + icon: 'error', showConfirmButton: false, timer: 1500 - }) + }); } }); - } - onReset(id: number, state: number){ - const path = state === 2 ? 'abortChunk' :'resetChunk' ; - const title = state === 2 ? 'Chunk Abort!' :'Chunk Reset!' ; - let payload = {'chunkId': id}; - this.gs.chelper(SERV.HELPER,path,payload).subscribe(() => { - this.alert.okAlert('Resetted!',''); + onReset(id: number, state: number) { + const path = state === 2 ? 'abortChunk' : 'resetChunk'; + const title = state === 2 ? 'Chunk Abort!' : 'Chunk Reset!'; + const payload = { chunkId: id }; + this.gs.chelper(SERV.HELPER, path, payload).subscribe(() => { + this.alert.okAlert('Resetted!', ''); this.ngOnInit(); this.rerender(); }); } -/** - * Task Speed Grap - * -**/ -initTaskSpeed(obj: object){ - - echarts.use([ - TitleComponent, - ToolboxComponent, - TooltipComponent, - GridComponent, - VisualMapComponent, - DataZoomComponent, - MarkLineComponent, - LineChart, - CanvasRenderer, - UniversalTransition - ]); - - type EChartsOption = echarts.ComposeOption< - | TitleComponentOption - | ToolboxComponentOption - | TooltipComponentOption - | GridComponentOption - | VisualMapComponentOption - | DataZoomComponentOption - | MarkLineComponentOption - | LineSeriesOption - >; - - const data:any = obj; - const arr = []; - const max = []; - const result = []; - - data.reduce(function(res, value) { - if (!res[value.time]) { - res[value.time] = { time: value.time, speed: 0 }; - result.push(res[value.time]) - } - res[value.time].speed += value.speed; - return res; - }, {}); - - for(let i=0; i < result.length; i++){ - - const iso = this.transDate(result[i]['time']); + /** + * Task Speed Grap + * + **/ + initTaskSpeed(obj: object) { + echarts.use([ + TitleComponent, + ToolboxComponent, + TooltipComponent, + GridComponent, + VisualMapComponent, + DataZoomComponent, + MarkLineComponent, + LineChart, + CanvasRenderer, + UniversalTransition + ]); + + type EChartsOption = echarts.ComposeOption< + | TitleComponentOption + | ToolboxComponentOption + | TooltipComponentOption + | GridComponentOption + | VisualMapComponentOption + | DataZoomComponentOption + | MarkLineComponentOption + | LineSeriesOption + >; + + const data: any = obj; + const arr = []; + const max = []; + const result = []; - arr.push([iso, this.fs.transform(result[i]['speed'],false,1000).match(/\d+(\.\d+)?/)[0], this.fs.transform(result[i]['speed'],false,1000).slice(-2)]); - max.push(result[i]['time']); - } + data.reduce(function (res, value) { + if (!res[value.time]) { + res[value.time] = { time: value.time, speed: 0 }; + result.push(res[value.time]); + } + res[value.time].speed += value.speed; + return res; + }, {}); + + for (let i = 0; i < result.length; i++) { + const iso = this.transDate(result[i]['time']); + + arr.push([ + iso, + this.fs + .transform(result[i]['speed'], false, 1000) + .match(/\d+(\.\d+)?/)[0], + this.fs.transform(result[i]['speed'], false, 1000).slice(-2) + ]); + max.push(result[i]['time']); + } - const startdate = max.slice(0)[0]; - const enddate = max.slice(-1)[0]; - console.log(enddate); - const datelabel = this.transDate(enddate); - const xAxis = this.generateIntervalsOf(1,+startdate,+enddate); + const startdate = max.slice(0)[0]; + const enddate = max.slice(-1)[0]; + console.log(enddate); + const datelabel = this.transDate(enddate); + const xAxis = this.generateIntervalsOf(1, +startdate, +enddate); - const chartDom = document.getElementById('tspeed'); - const myChart = echarts.init(chartDom); - let option: EChartsOption; + const chartDom = document.getElementById('tspeed'); + const myChart = echarts.init(chartDom); + let option: EChartsOption; - const self = this; + const self = this; - option = { + option = { title: { - subtext: 'Last record: '+ datelabel, + subtext: 'Last record: ' + datelabel }, tooltip: { position: 'top', @@ -560,7 +648,7 @@ initTaskSpeed(obj: object){ }, grid: { left: '5%', - right: '4%', + right: '4%' }, xAxis: { data: xAxis.map(function (item: any[] | any) { @@ -571,7 +659,7 @@ initTaskSpeed(obj: object){ type: 'value', name: 'H/s', position: 'left', - alignTicks: true, + alignTicks: true }, useUTC: true, toolbox: { @@ -584,7 +672,7 @@ initTaskSpeed(obj: object){ }, restore: {}, saveAsImage: { - name: "Task Speed" + name: 'Task Speed' } } }, @@ -600,41 +688,55 @@ initTaskSpeed(obj: object){ type: 'inside', start: 70, end: 100 - }, + } ], series: { name: '', type: 'line', data: arr, connectNulls: true, - markPoint: { - data: [ - { type: 'max', name: 'Max' }, - { type: 'min', name: 'Min' } - ] - }, + markPoint: { + data: [ + { type: 'max', name: 'Max' }, + { type: 'min', name: 'Min' } + ] + }, markLine: { lineStyle: { color: '#333' - }, - } + } } - }; - if(data.length > 0){ option && myChart.setOption(option);} - } + } + }; + if (data.length > 0) { + option && myChart.setOption(option); + } + } - leading_zeros(dt){ - return (dt < 10 ? '0' : '') + dt; - } + leading_zeros(dt) { + return (dt < 10 ? '0' : '') + dt; + } - transDate(dt){ - const date:any = new Date(dt* 1000); - // American Format - // return date.getUTCFullYear()+'-'+this.leading_zeros((date.getUTCMonth() + 1))+'-'+date.getUTCDate()+','+this.leading_zeros(date.getUTCHours())+':'+this.leading_zeros(date.getUTCMinutes())+':'+this.leading_zeros(date.getUTCSeconds()); - return date.getUTCDate()+'-'+this.leading_zeros((date.getUTCMonth() + 1))+'-'+date.getUTCFullYear()+','+this.leading_zeros(date.getUTCHours())+':'+this.leading_zeros(date.getUTCMinutes())+':'+this.leading_zeros(date.getUTCSeconds()); - } + transDate(dt) { + const date: any = new Date(dt * 1000); + // American Format + // return date.getUTCFullYear()+'-'+this.leading_zeros((date.getUTCMonth() + 1))+'-'+date.getUTCDate()+','+this.leading_zeros(date.getUTCHours())+':'+this.leading_zeros(date.getUTCMinutes())+':'+this.leading_zeros(date.getUTCSeconds()); + return ( + date.getUTCDate() + + '-' + + this.leading_zeros(date.getUTCMonth() + 1) + + '-' + + date.getUTCFullYear() + + ',' + + this.leading_zeros(date.getUTCHours()) + + ':' + + this.leading_zeros(date.getUTCMinutes()) + + ':' + + this.leading_zeros(date.getUTCSeconds()) + ); + } - generateIntervalsOf(interval, start, end) { + generateIntervalsOf(interval, start, end) { const result = []; let current = start; @@ -646,19 +748,18 @@ initTaskSpeed(obj: object){ return result; } - // @HostListener allows us to also guard against browser refresh, close, etc. + // @HostListener allows us to also guard against browser refresh, close, etc. @HostListener('window:beforeunload', ['$event']) unloadNotification($event: any) { if (!this.canDeactivate()) { - $event.returnValue = "IE and Edge Message"; + $event.returnValue = 'IE and Edge Message'; } } canDeactivate(): Observable | boolean { if (this.updateForm.valid) { - return false; + return false; } return true; } - } diff --git a/src/styles/components/_alert.scss b/src/styles/components/_alert.scss index d4c9bb6a..987e26c2 100644 --- a/src/styles/components/_alert.scss +++ b/src/styles/components/_alert.scss @@ -77,9 +77,13 @@ 02- Other */ -.closesys { // Custom to adjust margin in alert - margin-top: 2em; - margin: 2em auto; +.closesys { + margin-top: 1em; + margin-bottom: 1em; + margin-left: auto; + margin-right: auto; + background-color: $accent-900 !important; + color: white; } /* diff --git a/src/styles/components/_card.scss b/src/styles/components/_card.scss index 6d835a3f..98df6978 100644 --- a/src/styles/components/_card.scss +++ b/src/styles/components/_card.scss @@ -335,6 +335,52 @@ hr.break:after { } } -.info-message { - color: $color-red; /* Custom color for additional information */ + +/* + 03- Simulate a Mat Form Field to use in forms +*/ + +.simulate-form-field { + display: inline-block; + margin-bottom: 12px; + border-radius: 4px; + border: 1px solid #ccc; + overflow: hidden; + min-width: 210px; + max-width: 100%; + box-sizing: border-box; +} + +.simulate-form-field-label { + font-size: 12px; + color: rgba(0, 0, 0, 0.54); + padding: 4px 8px; + background-color: #fff; +} + +.simulate-form-field-flex { + display: flex; + box-sizing: border-box; + align-items: center; } + +.simulate-form-field-infix { + flex: 1; + min-width: 0; + padding: 8px; +} + +.simulate-form-field-content { + font-size: 14px; + color: rgba(0, 0, 0, 0.87); +} + +.simulate-form-field-disabled { + opacity: 0.5; + pointer-events: none; + background-color: #f0f0f0; + border: 1px solid #ccc; +} + + + diff --git a/src/styles/pages/_files.scss b/src/styles/pages/_files.scss index afa50428..fabccc4b 100644 --- a/src/styles/pages/_files.scss +++ b/src/styles/pages/_files.scss @@ -5,13 +5,8 @@ // Notes. Section to customize the files page .files-theme { - font-size: 12px; - line-height: 1; - font-weight: bold; color: #345F90; - text-transform: uppercase; - text-align: center; - padding: 11px 20px; + padding: 5px 20px; position: relative; z-index: 2; cursor: pointer; @@ -95,6 +90,7 @@ position: relative; overflow: hidden; display: inline-block; + cursor: pointer !important; } input.custom-file-input[type="file"] { @@ -103,6 +99,7 @@ input.custom-file-input[type="file"] { left: 0; top: 0; opacity: 0; + cursor: pointer !important; } .file-upload-label { From 4a66d71c845b66616c5a6923f3062bfbca911d7c Mon Sep 17 00:00:00 2001 From: itbf9 Date: Fri, 24 Nov 2023 14:16:30 +0100 Subject: [PATCH 303/419] wip --- .../_components/core-components.module.ts | 12 + .../tables/base-table/base-table.component.ts | 4 +- .../tables/ht-table/ht-table.component.html | 69 +-- .../tables/ht-table/ht-table.component.ts | 10 +- .../tables/ht-table/ht-table.models.ts | 12 + .../ht-table-type-default.component.html | 16 + .../ht-table-type-default.component.ts | 13 + .../ht-table-type-editable.component.html | 43 ++ .../ht-table-type-editable.component.ts | 65 +++ .../link/ht-table-type-link.component.html | 24 + .../type/link/ht-table-type-link.component.ts | 13 + .../tasks-table/tasks-table.component.html | 13 + .../tasks-table/tasks-table.component.ts | 536 ++++++++++++++++++ .../tasks-table/tasks-table.constants.ts | 21 + .../core/_datasources/agents.datasource.ts | 56 -- src/app/core/_datasources/base.datasource.ts | 77 ++- src/app/core/_datasources/tasks.datasource.ts | 64 +++ src/app/core/_models/chunk.model.ts | 2 + src/app/core/_models/task-wrapper.model.ts | 20 + src/app/hashlists/hashlist.model.ts | 37 +- .../show-tasks/show-tasks.component.html | 156 +---- .../tasks/show-tasks/show-tasks.component.ts | 489 +--------------- src/styles/components/_table.scss | 41 ++ 23 files changed, 1048 insertions(+), 745 deletions(-) create mode 100644 src/app/core/_components/tables/ht-table/type/default/ht-table-type-default.component.html create mode 100644 src/app/core/_components/tables/ht-table/type/default/ht-table-type-default.component.ts create mode 100644 src/app/core/_components/tables/ht-table/type/editable/ht-table-type-editable.component.html create mode 100644 src/app/core/_components/tables/ht-table/type/editable/ht-table-type-editable.component.ts create mode 100644 src/app/core/_components/tables/ht-table/type/link/ht-table-type-link.component.html create mode 100644 src/app/core/_components/tables/ht-table/type/link/ht-table-type-link.component.ts create mode 100644 src/app/core/_components/tables/tasks-table/tasks-table.component.html create mode 100644 src/app/core/_components/tables/tasks-table/tasks-table.component.ts create mode 100644 src/app/core/_components/tables/tasks-table/tasks-table.constants.ts create mode 100644 src/app/core/_datasources/tasks.datasource.ts create mode 100644 src/app/core/_models/task-wrapper.model.ts diff --git a/src/app/core/_components/core-components.module.ts b/src/app/core/_components/core-components.module.ts index dd7d2ff8..ae06f44b 100644 --- a/src/app/core/_components/core-components.module.ts +++ b/src/app/core/_components/core-components.module.ts @@ -19,6 +19,9 @@ import { CracksTableComponent } from './tables/cracks-table/cracks-table.compone import { ExportMenuComponent } from './menus/export-menu/export-menu.component'; import { FilesTableComponent } from './tables/files-table/files-table.component'; import { HTTableComponent } from './tables/ht-table/ht-table.component'; +import { HTTableTypeDefaultComponent } from './tables/ht-table/type/default/ht-table-type-default.component'; +import { HTTableTypeEditableComponent } from './tables/ht-table/type/editable/ht-table-type-editable.component'; +import { HTTableTypeLinkComponent } from './tables/ht-table/type/link/ht-table-type-link.component'; import { HashlistsTableComponent } from './tables/hashlists-table/hashlists-table.component'; import { HashtypesTableComponent } from './tables/hashtypes-table/hashtypes-table.component'; import { HealthChecksTableComponent } from './tables/health-checks-table/health-checks-table.component'; @@ -48,6 +51,7 @@ import { RowActionMenuComponent } from './menus/row-action-menu/row-action-menu. import { SuperHashlistsTableComponent } from './tables/super-hashlists-table/super-hashlists-table.component'; import { TableDialogComponent } from './tables/table-dialog/table-dialog.component'; import { TableTruncateComponent } from './tables/table-truncate/table-truncate.component'; +import { TasksTableComponent } from './tables/tasks-table/tasks-table.component'; import { UsersTableComponent } from './tables/users-table/users-table.component'; @NgModule({ @@ -56,6 +60,9 @@ import { UsersTableComponent } from './tables/users-table/users-table.component' TableTruncateComponent, BaseTableComponent, HTTableComponent, + HTTableTypeLinkComponent, + HTTableTypeDefaultComponent, + HTTableTypeEditableComponent, BaseMenuComponent, ActionMenuComponent, RowActionMenuComponent, @@ -77,6 +84,7 @@ import { UsersTableComponent } from './tables/users-table/users-table.component' AccessGroupsTableComponent, NotificationsTableComponent, PermissionsTableComponent, + TasksTableComponent, CracksTableComponent ], imports: [ @@ -107,6 +115,9 @@ import { UsersTableComponent } from './tables/users-table/users-table.component' BaseTableComponent, TableTruncateComponent, HTTableComponent, + HTTableTypeLinkComponent, + HTTableTypeDefaultComponent, + HTTableTypeEditableComponent, ColumnSelectionDialogComponent, BaseMenuComponent, ActionMenuComponent, @@ -128,6 +139,7 @@ import { UsersTableComponent } from './tables/users-table/users-table.component' AccessGroupsTableComponent, NotificationsTableComponent, PermissionsTableComponent, + TasksTableComponent, CracksTableComponent ], providers: [ 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 0854eb4f..8c5a0e1e 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 @@ -211,10 +211,10 @@ export class BaseTableComponent { ]; } - @Cacheable(['accessGroups']) + @Cacheable(['_id', 'accessGroups']) async renderAccessGroupLinks(obj: unknown): Promise { let links: HTTableRouterLink[] = []; - + console.log(obj); if (obj && obj['accessGroups'] && obj['accessGroups'].length) { links = obj['accessGroups'].map((accessGroup: AccessGroup) => { return { 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 0079ea9c..71a70d85 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 @@ -56,7 +56,6 @@ filter_list - + @@ -135,7 +147,7 @@ >
@@ -144,42 +156,19 @@ > - - - - - {{ link.label }} - - - {{ element[tableColumn.dataKey] }} - - - - - - - -
-
- - -
-
- - - {{ element[tableColumn.dataKey] }} - -
+ + +
diff --git a/src/app/core/_components/tables/ht-table/ht-table.component.ts b/src/app/core/_components/tables/ht-table/ht-table.component.ts index 015feba9..1bdde1a1 100644 --- a/src/app/core/_components/tables/ht-table/ht-table.component.ts +++ b/src/app/core/_components/tables/ht-table/ht-table.component.ts @@ -11,7 +11,7 @@ import { Output, ViewChild } from '@angular/core'; -import { DataType, HTTableColumn } from './ht-table.models'; +import { DataType, HTTableColumn, HTTableEditable } from './ht-table.models'; import { MatPaginator, PageEvent } from '@angular/material/paginator'; import { ActionMenuEvent } from '../../menus/action-menu/action-menu.model'; @@ -140,6 +140,10 @@ export class HTTableComponent implements OnInit, AfterViewInit { @Output() exportActionClicked: EventEmitter> = new EventEmitter>(); + /** Event emitter for when the user saves an editable input */ + @Output() editableSaved: EventEmitter = + new EventEmitter(); + /** Fetches user customizations */ private uiSettings: UISettingsUtilityClass; @@ -309,4 +313,8 @@ export class HTTableComponent implements OnInit, AfterViewInit { ); this.dataSource.reload(); } + + editableInputSaved(editable: HTTableEditable): void { + this.editableSaved.emit(editable); + } } diff --git a/src/app/core/_components/tables/ht-table/ht-table.models.ts b/src/app/core/_components/tables/ht-table/ht-table.models.ts index 7ff9dde6..1cda661c 100644 --- a/src/app/core/_components/tables/ht-table/ht-table.models.ts +++ b/src/app/core/_components/tables/ht-table/ht-table.models.ts @@ -17,6 +17,7 @@ export type DataType = | 'logs' | 'permissions' | 'cracks' + | 'tasks' | 'superhashlists'; export interface HTTableIcon { @@ -28,9 +29,19 @@ export interface HTTableIcon { export interface HTTableRouterLink { label?: string; routerLink: any[]; + tooltip?: string; +} + +export interface HTTableEditable { + id: number; + value: string; + action: string; } +export type HTTableColumnType = 'dafeult | link | editable'; + export interface HTTableColumn { + type?: HTTableColumnType; name: string; dataKey: string; position?: 'right' | 'left'; @@ -41,4 +52,5 @@ export interface HTTableColumn { routerLink?: (data: any) => Promise; export?: (data: any) => Promise; truncate?: boolean; + editable?: (data: any) => HTTableEditable; } diff --git a/src/app/core/_components/tables/ht-table/type/default/ht-table-type-default.component.html b/src/app/core/_components/tables/ht-table/type/default/ht-table-type-default.component.html new file mode 100644 index 00000000..beebf8f7 --- /dev/null +++ b/src/app/core/_components/tables/ht-table/type/default/ht-table-type-default.component.html @@ -0,0 +1,16 @@ + + + +
+
+ + +
+
+ + + {{ element[tableColumn.dataKey] }} + +
diff --git a/src/app/core/_components/tables/ht-table/type/default/ht-table-type-default.component.ts b/src/app/core/_components/tables/ht-table/type/default/ht-table-type-default.component.ts new file mode 100644 index 00000000..1d1daebc --- /dev/null +++ b/src/app/core/_components/tables/ht-table/type/default/ht-table-type-default.component.ts @@ -0,0 +1,13 @@ +import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; + +import { HTTableColumn } from '../../ht-table.models'; + +@Component({ + selector: 'ht-table-default', + templateUrl: './ht-table-type-default.component.html', + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class HTTableTypeDefaultComponent { + @Input() element: any; + @Input() tableColumn: HTTableColumn; +} diff --git a/src/app/core/_components/tables/ht-table/type/editable/ht-table-type-editable.component.html b/src/app/core/_components/tables/ht-table/type/editable/ht-table-type-editable.component.html new file mode 100644 index 00000000..6f7b31ad --- /dev/null +++ b/src/app/core/_components/tables/ht-table/type/editable/ht-table-type-editable.component.html @@ -0,0 +1,43 @@ +
+ + + + + + + + +
+ +
+
+
diff --git a/src/app/core/_components/tables/ht-table/type/editable/ht-table-type-editable.component.ts b/src/app/core/_components/tables/ht-table/type/editable/ht-table-type-editable.component.ts new file mode 100644 index 00000000..c46fb8a5 --- /dev/null +++ b/src/app/core/_components/tables/ht-table/type/editable/ht-table-type-editable.component.ts @@ -0,0 +1,65 @@ +import { + ChangeDetectionStrategy, + Component, + ElementRef, + EventEmitter, + Input, + OnInit, + Output, + ViewChild +} from '@angular/core'; +import { HTTableColumn, HTTableEditable } from '../../ht-table.models'; + +@Component({ + selector: 'ht-table-editable', + templateUrl: './ht-table-type-editable.component.html', + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class HTTableTypeEditableComponent implements OnInit { + editable: HTTableEditable; + + @Input() element: any; + @Input() tableColumn: HTTableColumn; + + @Output() editableInputSaved: EventEmitter = + new EventEmitter(); + @ViewChild('editableInput') editableInput: ElementRef; + + editMode = false; + + ngOnInit(): void { + if (this.tableColumn.editable) { + this.editable = this.tableColumn.editable(this.element); + } + } + + onSelect(event: MouseEvent): void { + event.stopPropagation(); + + this.editMode = true; + + setTimeout(() => { + if (this.editableInput) { + this.editableInput.nativeElement.focus(); + } + }, 100); + } + + onBlur(event: FocusEvent): void { + const targetElement = event.relatedTarget as HTMLElement; + if (!targetElement || targetElement.tagName.toLowerCase() !== 'button') { + event.stopPropagation(); + this.editMode = false; + } + } + + onFocus(event: FocusEvent): void { + event.stopPropagation(); + } + + onEditableInputSaved(event: MouseEvent): void { + event.stopPropagation(); + this.editableInputSaved.emit(this.editable); + this.editMode = false; + } +} 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 new file mode 100644 index 00000000..b1e026d1 --- /dev/null +++ b/src/app/core/_components/tables/ht-table/type/link/ht-table-type-link.component.html @@ -0,0 +1,24 @@ + + + + + + {{ 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 new file mode 100644 index 00000000..6e47e8a6 --- /dev/null +++ b/src/app/core/_components/tables/ht-table/type/link/ht-table-type-link.component.ts @@ -0,0 +1,13 @@ +import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; + +import { HTTableColumn } from '../../ht-table.models'; + +@Component({ + selector: 'ht-table-link', + templateUrl: './ht-table-type-link.component.html', + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class HTTableTypeLinkComponent { + @Input() element: any; + @Input() tableColumn: HTTableColumn; +} diff --git a/src/app/core/_components/tables/tasks-table/tasks-table.component.html b/src/app/core/_components/tables/tasks-table/tasks-table.component.html new file mode 100644 index 00000000..7e86a611 --- /dev/null +++ b/src/app/core/_components/tables/tasks-table/tasks-table.component.html @@ -0,0 +1,13 @@ + 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 new file mode 100644 index 00000000..b6399ef3 --- /dev/null +++ b/src/app/core/_components/tables/tasks-table/tasks-table.component.ts @@ -0,0 +1,536 @@ +import { + AfterViewInit, + Component, + OnDestroy, + OnInit, + Renderer2 +} from '@angular/core'; +import { + HTTableColumn, + HTTableEditable, + HTTableIcon, + HTTableRouterLink +} from '../ht-table/ht-table.models'; +import { catchError, forkJoin } from 'rxjs'; + +import { ActionMenuEvent } from '../../menus/action-menu/action-menu.model'; +import { BaseTableComponent } from '../base-table/base-table.component'; +import { BulkActionMenuAction } from '../../menus/bulk-action-menu/bulk-action-menu.constants'; +import { Cacheable } from 'src/app/core/_decorators/cacheable'; +import { ChunkData } from 'src/app/core/_models/chunk.model'; +import { DialogData } from '../table-dialog/table-dialog.model'; +import { RowActionMenuAction } from '../../menus/row-action-menu/row-action-menu.constants'; +import { SERV } from 'src/app/core/_services/main.config'; +import { SafeHtml } from '@angular/platform-browser'; +import { TableDialogComponent } from '../table-dialog/table-dialog.component'; +import { Task } from 'src/app/core/_models/task.model'; +import { + TaskTableColumnLabel, + TaskTableEditableAction +} from './tasks-table.constants'; +import { TaskWrapper } from 'src/app/core/_models/task-wrapper.model'; +import { TasksDataSource } from 'src/app/core/_datasources/tasks.datasource'; + +@Component({ + selector: 'tasks-table', + templateUrl: './tasks-table.component.html' +}) +export class TasksTableComponent + extends BaseTableComponent + implements OnInit, OnDestroy +{ + tableColumns: HTTableColumn[] = []; + dataSource: TasksDataSource; + chunkData: { [key: number]: ChunkData } = {}; + + ngOnInit(): void { + this.tableColumns = this.getColumns(); + this.dataSource = new TasksDataSource(this.cdr, this.gs, this.uiService); + this.dataSource.setColumns(this.tableColumns); + this.dataSource.loadAll(); + } + + ngOnDestroy(): void { + for (const sub of this.subscriptions) { + sub.unsubscribe(); + } + } + + filter(item: Task, filterValue: string): boolean { + /* + if (item.taskName.toLowerCase().includes(filterValue) || + item.clientSignature.toLowerCase().includes(filterValue) || + item.devices.toLowerCase().includes(filterValue)) { + return true + } +*/ + return false; + } + + getColumns(): HTTableColumn[] { + const tableColumns = [ + { + name: TaskTableColumnLabel.ID, + dataKey: '_id', + isSortable: true + }, + { + name: TaskTableColumnLabel.NAME, + dataKey: 'taskName', + routerLink: (wrapper: TaskWrapper) => + this.renderTaskWrapperLink(wrapper), + isSortable: true + }, + { + name: TaskTableColumnLabel.STATUS, + dataKey: 'keyspaceProgress', + async: (wrapper: TaskWrapper) => this.renderSpeed(wrapper), + icons: (wrapper: TaskWrapper) => this.renderStatusIcons(wrapper), + isSortable: false + }, + { + name: TaskTableColumnLabel.PRIORITY, + dataKey: 'priority', + editable: (wrapper: TaskWrapper) => { + return { + id: wrapper._id, + value: wrapper.priority + '', + action: TaskTableEditableAction.CHANGE_PRIORITY + }; + }, + isSortable: true + }, + { + name: TaskTableColumnLabel.PREPROCESSOR, + dataKey: 'preprocessorId', + render: (wrapper: TaskWrapper) => + wrapper.taskType === 0 && wrapper.tasks[0].preprocessorId === 1 + ? 'Prince' + : '', + isSortable: true + }, + { + name: TaskTableColumnLabel.HASHLISTS, + dataKey: 'userId', + routerLink: (wrapper: TaskWrapper) => this.renderHashlistLinks(wrapper), + isSortable: false + }, + { + name: TaskTableColumnLabel.DISPATCHED_SEARCHED, + dataKey: 'clientSignature', + async: (wrapper: TaskWrapper) => this.renderDispatchedSearched(wrapper), + isSortable: true + }, + { + name: TaskTableColumnLabel.CRACKED, + dataKey: 'cracked', + routerLink: (wrapper: TaskWrapper) => this.renderCrackedLink(wrapper), + isSortable: true + }, + { + name: TaskTableColumnLabel.AGENTS, + dataKey: 'agents', + async: (wrapper: TaskWrapper) => this.renderAgents(wrapper), + isSortable: false + }, + { + name: TaskTableColumnLabel.IS_SMALL, + dataKey: 'isSmall', + icons: (wrapper: TaskWrapper) => this.renderIsSmallIcon(wrapper), + isSortable: true + }, + { + name: TaskTableColumnLabel.IS_CPU_TASK, + dataKey: 'isCpuTask', + icons: (wrapper: TaskWrapper) => this.renderIsCpuTaskIcon(wrapper), + isSortable: true + } + ]; + + return tableColumns; + } + + rowActionClicked(event: ActionMenuEvent): void { + switch (event.menuItem.action) { + case RowActionMenuAction.EDIT: + this.rowActionEdit(event.data); + break; + case RowActionMenuAction.COPY_TO_TASK: + this.rowActionCopyToTask(event.data); + break; + case RowActionMenuAction.COPY_TO_PRETASK: + this.rowActionCopyToPretask(event.data); + break; + case RowActionMenuAction.EDIT_SUBTASKS: + console.log('edit-subtasks', event.data); + break; + case RowActionMenuAction.ARCHIVE: + this.rowActionArchive(event.data); + break; + case RowActionMenuAction.DELETE: + this.openDialog({ + rows: [event.data], + title: `Deleting ${event.data.taskName} ...`, + icon: 'warning', + body: `Are you sure you want to delete ${event.data.taskName}? Note that this action cannot be undone.`, + warn: true, + action: event.menuItem.action + }); + break; + } + } + + bulkActionClicked(event: ActionMenuEvent): void { + switch (event.menuItem.action) { + case BulkActionMenuAction.ARCHIVE: + this.openDialog({ + rows: event.data, + title: `Archiving ${event.data.length} tasks ...`, + icon: 'info', + listAttribute: 'taskName', + action: event.menuItem.action + }); + break; + case BulkActionMenuAction.DELETE: + this.openDialog({ + rows: event.data, + title: `Deleting ${event.data.length} tasks ...`, + icon: 'warning', + body: `Are you sure you want to permanently delete the selected tasks? Note that this action cannot be undone.`, + warn: true, + listAttribute: 'taskName', + action: event.menuItem.action + }); + break; + } + } + + openDialog(data: DialogData) { + const dialogRef = this.dialog.open(TableDialogComponent, { + data: data, + width: '450px' + }); + + this.subscriptions.push( + dialogRef.afterClosed().subscribe((result) => { + if (result && result.action) { + switch (result.action) { + case RowActionMenuAction.DELETE: + this.rowActionDelete(result.data); + break; + case BulkActionMenuAction.DELETE: + this.bulkActionDelete(result.data); + break; + } + } + }) + ); + } + + // --- Render functions --- + + @Cacheable(['_id', 'taskType']) + async renderTaskWrapperLink( + wrapper: TaskWrapper + ): Promise { + const links: HTTableRouterLink[] = []; + + if (wrapper.taskType === 0) { + for (const task of wrapper.tasks) { + const taskName = + task.taskName?.length > 40 + ? `${task.taskName.substring(40)}...` + : task.taskName; + + links.push({ + label: taskName, + routerLink: ['/tasks', 'show-tasks', task._id, 'edit'], + tooltip: task.attackCmd + }); + } + } else if (wrapper.taskType === 1) { + const taskWrapperName = + wrapper.taskWrapperName.length > 40 + ? `${wrapper.taskWrapperName.substring(40)}...` + : wrapper.taskWrapperName; + + links.push({ + label: taskWrapperName, + routerLink: ['/tasks', 'show-subtasks', wrapper._id], + tooltip: 'Supertask' + }); + } + + return links; + } + + @Cacheable(['_id', 'taskType', 'tasks']) + async renderStatusIcons(wrapper: TaskWrapper): Promise { + const icons: HTTableIcon[] = []; + + if (wrapper.taskType === 0 && wrapper.tasks.length > 0) { + const task: Task = wrapper.tasks[0]; + if (!(task._id in this.chunkData)) { + this.chunkData[task._id] = await this.dataSource.getChunkData( + task.taskId, + false, + task.keyspace + ); + } + const speed = this.chunkData[task._id].speed; + if (speed > 0) { + icons.push({ + name: 'radio_button_checked', + cls: 'pulsing-progress', + tooltip: 'In Progress' + }); + } else if ( + task.keyspaceProgress >= task.keyspace && + task.keyspaceProgress > 0 + ) { + icons.push({ + name: 'check', + tooltip: 'Completed' + }); + } else { + icons.push({ + name: 'radio_button_checked', + tooltip: 'Idle', + cls: 'text-primary' + }); + } + } + + return icons; + } + + @Cacheable(['_id', 'isSmall']) + async renderIsSmallIcon(wrapper: TaskWrapper): Promise { + return this.renderBoolIcon(wrapper, 'isSmall'); + } + + @Cacheable(['_id', 'isCpuTask']) + async renderIsCpuTaskIcon(wrapper: TaskWrapper): Promise { + return this.renderBoolIcon(wrapper, 'isCpuTask'); + } + + @Cacheable(['_id', 'taskType']) + async renderTaskTypeIcon(wrapper: TaskWrapper): Promise { + return this.renderBoolIcon(wrapper, 'taskType', 1); + } + + private renderBoolIcon( + wrapper: TaskWrapper, + key: string, + equals: any = '' + ): HTTableIcon[] { + const icons: HTTableIcon[] = []; + if (wrapper.taskType === 0) { + const task: Task = wrapper.tasks[0]; + if (equals === '') { + if (task[key] === true) { + icons.push({ + name: 'check', + cls: 'text-ok' + }); + } + } else if (task[key] === equals) { + icons.push({ + name: 'check', + cls: 'text-ok' + }); + } + } else { + if (equals === '') { + if (wrapper[key] === true) { + icons.push({ + name: 'check', + cls: 'text-ok' + }); + } + } else if (wrapper[key] === equals) { + icons.push({ + name: 'check', + cls: 'text-ok' + }); + } + } + + return icons; + } + + @Cacheable(['_id', 'taskType', 'tasks']) + async renderDispatchedSearched(wrapper: TaskWrapper): Promise { + let html = ''; + if (wrapper.taskType === 0) { + const task: Task = wrapper.tasks[0]; + if (task.keyspace > 0) { + if (!(task._id in this.chunkData)) { + this.chunkData[task._id] = await this.dataSource.getChunkData( + task.taskId, + false, + task.keyspace + ); + } + html = `${this.chunkData[task._id].dispatched} / ${ + this.chunkData[task._id].searched + }`; + } + } + return this.sanitize(html); + } + + @Cacheable(['_id', 'taskType', 'tasks']) + override async renderCrackedLink( + wrapper: TaskWrapper + ): Promise { + const links: HTTableRouterLink[] = []; + if (wrapper.taskType === 0) { + const task: Task = wrapper.tasks[0]; + if (!(task._id in this.chunkData)) { + this.chunkData[task._id] = await this.dataSource.getChunkData( + task.taskId, + false, + task.keyspace + ); + } + links.push({ + label: this.chunkData[task._id].cracked + '', + routerLink: ['/hashlists', 'hashes', 'tasks', task._id] + }); + } + + return links; + } + + @Cacheable(['_id', 'taskType', 'tasks']) + async renderAgents(wrapper: TaskWrapper): Promise { + let html = ''; + if (wrapper.taskType === 0) { + const task: Task = wrapper.tasks[0]; + if (!(task._id in this.chunkData)) { + this.chunkData[task._id] = await this.dataSource.getChunkData( + task.taskId, + false, + task.keyspace + ); + } + + html = task.maxAgents + ? `${this.chunkData[task._id].agents.length} / ${task.maxAgents}` + : `${this.chunkData[task._id].agents.length}`; + } + return this.sanitize(html); + } + + @Cacheable(['_id', 'taskType', 'tasks']) + async renderSpeed(wrapper: TaskWrapper): Promise { + let html = ''; + if (wrapper.taskType === 0) { + const task: Task = wrapper.tasks[0]; + if (!(task._id in this.chunkData)) { + this.chunkData[task._id] = await this.dataSource.getChunkData( + task._id, + false, + task.keyspace + ); + } + html = + this.chunkData[task._id].speed > 0 + ? `${this.chunkData[task._id].speed} H/s` + : ''; + } + return this.sanitize(html); + } + + // --- Action functions --- + + private rowActionEdit(task: Task): void { + this.router.navigate(['tasks', 'show-tasks', task._id, 'edit']); + } + + private bulkActionDelete(tasks: Task[]): void { + const requests = tasks.map((task: Task) => { + return this.gs.delete(SERV.TASKS, task._id); + }); + + this.subscriptions.push( + forkJoin(requests) + .pipe( + catchError((error) => { + console.error('Error during deletion:', error); + return []; + }) + ) + .subscribe((results) => { + this.snackBar.open( + `Successfully deleted ${results.length} tasks!`, + 'Close' + ); + this.dataSource.reload(); + }) + ); + } + + private rowActionDelete(task: Task): void { + this.subscriptions.push( + this.gs.delete(SERV.TASKS, task._id).subscribe(() => { + this.snackBar.open('Successfully deleted task!', 'Close'); + this.dataSource.reload(); + }) + ); + } + + private rowActionCopyToTask(task: Task): void { + this.router.navigate(['tasks', 'new-tasks', task._id, 'copy']); + } + + private rowActionCopyToPretask(task: Task): void { + this.router.navigate([ + 'tasks', + 'preconfigured-tasks', + task._id, + 'copytask' + ]); + } + + private rowActionArchive(task: Task): void { + this.subscriptions.push( + this.gs.archive(SERV.TASKS, task._id).subscribe(() => { + this.snackBar.open('Successfully archived task!', 'Close'); + this.dataSource.reload(); + }) + ); + } + + editableSaved(editable: HTTableEditable): void { + switch (editable.action) { + case TaskTableEditableAction.CHANGE_PRIORITY: + this.changePriority(editable.id, editable.value); + break; + } + } + + private changePriority(id: number, value: string): void { + const val = parseInt(value); + + const request$ = this.gs.update(SERV.TASKS_WRAPPER, id, { + priority: val + }); + + this.subscriptions.push( + request$ + .pipe( + catchError((error) => { + this.snackBar.open(`Failed to update prio!`, 'Close'); + console.error('Failed to update prio:', error); + return []; + }) + ) + .subscribe(() => { + this.snackBar.open(`Changed prio to ${val}!`, 'Close'); + this.reload(); + }) + ); + } +} diff --git a/src/app/core/_components/tables/tasks-table/tasks-table.constants.ts b/src/app/core/_components/tables/tasks-table/tasks-table.constants.ts new file mode 100644 index 00000000..3c4f15d4 --- /dev/null +++ b/src/app/core/_components/tables/tasks-table/tasks-table.constants.ts @@ -0,0 +1,21 @@ +export class TaskTableColumnLabel { + static readonly ID = 'ID'; + static readonly NAME = 'Name'; + static readonly STATUS = 'Status'; + static readonly ATTACK_CMD = 'Attack Command'; + static readonly IS_SMALL = 'Small Task'; + static readonly IS_CPU_TASK = 'CPU Task'; + static readonly TASK_TYPE_1 = 'Supertask'; + static readonly PREPROCESSOR = 'Preprocessor'; + static readonly HASHLISTS = 'Hashlists'; + static readonly DISPATCHED_SEARCHED = 'Dispatched/Searched'; + static readonly CRACKED = 'Cracked'; + static readonly AGENTS = 'Agents'; + static readonly SPEED = 'Speed'; + static readonly PRIORITY = 'Priority'; + static readonly MAX_AGENTS = 'Max Agents'; +} + +export const TaskTableEditableAction = { + CHANGE_PRIORITY: 'change-priority' +}; diff --git a/src/app/core/_datasources/agents.datasource.ts b/src/app/core/_datasources/agents.datasource.ts index fb839d85..c96eba81 100644 --- a/src/app/core/_datasources/agents.datasource.ts +++ b/src/app/core/_datasources/agents.datasource.ts @@ -130,62 +130,6 @@ export class AgentsDataSource extends BaseDataSource { ); } - async getChunkData(id: number, keyspace = 0): Promise { - const chunktime = this.uiService.getUIsettings('chunktime').value; - - const dispatched: number[] = []; - const searched: number[] = []; - const cracked: number[] = []; - const speed: number[] = []; - const timespent: number[] = []; - const now = Date.now(); - const current = 0; - - const params = { - maxResults: this.maxResults, - filter: `agentId=${id}` - }; - - const response: ListResponseWrapper = await firstValueFrom( - this.service.getAll(SERV.CHUNKS, params) - ); - - for (const chunk of response.values) { - if (chunk.progress >= 10000) { - dispatched.push(chunk.length); - } - cracked.push(chunk.cracked); - searched.push(chunk.checkpoint - chunk.skip); - if ( - now / 1000 - Math.max(chunk.solveTime, chunk.dispatchTime) < - chunktime && - chunk.progress < 10000 - ) { - speed.push(chunk.speed); - } - - if (chunk.dispatchTime > current) { - timespent.push(chunk.solveTime - chunk.dispatchTime); - } else if (chunk.solveTime > current) { - timespent.push(chunk.solveTime - current); - } - } - - return { - dispatched: - keyspace && dispatched.length - ? dispatched.reduce((a, i) => a + i, 0) / keyspace - : 0, - searched: - keyspace && searched.length - ? searched.reduce((a, i) => a + i, 0) / keyspace - : 0, - cracked: cracked.length ? cracked.reduce((a, i) => a + i, 0) : 0, - speed: speed.length ? speed.reduce((a, i) => a + i, 0) : 0, - timeSpent: timespent.length ? timespent.reduce((a, i) => a + i) : 0 - }; - } - reload(): void { this.clearSelection(); if (this._taskId) { diff --git a/src/app/core/_datasources/base.datasource.ts b/src/app/core/_datasources/base.datasource.ts index 6aec0b96..0b2034bd 100644 --- a/src/app/core/_datasources/base.datasource.ts +++ b/src/app/core/_datasources/base.datasource.ts @@ -1,12 +1,20 @@ -import { BehaviorSubject, Observable, Subscription } from 'rxjs'; +import { + BehaviorSubject, + Observable, + Subscription, + firstValueFrom +} from 'rxjs'; +import { Chunk, ChunkData } from '../_models/chunk.model'; import { CollectionViewer, DataSource } from '@angular/cdk/collections'; import { ChangeDetectorRef } from '@angular/core'; import { GlobalService } from '../_services/main.service'; import { HTTableColumn } from '../_components/tables/ht-table/ht-table.models'; +import { ListResponseWrapper } from '../_models/response.model'; import { MatPaginator } from '@angular/material/paginator'; import { MatSort } from '@angular/material/sort'; import { MatTableDataSourcePaginator } from '@angular/material/table'; +import { SERV } from '../_services/main.config'; import { SelectionModel } from '@angular/cdk/collections'; import { UIConfigService } from '../_services/shared/storage.service'; import { environment } from './../../../environments/environment'; @@ -328,4 +336,71 @@ export abstract class BaseDataSource< } abstract reload(): void; + + async getChunkData( + id: number, + isAgent = true, + keyspace = 0 + ): Promise { + const chunktime = this.uiService.getUIsettings('chunktime').value; + + const dispatched: number[] = []; + const searched: number[] = []; + const cracked: number[] = []; + const speed: number[] = []; + const timespent: number[] = []; + const now = Date.now(); + const tasks: number[] = !isAgent ? [id] : []; + const agents: number[] = isAgent ? [id] : []; + const current = 0; + + const params = { + maxResults: this.maxResults, + filter: isAgent ? `agentId=${id}` : `taskId=${id}` + }; + + const response: ListResponseWrapper = await firstValueFrom( + this.service.getAll(SERV.CHUNKS, params) + ); + + for (const chunk of response.values) { + agents.push(chunk.agentId); + tasks.push(chunk.taskId); + + if (chunk.progress >= 10000) { + dispatched.push(chunk.length); + } + cracked.push(chunk.cracked); + searched.push(chunk.checkpoint - chunk.skip); + if ( + now / 1000 - Math.max(chunk.solveTime, chunk.dispatchTime) < + chunktime && + chunk.progress < 10000 + ) { + speed.push(chunk.speed); + } + + if (chunk.dispatchTime > current) { + timespent.push(chunk.solveTime - chunk.dispatchTime); + } else if (chunk.solveTime > current) { + timespent.push(chunk.solveTime - current); + } + } + + return { + tasks: Array.from(new Set(tasks)), + agents: Array.from(new Set(agents)), + dispatched: + keyspace && dispatched.length + ? dispatched.reduce((a, i) => a + i, 0) / keyspace + : 0, + searched: + keyspace && searched.length + ? searched.reduce((a, i) => a + i, 0) / keyspace + : 0, + cracked: cracked.length ? cracked.reduce((a, i) => a + i, 0) : 0, + speed: speed.length ? speed.reduce((a, i) => a + i, 0) : 0, + timeSpent: timespent.length ? timespent.reduce((a, i) => a + i) : 0 + }; + } } diff --git a/src/app/core/_datasources/tasks.datasource.ts b/src/app/core/_datasources/tasks.datasource.ts new file mode 100644 index 00000000..611f28f0 --- /dev/null +++ b/src/app/core/_datasources/tasks.datasource.ts @@ -0,0 +1,64 @@ +import { catchError, finalize, forkJoin, of } from 'rxjs'; + +import { BaseDataSource } from './base.datasource'; +import { Hashlist } from 'src/app/hashlists/hashlist.model'; +import { MatTableDataSourcePaginator } from '@angular/material/table'; +import { SERV } from '../_services/main.config'; +import { TaskWrapper } from '../_models/task-wrapper.model'; + +export class TasksDataSource extends BaseDataSource< + TaskWrapper, + MatTableDataSourcePaginator +> { + private _isArchived = false; + + setIsArchived(isArchived: boolean): void { + this._isArchived = isArchived; + } + + loadAll(): void { + this.loading = true; + + const startAt = this.currentPage * this.pageSize; + const params = { + maxResults: this.pageSize, + startAt: startAt, + expand: 'accessGroup,tasks', + filter: `isArchived=${this._isArchived}` + }; + + const wrappers$ = this.service.getAll(SERV.TASKS_WRAPPER, params); + const hashLists$ = this.service.getAll(SERV.HASHLISTS, { + maxResults: this.maxResults + }); + + forkJoin([wrappers$, hashLists$]) + .pipe( + catchError(() => of([])), + finalize(() => (this.loading = false)) + ) + .subscribe(([taskWrapperResponse, hashlistResponse]) => { + const wrappers: TaskWrapper[] = taskWrapperResponse.values.map( + (wrapper: TaskWrapper) => { + const matchingHashList = hashlistResponse.values.find( + (hashlist: Hashlist) => hashlist._id === wrapper.hashlistId + ); + wrapper.hashlists = [matchingHashList]; + return wrapper; + } + ); + this.setPaginationConfig( + this.pageSize, + this.currentPage, + taskWrapperResponse.total + ); + + this.setData(wrappers); + }); + } + + reload(): void { + this.clearSelection(); + this.loadAll(); + } +} diff --git a/src/app/core/_models/chunk.model.ts b/src/app/core/_models/chunk.model.ts index 41460270..685733c1 100644 --- a/src/app/core/_models/chunk.model.ts +++ b/src/app/core/_models/chunk.model.ts @@ -29,4 +29,6 @@ export interface ChunkData { cracked: number; speed: number; timeSpent: number; + agents: number[]; + tasks: number[]; } diff --git a/src/app/core/_models/task-wrapper.model.ts b/src/app/core/_models/task-wrapper.model.ts new file mode 100644 index 00000000..ea67e2e0 --- /dev/null +++ b/src/app/core/_models/task-wrapper.model.ts @@ -0,0 +1,20 @@ +import { AccessGroup } from './access-group.model'; +import { Hashlist } from './hashlist.model'; +import { Task } from './task.model'; + +export interface TaskWrapper { + _id: number; + _self: string; + accessGroupId: number; + accessGroup?: AccessGroup; + cracked: number; + hashlistId: number; + hashlists?: Hashlist[]; + isArchived: boolean; + maxAgents: number; + priority: number; + taskType: number; + taskWrapperId: number; + taskWrapperName: string; + tasks?: Task[]; +} diff --git a/src/app/hashlists/hashlist.model.ts b/src/app/hashlists/hashlist.model.ts index e63f272f..36deb42d 100644 --- a/src/app/hashlists/hashlist.model.ts +++ b/src/app/hashlists/hashlist.model.ts @@ -1,19 +1,32 @@ export interface BaseHashlist { - hashlistId: number; - hashtypeId: number; - name: string; - format: number; - hashCount: number; + hashlistId: number; + hashtypeId: number; + name: string; + format: number; + hashCount: number; } export interface CreateHashlist extends BaseHashlist { - dataSourceType: string; - dataSource: string; + dataSourceType: string; + dataSource: string; } -export interface Hashlist extends BaseHashlist { - id: number; - hashCount: number; - crackedCount: number; - notes: string; +export interface Hashlist { + _id: number; + _self: string; + accessGroupId: number; + brainFeatures: number; + cracked: number; + format: number; + hashCount: number; + hashTypeId: number; + hashlistId: number; + isArchived: boolean; + isHexSalt: boolean; + isSalted: boolean; + isSecret: boolean; + name: string; + notes: string; + separator: string; + useBrain: false; } diff --git a/src/app/tasks/show-tasks/show-tasks.component.html b/src/app/tasks/show-tasks/show-tasks.component.html index 04443c6b..5c4bc605 100644 --- a/src/app/tasks/show-tasks/show-tasks.component.html +++ b/src/app/tasks/show-tasks/show-tasks.component.html @@ -1,150 +1,10 @@ -
-
- -
-
- -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
IDNameStatus+InfoPreprocessorHashlistDispatched/SearchedCrackedAgentsSpeedPriorityMax AgentsActions
{{ task.taskId }}{{ task.taskWrapperId }} - - {{ task.taskName | shortenString:40 }} - - {{ task.taskWrapperName | shortenString:40 }} -
-
- -
-
- -
-
-
- - - - - - - - SuperTask - - {{ task.preprocessorId === 1 ? 'Prince':'-'}}- - {{ task.hashlist[0].name | shortenString:30 }} - - - - {{ task.name | shortenString:30 }} - - - - {{ task.taskId | tdispatched:task.keyspace | async | percent:'1.2-2'}} / {{ task.taskId | tdsearched:task.keyspace | async | percent:'1.2-2'}} - - - {{ task.taskId | tdcracked | async }} - - - {{ task.assignedAgents.length > 0 ? task.assignedAgents.length: 0 }} - - - {{ task.taskId | aspeed | async | fileSize:false:1000 }} H/s - - - - - {{ task.priority }} - - - {{ task.maxAgents }} - - - -
-
- - - - - - - - - -
-
- - - - - - - - -
-
-
-
- + + + diff --git a/src/app/tasks/show-tasks/show-tasks.component.ts b/src/app/tasks/show-tasks/show-tasks.component.ts index 3a53fb64..9e6f68a3 100644 --- a/src/app/tasks/show-tasks/show-tasks.component.ts +++ b/src/app/tasks/show-tasks/show-tasks.component.ts @@ -1,497 +1,16 @@ -import { faPencil, faEdit, faTrash, faLock, faFileImport, faFileExport, faPlus, faHomeAlt, faArchive, faCopy, faBookmark, faEye, faMicrochip, faCheckCircle, faTerminal, faNoteSticky } from '@fortawesome/free-solid-svg-icons'; -import { ModalDismissReasons, NgbModal } from '@ng-bootstrap/ng-bootstrap'; -import { environment } from './../../../environments/environment'; -import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core'; -import { ActivatedRoute, Router } from '@angular/router'; -import { DataTableDirective } from 'angular-datatables'; -import Swal from 'sweetalert2/dist/sweetalert2.js'; -import { Subject, Subscription } from 'rxjs'; - import { AutoTitleService } from 'src/app/core/_services/shared/autotitle.service'; -import { ModalSubtasksComponent } from './modal-subtasks/modal-subtasks.component'; -import { AlertService } from 'src/app/core/_services/shared/alert.service'; -import { GlobalService } from '../../core/_services/main.service'; -import { SERV } from '../../core/_services/main.config'; - -declare let $:any; +import { Component } from '@angular/core'; @Component({ selector: 'app-show-tasks', templateUrl: './show-tasks.component.html' }) -/** - * ShowTasksComponent is a component that manages and displays all hashlist data. - * - * It uses DataTables to display and interact with the tasks data, including exporting, deleting, bulk actions - * and refreshing the table. -*/ -export class ShowTasksComponent implements OnInit, OnDestroy { - - // Font Awesome icons - faCheckCircle=faCheckCircle; - faNoteSticky=faNoteSticky; - faFileImport=faFileImport; - faFileExport=faFileExport; - faMicrochip=faMicrochip; - faTerminal=faTerminal; - faBookmark=faBookmark; - faArchive=faArchive; - faPencil=faPencil; - faHome=faHomeAlt; - faTrash=faTrash; - faEdit=faEdit; - faLock=faLock; - faPlus=faPlus; - faCopy=faCopy; - faEye=faEye; - - // ViewChild reference to the DataTableDirective - @ViewChild(DataTableDirective) - dtElement: DataTableDirective; - - dtTrigger: Subject = new Subject(); - dtTrigger1: Subject = new Subject(); - dtOptions: any = {}; - dtOptions1: any = {}; - - // List of tasks ToDo. Change to interface - alltasks: any = []; - - // Subscriptions to unsubscribe on component destruction - subscriptions: Subscription[] = [] - - private maxResults = environment.config.prodApiMaxResults; - +export class ShowTasksComponent { // View type,filter options isArchived: boolean; whichView: string; - isTaskactive = 0; - currenspeed = 0; - - constructor( - private titleService: AutoTitleService, - private modalService: NgbModal, - private route:ActivatedRoute, - private alert: AlertService, - private gs: GlobalService, - private router: Router - ) { - titleService.set(['Show Tasks']) - } - - /** - * Initializes DataTable and retrieves pretasks. - */ - - ngOnInit(): void { - this.loadTasks(); - this.setupTable(); - } - - /** - * Unsubscribes from active subscriptions. - */ - ngOnDestroy(): void { - this.dtTrigger.unsubscribe(); - for (const sub of this.subscriptions) { - sub.unsubscribe(); - } - } - - // Refresh the data and the DataTable - onRefresh() { - this.rerender(); - this.ngOnInit(); - } - - /** - * Rerender the DataTable. - */ - rerender(): void { - this.dtElement.dtInstance.then((dtInstance: DataTables.Api) => { - dtInstance.destroy(); - setTimeout(() => { - this.dtTrigger['new'].next(); - }); - }); - } - - /** - * Depending on route loads live or archived tasks - * - */ - loadTasks(): void { - this.route.data.subscribe(data => { - switch (data['kind']) { - - case 'show-tasks': - this.whichView = 'live'; - this.isArchived = false; - break; - - case 'show-tasks-archived': - this.whichView = 'archived'; - this.isArchived = true; - break; - - } - - this.getTasks(); - }); - } - - /** - * Fetches Tasks and SuperTasks from the server filtering by live or archived - * Subscribes to the API response and updates the task list. - */ - getTasks():void { - const params = {'maxResults': this.maxResults, 'expand': 'crackerBinary,crackerBinaryType,hashlist,assignedAgents', 'filter': 'isArchived='+this.isArchived+''}; - this.subscriptions.push(this.gs.getAll(SERV.TASKS_WRAPPER,{'maxResults': this.maxResults}).subscribe((tw: any) => { - this.subscriptions.push(this.gs.getAll(SERV.TASKS,params).subscribe((tasks: any) => { - this.subscriptions.push(this.gs.getAll(SERV.HASHLISTS,{'maxResults': this.maxResults}).subscribe((h: any) => { - let filtersupert = tw.values.filter(u=> (u.taskType == 1 && u.isArchived === this.isArchived)); // Active SuperTasks - let supertasks = filtersupert.map(mainObject => { - const matchObject = h.values.find(element => element.hashlistId === mainObject.hashlistId ); - return { ...mainObject, ...matchObject } - }) //Join Supertasks from TaskWrapper with Hashlist info - const addSTinfo = []; //Ass tasktype in tasks - for(let i=0; i < tw.values.length; i++){ - addSTinfo.push( {taskWrapperId: tw.values[i].taskWrapperId, taskType: tw.values[i].taskType}); - } - let mergeTasks = tasks.values.map(mainObject => { - const matchObject = addSTinfo.find(element => element.taskWrapperId === mainObject.taskWrapperId ); - return { ...mainObject, ...matchObject } - }) // Join Tasks with Taskwrapper information for filtering - let filtertasks = mergeTasks.filter(u=> (u.taskType == 0 && u.isArchived === this.isArchived)); //Filter Active Tasks remove subtasks - let prepdata = filtertasks.concat(supertasks); // Join with supertasks - //Order by Task Priority. filter exclude when is cracked && (a.keyspaceProgress < a.keyspace) - this.alltasks = prepdata.sort((a, b) => Number(b.priority) - Number(a.priority)); - this.dtTrigger.next(null); - })); - })); - })); - } - - /** - * Sets up the DataTable options and buttons. - * Customizes DataTable appearance and behavior. - */ - setupTable(): void { - // DataTables options - const self = this; - this.dtOptions = { - dom: 'Bfrtip', - scrollX: true, - pageLength: 25, - lengthMenu: [ - [10, 25, 50, 100, 250, -1], - [10, 25, 50, 100, 250, 'All'] - ], - bStateSave:true, - destroy: true, - order: [], // Removes the default order by id. We need it to sort by priority. - select: { - style: 'multi', - // selector: 'tr>td:nth-child(1)' //This only allows select the first row - }, - buttons: { - dom: { - button: { - className: 'dt-button buttons-collection btn btn-sm-dt btn-outline-gray-600-dt', - } - }, - buttons: [ - { - text: '↻', - autoClose: true, - action: function (e, dt, node, config) { - self.onRefresh(); - } - }, - { - extend: 'collection', - text: 'Export', - buttons: [ - { - extend: 'excelHtml5', - exportOptions: { - columns: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] - }, - }, - { - extend: 'print', - exportOptions: { - columns: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] - }, - customize: function ( win ) { - $(win.document.body) - .css( 'font-size', '10pt' ) - $(win.document.body).find( 'table' ) - .addClass( 'compact' ) - .css( 'font-size', 'inherit' ); - } - }, - { - extend: 'csvHtml5', - exportOptions: {modifier: {selected: true}}, - select: true, - customize: function (dt, csv) { - let data = ""; - for (let i = 0; i < dt.length; i++) { - data = "Show Tasks\n\n"+ dt; - } - return data; - } - }, - { - extend: 'copy', - } - ] - }, - { - extend: 'collection', - text: 'Bulk Actions', - drawCallback: function() { - const hasRows = this.api().rows({ filter: 'applied' }).data().length > 0; - $('.buttons-excel')[0].style.visibility = hasRows ? 'visible' : 'hidden' - }, - buttons: [ - { - text: 'Delete Task(s)', - autoClose: true, - action: function (e, dt, node, config) { - self.onDeleteBulk(); - } - }, - { - text: 'Archive Task(s)', - autoClose: true, - enabled: !this.isArchived, - action: function (e, dt, node, config) { - const edit = {isArchived: true}; - self.onUpdateBulk(edit); - } - }, - ] - }, - { - text: !this.isArchived? 'Show Archived':'Show Live', - action: function () { - if(!self.isArchived) { - self.router.navigate(['tasks/show-tasks-archived']); - } - if(self.isArchived){ - self.router.navigate(['tasks/show-tasks']); - } - } - }, - { - extend: 'colvis', - text: 'Column View', - columns: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], - }, - { - extend: "pageLength", - className: "btn-sm" - } - ], - } - }; - } - - // Refresh the table after a delete operation - onRefreshTable() { - setTimeout(() => { - this.ngOnInit(); - this.rerender(); // Rerender the DataTable - }, 2000); - } - - /** - * Archives a tasks with the given ID. - * - * @param {number} id - The ID of the tasks to archive. - * @param {number} type - The type of the task. - */ - onArchive(id: number, type: number){ - const path = type === 0 ? SERV.TASKS : SERV.TASKS_WRAPPER; - this.subscriptions.push(this.gs.archive(path,id).subscribe(() => { - this.alert.okAlert('Archived!',''); - this.onRefresh(); - })); - } - - /** - * Handles the deletion of a tasks. - * Displays a confirmation dialog and deletes the tasks if confirmed. - * - * @param {number} id - The ID of the tasks to delete. - * @param {number} type - The type of the task. - * @param {string} name - The name of the tasks. - */ - onDelete(id: number, type: number, name: string){ - this.alert.deleteConfirmation(name,'Task').then((confirmed) => { - if (confirmed) { - // Deletion - const path = type === 0 ? SERV.TASKS : SERV.TASKS_WRAPPER; //Task or supertask - this.subscriptions.push(this.gs.delete(path, id).subscribe(() => { - // Successful deletion - this.alert.okAlert(`Deleted Task ${name}`, ''); - this.onRefreshTable(); // Refresh the table - })); - } else { - // Handle cancellation - this.alert.okAlert(`Task ${name} is safe!`,''); - } - }); - } - - // Bulk actions - - /** - * Handles task selection for bulk actions. - * - * @returns {number[]} - An array of selected hashlist IDs. - */ - - onSelectedTasks(){ - $(".dt-button-background").trigger("click"); - const selection = $($(this.dtElement).DataTable.tables()).DataTable().rows({ selected: true } ).data().pluck(0).toArray(); - if(selection.length == 0) { - this.alert.okAlert('You have not selected any Task',''); - return; - } - const selectionnum = selection.map(i=>Number(i)); - - return selectionnum; - } - - /** - * Handles bulk deletion - * Delete the Tasks showing a progress bar - * - */ - async onDeleteBulk() { - const TasksIds = this.onSelectedTasks(); - const type = String($($(this.dtElement).DataTable.tables()).DataTable().rows({ selected: true } ).data().pluck(3).toArray()); - const search = type.includes("SuperTask"); - let path = !search ? SERV.TASKS: SERV.TASKS_WRAPPER; - this.alert.bulkDeleteAlert(TasksIds,'Hashtypes',path); - this.onRefreshTable(); - } - /** - * Updates the selected tasks with the given value. - * - * @param {any} value - The value to update the selected tasks with. - */ - async onUpdateBulk(value: any) { - const FilesIds = this.onSelectedTasks(); - const type = String($($(this.dtElement).DataTable.tables()).DataTable().rows({ selected: true } ).data().pluck(3).toArray()); - const search = type.includes("SuperTask"); - let path = !search ? SERV.TASKS: SERV.TASKS_WRAPPER; - this.alert.bulkUpdateAlert(FilesIds,value,'Files',path); - this.onRefreshTable(); + constructor(private titleService: AutoTitleService) { + titleService.set(['Show Tasks']); } - - /** - * Retrieves subtasks for a given task and opens a modal to display them. - * - * @param {string} name - The name of the task. - * @param {number} id - The ID of the task for which subtasks should be retrieved. - */ - getSubtasks(name: string, id: number){ - this.subscriptions.push(this.gs.getAll(SERV.TASKS,{'maxResults': this.maxResults, 'filter':'taskWrapperId='+id+'', 'expand':'assignedAgents'}).subscribe((subtasks: any) => { - const ref = this.modalService.open(ModalSubtasksComponent, { centered: true, size: 'xl' }); - ref.componentInstance.prep = subtasks.values; - ref.componentInstance.supertaskid = id; - ref.componentInstance.title = name; - })) - } - - /** - * Opens a modal dialog to update a project name. - * - * @param {string} title - The title of the dialog. - */ - onModalProject(title: string){ - (async () => { - - $(".dt-button-background").trigger("click"); - const selection = $($(this.dtElement).DataTable.tables()).DataTable().rows({ selected: true } ).data().pluck(0).toArray(); - if(selection.length == 0) { - this.alert.okAlert('You have not selected any Group',''); - return; - } - - const { value: formValues } = await Swal.fire({ - title: title, - html: - '', - focusConfirm: false, - confirmButtonColor: '#4B5563', - preConfirm: () => { - return [ - (document.getElementById('project-input')).value, - ] - } - }) - - if (formValues) { - const edit = {projectName: +formValues}; - this.onUpdateBulk(edit); - } - - })() - } - - /** - * Opens a modal dialog to update a task's properties. - * - * @param {string} title - The title of the dialog. - * @param {number} id - The ID of the task to be updated. - * @param {any} cvalue - The current value of the property being updated. - * @param {boolean} formlabel - Indicates whether the property is a label. - * @param {string} nameref - The name reference of the property. - * @param {number} type - The type of the task (0 for regular tasks, 1 for supertasks). - */ - onModalUpdate(title: string, id: number, cvalue: any, formlabel: boolean, nameref: string, type: number){ - (async () => { - - const { value: formValues } = await Swal.fire({ - title: title + ' - '+ nameref, - html: - '', - focusConfirm: false, - showCancelButton: true, - cancelButtonColor: this.alert.cancelButtonColor, - confirmButtonColor: this.alert.confirmButtonColor, - cancelButton: true, - preConfirm: () => { - return [ - (document.getElementById('project-input')).value, - ] - } - }) - - if (formValues) { - if(cvalue !== Number(formValues[0])){ - let update; - if(formlabel){ - update = {priority: +formValues}; - }else{ - update = {maxAgents: +formValues}; - } - const path = type === 0 ? SERV.TASKS : SERV.TASKS_WRAPPER; - this.subscriptions.push(this.gs.update(path,id, update).subscribe(() => { - this.alert.okAlert('Task saved!',''); - this.ngOnInit(); - this.rerender(); // rerender datatables - })); - } - } - - })() - } - } diff --git a/src/styles/components/_table.scss b/src/styles/components/_table.scss index 7e0a98de..102a27c9 100644 --- a/src/styles/components/_table.scss +++ b/src/styles/components/_table.scss @@ -240,6 +240,47 @@ table.hashtopolis-table { } .mat-mdc-cell { + .editable-container { + .mat-icon { + font-size: 14px; + position: absolute; + top: 10px; + left: 0; + } + .mat-mdc-form-field-infix { + min-height: auto; + padding: 0.5em 0px !important; + } + .mat-form-field-label-wrapper { + top: -1.5em; + } + + .mat-mdc-form-field-icon-suffix { + display: flex; + } + + .mat-mdc-form-field-subscript-wrapper { + display: none; + } + .editable-input { + width: 50px; + margin: 0; + } + .editable-text { + display: inline-block; + border-bottom: 1px dashed $primary-400; + cursor: pointer; + } + .mat-mdc-icon-button { + width: 30px; + height: 30px; + } + .mat-mdc-form-field { + margin: 0; + padding: 0; + max-width: 150px; + } + } .pulsing-progress { color: $warn-900; animation: pulse 1s infinite; From 97063f38703fa9f26c2b837b0c046bfaf5a90b01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Fri, 24 Nov 2023 15:58:00 +0000 Subject: [PATCH 304/419] Task and Modals --- src/app/core/_constants/tasks.config.ts | 10 + .../edit-hashlist.component.html | 2 +- .../cheatsheet/cheatsheet.component.html | 173 +++-- .../alert/cheatsheet/cheatsheet.component.ts | 96 +-- src/app/shared/forms.module.ts | 3 + src/app/shared/grid-containers/grid.module.ts | 2 + .../simulate-form-field.component.html | 9 +- .../simulate-form-field.component.ts | 14 +- .../shared/input/check/check.component.html | 10 + .../edit-tasks/edit-tasks.component.html | 494 +++--------- .../tasks/new-tasks/new-tasks.component.html | 431 ++++------- .../tasks/new-tasks/new-tasks.component.ts | 718 ++++++++++-------- src/app/tasks/tasks.module.ts | 6 +- 13 files changed, 845 insertions(+), 1123 deletions(-) create mode 100644 src/app/core/_constants/tasks.config.ts diff --git a/src/app/core/_constants/tasks.config.ts b/src/app/core/_constants/tasks.config.ts new file mode 100644 index 00000000..82c208a5 --- /dev/null +++ b/src/app/core/_constants/tasks.config.ts @@ -0,0 +1,10 @@ +export const staticChunking = [ + { _id: 0, name: 'No' }, + { _id: 1, name: 'Fixed Chunk Size' }, + { _id: 2, name: 'Fixed number of chunks' } +]; + +export const benchmarkType = [ + { _id: false, name: 'Runtime Benchmark' }, + { _id: true, name: 'Speed Test' } +]; diff --git a/src/app/hashlists/edit-hashlist/edit-hashlist.component.html b/src/app/hashlists/edit-hashlist/edit-hashlist.component.html index c461cce8..fb09ecb2 100644 --- a/src/app/hashlists/edit-hashlist/edit-hashlist.component.html +++ b/src/app/hashlists/edit-hashlist/edit-hashlist.component.html @@ -18,7 +18,7 @@
- + diff --git a/src/app/shared/alert/cheatsheet/cheatsheet.component.html b/src/app/shared/alert/cheatsheet/cheatsheet.component.html index 52b16368..77373bb4 100644 --- a/src/app/shared/alert/cheatsheet/cheatsheet.component.html +++ b/src/app/shared/alert/cheatsheet/cheatsheet.component.html @@ -1,87 +1,112 @@ - - - + + + + diff --git a/src/app/shared/alert/cheatsheet/cheatsheet.component.ts b/src/app/shared/alert/cheatsheet/cheatsheet.component.ts index 4fc687d3..82937aa2 100644 --- a/src/app/shared/alert/cheatsheet/cheatsheet.component.ts +++ b/src/app/shared/alert/cheatsheet/cheatsheet.component.ts @@ -1,66 +1,66 @@ import { ModalDismissReasons, NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { Component } from '@angular/core'; +import { MatDialogRef } from '@angular/material/dialog'; +import { ApplicationRef } from '@angular/core'; +import { MatTableDataSource } from '@angular/material/table'; @Component({ selector: 'app-cheatsheet', templateUrl: './cheatsheet.component.html' }) export class CheatsheetComponent { - constructor( - private modalService: NgbModal, - ) { } + public dialogRef: MatDialogRef, + private appRef: ApplicationRef + ) {} - // Modal Information - closeResult = ''; - open(content) { - this.modalService.open(content, { size: 'xl' }).result.then( - (result) => { - this.closeResult = `Closed with: ${result}`; - }, - (reason) => { - this.closeResult = `Dismissed ${this.getDismissReason(reason)}`; - }, - ); - } + // Display Table Information + attackmode = [ + { _id: '0', name: 'Straight(Using rules)' }, + { _id: '1', name: 'Combination' }, + { _id: '3', name: 'Brute-force' }, + { _id: '6', name: 'Hybrid Dictionary+ Mask' }, + { _id: '7', name: 'Hybrid Mask + Dictionary' } + ]; - private getDismissReason(reason: any): string { - if (reason === ModalDismissReasons.ESC) { - return 'by pressing ESC'; - } else if (reason === ModalDismissReasons.BACKDROP_CLICK) { - return 'by clicking on a backdrop'; - } else { - return `with: ${reason}`; + attackex = [ + { _id: 'Dictionary', name: '-w3 -O #HL# -a 0 rockyou.txt' }, + { + _id: 'Dictionary + Rules', + name: '-w3 -O #HL# -a 0 rockyou.txt -r base64rule.txt' + }, + { + _id: 'Combination', + name: '-w3 -O #HL# -a 1 rockyou.txt rockyou2.txt' + }, + { + _id: 'Hybrid Dictionary + Mask', + name: '-w3 -O #HL# -a 6 -m dict.txt ?a?a?a?a' + }, + { + _id: 'Hybrid Mask + Dictionary', + name: '-w3 -O #HL# -a 7 -m ?a?a?a?a dict.txt' } - } + ]; - // Modal Information - attackmode =[ - {'value': '0', 'name': 'Straight(Using rules)' }, - {'value': '1', 'name': 'Combination' }, - {'value': '3', 'name': 'Brute-force'}, - {'value': '6', 'name': 'Hybrid Dictionary+ Mask'}, - {'value': '7', 'name': 'Hybrid Mask + Dictionary'}, - ] + charsets = [ + { _id: '?l', name: 'abcdefghijklmnopqrstuvwxyz' }, + { _id: '?u', name: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' }, + { _id: '?d', name: '0123456789' }, + { _id: '?h', name: '0123456789abcdef' }, + { _id: '?H', name: '0123456789ABCDEF' }, + { _id: '?s', name: '«space»!"#$%&()*+,-./:;<=>?@[]^_`{|}~' }, + { _id: '?a', name: '?l?u?d?s' }, + { _id: '?b', name: '0x00 - 0xff' } + ]; - attackex =[ - {'value': 'Dictionary', 'example': '-w3 -O #HL# -a 0 rockyou.txt' }, - {'value': 'Dictionary + Rules', 'example': '-w3 -O #HL# -a 0 rockyou.txt -r base64rule.txt' }, - {'value': 'Combination', 'example': '-w3 -O #HL# -a 1 rockyou.txt rockyou2.txt'}, - {'value': 'Hybrid Dictionary + Mask', 'example': '-w3 -O #HL# -a 6 -m dict.txt ?a?a?a?a'}, - {'value': 'Hybrid Mask + Dictionary', 'example': '-w3 -O #HL# -a 7 -m ?a?a?a?a dict.txt'}, - ] + dataSource1 = new MatTableDataSource([...this.attackmode]); - charsets =[ - {'value': '?l', 'descrip': 'abcdefghijklmnopqrstuvwxyz' }, - {'value': '?u', 'descrip': 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' }, - {'value': '?d', 'descrip': '0123456789' }, - {'value': '?h', 'descrip': '0123456789abcdef' }, - {'value': '?H', 'descrip': '0123456789ABCDEF' }, - {'value': '?s', 'descrip': '«space»!"#$%&()*+,-./:;<=>?@[\]^_`{|}~'}, - {'value': '?a', 'descrip': '?l?u?d?s'}, - {'value': '?b', 'descrip': '0x00 - 0xff'}, - ] + dataSource2 = new MatTableDataSource([...this.attackex]); + dataSource3 = new MatTableDataSource([...this.charsets]); + onClose(): void { + this.dialogRef.close(); + } } diff --git a/src/app/shared/forms.module.ts b/src/app/shared/forms.module.ts index 6f38e57f..3208a96e 100644 --- a/src/app/shared/forms.module.ts +++ b/src/app/shared/forms.module.ts @@ -20,12 +20,14 @@ import { MatChipsModule } from '@angular/material/chips'; import { MatDatepickerModule } from '@angular/material/datepicker'; import { MatProgressBarModule } from '@angular/material/progress-bar'; import { MatSlideToggleModule } from '@angular/material/slide-toggle'; +import { MatExpansionModule } from '@angular/material/expansion'; @NgModule({ imports: [ ReactiveFormsModule, MatAutocompleteModule, MatSlideToggleModule, + MatExpansionModule, MatOptionModule, MatSelectModule, MatDividerModule, @@ -50,6 +52,7 @@ import { MatSlideToggleModule } from '@angular/material/slide-toggle'; ReactiveFormsModule, MatAutocompleteModule, MatSlideToggleModule, + MatExpansionModule, MatOptionModule, MatSelectModule, MatDividerModule, diff --git a/src/app/shared/grid-containers/grid.module.ts b/src/app/shared/grid-containers/grid.module.ts index e213bade..7a74edbd 100644 --- a/src/app/shared/grid-containers/grid.module.ts +++ b/src/app/shared/grid-containers/grid.module.ts @@ -5,12 +5,14 @@ import { GridAutoColComponent } from './grid-autocol'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { MatCardModule } from '@angular/material/card'; import { GridMainComponent } from './grid-main'; +import { RouterModule } from '@angular/router'; import { CommonModule } from '@angular/common'; import { FormsModule } from '@angular/forms'; import { NgModule } from '@angular/core'; @NgModule({ imports: [ + RouterModule, FontAwesomeModule, CommonModule, FormsModule, diff --git a/src/app/shared/grid-containers/simulate-form-field/simulate-form-field.component.html b/src/app/shared/grid-containers/simulate-form-field/simulate-form-field.component.html index 196c86ac..3859269c 100644 --- a/src/app/shared/grid-containers/simulate-form-field/simulate-form-field.component.html +++ b/src/app/shared/grid-containers/simulate-form-field/simulate-form-field.component.html @@ -1,10 +1,15 @@
- {{label}} + {{ label }}
- {{ message}} + + + + {{ label }} +
+ diff --git a/src/app/shared/grid-containers/simulate-form-field/simulate-form-field.component.ts b/src/app/shared/grid-containers/simulate-form-field/simulate-form-field.component.ts index 0d2f14c7..a09dea02 100644 --- a/src/app/shared/grid-containers/simulate-form-field/simulate-form-field.component.ts +++ b/src/app/shared/grid-containers/simulate-form-field/simulate-form-field.component.ts @@ -1,11 +1,19 @@ -// simulate-form-field.component.ts -import { Component, Input } from '@angular/core'; +import { Component, Input, ViewEncapsulation } from '@angular/core'; @Component({ selector: 'simulate-form-field', - templateUrl: './simulate-form-field.component.html' + templateUrl: './simulate-form-field.component.html', + encapsulation: ViewEncapsulation.None }) export class SimulateFormFieldComponent { @Input() label: string; @Input() message: string; + @Input() routerLink: string; + + isActive(): boolean { + // Add logic to determine if the link is active based on the current route + // You can use Angular's Router service to achieve this + // For example, you can inject Router in the component's constructor and use its methods to check the active route + return false; // Replace with your actual logic + } } diff --git a/src/app/shared/input/check/check.component.html b/src/app/shared/input/check/check.component.html index 09acf128..4188daf7 100644 --- a/src/app/shared/input/check/check.component.html +++ b/src/app/shared/input/check/check.component.html @@ -1,3 +1,13 @@ {{ title }} + diff --git a/src/app/tasks/edit-tasks/edit-tasks.component.html b/src/app/tasks/edit-tasks/edit-tasks.component.html index d7ba76f2..34c335d3 100644 --- a/src/app/tasks/edit-tasks/edit-tasks.component.html +++ b/src/app/tasks/edit-tasks/edit-tasks.component.html @@ -1,375 +1,127 @@ - - -
-
- -
-
-
-
- - - -
-
- - - -
-
- - - -
-
- - - - - - -
-
- - - -
-
- - - -
-
- -
- -
-
-
-
-
-
- - - -
-
- - - -
-
- -
- -
-
-
-
-
-
-
+ + + + + + +
+ + + + +
+ +
+ + +
- - + +
+ + + + + + + + + +
+ + + + + + +
+ + + + + + Task Information + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
- -
-
- - - - Task Information - - -
-
-
- - - -
-
- - - -
-
-
-
- - - -
-
- - - -
-
-
-
- - - -
-
- -
- - - {{ cprogress / tkeyspace | percent: '1.2-2' }} - - -
- - - N/A - - -
-
-
-
-
-
Calculations
-
- - - - {{ - editedTaskIndex - | ttimespent: false : true - | async - | sectotime - }} - - - -
-
- - - - {{ - ctimespent / (cprogress / tkeyspace) - ctimespent - | sectotime - }} - - - - - --- - - - -
-
- - - {{ currenspeed | fileSize: false }}H/s - - -
-
-
-
-
- - - - - {{ hashlistinform.name }} / {{ hashlistDescrip }} - -
- -
-
-
-
-
- - - - - Attached Files - - - - - - - - - - - - - - - - - - - - -
IDNameTypeSize
{{ file.fileId }}{{ file.filename | shortenString: 15 }}{{ file.fileType | fileType }}{{ file.size | fileSize: false }}
-
-
-
-
-
-
- + + + + + + + Attached Files + + + + + + + + + + + + + + + + + + + + +
IDNameTypeSize
{{ file.fileId }}{{ file.filename | shortenString: 15 }}{{ file.fileType | fileType }}{{ file.size | fileSize: false }}
+
+
+
+
+ Cracker Information

[tusepreprocessor]="tusepreprocessor" > - +
-
-
- - - -
-
- - - -
-
-
- - - -
- - - -
-
- - - - - Please remove manually the blacklisted Characters or use the button below! -
- BlackListed Chars: {{this.getBanChar()}} -
- -
-
-
-
-
- - - -
-
- - - + + + + + + + + +
+ + + + + + Please remove manually the blacklisted Characters or use the button below! +
+ BlackListed Chars: {{this.getBanChar()}} +
+ +
+ +
+ + + +
+ + + + +
+ + + + + + Advanced Settings + + +
+ + + + + + + + + + + + +
-
- - - - Advanced Settings - - -
-
- -
- -
-
-
-
- -
- -
-
-
-
- -
- -
-
-
-
-
-
- - +
+ +
+ + + + +
+
+ + -- + + +
+
+
+
+
+ + No + + + +
+
+
+
+ +
-
-
- - - -
-
- - - -
-
-
-
-
- - - -
-
- - - -
-
- - - -
-
-
-
-
- - - -
-
-
-
- - -
-
-
-
- - - - Please complete all the form!
- - - - +
+
+ + + Please complete all the form!
+ + + +
+
@@ -317,9 +166,9 @@ placement="bottom" ngbTooltip='T: Task, P: Preprocessor' container="body" - [icon]="faInfoCircle" aria-hidden="true" > + faInfoCircle @@ -345,14 +194,14 @@ - +
@@ -381,9 +230,9 @@ placement="bottom" ngbTooltip='T: Task, P: Preprocessor' container="body" - [icon]="faInfoCircle" aria-hidden="true" > + faInfoCircle @@ -409,14 +258,14 @@ - +
@@ -445,9 +294,9 @@ placement="bottom" ngbTooltip='T: Task, P: Preprocessor' container="body" - [icon]="faInfoCircle" aria-hidden="true" > + faInfoCircle @@ -473,14 +322,14 @@ - +
@@ -498,9 +347,5 @@
- - - - diff --git a/src/app/tasks/new-tasks/new-tasks.component.ts b/src/app/tasks/new-tasks/new-tasks.component.ts index 229cdb59..7a44a377 100644 --- a/src/app/tasks/new-tasks/new-tasks.component.ts +++ b/src/app/tasks/new-tasks/new-tasks.component.ts @@ -1,6 +1,18 @@ -import { Component, OnInit, ChangeDetectionStrategy ,ChangeDetectorRef, HostListener, ViewChild } from '@angular/core'; -import { faHomeAlt, faPlus, faTrash, faInfoCircle, faLock } from '@fortawesome/free-solid-svg-icons'; -import { AbstractControl, FormControl, FormGroup, ValidatorFn, Validators } from '@angular/forms'; +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + HostListener, + OnInit, + ViewChild +} from '@angular/core'; +import { + AbstractControl, + FormControl, + FormGroup, + ValidatorFn, + Validators +} from '@angular/forms'; import { ModalDismissReasons, NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { environment } from './../../../environments/environment'; import { ActivatedRoute, Params } from '@angular/router'; @@ -13,30 +25,41 @@ import { TooltipService } from '../../core/_services/shared/tooltip.service'; import { AlertService } from 'src/app/core/_services/shared/alert.service'; import { colorpicker } from '../../core/_constants/settings.config'; import { GlobalService } from 'src/app/core/_services/main.service'; -import { PageTitle } from 'src/app/core/_decorators/autotitle'; import { SERV } from '../../core/_services/main.config'; - +import { AutoTitleService } from 'src/app/core/_services/shared/autotitle.service'; +import { UnsubscribeService } from 'src/app/core/_services/unsubscribe.service'; +import { MatDialog } from '@angular/material/dialog'; +import { + benchmarkType, + staticChunking +} from 'src/app/core/_constants/tasks.config'; +import { CheatsheetComponent } from 'src/app/shared/alert/cheatsheet/cheatsheet.component'; + +/** + * Represents the NewTasksComponent responsible for creating a new Tasks. + */ @Component({ selector: 'app-new-tasks', templateUrl: './new-tasks.component.html', changeDetection: ChangeDetectionStrategy.Default }) -@PageTitle(['New Task']) export class NewTasksComponent implements OnInit { + /** Flag indicating whether data is still loading. */ + isLoading = true; + + /** Form group for the new SuperHashlist. */ + // form: FormGroup; - // Config + /** Select Options. */ + selectHashlists: any; + selectStaticChunking = staticChunking; + selectBenchmarktype = benchmarkType; + + // Initial Configuration private priority = environment.config.tasks.priority; private maxAgents = environment.config.tasks.maxAgents; private chunkSize = environment.config.tasks.chunkSize; - - faInfoCircle=faInfoCircle; - faHome=faHomeAlt; - faTrash=faTrash; - faPlus=faPlus; - faLock=faLock; - - color = '#fff'; - colorpicker=colorpicker; + private maxResults = environment.config.prodApiMaxResults; @ViewChild(DataTableDirective) dtElement: DataTableDirective; @@ -49,305 +72,338 @@ export class NewTasksComponent implements OnInit { editedIndex: number; whichView: string; copyType: number; //0 copy from task and 1 copy from pretask - prep: any; // ToDo change to interface - crackertype: any; // ToDo change to interface + prep: any; // ToDo change to interface + crackertype: any; // ToDo change to interface crackerversions: any = []; createForm: FormGroup; + // Tooltips + tasktip: any = []; // ToDo change to interface public allfiles: { - fileId: number, - filename: string, - size: number, - isSecret: number, - fileType: number, - accessGroupId: number, - lineCount:number - accessGroup: { - accessGroupId: number, - groupName: string - } - }[] = []; + fileId: number; + filename: string; + size: number; + isSecret: number; + fileType: number; + accessGroupId: number; + lineCount: number; + accessGroup: { + accessGroupId: number; + groupName: string; + }; + }[] = []; constructor( + private unsubscribeService: UnsubscribeService, private _changeDetectorRef: ChangeDetectorRef, + private titleService: AutoTitleService, private tooltipService: TooltipService, private uiService: UIConfigService, private modalService: NgbModal, - private route:ActivatedRoute, + private route: ActivatedRoute, private alert: AlertService, private gs: GlobalService, + private dialog: MatDialog, private router: Router - ) { } - - private maxResults = environment.config.prodApiMaxResults; + ) { + titleService.set(['New Task']); + } // New checkbox filesFormArray: Array = []; - onChange(fileId:number, fileType:number, fileName: string, cmdAttk: number, $target: EventTarget) { + onChange( + fileId: number, + fileType: number, + fileName: string, + cmdAttk: number, + $target: EventTarget + ) { const isChecked = ($target).checked; - if(isChecked && cmdAttk === 0 ) { - if (this.copyMode) { - this.filesFormArray = this.createForm.get('files').value; - } - this.filesFormArray.push(fileId); - this.OnChangeAttack(fileName, fileType); - this.createForm.get('files').value; - this.createForm.patchValue({files: this.filesFormArray}); + if (isChecked && cmdAttk === 0) { + if (this.copyMode) { + this.filesFormArray = this.createForm.get('files').value; + } + this.filesFormArray.push(fileId); + this.OnChangeAttack(fileName, fileType); + this.createForm.get('files').value; + this.createForm.patchValue({ files: this.filesFormArray }); } if (isChecked && cmdAttk === 1) { - this.OnChangeAttackPrep(fileName, fileType); - } if (!isChecked && cmdAttk === 0) { + this.OnChangeAttackPrep(fileName, fileType); + } + if (!isChecked && cmdAttk === 0) { if (this.copyMode) { this.filesFormArray = this.createForm.get('files').value; } const index = this.filesFormArray.indexOf(fileId); - this.filesFormArray.splice(index,1); - this.createForm.patchValue({files: this.filesFormArray}); + this.filesFormArray.splice(index, 1); + this.createForm.patchValue({ files: this.filesFormArray }); this.OnChangeAttack(fileName, fileType, true); - } if (!isChecked && cmdAttk === 1) { + } + if (!isChecked && cmdAttk === 1) { this.OnChangeAttackPrep(fileName, fileType, true); } } - onChecked(fileId: number){ + onChecked(fileId: number) { return this.createForm.get('files').value.includes(fileId); } - OnChangeAttack(item: string, fileType: number, onRemove?: boolean){ - if(onRemove === true){ - const currentCmd = this.createForm.get('attackCmd').value; - let newCmd = item - if (fileType === 1 ){newCmd = '-r '+ newCmd;} - newCmd = currentCmd.replace(newCmd,''); - newCmd = newCmd.replace(/^\s+|\s+$/g, ""); - this.createForm.patchValue({ - attackCmd: newCmd - }); + OnChangeAttack(item: string, fileType: number, onRemove?: boolean) { + if (onRemove === true) { + const currentCmd = this.createForm.get('attackCmd').value; + let newCmd = item; + if (fileType === 1) { + newCmd = '-r ' + newCmd; + } + newCmd = currentCmd.replace(newCmd, ''); + newCmd = newCmd.replace(/^\s+|\s+$/g, ''); + this.createForm.patchValue({ + attackCmd: newCmd + }); } else { - const currentCmd = this.createForm.get('attackCmd').value; - let newCmd = item; - this.validateFile(newCmd); - if (fileType === 1 ){ - newCmd = '-r '+ newCmd; - } - this.createForm.patchValue({ - attackCmd: currentCmd+' '+ newCmd - }); + const currentCmd = this.createForm.get('attackCmd').value; + let newCmd = item; + this.validateFile(newCmd); + if (fileType === 1) { + newCmd = '-r ' + newCmd; + } + this.createForm.patchValue({ + attackCmd: currentCmd + ' ' + newCmd + }); } } - OnChangeAttackPrep(item: string, fileType: number, onRemove?: boolean){ - if(onRemove === true){ - const currentCmd = this.createForm.get('preprocessorCommand').value; - let newCmd = item - if (fileType === 1 ){newCmd = '-r '+ newCmd;} - newCmd = currentCmd.replace(newCmd,''); - newCmd = newCmd.replace(/^\s+|\s+$/g, ""); - this.createForm.patchValue({ - preprocessorCommand: newCmd - }); + OnChangeAttackPrep(item: string, fileType: number, onRemove?: boolean) { + if (onRemove === true) { + const currentCmd = this.createForm.get('preprocessorCommand').value; + let newCmd = item; + if (fileType === 1) { + newCmd = '-r ' + newCmd; + } + newCmd = currentCmd.replace(newCmd, ''); + newCmd = newCmd.replace(/^\s+|\s+$/g, ''); + this.createForm.patchValue({ + preprocessorCommand: newCmd + }); } else { - const currentCmd = this.createForm.get('preprocessorCommand').value; - let newCmd = item; - this.validateFile(newCmd); - if (fileType === 1 ){ - newCmd = '-r '+ newCmd; - } - this.createForm.patchValue({ - preprocessorCommand: currentCmd+' '+ newCmd - }); + const currentCmd = this.createForm.get('preprocessorCommand').value; + let newCmd = item; + this.validateFile(newCmd); + if (fileType === 1) { + newCmd = '-r ' + newCmd; + } + this.createForm.patchValue({ + preprocessorCommand: currentCmd + ' ' + newCmd + }); } } - validateFile(value){ - if(value.split('.').pop() == '7zip'){ - this.alert.okAlert('Hashcat has some issues loading 7z files. Better convert it to a hash file ;)',''); + validateFile(value) { + if (value.split('.').pop() == '7zip') { + this.alert.okAlert( + 'Hashcat has some issues loading 7z files. Better convert it to a hash file ;)', + '' + ); } } - onRemoveFChars(){ + onRemoveFChars() { let currentCmd = this.createForm.get('attackCmd').value; - currentCmd = currentCmd.replace(this.getBanChars(),''); + currentCmd = currentCmd.replace(this.getBanChars(), ''); this.createForm.patchValue({ attackCmd: currentCmd }); } - getBanChars(){ - const chars = this.uiService.getUIsettings('blacklistChars').value.replace(']', '\\]').replace('[', '\\['); - return new RegExp('['+chars+'\/]', "g") + getBanChars() { + const chars = this.uiService + .getUIsettings('blacklistChars') + .value.replace(']', '\\]') + .replace('[', '\\['); + return new RegExp('[' + chars + '/]', 'g'); } - getBanChar(){ + getBanChar() { return this.uiService.getUIsettings('blacklistChars').value; } - // Tooltips - tasktip: any =[] - ngOnInit(): void { - - this.route.params - .subscribe( - (params: Params) => { - this.editedIndex = +params['id']; - this.copyMode = params['id'] != null; - } - ); + this.route.params.subscribe((params: Params) => { + this.editedIndex = +params['id']; + this.copyMode = params['id'] != null; + }); this.tasktip = this.tooltipService.getTaskTooltips(); - this.route.data.subscribe(data => { + this.route.data.subscribe((data) => { switch (data['kind']) { - case 'new-task': this.whichView = 'create'; - break; + break; case 'copy-task': this.whichView = 'edit'; this.copyType = 0; this.initFormt(); - break; + break; case 'copy-pretask': this.whichView = 'edit'; this.copyType = 1; this.initFormpt(); - break; - + break; } }); - this.fetchData(); + this.fetchData(); this.dtOptions = { dom: 'Bfrtip', scrollX: true, pageLength: 25, lengthMenu: [ - [10, 25, 50, 100, 250, -1], - [10, 25, 50, 100, 250, 'All'] + [10, 25, 50, 100, 250, -1], + [10, 25, 50, 100, 250, 'All'] ], - scrollY: "700px", + scrollY: '700px', scrollCollapse: true, paging: false, autoWidth: false, buttons: { - dom: { - button: { - className: 'dt-button buttons-collection btn btn-sm-dt btn-outline-gray-600-dt', - } - }, - buttons:[] + dom: { + button: { + className: + 'dt-button buttons-collection btn btn-sm-dt btn-outline-gray-600-dt' + } + }, + buttons: [] } - } + }; this.createForm = new FormGroup({ - 'taskName': new FormControl('', [Validators.required]), - 'notes': new FormControl(''), - 'hashlistId': new FormControl(), - 'attackCmd': new FormControl(this.uiService.getUIsettings('hashlistAlias').value, [Validators.required, this.forbiddenChars(this.getBanChars())]), - 'priority': new FormControl(null || this.priority,[Validators.required, Validators.pattern("^[0-9]*$")]), - 'maxAgents': new FormControl(null || this.maxAgents), - 'chunkTime': new FormControl(null || Number(this.uiService.getUIsettings('chunktime').value)), - 'statusTimer': new FormControl(null || Number(this.uiService.getUIsettings('statustimer').value)), - 'color': new FormControl(''), - 'isCpuTask': new FormControl(null || false), - 'skipKeyspace': new FormControl(null || 0), - 'crackerBinaryId': new FormControl(null || 1), - "crackerBinaryTypeId": new FormControl(), - "isArchived": new FormControl(false), - 'staticChunks': new FormControl(null || 0), - 'chunkSize': new FormControl(null || this.chunkSize), - 'forcePipe': new FormControl(null || false), - 'preprocessorId': new FormControl(null || 0), - 'preprocessorCommand': new FormControl(''), - 'isSmall': new FormControl(null || false), - 'useNewBench': new FormControl(null || true), - 'files': new FormControl('' || []) + taskName: new FormControl('', [Validators.required]), + notes: new FormControl(''), + hashlistId: new FormControl(), + attackCmd: new FormControl( + this.uiService.getUIsettings('hashlistAlias').value, + [Validators.required, this.forbiddenChars(this.getBanChars())] + ), + priority: new FormControl(null || this.priority, [ + Validators.required, + Validators.pattern('^[0-9]*$') + ]), + maxAgents: new FormControl(null || this.maxAgents), + chunkTime: new FormControl( + null || Number(this.uiService.getUIsettings('chunktime').value) + ), + statusTimer: new FormControl( + null || Number(this.uiService.getUIsettings('statustimer').value) + ), + color: new FormControl(''), + isCpuTask: new FormControl(null || false), + skipKeyspace: new FormControl(null || 0), + crackerBinaryId: new FormControl(null || 1), + crackerBinaryTypeId: new FormControl(), + isArchived: new FormControl(false), + staticChunks: new FormControl(null || 0), + chunkSize: new FormControl(null || this.chunkSize), + forcePipe: new FormControl(null || false), + preprocessorId: new FormControl(null || 0), + preprocessorCommand: new FormControl(''), + isSmall: new FormControl(null || false), + useNewBench: new FormControl(null || true), + files: new FormControl('' || []) }); this.patchHashalias(); - } - get attckcmd(){ + get attckcmd() { return this.createForm.controls['attackCmd']; } - forbiddenChars(name: RegExp): ValidatorFn{ + forbiddenChars(name: RegExp): ValidatorFn { return (control: AbstractControl): { [key: string]: any } => { const forbidden = name.test(control.value); - return forbidden ? { 'forbidden' : { value: control.value } } : null; + return forbidden ? { forbidden: { value: control.value } } : null; }; } getValueBchars(): void { - this.uiService.getUIsettings('blacklistChars').value + this.uiService.getUIsettings('blacklistChars').value; } async fetchData() { - await this.gs.getAll(SERV.CRACKERS_TYPES).subscribe((crackers) => { this.crackertype = crackers.values; let crackerBinaryTypeId = ''; - if(this.crackertype.find(obj => obj.typeName === 'hashcat').crackerBinaryTypeId){ - crackerBinaryTypeId = this.crackertype.find(obj => obj.typeName === 'hashcat').crackerBinaryTypeId; - }else{ - crackerBinaryTypeId = this.crackertype.slice(-1)[0]['crackerBinaryTypeId']; + if ( + this.crackertype.find((obj) => obj.typeName === 'hashcat') + .crackerBinaryTypeId + ) { + crackerBinaryTypeId = this.crackertype.find( + (obj) => obj.typeName === 'hashcat' + ).crackerBinaryTypeId; + } else { + crackerBinaryTypeId = + this.crackertype.slice(-1)[0]['crackerBinaryTypeId']; } - this.gs.getAll(SERV.CRACKERS,{'maxResults': this.maxResults,'filter': 'crackerBinaryTypeId='+crackerBinaryTypeId+'' }).subscribe((crackers) => { - this.crackerversions = crackers.values; - const lastItem = this.crackerversions.slice(-1)[0]['crackerBinaryId']; - this.createForm.get('crackerBinaryTypeId').patchValue(lastItem); - }) + this.gs + .getAll(SERV.CRACKERS, { + maxResults: this.maxResults, + filter: 'crackerBinaryTypeId=' + crackerBinaryTypeId + '' + }) + .subscribe((crackers) => { + this.crackerversions = crackers.values; + const lastItem = this.crackerversions.slice(-1)[0]['crackerBinaryId']; + this.createForm.get('crackerBinaryTypeId').patchValue(lastItem); + }); }); - await this.gs.getAll(SERV.PREPROCESSORS,{'maxResults': this.maxResults }).subscribe((prep) => { - this.prep = prep.values; - }); + await this.gs + .getAll(SERV.PREPROCESSORS, { maxResults: this.maxResults }) + .subscribe((prep) => { + this.prep = prep.values; + }); - await this.gs.getAll(SERV.FILES,{'maxResults': this.maxResults, 'expand': 'accessGroup'}).subscribe((files) => { - this.allfiles = files.values; - this.dtElement.dtInstance.then((dtInstance: DataTables.Api) => { - setTimeout(() => { - this.dtTrigger[0].next(null); - dtInstance.columns.adjust(); + await this.gs + .getAll(SERV.FILES, { + maxResults: this.maxResults, + expand: 'accessGroup' + }) + .subscribe((files) => { + this.allfiles = files.values; + this.dtElement.dtInstance.then((dtInstance: DataTables.Api) => { + setTimeout(() => { + this.dtTrigger[0].next(null); + dtInstance.columns.adjust(); + }); }); - }); - }); + }); } - patchHashalias(){ + patchHashalias() { this.createForm.patchValue({ - attackCmd:this.uiService.getUIsettings('hashlistAlias').value + attackCmd: this.uiService.getUIsettings('hashlistAlias').value }); } - active= 1; //Active show first table wordlist - ngAfterViewInit() { - - setTimeout(() => { - this.active = 1; - },2000); this.dtTrigger.next(null); - const params = {'maxResults': this.maxResults}; - - this.gs.getAll(SERV.HASHLISTS,params).subscribe((hlist: any) => { + this.gs.getAll(SERV.HASHLISTS).subscribe((hlist: any) => { const self = this; const response = hlist.values; - ($("#hashlist") as any).selectize({ + ($('#hashlist') as any).selectize({ plugins: ['remove_button'], preload: true, create: false, - valueField: "hashlistId", - placeholder: "Search hashlist...", - labelField: "name", - searchField: ["name"], + valueField: 'hashlistId', + placeholder: 'Search hashlist...', + labelField: 'name', + searchField: ['name'], loadingClass: 'Loading...', highlight: true, onChange: function (value) { @@ -355,48 +411,48 @@ export class NewTasksComponent implements OnInit { }, render: { option: function (item, escape) { - return '
' + escape(item.hashlistId) + ' - ' + escape(item.name) + '
'; - }, + return ( + '
' + + escape(item.hashlistId) + + ' - ' + + escape(item.name) + + '
' + ); + } }, load: function (query, callback) { if (self.copyMode && self.copyType === 0) { - let that = this; - self.gs.get(SERV.TASKS,self.editedIndex,{'expand': 'hashlist'}).subscribe((result)=>{ - that.setValue(result.hashlist[0]['hashlistId']); - }) + const that = this; + self.gs + .get(SERV.TASKS, self.editedIndex, { expand: 'hashlist' }) + .subscribe((result) => { + that.setValue(result.hashlist[0]['hashlistId']); + }); } }, - onInitialize: function(){ - const selectize = this; - selectize.addOption(response); - const selected_items = []; - $.each(response, function( i, obj) { - selected_items.push(obj.id); - }); - selectize.setValue(selected_items); - }, + onInitialize: function () { + const selectize = this; + selectize.addOption(response); + const selected_items = []; + $.each(response, function (i, obj) { + selected_items.push(obj.id); }); + selectize.setValue(selected_items); + } }); - - } - - OnChangeValue(value){ - this.createForm.patchValue({ - color: value }); - this._changeDetectorRef.detectChanges(); } - OnChangeHashlist(value){ + OnChangeHashlist(value) { this.createForm.patchValue({ hashlistId: Number(value) }); this._changeDetectorRef.detectChanges(); } - onChangeBinary(id: string){ - const params = {'filter': 'crackerBinaryTypeId='+id+''}; - this.gs.getAll(SERV.CRACKERS,params).subscribe((crackers: any) => { + onChangeBinary(id: string) { + const params = { filter: 'crackerBinaryTypeId=' + id + '' }; + this.gs.getAll(SERV.CRACKERS, params).subscribe((crackers: any) => { this.crackerversions = crackers.values; crackers.values.sort(this.compareVersions); const lastItem = this.crackerversions.slice(-1)[0]['crackerBinaryId']; @@ -406,7 +462,7 @@ export class NewTasksComponent implements OnInit { /** * Compare software versions instead of using id - */ + */ compareVersions(a, b): number { // Split the version strings into arrays of integers @@ -429,149 +485,157 @@ export class NewTasksComponent implements OnInit { return 0; } - onSubmit(){ + onSubmit() { if (this.createForm.valid) { - this.gs.create(SERV.TASKS,this.createForm.value).subscribe(() => { - this.alert.okAlert('New Task created!',''); + this.gs.create(SERV.TASKS, this.createForm.value).subscribe(() => { + this.alert.okAlert('New Task created!', ''); this.createForm.reset(); this.router.navigate(['tasks/show-tasks']); - } - ); + }); } } // Copied from Task private initFormt() { if (this.copyMode) { - this.gs.get(SERV.TASKS,this.editedIndex,{'expand': 'hashlist,speeds,crackerBinary,crackerBinaryType,files'}).subscribe((result)=>{ - this.color = result['color']; - const arrFiles: Array = []; - if(result.files){ - for(let i=0; i < result.files.length; i++){ - arrFiles.push(result.files[i]['fileId']); - } - } - this.createForm = new FormGroup({ - 'taskName': new FormControl(result['taskName']+'_(Copied_task_id_'+this.editedIndex+')', [Validators.required, Validators.minLength(1)]), - 'notes': new FormControl('Copied from task id'+this.editedIndex+''), - 'hashlistId': new FormControl(result.hashlist['hashlistId']), - 'attackCmd': new FormControl(result['attackCmd'], [Validators.required, this.forbiddenChars(/[&*;$()\[\]{}'"\\|<>\/]/)]), - 'maxAgents': new FormControl(result['maxAgents']), - 'chunkTime': new FormControl(result['chunkTime']), - 'statusTimer': new FormControl(result['statusTimer']), - 'priority': new FormControl(result['priority']), - 'color': new FormControl(result['color']), - 'isCpuTask': new FormControl(result['isCpuTask']), - 'crackerBinaryTypeId': new FormControl(result['crackerBinaryTypeId']), - 'isSmall': new FormControl(result['isSmall']), - 'useNewBench': new FormControl(result['useNewBench']), - // 'isMaskImport': new FormControl(result['isMaskImport']), - 'skipKeyspace': new FormControl(result['skipKeyspace']), - 'crackerBinaryId': new FormControl(result.crackerBinary['crackerBinaryId']), - "isArchived": new FormControl(false), - 'staticChunks': new FormControl(result['staticChunks']), - 'chunkSize': new FormControl(result['chunkSize']), - 'forcePipe': new FormControl(result['forcePipe']), - 'preprocessorId': new FormControl(result['preprocessorId']), - 'preprocessorCommand': new FormControl(result['preprocessorCommand']), - 'files': new FormControl(arrFiles) - }); - }); - } + this.gs + .get(SERV.TASKS, this.editedIndex, { + expand: 'hashlist,speeds,crackerBinary,crackerBinaryType,files' + }) + .subscribe((result) => { + const arrFiles: Array = []; + if (result.files) { + for (let i = 0; i < result.files.length; i++) { + arrFiles.push(result.files[i]['fileId']); + } + } + this.createForm = new FormGroup({ + taskName: new FormControl( + result['taskName'] + '_(Copied_task_id_' + this.editedIndex + ')', + [Validators.required, Validators.minLength(1)] + ), + notes: new FormControl( + 'Copied from task id' + this.editedIndex + '' + ), + hashlistId: new FormControl(result.hashlist['hashlistId']), + attackCmd: new FormControl(result['attackCmd'], [ + Validators.required, + this.forbiddenChars(/[&*;$()\[\]{}'"\\|<>\/]/) + ]), + maxAgents: new FormControl(result['maxAgents']), + chunkTime: new FormControl(result['chunkTime']), + statusTimer: new FormControl(result['statusTimer']), + priority: new FormControl(result['priority']), + color: new FormControl(result['color']), + isCpuTask: new FormControl(result['isCpuTask']), + crackerBinaryTypeId: new FormControl(result['crackerBinaryTypeId']), + isSmall: new FormControl(result['isSmall']), + useNewBench: new FormControl(result['useNewBench']), + // 'isMaskImport': new FormControl(result['isMaskImport']), + skipKeyspace: new FormControl(result['skipKeyspace']), + crackerBinaryId: new FormControl( + result.crackerBinary['crackerBinaryId'] + ), + isArchived: new FormControl(false), + staticChunks: new FormControl(result['staticChunks']), + chunkSize: new FormControl(result['chunkSize']), + forcePipe: new FormControl(result['forcePipe']), + preprocessorId: new FormControl(result['preprocessorId']), + preprocessorCommand: new FormControl(result['preprocessorCommand']), + files: new FormControl(arrFiles) + }); + }); + } } // Copied from PreTask private initFormpt() { if (this.copyMode) { - this.gs.get(SERV.PRETASKS,this.editedIndex,{'expand':'pretaskFiles'}).subscribe((result)=>{ - this.color = result['color']; - const arrFiles: Array = []; - if(result['pretaskFiles']){ - for(let i=0; i < result['pretaskFiles'].length; i++){ - arrFiles.push(result['pretaskFiles'][i]['fileId']); - } - this.copyFiles = arrFiles; - } - this.createForm = new FormGroup({ - 'taskName': new FormControl(result['taskName']+'_(Copied_pretask_id_'+this.editedIndex+')', [Validators.required, Validators.minLength(1)]), - 'notes': new FormControl('Copied from pretask id '+this.editedIndex+''), - 'hashlistId': new FormControl(), - 'attackCmd': new FormControl(result['attackCmd'], [Validators.required, this.forbiddenChars(/[&*;$()\[\]{}'"\\|<>\/]/)]), - 'maxAgents': new FormControl(result['maxAgents']), - 'chunkTime': new FormControl(result['chunkTime']), - 'statusTimer': new FormControl(result['statusTimer']), - 'priority': new FormControl(result['priority']), - 'color': new FormControl(result['color']), - 'isCpuTask': new FormControl(result['isCpuTask']), - 'crackerBinaryTypeId': new FormControl(result['crackerBinaryTypeId']), - 'isSmall': new FormControl(result['isSmall']), - 'useNewBench': new FormControl(result['useNewBench']), - // 'isMaskImport': new FormControl(result['isMaskImport']), //Now is not working with it - 'skipKeyspace': new FormControl(null || 0), - 'crackerBinaryId': new FormControl(null || 1), - "isArchived": new FormControl(false), - 'staticChunks': new FormControl(null || 0), - 'chunkSize': new FormControl(null || this.chunkSize), - 'forcePipe': new FormControl(null || false), - 'preprocessorId': new FormControl(0), - 'preprocessorCommand': new FormControl(''), - 'files': new FormControl(arrFiles), - }); - }); - } - } - - ngOnDestroy(){ - this.dtTrigger.unsubscribe(); - } - - rerender(): void { - this.dtElement.dtInstance.then((dtInstance: DataTables.Api) => { - // Destroy the table first - dtInstance.destroy(); - // Call the dtTrigger to rerender again - setTimeout(() => { - this.dtTrigger['new'].next(); - }); - }); + this.gs + .get(SERV.PRETASKS, this.editedIndex, { expand: 'pretaskFiles' }) + .subscribe((result) => { + const arrFiles: Array = []; + if (result['pretaskFiles']) { + for (let i = 0; i < result['pretaskFiles'].length; i++) { + arrFiles.push(result['pretaskFiles'][i]['fileId']); + } + this.copyFiles = arrFiles; + } + this.createForm = new FormGroup({ + taskName: new FormControl( + result['taskName'] + + '_(Copied_pretask_id_' + + this.editedIndex + + ')', + [Validators.required, Validators.minLength(1)] + ), + notes: new FormControl( + 'Copied from pretask id ' + this.editedIndex + '' + ), + hashlistId: new FormControl(), + attackCmd: new FormControl(result['attackCmd'], [ + Validators.required, + this.forbiddenChars(/[&*;$()\[\]{}'"\\|<>\/]/) + ]), + maxAgents: new FormControl(result['maxAgents']), + chunkTime: new FormControl(result['chunkTime']), + statusTimer: new FormControl(result['statusTimer']), + priority: new FormControl(result['priority']), + color: new FormControl(result['color']), + isCpuTask: new FormControl(result['isCpuTask']), + crackerBinaryTypeId: new FormControl(result['crackerBinaryTypeId']), + isSmall: new FormControl(result['isSmall']), + useNewBench: new FormControl(result['useNewBench']), + // 'isMaskImport': new FormControl(result['isMaskImport']), //Now is not working with it + skipKeyspace: new FormControl(null || 0), + crackerBinaryId: new FormControl(null || 1), + isArchived: new FormControl(false), + staticChunks: new FormControl(null || 0), + chunkSize: new FormControl(null || this.chunkSize), + forcePipe: new FormControl(null || false), + preprocessorId: new FormControl(0), + preprocessorCommand: new FormControl(''), + files: new FormControl(arrFiles) + }); + }); + } } // @HostListener allows us to also guard against browser refresh, close, etc. @HostListener('window:beforeunload', ['$event']) unloadNotification($event: any) { if (!this.canDeactivate()) { - $event.returnValue = "IE and Edge Message"; + $event.returnValue = 'IE and Edge Message'; } } canDeactivate(): Observable | boolean { if (this.createForm.valid) { - return false; + return false; } return true; } // Modal Information - closeResult = ''; - open(content) { - this.modalService.open(content, { size: 'xl' }).result.then( - (result) => { - this.closeResult = `Closed with: ${result}`; - }, - (reason) => { - this.closeResult = `Dismissed ${this.getDismissReason(reason)}`; - }, - ); + openHelpDialog(): void { + const dialogRef = this.dialog.open(CheatsheetComponent, { + width: '100%' + }); + dialogRef.afterClosed().subscribe((result) => { + console.log('Dialog closed with result:', result); + }); } - private getDismissReason(reason: any): string { - if (reason === ModalDismissReasons.ESC) { - return 'by pressing ESC'; - } else if (reason === ModalDismissReasons.BACKDROP_CLICK) { - return 'by clicking on a backdrop'; - } else { - return `with: ${reason}`; - } - } + // TABLE CODE TO BE REPLACED + rerender(): void { + this.dtElement.dtInstance.then((dtInstance: DataTables.Api) => { + // Destroy the table first + dtInstance.destroy(); + // Call the dtTrigger to rerender again + setTimeout(() => { + this.dtTrigger['new'].next(); + }); + }); + } } diff --git a/src/app/tasks/tasks.module.ts b/src/app/tasks/tasks.module.ts index 26500d62..7c9e5fc8 100644 --- a/src/app/tasks/tasks.module.ts +++ b/src/app/tasks/tasks.module.ts @@ -11,8 +11,6 @@ import { EditSupertasksComponent } from './edit-supertasks/edit-supertasks.compo import { EditTasksComponent } from './edit-tasks/edit-tasks.component'; import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; import { MasksComponent } from './import-supertasks/masks/masks.component'; -import { MatFormFieldModule } from '@angular/material/form-field'; -import { MatInputModule } from '@angular/material/input'; import { ModalPretasksComponent } from './supertasks/modal-pretasks/modal-pretasks.component'; import { ModalSubtasksComponent } from './show-tasks/modal-subtasks/modal-subtasks.component'; import { NewPreconfiguredTasksComponent } from './new-preconfigured-tasks/new-preconfigured-tasks.component'; @@ -27,6 +25,7 @@ import { ShowTasksComponent } from './show-tasks/show-tasks.component'; import { SupertasksComponent } from './supertasks/supertasks.component'; import { TasksRoutingModule } from './tasks-routing.module'; import { WrbulkComponent } from './import-supertasks/wrbulk/wrbulk.component'; +import { CoreFormsModule } from '../shared/forms.module'; @NgModule({ declarations: [ @@ -53,8 +52,7 @@ import { WrbulkComponent } from './import-supertasks/wrbulk/wrbulk.component'; DataTablesModule, ComponentsModule, CoreComponentsModule, - MatFormFieldModule, - MatInputModule, + CoreFormsModule, CommonModule, RouterModule, PipesModule, From 3d216d89ce1c20d32bc38c16805c2cd3ac91d2fc Mon Sep 17 00:00:00 2001 From: itbf9 Date: Fri, 24 Nov 2023 21:58:33 +0100 Subject: [PATCH 305/419] wip --- .../tables/ht-table/ht-table.component.ts | 6 ++--- .../tables/ht-table/ht-table.models.ts | 6 ++--- .../ht-table-type-editable.component.html | 2 +- .../ht-table-type-editable.component.ts | 14 ++++++++--- .../tasks-table/tasks-table.component.ts | 25 +++++++++++++------ 5 files changed, 36 insertions(+), 17 deletions(-) diff --git a/src/app/core/_components/tables/ht-table/ht-table.component.ts b/src/app/core/_components/tables/ht-table/ht-table.component.ts index 1bdde1a1..29237181 100644 --- a/src/app/core/_components/tables/ht-table/ht-table.component.ts +++ b/src/app/core/_components/tables/ht-table/ht-table.component.ts @@ -141,8 +141,8 @@ export class HTTableComponent implements OnInit, AfterViewInit { new EventEmitter>(); /** Event emitter for when the user saves an editable input */ - @Output() editableSaved: EventEmitter = - new EventEmitter(); + @Output() editableSaved: EventEmitter> = + new EventEmitter>(); /** Fetches user customizations */ private uiSettings: UISettingsUtilityClass; @@ -314,7 +314,7 @@ export class HTTableComponent implements OnInit, AfterViewInit { this.dataSource.reload(); } - editableInputSaved(editable: HTTableEditable): void { + editableInputSaved(editable: HTTableEditable): void { this.editableSaved.emit(editable); } } diff --git a/src/app/core/_components/tables/ht-table/ht-table.models.ts b/src/app/core/_components/tables/ht-table/ht-table.models.ts index 1cda661c..2cd9e020 100644 --- a/src/app/core/_components/tables/ht-table/ht-table.models.ts +++ b/src/app/core/_components/tables/ht-table/ht-table.models.ts @@ -32,8 +32,8 @@ export interface HTTableRouterLink { tooltip?: string; } -export interface HTTableEditable { - id: number; +export interface HTTableEditable { + data: T; value: string; action: string; } @@ -52,5 +52,5 @@ export interface HTTableColumn { routerLink?: (data: any) => Promise; export?: (data: any) => Promise; truncate?: boolean; - editable?: (data: any) => HTTableEditable; + editable?: (data: any) => HTTableEditable; } diff --git a/src/app/core/_components/tables/ht-table/type/editable/ht-table-type-editable.component.html b/src/app/core/_components/tables/ht-table/type/editable/ht-table-type-editable.component.html index 6f7b31ad..1e265b4c 100644 --- a/src/app/core/_components/tables/ht-table/type/editable/ht-table-type-editable.component.html +++ b/src/app/core/_components/tables/ht-table/type/editable/ht-table-type-editable.component.html @@ -24,7 +24,7 @@ matSuffix mat-icon-button color="primary" - (click)="editMode = false" + (click)="onClose()" [attr.aria-label]="'Cancel'" > close diff --git a/src/app/core/_components/tables/ht-table/type/editable/ht-table-type-editable.component.ts b/src/app/core/_components/tables/ht-table/type/editable/ht-table-type-editable.component.ts index c46fb8a5..fe8a24f5 100644 --- a/src/app/core/_components/tables/ht-table/type/editable/ht-table-type-editable.component.ts +++ b/src/app/core/_components/tables/ht-table/type/editable/ht-table-type-editable.component.ts @@ -16,13 +16,14 @@ import { HTTableColumn, HTTableEditable } from '../../ht-table.models'; changeDetection: ChangeDetectionStrategy.OnPush }) export class HTTableTypeEditableComponent implements OnInit { - editable: HTTableEditable; + editable: HTTableEditable; + original: string; @Input() element: any; @Input() tableColumn: HTTableColumn; - @Output() editableInputSaved: EventEmitter = - new EventEmitter(); + @Output() editableInputSaved: EventEmitter> = + new EventEmitter>(); @ViewChild('editableInput') editableInput: ElementRef; editMode = false; @@ -30,6 +31,7 @@ export class HTTableTypeEditableComponent implements OnInit { ngOnInit(): void { if (this.tableColumn.editable) { this.editable = this.tableColumn.editable(this.element); + this.original = this.editable.value; } } @@ -50,6 +52,7 @@ export class HTTableTypeEditableComponent implements OnInit { if (!targetElement || targetElement.tagName.toLowerCase() !== 'button') { event.stopPropagation(); this.editMode = false; + this.editable.value = this.original; } } @@ -62,4 +65,9 @@ export class HTTableTypeEditableComponent implements OnInit { this.editableInputSaved.emit(this.editable); this.editMode = false; } + + onClose(): void { + this.editMode = false; + this.editable.value = this.original; + } } 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 b6399ef3..9161a69d 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 @@ -30,6 +30,8 @@ import { } from './tasks-table.constants'; import { TaskWrapper } from 'src/app/core/_models/task-wrapper.model'; import { TasksDataSource } from 'src/app/core/_datasources/tasks.datasource'; +import { MatSnackBarConfig } from '@angular/material/snack-bar'; +import { wrap } from 'module'; @Component({ selector: 'tasks-table', @@ -93,7 +95,7 @@ export class TasksTableComponent dataKey: 'priority', editable: (wrapper: TaskWrapper) => { return { - id: wrapper._id, + data: wrapper, value: wrapper.priority + '', action: TaskTableEditableAction.CHANGE_PRIORITY }; @@ -503,21 +505,30 @@ export class TasksTableComponent ); } - editableSaved(editable: HTTableEditable): void { + editableSaved(editable: HTTableEditable): void { switch (editable.action) { case TaskTableEditableAction.CHANGE_PRIORITY: - this.changePriority(editable.id, editable.value); + this.changePriority(editable.data, editable.value); break; } } - private changePriority(id: number, value: string): void { - const val = parseInt(value); + private changePriority(wrapper: TaskWrapper, priority: string): void { + let val = 0; + try { + val = parseInt(priority); + } catch (error) { + // Do nothing + } + + if (!val || wrapper.priority == val) { + this.snackBar.open('Nothing changed!', 'Close'); + return; + } - const request$ = this.gs.update(SERV.TASKS_WRAPPER, id, { + const request$ = this.gs.update(SERV.TASKS_WRAPPER, wrapper._id, { priority: val }); - this.subscriptions.push( request$ .pipe( From fdb99813365dfa8973c2be44d16de16975d342ec Mon Sep 17 00:00:00 2001 From: Dan Date: Mon, 27 Nov 2023 15:10:45 +0100 Subject: [PATCH 306/419] wip --- .../menus/base-menu/base-menu.component.ts | 9 +- .../bulk-action-menu.component.ts | 22 +- .../row-action-menu.component.ts | 34 +- .../row-action-menu.constants.ts | 7 +- .../agents-table/agents-table.component.html | 1 + .../agents-table/agents-table.component.ts | 94 +++++- .../agents-table/agents-table.constants.ts | 4 + .../tables/base-table/base-table.component.ts | 2 + .../chunks-table/chunks-table.component.ts | 6 +- .../ht-table-type-editable.component.html | 1 + .../ht-table-type-editable.component.ts | 7 + .../tasks-table/tasks-table.component.html | 4 +- .../tasks-table/tasks-table.component.ts | 300 +++++++++++------- .../tasks-table/tasks-table.constants.ts | 38 +-- src/app/core/_datasources/base.datasource.ts | 4 + src/app/core/_datasources/tasks.datasource.ts | 13 +- src/app/core/_models/config-ui.model.ts | 4 +- src/app/core/_models/task-wrapper.model.ts | 2 + src/app/core/_models/task.model.ts | 81 +++-- .../edit-hashlist.component.html | 2 + .../hashlists/hashlist/hashlist.component.ts | 5 + .../show-tasks/show-tasks.component.html | 9 +- .../tasks/show-tasks/show-tasks.component.ts | 17 +- src/app/tasks/tasks.module.ts | 2 + 24 files changed, 463 insertions(+), 205 deletions(-) diff --git a/src/app/core/_components/menus/base-menu/base-menu.component.ts b/src/app/core/_components/menus/base-menu/base-menu.component.ts index 102ec759..346a3aba 100644 --- a/src/app/core/_components/menus/base-menu/base-menu.component.ts +++ b/src/app/core/_components/menus/base-menu/base-menu.component.ts @@ -77,11 +77,11 @@ export class BaseMenuComponent { } /** - * Check if the data row is of type "Task". - * @returns `true` if the data row is a task; otherwise, `false`. + * Check if the data row is of type "TaskWrapper". + * @returns `true` if the data row is a task wrapper; otherwise, `false`. */ - protected isTask(): boolean { - return this.checkId('taskId') && 'taskName' in this.data; + protected isTaskWrapper(): boolean { + return this.checkId('taskWrapperId') && 'priority' in this.data; } /** @@ -132,6 +132,7 @@ export class BaseMenuComponent { try { return ( this.data['_id'] === this.data['hashlistId'] && + 'brainFeatures' in this.data && this.data['format'] !== HashListFormat.SUPERHASHLIST ); } catch (error) { diff --git a/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.component.ts b/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.component.ts index f29bc745..9ebdd536 100644 --- a/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.component.ts +++ b/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.component.ts @@ -53,7 +53,15 @@ export class BulkActionMenuComponent BulkActionMenuLabel.DELETE_NOTIFICATIONS ); } else if (this.dataType === 'hashlists') { - this.setHashlistMenu(); + this.setArchiveDeleteMenu( + BulkActionMenuLabel.DELETE_HASHLISTS, + BulkActionMenuLabel.ARCHIVE_HASHLISTS + ); + } else if (this.dataType === 'tasks') { + this.setArchiveDeleteMenu( + BulkActionMenuLabel.DELETE_TASKS, + BulkActionMenuLabel.ARCHIVE_TASKS + ); } else if (this.dataType === 'access-groups') { this.setDeleteMenu(BulkActionMenuLabel.DELETE_ACCESSGROUPS); } else if (this.dataType === 'permissions') { @@ -91,6 +99,18 @@ export class BulkActionMenuComponent } } + private setArchiveDeleteMenu( + deleteLabel: string, + archiveLabel: string + ): void { + if (this.isArchived) { + this.setActionMenuItems(0, [this.getDeleteMenuItem(deleteLabel)]); + } else { + this.setActionMenuItems(0, [this.getArchiveMenuItem(archiveLabel)]); + this.setActionMenuItems(1, [this.getDeleteMenuItem(deleteLabel)]); + } + } + /** * Sets the bulk menu items for a data type with only a delete option. * @param label Delete action label. diff --git a/src/app/core/_components/menus/row-action-menu/row-action-menu.component.ts b/src/app/core/_components/menus/row-action-menu/row-action-menu.component.ts index 5aa4703f..3ddba43a 100644 --- a/src/app/core/_components/menus/row-action-menu/row-action-menu.component.ts +++ b/src/app/core/_components/menus/row-action-menu/row-action-menu.component.ts @@ -59,8 +59,8 @@ export class RowActionMenuComponent this.setUserMenu(); } else if (this.isNotification()) { this.setNotificationMenu(); - } else if (this.isTask()) { - this.setTaskMenu(); + } else if (this.isTaskWrapper()) { + this.setTaskWrapperMenu(); } else if (this.isHashlist()) { this.setHashlistMenu(); } else if (this.isHashtype()) { @@ -202,7 +202,7 @@ export class RowActionMenuComponent /** * Sets the context menu items for a task data row. */ - private setTaskMenu(): void { + private setTaskWrapperMenu(): void { this.setActionMenuItems(0, [ this.getEditMenuItem(RowActionMenuLabel.EDIT_TASK) ]); @@ -230,10 +230,17 @@ export class RowActionMenuComponent }); } - this.addActionMenuItem( - 0, - this.getArchiveMenuItem(RowActionMenuLabel.ARCHIVE_TASK) - ); + if (this.data.isArchived) { + this.addActionMenuItem( + 0, + this.getUnarchiveMenuItem(RowActionMenuLabel.UNARCHIVE_TASK) + ); + } else { + this.addActionMenuItem( + 0, + this.getArchiveMenuItem(RowActionMenuLabel.ARCHIVE_TASK) + ); + } } /** @@ -324,6 +331,19 @@ export class RowActionMenuComponent }; } + /** + * Creates an ActionMenuItem with archive action. + * @param label The label for the menu item. + * @returns The ActionMenuItem with archive action. + */ + private getUnarchiveMenuItem(label: string): ActionMenuItem { + return { + label: label, + action: RowActionMenuAction.UNARCHIVE, + icon: RowActionMenuIcon.UNARCHIVE + }; + } + /** * Creates an ActionMenuItem with activate action. * @param label The label for the menu item. 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 3b865779..9c73a916 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 @@ -37,7 +37,8 @@ export const RowActionMenuLabel = { ARCHIVE_TASK: 'Archive Task', IMPORT_HASHLIST: 'Import Hashlist', EXPORT_HASHLIST: 'Export Hashlist', - NEW_VERSION: 'Add Version' + NEW_VERSION: 'Add Version', + UNARCHIVE_TASK: 'Unarchive Task' }; export const RowActionMenuAction = { @@ -51,7 +52,8 @@ export const RowActionMenuAction = { IMPORT: 'import', EXPORT: 'export', ACTIVATE: 'activate', - DEACTIVATE: 'deactivate' + DEACTIVATE: 'deactivate', + UNARCHIVE: 'unrachive' }; export const RowActionMenuIcon = { @@ -62,6 +64,7 @@ export const RowActionMenuIcon = { EXPORT: 'arrow_downward', COPY: 'content_copy', ARCHIVE: 'archive', + UNARCHIVE: 'unarchive', ACTIVATE: 'check_circle', DEACTIVATE: 'remove_circle' }; diff --git a/src/app/core/_components/tables/agents-table/agents-table.component.html b/src/app/core/_components/tables/agents-table/agents-table.component.html index fcbb83e5..bf81b7c4 100644 --- a/src/app/core/_components/tables/agents-table/agents-table.component.html +++ b/src/app/core/_components/tables/agents-table/agents-table.component.html @@ -8,6 +8,7 @@ [isFilterable]="isFilterable" [hasRowAction]="hasRowAction" [hasBulkActions]="hasBulkActions" + (editableSaved)="editableSaved($event)" [filterFn]="filter" [isPageable]="true" (bulkActionClicked)="bulkActionClicked($event)" diff --git a/src/app/core/_components/tables/agents-table/agents-table.component.ts b/src/app/core/_components/tables/agents-table/agents-table.component.ts index 85a93435..76abd6b8 100644 --- a/src/app/core/_components/tables/agents-table/agents-table.component.ts +++ b/src/app/core/_components/tables/agents-table/agents-table.component.ts @@ -1,7 +1,12 @@ +import { + AgentTableEditableAction, + AgentsTableColumnLabel +} from './agents-table.constants'; /* eslint-disable @angular-eslint/component-selector */ import { Component, Input, OnDestroy, OnInit } from '@angular/core'; import { HTTableColumn, + HTTableEditable, HTTableIcon, HTTableRouterLink } from '../ht-table/ht-table.models'; @@ -15,7 +20,6 @@ import { AccessGroup } from 'src/app/core/_models/access-group.model'; import { ActionMenuEvent } from '../../menus/action-menu/action-menu.model'; import { Agent } from 'src/app/core/_models/agent.model'; import { AgentsDataSource } from 'src/app/core/_datasources/agents.datasource'; -import { AgentsTableColumnLabel } from './agents-table.constants'; import { BaseTableComponent } from '../base-table/base-table.component'; import { BulkActionMenuAction } from '../../menus/bulk-action-menu/bulk-action-menu.constants'; import { Cacheable } from 'src/app/core/_decorators/cacheable'; @@ -40,6 +44,7 @@ export class AgentsTableComponent tableColumns: HTTableColumn[] = []; dataSource: AgentsDataSource; chunkData: { [key: number]: ChunkData } = {}; + private chunkDataLock: { [key: string]: Promise } = {}; ngOnDestroy(): void { for (const sub of this.subscriptions) { @@ -71,7 +76,7 @@ export class AgentsTableComponent } getColumns(): HTTableColumn[] { - const tableColumns = [ + const tableColumns: HTTableColumn[] = [ { name: AgentsTableColumnLabel.ID, dataKey: '_id', @@ -171,6 +176,13 @@ export class AgentsTableComponent tableColumns.push({ name: AgentsTableColumnLabel.BENCHMARK, dataKey: 'benchmark', + editable: (agent: Agent) => { + return { + data: agent, + value: agent.benchmark, + action: AgentTableEditableAction.CHANGE_BENCHMARK + }; + }, isSortable: true, export: async (agent: Agent) => agent.benchmark }); @@ -195,6 +207,14 @@ export class AgentsTableComponent return tableColumns; } + editableSaved(editable: HTTableEditable): void { + switch (editable.action) { + case AgentTableEditableAction.CHANGE_BENCHMARK: + this.changeBenchmark(editable.data, editable.value); + break; + } + } + openDialog(data: DialogData) { const dialogRef = this.dialog.open(TableDialogComponent, { data: data, @@ -359,11 +379,9 @@ export class AgentsTableComponent agentId: number, key: string ): Promise { - if (!(agentId in this.chunkData)) { - this.chunkData[agentId] = await this.dataSource.getChunkData(agentId); - } - if (this.chunkData[agentId][key]) { - return this.chunkData[agentId][key]; + const cd: ChunkData = await this.getChunkData(agentId); + if (cd[key]) { + return cd[key]; } return 0; @@ -530,4 +548,66 @@ export class AgentsTableComponent this.router.navigate(links[0].routerLink); }); } + + /** + * Retrieves or fetches chunk data associated with a given agent from the data source. + * If the chunk data for the specified agent ID is not already cached, it is fetched + * asynchronously from the data source and stored in the cache for future use. + * + * @param {number} agentId - The ID of the agent for which chunk data is requested. + * @returns {Promise} - A promise that resolves to the chunk data associated with the specified agent. + * + * @remarks + * This function uses a locking mechanism to ensure that concurrent calls for the same agent ID + * do not interfere with each other. If another call is already fetching or has fetched + * the chunk data for the same agent ID, subsequent calls will wait for the operation to complete + * before proceeding. + */ + private async getChunkData(agentId: number): Promise { + if (!this.chunkDataLock[agentId]) { + // If there is no lock, create a new one + this.chunkDataLock[agentId] = (async () => { + if (!(agentId in this.chunkData)) { + // Inside the lock, await the asynchronous operation + this.chunkData[agentId] = await this.dataSource.getChunkData(agentId); + } + + // Release the lock when the operation is complete + delete this.chunkDataLock[agentId]; + })(); + } + + // Wait for the lock to be released before returning the data + await this.chunkDataLock[agentId]; + + return this.chunkData[agentId]; + } + + private changeBenchmark(agent: Agent, benchmark: string): void { + if (!benchmark || agent.benchmark == benchmark) { + this.snackBar.open('Nothing changed!', 'Close'); + return; + } + + const request$ = this.gs.update(SERV.AGENT_ASSIGN, agent._id, { + benchmark: benchmark + }); + this.subscriptions.push( + request$ + .pipe( + catchError((error) => { + this.snackBar.open(`Failed to update benchmark!`, 'Close'); + console.error('Failed to update benchmark:', error); + return []; + }) + ) + .subscribe(() => { + this.snackBar.open( + `Changed benchmark to ${benchmark} on Agent #${agent._id}!`, + 'Close' + ); + this.reload(); + }) + ); + } } diff --git a/src/app/core/_components/tables/agents-table/agents-table.constants.ts b/src/app/core/_components/tables/agents-table/agents-table.constants.ts index 3270d364..9d8c6af1 100644 --- a/src/app/core/_components/tables/agents-table/agents-table.constants.ts +++ b/src/app/core/_components/tables/agents-table/agents-table.constants.ts @@ -15,3 +15,7 @@ export const AgentsTableColumnLabel = { SEARCHED: 'Keyspace Searched', CRACKED: 'Cracked' }; + +export const AgentTableEditableAction = { + CHANGE_BENCHMARK: 'change-benchmark' +}; 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 8c5a0e1e..d49fb356 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 @@ -37,6 +37,8 @@ export class BaseTableComponent { @ViewChild('table') table: HTTableComponent; + @Input() hashlistId: number; + /** Name of the table, used when storing user customizations */ @Input() name: string; /** Flag to enable bulk action menu */ 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 97645569..6c1dcd70 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 @@ -70,14 +70,14 @@ export class ChunksTableComponent extends BaseTableComponent implements OnInit { { name: ChunksTableColumnLabel.TASK, dataKey: 'taskName', - routerLink: (chunk: Chunk) => this.renderTaskLink(chunk.taskId), + routerLink: (chunk: Chunk) => this.renderTaskLink(chunk), isSortable: true }, { name: ChunksTableColumnLabel.AGENT, dataKey: 'agentName', render: (chunk: Chunk) => this.renderAgent(chunk), - routerLink: (chunk: Chunk) => this.renderAgentLink(chunk.agentId), + routerLink: (chunk: Chunk) => this.renderAgentLink(chunk), isSortable: true }, { @@ -107,7 +107,7 @@ export class ChunksTableComponent extends BaseTableComponent implements OnInit { { name: ChunksTableColumnLabel.CRACKED, dataKey: 'cracked', - routerLink: (chunk: Chunk) => this.renderCrackedLink(chunk.taskId), + routerLink: (chunk: Chunk) => this.renderCrackedLink(chunk), isSortable: true } ]; diff --git a/src/app/core/_components/tables/ht-table/type/editable/ht-table-type-editable.component.html b/src/app/core/_components/tables/ht-table/type/editable/ht-table-type-editable.component.html index 1e265b4c..59251044 100644 --- a/src/app/core/_components/tables/ht-table/type/editable/ht-table-type-editable.component.html +++ b/src/app/core/_components/tables/ht-table/type/editable/ht-table-type-editable.component.html @@ -10,6 +10,7 @@ [class.hidden]="!editMode" (blur)="onBlur($event)" (focus)="onFocus($event)" + (keyup.enter)="onEditableInputEnter($event)" />
+
diff --git a/src/app/tasks/new-preconfigured-tasks/new-preconfigured-tasks.component.ts b/src/app/tasks/new-preconfigured-tasks/new-preconfigured-tasks.component.ts index 02721c8f..e3c4ad57 100644 --- a/src/app/tasks/new-preconfigured-tasks/new-preconfigured-tasks.component.ts +++ b/src/app/tasks/new-preconfigured-tasks/new-preconfigured-tasks.component.ts @@ -1,4 +1,10 @@ -import { AbstractControl, FormControl, FormGroup, ValidatorFn, Validators } from '@angular/forms'; +import { + AbstractControl, + FormControl, + FormGroup, + ValidatorFn, + Validators +} from '@angular/forms'; import { AfterViewInit, Component, OnInit, ViewChild } from '@angular/core'; import { ModalDismissReasons, NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { faInfoCircle, faLock } from '@fortawesome/free-solid-svg-icons'; @@ -10,328 +16,314 @@ import { Subject } from 'rxjs'; import { UIConfigService } from 'src/app/core/_services/shared/storage.service'; import { AlertService } from 'src/app/core/_services/shared/alert.service'; import { GlobalService } from 'src/app/core/_services/main.service'; -import { colorpicker } from '../../core/_constants/settings.config'; import { FileTypePipe } from 'src/app/core/_pipes/file-type.pipe'; import { PageTitle } from 'src/app/core/_decorators/autotitle'; import { SERV } from '../../core/_services/main.config'; +import { UnsubscribeService } from 'src/app/core/_services/unsubscribe.service'; +import { ChangeDetectorRef } from '@angular/core'; +import { AutoTitleService } from 'src/app/core/_services/shared/autotitle.service'; +import { benchmarkType } from 'src/app/core/_constants/tasks.config'; +import { transformSelectOptions } from 'src/app/shared/utils/forms'; @Component({ selector: 'app-new-preconfigured-tasks', templateUrl: './new-preconfigured-tasks.component.html' }) -@PageTitle(['New Preconfigured Tasks']) -export class NewPreconfiguredTasksComponent implements OnInit,AfterViewInit { - @ViewChild('cmdAttack', {static: true}) cmdAttack: any; - - faInfoCircle=faInfoCircle; - faLock=faLock; - // Config - private maxResults = environment.config.prodApiMaxResults - private priority = environment.config.tasks.priority; - private maxAgents = environment.config.tasks.maxAgents; - - constructor( - private uiService: UIConfigService, - private modalService: NgbModal, - private fileType: FileTypePipe, - private route:ActivatedRoute, - private alert: AlertService, - private gs: GlobalService, - private router: Router - ) { } +export class NewPreconfiguredTasksComponent implements OnInit, AfterViewInit { + /** Flag indicating whether data is still loading. */ + isLoading = true; + + /** Form group for the Pretask. */ + createForm: FormGroup; + + /** Select Options. */ + selectBenchmarktype = benchmarkType; + selectCrackertype: any; + + /** Select Options Mapping */ + selectCrackertypeMap = { + fieldMapping: { + name: 'typeName', + _id: 'crackerBinaryTypeId' + } + }; + @ViewChild('cmdAttack', { static: true }) cmdAttack: any; copyMode = false; editedIndex: number; whichView: string; - createForm: FormGroup - crackertype: any - color: any; - colorpicker=colorpicker; + // Form Preconfigured values + private priority = environment.config.tasks.priority; + private maxAgents = environment.config.tasks.maxAgents; + + // TABLES @ViewChild(DataTableDirective) dtElement: DataTableDirective; dtTrigger: Subject = new Subject(); dtOptions: any = {}; - // ToDo change to interface - public allfiles: { - fileId: number, - filename: string, - size: number, - isSecret: number, - fileType: number, - accessGroupId: number, - lineCount:number - accessGroup: { - accessGroupId: number, - groupName: string - } - }[] = []; + public allfiles: any; - ngOnDestroy(){ + constructor( + private unsubscribeService: UnsubscribeService, + private changeDetectorRef: ChangeDetectorRef, + private titleService: AutoTitleService, + private uiService: UIConfigService, + private modalService: NgbModal, + private fileType: FileTypePipe, + private route: ActivatedRoute, + private alert: AlertService, + private gs: GlobalService, + private router: Router + ) { + titleService.set(['New Preconfigured Tasks']); + } + + ngOnDestroy() { this.dtTrigger.unsubscribe(); } // New checkbox filesFormArray: Array = []; - onChange(fileId:number, fileType:number, fileName: string, $target: EventTarget) { + onChange( + fileId: number, + fileType: number, + fileName: string, + $target: EventTarget + ) { const isChecked = ($target).checked; - if(isChecked) { + if (isChecked) { if (this.copyMode) { this.filesFormArray = this.createForm.get('files').value; } this.filesFormArray.push(fileId); this.OnChangeAttack(fileName, fileType); - this.createForm.patchValue({files: this.filesFormArray }); + this.createForm.patchValue({ files: this.filesFormArray }); } else { if (this.copyMode) { this.filesFormArray = this.createForm.get('files').value; } const index = this.filesFormArray.indexOf(fileId); - this.filesFormArray.splice(index,1); - this.createForm.patchValue({files: this.filesFormArray}); + this.filesFormArray.splice(index, 1); + this.createForm.patchValue({ files: this.filesFormArray }); this.OnChangeAttack(fileName, fileType, true); } } - onChecked(fileId: number){ + onChecked(fileId: number) { return this.createForm.get('files').value.includes(fileId); } - OnChangeAttack(item: string, fileType: number, onRemove?: boolean){ - if(onRemove == true){ + OnChangeAttack(item: string, fileType: number, onRemove?: boolean) { + if (onRemove == true) { const currentCmd = this.createForm.get('attackCmd').value; - let newCmd = item - if (fileType === 1 ){newCmd = '-r '+ newCmd;} - newCmd = currentCmd.replace(newCmd,''); - newCmd = newCmd.replace(/^\s+|\s+$/g, ""); + let newCmd = item; + if (fileType === 1) { + newCmd = '-r ' + newCmd; + } + newCmd = currentCmd.replace(newCmd, ''); + newCmd = newCmd.replace(/^\s+|\s+$/g, ''); this.createForm.patchValue({ attackCmd: newCmd }); } else { const currentCmd = this.createForm.get('attackCmd').value; let newCmd = item; - this.validateFile(newCmd); - if (fileType === 1 ){ - newCmd = '-r '+ newCmd; + if (fileType === 1) { + newCmd = '-r ' + newCmd; } this.createForm.patchValue({ - attackCmd: currentCmd+' '+ newCmd + attackCmd: currentCmd + ' ' + newCmd }); } } - validateFile(value){ - if(value.split('.').pop() == '7zip'){ - this.alert.okAlert('Hashcat has some issues loading 7z files. Better convert it to a hash file ;)',''); - } - } - - ngOnInit(): void { + this.route.params.subscribe((params: Params) => { + this.editedIndex = +params['id']; + this.copyMode = params['id'] != null; + }); - this.route.params - .subscribe( - (params: Params) => { - this.editedIndex = +params['id']; - this.copyMode = params['id'] != null; - } - ); - - this.route.data.subscribe(data => { + this.route.data.subscribe((data) => { switch (data['kind']) { - case 'new-preconfigured-tasks': this.whichView = 'create'; - break; + break; case 'copy-preconfigured-tasks': this.whichView = 'edit'; this.initForm(); - break; + break; case 'copy-tasks': this.whichView = 'task'; this.initFormt(); - break; - + break; } }); this.createForm = new FormGroup({ - 'taskName': new FormControl('', [Validators.required]), - 'attackCmd': new FormControl(this.uiService.getUIsettings('hashlistAlias').value, [Validators.required, this.forbiddenChars(this.getBanChars())]), - 'maxAgents': new FormControl(null || this.maxAgents), - 'chunkTime': new FormControl(null || Number(this.uiService.getUIsettings('chunktime').value)), - 'statusTimer': new FormControl(null || Number(this.uiService.getUIsettings('statustimer').value)), - 'priority': new FormControl(0), - 'color': new FormControl(''), - 'isCpuTask': new FormControl(null || false), - "crackerBinaryTypeId": new FormControl(null || 1), - 'isSmall': new FormControl(null || false), - 'useNewBench': new FormControl(null || true), - 'isMaskImport': new FormControl(false), - 'files': new FormControl('' || []) + taskName: new FormControl('', [Validators.required]), + attackCmd: new FormControl( + this.uiService.getUIsettings('hashlistAlias').value, + [Validators.required] + ), + maxAgents: new FormControl(null || this.maxAgents), + chunkTime: new FormControl( + null || Number(this.uiService.getUIsettings('chunktime').value) + ), + statusTimer: new FormControl( + null || Number(this.uiService.getUIsettings('statustimer').value) + ), + priority: new FormControl(0), + color: new FormControl(''), + isCpuTask: new FormControl(null || false), + crackerBinaryTypeId: new FormControl(null || 1), + isSmall: new FormControl(null || false), + useNewBench: new FormControl(null || true), + isMaskImport: new FormControl(false), + files: new FormControl('' || []) }); - this.gs.getAll(SERV.CRACKERS_TYPES).subscribe((crackers: any) => { - this.crackertype = crackers.values; - }); - - const params = {'maxResults': this.maxResults, 'expand': 'accessGroup'} - - this.gs.getAll(SERV.FILES,params).subscribe((files: any) => { - this.allfiles = files.values; - this.dtElement.dtInstance.then((dtInstance: DataTables.Api) => { - setTimeout(() => { - this.dtTrigger[0].next(null); - dtInstance.columns.adjust(); + const loadCrackersSubscription$ = this.gs + .getAll(SERV.CRACKERS_TYPES) + .subscribe((response: any) => { + const transformedOptions = transformSelectOptions( + response.values, + this.selectCrackertypeMap + ); + this.selectCrackertype = transformedOptions; + }); + this.unsubscribeService.add(loadCrackersSubscription$); + + this.gs + .getAll(SERV.FILES, { expand: 'accessGroup' }) + .subscribe((files: any) => { + this.allfiles = files.values; + this.dtElement.dtInstance.then((dtInstance: DataTables.Api) => { + setTimeout(() => { + this.dtTrigger[0].next(null); + dtInstance.columns.adjust(); + }); }); - }); - }); - + }); this.dtOptions = { dom: 'Bfrtip', scrollX: true, pageLength: 25, lengthMenu: [ - [10, 25, 50, 100, 250, -1], - [10, 25, 50, 100, 250, 'All'] + [10, 25, 50, 100, 250, -1], + [10, 25, 50, 100, 250, 'All'] ], - scrollY: "1000px", + scrollY: '1000px', scrollCollapse: true, paging: false, // destroy: true, buttons: { - dom: { - button: { - className: 'dt-button buttons-collection btn btn-sm-dt btn-outline-gray-600-dt', - } - }, - buttons:[] + dom: { + button: { + className: + 'dt-button buttons-collection btn btn-sm-dt btn-outline-gray-600-dt' + } + }, + buttons: [] } - } - - } - - get attckcmd(){ - return this.createForm.controls['attackCmd']; - } - - forbiddenChars(name: RegExp): ValidatorFn{ - return (control: AbstractControl): { [key: string]: any } => { - const forbidden = name.test(control.value); - return forbidden ? { 'forbidden' : { value: control.value } } : null; }; } - onRemoveFChars(){ - let currentCmd = this.createForm.get('attackCmd').value; - currentCmd = currentCmd.replace(this.getBanChars(),''); - this.createForm.patchValue({ - attackCmd: currentCmd - }); - } - - getBanChars(){ - const chars = this.uiService.getUIsettings('blacklistChars').value.replace(']', '\\]').replace('[', '\\['); - return new RegExp('['+chars+'\/]', "g") - } - - getBanChar(){ - return this.uiService.getUIsettings('blacklistChars').value; - } - - // Patch Color DOM value - OnChangeValue(value){ - this.createForm.patchValue({ - color: value - }); - // this._changeDetectorRef.detectChanges(); - } - - onSubmit(){ + onSubmit() { if (this.createForm.valid) { - - this.gs.create(SERV.PRETASKS,this.createForm.value).subscribe(() => { - this.alert.okAlert('New PreTask created!',''); + this.gs.create(SERV.PRETASKS, this.createForm.value).subscribe(() => { + this.alert.okAlert('New PreTask created!', ''); this.createForm.reset(); // success, we reset form this.router.navigate(['tasks/preconfigured-tasks']); - } - ); + }); } } - public matchFileType: any - active= 0; //Active show first table wordlist + public matchFileType: any; + active = 0; //Active show first table wordlist ngAfterViewInit() { - setTimeout(() => { - this.active =1; - },1000); + this.active = 1; + }, 1000); this.dtTrigger[0].next(null); - } private initForm() { if (this.copyMode) { - this.gs.get(SERV.PRETASKS,this.editedIndex, {'expand':'pretaskFiles'}).subscribe((result)=>{ - this.color = result['color']; - const arrFiles: Array = []; - if(result['pretaskFiles']){ - for(let i=0; i < result['pretaskFiles'].length; i++){ - arrFiles.push(result['pretaskFiles'][i]['fileId']); - } - } - this.createForm = new FormGroup({ - 'taskName': new FormControl(result['taskName']+'_(Copied_pretask_id_'+this.editedIndex+')', [Validators.required, Validators.minLength(1)]), - 'attackCmd': new FormControl(result['attackCmd']), - 'maxAgents': new FormControl(result['maxAgents']), - 'chunkTime': new FormControl(result['chunkTime']), - 'statusTimer': new FormControl(result['statusTimer']), - 'priority': new FormControl(result['priority']), - 'color': new FormControl(result['color']), - 'isCpuTask': new FormControl(result['isCpuTask']), - 'crackerBinaryTypeId': new FormControl(result['crackerBinaryTypeId']), - 'isSmall': new FormControl(result['isSmall']), - 'useNewBench': new FormControl(result['useNewBench']), - 'isMaskImport': new FormControl(false), - 'files': new FormControl(arrFiles), - }); - }); - } + this.gs + .get(SERV.PRETASKS, this.editedIndex, { expand: 'pretaskFiles' }) + .subscribe((result) => { + const arrFiles: Array = []; + if (result['pretaskFiles']) { + for (let i = 0; i < result['pretaskFiles'].length; i++) { + arrFiles.push(result['pretaskFiles'][i]['fileId']); + } + } + this.createForm = new FormGroup({ + taskName: new FormControl( + result['taskName'] + + '_(Copied_pretask_id_' + + this.editedIndex + + ')', + [Validators.required, Validators.minLength(1)] + ), + attackCmd: new FormControl(result['attackCmd']), + maxAgents: new FormControl(result['maxAgents']), + chunkTime: new FormControl(result['chunkTime']), + statusTimer: new FormControl(result['statusTimer']), + priority: new FormControl(result['priority']), + color: new FormControl(result['color']), + isCpuTask: new FormControl(result['isCpuTask']), + crackerBinaryTypeId: new FormControl(result['crackerBinaryTypeId']), + isSmall: new FormControl(result['isSmall']), + useNewBench: new FormControl(result['useNewBench']), + isMaskImport: new FormControl(false), + files: new FormControl(arrFiles) + }); + }); + } } private initFormt() { if (this.copyMode) { - this.gs.get(SERV.TASKS,this.editedIndex,{'expand': 'files'}).subscribe((result)=>{ - this.color = result['color']; - const arrFiles: Array = []; - if(result.files){ - for(let i=0; i < result.files.length; i++){ - arrFiles.push(result.files[i]['fileId']); - } - } - this.createForm = new FormGroup({ - 'taskName': new FormControl(result['taskName']+'_(Copied_pretask_from_task_id_'+this.editedIndex+')', [Validators.required, Validators.minLength(1)]), - 'attackCmd': new FormControl(result['attackCmd']), - 'maxAgents': new FormControl(result['maxAgents']), - 'chunkTime': new FormControl(result['chunkTime']), - 'statusTimer': new FormControl(result['statusTimer']), - 'priority': new FormControl(result['priority']), - 'color': new FormControl(result['color']), - 'isCpuTask': new FormControl(result['isCpuTask']), - 'crackerBinaryTypeId': new FormControl(result['crackerBinaryTypeId']), - 'isSmall': new FormControl(result['isSmall']), - 'useNewBench': new FormControl(result['useNewBench']), - 'isMaskImport': new FormControl(false), - 'files': new FormControl(arrFiles), - }); - }); - } + this.gs + .get(SERV.TASKS, this.editedIndex, { expand: 'files' }) + .subscribe((result) => { + const arrFiles: Array = []; + if (result.files) { + for (let i = 0; i < result.files.length; i++) { + arrFiles.push(result.files[i]['fileId']); + } + } + this.createForm = new FormGroup({ + taskName: new FormControl( + result['taskName'] + + '_(Copied_pretask_from_task_id_' + + this.editedIndex + + ')', + [Validators.required, Validators.minLength(1)] + ), + attackCmd: new FormControl(result['attackCmd']), + maxAgents: new FormControl(result['maxAgents']), + chunkTime: new FormControl(result['chunkTime']), + statusTimer: new FormControl(result['statusTimer']), + priority: new FormControl(result['priority']), + color: new FormControl(result['color']), + isCpuTask: new FormControl(result['isCpuTask']), + crackerBinaryTypeId: new FormControl(result['crackerBinaryTypeId']), + isSmall: new FormControl(result['isSmall']), + useNewBench: new FormControl(result['useNewBench']), + isMaskImport: new FormControl(false), + files: new FormControl(arrFiles) + }); + }); + } } rerender(): void { @@ -354,24 +346,23 @@ export class NewPreconfiguredTasksComponent implements OnInit,AfterViewInit { // Modal Information closeResult = ''; open(content) { - this.modalService.open(content, { size: 'xl' }).result.then( - (result) => { - this.closeResult = `Closed with: ${result}`; - }, - (reason) => { - this.closeResult = `Dismissed ${this.getDismissReason(reason)}`; - }, - ); - } - - private getDismissReason(reason: any): string { - if (reason === ModalDismissReasons.ESC) { - return 'by pressing ESC'; - } else if (reason === ModalDismissReasons.BACKDROP_CLICK) { - return 'by clicking on a backdrop'; - } else { - return `with: ${reason}`; - } - } + this.modalService.open(content, { size: 'xl' }).result.then( + (result) => { + this.closeResult = `Closed with: ${result}`; + }, + (reason) => { + this.closeResult = `Dismissed ${this.getDismissReason(reason)}`; + } + ); + } + private getDismissReason(reason: any): string { + if (reason === ModalDismissReasons.ESC) { + return 'by pressing ESC'; + } else if (reason === ModalDismissReasons.BACKDROP_CLICK) { + return 'by clicking on a backdrop'; + } else { + return `with: ${reason}`; + } + } } diff --git a/src/app/tasks/new-tasks/new-tasks.component.html b/src/app/tasks/new-tasks/new-tasks.component.html index 3e23f186..f91825c2 100644 --- a/src/app/tasks/new-tasks/new-tasks.component.html +++ b/src/app/tasks/new-tasks/new-tasks.component.html @@ -20,19 +20,10 @@
- + - - - Please remove manually the blacklisted Characters or use the button below! -
- BlackListed Chars: {{this.getBanChar()}} -
- -
-
diff --git a/src/app/tasks/new-tasks/new-tasks.component.ts b/src/app/tasks/new-tasks/new-tasks.component.ts index 6afb72e6..6693ce7e 100644 --- a/src/app/tasks/new-tasks/new-tasks.component.ts +++ b/src/app/tasks/new-tasks/new-tasks.component.ts @@ -169,7 +169,7 @@ export class NewTasksComponent implements OnInit { hashlistId: new FormControl(), attackCmd: new FormControl( this.uiService.getUIsettings('hashlistAlias').value, - [Validators.required, this.forbiddenChars(this.getBanChars())] + [Validators.required] ), priority: new FormControl(null || this.priority, [ Validators.required, @@ -367,36 +367,6 @@ export class NewTasksComponent implements OnInit { } } - onRemoveFChars() { - let currentCmd = this.createForm.get('attackCmd').value; - currentCmd = currentCmd.replace(this.getBanChars(), ''); - this.createForm.patchValue({ - attackCmd: currentCmd - }); - } - - getBanChars() { - const chars = this.uiService - .getUIsettings('blacklistChars') - .value.replace(']', '\\]') - .replace('[', '\\['); - return new RegExp('[' + chars + '/]', 'g'); - } - - getBanChar() { - return this.uiService.getUIsettings('blacklistChars').value; - } - get attckcmd() { - return this.createForm.controls['attackCmd']; - } - - forbiddenChars(name: RegExp): ValidatorFn { - return (control: AbstractControl): { [key: string]: any } => { - const forbidden = name.test(control.value); - return forbidden ? { forbidden: { value: control.value } } : null; - }; - } - handleChangeBinary(id: string) { const onChangeBinarySubscription$ = this.gs .getAll(SERV.CRACKERS, { filter: 'crackerBinaryTypeId=' + id + '' }) @@ -451,8 +421,7 @@ export class NewTasksComponent implements OnInit { ), hashlistId: new FormControl(result.hashlist['hashlistId']), attackCmd: new FormControl(result['attackCmd'], [ - Validators.required, - this.forbiddenChars(/[&*;$()\[\]{}'"\\|<>\/]/) + Validators.required ]), maxAgents: new FormControl(result['maxAgents']), chunkTime: new FormControl(result['chunkTime']), @@ -508,8 +477,7 @@ export class NewTasksComponent implements OnInit { ), hashlistId: new FormControl(), attackCmd: new FormControl(result['attackCmd'], [ - Validators.required, - this.forbiddenChars(/[&*;$()\[\]{}'"\\|<>\/]/) + Validators.required ]), maxAgents: new FormControl(result['maxAgents']), chunkTime: new FormControl(result['chunkTime']), diff --git a/src/styles/components/_alert.scss b/src/styles/components/_alert.scss index 987e26c2..e3fb815d 100644 --- a/src/styles/components/_alert.scss +++ b/src/styles/components/_alert.scss @@ -135,3 +135,8 @@ /* 04- End */ + +.error-blacklisted { + display: block; + margin-top: 5px; +} From 6fd69de37a15b4e5375c61de104227b85e80c796 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Thu, 30 Nov 2023 14:53:08 +0000 Subject: [PATCH 324/419] Style changes --- .../new-superhashlist.component.html | 2 +- .../new-supertasks.component.html | 2 +- .../edit-hashlist.component.html | 25 ++++++++++--------- .../new-preconfigured-tasks.component.html | 15 +++++------ .../tasks/new-tasks/new-tasks.component.html | 6 ++--- src/styles/components/_form.scss | 20 +++++++++++++-- 6 files changed, 44 insertions(+), 26 deletions(-) diff --git a/src/app/core/_components/forms/custom-forms/superhashlist/new-superhashlist/new-superhashlist.component.html b/src/app/core/_components/forms/custom-forms/superhashlist/new-superhashlist/new-superhashlist.component.html index 2e2e8efd..43e4e78e 100644 --- a/src/app/core/_components/forms/custom-forms/superhashlist/new-superhashlist/new-superhashlist.component.html +++ b/src/app/core/_components/forms/custom-forms/superhashlist/new-superhashlist/new-superhashlist.component.html @@ -2,7 +2,7 @@ -
+ diff --git a/src/app/core/_components/forms/custom-forms/task/new-supertasks/new-supertasks.component.html b/src/app/core/_components/forms/custom-forms/task/new-supertasks/new-supertasks.component.html index 0d1982bb..7f6988f9 100644 --- a/src/app/core/_components/forms/custom-forms/task/new-supertasks/new-supertasks.component.html +++ b/src/app/core/_components/forms/custom-forms/task/new-supertasks/new-supertasks.component.html @@ -2,7 +2,7 @@ - + diff --git a/src/app/hashlists/edit-hashlist/edit-hashlist.component.html b/src/app/hashlists/edit-hashlist/edit-hashlist.component.html index 5ab0a709..e90c9228 100644 --- a/src/app/hashlists/edit-hashlist/edit-hashlist.component.html +++ b/src/app/hashlists/edit-hashlist/edit-hashlist.component.html @@ -10,25 +10,26 @@ - +
- + + + - +
- - - - - +
- + + + + + + - - - +
diff --git a/src/app/tasks/new-preconfigured-tasks/new-preconfigured-tasks.component.html b/src/app/tasks/new-preconfigured-tasks/new-preconfigured-tasks.component.html index 5fab2963..55a350c1 100644 --- a/src/app/tasks/new-preconfigured-tasks/new-preconfigured-tasks.component.html +++ b/src/app/tasks/new-preconfigured-tasks/new-preconfigured-tasks.component.html @@ -1,13 +1,13 @@
-
- -
-

New Preconfigured Tasks (Copied From {{whichView === 'edit' ? 'Pretask' : 'Task'}} ID {{editedIndex}})

-
-
-
+
+ +
+

New Preconfigured Tasks (Copied From {{whichView === 'edit' ? 'Pretask' : 'Task'}} ID {{editedIndex}})

+
+
+
@@ -43,6 +43,7 @@

New Preconfigured Tasks (Copied From {{whichView === +

diff --git a/src/app/tasks/new-tasks/new-tasks.component.html b/src/app/tasks/new-tasks/new-tasks.component.html index f91825c2..63f5a1be 100644 --- a/src/app/tasks/new-tasks/new-tasks.component.html +++ b/src/app/tasks/new-tasks/new-tasks.component.html @@ -20,10 +20,10 @@
- +
@@ -67,8 +67,8 @@
- - + +
diff --git a/src/styles/components/_form.scss b/src/styles/components/_form.scss index fc5d0173..51900ed3 100644 --- a/src/styles/components/_form.scss +++ b/src/styles/components/_form.scss @@ -286,8 +286,24 @@ progress::-webkit-progress-value { 08- Mat Autocomplete */ -.custom-chip-grid { - width: 100%; +:host-context(.custom-chip-grid) { + .mat-chip-list.custom-chip-list { + .mat-chip.custom-chip { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .mat-form-field { + width: auto; + max-width: 400px; + margin: 0 auto; + + .mat-input-element { + width: 100%; + } + } + } } .highlight-text { From 9bd5ab7f090c9af317eb1d8fb34aa5cd2e8dee20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Thu, 30 Nov 2023 15:11:22 +0000 Subject: [PATCH 325/419] Edit agent move items --- src/app/agents/edit-agent/edit-agent.component.html | 12 +++++++----- src/styles/components/_form.scss | 1 - 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/app/agents/edit-agent/edit-agent.component.html b/src/app/agents/edit-agent/edit-agent.component.html index b8c9e482..dbdc3d69 100644 --- a/src/app/agents/edit-agent/edit-agent.component.html +++ b/src/app/agents/edit-agent/edit-agent.component.html @@ -4,10 +4,9 @@
- - - - + + +
@@ -22,11 +21,14 @@

Detailed Information

- + + + +
diff --git a/src/styles/components/_form.scss b/src/styles/components/_form.scss index 51900ed3..42066026 100644 --- a/src/styles/components/_form.scss +++ b/src/styles/components/_form.scss @@ -310,7 +310,6 @@ progress::-webkit-progress-value { background-color: $search-text-highlight-color; } - /* 08- End */ From 692588b65dcf63896c68e052cd3f8f9ad3474469 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Thu, 30 Nov 2023 15:11:58 +0000 Subject: [PATCH 326/419] edit agents blank space --- src/app/agents/edit-agent/edit-agent.component.html | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/app/agents/edit-agent/edit-agent.component.html b/src/app/agents/edit-agent/edit-agent.component.html index dbdc3d69..5be47674 100644 --- a/src/app/agents/edit-agent/edit-agent.component.html +++ b/src/app/agents/edit-agent/edit-agent.component.html @@ -5,9 +5,6 @@ - - - From 8a79a0880d888099282819eb5b74c8b12c511365 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Thu, 30 Nov 2023 17:17:46 +0000 Subject: [PATCH 327/419] Fix multi select remove item and tooltip --- .../multiselect/multiselect.component.html | 10 +++---- .../multiselect/multiselect.component.ts | 20 ++++++------- src/styles/components/_form.scss | 29 ++----------------- 3 files changed, 18 insertions(+), 41 deletions(-) diff --git a/src/app/shared/input/multiselect/multiselect.component.html b/src/app/shared/input/multiselect/multiselect.component.html index b3da0c4d..57d6566e 100644 --- a/src/app/shared/input/multiselect/multiselect.component.html +++ b/src/app/shared/input/multiselect/multiselect.component.html @@ -6,12 +6,14 @@
- + {{label}} - {{ item._id }} - ({{ item.name }}) +
+ {{ item._id }} + {{ item.name.length > 30 ? (item.name | slice:0:30) + '...' : item.name }} +
@@ -36,5 +38,3 @@
- - diff --git a/src/app/shared/input/multiselect/multiselect.component.ts b/src/app/shared/input/multiselect/multiselect.component.ts index 6d92c83b..5957e710 100644 --- a/src/app/shared/input/multiselect/multiselect.component.ts +++ b/src/app/shared/input/multiselect/multiselect.component.ts @@ -151,28 +151,28 @@ export class InputMultiSelectComponent extends AbstractInputComponent { if (!this.multiselectEnabled) { // For single-select, clear the selected items before adding the new one this.selectedItems = []; - } - - this.searchTerm = ''; // Reset the search term - this.selectedItems.push(event.option.value); - - if (!this.multiselectEnabled) { // If single-select, remove the selected item from the unselected items - const index = this.getUnselectedItems().indexOf(event.option.value); + const index = this.items.indexOf(event.option.value); + if (index !== -1) { + this.items.splice(index, 1); + } + } else { + // For multi-select, remove the selected item from the items array + const index = this.items.indexOf(event.option.value); if (index !== -1) { this.items.splice(index, 1); } } + this.searchTerm = ''; // Reset the search term + this.selectedItems.push(event.option.value); + // Update the filteredItems observable this.searchInputSubject.next(this.searchTerm); // Notify about the change this.onChangeValue(this.selectedItems); // this.onTouched(); - - // Clear the search input - this.selectInput.value = ''; } /** diff --git a/src/styles/components/_form.scss b/src/styles/components/_form.scss index 42066026..158d59fd 100644 --- a/src/styles/components/_form.scss +++ b/src/styles/components/_form.scss @@ -286,30 +286,15 @@ progress::-webkit-progress-value { 08- Mat Autocomplete */ -:host-context(.custom-chip-grid) { - .mat-chip-list.custom-chip-list { - .mat-chip.custom-chip { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } - - .mat-form-field { - width: auto; - max-width: 400px; - margin: 0 auto; - - .mat-input-element { - width: 100%; - } - } - } +.custom-chip-grid { + width: 250%; } .highlight-text { background-color: $search-text-highlight-color; } + /* 08- End */ @@ -373,11 +358,3 @@ $mat-form-field-max-width: 300px; // Adjust the maximum width as needed .mat-form-field { max-width: $mat-form-field-max-width !important; } - -.mat-chip-list.custom-chip-list { - .mat-chip.custom-chip { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } -} From 62f6aa9ee6659156a4b5195d2e3cc5c2c8b6a2e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Thu, 30 Nov 2023 19:24:08 +0000 Subject: [PATCH 328/419] Multiselect width fixed --- .../new-hashlist/new-hashlist.component.html | 33 ++++++++----------- .../edit-supertasks.component.html | 2 ++ .../tasks/new-tasks/new-tasks.component.html | 5 +-- .../supertasks/applyhashlist.component.html | 2 ++ src/styles/components/_form.scss | 15 +++++++++ 5 files changed, 36 insertions(+), 21 deletions(-) diff --git a/src/app/hashlists/new-hashlist/new-hashlist.component.html b/src/app/hashlists/new-hashlist/new-hashlist.component.html index 612e53c2..d3eaafd0 100644 --- a/src/app/hashlists/new-hashlist/new-hashlist.component.html +++ b/src/app/hashlists/new-hashlist/new-hashlist.component.html @@ -4,16 +4,15 @@ - + - - - - - - -
+
+ + + +
+
-
- - - - - - - +
+ + + + +

@@ -97,7 +93,6 @@
Hashcat Brain Enabled
- -{{form.value | json}} + diff --git a/src/app/tasks/edit-supertasks/edit-supertasks.component.html b/src/app/tasks/edit-supertasks/edit-supertasks.component.html index f607d8ef..b38d5f3a 100644 --- a/src/app/tasks/edit-supertasks/edit-supertasks.component.html +++ b/src/app/tasks/edit-supertasks/edit-supertasks.component.html @@ -17,6 +17,7 @@
Add Pretask
+
Add Pretask

formControlName="pretasks" style="flex: 1;" > + diff --git a/src/app/tasks/new-tasks/new-tasks.component.html b/src/app/tasks/new-tasks/new-tasks.component.html index 63f5a1be..48d814e6 100644 --- a/src/app/tasks/new-tasks/new-tasks.component.html +++ b/src/app/tasks/new-tasks/new-tasks.component.html @@ -7,7 +7,8 @@ - + +
diff --git a/src/app/tasks/supertasks/applyhashlist.component.html b/src/app/tasks/supertasks/applyhashlist.component.html index 5407d3ff..16614c5d 100644 --- a/src/app/tasks/supertasks/applyhashlist.component.html +++ b/src/app/tasks/supertasks/applyhashlist.component.html @@ -3,6 +3,7 @@
+
+
diff --git a/src/styles/components/_form.scss b/src/styles/components/_form.scss index 158d59fd..7bf60f46 100644 --- a/src/styles/components/_form.scss +++ b/src/styles/components/_form.scss @@ -308,6 +308,15 @@ progress::-webkit-progress-value { align-items: center; /* Align items vertically */ } +.custom-hashlist-container { + display: flex; + align-items: center; /* Align items vertically */ + button { + order: 1; + margin-left: 320px; + } +} + .color-picker-field { flex: 1; /* Allow the mat-form-field to grow and take available space */ margin-right: 16px; /* Adjust the margin between the field and the button */ @@ -358,3 +367,9 @@ $mat-form-field-max-width: 300px; // Adjust the maximum width as needed .mat-form-field { max-width: $mat-form-field-max-width !important; } + +.mat-select-value-text { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} From 11f9b5d2251fcbe5fc767f8576d17f2553db823d Mon Sep 17 00:00:00 2001 From: itbf9 Date: Fri, 1 Dec 2023 07:36:05 +0100 Subject: [PATCH 329/419] Update page title components --- .../page-headers/page-subtitle.component.ts | 13 ----- .../page-subtitle.component.html | 9 ++++ .../page-subtitle.component.scss | 8 +++ .../page-subtitle/page-subtitle.component.ts | 18 +++++++ .../page-headers/page-title.component.ts | 35 ------------- .../shared/page-headers/page-title.module.ts | 22 +++----- .../page-title/page-title.component.html | 12 +++++ .../page-title/page-title.component.ts | 21 ++++++++ src/app/shared/utils/config.ts | 50 ++++++++++--------- 9 files changed, 102 insertions(+), 86 deletions(-) delete mode 100644 src/app/shared/page-headers/page-subtitle.component.ts create mode 100644 src/app/shared/page-headers/page-subtitle/page-subtitle.component.html create mode 100644 src/app/shared/page-headers/page-subtitle/page-subtitle.component.scss create mode 100644 src/app/shared/page-headers/page-subtitle/page-subtitle.component.ts delete mode 100644 src/app/shared/page-headers/page-title.component.ts create mode 100644 src/app/shared/page-headers/page-title/page-title.component.html create mode 100644 src/app/shared/page-headers/page-title/page-title.component.ts diff --git a/src/app/shared/page-headers/page-subtitle.component.ts b/src/app/shared/page-headers/page-subtitle.component.ts deleted file mode 100644 index 56edb07a..00000000 --- a/src/app/shared/page-headers/page-subtitle.component.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Component, Input } from '@angular/core'; - -@Component({ - selector: 'app-page-subtitle', - template: ` -

{{ subtitle }}

- ` -}) -export class PageSubTitleComponent { - - @Input() subtitle: any; - -} diff --git a/src/app/shared/page-headers/page-subtitle/page-subtitle.component.html b/src/app/shared/page-headers/page-subtitle/page-subtitle.component.html new file mode 100644 index 00000000..8ae93402 --- /dev/null +++ b/src/app/shared/page-headers/page-subtitle/page-subtitle.component.html @@ -0,0 +1,9 @@ +
+

{{ subtitle }}

+ + + +
diff --git a/src/app/shared/page-headers/page-subtitle/page-subtitle.component.scss b/src/app/shared/page-headers/page-subtitle/page-subtitle.component.scss new file mode 100644 index 00000000..6dc237cb --- /dev/null +++ b/src/app/shared/page-headers/page-subtitle/page-subtitle.component.scss @@ -0,0 +1,8 @@ +.page-subtitle-wrapper { + display: flex; + justify-content: space-between; + + button { + margin-left: auto; + } +} \ No newline at end of file diff --git a/src/app/shared/page-headers/page-subtitle/page-subtitle.component.ts b/src/app/shared/page-headers/page-subtitle/page-subtitle.component.ts new file mode 100644 index 00000000..59a39962 --- /dev/null +++ b/src/app/shared/page-headers/page-subtitle/page-subtitle.component.ts @@ -0,0 +1,18 @@ +import { Component, EventEmitter, Input, Output } from '@angular/core'; + +@Component({ + selector: 'app-page-subtitle', + templateUrl: './page-subtitle.component.html', + styleUrls: ['./page-subtitle.component.scss'] +}) +export class PageSubTitleComponent { + @Input() subtitle: string; + @Input() actionTitle = ''; + @Input() actionIcon = ''; + + @Output() actionClicked: EventEmitter = new EventEmitter(); + + onAction(): void { + this.actionClicked.emit(); + } +} diff --git a/src/app/shared/page-headers/page-title.component.ts b/src/app/shared/page-headers/page-title.component.ts deleted file mode 100644 index 4f042b6f..00000000 --- a/src/app/shared/page-headers/page-title.component.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { Component, Input } from '@angular/core'; -import { Router } from '@angular/router'; - -@Component({ - selector: 'app-page-title', - template: ` -
-

{{ title }}

- - - -
-
-
-
- ` -}) -export class PageTitleComponent { - - @Input() title: any; - @Input() buttontitle?: any; - @Input() buttonlink?: any; - @Input() subbutton?: boolean; - @Input() usetoggle?: boolean; - - constructor(private router: Router) { } - - navigate() { - this.router.navigate([this.buttonlink]); - } - -} diff --git a/src/app/shared/page-headers/page-title.module.ts b/src/app/shared/page-headers/page-title.module.ts index 947925be..1218b7d5 100644 --- a/src/app/shared/page-headers/page-title.module.ts +++ b/src/app/shared/page-headers/page-title.module.ts @@ -1,12 +1,12 @@ +import { CommonModule } from '@angular/common'; import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; -import { PageSubTitleComponent } from './page-subtitle.component'; -import { PageTitleComponent } from './page-title.component'; +import { FormsModule } from '@angular/forms'; import { MatButtonModule } from '@angular/material/button'; import { MatCardModule } from '@angular/material/card'; import { MatIconModule } from '@angular/material/icon'; -import { CommonModule } from '@angular/common'; -import { FormsModule } from '@angular/forms'; import { NgModule } from '@angular/core'; +import { PageSubTitleComponent } from './page-subtitle/page-subtitle.component'; +import { PageTitleComponent } from './page-title/page-title.component'; @NgModule({ imports: [ @@ -15,15 +15,9 @@ import { NgModule } from '@angular/core'; FontAwesomeModule, MatCardModule, MatButtonModule, - MatIconModule, - ], - exports: [ - PageTitleComponent, - PageSubTitleComponent + MatIconModule ], - declarations: [ - PageTitleComponent, - PageSubTitleComponent - ] + exports: [PageTitleComponent, PageSubTitleComponent], + declarations: [PageTitleComponent, PageSubTitleComponent] }) -export class PageTitleModule { } +export class PageTitleModule {} diff --git a/src/app/shared/page-headers/page-title/page-title.component.html b/src/app/shared/page-headers/page-title/page-title.component.html new file mode 100644 index 00000000..57dd6a17 --- /dev/null +++ b/src/app/shared/page-headers/page-title/page-title.component.html @@ -0,0 +1,12 @@ +
+

{{ title }}

+ + + +
+
+
+
\ No newline at end of file diff --git a/src/app/shared/page-headers/page-title/page-title.component.ts b/src/app/shared/page-headers/page-title/page-title.component.ts new file mode 100644 index 00000000..7446d93c --- /dev/null +++ b/src/app/shared/page-headers/page-title/page-title.component.ts @@ -0,0 +1,21 @@ +import { Component, Input } from '@angular/core'; + +import { Router } from '@angular/router'; + +@Component({ + selector: 'app-page-title', + templateUrl: './page-title.component.html' +}) +export class PageTitleComponent { + @Input() title: string; + @Input() buttontitle?: string; + @Input() buttonlink?: string; + @Input() subbutton?: boolean; + @Input() usetoggle?: boolean; + + constructor(private router: Router) {} + + navigate() { + this.router.navigate([this.buttonlink]); + } +} diff --git a/src/app/shared/utils/config.ts b/src/app/shared/utils/config.ts index e42bbcc6..5b9e1509 100644 --- a/src/app/shared/utils/config.ts +++ b/src/app/shared/utils/config.ts @@ -1,16 +1,19 @@ -import { UIConfig, uiConfigDefault } from "src/app/core/_models/config-ui.model" -import { LocalStorageService } from "src/app/core/_services/storage/local-storage.service" +import { + UIConfig, + uiConfigDefault +} from 'src/app/core/_models/config-ui.model'; + +import { LocalStorageService } from 'src/app/core/_services/storage/local-storage.service'; /** * Utility class for managing user interface settings and configurations. */ export class UISettingsUtilityClass { - /** The key used for storing UI configuration in local storage. */ - static readonly KEY = 'ui-config' + static readonly KEY = 'ui-config'; /** The UI configuration object. */ - uiConfig: UIConfig + uiConfig: UIConfig; /** * Creates an instance of the UISettingsUtilityClass. @@ -18,9 +21,9 @@ export class UISettingsUtilityClass { * @param storage - The LocalStorageService used for managing UI configuration storage. */ constructor(private storage: LocalStorageService) { - this.uiConfig = storage.getItem(UISettingsUtilityClass.KEY) + this.uiConfig = storage.getItem(UISettingsUtilityClass.KEY); if (!this.uiConfig) { - this.uiConfig = uiConfigDefault + this.uiConfig = uiConfigDefault; } } @@ -30,9 +33,9 @@ export class UISettingsUtilityClass { * @param key - The key for the table settings. * @param columns - An array of column names to set as table settings for the key. */ - updateTableSettings(key: string, columns: string[]): void { - this.uiConfig.tableSettings[key] = columns - this.storage.setItem(UISettingsUtilityClass.KEY, this.uiConfig, 0) + updateTableSettings(key: string, columns: number[]): void { + this.uiConfig.tableSettings[key] = columns; + this.storage.setItem(UISettingsUtilityClass.KEY, this.uiConfig, 0); } /** @@ -41,12 +44,12 @@ export class UISettingsUtilityClass { * @param key - The key for the table settings. * @returns An array of column names as table settings for the key. */ - getTableSettings(key: string): string[] { + getTableSettings(key: string): number[] { try { - return this.uiConfig.tableSettings[key] + return this.uiConfig.tableSettings[key]; } catch (error) { - console.log(error) - return [] + console.log(error); + return []; } } @@ -58,10 +61,10 @@ export class UISettingsUtilityClass { */ getSetting(key: string): T | undefined { try { - return this.uiConfig[key] + return this.uiConfig[key]; } catch (error) { - console.log(error) - return undefined + console.log(error); + return undefined; } } @@ -73,20 +76,19 @@ export class UISettingsUtilityClass { */ updateSettings(settings: { [key: string]: any }): number { const keys = Object.keys(settings); - let changedValues = 0 + let changedValues = 0; for (const key of keys) { if (key in this.uiConfig && this.uiConfig[key] !== settings[key]) { - this.uiConfig[key] = settings[key] - changedValues += 1 + this.uiConfig[key] = settings[key]; + changedValues += 1; } } if (changedValues > 0) { - this.storage.setItem(UISettingsUtilityClass.KEY, this.uiConfig, 0) + this.storage.setItem(UISettingsUtilityClass.KEY, this.uiConfig, 0); } - return changedValues + return changedValues; } - -} \ No newline at end of file +} From 16d06e3d5ac011cd825f9b5af454cc7d0eff38e2 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Fri, 1 Dec 2023 07:37:14 +0100 Subject: [PATCH 330/419] Refactor tables to use and store numerical value of column --- .../access-groups-table.component.html | 1 + .../access-groups-table.component.ts | 22 +- .../access-groups-table.constants.ts | 9 +- .../agent-binaries-table.component.html | 1 + .../agent-binaries-table.component.ts | 30 +- .../agent-binaries-table.constants.ts | 21 +- .../agents-table/agents-table.component.html | 1 + .../agents-table/agents-table.component.ts | 44 +- .../agents-table/agents-table.constants.ts | 48 +- .../tables/base-table/base-table.component.ts | 5 + .../chunks-table/chunks-table.component.html | 1 + .../chunks-table/chunks-table.component.ts | 30 +- .../chunks-table/chunks-table.constants.ts | 39 +- .../column-selection-dialog.component.html | 2 +- .../column-selection-dialog.component.ts | 24 +- .../crackers-table.component.html | 1 + .../crackers-table.component.ts | 24 +- .../crackers-table.constants.ts | 12 +- .../cracks-table/cracks-table.component.html | 1 + .../cracks-table/cracks-table.component.ts | 32 +- .../cracks-table/cracks-table.constants.ts | 33 +- .../files-table/files-table.component.html | 1 + .../files-table/files-table.component.ts | 26 +- .../files-table/files-table.constants.ts | 18 +- .../hashlists-table.component.html | 1 + .../hashlists-table.component.ts | 32 +- .../hashlists-table.constants.ts | 24 +- .../hashtypes-table.component.html | 1 + .../hashtypes-table.component.ts | 26 +- .../hashtypes-table.constants.ts | 15 +- .../health-checks-table.component.html | 1 + .../health-checks-table.component.ts | 22 +- .../health-checks-table.constants.ts | 15 +- .../tables/ht-table/ht-table.component.html | 12 +- .../tables/ht-table/ht-table.component.ts | 62 +- .../tables/ht-table/ht-table.models.ts | 2 +- .../logs-table/logs-table.component.html | 1 + .../tables/logs-table/logs-table.component.ts | 21 +- .../tables/logs-table/logs-table.constants.ts | 18 +- .../notifications-table.component.html | 1 + .../notifications-table.component.ts | 30 +- .../notifications-table.constants.ts | 21 +- .../permissions-table.component.html | 1 + .../permissions-table.component.ts | 24 +- .../permissions-table.constants.ts | 12 +- .../preprocessors-table.component.html | 1 + .../preprocessors-table.component.ts | 22 +- .../preprocessors-table.constants.ts | 9 +- .../super-hashlists-table.component.html | 1 + .../super-hashlists-table.component.ts | 32 +- .../super-hashlists-table.constants.ts | 24 +- .../tasks-table/tasks-table.component.html | 6 +- .../tasks-table/tasks-table.component.ts | 221 +++-- .../tasks-table/tasks-table.constants.ts | 52 +- .../users-table/users-table.component.html | 1 + .../users-table/users-table.component.ts | 30 +- .../users-table/users-table.constants.ts | 27 +- src/app/core/_datasources/base.datasource.ts | 2 +- src/app/core/_datasources/tasks.datasource.ts | 3 +- src/app/core/_models/config-ui.model.ts | 223 +++-- .../core/_services/export/export.service.ts | 66 +- src/app/core/_services/export/export.util.ts | 14 +- .../edit-hashlist.component.html | 63 +- .../edit-tasks/edit-tasks.component.html | 122 +-- .../tasks/edit-tasks/edit-tasks.component.ts | 821 ++++++++++-------- 65 files changed, 1487 insertions(+), 1021 deletions(-) diff --git a/src/app/core/_components/tables/access-groups-table/access-groups-table.component.html b/src/app/core/_components/tables/access-groups-table/access-groups-table.component.html index 43cd40ed..47d78c45 100644 --- a/src/app/core/_components/tables/access-groups-table/access-groups-table.component.html +++ b/src/app/core/_components/tables/access-groups-table/access-groups-table.component.html @@ -2,6 +2,7 @@ #table name="accessGroupsTable" dataType="access-groups" + [columnLabels]="columnLabels" [dataSource]="dataSource" [tableColumns]="tableColumns" [isSelectable]="true" diff --git a/src/app/core/_components/tables/access-groups-table/access-groups-table.component.ts b/src/app/core/_components/tables/access-groups-table/access-groups-table.component.ts index b0df713a..fead7816 100644 --- a/src/app/core/_components/tables/access-groups-table/access-groups-table.component.ts +++ b/src/app/core/_components/tables/access-groups-table/access-groups-table.component.ts @@ -1,3 +1,7 @@ +import { + AccessGroupsTableCol, + AccessGroupsTableColumnLabel +} from './access-groups-table.constants'; /* eslint-disable @angular-eslint/component-selector */ import { Component, OnDestroy, OnInit } from '@angular/core'; import { HTTableColumn, HTTableRouterLink } from '../ht-table/ht-table.models'; @@ -5,7 +9,6 @@ import { catchError, forkJoin } from 'rxjs'; import { AccessGroup } from 'src/app/core/_models/access-group.model'; import { AccessGroupsDataSource } from 'src/app/core/_datasources/access-groups.datasource'; -import { AccessGroupsTableColumnLabel } from './access-groups-table.constants'; import { ActionMenuEvent } from '../../menus/action-menu/action-menu.model'; import { BaseTableComponent } from '../base-table/base-table.component'; import { BulkActionMenuAction } from '../../menus/bulk-action-menu/bulk-action-menu.constants'; @@ -27,6 +30,7 @@ export class AccessGroupsTableComponent dataSource: AccessGroupsDataSource; ngOnInit(): void { + this.setColumnLabels(AccessGroupsTableColumnLabel); this.tableColumns = this.getColumns(); this.dataSource = new AccessGroupsDataSource( this.cdr, @@ -54,13 +58,13 @@ export class AccessGroupsTableComponent getColumns(): HTTableColumn[] { const tableColumns = [ { - name: AccessGroupsTableColumnLabel.ID, + id: AccessGroupsTableCol.ID, dataKey: '_id', isSortable: true, export: async (accessGroup: AccessGroup) => accessGroup._id + '' }, { - name: AccessGroupsTableColumnLabel.NAME, + id: AccessGroupsTableCol.NAME, dataKey: 'groupName', routerLink: (accessGroup: AccessGroup) => this.renderAccessGroupLink(accessGroup), @@ -102,19 +106,25 @@ export class AccessGroupsTableComponent this.exportService.toExcel( 'hashtopolis-access-groups', this.tableColumns, - event.data + event.data, + AccessGroupsTableColumnLabel ); break; case ExportMenuAction.CSV: this.exportService.toCsv( 'hashtopolis-access-groups', this.tableColumns, - event.data + event.data, + AccessGroupsTableColumnLabel ); break; case ExportMenuAction.COPY: this.exportService - .toClipboard(this.tableColumns, event.data) + .toClipboard( + this.tableColumns, + event.data, + AccessGroupsTableColumnLabel + ) .then(() => { this.snackBar.open( 'The selected rows are copied to the clipboard', diff --git a/src/app/core/_components/tables/access-groups-table/access-groups-table.constants.ts b/src/app/core/_components/tables/access-groups-table/access-groups-table.constants.ts index ed3e7f54..95d8483a 100644 --- a/src/app/core/_components/tables/access-groups-table/access-groups-table.constants.ts +++ b/src/app/core/_components/tables/access-groups-table/access-groups-table.constants.ts @@ -1,4 +1,9 @@ +export enum AccessGroupsTableCol { + ID, + NAME +} + export const AccessGroupsTableColumnLabel = { - ID: 'ID', - NAME: 'Name' + [AccessGroupsTableCol.ID]: 'ID', + [AccessGroupsTableCol.NAME]: 'Name' }; diff --git a/src/app/core/_components/tables/agent-binaries-table/agent-binaries-table.component.html b/src/app/core/_components/tables/agent-binaries-table/agent-binaries-table.component.html index e6452f4c..d28c0902 100644 --- a/src/app/core/_components/tables/agent-binaries-table/agent-binaries-table.component.html +++ b/src/app/core/_components/tables/agent-binaries-table/agent-binaries-table.component.html @@ -2,6 +2,7 @@ #table name="agentBinariesTable" dataType="agent-binaries" + [columnLabels]="columnLabels" [dataSource]="dataSource" [tableColumns]="tableColumns" [isSelectable]="true" diff --git a/src/app/core/_components/tables/agent-binaries-table/agent-binaries-table.component.ts b/src/app/core/_components/tables/agent-binaries-table/agent-binaries-table.component.ts index 197de3d3..e424a430 100644 --- a/src/app/core/_components/tables/agent-binaries-table/agent-binaries-table.component.ts +++ b/src/app/core/_components/tables/agent-binaries-table/agent-binaries-table.component.ts @@ -1,10 +1,13 @@ +import { + AgentBinariesTableCol, + AgentBinariesTableColumnLabel +} from './agent-binaries-table.constants'; /* eslint-disable @angular-eslint/component-selector */ import { Component, OnDestroy, OnInit } from '@angular/core'; import { catchError, forkJoin } from 'rxjs'; import { ActionMenuEvent } from '../../menus/action-menu/action-menu.model'; import { AgentBinariesDataSource } from 'src/app/core/_datasources/agent-binaries.datasource'; -import { AgentBinariesTableColumnLabel } from './agent-binaries-table.constants'; import { AgentBinary } from 'src/app/core/_models/agent-binary.model'; import { BaseTableComponent } from '../base-table/base-table.component'; import { BulkActionMenuAction } from '../../menus/bulk-action-menu/bulk-action-menu.constants'; @@ -27,6 +30,7 @@ export class AgentBinariesTableComponent dataSource: AgentBinariesDataSource; ngOnInit(): void { + this.setColumnLabels(AgentBinariesTableColumnLabel); this.tableColumns = this.getColumns(); this.dataSource = new AgentBinariesDataSource( this.cdr, @@ -54,37 +58,37 @@ export class AgentBinariesTableComponent getColumns(): HTTableColumn[] { const tableColumns = [ { - name: AgentBinariesTableColumnLabel.ID, + id: AgentBinariesTableCol.ID, dataKey: '_id', isSortable: true, export: async (agentBinary: AgentBinary) => agentBinary._id + '' }, { - name: AgentBinariesTableColumnLabel.TYPE, + id: AgentBinariesTableCol.TYPE, dataKey: 'type', isSortable: true, export: async (agentBinary: AgentBinary) => agentBinary.type }, { - name: AgentBinariesTableColumnLabel.OS, + id: AgentBinariesTableCol.OS, dataKey: 'operatingSystems', isSortable: true, export: async (agentBinary: AgentBinary) => agentBinary.operatingSystems }, { - name: AgentBinariesTableColumnLabel.FILENAME, + id: AgentBinariesTableCol.FILENAME, dataKey: 'filename', isSortable: true, export: async (agentBinary: AgentBinary) => agentBinary.filename }, { - name: AgentBinariesTableColumnLabel.VERSION, + id: AgentBinariesTableCol.VERSION, dataKey: 'version', isSortable: true, export: async (agentBinary: AgentBinary) => agentBinary.version }, { - name: AgentBinariesTableColumnLabel.UPDATE_TRACK, + id: AgentBinariesTableCol.UPDATE_TRACK, dataKey: 'updateTrack', isSortable: true, export: async (agentBinary: AgentBinary) => agentBinary.updateTrack @@ -124,19 +128,25 @@ export class AgentBinariesTableComponent this.exportService.toExcel( 'hashtopolis-agent-binaries', this.tableColumns, - event.data + event.data, + AgentBinariesTableColumnLabel ); break; case ExportMenuAction.CSV: this.exportService.toCsv( 'hashtopolis-agent-binaries', this.tableColumns, - event.data + event.data, + AgentBinariesTableColumnLabel ); break; case ExportMenuAction.COPY: this.exportService - .toClipboard(this.tableColumns, event.data) + .toClipboard( + this.tableColumns, + event.data, + AgentBinariesTableColumnLabel + ) .then(() => { this.snackBar.open( 'The selected rows are copied to the clipboard', diff --git a/src/app/core/_components/tables/agent-binaries-table/agent-binaries-table.constants.ts b/src/app/core/_components/tables/agent-binaries-table/agent-binaries-table.constants.ts index 02208a02..f8df7016 100644 --- a/src/app/core/_components/tables/agent-binaries-table/agent-binaries-table.constants.ts +++ b/src/app/core/_components/tables/agent-binaries-table/agent-binaries-table.constants.ts @@ -1,8 +1,17 @@ +export enum AgentBinariesTableCol { + ID, + TYPE, + OS, + FILENAME, + VERSION, + UPDATE_TRACK +} + export const AgentBinariesTableColumnLabel = { - ID: 'ID', - TYPE: 'Type', - OS: 'OS', - FILENAME: 'Filename', - VERSION: 'Current Version', - UPDATE_TRACK: 'Update Track' + [AgentBinariesTableCol.ID]: 'ID', + [AgentBinariesTableCol.TYPE]: 'Type', + [AgentBinariesTableCol.OS]: 'OS', + [AgentBinariesTableCol.FILENAME]: 'Filename', + [AgentBinariesTableCol.VERSION]: 'Current Version', + [AgentBinariesTableCol.UPDATE_TRACK]: 'Update Track' }; diff --git a/src/app/core/_components/tables/agents-table/agents-table.component.html b/src/app/core/_components/tables/agents-table/agents-table.component.html index bf81b7c4..1bf827fc 100644 --- a/src/app/core/_components/tables/agents-table/agents-table.component.html +++ b/src/app/core/_components/tables/agents-table/agents-table.component.html @@ -1,6 +1,7 @@ agent._id, export: async (agent: Agent) => agent._id + '' }, { - name: AgentsTableColumnLabel.NAME, + id: AgentsTableCol.NAME, dataKey: 'agentName', routerLink: (agent: Agent) => this.renderAgentLink(agent), isSortable: true, export: async (agent: Agent) => agent.agentName }, { - name: AgentsTableColumnLabel.STATUS, + id: AgentsTableCol.STATUS, dataKey: 'status', icons: (agent: Agent) => this.renderStatusIcon(agent), render: (agent: Agent) => this.renderStatus(agent), @@ -100,7 +102,7 @@ export class AgentsTableComponent export: async (agent: Agent) => (agent.isActive ? 'Active' : 'Inactive') }, { - name: AgentsTableColumnLabel.USER, + id: AgentsTableCol.USER, dataKey: 'userId', render: (agent: Agent) => this.renderOwner(agent), routerLink: (agent: Agent) => this.renderUserLink(agent), @@ -108,7 +110,7 @@ export class AgentsTableComponent export: async (agent: Agent) => (agent.user ? agent.user.name : '') }, { - name: AgentsTableColumnLabel.CLIENT, + id: AgentsTableCol.CLIENT, dataKey: 'clientSignature', render: (agent: Agent) => this.renderClient(agent), isSortable: true, @@ -116,7 +118,7 @@ export class AgentsTableComponent agent.clientSignature ? agent.clientSignature : '' }, { - name: AgentsTableColumnLabel.TASK_SPEED, + id: AgentsTableCol.TASK_SPEED, dataKey: 'taskId', icons: (agent: Agent) => this.renderProgressIcon(agent), async: (agent: Agent) => this.renderCurrentSpeed(agent), @@ -124,7 +126,7 @@ export class AgentsTableComponent export: async (agent: Agent) => (await this.getSpeed(agent)) + '' }, { - name: AgentsTableColumnLabel.CURRENT_CHUNK, + id: AgentsTableCol.CURRENT_CHUNK, dataKey: 'chunkId', routerLink: (agent: Agent) => this.renderChunkLink(agent), isSortable: true, @@ -132,13 +134,13 @@ export class AgentsTableComponent agent.chunk ? agent.chunk._id + '' : '' }, { - name: AgentsTableColumnLabel.GPUS_CPUS, + id: AgentsTableCol.GPUS_CPUS, dataKey: 'devices', isSortable: true, export: async (agent: Agent) => agent.devices }, { - name: AgentsTableColumnLabel.LAST_ACTIVITY, + id: AgentsTableCol.LAST_ACTIVITY, dataKey: 'lastTime', render: (agent: Agent) => this.renderLastActivity(agent), isSortable: true, @@ -146,7 +148,7 @@ export class AgentsTableComponent formatUnixTimestamp(agent.lastTime, this.dateFormat) }, { - name: AgentsTableColumnLabel.CRACKED, + id: AgentsTableCol.CRACKED, dataKey: 'cracked', routerLink: (agent: Agent) => this.renderCracked(agent), isSortable: true, @@ -157,14 +159,14 @@ export class AgentsTableComponent if (this.taskId === 0) { // If this is not assigned agents, add task and access group tableColumns.push({ - name: AgentsTableColumnLabel.CURRENT_TASK, + id: AgentsTableCol.CURRENT_TASK, dataKey: 'taskName', routerLink: (agent: Agent) => this.renderTaskLink(agent), isSortable: true, export: async (agent: Agent) => (agent.task ? agent.task.taskName : '') }); tableColumns.push({ - name: AgentsTableColumnLabel.ACCESS_GROUP, + id: AgentsTableCol.ACCESS_GROUP, dataKey: 'accessGroupId', routerLink: (agent: Agent) => this.renderAccessGroupLinks(agent), isSortable: true, @@ -174,7 +176,7 @@ export class AgentsTableComponent } else { // If this is assigned agents, add benchmark, time spent and keyspace searched tableColumns.push({ - name: AgentsTableColumnLabel.BENCHMARK, + id: AgentsTableCol.BENCHMARK, dataKey: 'benchmark', editable: (agent: Agent) => { return { @@ -187,7 +189,7 @@ export class AgentsTableComponent export: async (agent: Agent) => agent.benchmark }); tableColumns.push({ - name: AgentsTableColumnLabel.TIME_SPENT, + id: AgentsTableCol.TIME_SPENT, dataKey: 'timeSpent', async: (agent: Agent) => this.renderTimeSpent(agent), icons: undefined, @@ -195,7 +197,7 @@ export class AgentsTableComponent export: async (agent: Agent) => (await this.getTimeSpent(agent)) + '' }); tableColumns.push({ - name: AgentsTableColumnLabel.SEARCHED, + id: AgentsTableCol.SEARCHED, dataKey: 'searched', async: (agent: Agent) => this.renderSearched(agent), icons: undefined, @@ -395,19 +397,25 @@ export class AgentsTableComponent this.exportService.toExcel( 'hashtopolis-agents', this.tableColumns, - event.data + event.data, + AgentsTableColumnLabel ); break; case ExportMenuAction.CSV: this.exportService.toCsv( 'hashtopolis-agents', this.tableColumns, - event.data + event.data, + AgentsTableColumnLabel ); break; case ExportMenuAction.COPY: this.exportService - .toClipboard(this.tableColumns, event.data) + .toClipboard( + this.tableColumns, + event.data, + AgentsTableColumnLabel + ) .then(() => { this.snackBar.open( 'The selected rows are copied to the clipboard', diff --git a/src/app/core/_components/tables/agents-table/agents-table.constants.ts b/src/app/core/_components/tables/agents-table/agents-table.constants.ts index 9d8c6af1..ecf193a6 100644 --- a/src/app/core/_components/tables/agents-table/agents-table.constants.ts +++ b/src/app/core/_components/tables/agents-table/agents-table.constants.ts @@ -1,19 +1,37 @@ +export enum AgentsTableCol { + ID, + STATUS, + NAME, + USER, + CLIENT, + GPUS_CPUS, + LAST_ACTIVITY, + ACCESS_GROUP, + CURRENT_TASK, + CURRENT_CHUNK, + TASK_SPEED, + BENCHMARK, + TIME_SPENT, + SEARCHED, + CRACKED +} + export const AgentsTableColumnLabel = { - ID: 'ID', - STATUS: 'Status', - NAME: 'Name', - USER: 'Owner', - CLIENT: 'Client', - GPUS_CPUS: 'GPUs/CPUs', - LAST_ACTIVITY: 'Last Activity', - ACCESS_GROUP: 'Access Group', - CURRENT_TASK: 'Task', - CURRENT_CHUNK: 'Chunk', - TASK_SPEED: 'Speed', - BENCHMARK: 'Benchmark', - TIME_SPENT: 'Time Spent', - SEARCHED: 'Keyspace Searched', - CRACKED: 'Cracked' + [AgentsTableCol.ID]: 'ID', + [AgentsTableCol.STATUS]: 'Status', + [AgentsTableCol.NAME]: 'Name', + [AgentsTableCol.USER]: 'Owner', + [AgentsTableCol.CLIENT]: 'Client', + [AgentsTableCol.GPUS_CPUS]: 'GPUs/CPUs', + [AgentsTableCol.LAST_ACTIVITY]: 'Last Activity', + [AgentsTableCol.ACCESS_GROUP]: 'Access Group', + [AgentsTableCol.CURRENT_TASK]: 'Task', + [AgentsTableCol.CURRENT_CHUNK]: 'Chunk', + [AgentsTableCol.TASK_SPEED]: 'Speed', + [AgentsTableCol.BENCHMARK]: 'Benchmark', + [AgentsTableCol.TIME_SPENT]: 'Time Spent', + [AgentsTableCol.SEARCHED]: 'Keyspace Searched', + [AgentsTableCol.CRACKED]: 'Cracked' }; export const AgentTableEditableAction = { 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 d49fb356..8cddefc0 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 @@ -34,6 +34,7 @@ export class BaseTableComponent { protected uiSettings: UISettingsUtilityClass; protected dateFormat: string; protected subscriptions: Subscription[] = []; + protected columnLabels: { [key: string]: string } = {}; @ViewChild('table') table: HTTableComponent; @@ -85,6 +86,10 @@ export class BaseTableComponent { return this.sanitizer.bypassSecurityTrustHtml(html); } + protected setColumnLabels(labels: { [key: string]: string }): void { + this.columnLabels = labels; + } + reload(): void { if (this.table) { this.table.reload(); diff --git a/src/app/core/_components/tables/chunks-table/chunks-table.component.html b/src/app/core/_components/tables/chunks-table/chunks-table.component.html index 8548fd46..7ddd8838 100644 --- a/src/app/core/_components/tables/chunks-table/chunks-table.component.html +++ b/src/app/core/_components/tables/chunks-table/chunks-table.component.html @@ -1,6 +1,7 @@ this.renderCheckpoint(chunk), isSortable: true }, { - name: ChunksTableColumnLabel.PROGRESS, + id: ChunksTableCol.PROGRESS, dataKey: 'progress', render: (chunk: Chunk) => this.renderProgress(chunk), isSortable: true }, { - name: ChunksTableColumnLabel.TASK, + id: ChunksTableCol.TASK, dataKey: 'taskName', routerLink: (chunk: Chunk) => this.renderTaskLink(chunk), isSortable: true }, { - name: ChunksTableColumnLabel.AGENT, + id: ChunksTableCol.AGENT, dataKey: 'agentName', render: (chunk: Chunk) => this.renderAgent(chunk), routerLink: (chunk: Chunk) => this.renderAgentLink(chunk), isSortable: true }, { - name: ChunksTableColumnLabel.DISPATCH_TIME, + id: ChunksTableCol.DISPATCH_TIME, dataKey: 'dispatchTime', render: (chunk: Chunk) => this.renderDispatchTime(chunk), isSortable: true }, { - name: ChunksTableColumnLabel.LAST_ACTIVITY, + id: ChunksTableCol.LAST_ACTIVITY, dataKey: 'solveTime', render: (chunk: Chunk) => this.renderLastActivity(chunk), isSortable: true }, { - name: ChunksTableColumnLabel.TIME_SPENT, + id: ChunksTableCol.TIME_SPENT, dataKey: 'timeSpent', render: (chunk: Chunk) => this.renderTimeSpent(chunk), isSortable: true }, { - name: ChunksTableColumnLabel.STATE, + id: ChunksTableCol.STATE, dataKey: 'state', render: (chunk: Chunk) => this.renderState(chunk), isSortable: true }, { - name: ChunksTableColumnLabel.CRACKED, + id: ChunksTableCol.CRACKED, dataKey: 'cracked', routerLink: (chunk: Chunk) => this.renderCrackedLink(chunk), isSortable: true diff --git a/src/app/core/_components/tables/chunks-table/chunks-table.constants.ts b/src/app/core/_components/tables/chunks-table/chunks-table.constants.ts index 35b83307..eabbe733 100644 --- a/src/app/core/_components/tables/chunks-table/chunks-table.constants.ts +++ b/src/app/core/_components/tables/chunks-table/chunks-table.constants.ts @@ -1,14 +1,29 @@ +export enum ChunksTableCol { + ID, + START, + LENGTH, + CHECKPOINT, + PROGRESS, + TASK, + AGENT, + DISPATCH_TIME, + LAST_ACTIVITY, + TIME_SPENT, + STATE, + CRACKED +} + export const ChunksTableColumnLabel = { - ID: 'ID', - START: 'Start', - LENGTH: 'Length', - CHECKPOINT: 'Checkpoint', - PROGRESS: 'Progress', - TASK: 'Task', - AGENT: 'Agent', - DISPATCH_TIME: 'Dispatch Time', - LAST_ACTIVITY: 'Last Activity', - TIME_SPENT: 'Time Spent', - STATE: 'State', - CRACKED: 'Cracked' + [ChunksTableCol.ID]: 'ID', + [ChunksTableCol.START]: 'Start', + [ChunksTableCol.LENGTH]: 'Length', + [ChunksTableCol.CHECKPOINT]: 'Checkpoint', + [ChunksTableCol.PROGRESS]: 'Progress', + [ChunksTableCol.TASK]: 'Task', + [ChunksTableCol.AGENT]: 'Agent', + [ChunksTableCol.DISPATCH_TIME]: 'Dispatch Time', + [ChunksTableCol.LAST_ACTIVITY]: 'Last Activity', + [ChunksTableCol.TIME_SPENT]: 'Time Spent', + [ChunksTableCol.STATE]: 'State', + [ChunksTableCol.CRACKED]: 'Cracked' }; diff --git a/src/app/core/_components/tables/column-selection-dialog/column-selection-dialog.component.html b/src/app/core/_components/tables/column-selection-dialog/column-selection-dialog.component.html index 1dc02de0..c4a62ea9 100644 --- a/src/app/core/_components/tables/column-selection-dialog/column-selection-dialog.component.html +++ b/src/app/core/_components/tables/column-selection-dialog/column-selection-dialog.component.html @@ -2,7 +2,7 @@

Select Columns

- {{ column }} + {{ availableColumns[key] }} diff --git a/src/app/core/_components/tables/column-selection-dialog/column-selection-dialog.component.ts b/src/app/core/_components/tables/column-selection-dialog/column-selection-dialog.component.ts index 5d947417..fee6f389 100644 --- a/src/app/core/_components/tables/column-selection-dialog/column-selection-dialog.component.ts +++ b/src/app/core/_components/tables/column-selection-dialog/column-selection-dialog.component.ts @@ -3,22 +3,34 @@ import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; @Component({ selector: 'app-column-selection-dialog', - templateUrl: './column-selection-dialog.component.html', + templateUrl: './column-selection-dialog.component.html' }) export class ColumnSelectionDialogComponent { - availableColumns: string[]; + availableColumns: { [key: string]: string }; selectedColumns: string[]; constructor( public dialogRef: MatDialogRef, - @Inject(MAT_DIALOG_DATA) public data: { availableColumns: string[], selectedColumns: string[] } + @Inject(MAT_DIALOG_DATA) + public data: { + availableColumns: { [key: number]: string }; + selectedColumns: string[]; + } ) { // Initialize selectedColumns with the default columns - this.selectedColumns = [...data.selectedColumns]; - this.availableColumns = [...data.availableColumns]; + this.selectedColumns = [ + ...data.selectedColumns.filter( + (row: string) => row !== '100' && row !== '200' + ) + ]; + this.availableColumns = { ...data.availableColumns }; } closeDialog(): void { this.dialogRef.close(); } -} \ No newline at end of file + + getKeys(): string[] { + return Object.keys(this.availableColumns); + } +} diff --git a/src/app/core/_components/tables/crackers-table/crackers-table.component.html b/src/app/core/_components/tables/crackers-table/crackers-table.component.html index dd359e1c..cf1a996b 100644 --- a/src/app/core/_components/tables/crackers-table/crackers-table.component.html +++ b/src/app/core/_components/tables/crackers-table/crackers-table.component.html @@ -1,6 +1,7 @@ cracker._id + '' }, { - name: CrackersTableColumnLabel.NAME, + id: CrackersTableCol.NAME, dataKey: 'typeName', isSortable: true, export: async (cracker: CrackerBinaryType) => cracker.typeName }, { - name: CrackersTableColumnLabel.VERSIONS, + id: CrackersTableCol.VERSIONS, dataKey: 'crackerVersions', routerLink: (cracker: CrackerBinaryType) => this.renderVersions(cracker), @@ -111,19 +115,25 @@ export class CrackersTableComponent this.exportService.toExcel( 'hashtopolis-crackers', this.tableColumns, - event.data + event.data, + CrackersTableColumnLabel ); break; case ExportMenuAction.CSV: this.exportService.toCsv( 'hashtopolis-crackers', this.tableColumns, - event.data + event.data, + CrackersTableColumnLabel ); break; case ExportMenuAction.COPY: this.exportService - .toClipboard(this.tableColumns, event.data) + .toClipboard( + this.tableColumns, + event.data, + CrackersTableColumnLabel + ) .then(() => { this.snackBar.open( 'The selected rows are copied to the clipboard', diff --git a/src/app/core/_components/tables/crackers-table/crackers-table.constants.ts b/src/app/core/_components/tables/crackers-table/crackers-table.constants.ts index 0e07762f..0bc0f30f 100644 --- a/src/app/core/_components/tables/crackers-table/crackers-table.constants.ts +++ b/src/app/core/_components/tables/crackers-table/crackers-table.constants.ts @@ -1,5 +1,11 @@ +export enum CrackersTableCol { + ID, + NAME, + VERSIONS +} + export const CrackersTableColumnLabel = { - ID: 'ID', - NAME: 'Name', - VERSIONS: 'Available Versions' + [CrackersTableCol.ID]: 'ID', + [CrackersTableCol.NAME]: 'Name', + [CrackersTableCol.VERSIONS]: 'Available Versions' }; diff --git a/src/app/core/_components/tables/cracks-table/cracks-table.component.html b/src/app/core/_components/tables/cracks-table/cracks-table.component.html index cec6184c..7dbc3fd2 100644 --- a/src/app/core/_components/tables/cracks-table/cracks-table.component.html +++ b/src/app/core/_components/tables/cracks-table/cracks-table.component.html @@ -2,6 +2,7 @@ #table name="cracksTable" dataType="cracks" + [columnLabels]="columnLabels" [dataSource]="dataSource" [tableColumns]="tableColumns" [isSelectable]="true" diff --git a/src/app/core/_components/tables/cracks-table/cracks-table.component.ts b/src/app/core/_components/tables/cracks-table/cracks-table.component.ts index 4fe02db3..289c3752 100644 --- a/src/app/core/_components/tables/cracks-table/cracks-table.component.ts +++ b/src/app/core/_components/tables/cracks-table/cracks-table.component.ts @@ -1,12 +1,15 @@ /* eslint-disable @angular-eslint/component-selector */ import { Component, OnDestroy, OnInit } from '@angular/core'; +import { + CracksTableCol, + CracksTableColumnLabel +} from './cracks-table.constants'; import { catchError, forkJoin } from 'rxjs'; import { ActionMenuEvent } from '../../menus/action-menu/action-menu.model'; import { BaseTableComponent } from '../base-table/base-table.component'; import { BulkActionMenuAction } from '../../menus/bulk-action-menu/bulk-action-menu.constants'; import { CracksDataSource } from 'src/app/core/_datasources/cracks.datasource'; -import { CracksTableColumnLabel } from './cracks-table.constants'; import { DialogData } from '../table-dialog/table-dialog.model'; import { ExportMenuAction } from '../../menus/export-menu/export-menu.constants'; import { HTTableColumn } from '../ht-table/ht-table.models'; @@ -29,6 +32,7 @@ export class CracksTableComponent dataSource: CracksDataSource; ngOnInit(): void { + this.setColumnLabels(CracksTableColumnLabel); this.tableColumns = this.getColumns(); this.dataSource = new CracksDataSource(this.cdr, this.gs, this.uiService); this.dataSource.setColumns(this.tableColumns); @@ -52,7 +56,7 @@ export class CracksTableComponent getColumns(): HTTableColumn[] { const tableColumns = [ { - name: CracksTableColumnLabel.FOUND, + id: CracksTableCol.FOUND, dataKey: 'timeCracked', render: (crack: Hash) => formatUnixTimestamp(crack.timeCracked, this.dateFormat), @@ -61,41 +65,41 @@ export class CracksTableComponent formatUnixTimestamp(crack.timeCracked, this.dateFormat) }, { - name: CracksTableColumnLabel.PLAINTEXT, + id: CracksTableCol.PLAINTEXT, dataKey: 'plaintext', isSortable: true, export: async (crack: Hash) => crack.plaintext }, { - name: CracksTableColumnLabel.HASH, + id: CracksTableCol.HASH, dataKey: 'hash', isSortable: true, truncate: true, export: async (crack: Hash) => crack.hash }, { - name: CracksTableColumnLabel.AGENT, + id: CracksTableCol.AGENT, dataKey: 'agentId', isSortable: true, routerLink: (crack: Hash) => this.renderAgentLink(crack), export: async (crack: Hash) => crack.agentId + '' }, { - name: CracksTableColumnLabel.TASK, + id: CracksTableCol.TASK, dataKey: 'taskId', isSortable: true, routerLink: (crack: Hash) => this.renderTaskLink(crack), export: async (crack: Hash) => crack.taskId + '' }, { - name: CracksTableColumnLabel.CHUNK, + id: CracksTableCol.CHUNK, dataKey: 'chunkId', isSortable: true, routerLink: (crack: Hash) => this.renderChunkLink(crack), export: async (crack: Hash) => crack.chunkId + '' }, { - name: CracksTableColumnLabel.TYPE, + id: CracksTableCol.TYPE, dataKey: 'hashlistId', isSortable: true, render: (crack: Hash) => @@ -138,19 +142,25 @@ export class CracksTableComponent this.exportService.toExcel( 'hashtopolis-cracks', this.tableColumns, - event.data + event.data, + CracksTableColumnLabel ); break; case ExportMenuAction.CSV: this.exportService.toCsv( 'hashtopolis-cracks', this.tableColumns, - event.data + event.data, + CracksTableColumnLabel ); break; case ExportMenuAction.COPY: this.exportService - .toClipboard(this.tableColumns, event.data) + .toClipboard( + this.tableColumns, + event.data, + CracksTableColumnLabel + ) .then(() => { this.snackBar.open( 'The selected rows are copied to the clipboard', diff --git a/src/app/core/_components/tables/cracks-table/cracks-table.constants.ts b/src/app/core/_components/tables/cracks-table/cracks-table.constants.ts index 54c1e38f..37a6c496 100644 --- a/src/app/core/_components/tables/cracks-table/cracks-table.constants.ts +++ b/src/app/core/_components/tables/cracks-table/cracks-table.constants.ts @@ -1,12 +1,25 @@ +export enum CracksTableCol { + ID, + FOUND, + PLAINTEXT, + HASH, + HASHLIST, + AGENT, + TASK, + CHUNK, + TYPE, + SALT +} + export const CracksTableColumnLabel = { - ID: 'ID', - FOUND: 'Time Found', - PLAINTEXT: 'Plaintext', - HASH: 'Hash', - HASHLIST: 'Hashlist', - AGENT: 'Agent', - TASK: 'Task', - CHUNK: 'Chunk', - TYPE: 'Type', - SALT: 'Salt' + [CracksTableCol.ID]: 'ID', + [CracksTableCol.FOUND]: 'Time Found', + [CracksTableCol.PLAINTEXT]: 'Plaintext', + [CracksTableCol.HASH]: 'Hash', + [CracksTableCol.HASHLIST]: 'Hashlist', + [CracksTableCol.AGENT]: 'Agent', + [CracksTableCol.TASK]: 'Task', + [CracksTableCol.CHUNK]: 'Chunk', + [CracksTableCol.TYPE]: 'Type', + [CracksTableCol.SALT]: 'Salt' }; diff --git a/src/app/core/_components/tables/files-table/files-table.component.html b/src/app/core/_components/tables/files-table/files-table.component.html index 6d5817b2..e15a723e 100644 --- a/src/app/core/_components/tables/files-table/files-table.component.html +++ b/src/app/core/_components/tables/files-table/files-table.component.html @@ -2,6 +2,7 @@ #table name="filesTable" dataType="files" + [columnLabels]="columnLabels" [dataSource]="dataSource" [tableColumns]="tableColumns" [isSelectable]="true" diff --git a/src/app/core/_components/tables/files-table/files-table.component.ts b/src/app/core/_components/tables/files-table/files-table.component.ts index fa7bc73f..2e276e2f 100644 --- a/src/app/core/_components/tables/files-table/files-table.component.ts +++ b/src/app/core/_components/tables/files-table/files-table.component.ts @@ -1,6 +1,7 @@ /* eslint-disable @angular-eslint/component-selector */ import { Component, Input, OnDestroy, OnInit } from '@angular/core'; import { File, FileType } from 'src/app/core/_models/file.model'; +import { FilesTableCol, FilesTableColumnLabel } from './files-table.constants'; import { HTTableColumn, HTTableIcon, @@ -15,7 +16,6 @@ import { Cacheable } from 'src/app/core/_decorators/cacheable'; import { DialogData } from '../table-dialog/table-dialog.model'; import { ExportMenuAction } from '../../menus/export-menu/export-menu.constants'; import { FilesDataSource } from 'src/app/core/_datasources/files.datasource'; -import { FilesTableColumnLabel } from './files-table.constants'; import { RowActionMenuAction } from '../../menus/row-action-menu/row-action-menu.constants'; import { SERV } from 'src/app/core/_services/main.config'; import { TableDialogComponent } from '../table-dialog/table-dialog.component'; @@ -38,7 +38,7 @@ export class FilesTableComponent ngOnInit(): void { this.editPath = this.fileType === FileType.WORDLIST ? 'wordlist-edit' : 'rules-edit'; - + this.setColumnLabels(FilesTableColumnLabel); this.tableColumns = this.getColumns(); this.dataSource = new FilesDataSource(this.cdr, this.gs, this.uiService); this.dataSource.setColumns(this.tableColumns); @@ -63,13 +63,13 @@ export class FilesTableComponent getColumns(): HTTableColumn[] { const tableColumns = [ { - name: FilesTableColumnLabel.ID, + id: FilesTableCol.ID, dataKey: '_id', isSortable: true, export: async (file: File) => file._id + '' }, { - name: FilesTableColumnLabel.NAME, + id: FilesTableCol.NAME, dataKey: 'filename', icons: (file: File) => this.renderSecretIcon(file), routerLink: (file: File) => this.renderFileLink(file), @@ -77,20 +77,20 @@ export class FilesTableComponent export: async (file: File) => file.filename }, { - name: FilesTableColumnLabel.SIZE, + id: FilesTableCol.SIZE, dataKey: 'size', render: (file: File) => formatFileSize(file.size, 'short'), isSortable: true, export: async (file: File) => formatFileSize(file.size, 'short') }, { - name: FilesTableColumnLabel.LINE_COUNT, + id: FilesTableCol.LINE_COUNT, dataKey: 'lineCount', isSortable: true, export: async (file: File) => file.lineCount + '' }, { - name: FilesTableColumnLabel.ACCESS_GROUP, + id: FilesTableCol.ACCESS_GROUP, dataKey: 'accessGroupName', isSortable: true, export: async (file: File) => file.accessGroupName @@ -145,19 +145,25 @@ export class FilesTableComponent this.exportService.toExcel( 'hashtopolis-files', this.tableColumns, - event.data + event.data, + FilesTableColumnLabel ); break; case ExportMenuAction.CSV: this.exportService.toCsv( 'hashtopolis-files', this.tableColumns, - event.data + event.data, + FilesTableColumnLabel ); break; case ExportMenuAction.COPY: this.exportService - .toClipboard(this.tableColumns, event.data) + .toClipboard( + this.tableColumns, + event.data, + FilesTableColumnLabel + ) .then(() => { this.snackBar.open( 'The selected rows are copied to the clipboard', diff --git a/src/app/core/_components/tables/files-table/files-table.constants.ts b/src/app/core/_components/tables/files-table/files-table.constants.ts index 67fa304c..ba80c198 100644 --- a/src/app/core/_components/tables/files-table/files-table.constants.ts +++ b/src/app/core/_components/tables/files-table/files-table.constants.ts @@ -1,7 +1,15 @@ +export enum FilesTableCol { + ID, + NAME, + SIZE, + LINE_COUNT, + ACCESS_GROUP +} + export const FilesTableColumnLabel = { - ID: 'ID', - NAME: 'Name', - SIZE: 'Size', - LINE_COUNT: 'Line Count', - ACCESS_GROUP: 'Access Group' + [FilesTableCol.ID]: 'ID', + [FilesTableCol.NAME]: 'Name', + [FilesTableCol.SIZE]: 'Size', + [FilesTableCol.LINE_COUNT]: 'Line Count', + [FilesTableCol.ACCESS_GROUP]: 'Access Group' }; diff --git a/src/app/core/_components/tables/hashlists-table/hashlists-table.component.html b/src/app/core/_components/tables/hashlists-table/hashlists-table.component.html index d0c3e0e9..faa238e6 100644 --- a/src/app/core/_components/tables/hashlists-table/hashlists-table.component.html +++ b/src/app/core/_components/tables/hashlists-table/hashlists-table.component.html @@ -2,6 +2,7 @@ #table name="hashlistsTable" dataType="hashlists" + [columnLabels]="columnLabels" [dataSource]="dataSource" [tableColumns]="tableColumns" [isSelectable]="true" diff --git a/src/app/core/_components/tables/hashlists-table/hashlists-table.component.ts b/src/app/core/_components/tables/hashlists-table/hashlists-table.component.ts index b0889c53..2a5e398a 100644 --- a/src/app/core/_components/tables/hashlists-table/hashlists-table.component.ts +++ b/src/app/core/_components/tables/hashlists-table/hashlists-table.component.ts @@ -1,10 +1,14 @@ /* eslint-disable @angular-eslint/component-selector */ -import { Component, Input, OnDestroy, OnInit } from '@angular/core'; +import { Component, OnDestroy, OnInit } from '@angular/core'; import { HTTableColumn, HTTableIcon, HTTableRouterLink } from '../ht-table/ht-table.models'; +import { + HashlistsTableCol, + HashlistsTableColumnLabel +} from './hashlists-table.constants'; import { catchError, forkJoin } from 'rxjs'; import { ActionMenuEvent } from '../../menus/action-menu/action-menu.model'; @@ -16,7 +20,6 @@ import { ExportMenuAction } from '../../menus/export-menu/export-menu.constants' import { HashListFormatLabel } from 'src/app/core/_constants/hashlist.config'; import { Hashlist } from 'src/app/core/_models/hashlist.model'; import { HashlistsDataSource } from 'src/app/core/_datasources/hashlists.datasource'; -import { HashlistsTableColumnLabel } from './hashlists-table.constants'; import { RowActionMenuAction } from '../../menus/row-action-menu/row-action-menu.constants'; import { SERV } from 'src/app/core/_services/main.config'; import { TableDialogComponent } from '../table-dialog/table-dialog.component'; @@ -35,6 +38,7 @@ export class HashlistsTableComponent isArchived = false; ngOnInit(): void { + this.setColumnLabels(HashlistsTableColumnLabel); this.tableColumns = this.getColumns(); this.dataSource = new HashlistsDataSource( this.cdr, @@ -66,13 +70,13 @@ export class HashlistsTableComponent getColumns(): HTTableColumn[] { const tableColumns = [ { - name: HashlistsTableColumnLabel.ID, + id: HashlistsTableCol.ID, dataKey: '_id', isSortable: true, export: async (hashlist: Hashlist) => hashlist._id + '' }, { - name: HashlistsTableColumnLabel.NAME, + id: HashlistsTableCol.NAME, dataKey: 'name', icons: (hashlist: Hashlist) => this.renderSecretIcon(hashlist), routerLink: (hashlist: Hashlist) => this.renderHashlistLink(hashlist), @@ -80,14 +84,14 @@ export class HashlistsTableComponent export: async (hashlist: Hashlist) => hashlist.name }, { - name: HashlistsTableColumnLabel.HASH_COUNT, + id: HashlistsTableCol.HASH_COUNT, dataKey: 'hashCount', isSortable: true, routerLink: (hashlist: Hashlist) => this.renderHashCountLink(hashlist), export: async (hashlist: Hashlist) => hashlist.hashCount + '' }, { - name: HashlistsTableColumnLabel.CRACKED, + id: HashlistsTableCol.CRACKED, dataKey: 'cracked', icons: (hashlist: Hashlist) => this.renderCrackedStatusIcon(hashlist), render: (hashlist: Hashlist) => @@ -97,13 +101,13 @@ export class HashlistsTableComponent formatPercentage(hashlist.cracked, hashlist.hashCount) }, { - name: HashlistsTableColumnLabel.HASHTYPE, + id: HashlistsTableCol.HASHTYPE, dataKey: 'hashTypeDescription', isSortable: true, export: async (hashlist: Hashlist) => hashlist.hashTypeDescription }, { - name: HashlistsTableColumnLabel.FORMAT, + id: HashlistsTableCol.FORMAT, dataKey: 'format', isSortable: true, render: (hashlist: Hashlist) => @@ -178,19 +182,25 @@ export class HashlistsTableComponent this.exportService.toExcel( 'hashtopolis-hashlists', this.tableColumns, - event.data + event.data, + HashlistsTableColumnLabel ); break; case ExportMenuAction.CSV: this.exportService.toCsv( 'hashtopolis-hashlists', this.tableColumns, - event.data + event.data, + HashlistsTableColumnLabel ); break; case ExportMenuAction.COPY: this.exportService - .toClipboard(this.tableColumns, event.data) + .toClipboard( + this.tableColumns, + event.data, + HashlistsTableColumnLabel + ) .then(() => { this.snackBar.open( 'The selected rows are copied to the clipboard', diff --git a/src/app/core/_components/tables/hashlists-table/hashlists-table.constants.ts b/src/app/core/_components/tables/hashlists-table/hashlists-table.constants.ts index 9a6395a8..1f14e7e4 100644 --- a/src/app/core/_components/tables/hashlists-table/hashlists-table.constants.ts +++ b/src/app/core/_components/tables/hashlists-table/hashlists-table.constants.ts @@ -1,9 +1,19 @@ +export enum HashlistsTableCol { + ID, + NAME, + STATUS, + HASHTYPE, + FORMAT, + CRACKED, + HASH_COUNT +} + export const HashlistsTableColumnLabel = { - ID: 'ID', - NAME: 'Name', - STATUS: 'Status', - HASHTYPE: 'Hash Type', - FORMAT: 'Format', - CRACKED: 'Cracked', - HASH_COUNT: 'Hash Count' + [HashlistsTableCol.ID]: 'ID', + [HashlistsTableCol.NAME]: 'Name', + [HashlistsTableCol.STATUS]: 'Status', + [HashlistsTableCol.HASHTYPE]: 'Hash Type', + [HashlistsTableCol.FORMAT]: 'Format', + [HashlistsTableCol.CRACKED]: 'Cracked', + [HashlistsTableCol.HASH_COUNT]: 'Hash Count' }; diff --git a/src/app/core/_components/tables/hashtypes-table/hashtypes-table.component.html b/src/app/core/_components/tables/hashtypes-table/hashtypes-table.component.html index b2ad5fad..43e8bf71 100644 --- a/src/app/core/_components/tables/hashtypes-table/hashtypes-table.component.html +++ b/src/app/core/_components/tables/hashtypes-table/hashtypes-table.component.html @@ -2,6 +2,7 @@ #table name="hashtypesTable" dataType="hashtypes" + [columnLabels]="columnLabels" [dataSource]="dataSource" [tableColumns]="tableColumns" [isSelectable]="true" diff --git a/src/app/core/_components/tables/hashtypes-table/hashtypes-table.component.ts b/src/app/core/_components/tables/hashtypes-table/hashtypes-table.component.ts index 77588316..f6d70624 100644 --- a/src/app/core/_components/tables/hashtypes-table/hashtypes-table.component.ts +++ b/src/app/core/_components/tables/hashtypes-table/hashtypes-table.component.ts @@ -9,6 +9,10 @@ import { HTTableColumn, HTTableIcon } from '../../tables/ht-table/ht-table.models'; +import { + HashtypesTableCol, + HashtypesTableColumnLabel +} from './hashtypes-table.constants'; import { catchError, forkJoin } from 'rxjs'; import { ActionMenuEvent } from '../../menus/action-menu/action-menu.model'; @@ -19,7 +23,6 @@ import { DialogData } from '../table-dialog/table-dialog.model'; import { ExportMenuAction } from '../../menus/export-menu/export-menu.constants'; import { Hashtype } from '../../../_models/hashtype.model'; import { HashtypesDataSource } from '../../../_datasources/hashtypes.datasource'; -import { HashtypesTableColumnLabel } from './hashtypes-table.constants'; import { RowActionMenuAction } from '../../menus/row-action-menu/row-action-menu.constants'; import { SERV } from 'src/app/core/_services/main.config'; import { TableDialogComponent } from '../table-dialog/table-dialog.component'; @@ -37,6 +40,7 @@ export class HashtypesTableComponent dataSource: HashtypesDataSource; ngOnInit(): void { + this.setColumnLabels(HashtypesTableColumnLabel); this.tableColumns = this.getColumns(); this.dataSource = new HashtypesDataSource( this.cdr, @@ -54,26 +58,26 @@ export class HashtypesTableComponent getColumns(): HTTableColumn[] { const tableColumns = [ { - name: HashtypesTableColumnLabel.HASHTYPE, + id: HashtypesTableCol.HASHTYPE, dataKey: 'hashTypeId', isSortable: true, export: async (hashtype: Hashtype) => hashtype.hashTypeId + '' }, { - name: HashtypesTableColumnLabel.DESCRIPTION, + id: HashtypesTableCol.DESCRIPTION, dataKey: 'description', isSortable: true, export: async (hashtype: Hashtype) => hashtype.description }, { - name: HashtypesTableColumnLabel.SALTED, + id: HashtypesTableCol.SALTED, dataKey: 'isSalted', icons: (hashtype: Hashtype) => this.renderIsSaltedIcon(hashtype), isSortable: true, export: async (hashtype: Hashtype) => (hashtype.isSalted ? 'Yes' : 'No') }, { - name: HashtypesTableColumnLabel.SLOW_HASH, + id: HashtypesTableCol.SLOW_HASH, dataKey: 'isSlowHash', icons: (hashtype: Hashtype) => this.renderIsSlowIcon(hashtype), isSortable: true, @@ -208,19 +212,25 @@ export class HashtypesTableComponent this.exportService.toExcel( 'hashtopolis-hashtypes', this.tableColumns, - event.data + event.data, + HashtypesTableColumnLabel ); break; case ExportMenuAction.CSV: this.exportService.toCsv( 'hashtopolis-hashtypes', this.tableColumns, - event.data + event.data, + HashtypesTableColumnLabel ); break; case ExportMenuAction.COPY: this.exportService - .toClipboard(this.tableColumns, event.data) + .toClipboard( + this.tableColumns, + event.data, + HashtypesTableColumnLabel + ) .then(() => { this.snackBar.open( 'The selected rows are copied to the clipboard', diff --git a/src/app/core/_components/tables/hashtypes-table/hashtypes-table.constants.ts b/src/app/core/_components/tables/hashtypes-table/hashtypes-table.constants.ts index c75679d7..00849da3 100644 --- a/src/app/core/_components/tables/hashtypes-table/hashtypes-table.constants.ts +++ b/src/app/core/_components/tables/hashtypes-table/hashtypes-table.constants.ts @@ -1,6 +1,13 @@ +export enum HashtypesTableCol { + HASHTYPE, + DESCRIPTION, + SALTED, + SLOW_HASH +} + export const HashtypesTableColumnLabel = { - HASHTYPE: 'Hashtype (Hashcat -m)', - DESCRIPTION: 'Description', - SALTED: 'Salted', - SLOW_HASH: 'Slow Hash' + [HashtypesTableCol.HASHTYPE]: 'Hashtype (Hashcat -m)', + [HashtypesTableCol.DESCRIPTION]: 'Description', + [HashtypesTableCol.SALTED]: 'Salted', + [HashtypesTableCol.SLOW_HASH]: 'Slow Hash' }; diff --git a/src/app/core/_components/tables/health-checks-table/health-checks-table.component.html b/src/app/core/_components/tables/health-checks-table/health-checks-table.component.html index 49cc1594..cb6d1a41 100644 --- a/src/app/core/_components/tables/health-checks-table/health-checks-table.component.html +++ b/src/app/core/_components/tables/health-checks-table/health-checks-table.component.html @@ -2,6 +2,7 @@ #table name="healthChecksTable" dataType="health-checks" + [columnLabels]="columnLabels" [dataSource]="dataSource" [tableColumns]="tableColumns" [isSelectable]="true" diff --git a/src/app/core/_components/tables/health-checks-table/health-checks-table.component.ts b/src/app/core/_components/tables/health-checks-table/health-checks-table.component.ts index 6d5d46c0..50a9089b 100644 --- a/src/app/core/_components/tables/health-checks-table/health-checks-table.component.ts +++ b/src/app/core/_components/tables/health-checks-table/health-checks-table.component.ts @@ -1,6 +1,7 @@ /* eslint-disable @angular-eslint/component-selector */ import { Component, OnDestroy, OnInit } from '@angular/core'; import { + HealthChecksTableCol, HealthChecksTableColumnLabel, HealthChecksTableStatusLabel } from './health-checks-table.constants'; @@ -31,6 +32,7 @@ export class HealthChecksTableComponent dataSource: HealthChecksDataSource; ngOnInit(): void { + this.setColumnLabels(HealthChecksTableColumnLabel); this.tableColumns = this.getColumns(); this.dataSource = new HealthChecksDataSource( this.cdr, @@ -58,13 +60,13 @@ export class HealthChecksTableComponent getColumns(): HTTableColumn[] { const tableColumns = [ { - name: HealthChecksTableColumnLabel.ID, + id: HealthChecksTableCol.ID, dataKey: '_id', isSortable: true, export: async (healthCheck: HealthCheck) => healthCheck._id + '' }, { - name: HealthChecksTableColumnLabel.CREATED, + id: HealthChecksTableCol.CREATED, dataKey: 'created', isSortable: true, render: (healthCheck: HealthCheck) => @@ -73,7 +75,7 @@ export class HealthChecksTableComponent formatUnixTimestamp(healthCheck.time, this.dateFormat) }, { - name: HealthChecksTableColumnLabel.TYPE, + id: HealthChecksTableCol.TYPE, dataKey: 'hashtypeDescription', render: (healthCheck: HealthCheck) => healthCheck.hashtype @@ -86,7 +88,7 @@ export class HealthChecksTableComponent : '' }, { - name: HealthChecksTableColumnLabel.STATUS, + id: HealthChecksTableCol.STATUS, dataKey: 'status', render: (healthCheck: HealthCheck) => HealthChecksTableStatusLabel[healthCheck.status], @@ -129,19 +131,25 @@ export class HealthChecksTableComponent this.exportService.toExcel( 'hashtopolis-health-checks', this.tableColumns, - event.data + event.data, + HealthChecksTableColumnLabel ); break; case ExportMenuAction.CSV: this.exportService.toCsv( 'hashtopolis-health-checks', this.tableColumns, - event.data + event.data, + HealthChecksTableColumnLabel ); break; case ExportMenuAction.COPY: this.exportService - .toClipboard(this.tableColumns, event.data) + .toClipboard( + this.tableColumns, + event.data, + HealthChecksTableColumnLabel + ) .then(() => { this.snackBar.open( 'The selected rows are copied to the clipboard', diff --git a/src/app/core/_components/tables/health-checks-table/health-checks-table.constants.ts b/src/app/core/_components/tables/health-checks-table/health-checks-table.constants.ts index b18a55af..ce77a299 100644 --- a/src/app/core/_components/tables/health-checks-table/health-checks-table.constants.ts +++ b/src/app/core/_components/tables/health-checks-table/health-checks-table.constants.ts @@ -1,10 +1,17 @@ import { HealthCheckStatus } from 'src/app/core/_models/health-check.model'; +export enum HealthChecksTableCol { + ID, + CREATED, + TYPE, + STATUS +} + export const HealthChecksTableColumnLabel = { - ID: 'ID', - CREATED: 'Created', - TYPE: 'Type', - STATUS: 'Status' + [HealthChecksTableCol.ID]: 'ID', + [HealthChecksTableCol.CREATED]: 'Created', + [HealthChecksTableCol.TYPE]: 'Type', + [HealthChecksTableCol.STATUS]: 'Status' }; export const HealthChecksTableStatusLabel = { 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 71a70d85..5b3c2fe2 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 @@ -70,7 +70,7 @@
- + - + - {{ tableColumn.name }} + {{ columnLabels[tableColumn.id] }} @@ -136,7 +136,7 @@ *matHeaderCellDef [class.text-right]="tableColumn.position === 'right'" > - {{ tableColumn.name }} + {{ columnLabels[tableColumn.id] }} diff --git a/src/app/core/_components/tables/ht-table/ht-table.component.ts b/src/app/core/_components/tables/ht-table/ht-table.component.ts index 29237181..c5385a4f 100644 --- a/src/app/core/_components/tables/ht-table/ht-table.component.ts +++ b/src/app/core/_components/tables/ht-table/ht-table.component.ts @@ -13,6 +13,10 @@ import { } from '@angular/core'; import { DataType, HTTableColumn, HTTableEditable } from './ht-table.models'; import { MatPaginator, PageEvent } from '@angular/material/paginator'; +import { + UIConfig, + uiConfigDefault +} from 'src/app/core/_models/config-ui.model'; import { ActionMenuEvent } from '../../menus/action-menu/action-menu.model'; import { BaseDataSource } from 'src/app/core/_datasources/base.datasource'; @@ -21,7 +25,6 @@ import { ColumnSelectionDialogComponent } from '../column-selection-dialog/colum import { LocalStorageService } from 'src/app/core/_services/storage/local-storage.service'; import { MatDialog } from '@angular/material/dialog'; import { MatSort } from '@angular/material/sort'; -import { UIConfig } from 'src/app/core/_models/config-ui.model'; import { UISettingsUtilityClass } from 'src/app/shared/utils/config'; /** @@ -75,10 +78,7 @@ import { UISettingsUtilityClass } from 'src/app/shared/utils/config'; }) export class HTTableComponent implements OnInit, AfterViewInit { /** The list of column names to be displayed in the table. */ - displayedColumns: string[]; - - /** The list of all available column names. */ - columnNames: string[]; + displayedColumns: string[] = []; /** Reference to MatPaginator for pagination support. */ @ViewChild(MatPaginator, { static: false }) matPaginator: MatPaginator; @@ -89,6 +89,9 @@ export class HTTableComponent implements OnInit, AfterViewInit { /** Name of the table, used when storing user customizations */ @Input() name: string; + /** All available column labels */ + @Input() columnLabels: { [key: number]: string }; + /** Data type displayed in the table, used to load relevant context menus */ @Input() dataType: DataType; @@ -155,18 +158,13 @@ export class HTTableComponent implements OnInit, AfterViewInit { private storage: LocalStorageService ) {} - // @todo: fix ExpressionChangedAfterItHasBeenCheckedError. loading is causing trouble... - ngOnInit(): void { this.uiSettings = new UISettingsUtilityClass(this.storage); - this.columnNames = this.tableColumns.map( - (tableColumn: HTTableColumn) => tableColumn.name - ); const displayedColumns = this.uiSettings.getTableSettings(this.name); if (displayedColumns) { this.setDisplayedColumns(displayedColumns); } else { - this.setDisplayedColumns(this.columnNames); + this.setDisplayedColumns(uiConfigDefault.tableSettings[this.name]); } } @@ -187,12 +185,15 @@ export class HTTableComponent implements OnInit, AfterViewInit { const dialogRef = this.dialog.open(ColumnSelectionDialogComponent, { width: '400px', data: { - availableColumns: this.columnNames, + availableColumns: this.filterKeys( + this.columnLabels, + this.tableColumns.map((col) => col.id + '') + ), selectedColumns: this.displayedColumns } }); - dialogRef.afterClosed().subscribe((selectedColumns: string[]) => { + dialogRef.afterClosed().subscribe((selectedColumns: number[]) => { if (selectedColumns) { this.setDisplayedColumns(selectedColumns); this.uiSettings.updateTableSettings(this.name, selectedColumns); @@ -201,6 +202,21 @@ export class HTTableComponent implements OnInit, AfterViewInit { }); } + private filterKeys( + original: { [key: string]: string }, + include: string[] + ): any { + const filteredObject: { [key: string]: string } = {}; + + for (const attribute of include) { + if (original.hasOwnProperty(attribute)) { + filteredObject[attribute] = original[attribute]; + } + } + + return filteredObject; + } + rowAction(event: ActionMenuEvent): void { this.rowActionClicked.emit(event); } @@ -218,17 +234,19 @@ export class HTTableComponent implements OnInit, AfterViewInit { * * @param columnNames - The list of column names to display. */ - setDisplayedColumns(columnNames: string[]): void { - if (this.hasRowAction) { - // Add action menu if enabled - this.displayedColumns = [...columnNames, 'rowAction']; - } else { - this.displayedColumns = columnNames; - } - + setDisplayedColumns(columnNames: number[]): void { + this.displayedColumns = []; if (this.isSelectable) { // Add checkbox if enabled - this.displayedColumns = ['select', ...this.displayedColumns]; + this.displayedColumns.push('100'); // 100 = select + } + for (const num of columnNames) { + this.displayedColumns.push(num + ''); + } + + if (this.hasRowAction) { + // Add action menu if enabled + this.displayedColumns.push('200'); // 200 = row action } } diff --git a/src/app/core/_components/tables/ht-table/ht-table.models.ts b/src/app/core/_components/tables/ht-table/ht-table.models.ts index 2cd9e020..bf2db769 100644 --- a/src/app/core/_components/tables/ht-table/ht-table.models.ts +++ b/src/app/core/_components/tables/ht-table/ht-table.models.ts @@ -42,7 +42,7 @@ export type HTTableColumnType = 'dafeult | link | editable'; export interface HTTableColumn { type?: HTTableColumnType; - name: string; + id: number; dataKey: string; position?: 'right' | 'left'; isSortable?: boolean; diff --git a/src/app/core/_components/tables/logs-table/logs-table.component.html b/src/app/core/_components/tables/logs-table/logs-table.component.html index d742dac6..f90507b0 100644 --- a/src/app/core/_components/tables/logs-table/logs-table.component.html +++ b/src/app/core/_components/tables/logs-table/logs-table.component.html @@ -2,6 +2,7 @@ #table name="logsTable" dataType="logs" + [columnLabels]="columnLabels" [dataSource]="dataSource" [tableColumns]="tableColumns" [isSelectable]="true" diff --git a/src/app/core/_components/tables/logs-table/logs-table.component.ts b/src/app/core/_components/tables/logs-table/logs-table.component.ts index d37b16da..1a502c1e 100644 --- a/src/app/core/_components/tables/logs-table/logs-table.component.ts +++ b/src/app/core/_components/tables/logs-table/logs-table.component.ts @@ -1,5 +1,6 @@ /* eslint-disable @angular-eslint/component-selector */ import { Component, OnDestroy, OnInit } from '@angular/core'; +import { LogsTableCol, LogsTableColumnLabel } from './logs-table.constants'; import { ActionMenuEvent } from '../../menus/action-menu/action-menu.model'; import { BaseTableComponent } from '../base-table/base-table.component'; @@ -7,7 +8,6 @@ import { ExportMenuAction } from '../../menus/export-menu/export-menu.constants' import { HTTableColumn } from '../ht-table/ht-table.models'; import { Log } from 'src/app/core/_models/log.model'; import { LogsDataSource } from 'src/app/core/_datasources/logs.datasource'; -import { LogsTableColumnLabel } from './logs-table.constants'; import { formatUnixTimestamp } from 'src/app/shared/utils/datetime'; @Component({ @@ -22,6 +22,7 @@ export class LogsTableComponent dataSource: LogsDataSource; ngOnInit(): void { + this.setColumnLabels(LogsTableColumnLabel); this.tableColumns = this.getColumns(); this.dataSource = new LogsDataSource(this.cdr, this.gs, this.uiService); this.dataSource.setColumns(this.tableColumns); @@ -45,13 +46,13 @@ export class LogsTableComponent getColumns(): HTTableColumn[] { const tableColumns = [ { - name: LogsTableColumnLabel.ID, + id: LogsTableCol.ID, dataKey: '_id', isSortable: true, export: async (log: Log) => log._id + '' }, { - name: LogsTableColumnLabel.TIME, + id: LogsTableCol.TIME, dataKey: 'time', isSortable: true, render: (log: Log) => formatUnixTimestamp(log.time, this.dateFormat), @@ -59,7 +60,7 @@ export class LogsTableComponent formatUnixTimestamp(log.time, this.dateFormat) }, { - name: LogsTableColumnLabel.LEVEL, + id: LogsTableCol.LEVEL, dataKey: 'level', isSortable: true, render: (log: Log) => @@ -68,14 +69,14 @@ export class LogsTableComponent log.level.charAt(0).toUpperCase() + log.level.slice(1).toLowerCase() }, { - name: LogsTableColumnLabel.ISSUER, + id: LogsTableCol.ISSUER, dataKey: 'issuer', isSortable: true, render: (log: Log) => `${log.issuer}-ID-${log.issuerId}`, export: async (log: Log) => `${log.issuer}-ID-${log.issuerId}` }, { - name: LogsTableColumnLabel.MESSAGE, + id: LogsTableCol.MESSAGE, dataKey: 'message', isSortable: true, export: async (log: Log) => log.message @@ -93,19 +94,21 @@ export class LogsTableComponent this.exportService.toExcel( 'hashtopolis-logs', this.tableColumns, - event.data + event.data, + LogsTableColumnLabel ); break; case ExportMenuAction.CSV: this.exportService.toCsv( 'hashtopolis-logs', this.tableColumns, - event.data + event.data, + LogsTableColumnLabel ); break; case ExportMenuAction.COPY: this.exportService - .toClipboard(this.tableColumns, event.data) + .toClipboard(this.tableColumns, event.data, LogsTableColumnLabel) .then(() => { this.snackBar.open( 'The selected rows are copied to the clipboard', diff --git a/src/app/core/_components/tables/logs-table/logs-table.constants.ts b/src/app/core/_components/tables/logs-table/logs-table.constants.ts index e1c1f1d5..a94a8e40 100644 --- a/src/app/core/_components/tables/logs-table/logs-table.constants.ts +++ b/src/app/core/_components/tables/logs-table/logs-table.constants.ts @@ -1,7 +1,15 @@ +export enum LogsTableCol { + ID, + TIME, + LEVEL, + ISSUER, + MESSAGE +} + export const LogsTableColumnLabel = { - ID: 'ID', - TIME: 'Time', - LEVEL: 'Level', - ISSUER: 'Issuer', - MESSAGE: 'Message' + [LogsTableCol.ID]: 'ID', + [LogsTableCol.TIME]: 'Time', + [LogsTableCol.LEVEL]: 'Level', + [LogsTableCol.ISSUER]: 'Issuer', + [LogsTableCol.MESSAGE]: 'Message' }; diff --git a/src/app/core/_components/tables/notifications-table/notifications-table.component.html b/src/app/core/_components/tables/notifications-table/notifications-table.component.html index 74789a8f..a17c906d 100644 --- a/src/app/core/_components/tables/notifications-table/notifications-table.component.html +++ b/src/app/core/_components/tables/notifications-table/notifications-table.component.html @@ -2,6 +2,7 @@ #table name="notificationsTable" dataType="notifications" + [columnLabels]="columnLabels" [dataSource]="dataSource" [tableColumns]="tableColumns" [isSelectable]="true" diff --git a/src/app/core/_components/tables/notifications-table/notifications-table.component.ts b/src/app/core/_components/tables/notifications-table/notifications-table.component.ts index e9a3ec8a..9ea23dc4 100644 --- a/src/app/core/_components/tables/notifications-table/notifications-table.component.ts +++ b/src/app/core/_components/tables/notifications-table/notifications-table.component.ts @@ -1,6 +1,10 @@ /* eslint-disable @angular-eslint/component-selector */ import { Component, OnDestroy, OnInit } from '@angular/core'; import { HTTableColumn, HTTableRouterLink } from '../ht-table/ht-table.models'; +import { + NotificationsTableCol, + NotificationsTableColumnLabel +} from './notifications-table.constants'; import { catchError, forkJoin } from 'rxjs'; import { ACTION } from 'src/app/core/_constants/notifications.config'; @@ -12,7 +16,6 @@ import { DialogData } from '../table-dialog/table-dialog.model'; import { ExportMenuAction } from '../../menus/export-menu/export-menu.constants'; import { Notification } from 'src/app/core/_models/notification.model'; import { NotificationsDataSource } from 'src/app/core/_datasources/notifications.datasource'; -import { NotificationsTableColumnLabel } from './notifications-table.constants'; import { RowActionMenuAction } from '../../menus/row-action-menu/row-action-menu.constants'; import { SERV } from 'src/app/core/_services/main.config'; import { TableDialogComponent } from '../table-dialog/table-dialog.component'; @@ -29,6 +32,7 @@ export class NotificationsTableComponent dataSource: NotificationsDataSource; ngOnInit(): void { + this.setColumnLabels(NotificationsTableColumnLabel); this.tableColumns = this.getColumns(); this.dataSource = new NotificationsDataSource( this.cdr, @@ -56,13 +60,13 @@ export class NotificationsTableComponent getColumns(): HTTableColumn[] { const tableColumns = [ { - name: NotificationsTableColumnLabel.ID, + id: NotificationsTableCol.ID, dataKey: '_id', isSortable: true, export: async (notification: Notification) => notification._id + '' }, { - name: NotificationsTableColumnLabel.STATUS, + id: NotificationsTableCol.STATUS, dataKey: 'isActive', render: (notification: Notification) => notification.isActive ? 'Active' : 'Inactive', @@ -72,13 +76,13 @@ export class NotificationsTableComponent export: async (notification: Notification) => notification.isActive + '' }, { - name: NotificationsTableColumnLabel.ACTION, + id: NotificationsTableCol.ACTION, dataKey: 'action', isSortable: true, export: async (notification: Notification) => notification.action }, { - name: NotificationsTableColumnLabel.APPLIED_TO, + id: NotificationsTableCol.APPLIED_TO, dataKey: 'appliedTo', routerLink: (notification: Notification) => this.renderAppliedToLink(notification), @@ -86,13 +90,13 @@ export class NotificationsTableComponent export: async (notification: Notification) => notification.action }, { - name: NotificationsTableColumnLabel.NOTIFICATION, + id: NotificationsTableCol.NOTIFICATION, dataKey: 'notification', isSortable: true, export: async (notification: Notification) => notification.notification }, { - name: NotificationsTableColumnLabel.RECEIVER, + id: NotificationsTableCol.RECEIVER, dataKey: 'receiver', isSortable: true, export: async (notification: Notification) => notification.receiver @@ -138,19 +142,25 @@ export class NotificationsTableComponent this.exportService.toExcel( 'hashtopolis-notifications', this.tableColumns, - event.data + event.data, + NotificationsTableColumnLabel ); break; case ExportMenuAction.CSV: this.exportService.toCsv( 'hashtopolis-notifications', this.tableColumns, - event.data + event.data, + NotificationsTableColumnLabel ); break; case ExportMenuAction.COPY: this.exportService - .toClipboard(this.tableColumns, event.data) + .toClipboard( + this.tableColumns, + event.data, + NotificationsTableColumnLabel + ) .then(() => { this.snackBar.open( 'The selected rows are copied to the clipboard', diff --git a/src/app/core/_components/tables/notifications-table/notifications-table.constants.ts b/src/app/core/_components/tables/notifications-table/notifications-table.constants.ts index 7d54749f..ba4cf652 100644 --- a/src/app/core/_components/tables/notifications-table/notifications-table.constants.ts +++ b/src/app/core/_components/tables/notifications-table/notifications-table.constants.ts @@ -1,8 +1,17 @@ +export enum NotificationsTableCol { + ID, + STATUS, + APPLIED_TO, + ACTION, + NOTIFICATION, + RECEIVER +} + export const NotificationsTableColumnLabel = { - ID: 'ID', - STATUS: 'Status', - APPLIED_TO: 'Applied To', - ACTION: 'Trigger Action', - NOTIFICATION: 'Called Notification', - RECEIVER: 'Receiver' + [NotificationsTableCol.ID]: 'ID', + [NotificationsTableCol.STATUS]: 'Status', + [NotificationsTableCol.APPLIED_TO]: 'Applied To', + [NotificationsTableCol.ACTION]: 'Trigger Action', + [NotificationsTableCol.NOTIFICATION]: 'Called Notification', + [NotificationsTableCol.RECEIVER]: 'Receiver' }; diff --git a/src/app/core/_components/tables/permissions-table/permissions-table.component.html b/src/app/core/_components/tables/permissions-table/permissions-table.component.html index 311172b7..cdfd54b3 100644 --- a/src/app/core/_components/tables/permissions-table/permissions-table.component.html +++ b/src/app/core/_components/tables/permissions-table/permissions-table.component.html @@ -2,6 +2,7 @@ #table name="permissionsTable" dataType="permissions" + [columnLabels]="columnLabels" [dataSource]="dataSource" [tableColumns]="tableColumns" [isSelectable]="true" diff --git a/src/app/core/_components/tables/permissions-table/permissions-table.component.ts b/src/app/core/_components/tables/permissions-table/permissions-table.component.ts index bf424cf9..7839d26f 100644 --- a/src/app/core/_components/tables/permissions-table/permissions-table.component.ts +++ b/src/app/core/_components/tables/permissions-table/permissions-table.component.ts @@ -1,5 +1,9 @@ /* eslint-disable @angular-eslint/component-selector */ import { Component, OnDestroy, OnInit } from '@angular/core'; +import { + PermissionsTableCol, + PermissionsTableColumnLabel +} from './permissions-table.constants'; import { catchError, forkJoin } from 'rxjs'; import { ActionMenuEvent } from '../../menus/action-menu/action-menu.model'; @@ -10,7 +14,6 @@ import { ExportMenuAction } from '../../menus/export-menu/export-menu.constants' import { GlobalPermissionGroup } from 'src/app/core/_models/global-permission-group.model'; import { HTTableColumn } from '../ht-table/ht-table.models'; import { PermissionsDataSource } from 'src/app/core/_datasources/permissions.datasource'; -import { PermissionsTableColumnLabel } from './permissions-table.constants'; import { RowActionMenuAction } from '../../menus/row-action-menu/row-action-menu.constants'; import { SERV } from 'src/app/core/_services/main.config'; import { TableDialogComponent } from '../table-dialog/table-dialog.component'; @@ -27,6 +30,7 @@ export class PermissionsTableComponent dataSource: PermissionsDataSource; ngOnInit(): void { + this.setColumnLabels(PermissionsTableColumnLabel); this.tableColumns = this.getColumns(); this.dataSource = new PermissionsDataSource( this.cdr, @@ -54,13 +58,13 @@ export class PermissionsTableComponent getColumns(): HTTableColumn[] { const tableColumns = [ { - name: PermissionsTableColumnLabel.ID, + id: PermissionsTableCol.ID, dataKey: '_id', isSortable: true, export: async (permission: GlobalPermissionGroup) => permission._id + '' }, { - name: PermissionsTableColumnLabel.NAME, + id: PermissionsTableCol.NAME, dataKey: 'name', routerLink: (permission: GlobalPermissionGroup) => this.renderPermissionLink(permission), @@ -68,7 +72,7 @@ export class PermissionsTableComponent export: async (permission: GlobalPermissionGroup) => permission.name }, { - name: PermissionsTableColumnLabel.MEMBERS, + id: PermissionsTableCol.MEMBERS, dataKey: 'numUsers', isSortable: true, render: (permission: GlobalPermissionGroup) => permission.user.length, @@ -109,19 +113,25 @@ export class PermissionsTableComponent this.exportService.toExcel( 'hashtopolis-permissions', this.tableColumns, - event.data + event.data, + PermissionsTableColumnLabel ); break; case ExportMenuAction.CSV: this.exportService.toCsv( 'hashtopolis-permissions', this.tableColumns, - event.data + event.data, + PermissionsTableColumnLabel ); break; case ExportMenuAction.COPY: this.exportService - .toClipboard(this.tableColumns, event.data) + .toClipboard( + this.tableColumns, + event.data, + PermissionsTableColumnLabel + ) .then(() => { this.snackBar.open( 'The selected rows are copied to the clipboard', diff --git a/src/app/core/_components/tables/permissions-table/permissions-table.constants.ts b/src/app/core/_components/tables/permissions-table/permissions-table.constants.ts index 68e11b42..aaed8459 100644 --- a/src/app/core/_components/tables/permissions-table/permissions-table.constants.ts +++ b/src/app/core/_components/tables/permissions-table/permissions-table.constants.ts @@ -1,5 +1,11 @@ +export enum PermissionsTableCol { + ID, + NAME, + MEMBERS +} + export const PermissionsTableColumnLabel = { - ID: 'ID', - NAME: 'Name', - MEMBERS: 'Members' + [PermissionsTableCol.ID]: 'ID', + [PermissionsTableCol.NAME]: 'Name', + [PermissionsTableCol.MEMBERS]: 'Members' }; diff --git a/src/app/core/_components/tables/preprocessors-table/preprocessors-table.component.html b/src/app/core/_components/tables/preprocessors-table/preprocessors-table.component.html index 4500d0ba..7b44a79c 100644 --- a/src/app/core/_components/tables/preprocessors-table/preprocessors-table.component.html +++ b/src/app/core/_components/tables/preprocessors-table/preprocessors-table.component.html @@ -2,6 +2,7 @@ #table name="preprocessorsTable" dataType="preprocessors" + [columnLabels]="columnLabels" [dataSource]="dataSource" [tableColumns]="tableColumns" [isSelectable]="true" diff --git a/src/app/core/_components/tables/preprocessors-table/preprocessors-table.component.ts b/src/app/core/_components/tables/preprocessors-table/preprocessors-table.component.ts index 45511f3e..405aa5d0 100644 --- a/src/app/core/_components/tables/preprocessors-table/preprocessors-table.component.ts +++ b/src/app/core/_components/tables/preprocessors-table/preprocessors-table.component.ts @@ -1,5 +1,9 @@ /* eslint-disable @angular-eslint/component-selector */ import { Component, OnDestroy, OnInit } from '@angular/core'; +import { + PreprocessorsTableCol, + PreprocessorsTableColumnLabel +} from './preprocessors-table.constants'; import { catchError, forkJoin } from 'rxjs'; import { ActionMenuEvent } from '../../menus/action-menu/action-menu.model'; @@ -10,7 +14,6 @@ import { ExportMenuAction } from '../../menus/export-menu/export-menu.constants' import { HTTableColumn } from '../ht-table/ht-table.models'; import { Preprocessor } from 'src/app/core/_models/preprocessor.model'; import { PreprocessorsDataSource } from 'src/app/core/_datasources/preprocessors.datasource'; -import { PreprocessorsTableColumnLabel } from './preprocessors-table.constants'; import { RowActionMenuAction } from '../../menus/row-action-menu/row-action-menu.constants'; import { SERV } from 'src/app/core/_services/main.config'; import { TableDialogComponent } from '../table-dialog/table-dialog.component'; @@ -27,6 +30,7 @@ export class PreprocessorsTableComponent dataSource: PreprocessorsDataSource; ngOnInit(): void { + this.setColumnLabels(PreprocessorsTableColumnLabel); this.tableColumns = this.getColumns(); this.dataSource = new PreprocessorsDataSource( this.cdr, @@ -54,13 +58,13 @@ export class PreprocessorsTableComponent getColumns(): HTTableColumn[] { const tableColumns = [ { - name: PreprocessorsTableColumnLabel.ID, + id: PreprocessorsTableCol.ID, dataKey: '_id', isSortable: true, export: async (preprocessor: Preprocessor) => preprocessor._id + '' }, { - name: PreprocessorsTableColumnLabel.NAME, + id: PreprocessorsTableCol.NAME, dataKey: 'name', isSortable: true, export: async (preprocessor: Preprocessor) => preprocessor.name @@ -100,19 +104,25 @@ export class PreprocessorsTableComponent this.exportService.toExcel( 'hashtopolis-preprocessors', this.tableColumns, - event.data + event.data, + PreprocessorsTableColumnLabel ); break; case ExportMenuAction.CSV: this.exportService.toCsv( 'hashtopolis-preprocessors', this.tableColumns, - event.data + event.data, + PreprocessorsTableColumnLabel ); break; case ExportMenuAction.COPY: this.exportService - .toClipboard(this.tableColumns, event.data) + .toClipboard( + this.tableColumns, + event.data, + PreprocessorsTableColumnLabel + ) .then(() => { this.snackBar.open( 'The selected rows are copied to the clipboard', diff --git a/src/app/core/_components/tables/preprocessors-table/preprocessors-table.constants.ts b/src/app/core/_components/tables/preprocessors-table/preprocessors-table.constants.ts index 8a064c90..64c1c09e 100644 --- a/src/app/core/_components/tables/preprocessors-table/preprocessors-table.constants.ts +++ b/src/app/core/_components/tables/preprocessors-table/preprocessors-table.constants.ts @@ -1,4 +1,9 @@ +export enum PreprocessorsTableCol { + ID, + NAME +} + export const PreprocessorsTableColumnLabel = { - ID: 'ID', - NAME: 'Name' + [PreprocessorsTableCol.ID]: 'ID', + [PreprocessorsTableCol.NAME]: 'Name' }; diff --git a/src/app/core/_components/tables/super-hashlists-table/super-hashlists-table.component.html b/src/app/core/_components/tables/super-hashlists-table/super-hashlists-table.component.html index 16b20eef..d577883e 100644 --- a/src/app/core/_components/tables/super-hashlists-table/super-hashlists-table.component.html +++ b/src/app/core/_components/tables/super-hashlists-table/super-hashlists-table.component.html @@ -2,6 +2,7 @@ #table name="superHashlistsTable" dataType="superhashlists" + [columnLabels]="columnLabels" [dataSource]="dataSource" [tableColumns]="tableColumns" [isSelectable]="true" diff --git a/src/app/core/_components/tables/super-hashlists-table/super-hashlists-table.component.ts b/src/app/core/_components/tables/super-hashlists-table/super-hashlists-table.component.ts index dc808283..4b46bead 100644 --- a/src/app/core/_components/tables/super-hashlists-table/super-hashlists-table.component.ts +++ b/src/app/core/_components/tables/super-hashlists-table/super-hashlists-table.component.ts @@ -1,10 +1,10 @@ /* eslint-disable @angular-eslint/component-selector */ import { Component, OnDestroy, OnInit } from '@angular/core'; +import { HTTableColumn, HTTableIcon } from '../ht-table/ht-table.models'; import { - HTTableColumn, - HTTableIcon, - HTTableRouterLink -} from '../ht-table/ht-table.models'; + SuperHashlistsTableCol, + SuperHashlistsTableColumnLabel +} from './super-hashlists-table.constants'; import { catchError, forkJoin } from 'rxjs'; import { ActionMenuEvent } from '../../menus/action-menu/action-menu.model'; @@ -17,7 +17,6 @@ import { Hashlist } from 'src/app/core/_models/hashlist.model'; import { RowActionMenuAction } from '../../menus/row-action-menu/row-action-menu.constants'; import { SERV } from 'src/app/core/_services/main.config'; import { SuperHashlistsDataSource } from 'src/app/core/_datasources/super-hashlists.datasource'; -import { SuperHashlistsTableColumnLabel } from './super-hashlists-table.constants'; import { TableDialogComponent } from '../table-dialog/table-dialog.component'; import { formatPercentage } from 'src/app/shared/utils/util'; @@ -34,6 +33,7 @@ export class SuperHashlistsTableComponent isArchived = false; ngOnInit(): void { + this.setColumnLabels(SuperHashlistsTableColumnLabel); this.tableColumns = this.getColumns(); this.dataSource = new SuperHashlistsDataSource( this.cdr, @@ -65,13 +65,13 @@ export class SuperHashlistsTableComponent getColumns(): HTTableColumn[] { const tableColumns = [ { - name: SuperHashlistsTableColumnLabel.ID, + id: SuperHashlistsTableCol.ID, dataKey: '_id', isSortable: true, export: async (superHashlist: Hashlist) => superHashlist._id + '' }, { - name: SuperHashlistsTableColumnLabel.NAME, + id: SuperHashlistsTableCol.NAME, dataKey: 'name', icons: (superHashlist: Hashlist) => this.renderSecretIcon(superHashlist), @@ -81,7 +81,7 @@ export class SuperHashlistsTableComponent export: async (superHashlist: Hashlist) => superHashlist.name }, { - name: SuperHashlistsTableColumnLabel.CRACKED, + id: SuperHashlistsTableCol.CRACKED, dataKey: 'cracked', icons: (superHashlist: Hashlist) => this.renderCrackedStatusIcon(superHashlist), @@ -92,14 +92,14 @@ export class SuperHashlistsTableComponent formatPercentage(superHashlist.cracked, superHashlist.hashCount) }, { - name: SuperHashlistsTableColumnLabel.HASHTYPE, + id: SuperHashlistsTableCol.HASHTYPE, dataKey: 'hashTypeDescription', isSortable: true, export: async (superHashlist: Hashlist) => superHashlist.hashTypeDescription }, { - name: SuperHashlistsTableColumnLabel.HASHLISTS, + id: SuperHashlistsTableCol.HASHLISTS, dataKey: 'hashlists', routerLink: (superHashlist: Hashlist) => this.renderHashlistLinks(superHashlist), @@ -176,19 +176,25 @@ export class SuperHashlistsTableComponent this.exportService.toExcel( 'hashtopolis-super-hashlists', this.tableColumns, - event.data + event.data, + SuperHashlistsTableColumnLabel ); break; case ExportMenuAction.CSV: this.exportService.toCsv( 'hashtopolis-super-hashlists', this.tableColumns, - event.data + event.data, + SuperHashlistsTableColumnLabel ); break; case ExportMenuAction.COPY: this.exportService - .toClipboard(this.tableColumns, event.data) + .toClipboard( + this.tableColumns, + event.data, + SuperHashlistsTableColumnLabel + ) .then(() => { this.snackBar.open( 'The selected rows are copied to the clipboard', diff --git a/src/app/core/_components/tables/super-hashlists-table/super-hashlists-table.constants.ts b/src/app/core/_components/tables/super-hashlists-table/super-hashlists-table.constants.ts index af48e214..daa71401 100644 --- a/src/app/core/_components/tables/super-hashlists-table/super-hashlists-table.constants.ts +++ b/src/app/core/_components/tables/super-hashlists-table/super-hashlists-table.constants.ts @@ -1,9 +1,19 @@ +export enum SuperHashlistsTableCol { + ID, + NAME, + STATUS, + HASHTYPE, + PRE_CRACKED, + HASHLISTS, + CRACKED +} + export const SuperHashlistsTableColumnLabel = { - ID: 'ID', - NAME: 'Name', - STATUS: 'Status', - HASHTYPE: 'Hash Type', - PRE_CRACKED: 'Pre-cracked', - HASHLISTS: 'Hashlists', - CRACKED: 'Cracked' + [SuperHashlistsTableCol.ID]: 'ID', + [SuperHashlistsTableCol.NAME]: 'Name', + [SuperHashlistsTableCol.STATUS]: 'Status', + [SuperHashlistsTableCol.HASHTYPE]: 'Hash Type', + [SuperHashlistsTableCol.PRE_CRACKED]: 'Pre-cracked', + [SuperHashlistsTableCol.HASHLISTS]: 'Hashlists', + [SuperHashlistsTableCol.CRACKED]: 'Cracked' }; diff --git a/src/app/core/_components/tables/tasks-table/tasks-table.component.html b/src/app/core/_components/tables/tasks-table/tasks-table.component.html index 2f5e2c5b..d8a64130 100644 --- a/src/app/core/_components/tables/tasks-table/tasks-table.component.html +++ b/src/app/core/_components/tables/tasks-table/tasks-table.component.html @@ -1,14 +1,18 @@ } = {}; ngOnInit(): void { + this.setColumnLabels(TaskTableColumnLabel); this.tableColumns = this.getColumns(); this.dataSource = new TasksDataSource(this.cdr, this.gs, this.uiService); this.dataSource.setColumns(this.tableColumns); @@ -57,71 +61,91 @@ export class TasksTableComponent } } - filter(item: Task, filterValue: string): boolean { - /* - if (item.taskName.toLowerCase().includes(filterValue) || - item.clientSignature.toLowerCase().includes(filterValue) || - item.devices.toLowerCase().includes(filterValue)) { - return true + filter(item: TaskWrapper, filterValue: string): boolean { + if (item.taskName.toLowerCase().includes(filterValue)) { + return true; } -*/ + return false; } getColumns(): HTTableColumn[] { const tableColumns = [ { - name: TaskTableColumnLabel.ID, + id: TaskTableCol.ID, dataKey: '_id', - isSortable: true + isSortable: true, + export: async (wrapper: TaskWrapper) => wrapper._id + '' }, { - name: TaskTableColumnLabel.NAME, + id: TaskTableCol.NAME, dataKey: 'taskName', routerLink: (wrapper: TaskWrapper) => this.renderTaskWrapperLink(wrapper), - isSortable: true + isSortable: true, + export: async (wrapper: TaskWrapper) => wrapper.taskName }, { - name: TaskTableColumnLabel.STATUS, + id: TaskTableCol.STATUS, dataKey: 'keyspaceProgress', async: (wrapper: TaskWrapper) => this.renderSpeed(wrapper), icons: (wrapper: TaskWrapper) => this.renderStatusIcons(wrapper), - isSortable: false + isSortable: false, + export: async (wrapper: TaskWrapper) => { + const status = await this.getTaskStatus(wrapper); + switch (status) { + case TaskStatus.RUNNING: + return 'Running'; + case TaskStatus.COMPLETED: + return 'Completed'; + case TaskStatus.IDLE: + return 'Idle'; + default: + return ''; + } + } }, { - name: TaskTableColumnLabel.HASHLISTS, // TODO: fix + id: TaskTableCol.HASHLISTS, dataKey: 'userId', routerLink: (wrapper: TaskWrapper) => this.renderHashlistLinks(wrapper), - isSortable: false + isSortable: false, + export: async (wrapper: TaskWrapper) => + wrapper.hashlists.map((h) => h.name).join(', ') }, { - name: TaskTableColumnLabel.DISPATCHED_SEARCHED, + id: TaskTableCol.DISPATCHED_SEARCHED, dataKey: 'clientSignature', async: (wrapper: TaskWrapper) => this.renderDispatchedSearched(wrapper), - isSortable: true + isSortable: true, + export: async (wrapper: TaskWrapper) => + this.getDispatchedSearchedString(wrapper) }, { - name: TaskTableColumnLabel.CRACKED, + id: TaskTableCol.CRACKED, dataKey: 'cracked', routerLink: (wrapper: TaskWrapper) => this.renderCrackedLink(wrapper), - isSortable: true + isSortable: true, + export: async (wrapper: TaskWrapper) => wrapper.cracked + '' }, { - name: TaskTableColumnLabel.AGENTS, + id: TaskTableCol.AGENTS, dataKey: 'agents', async: (wrapper: TaskWrapper) => this.renderAgents(wrapper), - isSortable: false + isSortable: false, + export: async (wrapper: TaskWrapper) => + (await this.getNumAgents(wrapper)) + '' }, { - name: TaskTableColumnLabel.ACCESS_GROUP, + id: TaskTableCol.ACCESS_GROUP, dataKey: 'accessGroupName', routerLink: (wrapper: TaskWrapper) => this.renderAccessGroupLink(wrapper), - isSortable: false + isSortable: true, + export: async (wrapper: TaskWrapper) => wrapper.accessGroupName }, { - name: TaskTableColumnLabel.PRIORITY, + id: TaskTableCol.PRIORITY, dataKey: 'priority', editable: (wrapper: TaskWrapper) => { return { @@ -130,10 +154,11 @@ export class TasksTableComponent action: TaskTableEditableAction.CHANGE_PRIORITY }; }, - isSortable: true + isSortable: true, + export: async (wrapper: TaskWrapper) => wrapper.priority + '' }, { - name: TaskTableColumnLabel.MAX_AGENTS, + id: TaskTableCol.MAX_AGENTS, dataKey: 'maxAgents', editable: (wrapper: TaskWrapper) => { return { @@ -142,28 +167,45 @@ export class TasksTableComponent action: TaskTableEditableAction.CHANGE_MAX_AGENTS }; }, - isSortable: true + isSortable: true, + export: async (wrapper: TaskWrapper) => wrapper.maxAgents + '' }, { - name: TaskTableColumnLabel.PREPROCESSOR, + id: TaskTableCol.PREPROCESSOR, dataKey: 'preprocessorId', render: (wrapper: TaskWrapper) => wrapper.taskType === 0 && wrapper.tasks[0].preprocessorId === 1 ? 'Prince' : '', - isSortable: true + isSortable: true, + export: async (wrapper: TaskWrapper) => + wrapper.taskType === 0 && wrapper.tasks[0].preprocessorId === 1 + ? 'Prince' + : '' }, { - name: TaskTableColumnLabel.IS_SMALL, + id: TaskTableCol.IS_SMALL, dataKey: 'isSmall', icons: (wrapper: TaskWrapper) => this.renderIsSmallIcon(wrapper), - isSortable: true + isSortable: true, + export: async (wrapper: TaskWrapper) => + wrapper.taskType === 0 + ? wrapper.tasks[0].isSmall + ? 'Yes' + : 'No' + : '' }, { - name: TaskTableColumnLabel.IS_CPU_TASK, + id: TaskTableCol.IS_CPU_TASK, dataKey: 'isCpuTask', icons: (wrapper: TaskWrapper) => this.renderIsCpuTaskIcon(wrapper), - isSortable: true + isSortable: true, + export: async (wrapper: TaskWrapper) => + wrapper.taskType === 0 + ? wrapper.tasks[0].isCpuTask + ? 'Yes' + : 'No' + : '' } ]; @@ -228,6 +270,41 @@ export class TasksTableComponent } } + exportActionClicked(event: ActionMenuEvent): void { + switch (event.menuItem.action) { + case ExportMenuAction.EXCEL: + this.exportService.toExcel( + 'hashtopolis-tasks', + this.tableColumns, + event.data, + TaskTableColumnLabel + ); + break; + case ExportMenuAction.CSV: + this.exportService.toCsv( + 'hashtopolis-tasks', + this.tableColumns, + event.data, + TaskTableColumnLabel + ); + break; + case ExportMenuAction.COPY: + this.exportService + .toClipboard( + this.tableColumns, + event.data, + TaskTableColumnLabel + ) + .then(() => { + this.snackBar.open( + 'The selected rows are copied to the clipboard', + 'Close' + ); + }); + break; + } + } + openDialog(data: DialogData) { const dialogRef = this.dialog.open(TableDialogComponent, { data: data, @@ -255,6 +332,40 @@ export class TasksTableComponent this.dataSource.setIsArchived(isArchived); } + private async getTaskStatus(wrapper: TaskWrapper): Promise { + if (wrapper.taskType === 0 && wrapper.tasks.length > 0) { + const cd: ChunkData = await this.getChunkData(wrapper); + const speed = cd.speed; + + if (speed > 0) { + return TaskStatus.RUNNING; + } else if ( + wrapper.tasks[0].keyspaceProgress >= wrapper.tasks[0].keyspace && + wrapper.tasks[0].keyspaceProgress > 0 + ) { + return TaskStatus.COMPLETED; + } else { + return TaskStatus.IDLE; + } + } + + return TaskStatus.INVALID; + } + + async getDispatchedSearchedString(wrapper: TaskWrapper): Promise { + if (wrapper.taskType === 0) { + const task: Task = wrapper.tasks[0]; + if (task.keyspace > 0) { + const cd: ChunkData = await this.getChunkData(wrapper); + const disp = (cd.dispatched * 100).toFixed(2); + const sear = (cd.searched * 100).toFixed(2); + + return `${disp}% / ${sear}%`; + } + } + return ''; + } + // --- Render functions --- @Cacheable(['_id', 'taskType']) @@ -295,31 +406,29 @@ export class TasksTableComponent @Cacheable(['_id', 'taskType', 'tasks']) async renderStatusIcons(wrapper: TaskWrapper): Promise { const icons: HTTableIcon[] = []; + const status = await this.getTaskStatus(wrapper); - if (wrapper.taskType === 0 && wrapper.tasks.length > 0) { - const cd: ChunkData = await this.getChunkData(wrapper); - const speed = cd.speed; - if (speed > 0) { + switch (status) { + case TaskStatus.RUNNING: icons.push({ name: 'radio_button_checked', cls: 'pulsing-progress', tooltip: 'In Progress' }); - } else if ( - wrapper.tasks[0].keyspaceProgress >= wrapper.tasks[0].keyspace && - wrapper.tasks[0].keyspaceProgress > 0 - ) { + break; + case TaskStatus.COMPLETED: icons.push({ name: 'check', tooltip: 'Completed' }); - } else { + break; + case TaskStatus.IDLE: icons.push({ name: 'radio_button_checked', tooltip: 'Idle', cls: 'text-primary' }); - } + break; } return icons; @@ -382,16 +491,7 @@ export class TasksTableComponent @Cacheable(['_id', 'taskType', 'tasks']) async renderDispatchedSearched(wrapper: TaskWrapper): Promise { - let html = ''; - if (wrapper.taskType === 0) { - const task: Task = wrapper.tasks[0]; - if (task.keyspace > 0) { - const cd: ChunkData = await this.getChunkData(wrapper); - const disp = (cd.dispatched * 100).toFixed(2); - const sear = (cd.searched * 100).toFixed(2); - html = `${disp}% / ${sear}%`; - } - } + const html = await this.getDispatchedSearchedString(wrapper); return this.sanitize(html); } @@ -411,14 +511,19 @@ export class TasksTableComponent return links; } - @Cacheable(['_id', 'taskType', 'tasks']) - async renderAgents(wrapper: TaskWrapper): Promise { - let html = ''; + async getNumAgents(wrapper: TaskWrapper): Promise { if (wrapper.taskType === 0) { const cd: ChunkData = await this.getChunkData(wrapper); - html = `${cd.agents.length}`; + return cd.agents.length; } - return this.sanitize(html); + + return 0; + } + + @Cacheable(['_id', 'taskType', 'tasks']) + async renderAgents(wrapper: TaskWrapper): Promise { + const numAgents = await this.getNumAgents(wrapper); + return this.sanitize(`${numAgents}`); } @Cacheable(['_id', 'taskType', 'tasks']) diff --git a/src/app/core/_components/tables/tasks-table/tasks-table.constants.ts b/src/app/core/_components/tables/tasks-table/tasks-table.constants.ts index 2a638158..00e84304 100644 --- a/src/app/core/_components/tables/tasks-table/tasks-table.constants.ts +++ b/src/app/core/_components/tables/tasks-table/tasks-table.constants.ts @@ -1,23 +1,43 @@ +export enum TaskTableCol { + ID, + NAME, + STATUS, + IS_SMALL, + IS_CPU_TASK, + PREPROCESSOR, + HASHLISTS, + DISPATCHED_SEARCHED, + CRACKED, + AGENTS, + PRIORITY, + MAX_AGENTS, + ACCESS_GROUP +} + export const TaskTableColumnLabel = { - ID: 'ID', - NAME: 'Name', - STATUS: 'Status', - ATTACK_CMD: 'Attack Command', - IS_SMALL: 'Small Task', - IS_CPU_TASK: 'CPU Task', - TASK_TYPE_1: 'Supertask', - PREPROCESSOR: 'Preprocessor', - HASHLISTS: 'Hashlists', - DISPATCHED_SEARCHED: 'Dispatched/Searched', - CRACKED: 'Cracked', - AGENTS: 'Agents', - SPEED: 'Speed', - PRIORITY: 'Task Priority', - MAX_AGENTS: 'Max Agents', - ACCESS_GROUP: 'Access Group' + [TaskTableCol.ID]: 'ID', + [TaskTableCol.NAME]: 'Name', + [TaskTableCol.STATUS]: 'Status', + [TaskTableCol.IS_SMALL]: 'Small Task', + [TaskTableCol.IS_CPU_TASK]: 'CPU Task', + [TaskTableCol.PREPROCESSOR]: 'Preprocessor', + [TaskTableCol.HASHLISTS]: 'Hashlists', + [TaskTableCol.DISPATCHED_SEARCHED]: 'Dispatched/Searched', + [TaskTableCol.CRACKED]: 'Cracked', + [TaskTableCol.AGENTS]: 'Agents', + [TaskTableCol.PRIORITY]: 'Task Priority', + [TaskTableCol.MAX_AGENTS]: 'Max Agents', + [TaskTableCol.ACCESS_GROUP]: 'Access Group' }; export const TaskTableEditableAction = { CHANGE_PRIORITY: 'change-priority', CHANGE_MAX_AGENTS: 'change-max-agents' }; + +export enum TaskStatus { + INVALID, + RUNNING, + COMPLETED, + IDLE +} diff --git a/src/app/core/_components/tables/users-table/users-table.component.html b/src/app/core/_components/tables/users-table/users-table.component.html index 95faebfa..3042e3f4 100644 --- a/src/app/core/_components/tables/users-table/users-table.component.html +++ b/src/app/core/_components/tables/users-table/users-table.component.html @@ -2,6 +2,7 @@ #table name="usersTable" dataType="users" + [columnLabels]="columnLabels" [dataSource]="dataSource" [tableColumns]="tableColumns" [isSelectable]="true" diff --git a/src/app/core/_components/tables/users-table/users-table.component.ts b/src/app/core/_components/tables/users-table/users-table.component.ts index a4d8f8c7..5d71f057 100644 --- a/src/app/core/_components/tables/users-table/users-table.component.ts +++ b/src/app/core/_components/tables/users-table/users-table.component.ts @@ -6,6 +6,7 @@ import { HTTableRouterLink } from '../ht-table/ht-table.models'; import { + UsersTableCol, UsersTableColumnLabel, UsersTableStatus } from './users-table.constants'; @@ -36,6 +37,7 @@ export class UsersTableComponent dataSource: UsersDataSource; ngOnInit(): void { + this.setColumnLabels(UsersTableColumnLabel); this.tableColumns = this.getColumns(); this.dataSource = new UsersDataSource(this.cdr, this.gs, this.uiService); this.dataSource.setColumns(this.tableColumns); @@ -62,20 +64,20 @@ export class UsersTableComponent getColumns(): HTTableColumn[] { const tableColumns = [ { - name: UsersTableColumnLabel.ID, + id: UsersTableCol.ID, dataKey: '_id', isSortable: true, export: async (user: User) => user._id + '' }, { - name: UsersTableColumnLabel.NAME, + id: UsersTableCol.NAME, dataKey: 'name', routerLink: (user: User) => this.renderUserLink(user), isSortable: true, export: async (user: User) => user.name }, { - name: UsersTableColumnLabel.REGISTERED, + id: UsersTableCol.REGISTERED, dataKey: 'registeredSince', render: (user: User) => formatUnixTimestamp(user.registeredSince, this.dateFormat), @@ -84,7 +86,7 @@ export class UsersTableComponent formatUnixTimestamp(user.registeredSince, this.dateFormat) }, { - name: UsersTableColumnLabel.LAST_LOGIN, + id: UsersTableCol.LAST_LOGIN, dataKey: 'lastLoginDate', render: (user: User) => user.lastLoginDate @@ -97,13 +99,13 @@ export class UsersTableComponent : 'Never' }, { - name: UsersTableColumnLabel.EMAIL, + id: UsersTableCol.EMAIL, dataKey: 'email', isSortable: true, export: async (user: User) => user.email }, { - name: UsersTableColumnLabel.STATUS, + id: UsersTableCol.STATUS, dataKey: 'isValid', icons: (user: User) => this.renderIsValidIcon(user), render: (user: User) => @@ -113,13 +115,13 @@ export class UsersTableComponent user.isValid ? UsersTableStatus.VALID : UsersTableStatus.INVALID }, { - name: UsersTableColumnLabel.SESSION, + id: UsersTableCol.SESSION, dataKey: 'sessionLifetime', isSortable: true, export: async (user: User) => user.sessionLifetime + '' }, { - name: UsersTableColumnLabel.PERM_GROUP, + id: UsersTableCol.PERM_GROUP, dataKey: 'globalPermissionGroupName', isSortable: true, export: async (user: User) => user.globalPermissionGroupName @@ -184,19 +186,25 @@ export class UsersTableComponent this.exportService.toExcel( 'hashtopolis-users', this.tableColumns, - event.data + event.data, + UsersTableColumnLabel ); break; case ExportMenuAction.CSV: this.exportService.toCsv( 'hashtopolis-users', this.tableColumns, - event.data + event.data, + UsersTableColumnLabel ); break; case ExportMenuAction.COPY: this.exportService - .toClipboard(this.tableColumns, event.data) + .toClipboard( + this.tableColumns, + event.data, + UsersTableColumnLabel + ) .then(() => { this.snackBar.open( 'The selected rows are copied to the clipboard', diff --git a/src/app/core/_components/tables/users-table/users-table.constants.ts b/src/app/core/_components/tables/users-table/users-table.constants.ts index 9d843a2e..a645e53e 100644 --- a/src/app/core/_components/tables/users-table/users-table.constants.ts +++ b/src/app/core/_components/tables/users-table/users-table.constants.ts @@ -1,12 +1,23 @@ +export enum UsersTableCol { + ID, + NAME, + REGISTERED, + LAST_LOGIN, + EMAIL, + STATUS, + SESSION, + PERM_GROUP +} + export const UsersTableColumnLabel = { - ID: 'ID', - NAME: 'Name', - REGISTERED: 'Registered', - LAST_LOGIN: 'Last Login', - EMAIL: 'Email', - STATUS: 'Status', - SESSION: 'Session Lifetime', - PERM_GROUP: 'Permission Group' + [UsersTableCol.ID]: 'ID', + [UsersTableCol.NAME]: 'Name', + [UsersTableCol.REGISTERED]: 'Registered', + [UsersTableCol.LAST_LOGIN]: 'Last Login', + [UsersTableCol.EMAIL]: 'Email', + [UsersTableCol.STATUS]: 'Status', + [UsersTableCol.SESSION]: 'Session Lifetime', + [UsersTableCol.PERM_GROUP]: 'Permission Group' }; export const UsersTableStatus = { diff --git a/src/app/core/_datasources/base.datasource.ts b/src/app/core/_datasources/base.datasource.ts index 9441084d..be67aa56 100644 --- a/src/app/core/_datasources/base.datasource.ts +++ b/src/app/core/_datasources/base.datasource.ts @@ -164,7 +164,7 @@ export abstract class BaseDataSource< const sortDirection = this.sort.direction; const data = this.dataSubject.value.slice(); const columnMapping = this.columns.find( - (mapping) => mapping.name === this.sort.active + (mapping) => mapping.id + '' === this.sort.active ); if (!columnMapping) { diff --git a/src/app/core/_datasources/tasks.datasource.ts b/src/app/core/_datasources/tasks.datasource.ts index 5f5ffad7..b65a575e 100644 --- a/src/app/core/_datasources/tasks.datasource.ts +++ b/src/app/core/_datasources/tasks.datasource.ts @@ -25,6 +25,7 @@ export class TasksDataSource extends BaseDataSource< this.loading = true; const startAt = this.currentPage * this.pageSize; + // @todo Implement hashlist filter in API const additionalFilter = this._hashlistId ? `;hashlistId=${this._hashlistId}` : ''; @@ -32,7 +33,7 @@ export class TasksDataSource extends BaseDataSource< maxResults: this.pageSize, startAt: startAt, expand: 'accessGroup,tasks', - filter: `isArchived=${this._isArchived}${additionalFilter}` + filter: `isArchived=${this._isArchived}` //${additionalFilter}` }; const wrappers$ = this.service.getAll(SERV.TASKS_WRAPPER, params); diff --git a/src/app/core/_models/config-ui.model.ts b/src/app/core/_models/config-ui.model.ts index 54e28a3b..daf97d70 100644 --- a/src/app/core/_models/config-ui.model.ts +++ b/src/app/core/_models/config-ui.model.ts @@ -1,22 +1,26 @@ -import { AgentBinariesTableColumnLabel } from '../_components/tables/agent-binaries-table/agent-binaries-table.constants'; -import { AgentsTableColumnLabel } from '../_components/tables/agents-table/agents-table.constants'; -import { ChunksTableColumnLabel } from '../_components/tables/chunks-table/chunks-table.constants'; -import { CrackersTableColumnLabel } from '../_components/tables/crackers-table/crackers-table.constants'; -import { CracksTableColumnLabel } from '../_components/tables/cracks-table/cracks-table.constants'; -import { FilesTableColumnLabel } from '../_components/tables/files-table/files-table.constants'; -import { HashlistsTableColumnLabel } from '../_components/tables/hashlists-table/hashlists-table.constants'; -import { HashtypesTableColumnLabel } from '../_components/tables/hashtypes-table/hashtypes-table.constants'; -import { HealthChecksTableColumnLabel } from '../_components/tables/health-checks-table/health-checks-table.constants'; -import { NotificationsTableColumnLabel } from '../_components/tables/notifications-table/notifications-table.constants'; -import { PermissionsTableColumnLabel } from '../_components/tables/permissions-table/permissions-table.constants'; -import { PreprocessorsTableColumnLabel } from '../_components/tables/preprocessors-table/preprocessors-table.constants'; -import { SuperHashlistsTableColumnLabel } from '../_components/tables/super-hashlists-table/super-hashlists-table.constants'; +import { AccessGroupsTableCol } from '../_components/tables/access-groups-table/access-groups-table.constants'; +import { AgentBinariesTableCol } from '../_components/tables/agent-binaries-table/agent-binaries-table.constants'; +import { AgentsTableCol } from '../_components/tables/agents-table/agents-table.constants'; +import { ChunksTableCol } from '../_components/tables/chunks-table/chunks-table.constants'; +import { CrackersTableCol } from '../_components/tables/crackers-table/crackers-table.constants'; +import { CracksTableCol } from '../_components/tables/cracks-table/cracks-table.constants'; +import { FilesTableCol } from '../_components/tables/files-table/files-table.constants'; +import { HashlistsTableCol } from '../_components/tables/hashlists-table/hashlists-table.constants'; +import { HashtypesTableCol } from '../_components/tables/hashtypes-table/hashtypes-table.constants'; +import { HealthChecksTableCol } from '../_components/tables/health-checks-table/health-checks-table.constants'; +import { LogsTableCol } from '../_components/tables/logs-table/logs-table.constants'; +import { NotificationsTableCol } from '../_components/tables/notifications-table/notifications-table.constants'; +import { PermissionsTableCol } from '../_components/tables/permissions-table/permissions-table.constants'; +import { PreprocessorsTableCol } from '../_components/tables/preprocessors-table/preprocessors-table.constants'; +import { SuperHashlistsTableCol } from '../_components/tables/super-hashlists-table/super-hashlists-table.constants'; +import { TaskTableCol } from '../_components/tables/tasks-table/tasks-table.constants'; +import { UsersTableCol } from '../_components/tables/users-table/users-table.constants'; export type Layout = 'full' | 'fixed'; export type Theme = 'light' | 'dark'; export interface TableSettings { - [key: string]: string[]; + [key: string]: number[]; } export interface UIConfig { @@ -34,112 +38,141 @@ export const uiConfigDefault: UIConfig = { timefmt: 'dd/MM/yyyy h:mm:ss', tableSettings: { notificationsTable: [ - NotificationsTableColumnLabel.ID, - NotificationsTableColumnLabel.STATUS, - NotificationsTableColumnLabel.APPLIED_TO, - NotificationsTableColumnLabel.ACTION, - NotificationsTableColumnLabel.NOTIFICATION, - NotificationsTableColumnLabel.RECEIVER + NotificationsTableCol.ID, + NotificationsTableCol.STATUS, + NotificationsTableCol.APPLIED_TO, + NotificationsTableCol.ACTION, + NotificationsTableCol.NOTIFICATION, + NotificationsTableCol.RECEIVER ], permissionsTable: [ - PermissionsTableColumnLabel.ID, - PermissionsTableColumnLabel.NAME, - PermissionsTableColumnLabel.MEMBERS + PermissionsTableCol.ID, + PermissionsTableCol.NAME, + PermissionsTableCol.MEMBERS ], cracksTable: [ - CracksTableColumnLabel.FOUND, - CracksTableColumnLabel.PLAINTEXT, - CracksTableColumnLabel.HASH, - CracksTableColumnLabel.AGENT, - CracksTableColumnLabel.TASK, - CracksTableColumnLabel.CHUNK, - CracksTableColumnLabel.TYPE + CracksTableCol.FOUND, + CracksTableCol.PLAINTEXT, + CracksTableCol.HASH, + CracksTableCol.AGENT, + CracksTableCol.TASK, + CracksTableCol.CHUNK, + CracksTableCol.TYPE ], agentsTable: [ - AgentsTableColumnLabel.ID, - AgentsTableColumnLabel.NAME, - AgentsTableColumnLabel.STATUS, - AgentsTableColumnLabel.TASK_SPEED, - AgentsTableColumnLabel.CURRENT_TASK, - AgentsTableColumnLabel.CLIENT, - AgentsTableColumnLabel.GPUS_CPUS, - AgentsTableColumnLabel.LAST_ACTIVITY + AgentsTableCol.ID, + AgentsTableCol.NAME, + AgentsTableCol.STATUS, + AgentsTableCol.CURRENT_TASK, + AgentsTableCol.CLIENT, + AgentsTableCol.GPUS_CPUS, + AgentsTableCol.LAST_ACTIVITY ], assignedAgentsTable: [ - AgentsTableColumnLabel.ID, - AgentsTableColumnLabel.NAME, - AgentsTableColumnLabel.STATUS, - AgentsTableColumnLabel.TASK_SPEED, - AgentsTableColumnLabel.LAST_ACTIVITY, - AgentsTableColumnLabel.TIME_SPENT, - AgentsTableColumnLabel.BENCHMARK, - AgentsTableColumnLabel.CRACKED, - AgentsTableColumnLabel.SEARCHED + AgentsTableCol.ID, + AgentsTableCol.NAME, + AgentsTableCol.STATUS, + AgentsTableCol.TASK_SPEED, + AgentsTableCol.LAST_ACTIVITY, + AgentsTableCol.TIME_SPENT, + AgentsTableCol.BENCHMARK, + AgentsTableCol.CRACKED, + AgentsTableCol.SEARCHED ], chunksTable: [ - ChunksTableColumnLabel.ID, - ChunksTableColumnLabel.PROGRESS, - ChunksTableColumnLabel.TASK, - ChunksTableColumnLabel.AGENT, - ChunksTableColumnLabel.DISPATCH_TIME, - ChunksTableColumnLabel.LAST_ACTIVITY, - ChunksTableColumnLabel.TIME_SPENT, - ChunksTableColumnLabel.STATE, - ChunksTableColumnLabel.CRACKED + ChunksTableCol.ID, + ChunksTableCol.PROGRESS, + ChunksTableCol.TASK, + ChunksTableCol.AGENT, + ChunksTableCol.DISPATCH_TIME, + ChunksTableCol.LAST_ACTIVITY, + ChunksTableCol.TIME_SPENT, + ChunksTableCol.STATE, + ChunksTableCol.CRACKED ], hashlistsTable: [ - HashlistsTableColumnLabel.ID, - HashlistsTableColumnLabel.NAME, - HashlistsTableColumnLabel.HASHTYPE, - HashlistsTableColumnLabel.FORMAT, - HashlistsTableColumnLabel.CRACKED, - HashlistsTableColumnLabel.HASH_COUNT + HashlistsTableCol.ID, + HashlistsTableCol.NAME, + HashlistsTableCol.HASHTYPE, + HashlistsTableCol.FORMAT, + HashlistsTableCol.CRACKED, + HashlistsTableCol.HASH_COUNT ], superHashlistsTable: [ - SuperHashlistsTableColumnLabel.ID, - SuperHashlistsTableColumnLabel.NAME, - SuperHashlistsTableColumnLabel.HASHTYPE, - SuperHashlistsTableColumnLabel.CRACKED, - SuperHashlistsTableColumnLabel.HASHLISTS + SuperHashlistsTableCol.ID, + SuperHashlistsTableCol.NAME, + SuperHashlistsTableCol.HASHTYPE, + SuperHashlistsTableCol.CRACKED, + SuperHashlistsTableCol.HASHLISTS ], hashtypesTable: [ - HashtypesTableColumnLabel.HASHTYPE, - HashtypesTableColumnLabel.DESCRIPTION, - HashtypesTableColumnLabel.SALTED, - HashtypesTableColumnLabel.SLOW_HASH + HashtypesTableCol.HASHTYPE, + HashtypesTableCol.DESCRIPTION, + HashtypesTableCol.SALTED, + HashtypesTableCol.SLOW_HASH ], filesTable: [ - FilesTableColumnLabel.ID, - FilesTableColumnLabel.NAME, - FilesTableColumnLabel.SIZE, - FilesTableColumnLabel.LINE_COUNT, - FilesTableColumnLabel.ACCESS_GROUP + FilesTableCol.ID, + FilesTableCol.NAME, + FilesTableCol.SIZE, + FilesTableCol.LINE_COUNT, + FilesTableCol.ACCESS_GROUP ], crackersTable: [ - CrackersTableColumnLabel.ID, - CrackersTableColumnLabel.NAME, - CrackersTableColumnLabel.VERSIONS - ], - preprocessorsTable: [ - PreprocessorsTableColumnLabel.ID, - PreprocessorsTableColumnLabel.NAME + CrackersTableCol.ID, + CrackersTableCol.NAME, + CrackersTableCol.VERSIONS ], + preprocessorsTable: [PreprocessorsTableCol.ID, PreprocessorsTableCol.NAME], agentBinariesTable: [ - AgentBinariesTableColumnLabel.ID, - AgentBinariesTableColumnLabel.FILENAME, - AgentBinariesTableColumnLabel.OS, - AgentBinariesTableColumnLabel.TYPE, - AgentBinariesTableColumnLabel.UPDATE_TRACK, - AgentBinariesTableColumnLabel.VERSION + AgentBinariesTableCol.ID, + AgentBinariesTableCol.FILENAME, + AgentBinariesTableCol.OS, + AgentBinariesTableCol.TYPE, + AgentBinariesTableCol.UPDATE_TRACK, + AgentBinariesTableCol.VERSION ], healthChecksTable: [ - HealthChecksTableColumnLabel.ID, - HealthChecksTableColumnLabel.CREATED, - HealthChecksTableColumnLabel.STATUS, - HealthChecksTableColumnLabel.TYPE + HealthChecksTableCol.ID, + HealthChecksTableCol.CREATED, + HealthChecksTableCol.STATUS, + HealthChecksTableCol.TYPE + ], + tasksTable: [ + TaskTableCol.ID, + TaskTableCol.NAME, + TaskTableCol.STATUS, + TaskTableCol.HASHLISTS, + TaskTableCol.PRIORITY, + TaskTableCol.AGENTS, + TaskTableCol.MAX_AGENTS, + TaskTableCol.DISPATCHED_SEARCHED, + TaskTableCol.CRACKED + ], + hashlistTasksTable: [ + TaskTableCol.ID, + TaskTableCol.NAME, + TaskTableCol.DISPATCHED_SEARCHED, + TaskTableCol.CRACKED + ], + usersTable: [ + UsersTableCol.ID, + UsersTableCol.NAME, + UsersTableCol.REGISTERED, + UsersTableCol.LAST_LOGIN, + UsersTableCol.EMAIL, + UsersTableCol.STATUS, + UsersTableCol.SESSION, + UsersTableCol.PERM_GROUP + ], + logsTable: [ + LogsTableCol.ID, + LogsTableCol.ISSUER, + LogsTableCol.LEVEL, + LogsTableCol.MESSAGE, + LogsTableCol.TIME ], - tasksTable: [], - hashlistTasksTable: [] + accessGroupsTable: [AccessGroupsTableCol.ID, AccessGroupsTableCol.NAME] }, refreshPage: false, refreshInterval: 10 diff --git a/src/app/core/_services/export/export.service.ts b/src/app/core/_services/export/export.service.ts index b8f73bb3..5945b6cb 100644 --- a/src/app/core/_services/export/export.service.ts +++ b/src/app/core/_services/export/export.service.ts @@ -1,32 +1,43 @@ +import { Clipboard } from '@angular/cdk/clipboard'; +import { ExcelColumn } from './export.model'; +import { ExportUtil } from './export.util'; +import { HTTableColumn } from '../../_components/tables/ht-table/ht-table.models'; import { Injectable } from '@angular/core'; import { Workbook } from 'exceljs'; import { unparse } from 'papaparse'; -import { ExportUtil } from './export.util'; -import { ExcelColumn } from './export.model'; -import { Clipboard } from '@angular/cdk/clipboard'; -import { HTTableColumn } from '../../_components/tables/ht-table/ht-table.models'; /** * Service for exporting data to Excel, CSV formats and to the clipboard. */ @Injectable({ - providedIn: 'root', + providedIn: 'root' }) export class ExportService { - constructor(private exportUtil: ExportUtil, private clipboard: Clipboard) { } + constructor( + private exportUtil: ExportUtil, + private clipboard: Clipboard + ) {} /** * Exports data to an Excel file and triggers a download. - * + * * @param fileName - The name of the Excel file without ext. * @param tableColumns - Columns configuration for the export. * @param rawData - The data to export. */ - async toExcel(fileName: string, tableColumns: HTTableColumn[], rawData: T[]): Promise { + async toExcel( + fileName: string, + tableColumns: HTTableColumn[], + rawData: T[], + columnLabels: { [key: number]: string } + ): Promise { try { const workbook = new Workbook(); const worksheet = workbook.addWorksheet('Sheet 1'); - const columns: ExcelColumn[] = this.exportUtil.toExcelColumns(tableColumns); + const columns: ExcelColumn[] = this.exportUtil.toExcelColumns( + tableColumns, + columnLabels + ); const data = await this.exportUtil.toExcelRows(tableColumns, rawData); @@ -44,14 +55,22 @@ export class ExportService { /** * Exports data to a CSV file and triggers a download. - * + * * @param fileName - The name of the CSV file without ext. * @param tableColumns - Columns configuration for the export. * @param rawData - The data to export. */ - async toCsv(fileName: string, tableColumns: HTTableColumn[], rawData: T[]): Promise { + async toCsv( + fileName: string, + tableColumns: HTTableColumn[], + rawData: T[], + columnLabels: { [key: number]: string } + ): Promise { try { - const columns: string[] = this.exportUtil.toCsvColumns(tableColumns); + const columns: string[] = this.exportUtil.toCsvColumns( + tableColumns, + columnLabels + ); const data = await this.exportUtil.toCsvRows(tableColumns, rawData); if (data && data.length) { @@ -65,16 +84,25 @@ export class ExportService { /** * Copies data to the clipboard. - * + * * @param tableColumns - Columns configuration for the export. * @param rawData - The data to export. */ - async toClipboard(tableColumns: HTTableColumn[], rawData: T[]): Promise { + async toClipboard( + tableColumns: HTTableColumn[], + rawData: T[], + columnLabels: { [key: number]: string } + ): Promise { try { - const columns: string[] = this.exportUtil.toCsvColumns(tableColumns); + const columns: string[] = this.exportUtil.toCsvColumns( + tableColumns, + columnLabels + ); const data = await this.exportUtil.toCsvRows(tableColumns, rawData); - const textToCopy = [columns, ...data].map((row: string[]) => row.join('\t')).join('\n'); + const textToCopy = [columns, ...data] + .map((row: string[]) => row.join('\t')) + .join('\n'); this.clipboard.copy(textToCopy); } catch (error) { @@ -83,7 +111,11 @@ export class ExportService { } private saveExcelFile(data: ArrayBuffer, fileName: string): void { - this.exportUtil.download(data, `${fileName}.xlsx`, 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); + this.exportUtil.download( + data, + `${fileName}.xlsx`, + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' + ); } private saveCsvFile(data: ArrayBuffer, fileName: string): void { diff --git a/src/app/core/_services/export/export.util.ts b/src/app/core/_services/export/export.util.ts index 5c5fa11a..5fecf99b 100644 --- a/src/app/core/_services/export/export.util.ts +++ b/src/app/core/_services/export/export.util.ts @@ -12,11 +12,14 @@ export class ExportUtil { * @param tableColumns The table columns. * @returns Excel columns. */ - toExcelColumns(tableColumns: HTTableColumn[]): ExcelColumn[] { + toExcelColumns( + tableColumns: HTTableColumn[], + columnLabels: { [key: number]: string } + ): ExcelColumn[] { return tableColumns.map((col: HTTableColumn) => { return { key: col.dataKey, - header: col.name + header: columnLabels[col.id] }; }); } @@ -27,8 +30,11 @@ export class ExportUtil { * @param tableColumns The table columns. * @returns CSV columns. */ - toCsvColumns(tableColumns: HTTableColumn[]): string[] { - return tableColumns.map((col: HTTableColumn) => col.name); + toCsvColumns( + tableColumns: HTTableColumn[], + columnLabels: { [key: number]: string } + ): string[] { + return tableColumns.map((col: HTTableColumn) => columnLabels[col.id]); } /** diff --git a/src/app/hashlists/edit-hashlist/edit-hashlist.component.html b/src/app/hashlists/edit-hashlist/edit-hashlist.component.html index b3fbb517..4f9f099f 100644 --- a/src/app/hashlists/edit-hashlist/edit-hashlist.component.html +++ b/src/app/hashlists/edit-hashlist/edit-hashlist.component.html @@ -122,67 +122,6 @@ - - - - - - - - - - - - - - - - - - - - - -
IDNameDispatchedSearchedCracked
- {{ t.taskId }} - - {{ t.taskName }} - {{ t.taskId | tdispatched:t.keyspace | async | percent:'1.2-2'}}{{ t.taskId | tdsearched:t.keyspace | async | percent:'1.2-2'}} - {{ t.taskId | tdcracked | async }} -
-
- - - - - - - - - - - - - - - - - - -
IDNameCracked
{{ h.hashlistId }}{{ h.name }} -
- - {{ h.cracked / h.hashCount | percent:'1.2-2' }} - - ( - {{ h.cracked }} - / - {{ h.hashCount }} - ) -
-
+
diff --git a/src/app/tasks/edit-tasks/edit-tasks.component.html b/src/app/tasks/edit-tasks/edit-tasks.component.html index d7ba76f2..a6492c61 100644 --- a/src/app/tasks/edit-tasks/edit-tasks.component.html +++ b/src/app/tasks/edit-tasks/edit-tasks.component.html @@ -383,7 +383,11 @@
Cracker Information
- + Cracker Information

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
IDStatusNameAgent StatusBenchmarkSpeedKeyspace searchedTime spentCrackedLast activityAction
{{ a.assignmentId }} - - - - {{ a.agentName | shortenString: 30 }} - - - - - Active - - - Inactive - - - {{ - a.benchmark | shortenString: 20 - }} - - - {{ a.agentId | aspeed: true | async | fileSize: false : 1000 }} H/s - - {{ - a.agentId | tdsearched: tkeyspace : true | async | percent: '1.2-2' - }} - - {{ a.agentId | ttimespent: false : true | async | sectotime }} - - {{ a.agentId | tdcracked | async }} - {{ a.lastTime | uiDate }} - - - -
+
diff --git a/src/app/tasks/edit-tasks/edit-tasks.component.ts b/src/app/tasks/edit-tasks/edit-tasks.component.ts index 4f14020a..dd598c8f 100644 --- a/src/app/tasks/edit-tasks/edit-tasks.component.ts +++ b/src/app/tasks/edit-tasks/edit-tasks.component.ts @@ -1,6 +1,28 @@ -import { TitleComponent, TitleComponentOption, ToolboxComponent, ToolboxComponentOption, TooltipComponent, TooltipComponentOption, GridComponent, GridComponentOption, VisualMapComponent, VisualMapComponentOption, DataZoomComponent, DataZoomComponentOption, MarkLineComponent, MarkLineComponentOption } from 'echarts/components'; -import { faHomeAlt, faEye, faEraser, faLock, faTrash, faPencil } from '@fortawesome/free-solid-svg-icons'; -import { Component, OnInit, HostListener, ViewChild } from '@angular/core'; +import { + DataZoomComponent, + DataZoomComponentOption, + GridComponent, + GridComponentOption, + MarkLineComponent, + MarkLineComponentOption, + TitleComponent, + TitleComponentOption, + ToolboxComponent, + ToolboxComponentOption, + TooltipComponent, + TooltipComponentOption, + VisualMapComponent, + VisualMapComponentOption +} from 'echarts/components'; +import { + faEraser, + faEye, + faHomeAlt, + faLock, + faPencil, + faTrash +} from '@fortawesome/free-solid-svg-icons'; +import { Component, HostListener, OnInit, ViewChild } from '@angular/core'; import { environment } from './../../../environments/environment'; import { ActivatedRoute, Params, Router } from '@angular/router'; import { LineChart, LineSeriesOption } from 'echarts/charts'; @@ -27,34 +49,33 @@ import { SERV } from '../../core/_services/main.config'; providers: [FileSizePipe] }) @PageTitle(['Edit Task']) -export class EditTasksComponent implements OnInit,PendingChangesGuard { - +export class EditTasksComponent implements OnInit, PendingChangesGuard { editMode = false; editedTaskIndex: number; taskWrapperId: number; - editedTask: any // Change to Model + editedTask: any; // Change to Model - faPencil=faPencil; - faEraser=faEraser; - faHome=faHomeAlt; - faTrash=faTrash; - faLock=faLock; - faEye=faEye; + faPencil = faPencil; + faEraser = faEraser; + faHome = faHomeAlt; + faTrash = faTrash; + faLock = faLock; + faEye = faEye; private maxResults = environment.config.prodApiMaxResults; constructor( - private uiService:UIConfigService, + private uiService: UIConfigService, private route: ActivatedRoute, private alert: AlertService, private gs: GlobalService, - private fs:FileSizePipe, + private fs: FileSizePipe, private router: Router - ) { } + ) {} updateForm: FormGroup; createForm: FormGroup; // Assign Agent - colorpicker=colorpicker; + colorpicker = colorpicker; color = ''; @ViewChild(DataTableDirective) @@ -65,176 +86,211 @@ export class EditTasksComponent implements OnInit,PendingChangesGuard { dtOptions: any = {}; dtOptions1: any = {}; tusepreprocessor: any; - hashlistDescrip:any; - hashlistinform:any; + hashlistDescrip: any; + hashlistinform: any; assigAgents: any; - availAgents:any; - crackerinfo:any; + availAgents: any; + crackerinfo: any; tkeyspace: any; getchunks: any; getFiles: any; ngOnInit() { - - this.route.params - .subscribe( - (params: Params) => { - this.editedTaskIndex = +params['id']; - this.editMode = params['id'] != null; - this.initForm(); - this.assignChunksInit(this.editedTaskIndex); - } - ); + this.route.params.subscribe((params: Params) => { + this.editedTaskIndex = +params['id']; + this.editMode = params['id'] != null; + this.initForm(); + this.assignChunksInit(this.editedTaskIndex); + }); this.updateForm = new FormGroup({ - 'taskId': new FormControl({value: '', disabled: true}), - 'forcePipe': new FormControl({value: '', disabled: true}), - 'skipKeyspace': new FormControl({value: '', disabled: true}), - 'keyspace': new FormControl({value: '', disabled: true}), - 'keyspaceProgress': new FormControl({value: '', disabled: true}), - 'crackerBinaryId': new FormControl({value: '', disabled: true}), - 'chunkSize': new FormControl({value: '', disabled: true}), - 'updateData': new FormGroup({ - 'taskName': new FormControl(''), - 'attackCmd': new FormControl(''), - 'notes': new FormControl(''), - 'color': new FormControl(''), - 'chunkTime': new FormControl(''), - 'statusTimer': new FormControl(''), - 'priority': new FormControl(''), - 'maxAgents': new FormControl(''), - 'isCpuTask': new FormControl(''), - 'isSmall': new FormControl(''), - }), + taskId: new FormControl({ value: '', disabled: true }), + forcePipe: new FormControl({ value: '', disabled: true }), + skipKeyspace: new FormControl({ value: '', disabled: true }), + keyspace: new FormControl({ value: '', disabled: true }), + keyspaceProgress: new FormControl({ value: '', disabled: true }), + crackerBinaryId: new FormControl({ value: '', disabled: true }), + chunkSize: new FormControl({ value: '', disabled: true }), + updateData: new FormGroup({ + taskName: new FormControl(''), + attackCmd: new FormControl(''), + notes: new FormControl(''), + color: new FormControl(''), + chunkTime: new FormControl(''), + statusTimer: new FormControl(''), + priority: new FormControl(''), + maxAgents: new FormControl(''), + isCpuTask: new FormControl(''), + isSmall: new FormControl('') + }) }); this.createForm = new FormGroup({ - 'agentId': new FormControl(), + agentId: new FormControl() }); - } - OnChangeValue(value){ + OnChangeValue(value) { this.updateForm.patchValue({ - updateData:{color: value} + updateData: { color: value } }); } - onSubmit(){ + onSubmit() { if (this.updateForm.valid) { - this.gs.update(SERV.TASKS,this.editedTaskIndex,this.updateForm.value['updateData']).subscribe(() => { - this.alert.okAlert('Task saved!',''); + this.gs + .update( + SERV.TASKS, + this.editedTaskIndex, + this.updateForm.value['updateData'] + ) + .subscribe(() => { + this.alert.okAlert('Task saved!', ''); this.updateForm.reset(); // success, we reset form this.router.navigate(['tasks/show-tasks']); - } - ); + }); } } private initForm() { if (this.editMode) { - this.gs.get(SERV.TASKS,this.editedTaskIndex, {'expand': 'hashlist,speeds,crackerBinary,crackerBinaryType,files'}).subscribe((result)=>{ - this.color = result['color']; - this.getFiles = result.files; - this.crackerinfo = result.crackerBinary; - this.taskWrapperId - result.taskWrapperId; - // Graph Speed - this.initTaskSpeed(result.speeds); - // Assigned Agents init - this.assingAgentInit(); - // Hashlist Description and Type - this.hashlistinform = result.hashlist[0]; - this.gs.getAll(SERV.HASHTYPES,{'filter': 'hashTypeId='+result.hashlist[0]['hashTypeId']+''}).subscribe((htypes: any) => { - this.hashlistDescrip = htypes.values[0].description; - }) - this.tkeyspace = result['keyspace']; - this.tusepreprocessor = result['preprocessorId']; - this.updateForm = new FormGroup({ - 'taskId': new FormControl(result['taskId']), - 'forcePipe': new FormControl({value: result['forcePipe']== true? 'Yes':'No', disabled: true}), - 'skipKeyspace': new FormControl({value: result['skipKeyspace'] > 0?result['skipKeyspace']:'N/A', disabled: true}), - 'keyspace': new FormControl({value: result['keyspace'], disabled: true}), - 'keyspaceProgress': new FormControl({value: result['keyspaceProgress'], disabled: true}), - 'crackerBinaryId': new FormControl(result['crackerBinaryId']), - 'chunkSize': new FormControl({value: result['chunkSize'], disabled: true}), - 'updateData': new FormGroup({ - 'taskName': new FormControl(result['taskName']), - 'attackCmd': new FormControl(result['attackCmd']), - 'notes': new FormControl(result['notes']), - 'color': new FormControl(result['color']), - 'chunkTime': new FormControl(Number(result['chunkTime'])), - 'statusTimer': new FormControl(result['statusTimer']), - 'priority': new FormControl(result['priority']), - 'maxAgents': new FormControl(result['maxAgents']), - 'isCpuTask': new FormControl(result['isCpuTask']), - 'isSmall': new FormControl(result['isSmall']), - }), - }); - }); - } + this.gs + .get(SERV.TASKS, this.editedTaskIndex, { + expand: 'hashlist,speeds,crackerBinary,crackerBinaryType,files' + }) + .subscribe((result) => { + this.color = result['color']; + this.getFiles = result.files; + this.crackerinfo = result.crackerBinary; + this.taskWrapperId - result.taskWrapperId; + // Graph Speed + this.initTaskSpeed(result.speeds); + // Assigned Agents init + this.assingAgentInit(); + // Hashlist Description and Type + this.hashlistinform = result.hashlist[0]; + this.gs + .getAll(SERV.HASHTYPES, { + filter: 'hashTypeId=' + result.hashlist[0]['hashTypeId'] + '' + }) + .subscribe((htypes: any) => { + this.hashlistDescrip = htypes.values[0].description; + }); + this.tkeyspace = result['keyspace']; + this.tusepreprocessor = result['preprocessorId']; + this.updateForm = new FormGroup({ + taskId: new FormControl(result['taskId']), + forcePipe: new FormControl({ + value: result['forcePipe'] == true ? 'Yes' : 'No', + disabled: true + }), + skipKeyspace: new FormControl({ + value: + result['skipKeyspace'] > 0 ? result['skipKeyspace'] : 'N/A', + disabled: true + }), + keyspace: new FormControl({ + value: result['keyspace'], + disabled: true + }), + keyspaceProgress: new FormControl({ + value: result['keyspaceProgress'], + disabled: true + }), + crackerBinaryId: new FormControl(result['crackerBinaryId']), + chunkSize: new FormControl({ + value: result['chunkSize'], + disabled: true + }), + updateData: new FormGroup({ + taskName: new FormControl(result['taskName']), + attackCmd: new FormControl(result['attackCmd']), + notes: new FormControl(result['notes']), + color: new FormControl(result['color']), + chunkTime: new FormControl(Number(result['chunkTime'])), + statusTimer: new FormControl(result['statusTimer']), + priority: new FormControl(result['priority']), + maxAgents: new FormControl(result['maxAgents']), + isCpuTask: new FormControl(result['isCpuTask']), + isSmall: new FormControl(result['isSmall']) + }) + }); + }); + } } -/** - * The below functions are related with assign, manage and delete agents - * -**/ - assingAgentInit(){ - this.gs.getAll(SERV.AGENT_ASSIGN).subscribe((res)=>{ - this.gs.getAll(SERV.AGENTS,{'maxResults': this.maxResults}).subscribe((agents)=>{ - this.availAgents = this.getAvalAgents(res.values,agents.values); - this.assigAgents = res.values.map(mainObject => { - const matchObject = agents.values.find(element => element.agentId === mainObject.agentId) - return { ...mainObject, ...matchObject } - }) - this.dtTrigger1.next(void 0); - }); + /** + * The below functions are related with assign, manage and delete agents + * + **/ + assingAgentInit() { + this.gs.getAll(SERV.AGENT_ASSIGN).subscribe((res) => { + this.gs + .getAll(SERV.AGENTS, { maxResults: this.maxResults }) + .subscribe((agents) => { + this.availAgents = this.getAvalAgents(res.values, agents.values); + this.assigAgents = res.values.map((mainObject) => { + const matchObject = agents.values.find( + (element) => element.agentId === mainObject.agentId + ); + return { ...mainObject, ...matchObject }; + }); + this.dtTrigger1.next(void 0); + }); }); this.dtOptions1 = { dom: 'Bfrtip', - scrollY: "700px", + scrollY: '700px', scrollCollapse: true, paging: false, destroy: true, searching: false, bInfo: false, - buttons:[] - } + buttons: [] + }; } - getAvalAgents(assing: any, agents: any){ - - return agents.filter(u => assing.findIndex(lu => lu.agentId === u.agentId) === -1); + getAvalAgents(assing: any, agents: any) { + return agents.filter( + (u) => assing.findIndex((lu) => lu.agentId === u.agentId) === -1 + ); + } + assignAgents() { + console.log('Assign agents'); } - asignAgents(){ + asignAgents() { if (this.createForm.valid) { - const payload = {"taskId": this.editedTaskIndex, "agentId":this.createForm.value['agentId']}; - this.gs.create(SERV.AGENT_ASSIGN,payload).subscribe(() => { - this.alert.okAlert('Agent assigned!',''); - this.rerender(); // rerender datatables - this.ngOnInit(); - } - ); + const payload = { + taskId: this.editedTaskIndex, + agentId: this.createForm.value['agentId'] + }; + this.gs.create(SERV.AGENT_ASSIGN, payload).subscribe(() => { + this.alert.okAlert('Agent assigned!', ''); + this.rerender(); // rerender datatables + this.ngOnInit(); + }); } } - onDelete(id: number){ - this.gs.delete(SERV.AGENT_ASSIGN,id).subscribe(() => { - this.alert.okAlert('Deleted',''); - this.rerender(); // rerender datatables + onDelete(id: number) { + this.gs.delete(SERV.AGENT_ASSIGN, id).subscribe(() => { + this.alert.okAlert('Deleted', ''); + this.rerender(); // rerender datatables this.ngOnInit(); }); } - onModalUpdate(title: string, id: number, cvalue: any, nameref: string ){ + onModalUpdate(title: string, id: number, cvalue: any, nameref: string) { (async () => { - const { value: formValues } = await Swal.fire({ - title: title + ' - '+ nameref, + title: title + ' - ' + nameref, html: - '', + '', focusConfirm: false, showCancelButton: true, cancelButtonColor: this.alert.cancelButtonColor, @@ -242,46 +298,47 @@ export class EditTasksComponent implements OnInit,PendingChangesGuard { cancelButton: true, preConfirm: () => { return [ - (document.getElementById('project-input')).value, - ] + (document.getElementById('project-input')).value + ]; } - }) + }); if (formValues) { - if(cvalue !== Number(formValues[0])){ - this.gs.update(SERV.AGENT_ASSIGN,id, {benchmark: +formValues}).subscribe(() => { - this.alert.okAlert('Task saved!',''); - this.ngOnInit(); - this.rerender(); // rerender datatables - }); + if (cvalue !== Number(formValues[0])) { + this.gs + .update(SERV.AGENT_ASSIGN, id, { benchmark: +formValues }) + .subscribe(() => { + this.alert.okAlert('Task saved!', ''); + this.ngOnInit(); + this.rerender(); // rerender datatables + }); } } - - })() + })(); } -/** - * This function calculates Keyspace searched, Time Spent and Estimated Time - * -**/ + /** + * This function calculates Keyspace searched, Time Spent and Estimated Time + * + **/ // Keyspace searched cprogress: any; // Time Spent ctimespent: any; - timeCalc(chunks){ - const cprogress = []; - const timespent = []; - const current = 0; - for(let i=0; i < chunks.length; i++){ - cprogress.push(chunks[i].checkpoint - chunks[i].skip); - if(chunks[i].dispatchTime > current){ - timespent.push(chunks[i].solveTime - chunks[i].dispatchTime); - } else if (chunks[i].solveTime > current) { - timespent.push(chunks[i].solveTime- current); - } + timeCalc(chunks) { + const cprogress = []; + const timespent = []; + const current = 0; + for (let i = 0; i < chunks.length; i++) { + cprogress.push(chunks[i].checkpoint - chunks[i].skip); + if (chunks[i].dispatchTime > current) { + timespent.push(chunks[i].solveTime - chunks[i].dispatchTime); + } else if (chunks[i].solveTime > current) { + timespent.push(chunks[i].solveTime - current); } - this.cprogress = cprogress.reduce((a, i) => a + i); - this.ctimespent = timespent.reduce((a, i) => a + i); + } + this.cprogress = cprogress.reduce((a, i) => a + i); + this.ctimespent = timespent.reduce((a, i) => a + i); } // Chunk View @@ -292,28 +349,26 @@ export class EditTasksComponent implements OnInit,PendingChangesGuard { chunkresults: Object; activechunks: Object; - assignChunksInit(id: number){ - this.route.data.subscribe(data => { + assignChunksInit(id: number) { + this.route.data.subscribe((data) => { switch (data['kind']) { - case 'edit-task': this.chunkview = 0; this.chunktitle = 'Live Chunks'; this.chunkresults = this.maxResults; - break; + break; case 'edit-task-c100': this.chunkview = 1; this.chunktitle = 'Latest 100 Chunks'; this.chunkresults = 100; - break; + break; case 'edit-task-cAll': this.chunkview = 2; this.chunktitle = 'All Chunks'; this.chunkresults = 60000; - break; - + break; } }); @@ -323,99 +378,134 @@ export class EditTasksComponent implements OnInit,PendingChangesGuard { scrollX: true, pageLength: 25, lengthMenu: [ - [10, 25, 50, 100, 250, -1], - [10, 25, 50, 100, 250, 'All'] + [10, 25, 50, 100, 250, -1], + [10, 25, 50, 100, 250, 'All'] ], - scrollY: "700px", + scrollY: '700px', scrollCollapse: true, paging: false, destroy: true, buttons: { - dom: { - button: { - className: 'dt-button buttons-collection btn btn-sm-dt btn-outline-gray-600-dt', - } - }, - buttons:[ - { - text: '↻', - autoClose: true, - action: function (e, dt, node, config) { - self.onRefresh(); - } - }, - { - text: self.chunkview === 0 ? 'Show Latest 100':'Show Live', - action: function () { - if(self.chunkview === 0) { - self.router.navigate(['/tasks/show-tasks',id,'edit','show-100-chunks']); - } - if(self.chunkview === 1) { - self.router.navigate(['/tasks/show-tasks',id,'edit']); - } - if(self.chunkview === 2) { - self.router.navigate(['/tasks/show-tasks',id,'edit']); - } + dom: { + button: { + className: + 'dt-button buttons-collection btn btn-sm-dt btn-outline-gray-600-dt' } }, - { - text: self.chunkview === 0 ? 'Show All':'Show Latest 100', - action: function () { - if(self.chunkview === 0) { - console.log(id) - self.router.navigate(['/tasks/show-tasks',id,'edit','show-all-chunks']); + buttons: [ + { + text: '↻', + autoClose: true, + action: function (e, dt, node, config) { + self.onRefresh(); } - if(self.chunkview === 1) { - self.router.navigate(['/tasks/show-tasks',id,'edit','show-all-chunks']); + }, + { + text: self.chunkview === 0 ? 'Show Latest 100' : 'Show Live', + action: function () { + if (self.chunkview === 0) { + self.router.navigate([ + '/tasks/show-tasks', + id, + 'edit', + 'show-100-chunks' + ]); + } + if (self.chunkview === 1) { + self.router.navigate(['/tasks/show-tasks', id, 'edit']); + } + if (self.chunkview === 2) { + self.router.navigate(['/tasks/show-tasks', id, 'edit']); + } } - if(self.chunkview === 2) { - self.router.navigate(['/tasks/show-tasks',id,'edit','show-100-chunks']); + }, + { + text: self.chunkview === 0 ? 'Show All' : 'Show Latest 100', + action: function () { + if (self.chunkview === 0) { + console.log(id); + self.router.navigate([ + '/tasks/show-tasks', + id, + 'edit', + 'show-all-chunks' + ]); + } + if (self.chunkview === 1) { + self.router.navigate([ + '/tasks/show-tasks', + id, + 'edit', + 'show-all-chunks' + ]); + } + if (self.chunkview === 2) { + self.router.navigate([ + '/tasks/show-tasks', + id, + 'edit', + 'show-100-chunks' + ]); + } } + }, + { + extend: 'colvis', + text: 'Column View', + columns: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] } - }, - { - extend: 'colvis', - text: 'Column View', - columns: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], - } - ] + ] } - } + }; - const params = {'maxResults': this.chunkresults}; - this.gs.getAll(SERV.CHUNKS,{'maxResults': this.chunkresults, 'filter': 'taskId='+id+''}).subscribe((result: any)=>{ - this.timeCalc(result.values); - // this.initVisualGraph(result.values, 150, 150); // Get data for visual graph - this.gs.getAll(SERV.AGENTS,params).subscribe((agents: any) => { - this.getchunks = result.values.map(mainObject => { - const matchObject = agents.values.find(element => element.agentId === mainObject.agentId) - return { ...mainObject, ...matchObject } - }) - if(this.chunkview == 0){ - const chunktime = this.uiService.getUIsettings('chunktime').value; - const resultArray = []; - const cspeed = []; - for(let i=0; i < this.getchunks.length; i++){ - if(Date.now()/1000 - Math.max(this.getchunks[i].solveTime, this.getchunks[i].dispatchTime) < chunktime && this.getchunks[i].progress < 10000){ - this.isactive = 1; - cspeed.push(this.getchunks[i].speed); - resultArray.push(this.getchunks[i]); + const params = { maxResults: this.chunkresults }; + this.gs + .getAll(SERV.CHUNKS, { + maxResults: this.chunkresults, + filter: 'taskId=' + id + '' + }) + .subscribe((result: any) => { + this.timeCalc(result.values); + // this.initVisualGraph(result.values, 150, 150); // Get data for visual graph + this.gs.getAll(SERV.AGENTS, params).subscribe((agents: any) => { + this.getchunks = result.values.map((mainObject) => { + const matchObject = agents.values.find( + (element) => element.agentId === mainObject.agentId + ); + return { ...mainObject, ...matchObject }; + }); + if (this.chunkview == 0) { + const chunktime = this.uiService.getUIsettings('chunktime').value; + const resultArray = []; + const cspeed = []; + for (let i = 0; i < this.getchunks.length; i++) { + if ( + Date.now() / 1000 - + Math.max( + this.getchunks[i].solveTime, + this.getchunks[i].dispatchTime + ) < + chunktime && + this.getchunks[i].progress < 10000 + ) { + this.isactive = 1; + cspeed.push(this.getchunks[i].speed); + resultArray.push(this.getchunks[i]); + } + } + if (cspeed.length > 0) { + this.currenspeed = cspeed.reduce((a, i) => a + i); + } + this.getchunks = resultArray; } - } - if(cspeed.length > 0){ - this.currenspeed = cspeed.reduce((a, i) => a + i); - } - this.getchunks = resultArray; - } - this.dtTrigger.next(void 0); + this.dtTrigger.next(void 0); + }); }); - }); - } - onRefresh(){ + onRefresh() { this.ngOnInit(); - this.rerender(); // rerender datatables + this.rerender(); // rerender datatables } rerender(): void { @@ -429,128 +519,130 @@ export class EditTasksComponent implements OnInit,PendingChangesGuard { }); } -/** - * Helper functions - * -**/ + /** + * Helper functions + * + **/ - purgeTask(){ + purgeTask() { const swalWithBootstrapButtons = Swal.mixin({ customClass: { confirmButton: 'btn', cancelButton: 'btn' }, buttonsStyling: false - }) + }); Swal.fire({ - title: "Are you sure?", + title: 'Are you sure?', text: "It'll purge the Task!", - icon: "warning", + icon: 'warning', reverseButtons: true, showCancelButton: true, cancelButtonColor: this.alert.cancelButtonColor, confirmButtonColor: this.alert.confirmButtonColor, confirmButtonText: this.alert.delconfirmText - }) - .then((result) => { + }).then((result) => { if (result.isConfirmed) { - let payload = {"taskId":this.editedTaskIndex}; - this.gs.chelper(SERV.HELPER,'purgeTask',payload).subscribe(() => { - this.alert.okAlert('Deleted '+name+'',''); + const payload = { taskId: this.editedTaskIndex }; + this.gs.chelper(SERV.HELPER, 'purgeTask', payload).subscribe(() => { + this.alert.okAlert('Deleted ' + name + '', ''); this.ngOnInit(); - this.rerender(); // rerender datatables + this.rerender(); // rerender datatables }); } else { swalWithBootstrapButtons.fire({ - title: "Cancelled", - text: "Your Task is safe!", - icon: "error", + title: 'Cancelled', + text: 'Your Task is safe!', + icon: 'error', showConfirmButton: false, timer: 1500 - }) + }); } }); - } - onReset(id: number, state: number){ - const path = state === 2 ? 'abortChunk' :'resetChunk' ; - const title = state === 2 ? 'Chunk Abort!' :'Chunk Reset!' ; - let payload = {'chunkId': id}; - this.gs.chelper(SERV.HELPER,path,payload).subscribe(() => { - this.alert.okAlert('Resetted!',''); + onReset(id: number, state: number) { + const path = state === 2 ? 'abortChunk' : 'resetChunk'; + const title = state === 2 ? 'Chunk Abort!' : 'Chunk Reset!'; + const payload = { chunkId: id }; + this.gs.chelper(SERV.HELPER, path, payload).subscribe(() => { + this.alert.okAlert('Resetted!', ''); this.ngOnInit(); this.rerender(); }); } -/** - * Task Speed Grap - * -**/ -initTaskSpeed(obj: object){ - - echarts.use([ - TitleComponent, - ToolboxComponent, - TooltipComponent, - GridComponent, - VisualMapComponent, - DataZoomComponent, - MarkLineComponent, - LineChart, - CanvasRenderer, - UniversalTransition - ]); - - type EChartsOption = echarts.ComposeOption< - | TitleComponentOption - | ToolboxComponentOption - | TooltipComponentOption - | GridComponentOption - | VisualMapComponentOption - | DataZoomComponentOption - | MarkLineComponentOption - | LineSeriesOption - >; - - const data:any = obj; - const arr = []; - const max = []; - const result = []; - - data.reduce(function(res, value) { - if (!res[value.time]) { - res[value.time] = { time: value.time, speed: 0 }; - result.push(res[value.time]) - } - res[value.time].speed += value.speed; - return res; - }, {}); - - for(let i=0; i < result.length; i++){ - - const iso = this.transDate(result[i]['time']); + /** + * Task Speed Grap + * + **/ + initTaskSpeed(obj: object) { + echarts.use([ + TitleComponent, + ToolboxComponent, + TooltipComponent, + GridComponent, + VisualMapComponent, + DataZoomComponent, + MarkLineComponent, + LineChart, + CanvasRenderer, + UniversalTransition + ]); + + type EChartsOption = echarts.ComposeOption< + | TitleComponentOption + | ToolboxComponentOption + | TooltipComponentOption + | GridComponentOption + | VisualMapComponentOption + | DataZoomComponentOption + | MarkLineComponentOption + | LineSeriesOption + >; + + const data: any = obj; + const arr = []; + const max = []; + const result = []; - arr.push([iso, this.fs.transform(result[i]['speed'],false,1000).match(/\d+(\.\d+)?/)[0], this.fs.transform(result[i]['speed'],false,1000).slice(-2)]); - max.push(result[i]['time']); - } + data.reduce(function (res, value) { + if (!res[value.time]) { + res[value.time] = { time: value.time, speed: 0 }; + result.push(res[value.time]); + } + res[value.time].speed += value.speed; + return res; + }, {}); + + for (let i = 0; i < result.length; i++) { + const iso = this.transDate(result[i]['time']); + + arr.push([ + iso, + this.fs + .transform(result[i]['speed'], false, 1000) + .match(/\d+(\.\d+)?/)[0], + this.fs.transform(result[i]['speed'], false, 1000).slice(-2) + ]); + max.push(result[i]['time']); + } - const startdate = max.slice(0)[0]; - const enddate = max.slice(-1)[0]; - console.log(enddate); - const datelabel = this.transDate(enddate); - const xAxis = this.generateIntervalsOf(1,+startdate,+enddate); + const startdate = max.slice(0)[0]; + const enddate = max.slice(-1)[0]; + console.log(enddate); + const datelabel = this.transDate(enddate); + const xAxis = this.generateIntervalsOf(1, +startdate, +enddate); - const chartDom = document.getElementById('tspeed'); - const myChart = echarts.init(chartDom); - let option: EChartsOption; + const chartDom = document.getElementById('tspeed'); + const myChart = echarts.init(chartDom); + let option: EChartsOption; - const self = this; + const self = this; - option = { + option = { title: { - subtext: 'Last record: '+ datelabel, + subtext: 'Last record: ' + datelabel }, tooltip: { position: 'top', @@ -560,7 +652,7 @@ initTaskSpeed(obj: object){ }, grid: { left: '5%', - right: '4%', + right: '4%' }, xAxis: { data: xAxis.map(function (item: any[] | any) { @@ -571,7 +663,7 @@ initTaskSpeed(obj: object){ type: 'value', name: 'H/s', position: 'left', - alignTicks: true, + alignTicks: true }, useUTC: true, toolbox: { @@ -584,7 +676,7 @@ initTaskSpeed(obj: object){ }, restore: {}, saveAsImage: { - name: "Task Speed" + name: 'Task Speed' } } }, @@ -600,41 +692,55 @@ initTaskSpeed(obj: object){ type: 'inside', start: 70, end: 100 - }, + } ], series: { name: '', type: 'line', data: arr, connectNulls: true, - markPoint: { - data: [ - { type: 'max', name: 'Max' }, - { type: 'min', name: 'Min' } - ] - }, + markPoint: { + data: [ + { type: 'max', name: 'Max' }, + { type: 'min', name: 'Min' } + ] + }, markLine: { lineStyle: { color: '#333' - }, - } + } } - }; - if(data.length > 0){ option && myChart.setOption(option);} - } + } + }; + if (data.length > 0) { + option && myChart.setOption(option); + } + } - leading_zeros(dt){ - return (dt < 10 ? '0' : '') + dt; - } + leading_zeros(dt) { + return (dt < 10 ? '0' : '') + dt; + } - transDate(dt){ - const date:any = new Date(dt* 1000); - // American Format - // return date.getUTCFullYear()+'-'+this.leading_zeros((date.getUTCMonth() + 1))+'-'+date.getUTCDate()+','+this.leading_zeros(date.getUTCHours())+':'+this.leading_zeros(date.getUTCMinutes())+':'+this.leading_zeros(date.getUTCSeconds()); - return date.getUTCDate()+'-'+this.leading_zeros((date.getUTCMonth() + 1))+'-'+date.getUTCFullYear()+','+this.leading_zeros(date.getUTCHours())+':'+this.leading_zeros(date.getUTCMinutes())+':'+this.leading_zeros(date.getUTCSeconds()); - } + transDate(dt) { + const date: any = new Date(dt * 1000); + // American Format + // return date.getUTCFullYear()+'-'+this.leading_zeros((date.getUTCMonth() + 1))+'-'+date.getUTCDate()+','+this.leading_zeros(date.getUTCHours())+':'+this.leading_zeros(date.getUTCMinutes())+':'+this.leading_zeros(date.getUTCSeconds()); + return ( + date.getUTCDate() + + '-' + + this.leading_zeros(date.getUTCMonth() + 1) + + '-' + + date.getUTCFullYear() + + ',' + + this.leading_zeros(date.getUTCHours()) + + ':' + + this.leading_zeros(date.getUTCMinutes()) + + ':' + + this.leading_zeros(date.getUTCSeconds()) + ); + } - generateIntervalsOf(interval, start, end) { + generateIntervalsOf(interval, start, end) { const result = []; let current = start; @@ -646,19 +752,18 @@ initTaskSpeed(obj: object){ return result; } - // @HostListener allows us to also guard against browser refresh, close, etc. + // @HostListener allows us to also guard against browser refresh, close, etc. @HostListener('window:beforeunload', ['$event']) unloadNotification($event: any) { if (!this.canDeactivate()) { - $event.returnValue = "IE and Edge Message"; + $event.returnValue = 'IE and Edge Message'; } } canDeactivate(): Observable | boolean { if (this.updateForm.valid) { - return false; + return false; } return true; } - } From 7116a1ebc246fd1d27adb5f9c112f09c5734030a Mon Sep 17 00:00:00 2001 From: itbf9 Date: Fri, 1 Dec 2023 07:50:33 +0100 Subject: [PATCH 331/419] Use variable as column def name for selectable and row action columns --- .../tables/ht-table/ht-table.component.html | 4 ++-- .../tables/ht-table/ht-table.component.ts | 15 ++++++++++++--- .../tables/ht-table/ht-table.models.ts | 5 +++++ 3 files changed, 19 insertions(+), 5 deletions(-) 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 5b3c2fe2..3704fd9d 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 @@ -70,7 +70,7 @@
- + - + HTTableEditable; } + +/** Column def for selectable checkbox */ +export const COL_SELECT = 100; +/** Column def for row action */ +export const COL_ROW_ACTION = 200; From 2f5d0c11ef75c7f475341c55638eea8c56aee131 Mon Sep 17 00:00:00 2001 From: itbf9 Date: Fri, 1 Dec 2023 12:56:03 +0100 Subject: [PATCH 332/419] Update table to use numeric col id --- .../tables/ht-table/ht-table.component.html | 2 +- .../vouchers-table.component.ts | 22 ++++++++++++++----- .../vouchers-table.constants.ts | 9 ++++++-- 3 files changed, 24 insertions(+), 9 deletions(-) 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 cfb938f9..29be8527 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 @@ -44,7 +44,7 @@ -
+
From 46f3c0246d53e02b64b30a765832f9a7e719714d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Fri, 1 Dec 2023 15:12:38 +0000 Subject: [PATCH 335/419] Missing Modules --- src/app/agents/agent.module.ts | 5 ----- src/app/shared/components.module.ts | 10 +--------- src/app/shared/forms.module.ts | 3 +++ .../hashtype-detector/hashtype-detector.component.html | 2 +- src/app/tasks/edit-tasks/edit-tasks.component.html | 4 ++-- src/app/tasks/edit-tasks/edit-tasks.component.ts | 2 +- 6 files changed, 8 insertions(+), 18 deletions(-) diff --git a/src/app/agents/agent.module.ts b/src/app/agents/agent.module.ts index f3243378..bd6579a7 100644 --- a/src/app/agents/agent.module.ts +++ b/src/app/agents/agent.module.ts @@ -28,12 +28,7 @@ import { CoreFormsModule } from '../shared/forms.module'; CoreComponentsModule, FontAwesomeModule, ReactiveFormsModule, - MatButtonModule, - MatFormFieldModule, - MatInputModule, - MatStepperModule, AgentsRoutingModule, - MatIconModule, DataTablesModule, DirectivesModule, ComponentsModule, diff --git a/src/app/shared/components.module.ts b/src/app/shared/components.module.ts index 8f6dd629..1f818f01 100644 --- a/src/app/shared/components.module.ts +++ b/src/app/shared/components.module.ts @@ -1,6 +1,5 @@ import { ActiveSpinnerComponent } from './loading-spinner/loading-spinner-active.component'; import { PassStrenghtComponent } from './password/pass-strenght/pass-strenght.component'; -import { ButtonTruncateTextComponent } from './table/button-truncate-text.component'; import { HexconvertorComponent } from './utils/hexconvertor/hexconvertor.component'; import { TimeoutDialogComponent } from './dialog/timeout/timeout-dialog.component'; import { PassMatchComponent } from './password/pass-match/pass-match.component'; @@ -16,27 +15,20 @@ import { HorizontalNavModule } from './navigation/navigation.module'; import { InputModule } from './input/input.module'; import { LoadingSpinnerComponent } from '../shared/loading-spinner/loading-spinner.component'; import { LottiesModule } from './lottie/lottie.module'; -import { MatButtonModule } from '@angular/material/button'; -import { MatDialogModule } from '@angular/material/dialog'; -import { MatIconModule } from '@angular/material/icon'; -import { MatProgressBarModule } from '@angular/material/progress-bar'; import { NgModule } from '@angular/core'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { PageTitleModule } from './page-headers/page-title.module'; import { PaginationModule } from './pagination/pagination.module'; -import { DynamicFormModule } from './dynamic-form-builder/dynamicform.module'; import { GridModule } from './grid-containers/grid.module'; import { TableModule } from './table/table-actions.module'; import { FlexLayoutModule } from '@angular/flex-layout'; import { AlertComponent } from './alert/alert.component'; import { ButtonsModule } from './buttons/buttons.module'; -import { LottiesModule } from './lottie/lottie.module'; import { GraphsModule } from './graphs/graphs.module'; -import { ColorPickerModule } from 'ngx-color-picker'; -import { InputModule } from './input/input.module'; import { FormsModule } from '@angular/forms'; import { CoreFormsModule } from './forms.module'; import { AlertNavModule } from './alert/alert.module'; +import { HashtypeDetectorComponent } from './hashtype-detector/hashtype-detector.component'; @NgModule({ declarations: [ diff --git a/src/app/shared/forms.module.ts b/src/app/shared/forms.module.ts index 3208a96e..22f01266 100644 --- a/src/app/shared/forms.module.ts +++ b/src/app/shared/forms.module.ts @@ -1,5 +1,6 @@ import { NgModule } from '@angular/core'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { MatStepperModule } from '@angular/material/stepper'; import { MatTableModule } from '@angular/material/table'; import { MatTooltipModule } from '@angular/material/tooltip'; import { MatDialogModule } from '@angular/material/dialog'; @@ -26,6 +27,7 @@ import { MatExpansionModule } from '@angular/material/expansion'; imports: [ ReactiveFormsModule, MatAutocompleteModule, + MatStepperModule, MatSlideToggleModule, MatExpansionModule, MatOptionModule, @@ -53,6 +55,7 @@ import { MatExpansionModule } from '@angular/material/expansion'; MatAutocompleteModule, MatSlideToggleModule, MatExpansionModule, + MatStepperModule, MatOptionModule, MatSelectModule, MatDividerModule, diff --git a/src/app/shared/hashtype-detector/hashtype-detector.component.html b/src/app/shared/hashtype-detector/hashtype-detector.component.html index c0cc0a80..b79d47ec 100644 --- a/src/app/shared/hashtype-detector/hashtype-detector.component.html +++ b/src/app/shared/hashtype-detector/hashtype-detector.component.html @@ -18,7 +18,7 @@

Hashtype Detector

- {{ column | titlecase }} + {{ column }} {{ row[column] }} diff --git a/src/app/tasks/edit-tasks/edit-tasks.component.html b/src/app/tasks/edit-tasks/edit-tasks.component.html index b1670d4e..4d1d7d6f 100644 --- a/src/app/tasks/edit-tasks/edit-tasks.component.html +++ b/src/app/tasks/edit-tasks/edit-tasks.component.html @@ -136,7 +136,7 @@ - diff --git a/src/app/tasks/edit-tasks/edit-tasks.component.ts b/src/app/tasks/edit-tasks/edit-tasks.component.ts index fec91319..d7d8de83 100644 --- a/src/app/tasks/edit-tasks/edit-tasks.component.ts +++ b/src/app/tasks/edit-tasks/edit-tasks.component.ts @@ -236,7 +236,7 @@ export class EditTasksComponent implements OnInit { ); } - asignAgents() { + assignAgents() { if (this.createForm.valid) { const payload = { taskId: this.editedTaskIndex, From 4c1a16aab7ddc77c2a3ea263dd3d41ba50e4a88f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Mon, 4 Dec 2023 23:22:05 +0000 Subject: [PATCH 336/419] Small changes --- src/app/account/account.module.ts | 2 + .../new-notification.component.html | 12 +- .../acc-settings/acc-settings.component.html | 2 +- .../ui-settings/ui-settings.component.html | 59 +++---- .../agents/edit-agent/edit-agent.component.ts | 14 +- src/app/config/config.module.ts | 2 + .../edit-health-checks.component.html | 28 +--- .../new-health-checks.component.html | 64 ++----- .../new-health-checks.component.ts | 145 +++++++++++++--- .../new-supertasks.component.ts | 6 +- .../core/_constants/healthchecks.config.ts | 6 + src/app/core/_constants/select.config.ts | 40 +++++ .../files/new-files/new-files.component.ts | 27 ++- .../edit-hashlist/edit-hashlist.component.ts | 6 +- .../new-hashlist/new-hashlist.component.html | 34 +--- .../new-hashlist/new-hashlist.component.ts | 158 +++++++----------- src/app/shared/utils/forms.ts | 32 ++++ .../edit-supertasks.component.ts | 17 +- .../masks/masks.component.ts | 6 +- .../wrbulk/wrbulk.component.ts | 6 +- .../new-preconfigured-tasks.component.ts | 6 +- .../tasks/new-tasks/new-tasks.component.ts | 15 +- .../supertasks/applyhashlist.component.ts | 15 +- .../edit-users/edit-users.component.html | 21 +-- .../users/edit-users/edit-users.component.ts | 6 +- 25 files changed, 395 insertions(+), 334 deletions(-) create mode 100644 src/app/core/_constants/healthchecks.config.ts create mode 100644 src/app/core/_constants/select.config.ts diff --git a/src/app/account/account.module.ts b/src/app/account/account.module.ts index 80679006..33a4f528 100644 --- a/src/app/account/account.module.ts +++ b/src/app/account/account.module.ts @@ -21,6 +21,7 @@ import { NotificationsComponent } from './notifications/notifications.component' import { PipesModule } from '../shared/pipes.module'; import { RouterModule } from '@angular/router'; import { UiSettingsComponent } from './settings/ui-settings/ui-settings.component'; +import { CoreFormsModule } from '../shared/forms.module'; @NgModule({ declarations: [ @@ -37,6 +38,7 @@ import { UiSettingsComponent } from './settings/ui-settings/ui-settings.componen FontAwesomeModule, DataTablesModule, ComponentsModule, + CoreFormsModule, CoreComponentsModule, RouterModule, CommonModule, diff --git a/src/app/account/notifications/notification/new-notification.component.html b/src/app/account/notifications/notification/new-notification.component.html index 05533a91..914f1a5e 100644 --- a/src/app/account/notifications/notification/new-notification.component.html +++ b/src/app/account/notifications/notification/new-notification.component.html @@ -1,19 +1,19 @@
- +
+
- - - - + + + + -
diff --git a/src/app/account/settings/acc-settings/acc-settings.component.html b/src/app/account/settings/acc-settings/acc-settings.component.html index 34a202b8..f456f08e 100644 --- a/src/app/account/settings/acc-settings/acc-settings.component.html +++ b/src/app/account/settings/acc-settings/acc-settings.component.html @@ -1,4 +1,4 @@ - +
diff --git a/src/app/account/settings/ui-settings/ui-settings.component.html b/src/app/account/settings/ui-settings/ui-settings.component.html index 9e3c3580..4796ba51 100644 --- a/src/app/account/settings/ui-settings/ui-settings.component.html +++ b/src/app/account/settings/ui-settings/ui-settings.component.html @@ -1,29 +1,32 @@ - + + + +
+ + + + {{ d.description }} + + +
+
+ + + + {{ d.description }} + + +
+
+ + + + {{ t.description }} + + +
+ + +
+ -
-
- - - - {{ d.description }} - - -
-
- - - - {{ d.description }} - - -
-
- - - - {{ t.description }} - - -
- -
\ No newline at end of file diff --git a/src/app/agents/edit-agent/edit-agent.component.ts b/src/app/agents/edit-agent/edit-agent.component.ts index 61698c52..14b6a396 100644 --- a/src/app/agents/edit-agent/edit-agent.component.ts +++ b/src/app/agents/edit-agent/edit-agent.component.ts @@ -35,6 +35,10 @@ import { UnsubscribeService } from 'src/app/core/_services/unsubscribe.service'; import { ChangeDetectorRef } from '@angular/core'; import { AutoTitleService } from 'src/app/core/_services/shared/autotitle.service'; import { OnDestroy } from '@angular/core'; +import { + TASKS_FIELD_MAPPING, + USER_AGP_FIELD_MAPPING +} from 'src/app/core/_constants/select.config'; @Component({ selector: 'app-edit-agent', @@ -55,17 +59,11 @@ export class EditAgentComponent implements OnInit, OnDestroy { /** Select Options Mapping */ selectUserAgpMap = { - fieldMapping: { - name: 'groupName', - _id: '_id' - } + fieldMapping: USER_AGP_FIELD_MAPPING }; selectAssignMap = { - fieldMapping: { - name: 'taskName', - _id: 'taskId' - } + fieldMapping: TASKS_FIELD_MAPPING }; /** Assign Tasks */ diff --git a/src/app/config/config.module.ts b/src/app/config/config.module.ts index 967ad96e..0ba35acc 100644 --- a/src/app/config/config.module.ts +++ b/src/app/config/config.module.ts @@ -18,6 +18,7 @@ import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { PipesModule } from '../shared/pipes.module'; import { PreprocessorsComponent } from './engine/preprocessors/preprocessors.component'; import { RouterModule } from '@angular/router'; +import { CoreFormsModule } from '../shared/forms.module'; @NgModule({ declarations: [ @@ -36,6 +37,7 @@ import { RouterModule } from '@angular/router'; FontAwesomeModule, DataTablesModule, ComponentsModule, + CoreFormsModule, CoreComponentsModule, CommonModule, RouterModule, diff --git a/src/app/config/health-checks/edit-health-check/edit-health-checks.component.html b/src/app/config/health-checks/edit-health-check/edit-health-checks.component.html index 50af4413..ebc81e02 100644 --- a/src/app/config/health-checks/edit-health-check/edit-health-checks.component.html +++ b/src/app/config/health-checks/edit-health-check/edit-health-checks.component.html @@ -1,26 +1,12 @@ - + + +
+ + +
+
-
-
- - - - {{ healthc['time'] | uiDate }} - - - -
-
- - - - {{ healthc['status'] | HCstatus | lowercase | titlecase }} - - - -
-
diff --git a/src/app/config/health-checks/new-health-check/new-health-checks.component.html b/src/app/config/health-checks/new-health-check/new-health-checks.component.html index 6d6b787a..17924390 100644 --- a/src/app/config/health-checks/new-health-check/new-health-checks.component.html +++ b/src/app/config/health-checks/new-health-check/new-health-checks.component.html @@ -1,57 +1,21 @@ - - - + +
-
- - - - - - - - - - - - + +
+ + + + + + + +
+ -
+
diff --git a/src/app/config/health-checks/new-health-check/new-health-checks.component.ts b/src/app/config/health-checks/new-health-check/new-health-checks.component.ts index 821969a9..ecfcf30e 100644 --- a/src/app/config/health-checks/new-health-check/new-health-checks.component.ts +++ b/src/app/config/health-checks/new-health-check/new-health-checks.component.ts @@ -1,61 +1,152 @@ import { FormControl, FormGroup, Validators } from '@angular/forms'; -import { Component, OnInit } from '@angular/core'; +import { Component, OnDestroy, OnInit } from '@angular/core'; import { Router } from '@angular/router'; +import { + CRACKER_TYPE_FIELD_MAPPING, + CRACKER_VERSION_FIELD_MAPPING +} from 'src/app/core/_constants/select.config'; +import { attack, hashtype } from 'src/app/core/_constants/healthchecks.config'; import { AlertService } from 'src/app/core/_services/shared/alert.service'; import { GlobalService } from 'src/app/core/_services/main.service'; import { PageTitle } from 'src/app/core/_decorators/autotitle'; import { SERV } from '../../../core/_services/main.config'; +import { transformSelectOptions } from 'src/app/shared/utils/forms'; +import { UnsubscribeService } from 'src/app/core/_services/unsubscribe.service'; +import { AutoTitleService } from 'src/app/core/_services/shared/autotitle.service'; @Component({ selector: 'app-new-health-checks', templateUrl: './new-health-checks.component.html' }) -@PageTitle(['New Health Check']) -export class NewHealthChecksComponent implements OnInit { - - // Form create Health Check +export class NewHealthChecksComponent implements OnInit, OnDestroy { + /** Form group for Health Checks */ createForm: FormGroup; + // Lists of Selected inputs + selectAttack = attack; + selectHashtypes = hashtype; + selectCrackertype: any; + selectCrackerversions: any = []; + + /** Select Options Mapping */ + selectCrackertypeMap = { + fieldMapping: CRACKER_TYPE_FIELD_MAPPING + }; + + selectCrackervMap = { + fieldMapping: CRACKER_VERSION_FIELD_MAPPING + }; + + /** + * @param {UnsubscribeService} unsubscribeService - The service managing unsubscribing from observables. + * @param {AutoTitleService} titleService - The service for managing the title of the component. + * @param {AlertService} alert - The service for displaying alerts. + * @param {GlobalService} gs - The global service used for API calls and global functionalities. + * @param {Router} router - The Angular router service for navigation. + */ constructor( + private unsubscribeService: UnsubscribeService, + private titleService: AutoTitleService, private alert: AlertService, private gs: GlobalService, - private router:Router - ) { } - - crackertype: any = []; - crackerversions: any = []; + private router: Router + ) { + this.buildForm(); + titleService.set(['New Health Check']); + } + /** + * Lifecycle hook called after component initialization. + */ ngOnInit(): void { + this.loadData(); + } - this.gs.getAll(SERV.CRACKERS_TYPES).subscribe((crackers: any) => { - this.crackertype = crackers.values; - }); + /** + * Lifecycle hook called before the component is destroyed. + * Unsubscribes from all subscriptions to prevent memory leaks. + */ + ngOnDestroy(): void { + this.unsubscribeService.unsubscribeAll(); + } + /** + * Builds the form for creating a new SuperHashlist. + */ + buildForm(): void { this.createForm = new FormGroup({ - 'checkType': new FormControl(0), - 'hashtypeId': new FormControl(null || 0, [Validators.required]), - 'crackerBinaryId': new FormControl('', [Validators.required]) + checkType: new FormControl(0), + hashtypeId: new FormControl(null || 0, [Validators.required]), + crackerBinaryId: new FormControl('', [Validators.required]), + crackerBinaryType: new FormControl('') }); + this.createForm + .get('crackerBinaryType') + .valueChanges.subscribe((newvalue) => { + this.handleChangeBinary(newvalue); + }); + } + + /** + * Loads data, specifically hashlists, for the component. + */ + loadData(): void { + const loadSubscription$ = this.gs + .getAll(SERV.CRACKERS_TYPES) + .subscribe((response: any) => { + const transformedOptions = transformSelectOptions( + response.values, + this.selectCrackertypeMap + ); + this.selectCrackertype = transformedOptions; + }); + this.unsubscribeService.add(loadSubscription$); } - onChangeBinary(id: string){ - const params = {'filter': 'crackerBinaryTypeId='+id+''}; - this.gs.getAll(SERV.CRACKERS,params).subscribe((crackers: any) => { - this.crackerversions = crackers.values; + /** + * Handles the change event when the cracker binary type is selected. + * Fetches cracker versions based on the selected cracker binary type. + * + * @param {string} id - The ID of the selected cracker binary type. + * @returns {void} + */ + handleChangeBinary(id: string) { + const params = { filter: 'crackerBinaryTypeId=' + id + '' }; + this.gs.getAll(SERV.CRACKERS, params).subscribe((response: any) => { + const transformedOptions = transformSelectOptions( + response.values, + this.selectCrackervMap + ); + this.selectCrackerversions = transformedOptions; + const lastItem = this.selectCrackerversions.slice(-1)[0]['_id']; + this.createForm.get('crackerBinaryId').patchValue(lastItem); }); } - onSubmit(){ + /** + * Handles the form submission for creating a new health check. + * + * @returns {void} + */ + onSubmit() { if (this.createForm.valid) { + const { checkType, hashtypeId, crackerBinaryId } = this.createForm.value; - this.gs.create(SERV.HEALTH_CHECKS,this.createForm.value).subscribe(() => { - this.alert.okAlert('New Health Check created!',''); - this.router.navigate(['/config/health-checks']); - } - ); + const payload = { + checkType, + hashtypeId, + crackerBinaryId + }; + + const onSubmitSubscription$ = this.gs + .create(SERV.HEALTH_CHECKS, payload) + .subscribe(() => { + this.alert.okAlert('New Health Check created!', ''); + this.router.navigate(['/config/health-checks']); + }); + this.unsubscribeService.add(onSubmitSubscription$); } } - } diff --git a/src/app/core/_components/forms/custom-forms/task/new-supertasks/new-supertasks.component.ts b/src/app/core/_components/forms/custom-forms/task/new-supertasks/new-supertasks.component.ts index e9ec0a83..8606734f 100644 --- a/src/app/core/_components/forms/custom-forms/task/new-supertasks/new-supertasks.component.ts +++ b/src/app/core/_components/forms/custom-forms/task/new-supertasks/new-supertasks.component.ts @@ -24,6 +24,7 @@ import { UnsubscribeService } from 'src/app/core/_services/unsubscribe.service'; import { ListResponseWrapper } from 'src/app/core/_models/response.model'; import { SelectField } from 'src/app/core/_models/input.model'; import { SERV } from '../../../../../_services/main.config'; +import { PRETASKS_FIELD_MAPPING } from 'src/app/core/_constants/select.config'; /** * Represents the NewSupertasksComponent responsible for creating a new SuperTask. @@ -89,10 +90,7 @@ export class NewSupertasksComponent implements OnInit, OnDestroy { */ loadData(): void { const field = { - fieldMapping: { - name: 'taskName', - _id: '_id' - } + fieldMapping: PRETASKS_FIELD_MAPPING }; const loadSubscription$ = this.gs .getAll(SERV.PRETASKS) diff --git a/src/app/core/_constants/healthchecks.config.ts b/src/app/core/_constants/healthchecks.config.ts new file mode 100644 index 00000000..41c2de7c --- /dev/null +++ b/src/app/core/_constants/healthchecks.config.ts @@ -0,0 +1,6 @@ +export const attack = [{ _id: 0, name: 'Brute-Force' }]; + +export const hashtype = [ + { _id: 0, name: 'MD5' }, + { _id: 3200, name: 'BCRYPT' } +]; diff --git a/src/app/core/_constants/select.config.ts b/src/app/core/_constants/select.config.ts new file mode 100644 index 00000000..a7679c7c --- /dev/null +++ b/src/app/core/_constants/select.config.ts @@ -0,0 +1,40 @@ +export const ACCESS_GROUP_FIELD_MAPPING = { + name: 'groupName', + _id: '_id' +}; + +export const CRACKER_TYPE_FIELD_MAPPING = { + name: 'typeName', + _id: 'crackerBinaryTypeId' +}; + +export const CRACKER_VERSION_FIELD_MAPPING = { + name: 'version', + _id: 'crackerBinaryId' +}; + +export const HASHTYPE_FIELD_MAPPING = { + name: 'description', + _id: '_id' +}; + +export const PRETASKS_FIELD_MAPPING = { + name: 'taskName', + _id: '_id' +}; + +export const SUPER_TASK_FIELD_MAPPING = { + name: 'taskName', + _id: 'pretaskId' +}; + +export const TASKS_FIELD_MAPPING = { + name: 'taskName', + _id: 'taskId' +}; + +// Access Group Permission +export const USER_AGP_FIELD_MAPPING = { + name: 'groupName', + _id: '_id' +}; diff --git a/src/app/files/new-files/new-files.component.ts b/src/app/files/new-files/new-files.component.ts index 9ac6c15e..b2ad6b31 100644 --- a/src/app/files/new-files/new-files.component.ts +++ b/src/app/files/new-files/new-files.component.ts @@ -8,9 +8,8 @@ import { } from '@angular/core'; import { FormControl, FormGroup } from '@angular/forms'; import { Subject, Subscription, takeUntil } from 'rxjs'; -// import { takeUntil } from 'rxjs/operators'; -import { Buffer } from 'buffer'; +import { ACCESS_GROUP_FIELD_MAPPING } from 'src/app/core/_constants/select.config'; import { UploadTUSService } from 'src/app/core/_services/files/files_tus.service'; import { AlertService } from 'src/app/core/_services/shared/alert.service'; import { GlobalService } from 'src/app/core/_services/main.service'; @@ -23,7 +22,10 @@ import { SERV } from '../../core/_services/main.config'; import { subscribe } from 'diagnostics_channel'; import { AutoTitleService } from 'src/app/core/_services/shared/autotitle.service'; import { UnsubscribeService } from 'src/app/core/_services/unsubscribe.service'; -import { transformSelectOptions } from 'src/app/shared/utils/forms'; +import { + handleEncode, + transformSelectOptions +} from 'src/app/shared/utils/forms'; /** * Represents the NewFilesComponent responsible for creating and uploading files @@ -153,10 +155,7 @@ export class NewFilesComponent implements OnInit, OnDestroy { */ loadData() { const fieldAccess = { - fieldMapping: { - name: 'groupName', - _id: '_id' - } + fieldMapping: ACCESS_GROUP_FIELD_MAPPING }; const accedgroupSubscription$ = this.gs .getAll(SERV.ACCESS_GROUPS) @@ -206,16 +205,10 @@ export class NewFilesComponent implements OnInit, OnDestroy { * @returns {{ update: { filename: string, isSecret: boolean, fileType: number, accessGroupId: number, sourceType: string, sourceData: string }, status: boolean }} Prepared form data. */ onBeforeSubmit(form: any, status: boolean) { - let sourcadata; - let fname; - - if (form.sourceType === 'inline') { - fname = form.filename; - sourcadata = Buffer.from(form.sourceData).toString('base64'); - } else { - sourcadata = this.fileName; - fname = this.fileName; - } + const sourceType = form.sourceType || 'import'; + const isInline = sourceType === 'inline'; + const fname = isInline ? form.filename : this.fileName; + const sourcadata = isInline ? handleEncode(form.sourceData) : this.fileName; /** * Prepared form data for submission. diff --git a/src/app/hashlists/edit-hashlist/edit-hashlist.component.ts b/src/app/hashlists/edit-hashlist/edit-hashlist.component.ts index becbbca3..a642d150 100644 --- a/src/app/hashlists/edit-hashlist/edit-hashlist.component.ts +++ b/src/app/hashlists/edit-hashlist/edit-hashlist.component.ts @@ -20,6 +20,7 @@ import { } from 'src/app/core/_guards/pendingchanges.guard'; import { OnDestroy } from '@angular/core'; import { UnsavedChangesService } from 'src/app/core/_services/shared/unsaved-changes.service'; +import { ACCESS_GROUP_FIELD_MAPPING } from 'src/app/core/_constants/select.config'; /** * Represents the EditHashlistComponent responsible for editing a new hashlists. @@ -138,10 +139,7 @@ export class EditHashlistComponent */ loadData() { const fieldAccess = { - fieldMapping: { - name: 'groupName', - _id: '_id' - } + fieldMapping: ACCESS_GROUP_FIELD_MAPPING }; const accedgroupSubscription$ = this.gs .getAll(SERV.ACCESS_GROUPS) diff --git a/src/app/hashlists/new-hashlist/new-hashlist.component.html b/src/app/hashlists/new-hashlist/new-hashlist.component.html index d3eaafd0..7e121b75 100644 --- a/src/app/hashlists/new-hashlist/new-hashlist.component.html +++ b/src/app/hashlists/new-hashlist/new-hashlist.component.html @@ -4,8 +4,6 @@
- -
@@ -29,9 +27,9 @@
+ -

@@ -60,39 +58,23 @@
Hashcat Brain Enabled
- -
- - attach_file - Choose File - - - -
- +

-
- + +
+
-
-

Upload completed!

-

+ +

Upload completed!


- +
- diff --git a/src/app/hashlists/new-hashlist/new-hashlist.component.ts b/src/app/hashlists/new-hashlist/new-hashlist.component.ts index 8314d4c2..6c069b93 100644 --- a/src/app/hashlists/new-hashlist/new-hashlist.component.ts +++ b/src/app/hashlists/new-hashlist/new-hashlist.component.ts @@ -15,20 +15,26 @@ import { FormGroup, Validators } from '@angular/forms'; -import { Observable, Subject, Subscription, takeUntil } from 'rxjs'; +import { Subject, takeUntil } from 'rxjs'; import { environment } from './../../../environments/environment'; -import Swal from 'sweetalert2/dist/sweetalert2.js'; import { Router } from '@angular/router'; -import { Buffer } from 'buffer'; +import { + ACCESS_GROUP_FIELD_MAPPING, + HASHTYPE_FIELD_MAPPING +} from 'src/app/core/_constants/select.config'; import { UIConfigService } from 'src/app/core/_services/shared/storage.service'; import { UploadTUSService } from '../../core/_services/files/files_tus.service'; import { AlertService } from 'src/app/core/_services/shared/alert.service'; import { GlobalService } from 'src/app/core/_services/main.service'; import { FileSizePipe } from 'src/app/core/_pipes/file-size.pipe'; import { PageTitle } from 'src/app/core/_decorators/autotitle'; -import { extractIds, transformSelectOptions } from '../../shared/utils/forms'; -import { validateFileExt } from '../../shared/utils/util'; +import { + extractIds, + handleEncode, + removeFakePath, + transformSelectOptions +} from '../../shared/utils/forms'; import { UploadFileTUS } from '../../core/_models/file.model'; import { SERV } from '../../core/_services/main.config'; import { SelectField } from 'src/app/core/_models/input.model'; @@ -70,6 +76,17 @@ export class NewHashlistComponent implements OnInit, OnDestroy { selectFormatbrain = hashcatbrainFormat; hashcatbrain: string; + // Upload Hashlists + selectedFiles: FileList | null = null; + fileName: any; + uploadProgress = 0; + filenames: string[] = []; + selectedFile: ''; + fileToUpload: File | null = null; + + // Unsubcribe + private fileUnsubscribe = new Subject(); + constructor( private unsubscribeService: UnsubscribeService, private changeDetectorRef: ChangeDetectorRef, @@ -101,6 +118,8 @@ export class NewHashlistComponent implements OnInit, OnDestroy { */ ngOnDestroy(): void { this.unsubscribeService.unsubscribeAll(); + this.fileUnsubscribe.next(false); + this.fileUnsubscribe.complete(); } /** @@ -139,10 +158,7 @@ export class NewHashlistComponent implements OnInit, OnDestroy { */ loadData(): void { const fieldAccess = { - fieldMapping: { - name: 'groupName', - _id: '_id' - } + fieldMapping: ACCESS_GROUP_FIELD_MAPPING }; const accedgroupSubscription$ = this.gs .getAll(SERV.ACCESS_GROUPS) @@ -158,10 +174,7 @@ export class NewHashlistComponent implements OnInit, OnDestroy { this.unsubscribeService.add(accedgroupSubscription$); const fieldHashtype = { - fieldMapping: { - name: 'description', - _id: '_id' - } + fieldMapping: HASHTYPE_FIELD_MAPPING }; const hashtypesSubscription$ = this.gs .getAll(SERV.HASHTYPES) @@ -182,28 +195,33 @@ export class NewHashlistComponent implements OnInit, OnDestroy { return this.form.get('sourceType').value; } - // FILE UPLOAD: TUS File Uload - @ViewChild('file', { static: false }) file: ElementRef; - uploadProgress = 0; - filenames: string[] = []; - private ngUnsubscribe = new Subject(); + /** + * Handles the file upload process. + * + * @param {FileList | null} files - The list of files to be uploaded. + * @returns {void} + */ + onuploadFile(files: FileList | null): void { + // Represents the modified form data without the fake path prefix. + const newForm = { ...this.form.value }; - onuploadFile(files: FileList) { - if (this.form.valid) { - const newform = this.handlePathName(this.form.value); - const upload: Array = []; - for (let i = 0; i < files.length; i++) { - upload.push( - this.uploadService - .uploadFile(files[i], files[i].name, SERV.HASHLISTS, newform, [ - '/hashlists/hashlist' - ]) - .pipe(takeUntil(this.ngUnsubscribe)) - .subscribe((progress) => { - this.uploadProgress = progress; - }) - ); - } + // Modify the sourceData key if it exists + if (newForm.sourceData) { + newForm.sourceData = removeFakePath(newForm.sourceData); + } + + const upload: Array = []; + for (let i = 0; i < files.length; i++) { + upload.push( + this.uploadService + .uploadFile(files[0], files[0].name, SERV.HASHLISTS, newForm, [ + '/hashlists/hashlist' + ]) + .pipe(takeUntil(this.fileUnsubscribe)) + .subscribe((progress) => { + this.uploadProgress = progress; + }) + ); } } @@ -211,74 +229,28 @@ export class NewHashlistComponent implements OnInit, OnDestroy { * Handle Input and return file size * @param event */ - - selectedFile: ''; - fileGroup: number; - fileToUpload: File | null = null; - fileSize: any; - fileName: any; - - handleFileInput(event: any) { - this.fileToUpload = event.target.files[0]; - this.fileSize = this.fileToUpload.size; - this.fileName = this.fileToUpload.name; - $('.fileuploadspan').text( - ' ' + - this.fileName + - ' / Size: ' + - this.fs.transform(this.fileToUpload.size, false) - ); + onFilesSelected(files: FileList): void { + this.selectedFiles = files; + this.fileName = files[0].name; } /** * Create Hashlist * */ - onSubmit(): void { - if (this.form.valid) { - this.handleEncode(); - const onSubmitSubscription$ = this.gs - .create(SERV.HASHLISTS, this.form.value) - .subscribe(() => { - this.alert.okAlert('New HashList created!', ''); - this.router.navigate(['/hashlists/hashlist']); - }); - this.unsubscribeService.add(onSubmitSubscription$); - } - } + // Encode Paste hashes + this.form.patchValue({ + sourceData: handleEncode(this.form.get('sourceType').value) + }); - handleEncode() { - const fileType = this.form.get('sourceType').value; - if (fileType === 'paste') { - const fileSource = this.form.get('sourceType').value; - this.form.patchValue({ - sourceData: Buffer.from(fileSource).toString('base64') + const onSubmitSubscription$ = this.gs + .create(SERV.HASHLISTS, this.form.value) + .subscribe(() => { + this.alert.okAlert('New HashList created!', ''); + this.router.navigate(['/hashlists/hashlist']); }); - } - } - - handlePathName(form: any) { - const filePath = this.form.get('sourceData').value; - const fileReplacePath = filePath.replace('C:\\fakepath\\', ''); - const res = { - name: form.name, - hashTypeId: form.hashTypeId, - format: form.format, - separator: form.separator, - isSalted: form.isSalted, - isHexSalt: form.isHexSalt, - accessGroupId: form.accessGroupId, - useBrain: form.useBrain, - brainFeatures: form.brainFeatures, - notes: form.notes, - sourceType: form.sourceType, - sourceData: fileReplacePath, - hashCount: form.hashCount, - isArchived: form.isArchived, - isSecret: form.isSecret - }; - return res; + this.unsubscribeService.add(onSubmitSubscription$); } // Open Modal Hashtype Detector diff --git a/src/app/shared/utils/forms.ts b/src/app/shared/utils/forms.ts index 68e29e1e..7a0f7a7f 100644 --- a/src/app/shared/utils/forms.ts +++ b/src/app/shared/utils/forms.ts @@ -140,3 +140,35 @@ export function compareVersions(a, b): number { // If all segments are equal, return 0 return 0; } + +/** + * Removes the fake path prefix from the given file path. + * + * @param {string} originalPath - The original file path that may contain a fake path prefix. + * @returns {string} The file path with the fake path prefix removed. + * @throws {Error} Throws an error if the provided path is not a string. + */ +export function removeFakePath(originalPath: string): string { + const fakePathPrefix = 'C:\\fakepath\\'; + + // Ensure originalPath is a string + if (typeof originalPath !== 'string') { + throw new Error('Input must be a string.'); + } + + // Remove fake path prefix if it exists + return originalPath.startsWith(fakePathPrefix) + ? originalPath.slice(fakePathPrefix.length) + : originalPath; +} + +/** + * Handles the encoding of source data based on the selected file type. + * + * @param {string} fileSource - The source data to be encoded. + * @returns {string} The encoded source data in base64 format. + */ +import { Buffer } from 'buffer'; +export function handleEncode(fileSource: string): string { + return Buffer.from(fileSource).toString('base64'); +} diff --git a/src/app/tasks/edit-supertasks/edit-supertasks.component.ts b/src/app/tasks/edit-supertasks/edit-supertasks.component.ts index 9db8be08..f65ef019 100644 --- a/src/app/tasks/edit-supertasks/edit-supertasks.component.ts +++ b/src/app/tasks/edit-supertasks/edit-supertasks.component.ts @@ -5,6 +5,7 @@ import { DataTableDirective } from 'angular-datatables'; import { Subject } from 'rxjs'; +import { SUPER_TASK_FIELD_MAPPING } from 'src/app/core/_constants/select.config'; import { AlertService } from 'src/app/core/_services/shared/alert.service'; import { GlobalService } from 'src/app/core/_services/main.service'; import { environment } from './../../../environments/environment'; @@ -36,6 +37,11 @@ export class EditSupertasksComponent implements OnInit { /** List of PreTasks. */ selectPretasks: any[]; + /** Select Options Mapping */ + selectSuperTaskMap = { + fieldMapping: SUPER_TASK_FIELD_MAPPING + }; + // Edit editedSTIndex: number; editedST: any; // Change to Model @@ -111,12 +117,6 @@ export class EditSupertasksComponent implements OnInit { * Loads data, specifically hashlists, for the component. */ loadData(): void { - const field = { - fieldMapping: { - name: 'taskName', - _id: 'pretaskId' - } - }; const loadSTSubscription$ = this.gs .get(SERV.SUPER_TASKS, this.editedSTIndex, { expand: 'pretasks' }) .subscribe((res) => { @@ -138,7 +138,10 @@ export class EditSupertasksComponent implements OnInit { res.pretasks, htypes.values ); - const transformedOptions = transformSelectOptions(response, field); + const transformedOptions = transformSelectOptions( + response, + this.selectSuperTaskMap + ); this.selectPretasks = transformedOptions; this.isLoading = false; this.changeDetectorRef.detectChanges(); diff --git a/src/app/tasks/import-supertasks/masks/masks.component.ts b/src/app/tasks/import-supertasks/masks/masks.component.ts index 7dc4f822..999f6eab 100644 --- a/src/app/tasks/import-supertasks/masks/masks.component.ts +++ b/src/app/tasks/import-supertasks/masks/masks.component.ts @@ -4,6 +4,7 @@ import Swal from 'sweetalert2/dist/sweetalert2.js'; import { Component, OnInit } from '@angular/core'; import { Router } from '@angular/router'; +import { CRACKER_TYPE_FIELD_MAPPING } from 'src/app/core/_constants/select.config'; import { benchmarkType } from 'src/app/core/_constants/tasks.config'; import { AlertService } from 'src/app/core/_services/shared/alert.service'; import { GlobalService } from 'src/app/core/_services/main.service'; @@ -45,10 +46,7 @@ export class MasksComponent implements OnInit, OnDestroy { /** Select Options Mapping */ selectCrackertypeMap = { - fieldMapping: { - name: 'typeName', - _id: 'crackerBinaryTypeId' - } + fieldMapping: CRACKER_TYPE_FIELD_MAPPING }; /** diff --git a/src/app/tasks/import-supertasks/wrbulk/wrbulk.component.ts b/src/app/tasks/import-supertasks/wrbulk/wrbulk.component.ts index e54e1e42..36cf66a1 100644 --- a/src/app/tasks/import-supertasks/wrbulk/wrbulk.component.ts +++ b/src/app/tasks/import-supertasks/wrbulk/wrbulk.component.ts @@ -16,6 +16,7 @@ import { environment } from './../../../../environments/environment'; import { DataTableDirective } from 'angular-datatables'; import { Observable, Subject } from 'rxjs'; +import { CRACKER_TYPE_FIELD_MAPPING } from 'src/app/core/_constants/select.config'; import { benchmarkType } from 'src/app/core/_constants/tasks.config'; import { UIConfigService } from 'src/app/core/_services/shared/storage.service'; import { TooltipService } from '../../../core/_services/shared/tooltip.service'; @@ -56,10 +57,7 @@ export class WrbulkComponent implements OnInit, OnDestroy { /** Select Options Mapping */ selectCrackertypeMap = { - fieldMapping: { - name: 'typeName', - _id: 'crackerBinaryTypeId' - } + fieldMapping: CRACKER_TYPE_FIELD_MAPPING }; // TABLES, TO BE REMOVED diff --git a/src/app/tasks/new-preconfigured-tasks/new-preconfigured-tasks.component.ts b/src/app/tasks/new-preconfigured-tasks/new-preconfigured-tasks.component.ts index e3c4ad57..150225c0 100644 --- a/src/app/tasks/new-preconfigured-tasks/new-preconfigured-tasks.component.ts +++ b/src/app/tasks/new-preconfigured-tasks/new-preconfigured-tasks.component.ts @@ -13,6 +13,7 @@ import { ActivatedRoute, Params, Router } from '@angular/router'; import { DataTableDirective } from 'angular-datatables'; import { Subject } from 'rxjs'; +import { CRACKER_TYPE_FIELD_MAPPING } from 'src/app/core/_constants/select.config'; import { UIConfigService } from 'src/app/core/_services/shared/storage.service'; import { AlertService } from 'src/app/core/_services/shared/alert.service'; import { GlobalService } from 'src/app/core/_services/main.service'; @@ -42,10 +43,7 @@ export class NewPreconfiguredTasksComponent implements OnInit, AfterViewInit { /** Select Options Mapping */ selectCrackertypeMap = { - fieldMapping: { - name: 'typeName', - _id: 'crackerBinaryTypeId' - } + fieldMapping: CRACKER_TYPE_FIELD_MAPPING }; @ViewChild('cmdAttack', { static: true }) cmdAttack: any; diff --git a/src/app/tasks/new-tasks/new-tasks.component.ts b/src/app/tasks/new-tasks/new-tasks.component.ts index 6693ce7e..e6eebd0e 100644 --- a/src/app/tasks/new-tasks/new-tasks.component.ts +++ b/src/app/tasks/new-tasks/new-tasks.component.ts @@ -19,6 +19,10 @@ import { DataTableDirective } from 'angular-datatables'; import { Observable, Subject } from 'rxjs'; import { Router } from '@angular/router'; +import { + CRACKER_TYPE_FIELD_MAPPING, + CRACKER_VERSION_FIELD_MAPPING +} from 'src/app/core/_constants/select.config'; import { UIConfigService } from 'src/app/core/_services/shared/storage.service'; import { TooltipService } from '../../core/_services/shared/tooltip.service'; import { AlertService } from 'src/app/core/_services/shared/alert.service'; @@ -65,16 +69,11 @@ export class NewTasksComponent implements OnInit { /** Select Options Mapping */ selectCrackertypeMap = { - fieldMapping: { - name: 'typeName', - _id: 'crackerBinaryTypeId' - } + fieldMapping: CRACKER_TYPE_FIELD_MAPPING }; + selectCrackervMap = { - fieldMapping: { - name: 'version', - _id: 'crackerBinaryId' - } + fieldMapping: CRACKER_VERSION_FIELD_MAPPING }; // Initial Configuration diff --git a/src/app/tasks/supertasks/applyhashlist.component.ts b/src/app/tasks/supertasks/applyhashlist.component.ts index 013fbb7f..b4801e08 100644 --- a/src/app/tasks/supertasks/applyhashlist.component.ts +++ b/src/app/tasks/supertasks/applyhashlist.component.ts @@ -2,6 +2,10 @@ import { ActivatedRoute, Params, Router } from '@angular/router'; import { ChangeDetectorRef, Component } from '@angular/core'; import { DataTableDirective } from 'angular-datatables'; +import { + CRACKER_TYPE_FIELD_MAPPING, + CRACKER_VERSION_FIELD_MAPPING +} from 'src/app/core/_constants/select.config'; import { AlertService } from 'src/app/core/_services/shared/alert.service'; import { GlobalService } from 'src/app/core/_services/main.service'; import { environment } from '../../../environments/environment'; @@ -40,16 +44,11 @@ export class ApplyHashlistComponent implements OnInit, OnDestroy { /** Select Options Mapping */ selectCrackertypeMap = { - fieldMapping: { - name: 'typeName', - _id: 'crackerBinaryTypeId' - } + fieldMapping: CRACKER_TYPE_FIELD_MAPPING }; + selectCrackervMap = { - fieldMapping: { - name: 'version', - _id: 'crackerBinaryId' - } + fieldMapping: CRACKER_VERSION_FIELD_MAPPING }; // Get SuperTask Index diff --git a/src/app/users/edit-users/edit-users.component.html b/src/app/users/edit-users/edit-users.component.html index 8cdc0295..aec38d48 100644 --- a/src/app/users/edit-users/edit-users.component.html +++ b/src/app/users/edit-users/edit-users.component.html @@ -1,18 +1,19 @@
- +
- -
- +
+
+ + @@ -20,13 +21,13 @@ - - - - -
+
+ + + + + -
diff --git a/src/app/users/edit-users/edit-users.component.ts b/src/app/users/edit-users/edit-users.component.ts index 6c504255..1e35be4b 100644 --- a/src/app/users/edit-users/edit-users.component.ts +++ b/src/app/users/edit-users/edit-users.component.ts @@ -3,6 +3,7 @@ import { FormControl, FormGroup } from '@angular/forms'; import { Component, OnInit } from '@angular/core'; import { UIConfigService } from 'src/app/core/_services/shared/storage.service'; +import { USER_AGP_FIELD_MAPPING } from 'src/app/core/_constants/select.config'; import { AlertService } from 'src/app/core/_services/shared/alert.service'; import { GlobalService } from 'src/app/core/_services/main.service'; import { PageTitle } from 'src/app/core/_decorators/autotitle'; @@ -35,10 +36,7 @@ export class EditUsersComponent implements OnInit, OnDestroy { /** Select Options Mapping */ selectUserAgpMap = { - fieldMapping: { - name: 'groupName', - _id: '_id' - } + fieldMapping: USER_AGP_FIELD_MAPPING }; /** User Access Group Permissions. */ From 8288971aa3b1421939eb75b0a761f150986276f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Tue, 5 Dec 2023 15:03:05 +0000 Subject: [PATCH 337/419] Error required --- src/app/account/account.module.ts | 2 + .../new-notification.component.html | 6 +- .../acc-settings/acc-settings.component.html | 14 +- .../new-superhashlist.component.html | 7 +- .../new-superhashlist.component.ts | 3 - .../new-supertasks.component.html | 6 +- src/app/core/_constants/hashes.config.ts | 14 + .../files/new-files/new-files.component.html | 8 +- .../hashlists/hashes/hashes.component.html | 89 ++-- src/app/hashlists/hashes/hashes.component.ts | 397 +++++++++--------- .../new-hashlist/new-hashlist.component.html | 8 +- .../search-hash/search-hash.component.html | 5 +- src/app/shared/input/abstract-input.ts | 5 +- src/app/shared/input/date/date.component.html | 9 +- .../multiselect/multiselect.component.html | 3 +- .../shared/input/select/select.component.html | 3 +- .../input/text-area/text-area.component.html | 3 +- src/app/shared/input/text/text.component.html | 3 +- src/app/shared/input/text/text.component.ts | 1 + .../pass-strenght/pass-strenght.component.ts | 48 ++- .../hexconvertor/hexconvertor.component.html | 27 +- .../hexconvertor/hexconvertor.component.ts | 16 +- .../edit-supertasks.component.html | 2 +- .../masks/masks.component.html | 2 +- .../new-preconfigured-tasks.component.html | 2 +- .../tasks/new-tasks/new-tasks.component.html | 4 +- .../supertasks/applyhashlist.component.html | 7 +- src/styles/components/_card.scss | 3 + 28 files changed, 353 insertions(+), 344 deletions(-) create mode 100644 src/app/core/_constants/hashes.config.ts diff --git a/src/app/account/account.module.ts b/src/app/account/account.module.ts index 80679006..33a4f528 100644 --- a/src/app/account/account.module.ts +++ b/src/app/account/account.module.ts @@ -21,6 +21,7 @@ import { NotificationsComponent } from './notifications/notifications.component' import { PipesModule } from '../shared/pipes.module'; import { RouterModule } from '@angular/router'; import { UiSettingsComponent } from './settings/ui-settings/ui-settings.component'; +import { CoreFormsModule } from '../shared/forms.module'; @NgModule({ declarations: [ @@ -37,6 +38,7 @@ import { UiSettingsComponent } from './settings/ui-settings/ui-settings.componen FontAwesomeModule, DataTablesModule, ComponentsModule, + CoreFormsModule, CoreComponentsModule, RouterModule, CommonModule, diff --git a/src/app/account/notifications/notification/new-notification.component.html b/src/app/account/notifications/notification/new-notification.component.html index 05533a91..b2319fcb 100644 --- a/src/app/account/notifications/notification/new-notification.component.html +++ b/src/app/account/notifications/notification/new-notification.component.html @@ -3,10 +3,10 @@
- + - - + + diff --git a/src/app/account/settings/acc-settings/acc-settings.component.html b/src/app/account/settings/acc-settings/acc-settings.component.html index 34a202b8..501cf029 100644 --- a/src/app/account/settings/acc-settings/acc-settings.component.html +++ b/src/app/account/settings/acc-settings/acc-settings.component.html @@ -1,4 +1,4 @@ - + @@ -6,17 +6,19 @@ - - - - + + +
+ +
+ +
-
diff --git a/src/app/core/_components/forms/custom-forms/superhashlist/new-superhashlist/new-superhashlist.component.html b/src/app/core/_components/forms/custom-forms/superhashlist/new-superhashlist/new-superhashlist.component.html index 43e4e78e..16182a75 100644 --- a/src/app/core/_components/forms/custom-forms/superhashlist/new-superhashlist/new-superhashlist.component.html +++ b/src/app/core/_components/forms/custom-forms/superhashlist/new-superhashlist/new-superhashlist.component.html @@ -4,21 +4,22 @@
- + - + diff --git a/src/app/core/_components/forms/custom-forms/superhashlist/new-superhashlist/new-superhashlist.component.ts b/src/app/core/_components/forms/custom-forms/superhashlist/new-superhashlist/new-superhashlist.component.ts index 700fc8da..d8726340 100644 --- a/src/app/core/_components/forms/custom-forms/superhashlist/new-superhashlist/new-superhashlist.component.ts +++ b/src/app/core/_components/forms/custom-forms/superhashlist/new-superhashlist/new-superhashlist.component.ts @@ -40,9 +40,6 @@ export class NewSuperhashlistComponent implements OnInit, OnDestroy { /** Form group for the new SuperHashlist. */ form: FormGroup; - @Input() - error; - /** Select List of hashlists. */ selectHashlists: any; diff --git a/src/app/core/_components/forms/custom-forms/task/new-supertasks/new-supertasks.component.html b/src/app/core/_components/forms/custom-forms/task/new-supertasks/new-supertasks.component.html index 7f6988f9..caf2c435 100644 --- a/src/app/core/_components/forms/custom-forms/task/new-supertasks/new-supertasks.component.html +++ b/src/app/core/_components/forms/custom-forms/task/new-supertasks/new-supertasks.component.html @@ -4,13 +4,13 @@
- + - +
diff --git a/src/app/core/_constants/hashes.config.ts b/src/app/core/_constants/hashes.config.ts new file mode 100644 index 00000000..5a00304c --- /dev/null +++ b/src/app/core/_constants/hashes.config.ts @@ -0,0 +1,14 @@ +export const filters = [ + { _id: 'cracked', name: 'Cracked' }, + { _id: 'cracked', name: 'Uncracked' }, + { _id: '', name: 'All' } +]; + +export const displays = [ + { _id: '', name: 'Hashes + Plaintexts' }, + { _id: 'hash', name: 'Hashes only' }, + { _id: 'plain', name: 'Plaintexts only' }, + { _id: 'hpc', name: 'Hashes + Plaintexts + Crackposition' }, + { _id: 'hc', name: 'Hashes + Crackposition' }, + { _id: 'pc', name: 'Plaintexts + Crackposition' } +]; diff --git a/src/app/files/new-files/new-files.component.html b/src/app/files/new-files/new-files.component.html index 20d1139c..6d4175ff 100644 --- a/src/app/files/new-files/new-files.component.html +++ b/src/app/files/new-files/new-files.component.html @@ -12,7 +12,7 @@

Upload from your computer

- + @@ -35,15 +35,15 @@

Upload from your computer

Upload to the server using a public/private link

- + - + - + diff --git a/src/app/hashlists/hashes/hashes.component.html b/src/app/hashlists/hashes/hashes.component.html index d7be1f7e..efb847ad 100644 --- a/src/app/hashlists/hashes/hashes.component.html +++ b/src/app/hashlists/hashes/hashes.component.html @@ -1,68 +1,33 @@ - -
-
-
-
-

Hashes of chunk {{ titleName }}

-
-
-

Hashes of task {{ titleName }}

-
-
-

Hashes of hashlist {{ titleName }}

-
-
-
-
- + +

+ Hashes of + + chunk + task + hashlist + + {{ titleName }} +

+
-
-
-
- - -
-
- - - -
-
-
- -
-
- -
-
- -
+
+ + + + + + + + + + + +
+
Displaying: - Hash + Plain - {{ displayingDescr }} - Filter: - Cracked + Uncracked - {{ filteringDescr }} -
@@ -77,14 +42,14 @@

Hashes of hashlist + faCopy

- + diff --git a/src/app/hashlists/hashes/hashes.component.ts b/src/app/hashlists/hashes/hashes.component.ts index 9b3a46a4..c5216c42 100644 --- a/src/app/hashlists/hashes/hashes.component.ts +++ b/src/app/hashlists/hashes/hashes.component.ts @@ -1,33 +1,49 @@ -import { Component, OnInit,OnDestroy, ViewChild } from '@angular/core'; +import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core'; import { ActivatedRoute, Params, Router } from '@angular/router'; import { faCopy } from '@fortawesome/free-solid-svg-icons'; import { DataTableDirective } from 'angular-datatables'; import { FormControl, FormGroup } from '@angular/forms'; -import { Subject,Subscription } from 'rxjs'; +import { Subject, Subscription } from 'rxjs'; import { AutoTitleService } from 'src/app/core/_services/shared/autotitle.service'; import { GlobalService } from 'src/app/core/_services/main.service'; import { PageTitle } from 'src/app/core/_decorators/autotitle'; import { SERV } from '../../core/_services/main.config'; +import { UnsubscribeService } from 'src/app/core/_services/unsubscribe.service'; +import { displays, filters } from 'src/app/core/_constants/hashes.config'; +/** + * The `HashesComponent` is for managing and displaying a list of hashes + */ @Component({ selector: 'app-hashes', templateUrl: './hashes.component.html' }) -/** -* The `HashesComponent` is an Angular component responsible for managing and displaying a list of hashes -* with various filtering and display options using the DataTables library. This documentation provides -* an overview of the component's structure and functionality. -*/ -export class HashesComponent implements OnInit,OnDestroy { +export class HashesComponent implements OnInit, OnDestroy { + /** Form group for the Hashes View. */ + viewForm: FormGroup; + + /** Select Options */ + selectFilters = filters; + selectDisplays = displays; // Component Properties editMode = false; editedIndex: number; - edited: any // Change to Model + edited: any; // Change to Model - // Font Awesome icons - faCopy=faCopy; + // View type and filter options + whichView: string; + titleName: any; + + // Filtering and Display Properties + crackPos: any = true; + cracked: any; + filtering = ''; + filteringDescr = ''; + displaying = ''; + displayingDescr = ''; + matchHashes: any; // ViewChild reference to the DataTableDirective @ViewChild(DataTableDirective) @@ -36,188 +52,132 @@ export class HashesComponent implements OnInit,OnDestroy { dtTrigger: Subject = new Subject(); dtOptions: any = {}; - // Subscriptions to unsubscribe on component destruction - subscriptions: Subscription[] = [] - - // View type and filter options - whichView: string; - titleName: any; - constructor( + private unsubscribeService: UnsubscribeService, private titleService: AutoTitleService, - private route:ActivatedRoute, + private route: ActivatedRoute, private gs: GlobalService, - private router:Router + private router: Router ) { - titleService.set(['Show Hashes']) + titleService.set(['Show Hashes']); } - // Filtering and Display Properties - crackPos: any = true; - cracked:any; - filtering = ""; - filteringDescr = ""; - displaying = ""; - displayingDescr = ""; - - // Filter and Display Options - filters: any = [{"name":"cracked", "description":"Cracked"},{"name":"uncracked", "description": "Uncracked"},{"name":"", "description": "All"}]; - displays: any = [ - {"name":"", "description": "Hashes + Plaintexts"}, - {"name":"hash", "description":"Hashes only"}, - {"name":"plain","description": "Plaintexts only"}, - {"name":"hpc","description": "Hashes + Plaintexts + Crackposition"}, - {"name":"hc","description": "Hashes + Crackposition"}, - {"name":"pc","description": "Plaintexts + Crackposition"} - ]; - - matchHashes:any; - /** - * Initializes DataTable and retrieves hashes.. - */ - + * Lifecycle hook called after component initialization. + */ ngOnInit(): void { this.loadHashes(); this.setupTable(); } /** - * Unsubscribes from active subscriptions. + * Lifecycle hook called before the component is destroyed. + * Unsubscribes from all subscriptions to prevent memory leaks. */ ngOnDestroy(): void { - this.dtTrigger.unsubscribe(); - for (const sub of this.subscriptions) { - sub.unsubscribe(); + this.unsubscribeService.unsubscribeAll(); + } + + buildForm(): void { + const qp = this.route.snapshot.queryParams; + if (qp['crackpos']) { + this.crackPos = qp['crackpos']; + } + if (qp['filter']) { + this.filtering = qp['filter']; + this.filteringDescr = this.getDescrip(this.filtering, 2); + } + if (qp['display']) { + this.displaying = qp['display']; + this.displayingDescr = this.getDescrip(this.displaying, 3); + } + this.viewForm = new FormGroup({ + display: new FormControl(this.displaying), + displaydes: new FormControl(this.displayingDescr), + filter: new FormControl(this.filtering), + filterdes: new FormControl(this.filteringDescr) + }); + + //subscribe to changes to handle select trigger actions + this.viewForm.get('display').valueChanges.subscribe((newvalue) => { + this.onQueryp(newvalue, 0); + }); + + this.viewForm.get('filter').valueChanges.subscribe((newvalue) => { + this.onQueryp(newvalue, 1); + }); + } + + getRouterLink(): any[] { + switch (this.whichView) { + case 'chunks': + return ['/tasks/show-tasks/', this.editedIndex, 'edit']; + case 'tasks': + return ['/tasks/show-tasks/', this.editedIndex, 'edit']; + case 'hashlists': + return ['/hashlists/hashlist/', this.editedIndex, 'edit']; + default: + return []; } } // Refresh the data and the DataTable onRefresh() { - this.rerender(); this.ngOnInit(); } - /** - * Rerender the DataTable. - */ - rerender(): void { - this.dtElement.dtInstance.then((dtInstance: DataTables.Api) => { - dtInstance.destroy(); - setTimeout(() => { - this.dtTrigger['new'].next(); - }); - }); - } - /** * Fetches Hashes from the server * Subscribes to the API response and updates the hashes list. - */ + */ loadHashes(): void { - this.route.params - .subscribe( - (params: Params) => { - this.editedIndex = Number(params['id']); - } - ); + this.route.params.subscribe((params: Params) => { + this.editedIndex = Number(params['id']); + }); - this.route.data.subscribe(data => { + this.route.data.subscribe((data) => { switch (data['kind']) { - case 'chunkshash': this.whichView = 'chunks'; - this.subscriptions.push(this.gs.get(SERV.CHUNKS,this.editedIndex).subscribe((result)=>{this.titleName = result['chunkId']})); + this.gs.get(SERV.CHUNKS, this.editedIndex).subscribe((result) => { + this.titleName = result['chunkId']; + }); this.initChashes(); - break; + break; case 'taskhas': this.whichView = 'tasks'; - this.subscriptions.push(this.gs.get(SERV.TASKS,this.editedIndex).subscribe((result)=>{this.titleName = result['taskName']})); + this.gs.get(SERV.TASKS, this.editedIndex).subscribe((result) => { + this.titleName = result['taskName']; + }); this.initThashes(); - break; + break; case 'hashlisthash': this.whichView = 'hashlists'; - this.subscriptions.push(this.gs.get(SERV.HASHLISTS,this.editedIndex).subscribe((result)=>{this.titleName = result['name']})); + this.gs.get(SERV.HASHLISTS, this.editedIndex).subscribe((result) => { + this.titleName = result['name']; + }); this.initHhashes(); - break; - + break; } - this.initDisplay(); + this.buildForm(); }); } - /** - * Sets up the DataTable options and buttons. - * Customizes DataTable appearance and behavior. - */ - setupTable(): void { - // DataTables options - this.dtOptions = { - dom: 'Bfrtip', - scrollX: true, - pageLength: 25, - lengthMenu: [ - [10, 25, 50, 100, 250, -1], - [10, 25, 50, 100, 250, 'All'] - ], - searching: false, - buttons: { - dom: { - button: { - className: 'dt-button buttons-collection btn btn-sm-dt btn-outline-gray-600-dt', - } - }, - buttons: [ - { - extend: 'collection', - text: 'Export', - buttons: [ - { - extend: 'print', - customize: function ( win ) { - $(win.document.title) - .css( 'font-size', '14pt' ) - $(win.document.body) - .css( 'font-size', '10pt' ) - $(win.document.body).find( 'table' ) - .addClass( 'compact' ) - .css( 'font-size', 'inherit' ); - } - }, - { - extend: 'csvHtml5', - exportOptions: {modifier: {selected: true}}, - select: true, - customize: function (dt, csv) { - let data = ""; - for (let i = 0; i < dt.length; i++) { - data = "Hashes Information\n\n"+ dt; - } - return data; - } - } - ] - }, - ], - } - } - } - /** * Initialize based on Chunk hashes * - */ + */ private initChashes() { - const param = {'filter': 'chunkId='+this.editedIndex+''}; + const param = { filter: 'chunkId=' + this.editedIndex + '' }; this.getHashes(param); } /** * Initialize based on Tasks hashes * - */ + */ private initThashes() { // This should enough to filter by id // let param = {'filter': 'taskId='+this.editedIndex+''}; @@ -227,88 +187,66 @@ export class HashesComponent implements OnInit,OnDestroy { /** * Initialize based on Hashlists hashes * - */ + */ private initHhashes() { - const param = {'filter': 'hashlistId='+this.editedIndex+''}; + const param = { filter: 'hashlistId=' + this.editedIndex + '' }; this.getHashes(param); } /** * Fetch hashes from the server * - */ - async getHashes(param?: any){ - - const params = {'maxResults': 90000, 'expand':'hashlist,chunk'}; + */ + async getHashes(param?: any) { + const params = { maxResults: 90000, expand: 'hashlist,chunk' }; - const nwparams = {...params, ...param}; + const nwparams = { ...params, ...param }; - this.subscriptions.push(this.gs.getAll(SERV.HASHES,nwparams).subscribe((hashes: any) => { + this.gs.getAll(SERV.HASHES, nwparams).subscribe((hashes: any) => { let res = hashes.values; console.log(this.whichView); - if(this.whichView === 'tasks'){ - res = res.filter(u=> u.chunk?.taskId == this.editedIndex); - } - if(this.filtering === 'cracked'){ - this.matchHashes = res.filter(u=> u.isCracked == true); + if (this.whichView === 'tasks') { + res = res.filter((u) => u.chunk?.taskId == this.editedIndex); } - if(this.filtering === 'uncracked'){ - this.matchHashes = res.filter(u=> u.isCracked == false); + if (this.filtering === 'cracked') { + this.matchHashes = res.filter((u) => u.isCracked == true); } - else{ + if (this.filtering === 'uncracked') { + this.matchHashes = res.filter((u) => u.isCracked == false); + } else { this.matchHashes = res; } this.dtTrigger.next(null); - })); - - } - - // Initialize the form for display and filter options - - viewForm: FormGroup; - - initDisplay(){ - const qp = this.route.snapshot.queryParams; - if(qp['crackpos']){ - this.crackPos = qp['crackpos']; - }if(qp['filter']){ - this.filtering = qp['filter']; - this.filteringDescr = this.getDescrip(this.filtering,2); - }if(qp['display']){ - this.displaying = qp['display']; - this.displayingDescr = this.getDescrip(this.displaying,3); - } - this.viewForm = new FormGroup({ - 'display': new FormControl(this.displaying), - 'displaydes': new FormControl(this.displayingDescr), - 'filter': new FormControl(this.filtering), - 'filterdes': new FormControl(this.filteringDescr), }); } // Update query parameters and trigger updates - onQueryp(name: any, type: number){ - let query = {} - if(type == 0){ - query = {display: name} - }if(type == 1){ - query = {filter: name} + onQueryp(name: any, type: number) { + let query = {}; + if (type == 0) { + query = { display: name }; + } + if (type == 1) { + query = { filter: name }; } - this.router.navigate(['/hashlists/hashes/',this.whichView,this.editedIndex], {queryParams: query, queryParamsHandling: 'merge'}); + this.router.navigate( + ['/hashlists/hashes/', this.whichView, this.editedIndex], + { queryParams: query, queryParamsHandling: 'merge' } + ); this.onDisplaying(name, type); - this.rerender(); this.ngOnInit(); } // Update display or filter options - onDisplaying(name: string, type:number){ - if(type == 0){ + onDisplaying(name: string, type: number) { + if (type == 0) { this.displaying = name; this.viewForm.setValue({ display: this.displaying, displaydes: this.getDescrip(name, type) }); - }if(type == 1){ + } + if (type == 1) { this.filtering = name; this.viewForm.setValue({ filter: this.filtering, @@ -318,16 +256,77 @@ export class HashesComponent implements OnInit,OnDestroy { } // Get the description for filter and display options - getDescrip(item: string, type:number){ - if(type == 0){ - this.displayingDescr = this.displays.find(obj => obj.name === item).description; - }if(type == 1){ - this.filteringDescr = this.filters.find(obj => obj.name === item).description; - }if(type == 2){ - return this.filters.find(obj => obj.name === item).description; - }if(type == 3){ - return this.displays.find(obj => obj.name === item).description; + getDescrip(item: string, type: number): string | undefined { + const selectedArray = type === 0 ? this.selectDisplays : this.selectFilters; + const selectedItem = selectedArray?.find((obj) => obj?._id === item); + + if (selectedItem) { + if (type === 0) { + this.displayingDescr = selectedItem.name; + } else if (type === 1) { + this.filteringDescr = selectedItem.name; + } + + return selectedItem.name; } + + return undefined; } + /** + * Sets up the DataTable options and buttons. + * Customizes DataTable appearance and behavior. + */ + setupTable(): void { + // DataTables options + this.dtOptions = { + dom: 'Bfrtip', + scrollX: true, + pageLength: 25, + lengthMenu: [ + [10, 25, 50, 100, 250, -1], + [10, 25, 50, 100, 250, 'All'] + ], + searching: false, + buttons: { + dom: { + button: { + className: + 'dt-button buttons-collection btn btn-sm-dt btn-outline-gray-600-dt' + } + }, + buttons: [ + { + extend: 'collection', + text: 'Export', + buttons: [ + { + extend: 'print', + customize: function (win) { + $(win.document.title).css('font-size', '14pt'); + $(win.document.body).css('font-size', '10pt'); + $(win.document.body) + .find('table') + .addClass('compact') + .css('font-size', 'inherit'); + } + }, + { + extend: 'csvHtml5', + exportOptions: { modifier: { selected: true } }, + select: true, + customize: function (dt, csv) { + let data = ''; + for (let i = 0; i < dt.length; i++) { + data = 'Hashes Information\n\n' + dt; + } + return data; + } + } + ] + } + ] + } + }; + } } diff --git a/src/app/hashlists/new-hashlist/new-hashlist.component.html b/src/app/hashlists/new-hashlist/new-hashlist.component.html index d3eaafd0..9e611830 100644 --- a/src/app/hashlists/new-hashlist/new-hashlist.component.html +++ b/src/app/hashlists/new-hashlist/new-hashlist.component.html @@ -7,9 +7,9 @@
- + - +
@@ -17,7 +17,7 @@ [isLoading]="isLoadingHashtypes" [placeholder]="'Hashtypes...'" [items]="selectHashtypes" - [label]="'Hashtype:'" + [label]="'Hashtype *'" formControlName="hashTypeId" [multiselectEnabled]="false" [mergeIdAndName]="true" @@ -89,7 +89,7 @@
Hashcat Brain Enabled
- +
diff --git a/src/app/hashlists/search-hash/search-hash.component.html b/src/app/hashlists/search-hash/search-hash.component.html index 89560976..8a7eb7a5 100644 --- a/src/app/hashlists/search-hash/search-hash.component.html +++ b/src/app/hashlists/search-hash/search-hash.component.html @@ -1,11 +1,10 @@ -
- + - +
diff --git a/src/app/shared/input/abstract-input.ts b/src/app/shared/input/abstract-input.ts index cf627857..ccabc39b 100644 --- a/src/app/shared/input/abstract-input.ts +++ b/src/app/shared/input/abstract-input.ts @@ -13,7 +13,10 @@ export class AbstractInputComponent implements OnInit, ControlValueAccessor { disabled = false; @Input() - error: string; + error: boolean | string; + + @Input() + isRequired: boolean; value: T; diff --git a/src/app/shared/input/date/date.component.html b/src/app/shared/input/date/date.component.html index db72476d..325376a6 100644 --- a/src/app/shared/input/date/date.component.html +++ b/src/app/shared/input/date/date.component.html @@ -1,7 +1,12 @@ {{ title }} - + {{ hint }} diff --git a/src/app/shared/input/multiselect/multiselect.component.html b/src/app/shared/input/multiselect/multiselect.component.html index 57d6566e..375e6b7d 100644 --- a/src/app/shared/input/multiselect/multiselect.component.html +++ b/src/app/shared/input/multiselect/multiselect.component.html @@ -29,12 +29,13 @@ (matChipInputTokenEnd)="onChangeValue(value)" (input)="onSearchInputChange()" [(ngModel)]="searchTerm" + [required]="isRequired" /> + {{ label }} is required - diff --git a/src/app/shared/input/select/select.component.html b/src/app/shared/input/select/select.component.html index 92eee1dc..d58654ca 100644 --- a/src/app/shared/input/select/select.component.html +++ b/src/app/shared/input/select/select.component.html @@ -15,6 +15,7 @@ #inputField [(ngModel)]="value" (selectionChange)="onChangeValue($event.value)" + [required]="isRequired" > Disabled @@ -22,5 +23,5 @@ {{ hint }} - {{ error }} + {{ title }} is required diff --git a/src/app/shared/input/text-area/text-area.component.html b/src/app/shared/input/text-area/text-area.component.html index a0be9e97..c8fdf324 100644 --- a/src/app/shared/input/text-area/text-area.component.html +++ b/src/app/shared/input/text-area/text-area.component.html @@ -19,8 +19,9 @@ [(ngModel)]="value" (ngModelChange)="onChange(value)" matAutosize + [required]="isRequired" class="custom-textarea" > {{ hint }} - {{ error }} + {{ title }} is required diff --git a/src/app/shared/input/text/text.component.html b/src/app/shared/input/text/text.component.html index 1a060a6d..ea6175ca 100644 --- a/src/app/shared/input/text/text.component.html +++ b/src/app/shared/input/text/text.component.html @@ -22,7 +22,8 @@ (ngModelChange)="onChange(value)" [pattern]="pattern" [type]="inputType" + [required]="isRequired" /> {{ hint }} - {{ error }} + {{ title }} is required diff --git a/src/app/shared/input/text/text.component.ts b/src/app/shared/input/text/text.component.ts index 1fe9cad6..333681cd 100644 --- a/src/app/shared/input/text/text.component.ts +++ b/src/app/shared/input/text/text.component.ts @@ -1,3 +1,4 @@ +import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { AbstractInputComponent } from '../abstract-input'; import { Component, Input, forwardRef } from '@angular/core'; import { NG_VALUE_ACCESSOR } from '@angular/forms'; diff --git a/src/app/shared/password/pass-strenght/pass-strenght.component.ts b/src/app/shared/password/pass-strenght/pass-strenght.component.ts index e6b7ee5c..02f94377 100644 --- a/src/app/shared/password/pass-strenght/pass-strenght.component.ts +++ b/src/app/shared/password/pass-strenght/pass-strenght.component.ts @@ -1,4 +1,12 @@ -import { Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChange } from '@angular/core'; +import { + Component, + EventEmitter, + Input, + OnChanges, + OnInit, + Output, + SimpleChange +} from '@angular/core'; export const PasswordStrengthVal = { 1: 'Poor', @@ -18,20 +26,25 @@ export enum PasswordStrengthColors { @Component({ selector: 'app-pass-strenght', template: ` -
-
    -
  • -
  • -
  • -
  • -
- -

{{ message }}

-
-`, +
+
    +
  • +
  • +
  • +
  • +
+ +

+ {{ message }} +

+
+ ` }) export class PassStrenghtComponent implements OnChanges { - bar0: string; bar1: string; bar2: string; @@ -43,7 +56,6 @@ export class PassStrenghtComponent implements OnChanges { messageColor: string; checkStrength(password: string) { - let force = 0; // Identify if password contains @@ -85,7 +97,9 @@ export class PassStrenghtComponent implements OnChanges { if (password) { const pwdStrength = this.checkStrength(password); - pwdStrength === 40 ? this.passwordStrength.emit(true) : this.passwordStrength.emit(false); + pwdStrength === 40 + ? this.passwordStrength.emit(true) + : this.passwordStrength.emit(false); const color = this.getColor(pwdStrength); this.setBarColors(color.index, color.color); @@ -124,11 +138,11 @@ export class PassStrenghtComponent implements OnChanges { index = 4; } - this.messageColor = PasswordStrengthColors[index+1]; + this.messageColor = PasswordStrengthColors[index + 1]; return { index: index + 1, - color: PasswordStrengthColors[index+1], + color: PasswordStrengthColors[index + 1] }; } diff --git a/src/app/shared/utils/hexconvertor/hexconvertor.component.html b/src/app/shared/utils/hexconvertor/hexconvertor.component.html index 72805456..bb7f432c 100644 --- a/src/app/shared/utils/hexconvertor/hexconvertor.component.html +++ b/src/app/shared/utils/hexconvertor/hexconvertor.component.html @@ -1,16 +1,19 @@ - - - - + + + HEX Converter + + + + HEX Value: + +
- {{ hexVal }} + {{ hexVal }}
- -
+ + + + + diff --git a/src/app/shared/utils/hexconvertor/hexconvertor.component.ts b/src/app/shared/utils/hexconvertor/hexconvertor.component.ts index 9d56027b..ea31c118 100644 --- a/src/app/shared/utils/hexconvertor/hexconvertor.component.ts +++ b/src/app/shared/utils/hexconvertor/hexconvertor.component.ts @@ -1,4 +1,3 @@ -import { faCopy } from '@fortawesome/free-solid-svg-icons'; import { Component } from '@angular/core'; @Component({ @@ -6,11 +5,9 @@ import { Component } from '@angular/core'; templateUrl: './hexconvertor.component.html' }) export class HexconvertorComponent { + hexVal: any = ''; - faCopy=faCopy; - hexVal: any = ""; - - hexConv(hex: any){ + hexConv(hex: any) { let str = ''; for (let i = 0; i < hex.length; i += 2) { str += String.fromCharCode(parseInt(hex.substr(i, 2), 16)); @@ -18,12 +15,11 @@ export class HexconvertorComponent { this.hexVal = str; } - onClear(){ + onClear() { const elem = document.getElementById('hexval') as HTMLInputElement; - if (elem.value !="") { - elem.value = ""; - this.hexVal='' + if (elem.value != '') { + elem.value = ''; + this.hexVal = ''; } } - } diff --git a/src/app/tasks/edit-supertasks/edit-supertasks.component.html b/src/app/tasks/edit-supertasks/edit-supertasks.component.html index b38d5f3a..eb6a4eaa 100644 --- a/src/app/tasks/edit-supertasks/edit-supertasks.component.html +++ b/src/app/tasks/edit-supertasks/edit-supertasks.component.html @@ -22,7 +22,7 @@
Add Pretask
[isLoading]="isLoading" [placeholder]="'Pretasks...'" [items]="selectPretasks" - [label]="'Select or search Pretasks:'" + [label]="'Select or search Pretasks *'" [mergeIdAndName]="true" formControlName="pretasks" style="flex: 1;" diff --git a/src/app/tasks/import-supertasks/masks/masks.component.html b/src/app/tasks/import-supertasks/masks/masks.component.html index 40896326..c0b82068 100644 --- a/src/app/tasks/import-supertasks/masks/masks.component.html +++ b/src/app/tasks/import-supertasks/masks/masks.component.html @@ -4,7 +4,7 @@
- +
diff --git a/src/app/tasks/new-preconfigured-tasks/new-preconfigured-tasks.component.html b/src/app/tasks/new-preconfigured-tasks/new-preconfigured-tasks.component.html index 55a350c1..89235f12 100644 --- a/src/app/tasks/new-preconfigured-tasks/new-preconfigured-tasks.component.html +++ b/src/app/tasks/new-preconfigured-tasks/new-preconfigured-tasks.component.html @@ -11,7 +11,7 @@

New Preconfigured Tasks (Copied From {{whichView ===
- +
diff --git a/src/app/tasks/new-tasks/new-tasks.component.html b/src/app/tasks/new-tasks/new-tasks.component.html index 48d814e6..ef2c1ce8 100644 --- a/src/app/tasks/new-tasks/new-tasks.component.html +++ b/src/app/tasks/new-tasks/new-tasks.component.html @@ -4,7 +4,7 @@ - +
@@ -12,7 +12,7 @@ [isLoading]="isLoading" [placeholder]="'Hashlists...'" [items]="selectHashlists" - [label]="'Select or search Hashlists:'" + [label]="'Select or search Hashlists *'" [mergeIdAndName]="true" [multiselectEnabled]="false" formControlName="hashlistIds" diff --git a/src/app/tasks/supertasks/applyhashlist.component.html b/src/app/tasks/supertasks/applyhashlist.component.html index 16614c5d..c137d182 100644 --- a/src/app/tasks/supertasks/applyhashlist.component.html +++ b/src/app/tasks/supertasks/applyhashlist.component.html @@ -8,18 +8,19 @@ [isLoading]="isLoading" [placeholder]="'Hashlists...'" [items]="selectHashlists" - [label]="'Select or search Hashlists:'" + [label]="'Select or search Hashlists *'" [mergeIdAndName]="true" [multiselectEnabled]="false" formControlName="hashlistId" style="flex: 1;" + [isRequired]="true" >
- - + +
diff --git a/src/styles/components/_card.scss b/src/styles/components/_card.scss index a7856317..767e3a17 100644 --- a/src/styles/components/_card.scss +++ b/src/styles/components/_card.scss @@ -392,5 +392,8 @@ hr.break:after { border: 1px solid #ccc; } +.smaller-pass-strength { + width: 30%; +} From fa0c32fdc47121209eec756cff21a77d001c0a17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Tue, 5 Dec 2023 16:58:47 +0000 Subject: [PATCH 338/419] Styling Nav --- src/app/shared/navigation/horizontalnav.component.html | 2 +- src/styles/components/_button.scss | 5 ++++- src/styles/layout/_navigation.scss | 8 ++++++++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/app/shared/navigation/horizontalnav.component.html b/src/app/shared/navigation/horizontalnav.component.html index ff6f8470..92b3da96 100644 --- a/src/app/shared/navigation/horizontalnav.component.html +++ b/src/app/shared/navigation/horizontalnav.component.html @@ -1,4 +1,4 @@ - +
diff --git a/src/styles/components/_button.scss b/src/styles/components/_button.scss index a3cd0552..044561e5 100644 --- a/src/styles/components/_button.scss +++ b/src/styles/components/_button.scss @@ -252,5 +252,8 @@ mat-button-toggle-group { } .btn-toogle-select { - background-color: $color-grey-light; + background-color: $accent-200; + border-top: 2px solid $color-grey-border; + border-bottom: 2px solid $color-grey-border; + font-weight: bold; } diff --git a/src/styles/layout/_navigation.scss b/src/styles/layout/_navigation.scss index f6197d6e..ea57207a 100644 --- a/src/styles/layout/_navigation.scss +++ b/src/styles/layout/_navigation.scss @@ -69,3 +69,11 @@ a:link { min-height: 300px; } +.horizontalnav{ + + .mat-button-toggle { + font-size: 14px; + } + +} + From e600d9ca2055a69eccac377c6971ee8377333be5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Wed, 6 Dec 2023 09:38:56 +0000 Subject: [PATCH 339/419] Missing links, icon wrapper expand, graph darkmode --- src/app/home/home.component.html | 8 +- src/app/home/home.component.ts | 228 +++++++++++++++++++------------ src/styles/components/_card.scss | 4 + 3 files changed, 146 insertions(+), 94 deletions(-) diff --git a/src/app/home/home.component.html b/src/app/home/home.component.html index 029dd9d5..d4d114a7 100644 --- a/src/app/home/home.component.html +++ b/src/app/home/home.component.html @@ -12,7 +12,7 @@
-
+
dns

Agents

@@ -27,7 +27,7 @@

Agents

-
+
checklist

Tasks

@@ -42,7 +42,7 @@

Tasks

-
+
checklist

Supertasks

@@ -57,7 +57,7 @@

Supertasks

-
+
lock_open

Cracks

diff --git a/src/app/home/home.component.ts b/src/app/home/home.component.ts index ff3b0e7a..4af25a64 100644 --- a/src/app/home/home.component.ts +++ b/src/app/home/home.component.ts @@ -1,4 +1,9 @@ -import { TitleComponent, CalendarComponent, TooltipComponent, VisualMapComponent } from 'echarts/components'; +import { + CalendarComponent, + TitleComponent, + TooltipComponent, + VisualMapComponent +} from 'echarts/components'; import { Component, OnDestroy, OnInit } from '@angular/core'; import { environment } from 'src/environments/environment'; import { CanvasRenderer } from 'echarts/renderers'; @@ -17,7 +22,11 @@ import { Agent } from '../core/_models/agent.model'; import { Task } from '../core/_models/task.model'; import { SuperTask } from '../core/_models/supertask.model'; import { Hash } from '../core/_models/hash.model'; -import { formatDate, formatUnixTimestamp, unixTimestampInPast } from '../shared/utils/datetime'; +import { + formatDate, + formatUnixTimestamp, + unixTimestampInPast +} from '../shared/utils/datetime'; import { MatSnackBar } from '@angular/material/snack-bar'; @Component({ @@ -27,29 +36,32 @@ import { MatSnackBar } from '@angular/material/snack-bar'; }) @PageTitle(['Dashboard']) export class HomeComponent implements OnInit, OnDestroy { - - util: UISettingsUtilityClass + util: UISettingsUtilityClass; /** Flags for responsive design */ - screenXS = false - screenS = false - screenM = false - screenL = false - screenXL = false + screenXS = false; + screenS = false; + screenM = false; + screenL = false; + screenXL = false; + + /** DarkMode */ + protected uiSettings: UISettingsUtilityClass; + isDarkMode = false; /** Counters for dashboard statistics */ - activeAgents = 0 - totalAgents = 0 - totalTasks = 0 - totalCracks = 0 - totalSupertasks = 0 + activeAgents = 0; + totalAgents = 0; + totalTasks = 0; + totalCracks = 0; + totalSupertasks = 0; - lastUpdated: string + lastUpdated: string; private maxResults = environment.config.prodApiMaxResults; - private subscriptions: Subscription[] = [] - private pageReloadTimeout: NodeJS.Timeout - private crackedChart: echarts.ECharts + private subscriptions: Subscription[] = []; + private pageReloadTimeout: NodeJS.Timeout; + private crackedChart: echarts.ECharts; constructor( private gs: GlobalService, @@ -57,46 +69,50 @@ export class HomeComponent implements OnInit, OnDestroy { private snackBar: MatSnackBar, private breakpointObserver: BreakpointObserver ) { + this.uiSettings = new UISettingsUtilityClass(this.service); + this.isDarkMode = this.uiSettings.getSetting('theme') === 'dark'; // Observe screen breakpoints for responsive design - this.breakpointObserver.observe([Breakpoints.XSmall, Breakpoints.Small, Breakpoints.Medium, Breakpoints.Large, Breakpoints.XLarge]) - .subscribe(result => { + this.breakpointObserver + .observe([ + Breakpoints.XSmall, + Breakpoints.Small, + Breakpoints.Medium, + Breakpoints.Large, + Breakpoints.XLarge + ]) + .subscribe((result) => { const breakpoints = result.breakpoints; - this.screenXS = false - this.screenS = false - this.screenM = false - this.screenL = false - this.screenXL = false + this.screenXS = false; + this.screenS = false; + this.screenM = false; + this.screenL = false; + this.screenXL = false; if (breakpoints[Breakpoints.XSmall]) { - this.screenXS = true - } - else if (breakpoints[Breakpoints.Small]) { - this.screenS = true - } - else if (breakpoints[Breakpoints.Medium]) { - this.screenM = true - } - else if (breakpoints[Breakpoints.Large]) { - this.screenL = true - } - else if (breakpoints[Breakpoints.XLarge]) { - this.screenXL = true + this.screenXS = true; + } else if (breakpoints[Breakpoints.Small]) { + this.screenS = true; + } else if (breakpoints[Breakpoints.Medium]) { + this.screenM = true; + } else if (breakpoints[Breakpoints.Large]) { + this.screenL = true; + } else if (breakpoints[Breakpoints.XLarge]) { + this.screenXL = true; } }); } ngOnInit(): void { - this.util = new UISettingsUtilityClass(this.service) + this.util = new UISettingsUtilityClass(this.service); this.initChart(); this.initData(); this.onAutorefresh(); } - ngOnDestroy(): void { for (const sub of this.subscriptions) { - sub.unsubscribe() + sub.unsubscribe(); } } @@ -104,44 +120,44 @@ export class HomeComponent implements OnInit, OnDestroy { * Get the autorefresh interval setting. */ get refreshInterval(): number { - return this.util.getSetting('refreshInterval') + return this.util.getSetting('refreshInterval'); } /** * Check if autorefresh of the page is enabled. */ get refreshPage(): boolean { - return this.util.getSetting('refreshPage') + return this.util.getSetting('refreshPage'); } /** * Automatically refresh the page based on the configured interval. */ onAutorefresh() { - const timeout = this.refreshInterval + const timeout = this.refreshInterval; if (this.refreshPage) { this.pageReloadTimeout = setTimeout(() => { - this.initData() - this.onAutorefresh() + this.initData(); + this.onAutorefresh(); }, timeout * 1000); } } /** * Toggle the autoreload setting and refresh the data. - * + * * @param flag - New autoreload flag. */ setAutoreload(flag: boolean) { - const updatedSettings = this.util.updateSettings({ refreshPage: flag }) - let message = '' + const updatedSettings = this.util.updateSettings({ refreshPage: flag }); + let message = ''; if (updatedSettings) { if (flag) { - message = 'Autoreload is enabled' + message = 'Autoreload is enabled'; } else { - message = 'Autoreload is paused' - clearTimeout(this.pageReloadTimeout) + message = 'Autoreload is paused'; + clearTimeout(this.pageReloadTimeout); } this.snackBar.open(message, 'Close'); } @@ -153,65 +169,83 @@ export class HomeComponent implements OnInit, OnDestroy { * Initialize dashboard data. */ initData(): void { - this.getAgents() - this.getTasks() - this.getSuperTasks() - this.getCracks() + this.getAgents(); + this.getTasks(); + this.getSuperTasks(); + this.getCracks(); } /** * Get the list of active agents. */ private getAgents(): void { - const params = { maxResults: this.maxResults, filter: 'isActive=true' } - this.subscriptions.push(this.gs.getAll(SERV.AGENTS, params).subscribe((response: ListResponseWrapper) => { - this.totalAgents = response.total | 0; - this.activeAgents = response.values.length | 0; - })) + const params = { maxResults: this.maxResults, filter: 'isActive=true' }; + this.subscriptions.push( + this.gs + .getAll(SERV.AGENTS, params) + .subscribe((response: ListResponseWrapper) => { + this.totalAgents = response.total | 0; + this.activeAgents = response.values.length | 0; + }) + ); } /** * Get the list of tasks. */ private getTasks(): void { - const params = { 'maxResults': this.maxResults, filter: 'isArchived=false' } - - this.subscriptions.push(this.gs.getAll(SERV.TASKS, params).subscribe((response: ListResponseWrapper) => { - this.totalTasks = response.values.length | 0; - })); + const params = { maxResults: this.maxResults, filter: 'isArchived=false' }; + + this.subscriptions.push( + this.gs + .getAll(SERV.TASKS, params) + .subscribe((response: ListResponseWrapper) => { + this.totalTasks = response.values.length | 0; + }) + ); } /** * Get the list of supertasks. */ private getSuperTasks(): void { - const params = { 'maxResults': this.maxResults } - - this.subscriptions.push(this.gs.getAll(SERV.SUPER_TASKS, params).subscribe((response: ListResponseWrapper) => { - this.totalSupertasks = response.total | 0; - })); + const params = { maxResults: this.maxResults }; + + this.subscriptions.push( + this.gs + .getAll(SERV.SUPER_TASKS, params) + .subscribe((response: ListResponseWrapper) => { + this.totalSupertasks = response.total | 0; + }) + ); } /** * Get the list of cracked hashes from the last seven days. */ private getCracks(): void { - const timestampInPast = unixTimestampInPast(7) + const timestampInPast = unixTimestampInPast(7); const params = { maxResults: this.maxResults, filter: 'isCracked=true' - } + }; - this.subscriptions.push(this.gs.getAll(SERV.HASHES, params).subscribe((response: ListResponseWrapper) => { - const lastsevenObject = response.values.filter(u => (u.isCracked == true && u.timeCracked > timestampInPast)); - this.totalCracks = lastsevenObject.length | 0; - this.updateChart(response.values); - })); + this.subscriptions.push( + this.gs + .getAll(SERV.HASHES, params) + .subscribe((response: ListResponseWrapper) => { + const lastsevenObject = response.values.filter( + (u) => u.isCracked == true && u.timeCracked > timestampInPast + ); + this.totalCracks = lastsevenObject.length | 0; + this.updateChart(response.values); + }) + ); } /** * Count the occurrences of items in an array. - * + * * @param arr - The array to count occurrences in. * @returns An object with the occurrences of each item. */ @@ -234,39 +268,53 @@ export class HomeComponent implements OnInit, OnDestroy { HeatmapChart, CanvasRenderer ]); - + const isDarkTheme = this.isDarkMode ? 'dark' : ''; const chartDom = document.getElementById('pcard'); - this.crackedChart = echarts.init(chartDom); + this.crackedChart = echarts.init(chartDom, isDarkTheme); } /** * Update the heatmap chart. - * + * * @param data - Hash data used for the chart. */ updateChart(data: Hash[]): void { - const currentDate = new Date(); const currentYear = currentDate.getFullYear(); // Extract and format cracked dates - const formattedDates: string[] = data.map(item => formatUnixTimestamp(item.timeCracked, 'yyyy-MM-dd')); + const formattedDates: string[] = data.map((item) => + formatUnixTimestamp(item.timeCracked, 'yyyy-MM-dd') + ); // Count occurrences of each date - const dateCounts: { [key: string]: number } = this.countOccurrences(formattedDates); + const dateCounts: { [key: string]: number } = + this.countOccurrences(formattedDates); // Convert date counts to the required format - const countsExtended = Object.keys(dateCounts).map(date => [date, dateCounts[date]]); + const countsExtended = Object.keys(dateCounts).map((date) => [ + date, + dateCounts[date] + ]); + + // DarkMode + const backgroundColor = this.isDarkMode ? '#212121' : ''; const option = { + darkMode: true, title: {}, tooltip: { position: 'top', formatter: function (p) { - const format = echarts.time.format(p.data[0], '{dd}-{MM}-{yyyy}', false); + const format = echarts.time.format( + p.data[0], + '{dd}-{MM}-{yyyy}', + false + ); return format + ': ' + p.data[1]; } }, + backgroundColor: backgroundColor, visualMap: { min: 0, max: 300, @@ -295,11 +343,11 @@ export class HomeComponent implements OnInit, OnDestroy { formatter: function (params) { return currentDate.getDate() === params.data[0] ? 'X' : ''; } - }, + } } }; this.crackedChart.setOption(option); - this.lastUpdated = formatDate(new Date(), this.util.getSetting('timefmt')) + this.lastUpdated = formatDate(new Date(), this.util.getSetting('timefmt')); } -} \ No newline at end of file +} diff --git a/src/styles/components/_card.scss b/src/styles/components/_card.scss index a7856317..3ab18d8d 100644 --- a/src/styles/components/_card.scss +++ b/src/styles/components/_card.scss @@ -226,6 +226,10 @@ hr.break:after { height: 30px; text-align: center; + &:hover { + transform: scale(1.1); + } + .mat-icon { color: #fff; position: relative; From d2349a0d9e44db6cfe52814516bd85b813160b20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Wed, 6 Dec 2023 10:20:00 +0000 Subject: [PATCH 340/419] Error Notification Adapt to DarkMode --- src/styles/components/_card.scss | 3 ++- src/styles/dark.scss | 9 --------- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/src/styles/components/_card.scss b/src/styles/components/_card.scss index 3ab18d8d..2f5870a8 100644 --- a/src/styles/components/_card.scss +++ b/src/styles/components/_card.scss @@ -88,7 +88,8 @@ hr.break:after { } .err-msg { - background: #eee; + background: $color-grey-light; + color: $color-black; padding: 1rem; font-family: monospace; word-break: break-all; diff --git a/src/styles/dark.scss b/src/styles/dark.scss index 5916b922..9f785fd8 100644 --- a/src/styles/dark.scss +++ b/src/styles/dark.scss @@ -44,15 +44,6 @@ div.dataTables_wrapper div.dataTables_info { color: white; } -//Error Message - -.err-msg { - background: #060606; - padding: 1rem; - font-family: monospace; - word-break: break-all; -} - // Dashboard .card-category { From bcccd47abf36158e8e5e3c9dc0c0ed13f4069be4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Wed, 6 Dec 2023 10:21:24 +0000 Subject: [PATCH 341/419] Add black color to colors.scss --- src/styles/base/_colors.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/src/styles/base/_colors.scss b/src/styles/base/_colors.scss index c1cb1519..ddc81e9b 100644 --- a/src/styles/base/_colors.scss +++ b/src/styles/base/_colors.scss @@ -33,6 +33,7 @@ $warn-700: mat.get-color-from-palette($hashtopolis-warn-palette, 700); $warn-800: mat.get-color-from-palette($hashtopolis-warn-palette, 800); $warn-900: mat.get-color-from-palette($hashtopolis-warn-palette, 900); +$color-black: #080808; $color-white: #ffffff; $color-grey-light: #cccccc; $color-grey-border: #999999; From 14cdd0ac4a7d2be3cb860194266ec87861f914f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Wed, 6 Dec 2023 10:44:33 +0000 Subject: [PATCH 342/419] Style Grid padding --- src/app/shared/grid-containers/grid-main.ts | 38 ++++++--------------- src/styles/components/_card.scss | 10 ++++++ 2 files changed, 20 insertions(+), 28 deletions(-) diff --git a/src/app/shared/grid-containers/grid-main.ts b/src/app/shared/grid-containers/grid-main.ts index 8450fc03..301db83a 100644 --- a/src/app/shared/grid-containers/grid-main.ts +++ b/src/app/shared/grid-containers/grid-main.ts @@ -1,36 +1,18 @@ -import { Component, Input, OnInit } from '@angular/core'; +import { Component, Input } from '@angular/core'; @Component({ selector: 'grid-main', template: ` - - - - - - `, - host: { - '(window:resize)': 'onWindowResize($event)' - } +
+ + + + + +
+ ` }) -export class GridMainComponent implements OnInit { +export class GridMainComponent { @Input() class: any; @Input() centered?: boolean; - - isMobile = false; - width: number = window.innerWidth; - height: number = window.innerHeight; - mobileWidth = 760; - - constructor() {} - - ngOnInit(): void { - this.isMobile = this.width < this.mobileWidth; - } - - onWindowResize(event) { - this.width = event.target.innerWidth; - this.height = event.target.innerHeight; - this.isMobile = this.width < this.mobileWidth; - } } diff --git a/src/styles/components/_card.scss b/src/styles/components/_card.scss index 2f5870a8..10eab426 100644 --- a/src/styles/components/_card.scss +++ b/src/styles/components/_card.scss @@ -7,6 +7,16 @@ 01- custom card grid */ +.grid-wrapper { + padding-top: 5px; +} + +.grid-main { + display: flex; + flex-wrap: wrap; + justify-content: space-between; +} + .overflowAuto { overflow-x: hidden; overflow-y: auto; From 502ddd0a0f4d6ca96a0ff6bd640de2f89c3d9834 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Fri, 8 Dec 2023 16:14:00 +0000 Subject: [PATCH 343/419] PoC wordlist generator --- package-lock.json | 6 + package.json | 1 + .../files/new-files/new-files.component.html | 23 ++-- .../files/new-files/new-files.component.ts | 13 ++ src/app/shared/components.module.ts | 4 + .../wordlist-generator.component.html | 104 ++++++++++++++++ .../wordlist-generatorcomponent.ts | 115 ++++++++++++++++++ 7 files changed, 257 insertions(+), 9 deletions(-) create mode 100644 src/app/shared/wordlist-generator/wordlist-generator.component.html create mode 100644 src/app/shared/wordlist-generator/wordlist-generatorcomponent.ts diff --git a/package-lock.json b/package-lock.json index b26da6ef..e466bd33 100644 --- a/package-lock.json +++ b/package-lock.json @@ -69,6 +69,7 @@ "sweetalert2": "^11.7.32", "tslib": "^2.6.0", "tus-js-client": "^3.1.1", + "wordpolis": "^0.0.3", "zone.js": "~0.13.1" }, "devDependencies": { @@ -19638,6 +19639,11 @@ "node": ">=0.10.0" } }, + "node_modules/wordpolis": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/wordpolis/-/wordpolis-0.0.3.tgz", + "integrity": "sha512-Lu4nHBSKkU0FCoc0LlQvTzN+m2+prNgVlI3sX6cy7k0K6cLK3UmfD22JgaeevXgthSTaf6xoWK8vYkyKqDZm3Q==" + }, "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", diff --git a/package.json b/package.json index 3ca5ce4d..9cd3d654 100644 --- a/package.json +++ b/package.json @@ -75,6 +75,7 @@ "sweetalert2": "^11.7.32", "tslib": "^2.6.0", "tus-js-client": "^3.1.1", + "wordpolis": "^0.0.3", "zone.js": "~0.13.1" }, "devDependencies": { diff --git a/src/app/files/new-files/new-files.component.html b/src/app/files/new-files/new-files.component.html index 20d1139c..94ca0cd9 100644 --- a/src/app/files/new-files/new-files.component.html +++ b/src/app/files/new-files/new-files.component.html @@ -1,6 +1,5 @@ -
    upload @@ -9,12 +8,20 @@
-

Upload from your computer

- +

Upload from your computer

+ - +
+ + +
+ +
+
@@ -27,13 +34,12 @@

Upload from your computer

- - +
-

Upload to the server using a public/private link

-
+

Upload to the server using a public/private link

+ @@ -47,7 +53,6 @@

Upload to the server using a public/private link

-
diff --git a/src/app/files/new-files/new-files.component.ts b/src/app/files/new-files/new-files.component.ts index 9ac6c15e..61dc0132 100644 --- a/src/app/files/new-files/new-files.component.ts +++ b/src/app/files/new-files/new-files.component.ts @@ -24,6 +24,8 @@ import { subscribe } from 'diagnostics_channel'; import { AutoTitleService } from 'src/app/core/_services/shared/autotitle.service'; import { UnsubscribeService } from 'src/app/core/_services/unsubscribe.service'; import { transformSelectOptions } from 'src/app/shared/utils/forms'; +import { MatDialog } from '@angular/material/dialog'; +import { WordlisGeneratorComponent } from 'src/app/shared/wordlist-generator/wordlist-generatorcomponent'; /** * Represents the NewFilesComponent responsible for creating and uploading files @@ -81,6 +83,7 @@ export class NewFilesComponent implements OnInit, OnDestroy { private titleService: AutoTitleService, private route: ActivatedRoute, private alert: AlertService, + private dialog: MatDialog, private gs: GlobalService, private router: Router ) { @@ -268,6 +271,16 @@ export class NewFilesComponent implements OnInit, OnDestroy { this.fileName = files[0].name; } + showHelp(): void { + const dialogRef = this.dialog.open(WordlisGeneratorComponent, { + width: '100%', + maxWidth: '100vw' + }); + dialogRef.afterClosed().subscribe((result) => { + console.log('Dialog closed with result:', result); + }); + } + /** * Handles the upload of files. * Prepares form data and initiates the file upload process. diff --git a/src/app/shared/components.module.ts b/src/app/shared/components.module.ts index 1f818f01..87dee3f8 100644 --- a/src/app/shared/components.module.ts +++ b/src/app/shared/components.module.ts @@ -29,9 +29,11 @@ import { FormsModule } from '@angular/forms'; import { CoreFormsModule } from './forms.module'; import { AlertNavModule } from './alert/alert.module'; import { HashtypeDetectorComponent } from './hashtype-detector/hashtype-detector.component'; +import { WordlisGeneratorComponent } from './wordlist-generator/wordlist-generatorcomponent'; @NgModule({ declarations: [ + WordlisGeneratorComponent, HashtypeDetectorComponent, LoadingSpinnerComponent, TimeoutDialogComponent, @@ -57,6 +59,7 @@ import { HashtypeDetectorComponent } from './hashtype-detector/hashtype-detector AlertNavModule, ButtonsModule, LottiesModule, + CommonModule, GraphsModule, TableModule, InputModule, @@ -64,6 +67,7 @@ import { HashtypeDetectorComponent } from './hashtype-detector/hashtype-detector NgbModule ], exports: [ + WordlisGeneratorComponent, HashtypeDetectorComponent, LoadingSpinnerComponent, TimeoutDialogComponent, diff --git a/src/app/shared/wordlist-generator/wordlist-generator.component.html b/src/app/shared/wordlist-generator/wordlist-generator.component.html new file mode 100644 index 00000000..340b3ae9 --- /dev/null +++ b/src/app/shared/wordlist-generator/wordlist-generator.component.html @@ -0,0 +1,104 @@ + + +

WordList Generator

+
+
+ +
+ +
+
+ + Name 1 + + help + + + +
+ +
+ + Name {{ i + 2 }} + + + + +
+
+ + +
+
+ + Special Attribute 1 + + help + + + +
+ +
+ + Special Attribute {{ i + 2 }} + + + + +
+
+ +
+ + Special Date + + + + +
+ +
+ + + + + + + Additional Options + + +
+ + File Name + + + {{ filenamehint }} + + +
+ Add special chars + Permutations + Capitalize + Alternate capitalize + All Uppercasse + All Lowercasse + Similar Vowels (Replace vowels for similar numbers. ie. G = 6) + Similar Consonants (Replace consonants for similar numbers. ie. s = 5) + Similar Special Chars (Replace vowels and consonants for similar special chars. ie. h = #) +
+
+ + + +
+
+
diff --git a/src/app/shared/wordlist-generator/wordlist-generatorcomponent.ts b/src/app/shared/wordlist-generator/wordlist-generatorcomponent.ts new file mode 100644 index 00000000..cce603f4 --- /dev/null +++ b/src/app/shared/wordlist-generator/wordlist-generatorcomponent.ts @@ -0,0 +1,115 @@ +import { ChangeDetectorRef, Component, OnInit } from '@angular/core'; +import { + AbstractControl, + FormArray, + FormBuilder, + FormControl, + FormGroup +} from '@angular/forms'; +import { generateCandidates } from 'wordpolis'; + +@Component({ + selector: 'wordlist-generator', + templateUrl: './wordlist-generator.component.html' +}) +export class WordlisGeneratorComponent implements OnInit { + /** Form group for the password candidates. */ + form: FormGroup; + + // Form hints + namehint = + 'Name, Middle, Surname. Include as many middle names as necessary.'; + specialdatehint = 'Birthday,family member date. '; + specialhint = + 'Job Title,Platform Name (i.e., github),Social Media username,Pet name,Lovers name/surname,Street name,Street number,Old Password,favorite cars, bikes, hobbies, phone number'; + filenamehint = 'Default wordlist.txt'; + + constructor( + private fb: FormBuilder, + private cdr: ChangeDetectorRef + ) {} + + ngOnInit(): void { + this.buildForm(); + } + + buildForm() { + this.form = this.fb.group({ + names: this.fb.array(['']), + specialdates: [''], + sparetext: this.fb.array(['']), + useSpecCharacters: [false], + usePermutations: [false], + isCapitalize: [false], + isAlternated: [false], + isUppercasse: [false], + isLowercasse: [false], + isSimilarVowels: [false], + isSimilarConsonant: [false], + isSimilarSpecialchars: [false], + filename: ['wordlist.txt'] + }); + } + + get names() { + return this.form.get('names') as FormArray; + } + + get sparetext() { + return this.form.get('sparetext') as FormArray; + } + + removeItem(index: number, controlName: string): void { + const formArray = this.form.get(controlName) as FormArray; + formArray.removeAt(index); + this.cdr.detectChanges(); + } + + addControl(controlName: string): void { + const formArray = this.form.get(controlName) as FormArray; + formArray.push(this.fb.control('')); + this.cdr.detectChanges(); + } + + getFormControl(control: AbstractControl): FormControl { + return control as FormControl; + } + + /** + * Generates a file containing candidate passwords based on input parameters and downloads it. + * + * @returns {void} + */ + onSubmit(): void { + const formData = this.form.value; + + const { + names, + specialdates, + sparetext, + useSpecCharacters, + usePermutations, + isCapitalize, + isAlternated, + isUppercasse, + isLowercasse, + isSimilarVowels, + isSimilarConsonant, + isSimilarSpecialchars, + filename + } = formData; + const options = { + useSpecialchars: useSpecCharacters, + usePermutations: usePermutations, + isCapitalize: isCapitalize, + isAlternated: isAlternated, + isUppercase: isUppercasse, + isLowercase: isLowercasse, + isSimilarVowels: isSimilarVowels, + isSimilarConsonant: isSimilarConsonant, + isSimilarSpecialchars: isSimilarSpecialchars, + filename: filename + }; + generateCandidates(names, specialdates, sparetext, options); + } +} From 9cee1690846bb3c93ec25dbf448352bd34357892 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Mon, 11 Dec 2023 11:19:01 +0000 Subject: [PATCH 344/419] Change createForm for form --- .../new-health-checks.component.html | 2 +- .../new-health-checks.component.ts | 18 ++++++++---------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/app/config/health-checks/new-health-check/new-health-checks.component.html b/src/app/config/health-checks/new-health-check/new-health-checks.component.html index 17924390..5c17dcc3 100644 --- a/src/app/config/health-checks/new-health-check/new-health-checks.component.html +++ b/src/app/config/health-checks/new-health-check/new-health-checks.component.html @@ -1,6 +1,6 @@ -
+
diff --git a/src/app/config/health-checks/new-health-check/new-health-checks.component.ts b/src/app/config/health-checks/new-health-check/new-health-checks.component.ts index ecfcf30e..db0f1ac3 100644 --- a/src/app/config/health-checks/new-health-check/new-health-checks.component.ts +++ b/src/app/config/health-checks/new-health-check/new-health-checks.component.ts @@ -21,7 +21,7 @@ import { AutoTitleService } from 'src/app/core/_services/shared/autotitle.servic }) export class NewHealthChecksComponent implements OnInit, OnDestroy { /** Form group for Health Checks */ - createForm: FormGroup; + form: FormGroup; // Lists of Selected inputs selectAttack = attack; @@ -75,18 +75,16 @@ export class NewHealthChecksComponent implements OnInit, OnDestroy { * Builds the form for creating a new SuperHashlist. */ buildForm(): void { - this.createForm = new FormGroup({ + this.form = new FormGroup({ checkType: new FormControl(0), hashtypeId: new FormControl(null || 0, [Validators.required]), crackerBinaryId: new FormControl('', [Validators.required]), crackerBinaryType: new FormControl('') }); - this.createForm - .get('crackerBinaryType') - .valueChanges.subscribe((newvalue) => { - this.handleChangeBinary(newvalue); - }); + this.form.get('crackerBinaryType').valueChanges.subscribe((newvalue) => { + this.handleChangeBinary(newvalue); + }); } /** @@ -121,7 +119,7 @@ export class NewHealthChecksComponent implements OnInit, OnDestroy { ); this.selectCrackerversions = transformedOptions; const lastItem = this.selectCrackerversions.slice(-1)[0]['_id']; - this.createForm.get('crackerBinaryId').patchValue(lastItem); + this.form.get('crackerBinaryId').patchValue(lastItem); }); } @@ -131,8 +129,8 @@ export class NewHealthChecksComponent implements OnInit, OnDestroy { * @returns {void} */ onSubmit() { - if (this.createForm.valid) { - const { checkType, hashtypeId, crackerBinaryId } = this.createForm.value; + if (this.form.valid) { + const { checkType, hashtypeId, crackerBinaryId } = this.form.value; const payload = { checkType, From ea5bf1a7745dd4ebab1e1848c908c3e809caa95c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Mon, 11 Dec 2023 11:22:48 +0000 Subject: [PATCH 345/419] Added missing subscriptions --- .../new-health-checks.component.ts | 30 +++++++++++-------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/src/app/config/health-checks/new-health-check/new-health-checks.component.ts b/src/app/config/health-checks/new-health-check/new-health-checks.component.ts index db0f1ac3..9c46c7e4 100644 --- a/src/app/config/health-checks/new-health-check/new-health-checks.component.ts +++ b/src/app/config/health-checks/new-health-check/new-health-checks.component.ts @@ -82,9 +82,12 @@ export class NewHealthChecksComponent implements OnInit, OnDestroy { crackerBinaryType: new FormControl('') }); - this.form.get('crackerBinaryType').valueChanges.subscribe((newvalue) => { - this.handleChangeBinary(newvalue); - }); + const onHandleBinarySubscription$ = this.form + .get('crackerBinaryType') + .valueChanges.subscribe((newvalue) => { + this.handleChangeBinary(newvalue); + }); + this.unsubscribeService.add(onHandleBinarySubscription$); } /** @@ -112,15 +115,18 @@ export class NewHealthChecksComponent implements OnInit, OnDestroy { */ handleChangeBinary(id: string) { const params = { filter: 'crackerBinaryTypeId=' + id + '' }; - this.gs.getAll(SERV.CRACKERS, params).subscribe((response: any) => { - const transformedOptions = transformSelectOptions( - response.values, - this.selectCrackervMap - ); - this.selectCrackerversions = transformedOptions; - const lastItem = this.selectCrackerversions.slice(-1)[0]['_id']; - this.form.get('crackerBinaryId').patchValue(lastItem); - }); + const onChangeBinarySubscription$ = this.gs + .getAll(SERV.CRACKERS, params) + .subscribe((response: any) => { + const transformedOptions = transformSelectOptions( + response.values, + this.selectCrackervMap + ); + this.selectCrackerversions = transformedOptions; + const lastItem = this.selectCrackerversions.slice(-1)[0]['_id']; + this.form.get('crackerBinaryId').patchValue(lastItem); + }); + this.unsubscribeService.add(onChangeBinarySubscription$); } /** From f08c82df71fa152624184228b86235e668d65740 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Mon, 11 Dec 2023 11:26:56 +0000 Subject: [PATCH 346/419] Change camelCased --- src/app/files/new-files/new-files.component.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/files/new-files/new-files.component.ts b/src/app/files/new-files/new-files.component.ts index b2ad6b31..3d9dbc22 100644 --- a/src/app/files/new-files/new-files.component.ts +++ b/src/app/files/new-files/new-files.component.ts @@ -207,7 +207,7 @@ export class NewFilesComponent implements OnInit, OnDestroy { onBeforeSubmit(form: any, status: boolean) { const sourceType = form.sourceType || 'import'; const isInline = sourceType === 'inline'; - const fname = isInline ? form.filename : this.fileName; + const fileName = isInline ? form.filename : this.fileName; const sourcadata = isInline ? handleEncode(form.sourceData) : this.fileName; /** @@ -215,7 +215,7 @@ export class NewFilesComponent implements OnInit, OnDestroy { */ const res = { update: { - filename: fname, + filename: fileName, isSecret: form.isSecret, fileType: this.filterType, accessGroupId: form.accessGroupId, From 6c7910eecde7642cf4d49897fa80aa04320c79af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Mon, 11 Dec 2023 13:25:51 +0000 Subject: [PATCH 347/419] Move library to top --- src/app/shared/utils/forms.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/app/shared/utils/forms.ts b/src/app/shared/utils/forms.ts index 7a0f7a7f..5d5c5b7b 100644 --- a/src/app/shared/utils/forms.ts +++ b/src/app/shared/utils/forms.ts @@ -1,3 +1,5 @@ +import { Buffer } from 'buffer'; + /** * Section for reusable functions used in forms * @@ -168,7 +170,6 @@ export function removeFakePath(originalPath: string): string { * @param {string} fileSource - The source data to be encoded. * @returns {string} The encoded source data in base64 format. */ -import { Buffer } from 'buffer'; export function handleEncode(fileSource: string): string { return Buffer.from(fileSource).toString('base64'); } From dff1b3951e4bd2c87bd02bd85efd7091efb7b389 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Mon, 11 Dec 2023 14:41:08 +0000 Subject: [PATCH 348/419] Small missing styling --- .../acc-settings/acc-settings.component.html | 24 +++++++++---------- .../files/new-files/new-files.component.html | 4 ++-- .../dynamicform.component.html | 4 +++- .../shared/grid-containers/grid-autocol.ts | 2 +- src/styles/components/_card.scss | 1 + src/styles/pages/_files.scss | 6 ++--- 6 files changed, 22 insertions(+), 19 deletions(-) diff --git a/src/app/account/settings/acc-settings/acc-settings.component.html b/src/app/account/settings/acc-settings/acc-settings.component.html index 501cf029..43f9aa79 100644 --- a/src/app/account/settings/acc-settings/acc-settings.component.html +++ b/src/app/account/settings/acc-settings/acc-settings.component.html @@ -1,24 +1,24 @@ - - - - - - -
- +
+ + + + + +
+ +
+ + +
- - - - diff --git a/src/app/files/new-files/new-files.component.html b/src/app/files/new-files/new-files.component.html index 6d4175ff..319f724a 100644 --- a/src/app/files/new-files/new-files.component.html +++ b/src/app/files/new-files/new-files.component.html @@ -10,7 +10,7 @@

Upload from your computer

-
+ @@ -33,7 +33,7 @@

Upload from your computer

Upload to the server using a public/private link

- + diff --git a/src/app/shared/dynamic-form-builder/dynamicform.component.html b/src/app/shared/dynamic-form-builder/dynamicform.component.html index 6b630571..69711dc2 100644 --- a/src/app/shared/dynamic-form-builder/dynamicform.component.html +++ b/src/app/shared/dynamic-form-builder/dynamicform.component.html @@ -88,7 +88,8 @@

{{ field.label }}

- + {{ field.label }}

[name]="buttonText" [disabled]="!formIsValid()" > + diff --git a/src/app/shared/grid-containers/grid-autocol.ts b/src/app/shared/grid-containers/grid-autocol.ts index 8ad375e8..bc779efb 100644 --- a/src/app/shared/grid-containers/grid-autocol.ts +++ b/src/app/shared/grid-containers/grid-autocol.ts @@ -96,7 +96,7 @@ export class GridAutoColComponent implements OnInit { */ public getStyles() { // If the item count is less than 6, return an empty object to disable the component - if (this.itemCount < 6) { + if (this.itemCount < 8) { return {}; } diff --git a/src/styles/components/_card.scss b/src/styles/components/_card.scss index e172ee30..38ea956a 100644 --- a/src/styles/components/_card.scss +++ b/src/styles/components/_card.scss @@ -409,6 +409,7 @@ hr.break:after { .smaller-pass-strength { width: 30%; + padding-bottom: 20px; } diff --git a/src/styles/pages/_files.scss b/src/styles/pages/_files.scss index fabccc4b..62b4b49f 100644 --- a/src/styles/pages/_files.scss +++ b/src/styles/pages/_files.scss @@ -25,8 +25,8 @@ } .files-theme.active { - color: #fff; - background: #343d49; + color: $color-white; + background: $color-grey-metal; } .tab-slider--body { @@ -50,7 +50,7 @@ border-radius: 35px; overflow: hidden; background: #f0eaea; - height: 35px; + height: 34px; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; From 6d37b0989e7ab296aecb0c4a43ec190139ae9a73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Wed, 13 Dec 2023 14:00:16 +0000 Subject: [PATCH 349/419] Intrododuction --- .../tasks/new-tasks/new-tasks.component.html | 222 +++--------------- .../tasks/new-tasks/new-tasks.component.ts | 6 + 2 files changed, 34 insertions(+), 194 deletions(-) diff --git a/src/app/tasks/new-tasks/new-tasks.component.html b/src/app/tasks/new-tasks/new-tasks.component.html index ef2c1ce8..da70334b 100644 --- a/src/app/tasks/new-tasks/new-tasks.component.html +++ b/src/app/tasks/new-tasks/new-tasks.component.html @@ -84,200 +84,34 @@
- - - - Wordlists - - - - - - - - - - - - - - - -
- T|P - - - File Name
-
- -
- - T -
- -
- P -
-
-
- - - - -
- Full Name: {{f.filename}} - Line Count: {{f.lineCount}} - Size: {{ f.size | fileSize:false}} -
-
-
-
-
- - - Rules - - - - - - - - - - - - - - - -
- T|P - - - File Name
-
- -
- - T -
- -
- P -
-
-
- - - - -
- Full Name: {{f.filename}} - Line Count: {{f.lineCount}} - Size: {{ f.size | fileSize:false}} -
-
-
-
-
- - - Other - - - - - - - - - - - - - - - -
- T|P - - - File Name
-
- -
- - T -
- -
- P -
-
-
- - - - -
- Full Name: {{f.filename}} - Line Count: {{f.lineCount}} - Size: {{ f.size | fileSize:false}} -
-
-
-
-
-
+ + + + + Wordlists + + + + + + + + + Rules + + + + + + + + + Other + + + + +
diff --git a/src/app/tasks/new-tasks/new-tasks.component.ts b/src/app/tasks/new-tasks/new-tasks.component.ts index e6eebd0e..dc751ec5 100644 --- a/src/app/tasks/new-tasks/new-tasks.component.ts +++ b/src/app/tasks/new-tasks/new-tasks.component.ts @@ -43,6 +43,7 @@ import { } from '../../shared/utils/forms'; import { ListResponseWrapper } from 'src/app/core/_models/response.model'; import { Preprocessor } from 'src/app/core/_models/preprocessor.model'; +import { FileType } from 'src/app/core/_models/file.model'; /** * Represents the NewTasksComponent responsible for creating a new Tasks. @@ -91,6 +92,11 @@ export class NewTasksComponent implements OnInit { // Tooltips tasktip: any = []; + // Tables File Types + fileTypeWordlist: FileType = 0; + fileTypeRules: FileType = 1; + fileTypeOther: FileType = 2; + // TABLES @ViewChild(DataTableDirective) dtElement: DataTableDirective; From 885f20ea2c4a2fde1e67bbae7f2a8a8673e2b4fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Thu, 14 Dec 2023 14:03:10 +0000 Subject: [PATCH 350/419] Preconfigured Task Table --- .../_components/core-components.module.ts | 3 + .../menus/base-menu/base-menu.component.ts | 8 + .../bulk-action-menu.component.ts | 2 + .../bulk-action-menu.constants.ts | 1 + .../row-action-menu.component.ts | 24 ++ .../row-action-menu.constants.ts | 2 + .../tables/ht-table/ht-table.models.ts | 1 + .../pretasks-table.component.html | 17 + .../pretasks-table.component.ts | 312 ++++++++++++++++++ .../pretasks-table.constants.ts | 19 ++ .../preconfigured-tasks.datasource.ts | 48 +++ src/app/core/_models/config-ui.model.ts | 10 + src/app/core/_models/pretask.model.ts | 53 ++- .../tasks/new-tasks/new-tasks.component.html | 2 +- .../preconfigured-tasks.component.html | 57 +--- .../preconfigured-tasks.component.ts | 274 +-------------- 16 files changed, 508 insertions(+), 325 deletions(-) create mode 100644 src/app/core/_components/tables/pretasks-table/pretasks-table.component.html create mode 100644 src/app/core/_components/tables/pretasks-table/pretasks-table.component.ts create mode 100644 src/app/core/_components/tables/pretasks-table/pretasks-table.constants.ts create mode 100644 src/app/core/_datasources/preconfigured-tasks.datasource.ts diff --git a/src/app/core/_components/core-components.module.ts b/src/app/core/_components/core-components.module.ts index cde7d514..83bc15d9 100644 --- a/src/app/core/_components/core-components.module.ts +++ b/src/app/core/_components/core-components.module.ts @@ -46,6 +46,7 @@ import { NgModule } from '@angular/core'; import { NotificationsTableComponent } from './tables/notifications-table/notifications-table.component'; import { PermissionsTableComponent } from './tables/permissions-table/permissions-table.component'; import { PreprocessorsTableComponent } from './tables/preprocessors-table/preprocessors-table.component'; +import { PretasksTableComponent } from './tables/pretasks-table/pretasks-table.component'; import { RouterModule } from '@angular/router'; import { RowActionMenuComponent } from './menus/row-action-menu/row-action-menu.component'; import { SuperHashlistsTableComponent } from './tables/super-hashlists-table/super-hashlists-table.component'; @@ -78,6 +79,7 @@ import { VouchersTableComponent } from './tables/vouchers-table/vouchers-table.c FilesTableComponent, CrackersTableComponent, PreprocessorsTableComponent, + PretasksTableComponent, AgentBinariesTableComponent, HealthChecksTableComponent, LogsTableComponent, @@ -135,6 +137,7 @@ import { VouchersTableComponent } from './tables/vouchers-table/vouchers-table.c FilesTableComponent, CrackersTableComponent, PreprocessorsTableComponent, + PretasksTableComponent, AgentBinariesTableComponent, HealthChecksTableComponent, LogsTableComponent, diff --git a/src/app/core/_components/menus/base-menu/base-menu.component.ts b/src/app/core/_components/menus/base-menu/base-menu.component.ts index 43de7be9..880cd01c 100644 --- a/src/app/core/_components/menus/base-menu/base-menu.component.ts +++ b/src/app/core/_components/menus/base-menu/base-menu.component.ts @@ -76,6 +76,14 @@ export class BaseMenuComponent { return this.checkId('crackerBinaryTypeId') && 'typeName' in this.data; } + /** + * Check if the data row is of type "Pretask". + * @returns `true` if the data row is a pretask; otherwise, `false`. + */ + protected isPretask(): boolean { + return this.checkId('pretaskId') && 'priority' in this.data; + } + /** * Check if the data row is of type "TaskWrapper". * @returns `true` if the data row is a task wrapper; otherwise, `false`. diff --git a/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.component.ts b/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.component.ts index 9cdd0ecb..622efee0 100644 --- a/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.component.ts +++ b/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.component.ts @@ -57,6 +57,8 @@ export class BulkActionMenuComponent BulkActionMenuLabel.DELETE_HASHLISTS, BulkActionMenuLabel.ARCHIVE_HASHLISTS ); + } else if (this.dataType === 'pretasks') { + this.setDeleteMenu(BulkActionMenuLabel.DELETE_PRETASKS); } else if (this.dataType === 'tasks') { this.setArchiveDeleteMenu( BulkActionMenuLabel.DELETE_TASKS, diff --git a/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.constants.ts b/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.constants.ts index 40fb1c57..41d8ca73 100644 --- a/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.constants.ts +++ b/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.constants.ts @@ -1,6 +1,7 @@ export const BulkActionMenuLabel = { DELETE_AGENTS: 'Delete Agents', DELETE_NOTIFICATIONS: 'Delete Notification', + DELETE_PRETASKS: 'Delete PreTasks', DELETE_TASKS: 'Delete Tasks', DELETE_HASHLISTS: 'Delete Hashlists', DELETE_HASHTYPES: 'Delete Hashtypes', diff --git a/src/app/core/_components/menus/row-action-menu/row-action-menu.component.ts b/src/app/core/_components/menus/row-action-menu/row-action-menu.component.ts index 6c88e54f..ae424b45 100644 --- a/src/app/core/_components/menus/row-action-menu/row-action-menu.component.ts +++ b/src/app/core/_components/menus/row-action-menu/row-action-menu.component.ts @@ -60,6 +60,8 @@ export class RowActionMenuComponent this.setAgentBinaryMenu(); } else if (this.isNotification()) { this.setNotificationMenu(); + } else if (this.isPretask()) { + this.setPretaskMenu(); } else if (this.isTaskWrapper()) { this.setTaskWrapperMenu(); } else if (this.isHashlist()) { @@ -219,6 +221,28 @@ export class RowActionMenuComponent ]); } + /** + * Sets the context menu items for a pretask data row. + */ + private setPretaskMenu(): void { + this.setActionMenuItems(0, [ + this.getEditMenuItem(RowActionMenuLabel.EDIT_PRETASK) + ]); + this.addActionMenuItem(0, { + label: RowActionMenuLabel.COPY_TO_TASK, + action: RowActionMenuAction.COPY_TO_TASK, + icon: RowActionMenuIcon.COPY + }); + this.addActionMenuItem(0, { + label: RowActionMenuLabel.COPY_TO_PRETASK, + action: RowActionMenuAction.COPY_TO_PRETASK, + icon: RowActionMenuIcon.COPY + }); + this.setActionMenuItems(1, [ + this.getDeleteMenuItem(RowActionMenuLabel.DELETE_PRETASK) + ]); + } + /** * Sets the context menu items for a task data row. */ 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 a8fd19cc..eca930f0 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 @@ -7,6 +7,7 @@ export const RowActionMenuLabel = { DEACTIVATE_NOTIFICATION: 'Deactivate Notification', EDIT_AGENT: 'Edit Agent', EDIT_NOTIFICATION: 'Edit Notification', + EDIT_PRETASK: 'Edit Pretask', EDIT_TASK: 'Edit Task', EDIT_SUBTASKS: 'Edit Subtasks', EDIT_HASHLIST: 'Edit Hashlist', @@ -21,6 +22,7 @@ export const RowActionMenuLabel = { DELETE_AGENT: 'Delete Agent', DELETE_NOTIFICATION: 'Delete Notification', DELETE_CRACKER: 'Delete Cracker', + DELETE_PRETASK: 'Delete Pretask', DELETE_TASK: 'Delete Task', DELETE_HASHLIST: 'Delete Hashlist', DELETE_HASHTYPE: 'Delete Hashtype', diff --git a/src/app/core/_components/tables/ht-table/ht-table.models.ts b/src/app/core/_components/tables/ht-table/ht-table.models.ts index be3e605b..f1525149 100644 --- a/src/app/core/_components/tables/ht-table/ht-table.models.ts +++ b/src/app/core/_components/tables/ht-table/ht-table.models.ts @@ -18,6 +18,7 @@ export type DataType = | 'permissions' | 'cracks' | 'vouchers' + | 'pretasks' | 'tasks' | 'superhashlists'; diff --git a/src/app/core/_components/tables/pretasks-table/pretasks-table.component.html b/src/app/core/_components/tables/pretasks-table/pretasks-table.component.html new file mode 100644 index 00000000..795b3418 --- /dev/null +++ b/src/app/core/_components/tables/pretasks-table/pretasks-table.component.html @@ -0,0 +1,17 @@ + diff --git a/src/app/core/_components/tables/pretasks-table/pretasks-table.component.ts b/src/app/core/_components/tables/pretasks-table/pretasks-table.component.ts new file mode 100644 index 00000000..19c4092c --- /dev/null +++ b/src/app/core/_components/tables/pretasks-table/pretasks-table.component.ts @@ -0,0 +1,312 @@ +/* eslint-disable @angular-eslint/component-selector */ +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { + HTTableColumn, + HTTableIcon, + HTTableRouterLink +} from '../ht-table/ht-table.models'; +import { + PretasksTableCol, + PretasksTableColumnLabel +} from './pretasks-table.constants'; +import { catchError, forkJoin } from 'rxjs'; + +import { ActionMenuEvent } from '../../menus/action-menu/action-menu.model'; +import { BaseTableComponent } from '../base-table/base-table.component'; +import { BulkActionMenuAction } from '../../menus/bulk-action-menu/bulk-action-menu.constants'; +import { Cacheable } from 'src/app/core/_decorators/cacheable'; +import { DialogData } from '../table-dialog/table-dialog.model'; +import { ExportMenuAction } from '../../menus/export-menu/export-menu.constants'; +import { PreTasksDataSource } from 'src/app/core/_datasources/preconfigured-tasks.datasource'; +import { Pretask } from 'src/app/core/_models/pretask.model'; +import { RowActionMenuAction } from '../../menus/row-action-menu/row-action-menu.constants'; +import { SERV } from 'src/app/core/_services/main.config'; +import { TableDialogComponent } from '../table-dialog/table-dialog.component'; +import { formatFileSize } from 'src/app/shared/utils/util'; + +@Component({ + selector: 'pretasks-table', + templateUrl: './pretasks-table.component.html' +}) +export class PretasksTableComponent + extends BaseTableComponent + implements OnInit, OnDestroy +{ + tableColumns: HTTableColumn[] = []; + dataSource: PreTasksDataSource; + + ngOnInit(): void { + this.setColumnLabels(PretasksTableColumnLabel); + this.tableColumns = this.getColumns(); + this.dataSource = new PreTasksDataSource(this.cdr, this.gs, this.uiService); + this.dataSource.setColumns(this.tableColumns); + this.dataSource.loadAll(); + } + + ngOnDestroy(): void { + for (const sub of this.subscriptions) { + sub.unsubscribe(); + } + } + + filter(item: Pretask, filterValue: string): boolean { + return ( + item.taskName.toLowerCase().includes(filterValue) || + item.attackCmd.toLowerCase().includes(filterValue) + ); + } + + // + + getColumns(): HTTableColumn[] { + const tableColumns = [ + { + id: PretasksTableCol.ID, + dataKey: '_id', + isSortable: true, + export: async (pretask: Pretask) => pretask._id + '' + }, + { + id: PretasksTableCol.NAME, + dataKey: 'taskName', + routerLink: (pretask: Pretask) => this.renderPretaskLink(pretask), + isSortable: true, + export: async (pretask: Pretask) => pretask.taskName + }, + { + id: PretasksTableCol.ATTACK_COMMAND, + dataKey: 'attackCmd', + isSortable: true, + export: async (pretask: Pretask) => pretask.attackCmd + }, + { + id: PretasksTableCol.FILES_TOTAL, + dataKey: 'pretaskFiles', + isSortable: true, + icons: (pretask: Pretask) => this.renderSecretIcon(pretask), + render: (pretask: Pretask) => pretask.pretaskFiles.length, + export: async (pretask: Pretask) => + pretask.pretaskFiles.length.toString() + }, + { + id: PretasksTableCol.FILES_SIZE, + dataKey: 'pretaskFiles', + isSortable: true, + render: (pretask: Pretask) => + formatFileSize( + pretask.pretaskFiles.reduce((sum, file) => sum + file.size, 0), + 'short' + ), + export: async (pretask: Pretask) => + formatFileSize( + pretask.pretaskFiles.reduce((sum, file) => sum + file.size, 0), + 'short' + ) + }, + { + id: PretasksTableCol.PRIORITY, + dataKey: 'priority', + isSortable: true, + export: async (pretask: Pretask) => pretask.priority.toString() + }, + { + id: PretasksTableCol.MAX_AGENTS, + dataKey: 'maxAgents', + isSortable: true, + export: async (pretask: Pretask) => pretask.maxAgents.toString() + } + ]; + + return tableColumns; + } + + openDialog(data: DialogData) { + const dialogRef = this.dialog.open(TableDialogComponent, { + data: data, + width: '450px' + }); + + this.subscriptions.push( + dialogRef.afterClosed().subscribe((result) => { + if (result && result.action) { + switch (result.action) { + case RowActionMenuAction.DELETE: + this.rowActionDelete(result.data); + break; + case BulkActionMenuAction.DELETE: + this.bulkActionDelete(result.data); + break; + } + } + }) + ); + } + + // --- Action functions --- + + exportActionClicked(event: ActionMenuEvent): void { + switch (event.menuItem.action) { + case ExportMenuAction.EXCEL: + this.exportService.toExcel( + 'hashtopolis-pretasks', + this.tableColumns, + event.data, + PretasksTableColumnLabel + ); + break; + case ExportMenuAction.CSV: + this.exportService.toCsv( + 'hashtopolis-pretasks', + this.tableColumns, + event.data, + PretasksTableColumnLabel + ); + break; + case ExportMenuAction.COPY: + this.exportService + .toClipboard( + this.tableColumns, + event.data, + PretasksTableColumnLabel + ) + .then(() => { + this.snackBar.open( + 'The selected rows are copied to the clipboard', + 'Close' + ); + }); + break; + } + } + + rowActionClicked(event: ActionMenuEvent): void { + switch (event.menuItem.action) { + case RowActionMenuAction.EDIT: + this.rowActionEdit(event.data); + break; + case RowActionMenuAction.COPY_TO_TASK: + console.log('Copy to Task clicked:', event.data); + this.rowActionCopyToTask(event.data); + break; + case RowActionMenuAction.COPY_TO_PRETASK: + console.log('Copy to Pretask clicked:', event.data); + this.rowActionCopyToPretask(event.data); + break; + case RowActionMenuAction.DELETE: + this.openDialog({ + rows: [event.data], + title: `Deleting Pretask ${event.data.taskName} ...`, + icon: 'warning', + body: `Are you sure you want to delete it? Note that this action cannot be undone.`, + warn: true, + action: event.menuItem.action + }); + break; + } + } + + bulkActionClicked(event: ActionMenuEvent): void { + switch (event.menuItem.action) { + case BulkActionMenuAction.DELETE: + this.openDialog({ + rows: event.data, + title: `Deleting ${event.data.length} pretasks ...`, + icon: 'warning', + body: `Are you sure you want to delete the above pretasks? Note that this action cannot be undone.`, + warn: true, + listAttribute: 'taskName', + action: event.menuItem.action + }); + break; + } + } + + /** + * @todo Implement error handling. + */ + private bulkActionDelete(pretasks: Pretask[]): void { + const requests = pretasks.map((pretask: Pretask) => { + return this.gs.delete(SERV.PRETASKS, pretask._id); + }); + + this.subscriptions.push( + forkJoin(requests) + .pipe( + catchError((error) => { + console.error('Error during deletion:', error); + return []; + }) + ) + .subscribe((results) => { + this.snackBar.open( + `Successfully deleted ${results.length} pretasks!`, + 'Close' + ); + this.reload(); + }) + ); + } + + @Cacheable(['_id', 'isSecret']) + async renderSecretIcon(pretask: Pretask): Promise { + const icons: HTTableIcon[] = []; + const secretFilesCount = pretask.pretaskFiles.reduce( + (sum, file) => sum + (file.isSecret ? 1 : 0), + 0 + ); + + if (secretFilesCount > 0) { + icons.push({ + name: 'lock', + tooltip: `Secret: ${secretFilesCount} ${ + secretFilesCount > 1 ? 'files' : 'file' + }` + }); + } + + return icons; + } + + @Cacheable(['_id']) + async renderPretaskLink(pretask: Pretask): Promise { + return [ + { + routerLink: ['/tasks/preconfigured-tasks', pretask._id, 'edit'] + } + ]; + } + + /** + * @todo Implement error handling. + */ + private rowActionDelete(pretasks: Pretask[]): void { + this.subscriptions.push( + this.gs + .delete(SERV.PRETASKS, pretasks[0]._id) + .pipe( + catchError((error) => { + console.error('Error during deletion:', error); + return []; + }) + ) + .subscribe(() => { + this.snackBar.open('Successfully deleted pretask!', 'Close'); + this.reload(); + }) + ); + } + + private rowActionCopyToTask(pretask: Pretask): void { + this.router.navigate(['/tasks/new-tasks', pretask._id, 'copypretask']); + } + + private rowActionCopyToPretask(pretask: Pretask): void { + this.router.navigate(['/tasks/preconfigured-tasks', pretask._id, 'copy']); + } + + private rowActionEdit(pretask: Pretask): void { + this.renderPretaskLink(pretask).then((links: HTTableRouterLink[]) => { + this.router.navigate(links[0].routerLink); + }); + } +} diff --git a/src/app/core/_components/tables/pretasks-table/pretasks-table.constants.ts b/src/app/core/_components/tables/pretasks-table/pretasks-table.constants.ts new file mode 100644 index 00000000..788eb630 --- /dev/null +++ b/src/app/core/_components/tables/pretasks-table/pretasks-table.constants.ts @@ -0,0 +1,19 @@ +export enum PretasksTableCol { + ID, + NAME, + ATTACK_COMMAND, + FILES_TOTAL, + FILES_SIZE, + PRIORITY, + MAX_AGENTS +} + +export const PretasksTableColumnLabel = { + [PretasksTableCol.ID]: 'ID', + [PretasksTableCol.NAME]: 'Name', + [PretasksTableCol.ATTACK_COMMAND]: 'Attack command', + [PretasksTableCol.FILES_TOTAL]: 'Overall count', + [PretasksTableCol.FILES_SIZE]: 'Total Size', + [PretasksTableCol.PRIORITY]: 'Priority', + [PretasksTableCol.MAX_AGENTS]: 'Max Agents' +}; diff --git a/src/app/core/_datasources/preconfigured-tasks.datasource.ts b/src/app/core/_datasources/preconfigured-tasks.datasource.ts new file mode 100644 index 00000000..04626855 --- /dev/null +++ b/src/app/core/_datasources/preconfigured-tasks.datasource.ts @@ -0,0 +1,48 @@ +import { catchError, finalize, of } from 'rxjs'; + +import { BaseDataSource } from './base.datasource'; +import { ListResponseWrapper } from '../_models/response.model'; +import { MatTableDataSourcePaginator } from '@angular/material/table'; +import { Pretask } from '../_models/pretask.model'; +import { SERV } from '../_services/main.config'; + +export class PreTasksDataSource extends BaseDataSource< + Pretask, + MatTableDataSourcePaginator +> { + loadAll(): void { + this.loading = true; + + const startAt = this.currentPage * this.pageSize; + const params = { + maxResults: this.pageSize, + startAt: startAt, + expand: 'pretaskFiles' + }; + + const pretasks$ = this.service.getAll(SERV.PRETASKS, params); + + this.subscriptions.push( + pretasks$ + .pipe( + catchError(() => of([])), + finalize(() => (this.loading = false)) + ) + .subscribe((response: ListResponseWrapper) => { + const pretasks: Pretask[] = response.values; + + this.setPaginationConfig( + this.pageSize, + this.currentPage, + response.total + ); + this.setData(pretasks); + }) + ); + } + + reload(): void { + this.clearSelection(); + this.loadAll(); + } +} diff --git a/src/app/core/_models/config-ui.model.ts b/src/app/core/_models/config-ui.model.ts index daf97d70..f6253124 100644 --- a/src/app/core/_models/config-ui.model.ts +++ b/src/app/core/_models/config-ui.model.ts @@ -12,6 +12,7 @@ import { LogsTableCol } from '../_components/tables/logs-table/logs-table.consta import { NotificationsTableCol } from '../_components/tables/notifications-table/notifications-table.constants'; import { PermissionsTableCol } from '../_components/tables/permissions-table/permissions-table.constants'; import { PreprocessorsTableCol } from '../_components/tables/preprocessors-table/preprocessors-table.constants'; +import { PretasksTableCol } from '../_components/tables/pretasks-table/pretasks-table.constants'; import { SuperHashlistsTableCol } from '../_components/tables/super-hashlists-table/super-hashlists-table.constants'; import { TaskTableCol } from '../_components/tables/tasks-table/tasks-table.constants'; import { UsersTableCol } from '../_components/tables/users-table/users-table.constants'; @@ -138,6 +139,15 @@ export const uiConfigDefault: UIConfig = { HealthChecksTableCol.STATUS, HealthChecksTableCol.TYPE ], + pretasksTable: [ + PretasksTableCol.ID, + PretasksTableCol.NAME, + PretasksTableCol.ATTACK_COMMAND, + PretasksTableCol.FILES_TOTAL, + PretasksTableCol.FILES_SIZE, + PretasksTableCol.PRIORITY, + PretasksTableCol.MAX_AGENTS + ], tasksTable: [ TaskTableCol.ID, TaskTableCol.NAME, diff --git a/src/app/core/_models/pretask.model.ts b/src/app/core/_models/pretask.model.ts index 4421350f..2855498b 100644 --- a/src/app/core/_models/pretask.model.ts +++ b/src/app/core/_models/pretask.model.ts @@ -1,21 +1,42 @@ +import { File } from './file.model'; + export class Pretask { - public pretaskId: number - public taskName: string - public attackCmd: string - public chunkTime: number - public statusTimer: number - public color: number - public isSmall: boolean - public isCpuTask: boolean - public useNewBench: boolean - public priority: number - public maxAgents: number - public isMaskImport: boolean - public crackerBinaryTypeId: number + public pretaskId: number; + public taskName: string; + public attackCmd: string; + public chunkTime: number; + public statusTimer: number; + public color: string; + public isSmall: boolean; + public isCpuTask: boolean; + public useNewBench: boolean; + public priority: number; + public maxAgents: number; + public isMaskImport: boolean; + public crackerBinaryTypeId: number; constructor(pretaskId: number, taskName: string, attackCmd: string) { - this.pretaskId = pretaskId - this.taskName = taskName - this.attackCmd = attackCmd + this.pretaskId = pretaskId; + this.taskName = taskName; + this.attackCmd = attackCmd; } } + +export interface Pretask { + _id: number; + _self: string; + attackCmd: string; + chunkTime: number; + color: string; + crackerBinaryTypeId: number; + isCpuTask: boolean; + isMaskImport: boolean; + isSmall: boolean; + maxAgents: number; + pretaskFiles: File[]; + pretaskId: number; + priority: number; + statusTimer: number; + taskName: string; + useNewBench: boolean; +} diff --git a/src/app/tasks/new-tasks/new-tasks.component.html b/src/app/tasks/new-tasks/new-tasks.component.html index da70334b..385545eb 100644 --- a/src/app/tasks/new-tasks/new-tasks.component.html +++ b/src/app/tasks/new-tasks/new-tasks.component.html @@ -15,7 +15,7 @@ [label]="'Select or search Hashlists *'" [mergeIdAndName]="true" [multiselectEnabled]="false" - formControlName="hashlistIds" + formControlName="hashlistId" > diff --git a/src/app/tasks/preconfigured-tasks/preconfigured-tasks.component.html b/src/app/tasks/preconfigured-tasks/preconfigured-tasks.component.html index dd3b1869..74829a87 100644 --- a/src/app/tasks/preconfigured-tasks/preconfigured-tasks.component.html +++ b/src/app/tasks/preconfigured-tasks/preconfigured-tasks.component.html @@ -1,52 +1,9 @@ - - - - - - - - - - - - - - - - - - - - - - - - - -
IDNameAttack commandFiles / SizePriorityMax AgentsActions
{{ ptask.pretaskId }} - - {{ ptask.taskName | shortenString:20 }} - - {{ ptask.attackCmd | shortenString:25 }} - {{ ptask.pretaskFiles.length }} - - / {{ ptask.pretaskFiles | sum:'size' | fileSize:false }} - {{ ptask.priority }}{{ ptask.maxAgents }} - - - - - - - -
+ +
diff --git a/src/app/tasks/preconfigured-tasks/preconfigured-tasks.component.ts b/src/app/tasks/preconfigured-tasks/preconfigured-tasks.component.ts index bcf3d72b..d483afce 100644 --- a/src/app/tasks/preconfigured-tasks/preconfigured-tasks.component.ts +++ b/src/app/tasks/preconfigured-tasks/preconfigured-tasks.component.ts @@ -1,4 +1,15 @@ -import { faEdit, faTrash, faLock, faFileImport, faFileExport, faPlus, faHomeAlt, faArchive, faCopy, faBookmark } from '@fortawesome/free-solid-svg-icons'; +import { + faArchive, + faBookmark, + faCopy, + faEdit, + faFileExport, + faFileImport, + faHomeAlt, + faLock, + faPlus, + faTrash +} from '@fortawesome/free-solid-svg-icons'; import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core'; import { environment } from './../../../environments/environment'; import { DataTableDirective } from 'angular-datatables'; @@ -9,7 +20,7 @@ import { AlertService } from 'src/app/core/_services/shared/alert.service'; import { GlobalService } from 'src/app/core/_services/main.service'; import { SERV } from '../../core/_services/main.config'; -declare let $:any; +declare let $: any; @Component({ selector: 'app-preconfigured-tasks', @@ -18,262 +29,9 @@ declare let $:any; /** * PreconfiguredTasksComponent is a component that manages and displays preconfigured tasks data. * - * It uses DataTables to display and interact with the preconfigured tasks data, including exporting, deleting, bulk actions - * and refreshing the table. */ -export class PreconfiguredTasksComponent implements OnInit, OnDestroy { - - // Font Awesome icons - faFileImport = faFileImport; - faFileExport = faFileExport; - faBookmark = faBookmark; - faArchive = faArchive; - faHome = faHomeAlt; - faTrash = faTrash; - faEdit = faEdit; - faLock = faLock; - faPlus = faPlus; - faCopy = faCopy; - - // ViewChild reference to the DataTableDirective - @ViewChild(DataTableDirective) - dtElement: DataTableDirective; - - dtTrigger: Subject = new Subject(); - dtOptions: any = {}; - - // List of pretasks - allpretasks: any = []; - - // Subscriptions to unsubscribe on component destruction - subscriptions: Subscription[] = [] - - private maxResults = environment.config.prodApiMaxResults; - - constructor( - private titleService: AutoTitleService, - private alert: AlertService, - private gs: GlobalService, - ) { - titleService.set(['Show Preconfigured Task']) - } - - /** - * Initializes DataTable and retrieves pretasks. - */ - - ngOnInit(): void { - this.getPretasks(); - this.setupTable(); - } - - /** - * Unsubscribes from active subscriptions. - */ - ngOnDestroy(): void { - this.dtTrigger.unsubscribe(); - for (const sub of this.subscriptions) { - sub.unsubscribe(); - } - } - - // Refresh the data and the DataTable - onRefresh() { - this.rerender(); - this.ngOnInit(); - } - - /** - * Rerenders the DataTable instance. - * Destroys and recreates the DataTable to reflect changes. - */ - rerender(): void { - this.dtElement.dtInstance.then((dtInstance: DataTables.Api) => { - // Destroy the table first - dtInstance.destroy(); - // Call the dtTrigger to rerender again - setTimeout(() => { - if (this.dtTrigger['new']) { - this.dtTrigger['new'].next(); - } - }); - }); +export class PreconfiguredTasksComponent { + constructor(private titleService: AutoTitleService) { + titleService.set(['Show Preconfigured Task']); } - - /** - * Fetches Pretasks from the server. - * Subscribes to the API response and updates the Pretasks list. - */ - getPretasks(): void { - // Fetch preconfigured tasks data from the API - const params = { 'maxResults': this.maxResults, 'expand': 'pretaskFiles' }; - this.subscriptions.push(this.gs.getAll(SERV.PRETASKS, params).subscribe((response: any) => { - this.allpretasks = response.values; - this.dtTrigger.next(void 0); - })); - } - - /** - * Sets up the DataTable options and buttons. - * Customizes DataTable appearance and behavior. - */ - setupTable(): void { - // DataTables options - const self = this; - this.dtOptions = { - dom: 'Bfrtip', - scrollX: true, - pageLength: 25, - lengthMenu: [ - [10, 25, 50, 100, 250, -1], - [10, 25, 50, 100, 250, 'All'] - ], - stateSave: true, - select: true, - buttons: { - dom: { - button: { - className: 'dt-button buttons-collection btn btn-sm-dt btn-outline-gray-600-dt', - } - }, - buttons: [ - { - text: '↻', - autoClose: true, - action: function (e, dt, node, config) { - self.onRefresh(); - } - }, - { - extend: 'collection', - text: 'Export', - buttons: [ - { - extend: 'excelHtml5', - exportOptions: { - columns: [0, 1, 2, 3, 4, 5] - }, - }, - { - extend: 'print', - exportOptions: { - columns: [0, 1, 2, 3, 4, 5] - }, - customize: function (win) { - $(win.document.body) - .css('font-size', '10pt'); - $(win.document.body).find('table') - .addClass('compact') - .css('font-size', 'inherit'); - } - }, - { - extend: 'csvHtml5', - exportOptions: { modifier: { selected: true } }, - select: true, - customize: function (dt, csv) { - let data = ''; - for (let i = 0; i < dt.length; i++) { - data = 'Agents\n\n' + dt; - } - return data; - } - }, - { - extend: 'copy', - }, - ] - }, - { - extend: 'collection', - text: 'Bulk Actions', - buttons: [ - { - text: 'Delete PreTask(s)', - autoClose: true, - action: function (e, dt, node, config) { - self.onDeleteBulk(); - } - } - ] - }, - { - extend: 'colvis', - text: 'Column View', - columns: [1, 2, 3, 4, 5], - }, - { - extend: 'pageLength', - className: 'btn-sm', - }, - ], - } - }; - } - - // Refresh the table after a delete operation - onRefreshTable() { - setTimeout(() => { - this.ngOnInit(); - this.rerender(); // Rerender the DataTable - }, 2000); - } - - /** - * Handles pretask deletion. - * Displays a confirmation dialog and deletes the pretask if confirmed. - * - * @param {number} id - The ID of the pretask to delete. - * @param {string} name - The name of the pretask. - */ - onDelete(id: number, name: string) { - this.alert.deleteConfirmation(name, 'Pretasks').then((confirmed) => { - if (confirmed) { - // Deletion - this.subscriptions.push(this.gs.delete(SERV.PRETASKS, id).subscribe(() => { - // Successful deletion - this.alert.okAlert(`Deleted Pretask ${name}`, ''); - this.onRefreshTable(); // Refresh the table - })); - } else { - // Handle cancellation - this.alert.okAlert(`Pretask ${name} is safe!`, ''); - } - }); - } - - /** - * BULK ACTIONS - * - */ - - /** - * Handles pretask selection. - * On multi select grabs the ids to be used for bulk action - * - */ - onSelectedPretasks(){ - $(".dt-button-background").trigger("click"); - const selection = $($(this.dtElement).DataTable.tables()).DataTable().rows({ selected: true } ).data().pluck(0).toArray(); - if(selection.length == 0) { - this.alert.okAlert('You have not selected any Pretask',''); - return; - } - const selectionnum = selection.map(i=>Number(i)); - - return selectionnum; - } - - /** - * Handles bulk deletion - * Delete the pretasks showing a progress bar - * - */ - async onDeleteBulk() { - const PretasksIds = this.onSelectedPretasks(); - this.alert.bulkDeleteAlert(PretasksIds,'Pretasks',SERV.PRETASKS); - this.onRefreshTable(); - } - - } From f3afad923542fc27c4e0b3388d6075ecdda77858 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Thu, 14 Dec 2023 14:03:10 +0000 Subject: [PATCH 351/419] Pretask info From 45f5a4f349747ea209d68fbc3d58076e3e2a575f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Thu, 14 Dec 2023 15:59:55 +0000 Subject: [PATCH 352/419] Supertak Table --- .../_components/core-components.module.ts | 3 + .../menus/base-menu/base-menu.component.ts | 8 + .../bulk-action-menu.component.ts | 2 + .../bulk-action-menu.constants.ts | 1 + .../row-action-menu.component.ts | 19 ++ .../row-action-menu.constants.ts | 4 + .../tables/ht-table/ht-table.models.ts | 1 + .../supertasks-table.component.html | 17 ++ .../supertasks-table.component.ts | 273 ++++++++++++++++++ .../supertasks-table.constants.ts | 11 + .../_datasources/super-tasks.datasource.ts | 48 +++ src/app/core/_models/config-ui.model.ts | 6 + src/app/core/_models/supertask.model.ts | 10 +- .../supertasks/supertasks.component.html | 13 +- .../tasks/supertasks/supertasks.component.ts | 181 +----------- 15 files changed, 422 insertions(+), 175 deletions(-) create mode 100644 src/app/core/_components/tables/supertasks-table/supertasks-table.component.html create mode 100644 src/app/core/_components/tables/supertasks-table/supertasks-table.component.ts create mode 100644 src/app/core/_components/tables/supertasks-table/supertasks-table.constants.ts create mode 100644 src/app/core/_datasources/super-tasks.datasource.ts diff --git a/src/app/core/_components/core-components.module.ts b/src/app/core/_components/core-components.module.ts index 83bc15d9..3c4afa73 100644 --- a/src/app/core/_components/core-components.module.ts +++ b/src/app/core/_components/core-components.module.ts @@ -50,6 +50,7 @@ import { PretasksTableComponent } from './tables/pretasks-table/pretasks-table.c import { RouterModule } from '@angular/router'; import { RowActionMenuComponent } from './menus/row-action-menu/row-action-menu.component'; import { SuperHashlistsTableComponent } from './tables/super-hashlists-table/super-hashlists-table.component'; +import { SuperTasksTableComponent } from './tables/supertasks-table/supertasks-table.component'; import { TableDialogComponent } from './tables/table-dialog/table-dialog.component'; import { TableTruncateComponent } from './tables/table-truncate/table-truncate.component'; import { TasksTableComponent } from './tables/tasks-table/tasks-table.component'; @@ -80,6 +81,7 @@ import { VouchersTableComponent } from './tables/vouchers-table/vouchers-table.c CrackersTableComponent, PreprocessorsTableComponent, PretasksTableComponent, + SuperTasksTableComponent, AgentBinariesTableComponent, HealthChecksTableComponent, LogsTableComponent, @@ -138,6 +140,7 @@ import { VouchersTableComponent } from './tables/vouchers-table/vouchers-table.c CrackersTableComponent, PreprocessorsTableComponent, PretasksTableComponent, + SuperTasksTableComponent, AgentBinariesTableComponent, HealthChecksTableComponent, LogsTableComponent, diff --git a/src/app/core/_components/menus/base-menu/base-menu.component.ts b/src/app/core/_components/menus/base-menu/base-menu.component.ts index 880cd01c..6035a1d5 100644 --- a/src/app/core/_components/menus/base-menu/base-menu.component.ts +++ b/src/app/core/_components/menus/base-menu/base-menu.component.ts @@ -92,6 +92,14 @@ export class BaseMenuComponent { return this.checkId('taskWrapperId') && 'priority' in this.data; } + /** + * Check if the data row is of type "Supertask". + * @returns `true` if the data row is a supertask; otherwise, `false`. + */ + protected isSupertask(): boolean { + return this.checkId('supertaskId'); + } + /** * Check if the data row is of type "Voucher". * @returns `true` if the data row is a voucher; otherwise, `false`. diff --git a/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.component.ts b/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.component.ts index 622efee0..93edc4e5 100644 --- a/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.component.ts +++ b/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.component.ts @@ -64,6 +64,8 @@ export class BulkActionMenuComponent BulkActionMenuLabel.DELETE_TASKS, BulkActionMenuLabel.ARCHIVE_TASKS ); + } else if (this.dataType === 'supertasks') { + this.setDeleteMenu(BulkActionMenuLabel.DELETE_SUPERTASKS); } else if (this.dataType === 'access-groups') { this.setDeleteMenu(BulkActionMenuLabel.DELETE_ACCESSGROUPS); } else if (this.dataType === 'permissions') { diff --git a/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.constants.ts b/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.constants.ts index 41d8ca73..77645271 100644 --- a/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.constants.ts +++ b/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.constants.ts @@ -3,6 +3,7 @@ export const BulkActionMenuLabel = { DELETE_NOTIFICATIONS: 'Delete Notification', DELETE_PRETASKS: 'Delete PreTasks', DELETE_TASKS: 'Delete Tasks', + DELETE_SUPERTASKS: 'Delete Supertasks', DELETE_HASHLISTS: 'Delete Hashlists', DELETE_HASHTYPES: 'Delete Hashtypes', DELETE_FILES: 'Delete Files', diff --git a/src/app/core/_components/menus/row-action-menu/row-action-menu.component.ts b/src/app/core/_components/menus/row-action-menu/row-action-menu.component.ts index ae424b45..f63f778b 100644 --- a/src/app/core/_components/menus/row-action-menu/row-action-menu.component.ts +++ b/src/app/core/_components/menus/row-action-menu/row-action-menu.component.ts @@ -64,6 +64,8 @@ export class RowActionMenuComponent this.setPretaskMenu(); } else if (this.isTaskWrapper()) { this.setTaskWrapperMenu(); + } else if (this.isSupertask()) { + this.setSupertaskMenu(); } else if (this.isHashlist()) { this.setHashlistMenu(); } else if (this.isCrackerBinaryType()) { @@ -287,6 +289,23 @@ export class RowActionMenuComponent } } + /** + * Sets the context menu items for a pretask data row. + */ + private setSupertaskMenu(): void { + this.setActionMenuItems(0, [ + this.getEditMenuItem(RowActionMenuLabel.EDIT_SUPERTASK) + ]); + this.addActionMenuItem(0, { + label: RowActionMenuLabel.APPLY_HASHLIST, + action: RowActionMenuAction.APPLY_TO_HASHLIST, + icon: RowActionMenuIcon.COPY + }); + this.setActionMenuItems(1, [ + this.getDeleteMenuItem(RowActionMenuLabel.DELETE_SUPERTASK) + ]); + } + /** * Creates an ActionMenuItem with delete action. * @param label The label for the menu item. 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 eca930f0..d9a8647d 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 @@ -2,6 +2,7 @@ export const RowActionMenuLabel = { ACTIVATE_USER: 'Activate User', ACTIVATE_AGENT: 'Activate Agent', ACTIVATE_NOTIFICATION: 'Activate Notification', + APPLY_HASHLIST: 'Apply Hashlist', DEACTIVATE_USER: 'Deactivate User', DEACTIVATE_AGENT: 'Deactivate Agent', DEACTIVATE_NOTIFICATION: 'Deactivate Notification', @@ -9,6 +10,7 @@ export const RowActionMenuLabel = { EDIT_NOTIFICATION: 'Edit Notification', EDIT_PRETASK: 'Edit Pretask', EDIT_TASK: 'Edit Task', + EDIT_SUPERTASK: 'Edit Supertask', EDIT_SUBTASKS: 'Edit Subtasks', EDIT_HASHLIST: 'Edit Hashlist', EDIT_SUPERHASHLIST: 'Edit Superhashlist', @@ -24,6 +26,7 @@ export const RowActionMenuLabel = { DELETE_CRACKER: 'Delete Cracker', DELETE_PRETASK: 'Delete Pretask', DELETE_TASK: 'Delete Task', + DELETE_SUPERTASK: 'Delete Supertask', DELETE_HASHLIST: 'Delete Hashlist', DELETE_HASHTYPE: 'Delete Hashtype', DELETE_SUPERHASHLIST: 'Delete Superhashlist', @@ -49,6 +52,7 @@ export const RowActionMenuAction = { EDIT: 'edit', DELETE: 'delete', ARCHIVE: 'archive', + APPLY_TO_HASHLIST: 'apply-to-hashlist', COPY_TO_TASK: 'copy-to-task', COPY_TO_PRETASK: 'copy-to-pretask', EDIT_SUBTASKS: 'edit-subtasks', diff --git a/src/app/core/_components/tables/ht-table/ht-table.models.ts b/src/app/core/_components/tables/ht-table/ht-table.models.ts index f1525149..010126e0 100644 --- a/src/app/core/_components/tables/ht-table/ht-table.models.ts +++ b/src/app/core/_components/tables/ht-table/ht-table.models.ts @@ -20,6 +20,7 @@ export type DataType = | 'vouchers' | 'pretasks' | 'tasks' + | 'supertasks' | 'superhashlists'; export interface HTTableIcon { diff --git a/src/app/core/_components/tables/supertasks-table/supertasks-table.component.html b/src/app/core/_components/tables/supertasks-table/supertasks-table.component.html new file mode 100644 index 00000000..08e3a64c --- /dev/null +++ b/src/app/core/_components/tables/supertasks-table/supertasks-table.component.html @@ -0,0 +1,17 @@ + diff --git a/src/app/core/_components/tables/supertasks-table/supertasks-table.component.ts b/src/app/core/_components/tables/supertasks-table/supertasks-table.component.ts new file mode 100644 index 00000000..8fcee9d7 --- /dev/null +++ b/src/app/core/_components/tables/supertasks-table/supertasks-table.component.ts @@ -0,0 +1,273 @@ +/* eslint-disable @angular-eslint/component-selector */ +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { + HTTableColumn, + HTTableIcon, + HTTableRouterLink +} from '../ht-table/ht-table.models'; +import { + SupertasksTableCol, + SupertasksTableColumnLabel +} from './supertasks-table.constants'; +import { catchError, forkJoin } from 'rxjs'; + +import { ActionMenuEvent } from '../../menus/action-menu/action-menu.model'; +import { BaseTableComponent } from '../base-table/base-table.component'; +import { BulkActionMenuAction } from '../../menus/bulk-action-menu/bulk-action-menu.constants'; +import { Cacheable } from 'src/app/core/_decorators/cacheable'; +import { DialogData } from '../table-dialog/table-dialog.model'; +import { ExportMenuAction } from '../../menus/export-menu/export-menu.constants'; +import { Pretask } from 'src/app/core/_models/pretask.model'; +import { RowActionMenuAction } from '../../menus/row-action-menu/row-action-menu.constants'; +import { SERV } from 'src/app/core/_services/main.config'; +import { SuperTask } from 'src/app/core/_models/supertask.model'; +import { SuperTasksDataSource } from 'src/app/core/_datasources/super-tasks.datasource'; +import { TableDialogComponent } from '../table-dialog/table-dialog.component'; + +@Component({ + selector: 'supertasks-table', + templateUrl: './supertasks-table.component.html' +}) +export class SuperTasksTableComponent + extends BaseTableComponent + implements OnInit, OnDestroy +{ + tableColumns: HTTableColumn[] = []; + dataSource: SuperTasksDataSource; + + ngOnInit(): void { + this.setColumnLabels(SupertasksTableColumnLabel); + this.tableColumns = this.getColumns(); + this.dataSource = new SuperTasksDataSource( + this.cdr, + this.gs, + this.uiService + ); + this.dataSource.setColumns(this.tableColumns); + this.dataSource.loadAll(); + } + + ngOnDestroy(): void { + for (const sub of this.subscriptions) { + sub.unsubscribe(); + } + } + + filter(item: Pretask, filterValue: string): boolean { + return ( + item.taskName.toLowerCase().includes(filterValue) || + item.attackCmd.toLowerCase().includes(filterValue) + ); + } + + getColumns(): HTTableColumn[] { + const tableColumns = [ + { + id: SupertasksTableCol.ID, + dataKey: '_id', + isSortable: true, + export: async (supertask: SuperTask) => supertask._id + '' + }, + { + id: SupertasksTableCol.NAME, + dataKey: 'supertaskName', + // routerLink: (pretask: Pretask) => this.renderPretaskLink(pretask), + isSortable: true, + export: async (supertask: SuperTask) => supertask.supertaskName + }, + { + id: SupertasksTableCol.PRETASKS, + dataKey: 'attackCmd', + isSortable: true + } + ]; + + return tableColumns; + } + + openDialog(data: DialogData) { + const dialogRef = this.dialog.open(TableDialogComponent, { + data: data, + width: '450px' + }); + + this.subscriptions.push( + dialogRef.afterClosed().subscribe((result) => { + if (result && result.action) { + switch (result.action) { + case RowActionMenuAction.DELETE: + this.rowActionDelete(result.data); + break; + case BulkActionMenuAction.DELETE: + this.bulkActionDelete(result.data); + break; + } + } + }) + ); + } + + // --- Action functions --- + + exportActionClicked(event: ActionMenuEvent): void { + switch (event.menuItem.action) { + case ExportMenuAction.EXCEL: + this.exportService.toExcel( + 'hashtopolis-supertasks', + this.tableColumns, + event.data, + SupertasksTableColumnLabel + ); + break; + case ExportMenuAction.CSV: + this.exportService.toCsv( + 'hashtopolis-supertasks', + this.tableColumns, + event.data, + SupertasksTableColumnLabel + ); + break; + case ExportMenuAction.COPY: + this.exportService + .toClipboard( + this.tableColumns, + event.data, + SupertasksTableColumnLabel + ) + .then(() => { + this.snackBar.open( + 'The selected rows are copied to the clipboard', + 'Close' + ); + }); + break; + } + } + + rowActionClicked(event: ActionMenuEvent): void { + switch (event.menuItem.action) { + case RowActionMenuAction.EDIT: + this.rowActionEdit(event.data); + break; + // ADD Apply to hashlist + // case RowActionMenuAction.COPY_TO_TASK: + // console.log('Copy to Task clicked:', event.data); + // this.rowActionCopyToTask(event.data); + // break; + case RowActionMenuAction.DELETE: + this.openDialog({ + rows: [event.data], + title: `Deleting Supertask ${event.data.supertaskName} ...`, + icon: 'warning', + body: `Are you sure you want to delete it? Note that this action cannot be undone.`, + warn: true, + action: event.menuItem.action + }); + break; + } + } + + bulkActionClicked(event: ActionMenuEvent): void { + switch (event.menuItem.action) { + case BulkActionMenuAction.DELETE: + this.openDialog({ + rows: event.data, + title: `Deleting ${event.data.length} supertasks ...`, + icon: 'warning', + body: `Are you sure you want to delete the above supertasks? Note that this action cannot be undone.`, + warn: true, + listAttribute: 'taskName', + action: event.menuItem.action + }); + break; + } + } + + /** + * @todo Implement error handling. + */ + private bulkActionDelete(supertask: SuperTask[]): void { + const requests = supertask.map((supertask: SuperTask) => { + return this.gs.delete(SERV.SUPER_TASKS, supertask._id); + }); + + this.subscriptions.push( + forkJoin(requests) + .pipe( + catchError((error) => { + console.error('Error during deletion:', error); + return []; + }) + ) + .subscribe((results) => { + this.snackBar.open( + `Successfully deleted ${results.length} supertasks!`, + 'Close' + ); + this.reload(); + }) + ); + } + + @Cacheable(['_id', 'isSecret']) + // async renderSecretIcon(supertask: SuperTask): Promise { + // // const icons: HTTableIcon[] = []; + // // const secretFilesCount = supertask.pretaskFiles.reduce( + // // (sum, file) => sum + (file.isSecret ? 1 : 0), + // // 0 + // // ); + + // // if (secretFilesCount > 0) { + // // icons.push({ + // // name: 'lock', + // // tooltip: `Secret: ${secretFilesCount} ${ + // // secretFilesCount > 1 ? 'files' : 'file' + // // }` + // // }); + // // } + + // // return icons; + // } + @Cacheable(['_id']) + async renderPretaskLink(supertask: SuperTask): Promise { + return [ + { + routerLink: ['/tasks/preconfigured-tasks', supertask._id, 'edit'] + } + ]; + } + + /** + * @todo Implement error handling. + */ + private rowActionDelete(supertasks: SuperTask[]): void { + this.subscriptions.push( + this.gs + .delete(SERV.SUPER_TASKS, supertasks[0]._id) + .pipe( + catchError((error) => { + console.error('Error during deletion:', error); + return []; + }) + ) + .subscribe(() => { + this.snackBar.open('Successfully deleted supertask!', 'Close'); + this.reload(); + }) + ); + } + + private rowActionCopyToTask(supertask: SuperTask): void { + this.router.navigate(['/tasks/new-tasks', supertask._id, 'copypretask']); + } + + private rowActionCopyToPretask(supertask: SuperTask): void { + this.router.navigate(['/tasks/preconfigured-tasks', supertask._id, 'copy']); + } + + private rowActionEdit(supertask: SuperTask): void { + this.renderPretaskLink(supertask).then((links: HTTableRouterLink[]) => { + this.router.navigate(links[0].routerLink); + }); + } +} diff --git a/src/app/core/_components/tables/supertasks-table/supertasks-table.constants.ts b/src/app/core/_components/tables/supertasks-table/supertasks-table.constants.ts new file mode 100644 index 00000000..2bcfdda8 --- /dev/null +++ b/src/app/core/_components/tables/supertasks-table/supertasks-table.constants.ts @@ -0,0 +1,11 @@ +export enum SupertasksTableCol { + ID, + NAME, + PRETASKS +} + +export const SupertasksTableColumnLabel = { + [SupertasksTableCol.ID]: 'ID', + [SupertasksTableCol.NAME]: 'Name', + [SupertasksTableCol.PRETASKS]: 'Pretasks' +}; diff --git a/src/app/core/_datasources/super-tasks.datasource.ts b/src/app/core/_datasources/super-tasks.datasource.ts new file mode 100644 index 00000000..6329fbe0 --- /dev/null +++ b/src/app/core/_datasources/super-tasks.datasource.ts @@ -0,0 +1,48 @@ +import { catchError, finalize, of } from 'rxjs'; + +import { BaseDataSource } from './base.datasource'; +import { ListResponseWrapper } from '../_models/response.model'; +import { MatTableDataSourcePaginator } from '@angular/material/table'; +import { SERV } from '../_services/main.config'; +import { SuperTask } from '../_models/supertask.model'; + +export class SuperTasksDataSource extends BaseDataSource< + SuperTask, + MatTableDataSourcePaginator +> { + loadAll(): void { + this.loading = true; + + const startAt = this.currentPage * this.pageSize; + const params = { + maxResults: this.pageSize, + startAt: startAt, + expand: 'pretasks' + }; + + const supertasks$ = this.service.getAll(SERV.SUPER_TASKS, params); + + this.subscriptions.push( + supertasks$ + .pipe( + catchError(() => of([])), + finalize(() => (this.loading = false)) + ) + .subscribe((response: ListResponseWrapper) => { + const supertasks: SuperTask[] = response.values; + + this.setPaginationConfig( + this.pageSize, + this.currentPage, + response.total + ); + this.setData(supertasks); + }) + ); + } + + reload(): void { + this.clearSelection(); + this.loadAll(); + } +} diff --git a/src/app/core/_models/config-ui.model.ts b/src/app/core/_models/config-ui.model.ts index f6253124..db2bccbb 100644 --- a/src/app/core/_models/config-ui.model.ts +++ b/src/app/core/_models/config-ui.model.ts @@ -14,6 +14,7 @@ import { PermissionsTableCol } from '../_components/tables/permissions-table/per import { PreprocessorsTableCol } from '../_components/tables/preprocessors-table/preprocessors-table.constants'; import { PretasksTableCol } from '../_components/tables/pretasks-table/pretasks-table.constants'; import { SuperHashlistsTableCol } from '../_components/tables/super-hashlists-table/super-hashlists-table.constants'; +import { SupertasksTableCol } from '../_components/tables/supertasks-table/supertasks-table.constants'; import { TaskTableCol } from '../_components/tables/tasks-table/tasks-table.constants'; import { UsersTableCol } from '../_components/tables/users-table/users-table.constants'; @@ -159,6 +160,11 @@ export const uiConfigDefault: UIConfig = { TaskTableCol.DISPATCHED_SEARCHED, TaskTableCol.CRACKED ], + supertasksTable: [ + SupertasksTableCol.ID, + SupertasksTableCol.NAME, + SupertasksTableCol.PRETASKS + ], hashlistTasksTable: [ TaskTableCol.ID, TaskTableCol.NAME, diff --git a/src/app/core/_models/supertask.model.ts b/src/app/core/_models/supertask.model.ts index b6ea16f7..97bf8174 100644 --- a/src/app/core/_models/supertask.model.ts +++ b/src/app/core/_models/supertask.model.ts @@ -1,6 +1,6 @@ export interface SuperTask { - _id: number - _self: string - supertaskId: number - supertaskName: string -} \ No newline at end of file + _id: number; + _self: string; + supertaskId: number; + supertaskName: string; +} diff --git a/src/app/tasks/supertasks/supertasks.component.html b/src/app/tasks/supertasks/supertasks.component.html index 16329571..769b0212 100644 --- a/src/app/tasks/supertasks/supertasks.component.html +++ b/src/app/tasks/supertasks/supertasks.component.html @@ -1,6 +1,6 @@ - + - + + + + diff --git a/src/app/tasks/supertasks/supertasks.component.ts b/src/app/tasks/supertasks/supertasks.component.ts index f87d73d9..7d2d8c50 100644 --- a/src/app/tasks/supertasks/supertasks.component.ts +++ b/src/app/tasks/supertasks/supertasks.component.ts @@ -1,178 +1,23 @@ -import { faEdit, faTrash, faPlus, faAdd, faChevronRight, faChevronUp } from '@fortawesome/free-solid-svg-icons'; -import { Component, OnInit, ViewChild } from '@angular/core'; -import { DataTableDirective } from 'angular-datatables'; -import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; -import Swal from 'sweetalert2/dist/sweetalert2.js'; -import { Subject } from 'rxjs'; +import { Component } from '@angular/core'; import { ModalPretasksComponent } from './modal-pretasks/modal-pretasks.component'; -import { AlertService } from 'src/app/core/_services/shared/alert.service'; -import { GlobalService } from 'src/app/core/_services/main.service'; -import { environment } from './../../../environments/environment'; -import { PageTitle } from 'src/app/core/_decorators/autotitle'; -import { SERV } from '../../core/_services/main.config'; +import { AutoTitleService } from 'src/app/core/_services/shared/autotitle.service'; @Component({ selector: 'app-supertasks', templateUrl: './supertasks.component.html' }) -@PageTitle(['Show SuperTasks']) -export class SupertasksComponent implements OnInit { - - faChevronRight=faChevronRight; - faChevronUp=faChevronUp; - faTrash=faTrash; - faEdit=faEdit; - faPlus=faPlus; - faAdd=faAdd; - - allsupertasks: any = []; - pretasks: any = []; - - constructor( - private modalService: NgbModal, - private alert: AlertService, - private gs: GlobalService - ) { } - - @ViewChild(DataTableDirective, {static: false}) - dtElement: DataTableDirective; - - dtTrigger: Subject = new Subject(); - dtOptions: any = {}; - - ngOnDestroy(): void { - this.dtTrigger.unsubscribe(); - } - - private maxResults = environment.config.prodApiMaxResults; - - ngOnInit(): void { - - this.gs.getAll(SERV.SUPER_TASKS,{'maxResults': this.maxResults, 'expand': 'pretasks' }).subscribe((stasks: any) => { - this.allsupertasks = stasks.values; - this.dtTrigger.next(void 0); - }); - - const self = this; - this.dtOptions[0] = { - dom: 'Bfrtip', - bStateSave:true, - destroy: true, - select: { - style: 'multi', - }, - buttons: { - dom: { - button: { - className: 'dt-button buttons-collection btn btn-sm-dt btn-outline-gray-600-dt', - } - }, - buttons: [ - { - text: '↻', - autoClose: true, - action: function (e, dt, node, config) { - self.onRefresh(); - } - }, - { - extend: 'collection', - text: 'Export', - buttons: [ - { - extend: 'excelHtml5', - exportOptions: { - columns: [0, 1] - }, - }, - { - extend: 'print', - exportOptions: { - columns: [0, 1] - }, - customize: function ( win ) { - $(win.document.body) - .css( 'font-size', '10pt' ) - $(win.document.body).find( 'table' ) - .addClass( 'compact' ) - .css( 'font-size', 'inherit' ); - } - }, - { - extend: 'csvHtml5', - exportOptions: {modifier: {selected: true}}, - select: true, - customize: function (dt, csv) { - let data = ""; - for (let i = 0; i < dt.length; i++) { - data = "Show SuperTasks\n\n"+ dt; - } - return data; - } - }, - { - extend: 'copy', - } - ] - }, - { - extend: "pageLength", - className: "btn-sm" - } - ], - } - }; - +export class SupertasksComponent { + constructor(private titleService: AutoTitleService) { + titleService.set(['Show Preconfigured Task']); } - getPretasks(id: number){ - const ref = this.modalService.open(ModalPretasksComponent, { centered: true, size: 'xl' }); - const _filter = this.allsupertasks.filter(u=> u.supertaskId == id); - this.pretasks = _filter[0]['pretasks']; - ref.componentInstance.prep = _filter[0]['pretasks']; - ref.componentInstance.supertaskid = id; - ref.componentInstance.title = 'Edit Pretaks' - } - - onRefresh(){ - this.ngOnInit(); - this.rerender(); // rerender datatables - } - - rerender(): void { - this.dtElement.dtInstance.then((dtInstance: DataTables.Api) => { - // Destroy the table first - dtInstance.destroy(); - // Call the dtTrigger to rerender again - setTimeout(() => { - this.dtTrigger['new'].next(); - }); - }); - } - - onDelete(id: number, name: string){ - this.alert.deleteConfirmation(name,'Supertasks').then((confirmed) => { - if (confirmed) { - // Deletion - this.gs.delete(SERV.SUPER_TASKS, id).subscribe(() => { - // Successful deletion - this.alert.okAlert(`Deleted Supertask ${name}`, ''); - this.onRefreshTable(); // Refresh the table - }); - } else { - // Handle cancellation - this.alert.okAlert(`Supertask ${name} is safe!`,''); - } - }); - } - - onRefreshTable(){ - setTimeout(() => { - this.ngOnInit(); - this.rerender(); // rerender datatables - },2000); - } - - + // getPretasks(id: number){ + // const ref = this.modalService.open(ModalPretasksComponent, { centered: true, size: 'xl' }); + // const _filter = this.allsupertasks.filter(u=> u.supertaskId == id); + // this.pretasks = _filter[0]['pretasks']; + // ref.componentInstance.prep = _filter[0]['pretasks']; + // ref.componentInstance.supertaskid = id; + // ref.componentInstance.title = 'Edit Pretaks' + // } } From 52ca45884e2c97335778ad67b92633ab3fc63c45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Thu, 14 Dec 2023 16:01:49 +0000 Subject: [PATCH 353/419] Remove unused libraries --- .../preconfigured-tasks.component.ts | 23 +------------------ 1 file changed, 1 insertion(+), 22 deletions(-) diff --git a/src/app/tasks/preconfigured-tasks/preconfigured-tasks.component.ts b/src/app/tasks/preconfigured-tasks/preconfigured-tasks.component.ts index d483afce..06b53785 100644 --- a/src/app/tasks/preconfigured-tasks/preconfigured-tasks.component.ts +++ b/src/app/tasks/preconfigured-tasks/preconfigured-tasks.component.ts @@ -1,26 +1,5 @@ -import { - faArchive, - faBookmark, - faCopy, - faEdit, - faFileExport, - faFileImport, - faHomeAlt, - faLock, - faPlus, - faTrash -} from '@fortawesome/free-solid-svg-icons'; -import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core'; -import { environment } from './../../../environments/environment'; -import { DataTableDirective } from 'angular-datatables'; -import { Subject, Subscription } from 'rxjs'; - import { AutoTitleService } from 'src/app/core/_services/shared/autotitle.service'; -import { AlertService } from 'src/app/core/_services/shared/alert.service'; -import { GlobalService } from 'src/app/core/_services/main.service'; -import { SERV } from '../../core/_services/main.config'; - -declare let $: any; +import { Component } from '@angular/core'; @Component({ selector: 'app-preconfigured-tasks', From e3e2d8d0f7a8a43746e3f7655acf116c00927b85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Thu, 14 Dec 2023 16:02:53 +0000 Subject: [PATCH 354/419] Remove unused line --- .../tables/pretasks-table/pretasks-table.component.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/app/core/_components/tables/pretasks-table/pretasks-table.component.ts b/src/app/core/_components/tables/pretasks-table/pretasks-table.component.ts index 19c4092c..981d9db8 100644 --- a/src/app/core/_components/tables/pretasks-table/pretasks-table.component.ts +++ b/src/app/core/_components/tables/pretasks-table/pretasks-table.component.ts @@ -56,8 +56,6 @@ export class PretasksTableComponent ); } - // - getColumns(): HTTableColumn[] { const tableColumns = [ { From c9dd874a5f54758c2e0b04b6b8616ba2792f700a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Thu, 14 Dec 2023 17:26:18 +0000 Subject: [PATCH 355/419] Supertasks table --- .../row-action-menu.component.ts | 5 ++ .../supertasks-table.component.ts | 65 +++++++------------ src/app/core/_models/supertask.model.ts | 3 + .../supertasks/supertasks.component.html | 47 ++------------ 4 files changed, 38 insertions(+), 82 deletions(-) diff --git a/src/app/core/_components/menus/row-action-menu/row-action-menu.component.ts b/src/app/core/_components/menus/row-action-menu/row-action-menu.component.ts index f63f778b..964d0d30 100644 --- a/src/app/core/_components/menus/row-action-menu/row-action-menu.component.ts +++ b/src/app/core/_components/menus/row-action-menu/row-action-menu.component.ts @@ -301,6 +301,11 @@ export class RowActionMenuComponent action: RowActionMenuAction.APPLY_TO_HASHLIST, icon: RowActionMenuIcon.COPY }); + this.addActionMenuItem(0, { + label: RowActionMenuLabel.EDIT_SUBTASKS, + action: RowActionMenuAction.EDIT_SUBTASKS, + icon: RowActionMenuIcon.EDIT + }); this.setActionMenuItems(1, [ this.getDeleteMenuItem(RowActionMenuLabel.DELETE_SUPERTASK) ]); diff --git a/src/app/core/_components/tables/supertasks-table/supertasks-table.component.ts b/src/app/core/_components/tables/supertasks-table/supertasks-table.component.ts index 8fcee9d7..bb938fbe 100644 --- a/src/app/core/_components/tables/supertasks-table/supertasks-table.component.ts +++ b/src/app/core/_components/tables/supertasks-table/supertasks-table.component.ts @@ -53,11 +53,8 @@ export class SuperTasksTableComponent } } - filter(item: Pretask, filterValue: string): boolean { - return ( - item.taskName.toLowerCase().includes(filterValue) || - item.attackCmd.toLowerCase().includes(filterValue) - ); + filter(item: SuperTask, filterValue: string): boolean { + return item.supertaskName.toLowerCase().includes(filterValue); } getColumns(): HTTableColumn[] { @@ -71,14 +68,18 @@ export class SuperTasksTableComponent { id: SupertasksTableCol.NAME, dataKey: 'supertaskName', - // routerLink: (pretask: Pretask) => this.renderPretaskLink(pretask), + routerLink: (supertask: SuperTask) => + this.renderSupertaskLink(supertask), isSortable: true, export: async (supertask: SuperTask) => supertask.supertaskName }, { id: SupertasksTableCol.PRETASKS, - dataKey: 'attackCmd', - isSortable: true + dataKey: 'pretasks', + isSortable: true, + render: (supertask: SuperTask) => supertask.pretasks.length, + export: async (supertask: SuperTask) => + supertask.pretasks.length.toString() } ]; @@ -149,11 +150,12 @@ export class SuperTasksTableComponent case RowActionMenuAction.EDIT: this.rowActionEdit(event.data); break; - // ADD Apply to hashlist - // case RowActionMenuAction.COPY_TO_TASK: - // console.log('Copy to Task clicked:', event.data); - // this.rowActionCopyToTask(event.data); - // break; + case RowActionMenuAction.APPLY_TO_HASHLIST: + this.rowActionApplyToHashlist(event.data); + break; + case RowActionMenuAction.EDIT_SUBTASKS: + this.rowActionEditSubtasks(event.data); + break; case RowActionMenuAction.DELETE: this.openDialog({ rows: [event.data], @@ -176,7 +178,7 @@ export class SuperTasksTableComponent icon: 'warning', body: `Are you sure you want to delete the above supertasks? Note that this action cannot be undone.`, warn: true, - listAttribute: 'taskName', + listAttribute: 'supertaskName', action: event.menuItem.action }); break; @@ -209,30 +211,13 @@ export class SuperTasksTableComponent ); } - @Cacheable(['_id', 'isSecret']) - // async renderSecretIcon(supertask: SuperTask): Promise { - // // const icons: HTTableIcon[] = []; - // // const secretFilesCount = supertask.pretaskFiles.reduce( - // // (sum, file) => sum + (file.isSecret ? 1 : 0), - // // 0 - // // ); - - // // if (secretFilesCount > 0) { - // // icons.push({ - // // name: 'lock', - // // tooltip: `Secret: ${secretFilesCount} ${ - // // secretFilesCount > 1 ? 'files' : 'file' - // // }` - // // }); - // // } - - // // return icons; - // } @Cacheable(['_id']) - async renderPretaskLink(supertask: SuperTask): Promise { + async renderSupertaskLink( + supertask: SuperTask + ): Promise { return [ { - routerLink: ['/tasks/preconfigured-tasks', supertask._id, 'edit'] + routerLink: ['/tasks/', supertask._id, 'edit'] } ]; } @@ -257,16 +242,16 @@ export class SuperTasksTableComponent ); } - private rowActionCopyToTask(supertask: SuperTask): void { - this.router.navigate(['/tasks/new-tasks', supertask._id, 'copypretask']); + private rowActionApplyToHashlist(supertask: SuperTask): void { + this.router.navigate(['/tasks/', supertask._id, 'applyhashlist']); } - private rowActionCopyToPretask(supertask: SuperTask): void { - this.router.navigate(['/tasks/preconfigured-tasks', supertask._id, 'copy']); + private rowActionEditSubtasks(supertask: SuperTask): void { + console.log('here'); } private rowActionEdit(supertask: SuperTask): void { - this.renderPretaskLink(supertask).then((links: HTTableRouterLink[]) => { + this.renderSupertaskLink(supertask).then((links: HTTableRouterLink[]) => { this.router.navigate(links[0].routerLink); }); } diff --git a/src/app/core/_models/supertask.model.ts b/src/app/core/_models/supertask.model.ts index 97bf8174..86b8dd75 100644 --- a/src/app/core/_models/supertask.model.ts +++ b/src/app/core/_models/supertask.model.ts @@ -1,6 +1,9 @@ +import { Pretask } from './pretask.model'; + export interface SuperTask { _id: number; _self: string; + pretasks?: Pretask[]; supertaskId: number; supertaskName: string; } diff --git a/src/app/tasks/supertasks/supertasks.component.html b/src/app/tasks/supertasks/supertasks.component.html index 769b0212..12329678 100644 --- a/src/app/tasks/supertasks/supertasks.component.html +++ b/src/app/tasks/supertasks/supertasks.component.html @@ -1,45 +1,3 @@ - - - + + + From b9302515c8ac304a6f04591d1eac0fe2227128aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Mon, 18 Dec 2023 11:29:36 +0000 Subject: [PATCH 356/419] Health Check View --- .../edit-health-checks.component.html | 63 +----- .../edit-health-checks.component.ts | 193 +++++------------- .../_components/core-components.module.ts | 3 + .../menus/base-menu/base-menu.component.ts | 8 + .../health-checks-view-table.component.html | 15 ++ .../health-checks-view-table.component.ts | 164 +++++++++++++++ .../health-checks-view-table.constants.ts | 19 ++ .../tables/ht-table/ht-table.models.ts | 1 + .../health-checks-view.datasource.ts | 63 ++++++ src/app/core/_models/config-ui.model.ts | 10 + src/app/core/_models/health-check.model.ts | 15 ++ 11 files changed, 353 insertions(+), 201 deletions(-) create mode 100644 src/app/core/_components/tables/health-checks-view-table/health-checks-view-table.component.html create mode 100644 src/app/core/_components/tables/health-checks-view-table/health-checks-view-table.component.ts create mode 100644 src/app/core/_components/tables/health-checks-view-table/health-checks-view-table.constants.ts create mode 100644 src/app/core/_datasources/health-checks-view.datasource.ts diff --git a/src/app/config/health-checks/edit-health-check/edit-health-checks.component.html b/src/app/config/health-checks/edit-health-check/edit-health-checks.component.html index ebc81e02..e9183106 100644 --- a/src/app/config/health-checks/edit-health-check/edit-health-checks.component.html +++ b/src/app/config/health-checks/edit-health-check/edit-health-checks.component.html @@ -1,62 +1,15 @@
- - + +
- + - - - - - - - - - - - - - - - - - - - - - - - - -
IDAgentStatusTime Required# DevicesCrackedErrors
{{ ha.healthCheckAgentId }} - {{ ha.agentName | shortenString:15 }} - - - - {{ ha.status | HCstatus | lowercase | titlecase }} -
- {{ ha.start }} -
-
-
- {{ ha.numGpus }} -
-
-
- {{ ha.cracked }} -
-
-
- {{ ha.errors }} -
-
+ +
- - - - N/A - - diff --git a/src/app/config/health-checks/edit-health-check/edit-health-checks.component.ts b/src/app/config/health-checks/edit-health-check/edit-health-checks.component.ts index af318d3a..6dcb57cd 100644 --- a/src/app/config/health-checks/edit-health-check/edit-health-checks.component.ts +++ b/src/app/config/health-checks/edit-health-check/edit-health-checks.component.ts @@ -1,169 +1,70 @@ -import { Component, OnInit, ViewChild } from '@angular/core'; -import { faEye } from '@fortawesome/free-solid-svg-icons'; -import { ActivatedRoute, Params } from '@angular/router'; -import { DataTableDirective } from 'angular-datatables'; -import { Subject } from 'rxjs'; +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; -import { UIConfigService } from 'src/app/core/_services/shared/storage.service'; +import { AutoTitleService } from 'src/app/core/_services/shared/autotitle.service'; import { GlobalService } from 'src/app/core/_services/main.service'; +import { HealthCheck } from 'src/app/core/_models/health-check.model'; import { PageTitle } from 'src/app/core/_decorators/autotitle'; -import { environment } from 'src/environments/environment'; import { SERV } from '../../../core/_services/main.config'; +import { UnsubscribeService } from 'src/app/core/_services/unsubscribe.service'; @Component({ selector: 'app-edit-health-checks', templateUrl: './edit-health-checks.component.html' }) -@PageTitle(['Edit Health Checks']) -export class EditHealthChecksComponent implements OnInit { +export class EditHealthChecksComponent implements OnInit, OnDestroy { + // The index of the edited health check. editedHealthCIndex: number; - - faEye=faEye; - - @ViewChild(DataTableDirective, {static: false}) - dtElement: DataTableDirective; - - dtTrigger: Subject = new Subject(); - dtOptions: any = {}; - + // The health check object. + public healthc: HealthCheck; + + /** + * Constructs a new instance of the YourComponentName class. + * @param {AutoTitleService} titleService - The service for managing auto titles. + * @param {ActivatedRoute} route - The activated route. + * @param {GlobalService} gs - The global service. + */ constructor( - private uiService: UIConfigService, - private route:ActivatedRoute, + private unsubscribeService: UnsubscribeService, + private titleService: AutoTitleService, + private route: ActivatedRoute, private gs: GlobalService - ) { } - - private maxResults = environment.config.prodApiMaxResults; - - public healthc: { - attackCmd: string, - checkType: number, - crackerBinaryId: number, - expectedCracks: number, - hashtypeId: number, - hashtypeName: string, - healthCheckId: number, - status: number, - time: number - }[] = []; - - public healthca: { - healthCheckAgentId: number; - healthCheckId: number; - agentId: number; - status: number; - cracked: number; - numGpus: number; - start: number; - end: number; - errors: string; - agentName: string; - }[] = []; - - // healthca: [] - - ngOnInit(): void { + ) { + this.onInitialize(); + titleService.set(['Edit Health Checks']); + } + /** + * Component initialization get ID to use in Table component + */ + onInitialize(): void { this.editedHealthCIndex = +this.route.snapshot.params['id']; - - this.gs.get(SERV.HEALTH_CHECKS,this.editedHealthCIndex).subscribe((hc: any) => { - this.healthc = hc; - }); - - this.agentsInit(); - } - agentsInit(){ - const paramshc = {'maxResults': this.maxResults, 'filter': 'healthCheckId='+this.editedHealthCIndex+''}; - const paramsa = {'maxResults': this.maxResults}; - this.gs.getAll(SERV.HEALTH_CHECKS_AGENTS,paramshc).subscribe((hc: any) => { - this.gs.getAll(SERV.AGENTS,paramsa).subscribe((agents: any) => { - this.healthca = hc.values.map(mainObject => { - const matchAObject = agents.values.find(element => element.agentId === mainObject.agentId) - return { ...mainObject, ...matchAObject } - }) - this.dtTrigger.next(void 0); - }); - }); - const self = this; - this.dtOptions = { - dom: 'Bfrtip', - scrollX: true, - pageLength: 25, - lengthMenu: [ - [10, 25, 50, 100, 250, -1], - [10, 25, 50, 100, 250, 'All'] - ], - stateSave: true, - select: true, - buttons: { - dom: { - button: { - className: 'dt-button buttons-collection btn btn-sm-dt btn-outline-gray-600-dt' - } - }, - buttons: [ - { - text: '↻', - autoClose: true, - action: function (e, dt, node, config) { - self.onRefresh(); - } - }, - { - extend: 'collection', - text: 'Export', - buttons: [ - { - extend: 'excelHtml5', - }, - { - extend: 'print', - customize: function ( win ) { - $(win.document.body) - .css( 'font-size', '10pt' ) - $(win.document.body).find( 'table' ) - .addClass( 'compact' ) - .css( 'font-size', 'inherit' ); - } - }, - { - extend: 'csvHtml5', - exportOptions: {modifier: {selected: true}}, - select: true, - customize: function (dt, csv) { - let data = ""; - for (let i = 0; i < dt.length; i++) { - data = "HealthChecks\n\n"+ dt; - } - return data; - } - }, - { - extend: 'copy', - } - ] - } - ], - }} - - + /** + * Lifecycle hook called after component initialization. + */ + ngOnInit(): void { + this.loadData(); } - onRefresh(){ - this.rerender(); - this.ngOnInit(); + /** + * Lifecycle hook called before the component is destroyed. + * Unsubscribes from all subscriptions to prevent memory leaks. + */ + ngOnDestroy(): void { + this.unsubscribeService.unsubscribeAll(); } - rerender(): void { - this.dtElement.dtInstance.then((dtInstance: DataTables.Api) => { - dtInstance.destroy(); - setTimeout(() => { - this.dtTrigger['new'].next(); + /** + * Loads data, specifically health checks, for the view component. + */ + loadData(): void { + const loadSubscription$ = this.gs + .get(SERV.HEALTH_CHECKS, this.editedHealthCIndex) + .subscribe((healthCheck: HealthCheck) => { + this.healthc = healthCheck; }); - }); + this.unsubscribeService.add(loadSubscription$); } - - } - diff --git a/src/app/core/_components/core-components.module.ts b/src/app/core/_components/core-components.module.ts index cde7d514..3bfd7ed9 100644 --- a/src/app/core/_components/core-components.module.ts +++ b/src/app/core/_components/core-components.module.ts @@ -54,6 +54,7 @@ import { TableTruncateComponent } from './tables/table-truncate/table-truncate.c import { TasksTableComponent } from './tables/tasks-table/tasks-table.component'; import { UsersTableComponent } from './tables/users-table/users-table.component'; import { VouchersTableComponent } from './tables/vouchers-table/vouchers-table.component'; +import { HealthChecksViewTableComponent } from './tables/health-checks-view-table/health-checks-view-table.component'; @NgModule({ declarations: [ @@ -80,6 +81,7 @@ import { VouchersTableComponent } from './tables/vouchers-table/vouchers-table.c PreprocessorsTableComponent, AgentBinariesTableComponent, HealthChecksTableComponent, + HealthChecksViewTableComponent, LogsTableComponent, UsersTableComponent, AccessGroupsTableComponent, @@ -137,6 +139,7 @@ import { VouchersTableComponent } from './tables/vouchers-table/vouchers-table.c PreprocessorsTableComponent, AgentBinariesTableComponent, HealthChecksTableComponent, + HealthChecksViewTableComponent, LogsTableComponent, UsersTableComponent, AccessGroupsTableComponent, diff --git a/src/app/core/_components/menus/base-menu/base-menu.component.ts b/src/app/core/_components/menus/base-menu/base-menu.component.ts index 43de7be9..a36445e0 100644 --- a/src/app/core/_components/menus/base-menu/base-menu.component.ts +++ b/src/app/core/_components/menus/base-menu/base-menu.component.ts @@ -124,6 +124,14 @@ export class BaseMenuComponent { return this.checkId('healthCheckId'); } + /** + * Check if the data row is of type "HealthCheckEdit". + * @returns `true` if the data row is a health check; otherwise, `false`. + */ + protected isHealthCheckEdit(): boolean { + return this.checkId('healthCheckId') && 'healthCheckAgentId' in this.data; + } + /** * Check if the data row is of type "File". * @returns `true` if the data row is a file; otherwise, `false`. diff --git a/src/app/core/_components/tables/health-checks-view-table/health-checks-view-table.component.html b/src/app/core/_components/tables/health-checks-view-table/health-checks-view-table.component.html new file mode 100644 index 00000000..4576121a --- /dev/null +++ b/src/app/core/_components/tables/health-checks-view-table/health-checks-view-table.component.html @@ -0,0 +1,15 @@ + diff --git a/src/app/core/_components/tables/health-checks-view-table/health-checks-view-table.component.ts b/src/app/core/_components/tables/health-checks-view-table/health-checks-view-table.component.ts new file mode 100644 index 00000000..0b30add3 --- /dev/null +++ b/src/app/core/_components/tables/health-checks-view-table/health-checks-view-table.component.ts @@ -0,0 +1,164 @@ +/* eslint-disable @angular-eslint/component-selector */ +import { Component, Input, OnDestroy, OnInit } from '@angular/core'; +import { + HealthChecksViewTableCol, + HealthChecksViewTableColumnLabel +} from './health-checks-view-table.constants'; +import { catchError, forkJoin } from 'rxjs'; + +import { ActionMenuEvent } from '../../menus/action-menu/action-menu.model'; +import { BaseTableComponent } from '../base-table/base-table.component'; +import { BulkActionMenuAction } from '../../menus/bulk-action-menu/bulk-action-menu.constants'; +import { DialogData } from '../table-dialog/table-dialog.model'; +import { ExportMenuAction } from '../../menus/export-menu/export-menu.constants'; +import { HTTableColumn } from '../ht-table/ht-table.models'; +import { + HealthCheck, + HealthCheckView +} from 'src/app/core/_models/health-check.model'; +import { HealthChecksViewDataSource } from 'src/app/core/_datasources/health-checks-view.datasource'; +import { RowActionMenuAction } from '../../menus/row-action-menu/row-action-menu.constants'; +import { SERV } from 'src/app/core/_services/main.config'; +import { TableDialogComponent } from '../table-dialog/table-dialog.component'; +import { formatUnixTimestamp } from 'src/app/shared/utils/datetime'; +import { HealthChecksTableStatusLabel } from '../health-checks-table/health-checks-table.constants'; + +@Component({ + selector: 'health-checks-view-table', + templateUrl: './health-checks-view-table.component.html' +}) +export class HealthChecksViewTableComponent + extends BaseTableComponent + implements OnInit, OnDestroy +{ + @Input() healthCheckId = 0; + + tableColumns: HTTableColumn[] = []; + dataSource: HealthChecksViewDataSource; + + ngOnInit(): void { + this.setColumnLabels(HealthChecksViewTableColumnLabel); + this.tableColumns = this.getColumns(); + this.dataSource = new HealthChecksViewDataSource( + this.cdr, + this.gs, + this.uiService + ); + if (this.healthCheckId) { + this.dataSource.setHealthCheckId(this.healthCheckId); + } + this.dataSource.setColumns(this.tableColumns); + this.dataSource.loadAll(); + } + + ngOnDestroy(): void { + for (const sub of this.subscriptions) { + sub.unsubscribe(); + } + } + + filter(item: HealthCheckView, filterValue: string): boolean { + if (item.agentName.toLowerCase().includes(filterValue)) { + return true; + } + + return false; + } + + getColumns(): HTTableColumn[] { + const tableColumns = [ + { + id: HealthChecksViewTableCol.AGENT_ID, + dataKey: 'healthCheckAgentId', + isSortable: true, + export: async (healthCheckView: HealthCheckView) => + healthCheckView.healthCheckAgentId + '' + }, + { + id: HealthChecksViewTableCol.AGENT_NAME, + dataKey: 'agentName', + isSortable: true, + export: async (healthCheckView: HealthCheckView) => + healthCheckView.agentName + '' + }, + { + id: HealthChecksViewTableCol.STATUS, + dataKey: 'status', + render: (healthCheckView: HealthCheckView) => + HealthChecksTableStatusLabel[healthCheckView.status], + isSortable: true, + export: async (healthCheckView: HealthCheckView) => + HealthChecksTableStatusLabel[healthCheckView.status] + }, + { + id: HealthChecksViewTableCol.START, + dataKey: 'start', + isSortable: true, + render: (healthCheckView: HealthCheckView) => + formatUnixTimestamp(healthCheckView.start, this.dateFormat), + export: async (healthCheckView: HealthCheckView) => + formatUnixTimestamp(healthCheckView.start, this.dateFormat) + }, + { + id: HealthChecksViewTableCol.GPUS, + dataKey: 'numGpus', + isSortable: true, + export: async (healthCheckView: HealthCheckView) => + healthCheckView.numGpus + '' + }, + { + id: HealthChecksViewTableCol.CRACKED, + dataKey: 'numGpus', + isSortable: true, + export: async (healthCheckView: HealthCheckView) => + healthCheckView.cracked + '' + }, + { + id: HealthChecksViewTableCol.ERRORS, + dataKey: 'numGpus', + isSortable: true, + export: async (healthCheckView: HealthCheckView) => + healthCheckView.errors + '' + } + ]; + + return tableColumns; + } + + // --- Action functions --- + + exportActionClicked(event: ActionMenuEvent): void { + switch (event.menuItem.action) { + case ExportMenuAction.EXCEL: + this.exportService.toExcel( + 'hashtopolis-health-checks-view', + this.tableColumns, + event.data, + HealthChecksViewTableColumnLabel + ); + break; + case ExportMenuAction.CSV: + this.exportService.toCsv( + 'hashtopolis-health-checks-view', + this.tableColumns, + event.data, + HealthChecksViewTableColumnLabel + ); + break; + case ExportMenuAction.COPY: + this.exportService + .toClipboard( + this.tableColumns, + event.data, + HealthChecksViewTableColumnLabel + ) + .then(() => { + this.snackBar.open( + 'The selected rows are copied to the clipboard', + 'Close' + ); + }); + break; + } + } +} diff --git a/src/app/core/_components/tables/health-checks-view-table/health-checks-view-table.constants.ts b/src/app/core/_components/tables/health-checks-view-table/health-checks-view-table.constants.ts new file mode 100644 index 00000000..db9cf80b --- /dev/null +++ b/src/app/core/_components/tables/health-checks-view-table/health-checks-view-table.constants.ts @@ -0,0 +1,19 @@ +export enum HealthChecksViewTableCol { + AGENT_ID, + AGENT_NAME, + STATUS, + START, + GPUS, + CRACKED, + ERRORS +} + +export const HealthChecksViewTableColumnLabel = { + [HealthChecksViewTableCol.AGENT_ID]: 'Id', + [HealthChecksViewTableCol.AGENT_NAME]: 'Name', + [HealthChecksViewTableCol.STATUS]: 'Status', + [HealthChecksViewTableCol.START]: 'Start', + [HealthChecksViewTableCol.GPUS]: 'Number of Devices', + [HealthChecksViewTableCol.CRACKED]: 'Cracked', + [HealthChecksViewTableCol.ERRORS]: 'Errors' +}; diff --git a/src/app/core/_components/tables/ht-table/ht-table.models.ts b/src/app/core/_components/tables/ht-table/ht-table.models.ts index be3e605b..9b730a66 100644 --- a/src/app/core/_components/tables/ht-table/ht-table.models.ts +++ b/src/app/core/_components/tables/ht-table/ht-table.models.ts @@ -14,6 +14,7 @@ export type DataType = | 'notifications' | 'agent-binaries' | 'health-checks' + | 'health-checks-view' | 'logs' | 'permissions' | 'cracks' diff --git a/src/app/core/_datasources/health-checks-view.datasource.ts b/src/app/core/_datasources/health-checks-view.datasource.ts new file mode 100644 index 00000000..1dff6f25 --- /dev/null +++ b/src/app/core/_datasources/health-checks-view.datasource.ts @@ -0,0 +1,63 @@ +import { catchError, finalize, forkJoin, of } from 'rxjs'; +import { Agent } from '../_models/agent.model'; +import { BaseDataSource } from './base.datasource'; +import { HealthCheckView } from '../_models/health-check.model'; +import { ListResponseWrapper } from '../_models/response.model'; +import { SERV } from '../_services/main.config'; + +export class HealthChecksViewDataSource extends BaseDataSource { + private _healthCheckId = 0; + + setHealthCheckId(healthCheckId: number): void { + this._healthCheckId = healthCheckId; + } + + loadAll(): void { + this.loading = true; + + /** + * @todo Extend health checks api response with Agents + */ + const healthChecks$ = this.service.getAll(SERV.HEALTH_CHECKS_AGENTS, { + maxResults: this.pageSize, + filter: `healthCheckId=${this._healthCheckId}` + }); + + const agents$ = this.service.getAll(SERV.AGENTS, { + maxResults: this.maxResults + }); + + this.subscriptions.push( + forkJoin([healthChecks$, agents$]) + .pipe( + catchError(() => of([])), + finalize(() => (this.loading = false)) + ) + .subscribe( + ([healthCheckResponse, hashTypesResponse]: [ + ListResponseWrapper, + ListResponseWrapper + ]) => { + const healthChecks: HealthCheckView[] = healthCheckResponse.values; + const agents: Agent[] = hashTypesResponse.values; + + healthChecks.map((healthCheck) => { + agents.find((el) => el.agentId === healthCheck.agentId); + }); + + this.setPaginationConfig( + this.pageSize, + this.currentPage, + healthCheckResponse.total + ); + this.setData(healthChecks); + } + ) + ); + } + + reload(): void { + this.clearSelection(); + this.loadAll(); + } +} diff --git a/src/app/core/_models/config-ui.model.ts b/src/app/core/_models/config-ui.model.ts index daf97d70..0b94a3bb 100644 --- a/src/app/core/_models/config-ui.model.ts +++ b/src/app/core/_models/config-ui.model.ts @@ -8,6 +8,7 @@ import { FilesTableCol } from '../_components/tables/files-table/files-table.con import { HashlistsTableCol } from '../_components/tables/hashlists-table/hashlists-table.constants'; import { HashtypesTableCol } from '../_components/tables/hashtypes-table/hashtypes-table.constants'; import { HealthChecksTableCol } from '../_components/tables/health-checks-table/health-checks-table.constants'; +import { HealthChecksViewTableCol } from '../_components/tables/health-checks-view-table/health-checks-view-table.constants'; import { LogsTableCol } from '../_components/tables/logs-table/logs-table.constants'; import { NotificationsTableCol } from '../_components/tables/notifications-table/notifications-table.constants'; import { PermissionsTableCol } from '../_components/tables/permissions-table/permissions-table.constants'; @@ -138,6 +139,15 @@ export const uiConfigDefault: UIConfig = { HealthChecksTableCol.STATUS, HealthChecksTableCol.TYPE ], + healthChecksviewTable: [ + HealthChecksViewTableCol.AGENT_ID, + HealthChecksViewTableCol.AGENT_NAME, + HealthChecksViewTableCol.STATUS, + HealthChecksViewTableCol.START, + HealthChecksViewTableCol.GPUS, + HealthChecksViewTableCol.CRACKED, + HealthChecksViewTableCol.ERRORS + ], tasksTable: [ TaskTableCol.ID, TaskTableCol.NAME, diff --git a/src/app/core/_models/health-check.model.ts b/src/app/core/_models/health-check.model.ts index cfe38886..b24e53c9 100644 --- a/src/app/core/_models/health-check.model.ts +++ b/src/app/core/_models/health-check.model.ts @@ -27,3 +27,18 @@ export interface HealthCheck { status: number; time: number; } + +export interface HealthCheckView { + _id: number; + _self: string; + healthCheckAgentId: number; + healthCheckId: number; + agentId: number; + status: number; + cracked: number; + numGpus: number; + start: number; + end: number; + errors: string; + agentName: string; +} From 1a258ff4f7f934d30bbf6ea2bf7c98ac598a401f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Tue, 19 Dec 2023 16:33:26 +0000 Subject: [PATCH 357/419] PreTasks attach files --- .../_components/core-components.module.ts | 3 + .../files-attack-table.component.html | 17 ++ .../files-attack-table.component.ts | 160 +++++++++++++++ .../files-attack-table.constants.ts | 11 ++ .../tables/ht-table/ht-table.component.html | 47 +++-- .../tables/ht-table/ht-table.component.ts | 30 +++ .../tables/ht-table/ht-table.models.ts | 7 + src/app/core/_models/config-ui.model.ts | 6 + .../new-preconfigured-tasks.component.html | 174 +++------------- .../new-preconfigured-tasks.component.ts | 186 +++++------------- 10 files changed, 345 insertions(+), 296 deletions(-) create mode 100644 src/app/core/_components/tables/files-attack-table/files-attack-table.component.html create mode 100644 src/app/core/_components/tables/files-attack-table/files-attack-table.component.ts create mode 100644 src/app/core/_components/tables/files-attack-table/files-attack-table.constants.ts diff --git a/src/app/core/_components/core-components.module.ts b/src/app/core/_components/core-components.module.ts index cde7d514..66c5a6a1 100644 --- a/src/app/core/_components/core-components.module.ts +++ b/src/app/core/_components/core-components.module.ts @@ -54,6 +54,7 @@ import { TableTruncateComponent } from './tables/table-truncate/table-truncate.c import { TasksTableComponent } from './tables/tasks-table/tasks-table.component'; import { UsersTableComponent } from './tables/users-table/users-table.component'; import { VouchersTableComponent } from './tables/vouchers-table/vouchers-table.component'; +import { FilesAttackTableComponent } from './tables/files-attack-table/files-attack-table.component'; @NgModule({ declarations: [ @@ -75,6 +76,7 @@ import { VouchersTableComponent } from './tables/vouchers-table/vouchers-table.c HashtypesTableComponent, HashlistsTableComponent, SuperHashlistsTableComponent, + FilesAttackTableComponent, FilesTableComponent, CrackersTableComponent, PreprocessorsTableComponent, @@ -132,6 +134,7 @@ import { VouchersTableComponent } from './tables/vouchers-table/vouchers-table.c HashlistsTableComponent, HashtypesTableComponent, SuperHashlistsTableComponent, + FilesAttackTableComponent, FilesTableComponent, CrackersTableComponent, PreprocessorsTableComponent, diff --git a/src/app/core/_components/tables/files-attack-table/files-attack-table.component.html b/src/app/core/_components/tables/files-attack-table/files-attack-table.component.html new file mode 100644 index 00000000..dc4d9f83 --- /dev/null +++ b/src/app/core/_components/tables/files-attack-table/files-attack-table.component.html @@ -0,0 +1,17 @@ + diff --git a/src/app/core/_components/tables/files-attack-table/files-attack-table.component.ts b/src/app/core/_components/tables/files-attack-table/files-attack-table.component.ts new file mode 100644 index 00000000..da387864 --- /dev/null +++ b/src/app/core/_components/tables/files-attack-table/files-attack-table.component.ts @@ -0,0 +1,160 @@ +import { + Component, + EventEmitter, + Input, + OnDestroy, + OnInit, + Output +} from '@angular/core'; +import { File, FileType } from 'src/app/core/_models/file.model'; +import { + FilesAttackTableCol, + FilesAttackTableColumnLabel +} from './files-attack-table.constants'; +import { + CheckboxChangeEvent, + HTTableColumn, + HTTableIcon +} from '../ht-table/ht-table.models'; + +import { BaseTableComponent } from '../base-table/base-table.component'; +import { Cacheable } from 'src/app/core/_decorators/cacheable'; +import { FilesDataSource } from 'src/app/core/_datasources/files.datasource'; +import { formatFileSize } from 'src/app/shared/utils/util'; + +@Component({ + selector: 'files-attack-table', + templateUrl: './files-attack-table.component.html' +}) +export class FilesAttackTableComponent + extends BaseTableComponent + implements OnInit, OnDestroy +{ + @Input() fileType: FileType = 0; + @Input() cmdTask = true; + @Input() cmdPrepro = false; + @Input() checkboxChangedData: CheckboxChangeEvent; + @Input() formData: { attackCmd: string; files: any }; + + @Output() updateFormEvent = new EventEmitter(); + + tableColumns: HTTableColumn[] = []; + dataSource: FilesDataSource; + + ngOnInit(): void { + this.setColumnLabels(FilesAttackTableColumnLabel); + this.tableColumns = this.getColumns(); + this.dataSource = new FilesDataSource(this.cdr, this.gs, this.uiService); + this.dataSource.setColumns(this.tableColumns); + this.dataSource.setFileType(this.fileType); + this.dataSource.loadAll(); + } + + ngOnDestroy(): void { + for (const sub of this.subscriptions) { + sub.unsubscribe(); + } + } + + filter(item: File, filterValue: string): boolean { + if (item.filename.toLowerCase().includes(filterValue)) { + return true; + } + + return false; + } + + getColumns(): HTTableColumn[] { + const tableColumns = [ + { + id: FilesAttackTableCol.ID, + dataKey: '_id', + isSortable: true, + export: async (file: File) => file._id + '' + }, + { + id: FilesAttackTableCol.NAME, + dataKey: 'filename', + icons: (file: File) => this.renderSecretIcon(file), + render: (file: File) => file.filename, + isSortable: true, + export: async (file: File) => file.filename + }, + { + id: FilesAttackTableCol.SIZE, + dataKey: 'size', + render: (file: File) => formatFileSize(file.size, 'short'), + isSortable: true, + export: async (file: File) => formatFileSize(file.size, 'short') + } + ]; + + return tableColumns; + } + + @Cacheable(['_id', 'isSecret']) + async renderSecretIcon(file: File): Promise { + const icons: HTTableIcon[] = []; + if (file.isSecret) { + icons.push({ + name: 'lock', + tooltip: 'Secret' + }); + } + + return icons; + } + + onCheckboxChanged(event: CheckboxChangeEvent): void { + // Handle the checkbox change event + this.cdr.detectChanges(); + const transformed = this.onPrepareAttack(this.formData, event); + // Pass the event data to another component + this.updateFormEvent.emit(transformed); + } + + onPrepareAttack(form: any, event: CheckboxChangeEvent): object { + const currentCmd = form.attackCmd; + const newCmdArray = currentCmd.split(' '); + const fileName = event.row.filename; + const fileId = event.row._id; + const newFileIds = [...form.files]; + + if (!event.checked) { + // Remove -r and filename from the command + if (event.row.fileType === 1) { + const indexR = newCmdArray.indexOf('-r'); + if (indexR !== -1) { + newCmdArray.splice(indexR, 1); + } + } + + const indexFileName = newCmdArray.indexOf(fileName); + if (indexFileName !== -1) { + newCmdArray.splice(indexFileName, 1); + } + + // Remove fileId from the array + const fileIdIndex = newFileIds.indexOf(fileId); + if (fileIdIndex !== -1) { + newFileIds.splice(fileIdIndex, 1); + } + } else { + // Add -r and filename to the command + if (event.row.fileType === 1) { + newCmdArray.push('-r'); + } + + newCmdArray.push(fileName); + + // Add fileId to the array + if (!newFileIds.includes(fileId)) { + newFileIds.push(fileId); + } + } + + const newCmd = newCmdArray.join(' ').trim(); + + return { attackCmd: newCmd, files: newFileIds }; + } +} diff --git a/src/app/core/_components/tables/files-attack-table/files-attack-table.constants.ts b/src/app/core/_components/tables/files-attack-table/files-attack-table.constants.ts new file mode 100644 index 00000000..5997812c --- /dev/null +++ b/src/app/core/_components/tables/files-attack-table/files-attack-table.constants.ts @@ -0,0 +1,11 @@ +export enum FilesAttackTableCol { + ID, + NAME, + SIZE +} + +export const FilesAttackTableColumnLabel = { + [FilesAttackTableCol.ID]: 'ID', + [FilesAttackTableCol.NAME]: 'Name', + [FilesAttackTableCol.SIZE]: 'Size' +}; 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 29be8527..1193c26e 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 @@ -72,12 +72,19 @@ - + + + Task | Preprocessor + + + + + Task + + + + + - + + + + + + + + + + diff --git a/src/app/core/_components/tables/ht-table/ht-table.component.ts b/src/app/core/_components/tables/ht-table/ht-table.component.ts index dc8a539d..a7e7c454 100644 --- a/src/app/core/_components/tables/ht-table/ht-table.component.ts +++ b/src/app/core/_components/tables/ht-table/ht-table.component.ts @@ -14,6 +14,7 @@ import { import { COL_ROW_ACTION, COL_SELECT, + CheckboxChangeEvent, DataType, HTTableColumn, HTTableEditable @@ -32,6 +33,7 @@ import { LocalStorageService } from 'src/app/core/_services/storage/local-storag import { MatDialog } from '@angular/material/dialog'; import { MatSort } from '@angular/material/sort'; import { UISettingsUtilityClass } from 'src/app/shared/utils/config'; +import { MatCheckboxChange } from '@angular/material/checkbox'; /** * The `HTTableComponent` is a custom table component that allows you to display tabular data with @@ -119,6 +121,12 @@ export class HTTableComponent implements OnInit, AfterViewInit { /** Flag to enable or disable selectable rows. */ @Input() isSelectable = false; + /** Flag to enable or disable cmd task attack checkbox. */ + @Input() isCmdTask = false; + + /** Flag to enable or disable cmd preprocessor attack checkbox. */ + @Input() isCmdPreproAttack = false; + /** Flag to enable or disable filtering. */ @Input() isFilterable = false; @@ -156,6 +164,10 @@ export class HTTableComponent implements OnInit, AfterViewInit { @Output() editableSaved: EventEmitter> = new EventEmitter>(); + /** Event emitter for checkbox attack */ + @Output() checkboxChanged: EventEmitter = + new EventEmitter(); + /** Fetches user customizations */ private uiSettings: UISettingsUtilityClass; @@ -316,6 +328,24 @@ export class HTTableComponent implements OnInit, AfterViewInit { } } + /** + * Handles the change event for the attack checkbox. + * + * @param event - The MatCheckboxChange event. + * @param row - The data of the row. + * @param type - The type of the column (CMD, main attack, or preprocessor). + */ + toggleAttack(event: MatCheckboxChange, row: any, type: string): void { + // Handle the change event for the Cmd Attack checkbox + const checked = event.checked; + // Emit the event with specific properties + this.checkboxChanged.emit({ + row, // All row data + columnType: type, // Column type CMD main attack or preprocessor + checked: checked // Boolean + }); + } + /** * Reloads the data in the table and the bulk menu. */ diff --git a/src/app/core/_components/tables/ht-table/ht-table.models.ts b/src/app/core/_components/tables/ht-table/ht-table.models.ts index be3e605b..14cf6344 100644 --- a/src/app/core/_components/tables/ht-table/ht-table.models.ts +++ b/src/app/core/_components/tables/ht-table/ht-table.models.ts @@ -7,6 +7,7 @@ export type DataType = | 'chunks' | 'hashtypes' | 'files' + | 'files-attack' | 'crackers' | 'preprocessors' | 'users' @@ -39,6 +40,12 @@ export interface HTTableEditable { action: string; } +export interface CheckboxChangeEvent { + row: any; + columnType: string; + checked: boolean; +} + export type HTTableColumnType = 'dafeult | link | editable'; export interface HTTableColumn { diff --git a/src/app/core/_models/config-ui.model.ts b/src/app/core/_models/config-ui.model.ts index daf97d70..076e6761 100644 --- a/src/app/core/_models/config-ui.model.ts +++ b/src/app/core/_models/config-ui.model.ts @@ -4,6 +4,7 @@ import { AgentsTableCol } from '../_components/tables/agents-table/agents-table. import { ChunksTableCol } from '../_components/tables/chunks-table/chunks-table.constants'; import { CrackersTableCol } from '../_components/tables/crackers-table/crackers-table.constants'; import { CracksTableCol } from '../_components/tables/cracks-table/cracks-table.constants'; +import { FilesAttackTableCol } from '../_components/tables/files-attack-table/files-attack-table.constants'; import { FilesTableCol } from '../_components/tables/files-table/files-table.constants'; import { HashlistsTableCol } from '../_components/tables/hashlists-table/hashlists-table.constants'; import { HashtypesTableCol } from '../_components/tables/hashtypes-table/hashtypes-table.constants'; @@ -118,6 +119,11 @@ export const uiConfigDefault: UIConfig = { FilesTableCol.LINE_COUNT, FilesTableCol.ACCESS_GROUP ], + filesAttackTable: [ + FilesAttackTableCol.ID, + FilesAttackTableCol.NAME, + FilesAttackTableCol.SIZE + ], crackersTable: [ CrackersTableCol.ID, CrackersTableCol.NAME, diff --git a/src/app/tasks/new-preconfigured-tasks/new-preconfigured-tasks.component.html b/src/app/tasks/new-preconfigured-tasks/new-preconfigured-tasks.component.html index 89235f12..264866ab 100644 --- a/src/app/tasks/new-preconfigured-tasks/new-preconfigured-tasks.component.html +++ b/src/app/tasks/new-preconfigured-tasks/new-preconfigured-tasks.component.html @@ -39,150 +39,38 @@

New Preconfigured Tasks (Copied From {{whichView === - - - - + + + + - - - -
- - - - Wordlists - - - - - - - - - - - - - -
File Name
-
-
- - - - - -
- Full Name: {{f.filename}} - Line Count: {{f.lineCount}} - Size: {{ f.size | fileSize:false}} -
-
-
-
-
-
-
- - - Rules - - - - - - - - - - - - - -
File Name
-
-
- - - - - -
- Full Name: {{f.filename}} - Line Count: {{f.lineCount}} - Size: {{ f.size | fileSize:false}} -
-
-
-
-
-
-
- - - Other - - - - - - - - - - - - - -
File Name
-
-
- - - - - -
- Full Name: {{f.filename}} - Line Count: {{f.lineCount}} - Size: {{ f.size | fileSize:false}} -
-
-
-
-
-
-
-
-
+ + +
+ + + + Wordlists + + + + + + + Rules + + + + + + + Other + + + + +
+ diff --git a/src/app/tasks/new-preconfigured-tasks/new-preconfigured-tasks.component.ts b/src/app/tasks/new-preconfigured-tasks/new-preconfigured-tasks.component.ts index 150225c0..556bee1e 100644 --- a/src/app/tasks/new-preconfigured-tasks/new-preconfigured-tasks.component.ts +++ b/src/app/tasks/new-preconfigured-tasks/new-preconfigured-tasks.component.ts @@ -1,13 +1,6 @@ -import { - AbstractControl, - FormControl, - FormGroup, - ValidatorFn, - Validators -} from '@angular/forms'; -import { AfterViewInit, Component, OnInit, ViewChild } from '@angular/core'; +import { FormControl, FormGroup, Validators } from '@angular/forms'; +import { Component, OnDestroy, OnInit } from '@angular/core'; import { ModalDismissReasons, NgbModal } from '@ng-bootstrap/ng-bootstrap'; -import { faInfoCircle, faLock } from '@fortawesome/free-solid-svg-icons'; import { environment } from './../../../environments/environment'; import { ActivatedRoute, Params, Router } from '@angular/router'; import { DataTableDirective } from 'angular-datatables'; @@ -18,6 +11,7 @@ import { UIConfigService } from 'src/app/core/_services/shared/storage.service'; import { AlertService } from 'src/app/core/_services/shared/alert.service'; import { GlobalService } from 'src/app/core/_services/main.service'; import { FileTypePipe } from 'src/app/core/_pipes/file-type.pipe'; +import { FileType } from 'src/app/core/_models/file.model'; import { PageTitle } from 'src/app/core/_decorators/autotitle'; import { SERV } from '../../core/_services/main.config'; import { UnsubscribeService } from 'src/app/core/_services/unsubscribe.service'; @@ -30,7 +24,7 @@ import { transformSelectOptions } from 'src/app/shared/utils/forms'; selector: 'app-new-preconfigured-tasks', templateUrl: './new-preconfigured-tasks.component.html' }) -export class NewPreconfiguredTasksComponent implements OnInit, AfterViewInit { +export class NewPreconfiguredTasksComponent implements OnInit, OnDestroy { /** Flag indicating whether data is still loading. */ isLoading = true; @@ -45,8 +39,8 @@ export class NewPreconfiguredTasksComponent implements OnInit, AfterViewInit { selectCrackertypeMap = { fieldMapping: CRACKER_TYPE_FIELD_MAPPING }; - - @ViewChild('cmdAttack', { static: true }) cmdAttack: any; + cmdPrepro = false; + // Copy Mode copyMode = false; editedIndex: number; whichView: string; @@ -55,95 +49,32 @@ export class NewPreconfiguredTasksComponent implements OnInit, AfterViewInit { private priority = environment.config.tasks.priority; private maxAgents = environment.config.tasks.maxAgents; - // TABLES - @ViewChild(DataTableDirective) - dtElement: DataTableDirective; - - dtTrigger: Subject = new Subject(); - dtOptions: any = {}; - - public allfiles: any; - constructor( private unsubscribeService: UnsubscribeService, private changeDetectorRef: ChangeDetectorRef, private titleService: AutoTitleService, private uiService: UIConfigService, private modalService: NgbModal, - private fileType: FileTypePipe, private route: ActivatedRoute, private alert: AlertService, private gs: GlobalService, private router: Router ) { + this.onInitialize(); titleService.set(['New Preconfigured Tasks']); } - ngOnDestroy() { - this.dtTrigger.unsubscribe(); - } - - // New checkbox - filesFormArray: Array = []; - onChange( - fileId: number, - fileType: number, - fileName: string, - $target: EventTarget - ) { - const isChecked = ($target).checked; - if (isChecked) { - if (this.copyMode) { - this.filesFormArray = this.createForm.get('files').value; - } - this.filesFormArray.push(fileId); - this.OnChangeAttack(fileName, fileType); - this.createForm.patchValue({ files: this.filesFormArray }); - } else { - if (this.copyMode) { - this.filesFormArray = this.createForm.get('files').value; - } - const index = this.filesFormArray.indexOf(fileId); - this.filesFormArray.splice(index, 1); - this.createForm.patchValue({ files: this.filesFormArray }); - this.OnChangeAttack(fileName, fileType, true); - } - } - - onChecked(fileId: number) { - return this.createForm.get('files').value.includes(fileId); - } - - OnChangeAttack(item: string, fileType: number, onRemove?: boolean) { - if (onRemove == true) { - const currentCmd = this.createForm.get('attackCmd').value; - let newCmd = item; - if (fileType === 1) { - newCmd = '-r ' + newCmd; - } - newCmd = currentCmd.replace(newCmd, ''); - newCmd = newCmd.replace(/^\s+|\s+$/g, ''); - this.createForm.patchValue({ - attackCmd: newCmd - }); - } else { - const currentCmd = this.createForm.get('attackCmd').value; - let newCmd = item; - if (fileType === 1) { - newCmd = '-r ' + newCmd; - } - this.createForm.patchValue({ - attackCmd: currentCmd + ' ' + newCmd - }); - } - } - - ngOnInit(): void { + /** + * Initializes the component by extracting and setting the copy ID, + */ + onInitialize() { this.route.params.subscribe((params: Params) => { this.editedIndex = +params['id']; this.copyMode = params['id'] != null; }); + } + ngOnInit(): void { this.route.data.subscribe((data) => { switch (data['kind']) { case 'new-preconfigured-tasks': @@ -195,66 +126,46 @@ export class NewPreconfiguredTasksComponent implements OnInit, AfterViewInit { this.selectCrackertype = transformedOptions; }); this.unsubscribeService.add(loadCrackersSubscription$); + } - this.gs - .getAll(SERV.FILES, { expand: 'accessGroup' }) - .subscribe((files: any) => { - this.allfiles = files.values; - this.dtElement.dtInstance.then((dtInstance: DataTables.Api) => { - setTimeout(() => { - this.dtTrigger[0].next(null); - dtInstance.columns.adjust(); - }); - }); - }); + /** + * Lifecycle hook called before the component is destroyed. + * Unsubscribes from all subscriptions to prevent memory leaks. + */ + ngOnDestroy(): void { + this.unsubscribeService.unsubscribeAll(); + } - this.dtOptions = { - dom: 'Bfrtip', - scrollX: true, - pageLength: 25, - lengthMenu: [ - [10, 25, 50, 100, 250, -1], - [10, 25, 50, 100, 250, 'All'] - ], - scrollY: '1000px', - scrollCollapse: true, - paging: false, - // destroy: true, - buttons: { - dom: { - button: { - className: - 'dt-button buttons-collection btn btn-sm-dt btn-outline-gray-600-dt' - } - }, - buttons: [] - } + getFormData() { + return { + attackCmd: this.createForm.get('attackCmd').value, + files: this.createForm.get('files').value }; } + onUpdateForm(event: any): void { + this.createForm.patchValue({ + attackCmd: event.attackCmd, + files: event.files + }); + } + onSubmit() { if (this.createForm.valid) { - this.gs.create(SERV.PRETASKS, this.createForm.value).subscribe(() => { - this.alert.okAlert('New PreTask created!', ''); - this.createForm.reset(); // success, we reset form - this.router.navigate(['tasks/preconfigured-tasks']); - }); + const onSubmitSubscription$ = this.gs + .create(SERV.PRETASKS, this.createForm.value) + .subscribe(() => { + this.alert.okAlert('New PreTask created!', ''); + this.createForm.reset(); // success, we reset form + this.router.navigate(['tasks/preconfigured-tasks']); + }); + this.unsubscribeService.add(onSubmitSubscription$); } } - public matchFileType: any; - active = 0; //Active show first table wordlist - - ngAfterViewInit() { - setTimeout(() => { - this.active = 1; - }, 1000); - this.dtTrigger[0].next(null); - } - private initForm() { if (this.copyMode) { - this.gs + const onCopyPretask$ = this.gs .get(SERV.PRETASKS, this.editedIndex, { expand: 'pretaskFiles' }) .subscribe((result) => { const arrFiles: Array = []; @@ -285,12 +196,13 @@ export class NewPreconfiguredTasksComponent implements OnInit, AfterViewInit { files: new FormControl(arrFiles) }); }); + this.unsubscribeService.add(onCopyPretask$); } } private initFormt() { if (this.copyMode) { - this.gs + const onCopyTask$ = this.gs .get(SERV.TASKS, this.editedIndex, { expand: 'files' }) .subscribe((result) => { const arrFiles: Array = []; @@ -321,20 +233,10 @@ export class NewPreconfiguredTasksComponent implements OnInit, AfterViewInit { files: new FormControl(arrFiles) }); }); + this.unsubscribeService.add(onCopyTask$); } } - rerender(): void { - this.dtElement.dtInstance.then((dtInstance: DataTables.Api) => { - // Destroy the table first - dtInstance.destroy(); - // Call the dtTrigger to rerender again - setTimeout(() => { - this.dtTrigger['new'].next(); - }); - }); - } - // Navigation Modals navChanged(event) { From 1c783355524a962d18d4694b964fb903b5d92b54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Tue, 19 Dec 2023 17:29:50 +0000 Subject: [PATCH 358/419] Adding preprocessor command --- .../files-attack-table.component.ts | 15 +- .../tasks/new-tasks/new-tasks.component.html | 216 ++---------------- .../tasks/new-tasks/new-tasks.component.ts | 149 +++--------- 3 files changed, 63 insertions(+), 317 deletions(-) diff --git a/src/app/core/_components/tables/files-attack-table/files-attack-table.component.ts b/src/app/core/_components/tables/files-attack-table/files-attack-table.component.ts index da387864..e7655908 100644 --- a/src/app/core/_components/tables/files-attack-table/files-attack-table.component.ts +++ b/src/app/core/_components/tables/files-attack-table/files-attack-table.component.ts @@ -34,7 +34,11 @@ export class FilesAttackTableComponent @Input() cmdTask = true; @Input() cmdPrepro = false; @Input() checkboxChangedData: CheckboxChangeEvent; - @Input() formData: { attackCmd: string; files: any }; + @Input() formData: { + attackCmd: string; + preprocessorCommand?: string; + files: any; + }; @Output() updateFormEvent = new EventEmitter(); @@ -114,7 +118,12 @@ export class FilesAttackTableComponent } onPrepareAttack(form: any, event: CheckboxChangeEvent): object { - const currentCmd = form.attackCmd; + let currentCmd; + if (event.columnType === 'CMD') { + currentCmd = form.attackCmd; + } else { + currentCmd = form.preprocessorCommand; + } const newCmdArray = currentCmd.split(' '); const fileName = event.row.filename; const fileId = event.row._id; @@ -155,6 +164,6 @@ export class FilesAttackTableComponent const newCmd = newCmdArray.join(' ').trim(); - return { attackCmd: newCmd, files: newFileIds }; + return { attackCmd: newCmd, files: newFileIds, type: event.columnType }; } } diff --git a/src/app/tasks/new-tasks/new-tasks.component.html b/src/app/tasks/new-tasks/new-tasks.component.html index ef2c1ce8..a903578d 100644 --- a/src/app/tasks/new-tasks/new-tasks.component.html +++ b/src/app/tasks/new-tasks/new-tasks.component.html @@ -2,7 +2,7 @@
-
+ @@ -24,7 +24,7 @@ - +
@@ -74,7 +74,7 @@ - Please complete all the form!
+ Please complete all the form!
@@ -84,200 +84,28 @@
- - - + + + Wordlists - - - - - - - - - - - - - - - -
- T|P - - - File Name
- - -
- - T -
- -
- P -
- -
- - - - -
- Full Name: {{f.filename}} - Line Count: {{f.lineCount}} - Size: {{ f.size | fileSize:false}} -
-
-
-
-
- - + + + + + + Rules - - - - - - - - - - - - - - - -
- T|P - - - File Name
-
- -
- - T -
- -
- P -
-
-
- - - - -
- Full Name: {{f.filename}} - Line Count: {{f.lineCount}} - Size: {{ f.size | fileSize:false}} -
-
-
-
-
- - + + + + + + Other - - - - - - - - - - - - - - - -
- T|P - - - File Name
-
- -
- - T -
- -
- P -
-
-
- - - - -
- Full Name: {{f.filename}} - Line Count: {{f.lineCount}} - Size: {{ f.size | fileSize:false}} -
-
-
-
-
-
+ + + +
diff --git a/src/app/tasks/new-tasks/new-tasks.component.ts b/src/app/tasks/new-tasks/new-tasks.component.ts index e6eebd0e..10f3aa3f 100644 --- a/src/app/tasks/new-tasks/new-tasks.component.ts +++ b/src/app/tasks/new-tasks/new-tasks.component.ts @@ -57,7 +57,7 @@ export class NewTasksComponent implements OnInit { isLoading = true; /** Form group for the new SuperHashlist. */ - createForm: FormGroup; + form: FormGroup; /** Select Options. */ selectHashlists: any; @@ -91,15 +91,6 @@ export class NewTasksComponent implements OnInit { // Tooltips tasktip: any = []; - // TABLES - @ViewChild(DataTableDirective) - dtElement: DataTableDirective; - - dtTrigger: Subject = new Subject(); - dtOptions: any = {}; - - public allfiles: any; - /** * Constructor for the Component. * Initializes and sets up necessary services, properties, and components. @@ -160,9 +151,8 @@ export class NewTasksComponent implements OnInit { }); this.loadData(); - this.loadTableData(); - this.createForm = new FormGroup({ + this.form = new FormGroup({ taskName: new FormControl('', [Validators.required]), notes: new FormControl(''), hashlistId: new FormControl(), @@ -198,11 +188,9 @@ export class NewTasksComponent implements OnInit { }); //subscribe to changes to handle select cracker binary - this.createForm - .get('crackerBinaryId') - .valueChanges.subscribe((newvalue) => { - this.handleChangeBinary(newvalue); - }); + this.form.get('crackerBinaryId').valueChanges.subscribe((newvalue) => { + this.handleChangeBinary(newvalue); + }); } loadData() { @@ -244,7 +232,7 @@ export class NewTasksComponent implements OnInit { ); this.selectCrackerversions = transformedOptions; const lastItem = this.selectCrackerversions.slice(-1)[0]['_id']; - this.createForm.get('crackerBinaryTypeId').patchValue(lastItem); + this.form.get('crackerBinaryTypeId').patchValue(lastItem); }); this.unsubscribeService.add(loadCrackersSubscription$); }); @@ -261,107 +249,28 @@ export class NewTasksComponent implements OnInit { this.unsubscribeService.add(loadPreprocessorsSubscription$); } - // TABLES TO BE REMOVED - loadTableData() { - this.gs - .getAll(SERV.FILES, { - expand: 'accessGroup' - }) - .subscribe((files) => { - this.allfiles = files.values; - this.dtElement.dtInstance.then((dtInstance: DataTables.Api) => { - setTimeout(() => { - this.dtTrigger[0].next(null); - dtInstance.columns.adjust(); - }); - }); - }); - } - - // Attack Command with Files Table - filesFormArray: Array = []; - onChange( - fileId: number, - fileType: number, - fileName: string, - cmdAttk: number, - $target: EventTarget - ) { - const isChecked = ($target).checked; - if (isChecked && cmdAttk === 0) { - if (this.copyMode) { - this.filesFormArray = this.createForm.get('files').value; - } - this.filesFormArray.push(fileId); - this.OnChangeAttack(fileName, fileType); - this.createForm.get('files').value; - this.createForm.patchValue({ files: this.filesFormArray }); - } - if (isChecked && cmdAttk === 1) { - this.OnChangeAttackPrep(fileName, fileType); - } - if (!isChecked && cmdAttk === 0) { - if (this.copyMode) { - this.filesFormArray = this.createForm.get('files').value; - } - const index = this.filesFormArray.indexOf(fileId); - this.filesFormArray.splice(index, 1); - this.createForm.patchValue({ files: this.filesFormArray }); - this.OnChangeAttack(fileName, fileType, true); - } - if (!isChecked && cmdAttk === 1) { - this.OnChangeAttackPrep(fileName, fileType, true); - } - } - - onChecked(fileId: number) { - return this.createForm.get('files').value.includes(fileId); + getFormData() { + return { + attackCmd: this.form.get('attackCmd').value, + files: this.form.get('files').value, + preprocessorCommand: this.form.get('preprocessorCommand').value + }; } - OnChangeAttack(item: string, fileType: number, onRemove?: boolean) { - if (onRemove === true) { - const currentCmd = this.createForm.get('attackCmd').value; - let newCmd = item; - if (fileType === 1) { - newCmd = '-r ' + newCmd; - } - newCmd = currentCmd.replace(newCmd, ''); - newCmd = newCmd.replace(/^\s+|\s+$/g, ''); - this.createForm.patchValue({ - attackCmd: newCmd - }); - } else { - const currentCmd = this.createForm.get('attackCmd').value; - let newCmd = item; - if (fileType === 1) { - newCmd = '-r ' + newCmd; - } - this.createForm.patchValue({ - attackCmd: currentCmd + ' ' + newCmd - }); - } + isPreprocessor(): boolean { + const preprocessorId = this.form.get('preprocessorId').value; + return preprocessorId !== 0 && preprocessorId !== 'null'; } - OnChangeAttackPrep(item: string, fileType: number, onRemove?: boolean) { - if (onRemove === true) { - const currentCmd = this.createForm.get('preprocessorCommand').value; - let newCmd = item; - if (fileType === 1) { - newCmd = '-r ' + newCmd; - } - newCmd = currentCmd.replace(newCmd, ''); - newCmd = newCmd.replace(/^\s+|\s+$/g, ''); - this.createForm.patchValue({ - preprocessorCommand: newCmd + onUpdateForm(event: any): void { + if (event.type === 'CMD') { + this.form.patchValue({ + attackCmd: event.attackCmd, + files: event.files }); } else { - const currentCmd = this.createForm.get('preprocessorCommand').value; - let newCmd = item; - if (fileType === 1) { - newCmd = '-r ' + newCmd; - } - this.createForm.patchValue({ - preprocessorCommand: currentCmd + ' ' + newCmd + this.form.patchValue({ + preprocessorCommand: event.attackCmd }); } } @@ -376,18 +285,18 @@ export class NewTasksComponent implements OnInit { ); this.selectCrackerversions = transformedOptions; const lastItem = this.selectCrackerversions.slice(-1)[0]['_id']; - this.createForm.get('crackerBinaryTypeId').patchValue(lastItem); + this.form.get('crackerBinaryTypeId').patchValue(lastItem); }); this.unsubscribeService.add(onChangeBinarySubscription$); } onSubmit() { - if (this.createForm.valid) { + if (this.form.valid) { const onSubmitSubscription$ = this.gs - .create(SERV.TASKS, this.createForm.value) + .create(SERV.TASKS, this.form.value) .subscribe(() => { this.alert.okAlert('New Task created!', ''); - this.createForm.reset(); + this.form.reset(); this.router.navigate(['tasks/show-tasks']); }); this.unsubscribeService.add(onSubmitSubscription$); @@ -410,7 +319,7 @@ export class NewTasksComponent implements OnInit { arrFiles.push(result.files[i]['fileId']); } } - this.createForm = new FormGroup({ + this.form = new FormGroup({ taskName: new FormControl( result['taskName'] + '_(Copied_task_id_' + this.editedIndex + ')', [Validators.required, Validators.minLength(1)] @@ -463,7 +372,7 @@ export class NewTasksComponent implements OnInit { } this.copyFiles = arrFiles; } - this.createForm = new FormGroup({ + this.form = new FormGroup({ taskName: new FormControl( result['taskName'] + '_(Copied_pretask_id_' + @@ -511,7 +420,7 @@ export class NewTasksComponent implements OnInit { } canDeactivate(): Observable | boolean { - if (this.createForm.valid) { + if (this.form.valid) { return false; } return true; From 800869f03e11c48a1f41205aaccf79d8074dc942 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Wed, 20 Dec 2023 10:51:08 +0000 Subject: [PATCH 359/419] Files on copied checked --- .../files-attack-table.component.html | 1 + .../tables/ht-table/ht-table.component.html | 3 ++- .../_components/tables/ht-table/ht-table.component.ts | 10 +++++++++- .../_components/tables/ht-table/ht-table.models.ts | 4 ++++ .../new-preconfigured-tasks.component.html | 2 -- .../new-preconfigured-tasks.component.ts | 2 +- 6 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/app/core/_components/tables/files-attack-table/files-attack-table.component.html b/src/app/core/_components/tables/files-attack-table/files-attack-table.component.html index dc4d9f83..498583ba 100644 --- a/src/app/core/_components/tables/files-attack-table/files-attack-table.component.html +++ b/src/app/core/_components/tables/files-attack-table/files-attack-table.component.html @@ -12,6 +12,7 @@ [isPageable]="true" [isCmdTask]="cmdTask" [isCmdPreproAttack]="cmdPrepro" + [isCmdFiles]="formData.files" [isSelectable]="true" (checkboxChanged)="onCheckboxChanged($event)" /> 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 1193c26e..d603b750 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 @@ -108,7 +108,7 @@ /> - + + 0) { + return this.isCmdFiles.includes(row._id); + } else { + return this.dataSource.isSelected(row); + } } /** diff --git a/src/app/core/_components/tables/ht-table/ht-table.models.ts b/src/app/core/_components/tables/ht-table/ht-table.models.ts index 14cf6344..56b70203 100644 --- a/src/app/core/_components/tables/ht-table/ht-table.models.ts +++ b/src/app/core/_components/tables/ht-table/ht-table.models.ts @@ -46,6 +46,10 @@ export interface CheckboxChangeEvent { checked: boolean; } +export interface CheckboxFiles { + files?: any[]; +} + export type HTTableColumnType = 'dafeult | link | editable'; export interface HTTableColumn { diff --git a/src/app/tasks/new-preconfigured-tasks/new-preconfigured-tasks.component.html b/src/app/tasks/new-preconfigured-tasks/new-preconfigured-tasks.component.html index 264866ab..f246f556 100644 --- a/src/app/tasks/new-preconfigured-tasks/new-preconfigured-tasks.component.html +++ b/src/app/tasks/new-preconfigured-tasks/new-preconfigured-tasks.component.html @@ -72,5 +72,3 @@

New Preconfigured Tasks (Copied From {{whichView === - - diff --git a/src/app/tasks/new-preconfigured-tasks/new-preconfigured-tasks.component.ts b/src/app/tasks/new-preconfigured-tasks/new-preconfigured-tasks.component.ts index 556bee1e..6f1b6882 100644 --- a/src/app/tasks/new-preconfigured-tasks/new-preconfigured-tasks.component.ts +++ b/src/app/tasks/new-preconfigured-tasks/new-preconfigured-tasks.component.ts @@ -70,7 +70,7 @@ export class NewPreconfiguredTasksComponent implements OnInit, OnDestroy { onInitialize() { this.route.params.subscribe((params: Params) => { this.editedIndex = +params['id']; - this.copyMode = params['id'] != null; + this.copyMode = params && params['id'] !== null; }); } From db79ab4263cd49b57a3e7cdb1be22b7347a27d63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Wed, 20 Dec 2023 11:15:28 +0000 Subject: [PATCH 360/419] Remove redundancy code and documentation --- .../new-preconfigured-tasks.component.ts | 176 +++++++++++------- 1 file changed, 105 insertions(+), 71 deletions(-) diff --git a/src/app/tasks/new-preconfigured-tasks/new-preconfigured-tasks.component.ts b/src/app/tasks/new-preconfigured-tasks/new-preconfigured-tasks.component.ts index 6f1b6882..b871baac 100644 --- a/src/app/tasks/new-preconfigured-tasks/new-preconfigured-tasks.component.ts +++ b/src/app/tasks/new-preconfigured-tasks/new-preconfigured-tasks.component.ts @@ -30,6 +30,7 @@ export class NewPreconfiguredTasksComponent implements OnInit, OnDestroy { /** Form group for the Pretask. */ createForm: FormGroup; + cmdPrepro = false; /** Select Options. */ selectBenchmarktype = benchmarkType; @@ -39,7 +40,7 @@ export class NewPreconfiguredTasksComponent implements OnInit, OnDestroy { selectCrackertypeMap = { fieldMapping: CRACKER_TYPE_FIELD_MAPPING }; - cmdPrepro = false; + // Copy Mode copyMode = false; editedIndex: number; @@ -74,25 +75,62 @@ export class NewPreconfiguredTasksComponent implements OnInit, OnDestroy { }); } + /** + * Initializes the component when it is created. + */ ngOnInit(): void { this.route.data.subscribe((data) => { - switch (data['kind']) { - case 'new-preconfigured-tasks': - this.whichView = 'create'; - break; + // Determine the view type based on the provided data + this.whichView = this.determineView(data['kind']); - case 'copy-preconfigured-tasks': - this.whichView = 'edit'; - this.initForm(); - break; - - case 'copy-tasks': - this.whichView = 'task'; - this.initFormt(); - break; - } + // Initialize the form based on the determined view type + this.initializeForm(this.whichView); }); + // Build the default form and load additional data + this.buildForm(); + this.loadData(); + } + + /** + * Determines the view based on the specified kind. + * @param {string} kind - The kind of view ('new-preconfigured-tasks', 'copy-preconfigured-tasks', or 'copy-tasks'). + * @returns {string} The determined view type ('create', 'edit', or 'task'). + */ + private determineView(kind: string): string { + switch (kind) { + case 'new-preconfigured-tasks': + return 'create'; + case 'copy-preconfigured-tasks': + return 'edit'; + case 'copy-tasks': + return 'task'; + default: + return 'create'; + } + } + + /** + * Initializes the form based on the specified view. + * @param {string} view - The view type ('edit' or 'task'). + * @returns {void} + */ + private initializeForm(view: string): void { + switch (view) { + case 'edit': + this.initForm(true); + break; + case 'task': + this.initForm(false); + break; + } + } + + /** + * Builds the form for creating a task. + * Initializes the form controls with default or UI settings values. + */ + buildForm() { this.createForm = new FormGroup({ taskName: new FormControl('', [Validators.required]), attackCmd: new FormControl( @@ -115,7 +153,14 @@ export class NewPreconfiguredTasksComponent implements OnInit, OnDestroy { isMaskImport: new FormControl(false), files: new FormControl('' || []) }); + } + /** + * Loads data for crackers types. + * Fetches all crackers types from the backend, transforms the options, + * and assigns them to the selectCrackertype property. + */ + loadData() { const loadCrackersSubscription$ = this.gs .getAll(SERV.CRACKERS_TYPES) .subscribe((response: any) => { @@ -136,6 +181,10 @@ export class NewPreconfiguredTasksComponent implements OnInit, OnDestroy { this.unsubscribeService.unsubscribeAll(); } + /** + * Retrieves the form data containing attack command and files. + * @returns An object with attack command and files. + */ getFormData() { return { attackCmd: this.createForm.get('attackCmd').value, @@ -143,6 +192,10 @@ export class NewPreconfiguredTasksComponent implements OnInit, OnDestroy { }; } + /** + * Updates the form based on the provided event data. + * @param event - The event data containing attack command and files. + */ onUpdateForm(event: any): void { this.createForm.patchValue({ attackCmd: event.attackCmd, @@ -150,36 +203,36 @@ export class NewPreconfiguredTasksComponent implements OnInit, OnDestroy { }); } - onSubmit() { - if (this.createForm.valid) { - const onSubmitSubscription$ = this.gs - .create(SERV.PRETASKS, this.createForm.value) - .subscribe(() => { - this.alert.okAlert('New PreTask created!', ''); - this.createForm.reset(); // success, we reset form - this.router.navigate(['tasks/preconfigured-tasks']); - }); - this.unsubscribeService.add(onSubmitSubscription$); - } - } - - private initForm() { + /** + * Initializes the form based on the copy mode and the type of task. + * @param isPretask - Indicates whether the task is a preconfigured task. + */ + private initForm(isPretask: boolean) { if (this.copyMode) { - const onCopyPretask$ = this.gs - .get(SERV.PRETASKS, this.editedIndex, { expand: 'pretaskFiles' }) + const endpoint = isPretask ? SERV.PRETASKS : SERV.TASKS; + const onCopy$ = this.gs + .get(endpoint, this.editedIndex, { + expand: isPretask ? 'pretaskFiles' : 'files' + }) .subscribe((result) => { const arrFiles: Array = []; - if (result['pretaskFiles']) { - for (let i = 0; i < result['pretaskFiles'].length; i++) { - arrFiles.push(result['pretaskFiles'][i]['fileId']); + if (result[isPretask ? 'pretaskFiles' : 'files']) { + for ( + let i = 0; + i < result[isPretask ? 'pretaskFiles' : 'files'].length; + i++ + ) { + arrFiles.push( + result[isPretask ? 'pretaskFiles' : 'files'][i]['fileId'] + ); } } this.createForm = new FormGroup({ taskName: new FormControl( result['taskName'] + - '_(Copied_pretask_id_' + - this.editedIndex + - ')', + `_(Copied_${ + isPretask ? 'pretask_id' : 'pretask_from_task_id' + }_${this.editedIndex})`, [Validators.required, Validators.minLength(1)] ), attackCmd: new FormControl(result['attackCmd']), @@ -196,44 +249,25 @@ export class NewPreconfiguredTasksComponent implements OnInit, OnDestroy { files: new FormControl(arrFiles) }); }); - this.unsubscribeService.add(onCopyPretask$); + this.unsubscribeService.add(onCopy$); } } - private initFormt() { - if (this.copyMode) { - const onCopyTask$ = this.gs - .get(SERV.TASKS, this.editedIndex, { expand: 'files' }) - .subscribe((result) => { - const arrFiles: Array = []; - if (result.files) { - for (let i = 0; i < result.files.length; i++) { - arrFiles.push(result.files[i]['fileId']); - } - } - this.createForm = new FormGroup({ - taskName: new FormControl( - result['taskName'] + - '_(Copied_pretask_from_task_id_' + - this.editedIndex + - ')', - [Validators.required, Validators.minLength(1)] - ), - attackCmd: new FormControl(result['attackCmd']), - maxAgents: new FormControl(result['maxAgents']), - chunkTime: new FormControl(result['chunkTime']), - statusTimer: new FormControl(result['statusTimer']), - priority: new FormControl(result['priority']), - color: new FormControl(result['color']), - isCpuTask: new FormControl(result['isCpuTask']), - crackerBinaryTypeId: new FormControl(result['crackerBinaryTypeId']), - isSmall: new FormControl(result['isSmall']), - useNewBench: new FormControl(result['useNewBench']), - isMaskImport: new FormControl(false), - files: new FormControl(arrFiles) - }); + /** + * Submits the form data and creates a new PreTask. + * If the form is valid, it sends a request to create a new PreTask and + * resets the form on success. + */ + onSubmit() { + if (this.createForm.valid) { + const onSubmitSubscription$ = this.gs + .create(SERV.PRETASKS, this.createForm.value) + .subscribe(() => { + this.alert.okAlert('New PreTask created!', ''); + this.createForm.reset(); // success, we reset form + this.router.navigate(['tasks/preconfigured-tasks']); }); - this.unsubscribeService.add(onCopyTask$); + this.unsubscribeService.add(onSubmitSubscription$); } } From a3571da9bc1c1a8fd8cdcffbfd5713448f906d10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Wed, 20 Dec 2023 13:14:01 +0000 Subject: [PATCH 361/419] New Tasks refactoring, unsubscription and redundancies --- .../new-preconfigured-tasks.component.ts | 18 +- .../tasks/new-tasks/new-tasks.component.html | 2 +- .../tasks/new-tasks/new-tasks.component.ts | 245 ++++++++++-------- 3 files changed, 144 insertions(+), 121 deletions(-) diff --git a/src/app/tasks/new-preconfigured-tasks/new-preconfigured-tasks.component.ts b/src/app/tasks/new-preconfigured-tasks/new-preconfigured-tasks.component.ts index b871baac..d0406636 100644 --- a/src/app/tasks/new-preconfigured-tasks/new-preconfigured-tasks.component.ts +++ b/src/app/tasks/new-preconfigured-tasks/new-preconfigured-tasks.component.ts @@ -92,6 +92,14 @@ export class NewPreconfiguredTasksComponent implements OnInit, OnDestroy { this.loadData(); } + /** + * Lifecycle hook called before the component is destroyed. + * Unsubscribes from all subscriptions to prevent memory leaks. + */ + ngOnDestroy(): void { + this.unsubscribeService.unsubscribeAll(); + } + /** * Determines the view based on the specified kind. * @param {string} kind - The kind of view ('new-preconfigured-tasks', 'copy-preconfigured-tasks', or 'copy-tasks'). @@ -127,7 +135,7 @@ export class NewPreconfiguredTasksComponent implements OnInit, OnDestroy { } /** - * Builds the form for creating a task. + * Builds the form for creating a Pretask. * Initializes the form controls with default or UI settings values. */ buildForm() { @@ -173,14 +181,6 @@ export class NewPreconfiguredTasksComponent implements OnInit, OnDestroy { this.unsubscribeService.add(loadCrackersSubscription$); } - /** - * Lifecycle hook called before the component is destroyed. - * Unsubscribes from all subscriptions to prevent memory leaks. - */ - ngOnDestroy(): void { - this.unsubscribeService.unsubscribeAll(); - } - /** * Retrieves the form data containing attack command and files. * @returns An object with attack command and files. diff --git a/src/app/tasks/new-tasks/new-tasks.component.html b/src/app/tasks/new-tasks/new-tasks.component.html index a903578d..4a472ddb 100644 --- a/src/app/tasks/new-tasks/new-tasks.component.html +++ b/src/app/tasks/new-tasks/new-tasks.component.html @@ -15,7 +15,7 @@ [label]="'Select or search Hashlists *'" [mergeIdAndName]="true" [multiselectEnabled]="false" - formControlName="hashlistIds" + formControlName="hashlistId" > diff --git a/src/app/tasks/new-tasks/new-tasks.component.ts b/src/app/tasks/new-tasks/new-tasks.component.ts index 10f3aa3f..59a9d7ef 100644 --- a/src/app/tasks/new-tasks/new-tasks.component.ts +++ b/src/app/tasks/new-tasks/new-tasks.component.ts @@ -3,20 +3,13 @@ import { ChangeDetectorRef, Component, HostListener, - OnInit, - ViewChild + OnDestroy, + OnInit } from '@angular/core'; -import { - AbstractControl, - FormControl, - FormGroup, - ValidatorFn, - Validators -} from '@angular/forms'; +import { FormControl, FormGroup, Validators } from '@angular/forms'; import { environment } from './../../../environments/environment'; import { ActivatedRoute, Params } from '@angular/router'; -import { DataTableDirective } from 'angular-datatables'; -import { Observable, Subject } from 'rxjs'; +import { Observable } from 'rxjs'; import { Router } from '@angular/router'; import { @@ -52,7 +45,7 @@ import { Preprocessor } from 'src/app/core/_models/preprocessor.model'; templateUrl: './new-tasks.component.html', changeDetection: ChangeDetectionStrategy.Default }) -export class NewTasksComponent implements OnInit { +export class NewTasksComponent implements OnInit, OnDestroy { /** Flag indicating whether data is still loading. */ isLoading = true; @@ -119,39 +112,83 @@ export class NewTasksComponent implements OnInit { private dialog: MatDialog, private router: Router ) { + this.onInitialize(); titleService.set(['New Task']); } - ngOnInit(): void { + /** + * Initializes the component by extracting and setting the copy ID, + */ + onInitialize() { this.route.params.subscribe((params: Params) => { this.editedIndex = +params['id']; this.copyMode = params['id'] != null; }); - this.tasktip = this.tooltipService.getTaskTooltips(); + } + /** + * Initialize the component based on the data kind. + */ + ngOnInit(): void { this.route.data.subscribe((data) => { - switch (data['kind']) { - case 'new-task': - this.whichView = 'create'; - break; - - case 'copy-task': - this.whichView = 'edit'; - this.copyType = 0; - this.initFormt(); - break; - - case 'copy-pretask': - this.whichView = 'edit'; - this.copyType = 1; - this.initFormpt(); - break; - } + this.determineView(data['kind']); }); + this.buildForm(); this.loadData(); + } + /** + * Lifecycle hook called before the component is destroyed. + * Unsubscribes from all subscriptions to prevent memory leaks. + */ + ngOnDestroy(): void { + this.unsubscribeService.unsubscribeAll(); + } + + /** + * Determine the view and set up the component accordingly. + * @param kind The type of data (e.g., 'new-task', 'copy-task', 'copy-pretask'). + */ + private determineView(kind: string): void { + switch (kind) { + case 'new-task': + this.setupForCreate(); + break; + + case 'copy-task': + this.setupForCopy(0); + break; + + case 'copy-pretask': + this.setupForCopy(1); + break; + } + } + + /** + * Set up the component for creating a new task. + */ + private setupForCreate(): void { + this.whichView = 'create'; + } + + /** + * Set up the component for copying an existing task or pretask. + * @param copyType The type of data to copy (0 for task, 1 for pretask). + */ + private setupForCopy(copyType: number): void { + this.whichView = 'edit'; + this.copyType = copyType; + this.initForm(copyType === 0); + } + + /** + * Builds the form for creating a Task. + * Initializes the form controls with default or UI settings values. + */ + buildForm() { this.form = new FormGroup({ taskName: new FormControl('', [Validators.required]), notes: new FormControl(''), @@ -193,6 +230,9 @@ export class NewTasksComponent implements OnInit { }); } + /** + * Loads data for hashslists and crackers types. + */ loadData() { // Load Hahslists Select Options const loadHashlistsSubscription$ = this.gs @@ -249,6 +289,10 @@ export class NewTasksComponent implements OnInit { this.unsubscribeService.add(loadPreprocessorsSubscription$); } + /** + * Retrieves the form data containing attack command and files. + * @returns An object with attack command and files. + */ getFormData() { return { attackCmd: this.form.get('attackCmd').value, @@ -257,11 +301,19 @@ export class NewTasksComponent implements OnInit { }; } + /** + * Check if the current form has preprocessor enabled, otherwise dont show on table + * @returns {boolean} True if the form is for a preprocessor task, false otherwise. + */ isPreprocessor(): boolean { const preprocessorId = this.form.get('preprocessorId').value; return preprocessorId !== 0 && preprocessorId !== 'null'; } + /** + * Updates the form based on the provided event data. + * @param event - The event data containing attack command and files. + */ onUpdateForm(event: any): void { if (event.type === 'CMD') { this.form.patchValue({ @@ -275,6 +327,10 @@ export class NewTasksComponent implements OnInit { } } + /** + * Handle the change of cracker binary type and update the available cracker versions. + * @param {string} id - The identifier of the selected cracker binary type. + */ handleChangeBinary(id: string) { const onChangeBinarySubscription$ = this.gs .getAll(SERV.CRACKERS, { filter: 'crackerBinaryTypeId=' + id + '' }) @@ -290,44 +346,44 @@ export class NewTasksComponent implements OnInit { this.unsubscribeService.add(onChangeBinarySubscription$); } - onSubmit() { - if (this.form.valid) { - const onSubmitSubscription$ = this.gs - .create(SERV.TASKS, this.form.value) - .subscribe(() => { - this.alert.okAlert('New Task created!', ''); - this.form.reset(); - this.router.navigate(['tasks/show-tasks']); - }); - this.unsubscribeService.add(onSubmitSubscription$); - } - } - /** - * Copied from Task + * Initialize the form based on the copied data. + * @param isTask Determines whether the copied data is a Task or Pretask. */ - private initFormt() { + private initForm(isTask: boolean) { if (this.copyMode) { + const endpoint = isTask ? SERV.TASKS : SERV.PRETASKS; + const expandField = isTask + ? 'hashlist,speeds,crackerBinary,crackerBinaryType,files' + : 'pretaskFiles'; + this.gs - .get(SERV.TASKS, this.editedIndex, { - expand: 'hashlist,speeds,crackerBinary,crackerBinaryType,files' - }) + .get(endpoint, this.editedIndex, { expand: expandField }) .subscribe((result) => { const arrFiles: Array = []; - if (result.files) { - for (let i = 0; i < result.files.length; i++) { - arrFiles.push(result.files[i]['fileId']); + const filesField = isTask ? 'files' : 'pretaskFiles'; + + if (result[filesField]) { + for (let i = 0; i < result[filesField].length; i++) { + arrFiles.push(result[filesField][i]['fileId']); } + this.copyFiles = arrFiles; } + this.form = new FormGroup({ taskName: new FormControl( - result['taskName'] + '_(Copied_task_id_' + this.editedIndex + ')', + result['taskName'] + + `_(Copied_${isTask ? 'task_id' : 'pretask_id'}_${ + this.editedIndex + })`, [Validators.required, Validators.minLength(1)] ), notes: new FormControl( - 'Copied from task id' + this.editedIndex + '' + `Copied from ${isTask ? 'task' : 'pretask'} id ${ + this.editedIndex + }` ), - hashlistId: new FormControl(result.hashlist['hashlistId']), + hashlistId: new FormControl(result['hashlist']['hashlistId']), attackCmd: new FormControl(result['attackCmd'], [ Validators.required ]), @@ -340,17 +396,22 @@ export class NewTasksComponent implements OnInit { crackerBinaryTypeId: new FormControl(result['crackerBinaryTypeId']), isSmall: new FormControl(result['isSmall']), useNewBench: new FormControl(result['useNewBench']), - // 'isMaskImport': new FormControl(result['isMaskImport']), - skipKeyspace: new FormControl(result['skipKeyspace']), + skipKeyspace: new FormControl(isTask ? result['skipKeyspace'] : 0), crackerBinaryId: new FormControl( - result.crackerBinary['crackerBinaryId'] + isTask ? 1 : result.crackerBinary['crackerBinaryId'] ), isArchived: new FormControl(false), - staticChunks: new FormControl(result['staticChunks']), - chunkSize: new FormControl(result['chunkSize']), - forcePipe: new FormControl(result['forcePipe']), - preprocessorId: new FormControl(result['preprocessorId']), - preprocessorCommand: new FormControl(result['preprocessorCommand']), + staticChunks: new FormControl(isTask ? result['staticChunks'] : 0), + chunkSize: new FormControl( + isTask ? this.chunkSize : result['chunkSize'] + ), + forcePipe: new FormControl(isTask ? result['forcePipe'] : false), + preprocessorId: new FormControl( + isTask ? result['preprocessorId'] : 0 + ), + preprocessorCommand: new FormControl( + isTask ? result['preprocessorCommand'] : '' + ), files: new FormControl(arrFiles) }); }); @@ -358,56 +419,18 @@ export class NewTasksComponent implements OnInit { } /** - * Copied from PreTask + * Submits the form data and creates a new Task. */ - private initFormpt() { - if (this.copyMode) { - this.gs - .get(SERV.PRETASKS, this.editedIndex, { expand: 'pretaskFiles' }) - .subscribe((result) => { - const arrFiles: Array = []; - if (result['pretaskFiles']) { - for (let i = 0; i < result['pretaskFiles'].length; i++) { - arrFiles.push(result['pretaskFiles'][i]['fileId']); - } - this.copyFiles = arrFiles; - } - this.form = new FormGroup({ - taskName: new FormControl( - result['taskName'] + - '_(Copied_pretask_id_' + - this.editedIndex + - ')', - [Validators.required, Validators.minLength(1)] - ), - notes: new FormControl( - 'Copied from pretask id ' + this.editedIndex + '' - ), - hashlistId: new FormControl(), - attackCmd: new FormControl(result['attackCmd'], [ - Validators.required - ]), - maxAgents: new FormControl(result['maxAgents']), - chunkTime: new FormControl(result['chunkTime']), - statusTimer: new FormControl(result['statusTimer']), - priority: new FormControl(result['priority']), - color: new FormControl(result['color']), - isCpuTask: new FormControl(result['isCpuTask']), - crackerBinaryTypeId: new FormControl(result['crackerBinaryTypeId']), - isSmall: new FormControl(result['isSmall']), - useNewBench: new FormControl(result['useNewBench']), - // 'isMaskImport': new FormControl(result['isMaskImport']), //Now is not working with it - skipKeyspace: new FormControl(null || 0), - crackerBinaryId: new FormControl(null || 1), - isArchived: new FormControl(false), - staticChunks: new FormControl(null || 0), - chunkSize: new FormControl(null || this.chunkSize), - forcePipe: new FormControl(null || false), - preprocessorId: new FormControl(0), - preprocessorCommand: new FormControl(''), - files: new FormControl(arrFiles) - }); + onSubmit() { + if (this.form.valid) { + const onSubmitSubscription$ = this.gs + .create(SERV.TASKS, this.form.value) + .subscribe(() => { + this.alert.okAlert('New Task created!', ''); + this.form.reset(); + this.router.navigate(['tasks/show-tasks']); }); + this.unsubscribeService.add(onSubmitSubscription$); } } From 0440f57321d865c5ae4bfe8ccd3819dcfa56cbbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Thu, 21 Dec 2023 14:21:28 +0000 Subject: [PATCH 362/419] Search Hash, Table and Form --- .../_components/core-components.module.ts | 3 + .../tables/ht-table/ht-table.models.ts | 1 + .../search-hash-table.component.html | 15 ++ .../search-hash-table.component.ts | 151 ++++++++++++++++++ .../search-hash-table.constants.ts | 9 ++ .../_datasources/search-hash.datasource.ts | 66 ++++++++ src/app/core/_models/config-ui.model.ts | 2 + .../search-hash/search-hash.component.html | 34 +--- .../search-hash/search-hash.component.ts | 82 ++++++---- 9 files changed, 299 insertions(+), 64 deletions(-) create mode 100644 src/app/core/_components/tables/search-hash-table/search-hash-table.component.html create mode 100644 src/app/core/_components/tables/search-hash-table/search-hash-table.component.ts create mode 100644 src/app/core/_components/tables/search-hash-table/search-hash-table.constants.ts create mode 100644 src/app/core/_datasources/search-hash.datasource.ts diff --git a/src/app/core/_components/core-components.module.ts b/src/app/core/_components/core-components.module.ts index cde7d514..931cebb0 100644 --- a/src/app/core/_components/core-components.module.ts +++ b/src/app/core/_components/core-components.module.ts @@ -54,6 +54,7 @@ import { TableTruncateComponent } from './tables/table-truncate/table-truncate.c import { TasksTableComponent } from './tables/tasks-table/tasks-table.component'; import { UsersTableComponent } from './tables/users-table/users-table.component'; import { VouchersTableComponent } from './tables/vouchers-table/vouchers-table.component'; +import { SearchHashTableComponent } from './tables/search-hash-table/search-hash-table.component'; @NgModule({ declarations: [ @@ -87,6 +88,7 @@ import { VouchersTableComponent } from './tables/vouchers-table/vouchers-table.c PermissionsTableComponent, CracksTableComponent, VouchersTableComponent, + SearchHashTableComponent, TasksTableComponent, CracksTableComponent ], @@ -144,6 +146,7 @@ import { VouchersTableComponent } from './tables/vouchers-table/vouchers-table.c PermissionsTableComponent, CracksTableComponent, VouchersTableComponent, + SearchHashTableComponent, TasksTableComponent, CracksTableComponent ], diff --git a/src/app/core/_components/tables/ht-table/ht-table.models.ts b/src/app/core/_components/tables/ht-table/ht-table.models.ts index be3e605b..2c607c6c 100644 --- a/src/app/core/_components/tables/ht-table/ht-table.models.ts +++ b/src/app/core/_components/tables/ht-table/ht-table.models.ts @@ -4,6 +4,7 @@ import { SafeHtml } from '@angular/platform-browser'; export type DataType = | 'agents' | 'hashlists' + | 'search-hash' | 'chunks' | 'hashtypes' | 'files' diff --git a/src/app/core/_components/tables/search-hash-table/search-hash-table.component.html b/src/app/core/_components/tables/search-hash-table/search-hash-table.component.html new file mode 100644 index 00000000..281ecac0 --- /dev/null +++ b/src/app/core/_components/tables/search-hash-table/search-hash-table.component.html @@ -0,0 +1,15 @@ + diff --git a/src/app/core/_components/tables/search-hash-table/search-hash-table.component.ts b/src/app/core/_components/tables/search-hash-table/search-hash-table.component.ts new file mode 100644 index 00000000..94809be4 --- /dev/null +++ b/src/app/core/_components/tables/search-hash-table/search-hash-table.component.ts @@ -0,0 +1,151 @@ +/* eslint-disable @angular-eslint/component-selector */ +import { + Component, + Input, + OnChanges, + OnDestroy, + OnInit, + SimpleChanges +} from '@angular/core'; +import { + SearchHashTableCol, + SearchHashTableColumnLabel +} from './search-hash-table.constants'; + +import { ActionMenuEvent } from '../../menus/action-menu/action-menu.model'; +import { BaseTableComponent } from '../base-table/base-table.component'; +import { ExportMenuAction } from '../../menus/export-menu/export-menu.constants'; +import { HTTableColumn } from '../ht-table/ht-table.models'; +import { SearchHashDataSource } from 'src/app/core/_datasources/search-hash.datasource'; +import { formatUnixTimestamp } from 'src/app/shared/utils/datetime'; +import { SafeHtml } from '@angular/platform-browser'; + +@Component({ + selector: 'search-hash-table', + templateUrl: './search-hash-table.component.html' +}) +export class SearchHashTableComponent + extends BaseTableComponent + implements OnInit, OnDestroy, OnChanges +{ + @Input() search: any[]; + tableColumns: HTTableColumn[] = []; + dataSource: SearchHashDataSource; + + ngOnInit(): void { + this.setColumnLabels(SearchHashTableColumnLabel); + this.tableColumns = this.getColumns(); + this.dataSource = new SearchHashDataSource( + this.cdr, + this.gs, + this.uiService + ); + if (this.search) { + this.dataSource.setSearch(this.search); + } + this.dataSource.setColumns(this.tableColumns); + this.dataSource.loadAll(); + } + + ngOnDestroy(): void { + for (const sub of this.subscriptions) { + sub.unsubscribe(); + } + } + + filter(item: any, filterValue: string): boolean { + if (item.message.toLowerCase().includes(filterValue)) { + return true; + } + + return false; + } + + getColumns(): HTTableColumn[] { + const tableColumns = [ + { + id: SearchHashTableCol.HASH, + dataKey: 'hash', + isSortable: true, + export: async (hash: any) => hash.hash + '' + }, + { + id: SearchHashTableCol.INFO, + dataKey: 'isCracked', + isSortable: true, + render: (hash: any) => this.renderHashInfo(hash), + export: async (hash: any) => this.renderHashInfo(hash) + '' + } + ]; + + return tableColumns; + } + + // --- Action functions --- + + exportActionClicked(event: ActionMenuEvent): void { + switch (event.menuItem.action) { + case ExportMenuAction.EXCEL: + this.exportService.toExcel( + 'hashtopolis-search-hash', + this.tableColumns, + event.data, + SearchHashTableColumnLabel + ); + break; + case ExportMenuAction.CSV: + this.exportService.toCsv( + 'hashtopolis-search-hash', + this.tableColumns, + event.data, + SearchHashTableColumnLabel + ); + break; + case ExportMenuAction.COPY: + this.exportService + .toClipboard( + this.tableColumns, + event.data, + SearchHashTableColumnLabel + ) + .then(() => { + this.snackBar.open( + 'The selected rows are copied to the clipboard', + 'Close' + ); + }); + break; + } + } + + ngOnChanges(changes: SimpleChanges): void { + if (changes['search']) { + this.dataSource.setSearch(changes['search'].currentValue); + this.dataSource.setColumns(this.tableColumns); + this.dataSource.loadAll(); + } + } + + renderHashInfo(hash: any): SafeHtml { + let htmlContent = ''; + if (hash.isCracked !== 3) { + htmlContent = ` + ${hash.isCracked ? 'Cracked' : 'Uncracked'} on ${formatUnixTimestamp( + hash.timeCracked, + this.dateFormat + )} +
+ Hashlist: +
${hash.hashlistId} + `; + } else { + htmlContent = 'Not Found'; + } + return this.sanitizer.bypassSecurityTrustHtml(htmlContent); + } +} diff --git a/src/app/core/_components/tables/search-hash-table/search-hash-table.constants.ts b/src/app/core/_components/tables/search-hash-table/search-hash-table.constants.ts new file mode 100644 index 00000000..cf2732cd --- /dev/null +++ b/src/app/core/_components/tables/search-hash-table/search-hash-table.constants.ts @@ -0,0 +1,9 @@ +export enum SearchHashTableCol { + HASH, + INFO +} + +export const SearchHashTableColumnLabel = { + [SearchHashTableCol.HASH]: 'Hash', + [SearchHashTableCol.INFO]: 'Info' +}; diff --git a/src/app/core/_datasources/search-hash.datasource.ts b/src/app/core/_datasources/search-hash.datasource.ts new file mode 100644 index 00000000..0f53c04b --- /dev/null +++ b/src/app/core/_datasources/search-hash.datasource.ts @@ -0,0 +1,66 @@ +import { catchError, finalize, of } from 'rxjs'; +import { BaseDataSource } from './base.datasource'; +import { ListResponseWrapper } from '../_models/response.model'; +import { Log } from '../_models/log.model'; +import { SERV } from '../_services/main.config'; + +export class SearchHashDataSource extends BaseDataSource { + private _search: string[]; + + setSearch(hashArray: string[]): void { + this._search = hashArray; + } + + loadAll(): void { + this.loading = true; + + const startAt = this.currentPage * this.pageSize; + const arr = []; + + for (let i = 0; i < this._search.length; i++) { + const params = { + maxResults: this.pageSize, + startAt: startAt, + filter: `hash=${this._search[i]}` + }; + + const hashs$ = this.service.getAll(SERV.HASHES, params); + + this.subscriptions.push( + hashs$ + .pipe( + catchError((error) => { + console.error(`Error loading hash: ${this._search[i]}`, error); + return of([]); + }), + finalize(() => { + if (i === this._search.length - 1) { + this.loading = false; + this.setData(arr); + } + }) + ) + .subscribe((response: ListResponseWrapper) => { + const hashs: any[] = response.values; + + if (hashs[0]) { + arr.push(hashs[0]); + } else { + arr.push({ hash: this._search[i], isCracked: 3 }); + } + this.setPaginationConfig( + this.pageSize, + this.currentPage, + response.total + ); + this.setData(arr); + }) + ); + } + } + + reload(): void { + this.clearSelection(); + this.loadAll(); + } +} diff --git a/src/app/core/_models/config-ui.model.ts b/src/app/core/_models/config-ui.model.ts index daf97d70..caa4dff0 100644 --- a/src/app/core/_models/config-ui.model.ts +++ b/src/app/core/_models/config-ui.model.ts @@ -12,6 +12,7 @@ import { LogsTableCol } from '../_components/tables/logs-table/logs-table.consta import { NotificationsTableCol } from '../_components/tables/notifications-table/notifications-table.constants'; import { PermissionsTableCol } from '../_components/tables/permissions-table/permissions-table.constants'; import { PreprocessorsTableCol } from '../_components/tables/preprocessors-table/preprocessors-table.constants'; +import { SearchHashTableCol } from '../_components/tables/search-hash-table/search-hash-table.constants'; import { SuperHashlistsTableCol } from '../_components/tables/super-hashlists-table/super-hashlists-table.constants'; import { TaskTableCol } from '../_components/tables/tasks-table/tasks-table.constants'; import { UsersTableCol } from '../_components/tables/users-table/users-table.constants'; @@ -155,6 +156,7 @@ export const uiConfigDefault: UIConfig = { TaskTableCol.DISPATCHED_SEARCHED, TaskTableCol.CRACKED ], + searchHashTable: [SearchHashTableCol.HASH, SearchHashTableCol.INFO], usersTable: [ UsersTableCol.ID, UsersTableCol.NAME, diff --git a/src/app/hashlists/search-hash/search-hash.component.html b/src/app/hashlists/search-hash/search-hash.component.html index 8a7eb7a5..eedea5c1 100644 --- a/src/app/hashlists/search-hash/search-hash.component.html +++ b/src/app/hashlists/search-hash/search-hash.component.html @@ -1,36 +1,10 @@ -
- + - - - +
- - - - - - - - - - - - - - - - -
HashInfo
{{ n.hash }} - {{ n.isCracked ? 'Cracked':'Uncracked' }} on {{ n.timeCracked | uiDate }} -
- Hashlist: - {{ n.hashlistId }} -
Not Found
+ + diff --git a/src/app/hashlists/search-hash/search-hash.component.ts b/src/app/hashlists/search-hash/search-hash.component.ts index 43d78dd1..b548ab28 100644 --- a/src/app/hashlists/search-hash/search-hash.component.ts +++ b/src/app/hashlists/search-hash/search-hash.component.ts @@ -1,62 +1,76 @@ -import { faMagnifyingGlass } from '@fortawesome/free-solid-svg-icons'; import { FormControl, FormGroup, Validators } from '@angular/forms'; -import { Component, OnInit } from '@angular/core'; -import { Router } from '@angular/router'; +import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core'; -import { UIConfigService } from 'src/app/core/_services/shared/storage.service'; +import { AutoTitleService } from 'src/app/core/_services/shared/autotitle.service'; import { GlobalService } from 'src/app/core/_services/main.service'; -import { environment } from './../../../environments/environment'; import { PageTitle } from 'src/app/core/_decorators/autotitle'; -import { SERV } from '../../core/_services/main.config'; +import { UnsubscribeService } from 'src/app/core/_services/unsubscribe.service'; @Component({ selector: 'app-search-hash', templateUrl: './search-hash.component.html' }) @PageTitle(['Search Hash']) -export class SearchHashComponent implements OnInit { +export class SearchHashComponent implements OnInit, OnDestroy { + /** Form group for Search Hashes. */ + form: FormGroup; - faMagnifyingGlass=faMagnifyingGlass; + /** Result of the search. */ + private _searchResults: any[] = []; constructor( - private uiService: UIConfigService, - private gs: GlobalService, - private router: Router - ) { } - - createForm: FormGroup; - searh: any; - - private maxResults = environment.config.prodApiMaxResults; + private unsubscribeService: UnsubscribeService, + private titleService: AutoTitleService, + private cdr: ChangeDetectorRef, + private gs: GlobalService + ) { + titleService.set(['Search Hash']); + } ngOnInit(): void { + this.buildForm(); + } - this.createForm = new FormGroup({ - hashes: new FormControl('', [Validators.required]), + /** + * Build Form + */ + buildForm() { + this.form = new FormGroup({ + hashes: new FormControl('', [Validators.required]) }); + } + /** + * Lifecycle hook called before the component is destroyed. + * Unsubscribes from all subscriptions to prevent memory leaks. + */ + ngOnDestroy(): void { + this.unsubscribeService.unsubscribeAll(); } - onSubmit(){ - if (this.createForm.valid) { + get searchResults(): any[] { + return this._searchResults; + } - let hash = this.createForm.value['hashes'].split(/(\s+)/).filter( function(e) { return e.trim().length > 0; } ); - const arr = []; - for(let i=0; i < hash.length; i++){ + set searchResults(value: any[]) { + this._searchResults = value; + } - this.gs.getAll(SERV.HASHES,{'filter':'hash='+hash[i]+''}).subscribe((res: any) => { - if(res.values[0]){ - arr.push(res.values[0]); - }else{ - arr.push({'hash':hash[i],'isCracked': 3}); - } + /** + * Search Hash and return results + */ + onSubmit() { + if (this.form.valid) { + const currentSearchResult = this.form.value['hashes'] + .split(/(\s+)/) + .filter(function (e) { + return e.trim().length > 0; }); - } - - this.searh = arr; + this.searchResults = [currentSearchResult]; + this.cdr.detectChanges(); + this.form.reset(); } } - } From b75fcb4226b13fa83a791521ea994b70d7bb7482 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Fri, 22 Dec 2023 16:37:18 +0000 Subject: [PATCH 363/419] Supertask subtasks modal --- .../_components/core-components.module.ts | 3 + .../tables/ht-table/ht-table.models.ts | 1 + .../supertasks-pretasks-table.component.html | 17 + .../supertasks-pretasks-table.component.ts | 314 ++++++++++++++++++ .../supertasks-pretasks-table.constants.ts | 11 + .../supertasks-table.component.ts | 15 +- src/app/core/_datasources/base.datasource.ts | 8 + .../supertasks-pretasks.datasource.ts | 53 +++ ...datasource.ts => supertasks.datasource.ts} | 0 src/app/core/_models/config-ui.model.ts | 6 + .../modal-pretasks.component.html | 51 +-- .../modal-pretasks.component.ts | 121 +------ .../supertasks/supertasks.component.html | 5 - .../tasks/supertasks/supertasks.component.ts | 9 - 14 files changed, 442 insertions(+), 172 deletions(-) create mode 100644 src/app/core/_components/tables/supertasks-pretasks-table/supertasks-pretasks-table.component.html create mode 100644 src/app/core/_components/tables/supertasks-pretasks-table/supertasks-pretasks-table.component.ts create mode 100644 src/app/core/_components/tables/supertasks-pretasks-table/supertasks-pretasks-table.constants.ts create mode 100644 src/app/core/_datasources/supertasks-pretasks.datasource.ts rename src/app/core/_datasources/{super-tasks.datasource.ts => supertasks.datasource.ts} (100%) diff --git a/src/app/core/_components/core-components.module.ts b/src/app/core/_components/core-components.module.ts index 3c4afa73..8790f5f4 100644 --- a/src/app/core/_components/core-components.module.ts +++ b/src/app/core/_components/core-components.module.ts @@ -56,6 +56,7 @@ import { TableTruncateComponent } from './tables/table-truncate/table-truncate.c import { TasksTableComponent } from './tables/tasks-table/tasks-table.component'; import { UsersTableComponent } from './tables/users-table/users-table.component'; import { VouchersTableComponent } from './tables/vouchers-table/vouchers-table.component'; +import { SuperTasksPretasksTableComponent } from './tables/supertasks-pretasks-table/supertasks-pretasks-table.component'; @NgModule({ declarations: [ @@ -82,6 +83,7 @@ import { VouchersTableComponent } from './tables/vouchers-table/vouchers-table.c PreprocessorsTableComponent, PretasksTableComponent, SuperTasksTableComponent, + SuperTasksPretasksTableComponent, AgentBinariesTableComponent, HealthChecksTableComponent, LogsTableComponent, @@ -141,6 +143,7 @@ import { VouchersTableComponent } from './tables/vouchers-table/vouchers-table.c PreprocessorsTableComponent, PretasksTableComponent, SuperTasksTableComponent, + SuperTasksPretasksTableComponent, AgentBinariesTableComponent, HealthChecksTableComponent, LogsTableComponent, diff --git a/src/app/core/_components/tables/ht-table/ht-table.models.ts b/src/app/core/_components/tables/ht-table/ht-table.models.ts index 010126e0..4f07a183 100644 --- a/src/app/core/_components/tables/ht-table/ht-table.models.ts +++ b/src/app/core/_components/tables/ht-table/ht-table.models.ts @@ -21,6 +21,7 @@ export type DataType = | 'pretasks' | 'tasks' | 'supertasks' + | 'supertasks-pretasks' | 'superhashlists'; export interface HTTableIcon { diff --git a/src/app/core/_components/tables/supertasks-pretasks-table/supertasks-pretasks-table.component.html b/src/app/core/_components/tables/supertasks-pretasks-table/supertasks-pretasks-table.component.html new file mode 100644 index 00000000..90123449 --- /dev/null +++ b/src/app/core/_components/tables/supertasks-pretasks-table/supertasks-pretasks-table.component.html @@ -0,0 +1,17 @@ + diff --git a/src/app/core/_components/tables/supertasks-pretasks-table/supertasks-pretasks-table.component.ts b/src/app/core/_components/tables/supertasks-pretasks-table/supertasks-pretasks-table.component.ts new file mode 100644 index 00000000..f6486f84 --- /dev/null +++ b/src/app/core/_components/tables/supertasks-pretasks-table/supertasks-pretasks-table.component.ts @@ -0,0 +1,314 @@ +/* eslint-disable @angular-eslint/component-selector */ +import { Component, Input, OnDestroy, OnInit } from '@angular/core'; +import { + HTTableColumn, + HTTableIcon, + HTTableRouterLink +} from '../ht-table/ht-table.models'; +import { + SupertasksPretasksTableCol, + SupertasksPretasksTableColumnLabel +} from './supertasks-pretasks-table.constants'; +import { catchError, forkJoin } from 'rxjs'; + +import { ActionMenuEvent } from '../../menus/action-menu/action-menu.model'; +import { BaseTableComponent } from '../base-table/base-table.component'; +import { BulkActionMenuAction } from '../../menus/bulk-action-menu/bulk-action-menu.constants'; +import { Cacheable } from 'src/app/core/_decorators/cacheable'; +import { DialogData } from '../table-dialog/table-dialog.model'; +import { ExportMenuAction } from '../../menus/export-menu/export-menu.constants'; +import { Pretask } from 'src/app/core/_models/pretask.model'; +import { RowActionMenuAction } from '../../menus/row-action-menu/row-action-menu.constants'; +import { SERV } from 'src/app/core/_services/main.config'; +import { TableDialogComponent } from '../table-dialog/table-dialog.component'; +import { SuperTasksPretasksDataSource } from 'src/app/core/_datasources/supertasks-pretasks.datasource'; +import { SafeHtml } from '@angular/platform-browser'; + +@Component({ + selector: 'supertasks-pretasks-table', + templateUrl: './supertasks-pretasks-table.component.html' +}) +export class SuperTasksPretasksTableComponent + extends BaseTableComponent + implements OnInit, OnDestroy +{ + @Input() supertaskId = 0; + + tableColumns: HTTableColumn[] = []; + dataSource: SuperTasksPretasksDataSource; + + ngOnInit(): void { + this.setColumnLabels(SupertasksPretasksTableColumnLabel); + this.tableColumns = this.getColumns(); + this.dataSource = new SuperTasksPretasksDataSource( + this.cdr, + this.gs, + this.uiService + ); + this.dataSource.setColumns(this.tableColumns); + if (this.supertaskId) { + this.dataSource.setSuperTaskId(this.supertaskId); + } + this.dataSource.loadAll(); + } + + ngOnDestroy(): void { + for (const sub of this.subscriptions) { + sub.unsubscribe(); + } + } + + filter(item: Pretask, filterValue: string): boolean { + return item.taskName.toLowerCase().includes(filterValue); + } + + getColumns(): HTTableColumn[] { + const tableColumns = [ + { + id: SupertasksPretasksTableCol.ID, + dataKey: 'pretaskId', + routerLink: (pretask: Pretask) => this.renderPretaskLink(pretask), + isSortable: true, + export: async (pretask: Pretask) => pretask._id + '' + }, + { + id: SupertasksPretasksTableCol.NAME, + dataKey: 'taskName', + isSortable: true, + render: (pretask: Pretask) => pretask.taskName, + export: async (pretask: Pretask) => pretask.taskName + '' + }, + { + id: SupertasksPretasksTableCol.PRIORITY, + dataKey: 'priority', + isSortable: true, + // render: (pretask: Pretask) => this.renderPriorityColumn(pretask), + export: async (pretask: Pretask) => pretask.priority + '' + } + ]; + + return tableColumns; + } + + openDialog(data: DialogData) { + const dialogRef = this.dialog.open(TableDialogComponent, { + data: data, + width: '450px' + }); + + this.subscriptions.push( + dialogRef.afterClosed().subscribe((result) => { + if (result && result.action) { + switch (result.action) { + case RowActionMenuAction.DELETE: + this.rowActionDelete(result.data); + break; + case BulkActionMenuAction.DELETE: + this.bulkActionDelete(result.data); + break; + } + } + }) + ); + } + + // --- Action functions --- + + exportActionClicked(event: ActionMenuEvent): void { + switch (event.menuItem.action) { + case ExportMenuAction.EXCEL: + this.exportService.toExcel( + 'hashtopolis-supertasks-pretasks', + this.tableColumns, + event.data, + SupertasksPretasksTableColumnLabel + ); + break; + case ExportMenuAction.CSV: + this.exportService.toCsv( + 'hashtopolis-supertasks-pretasks', + this.tableColumns, + event.data, + SupertasksPretasksTableColumnLabel + ); + break; + case ExportMenuAction.COPY: + this.exportService + .toClipboard( + this.tableColumns, + event.data, + SupertasksPretasksTableColumnLabel + ) + .then(() => { + this.snackBar.open( + 'The selected rows are copied to the clipboard', + 'Close' + ); + }); + break; + } + } + + rowActionClicked(event: ActionMenuEvent): void { + switch (event.menuItem.action) { + case RowActionMenuAction.EDIT: + this.rowActionEdit(event.data); + break; + case RowActionMenuAction.COPY_TO_TASK: + console.log('Copy to Task clicked:', event.data); + this.rowActionCopyToTask(event.data); + break; + case RowActionMenuAction.COPY_TO_PRETASK: + console.log('Copy to Pretask clicked:', event.data); + this.rowActionCopyToPretask(event.data); + break; + case RowActionMenuAction.DELETE: + this.openDialog({ + rows: [event.data], + title: `Deleting Pretask ${event.data.taskName} ...`, + icon: 'warning', + body: `Are you sure you want to delete it? Note that this action cannot be undone.`, + warn: true, + action: event.menuItem.action + }); + break; + } + } + + bulkActionClicked(event: ActionMenuEvent): void { + switch (event.menuItem.action) { + case BulkActionMenuAction.DELETE: + this.openDialog({ + rows: event.data, + title: `Deleting ${event.data.length} pretasks ...`, + icon: 'warning', + body: `Are you sure you want to delete the above pretasks? Note that this action cannot be undone.`, + warn: true, + listAttribute: 'supertaskName', + action: event.menuItem.action + }); + break; + } + } + + /** + * @todo Implement delete, currently we need to update to delete + */ + private bulkActionDelete(pretasks: Pretask[]): void { + //Get the IDs of pretasks to be deleted + const pretaskIdsToDelete = pretasks.map((pretask) => pretask._id); + //Remove the selected pretasks from the list + const updatedPretasks = this.dataSource + .getData() + .filter((pretask) => !pretaskIdsToDelete.includes(pretask._id)); + //Update the supertask with the modified list of pretasks + const payload = { pretasks: updatedPretasks.map((pretask) => pretask._id) }; + //Update the supertask with the new list of pretasks + const updateRequest = this.gs.update( + SERV.SUPER_TASKS, + this.supertaskId, + payload + ); + this.subscriptions.push( + updateRequest + .pipe( + catchError((error) => { + console.error('Error during deletion:', error); + return []; + }) + ) + .subscribe(() => { + this.snackBar.open( + `Successfully deleted ${pretasks.length} pretasks!`, + 'Close' + ); + this.reload(); + }) + ); + } + + @Cacheable(['pretaskId']) + async renderPretaskLink(pretask: Pretask): Promise { + return [ + { + routerLink: ['/tasks/preconfigured-tasks/', pretask._id, 'edit'] + } + ]; + } + + /** + * @todo Implement error handling. + */ + private rowActionDelete(pretasks: Pretask[]): void { + //Get the IDs of pretasks to be deleted + const pretaskIdsToDelete = pretasks.map((pretask) => pretask._id); + //Remove the selected pretasks from the list + const updatedPretasks = this.dataSource + .getData() + .filter((pretask) => !pretaskIdsToDelete.includes(pretask._id)); + //Update the supertask with the modified list of pretasks + const payload = { pretasks: updatedPretasks.map((pretask) => pretask._id) }; + this.subscriptions.push( + this.gs + .update(SERV.SUPER_TASKS, this.supertaskId, payload) + .pipe( + catchError((error) => { + console.error('Error during deletion:', error); + return []; + }) + ) + .subscribe(() => { + this.snackBar.open('Successfully deleted pretasks!', 'Close'); + this.reload(); + }) + ); + } + + /** + * @todo Implement error handling. + */ + private rowSavePriority(pretasks: Pretask[]): void { + this.subscriptions.push( + this.gs + .update(SERV.PRETASKS, pretasks[0]._id, { + priority: 2 + }) + .pipe( + catchError((error) => { + console.error('Error during deletion:', error); + return []; + }) + ) + .subscribe(() => { + this.snackBar.open('Successfully deleted pretasks!', 'Close'); + this.reload(); + }) + ); + } + + renderPriorityColumn(pretask: Pretask): SafeHtml { + const inputHtml = ` + + + + + `; + return this.sanitizer.bypassSecurityTrustHtml(inputHtml); + } + + private rowActionCopyToTask(pretask: Pretask): void { + this.router.navigate(['/tasks/new-tasks', pretask._id, 'copypretask']); + } + + private rowActionCopyToPretask(pretask: Pretask): void { + this.router.navigate(['/tasks/preconfigured-tasks', pretask._id, 'copy']); + } + + private rowActionEdit(pretasks: Pretask): void { + this.renderPretaskLink(pretasks).then((links: HTTableRouterLink[]) => { + this.router.navigate(links[0].routerLink); + }); + } +} diff --git a/src/app/core/_components/tables/supertasks-pretasks-table/supertasks-pretasks-table.constants.ts b/src/app/core/_components/tables/supertasks-pretasks-table/supertasks-pretasks-table.constants.ts new file mode 100644 index 00000000..ed184b87 --- /dev/null +++ b/src/app/core/_components/tables/supertasks-pretasks-table/supertasks-pretasks-table.constants.ts @@ -0,0 +1,11 @@ +export enum SupertasksPretasksTableCol { + ID, + NAME, + PRIORITY +} + +export const SupertasksPretasksTableColumnLabel = { + [SupertasksPretasksTableCol.ID]: 'ID', + [SupertasksPretasksTableCol.NAME]: 'Name', + [SupertasksPretasksTableCol.PRIORITY]: 'Subtask Priority' +}; diff --git a/src/app/core/_components/tables/supertasks-table/supertasks-table.component.ts b/src/app/core/_components/tables/supertasks-table/supertasks-table.component.ts index bb938fbe..ec1b26cf 100644 --- a/src/app/core/_components/tables/supertasks-table/supertasks-table.component.ts +++ b/src/app/core/_components/tables/supertasks-table/supertasks-table.component.ts @@ -21,8 +21,9 @@ import { Pretask } from 'src/app/core/_models/pretask.model'; import { RowActionMenuAction } from '../../menus/row-action-menu/row-action-menu.constants'; import { SERV } from 'src/app/core/_services/main.config'; import { SuperTask } from 'src/app/core/_models/supertask.model'; -import { SuperTasksDataSource } from 'src/app/core/_datasources/super-tasks.datasource'; +import { SuperTasksDataSource } from 'src/app/core/_datasources/supertasks.datasource'; import { TableDialogComponent } from '../table-dialog/table-dialog.component'; +import { ModalPretasksComponent } from 'src/app/tasks/supertasks/modal-pretasks/modal-pretasks.component'; @Component({ selector: 'supertasks-table', @@ -247,7 +248,17 @@ export class SuperTasksTableComponent } private rowActionEditSubtasks(supertask: SuperTask): void { - console.log('here'); + const dialogRef = this.dialog.open(ModalPretasksComponent, { + width: '100%', + data: { + supertaskId: supertask._id, + supertaskName: supertask.supertaskName + } + }); + + dialogRef.afterClosed().subscribe((result) => { + console.log('The modal was closed'); + }); } private rowActionEdit(supertask: SuperTask): void { diff --git a/src/app/core/_datasources/base.datasource.ts b/src/app/core/_datasources/base.datasource.ts index be67aa56..7ce9fd2c 100644 --- a/src/app/core/_datasources/base.datasource.ts +++ b/src/app/core/_datasources/base.datasource.ts @@ -109,6 +109,14 @@ export abstract class BaseDataSource< this.cdr.detectChanges(); } + /** + * Gets the original unfiltered data for the table. + * @returns {T[]} The original unfiltered data. + */ + getOriginalData(): T[] { + return this.originalData; + } + /** * Connect the data source to a collection viewer. * diff --git a/src/app/core/_datasources/supertasks-pretasks.datasource.ts b/src/app/core/_datasources/supertasks-pretasks.datasource.ts new file mode 100644 index 00000000..7977e4bf --- /dev/null +++ b/src/app/core/_datasources/supertasks-pretasks.datasource.ts @@ -0,0 +1,53 @@ +import { catchError, finalize, of } from 'rxjs'; + +import { BaseDataSource } from './base.datasource'; +import { ListResponseWrapper } from '../_models/response.model'; +import { MatTableDataSourcePaginator } from '@angular/material/table'; +import { SERV } from '../_services/main.config'; +import { SuperTask } from '../_models/supertask.model'; + +export class SuperTasksPretasksDataSource extends BaseDataSource< + SuperTask, + MatTableDataSourcePaginator +> { + private _supertTaskId = 0; + + setSuperTaskId(supertTaskId: number) { + this._supertTaskId = supertTaskId; + } + + loadAll(): void { + this.loading = true; + + const params = { + expand: 'pretasks' + }; + + const pretasks$ = this.service.get( + SERV.SUPER_TASKS, + this._supertTaskId, + params + ); + + this.subscriptions.push( + pretasks$ + .pipe( + catchError(() => of([])), + finalize(() => (this.loading = false)) + ) + .subscribe((response: ListResponseWrapper) => { + const pretasks: SuperTask[] = response['pretasks']; + this.setData(pretasks); + }) + ); + } + + getData(): SuperTask[] { + return this.getOriginalData(); + } + + reload(): void { + this.clearSelection(); + this.loadAll(); + } +} diff --git a/src/app/core/_datasources/super-tasks.datasource.ts b/src/app/core/_datasources/supertasks.datasource.ts similarity index 100% rename from src/app/core/_datasources/super-tasks.datasource.ts rename to src/app/core/_datasources/supertasks.datasource.ts diff --git a/src/app/core/_models/config-ui.model.ts b/src/app/core/_models/config-ui.model.ts index db2bccbb..08d45cc5 100644 --- a/src/app/core/_models/config-ui.model.ts +++ b/src/app/core/_models/config-ui.model.ts @@ -14,6 +14,7 @@ import { PermissionsTableCol } from '../_components/tables/permissions-table/per import { PreprocessorsTableCol } from '../_components/tables/preprocessors-table/preprocessors-table.constants'; import { PretasksTableCol } from '../_components/tables/pretasks-table/pretasks-table.constants'; import { SuperHashlistsTableCol } from '../_components/tables/super-hashlists-table/super-hashlists-table.constants'; +import { SupertasksPretasksTableCol } from '../_components/tables/supertasks-pretasks-table/supertasks-pretasks-table.constants'; import { SupertasksTableCol } from '../_components/tables/supertasks-table/supertasks-table.constants'; import { TaskTableCol } from '../_components/tables/tasks-table/tasks-table.constants'; import { UsersTableCol } from '../_components/tables/users-table/users-table.constants'; @@ -165,6 +166,11 @@ export const uiConfigDefault: UIConfig = { SupertasksTableCol.NAME, SupertasksTableCol.PRETASKS ], + supertasksPretasksTable: [ + SupertasksPretasksTableCol.ID, + SupertasksPretasksTableCol.NAME, + SupertasksPretasksTableCol.PRIORITY + ], hashlistTasksTable: [ TaskTableCol.ID, TaskTableCol.NAME, diff --git a/src/app/tasks/supertasks/modal-pretasks/modal-pretasks.component.html b/src/app/tasks/supertasks/modal-pretasks/modal-pretasks.component.html index 266c4b7c..598c620c 100644 --- a/src/app/tasks/supertasks/modal-pretasks/modal-pretasks.component.html +++ b/src/app/tasks/supertasks/modal-pretasks/modal-pretasks.component.html @@ -1,46 +1,7 @@ - - - + + + + + + diff --git a/src/app/tasks/supertasks/modal-pretasks/modal-pretasks.component.ts b/src/app/tasks/supertasks/modal-pretasks/modal-pretasks.component.ts index efa6a25b..20a692c3 100644 --- a/src/app/tasks/supertasks/modal-pretasks/modal-pretasks.component.ts +++ b/src/app/tasks/supertasks/modal-pretasks/modal-pretasks.component.ts @@ -1,122 +1,21 @@ -import { FormArray, FormBuilder, FormControl, FormGroup } from '@angular/forms'; -import { Component, Input, OnInit, ViewChild } from '@angular/core'; -import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; -import { faTrash } from '@fortawesome/free-solid-svg-icons'; -import { DataTableDirective } from 'angular-datatables'; -import Swal from 'sweetalert2/dist/sweetalert2.js'; -import { Router } from '@angular/router'; -import { Subject } from 'rxjs'; - -import { GlobalService } from 'src/app/core/_services/main.service'; -import { SERV } from '../../../core/_services/main.config'; +import { Component, Inject } from '@angular/core'; +import { MAT_DIALOG_DATA } from '@angular/material/dialog'; @Component({ selector: 'app-modal-st-pretasks', templateUrl: './modal-pretasks.component.html' }) -export class ModalPretasksComponent implements OnInit { - - title: any; - faTrash=faTrash; - supertaskid: any; - pretasks: any; - prep: any; - - @ViewChild(DataTableDirective) - dtElement: DataTableDirective; - dtOptions: any = {}; - dtTrigger: Subject = new Subject(); - createForm: FormGroup; - +export class ModalPretasksComponent { constructor( - public modal: NgbActiveModal, - private gs: GlobalService, - private router: Router, - private fb: FormBuilder - ) { } - - ngOnInit(): void { - - this.pretasks = this.prep; - this.dtTrigger.next(null); - - this.dtOptions = { - dom: 'Bfrtip', - scrollX: true, - pageLength: 25, - lengthMenu: [ - [10, 25, 50, 100, 250, -1], - [10, 25, 50, 100, 250, 'All'] - ], - destroy: true, - buttons:[] - }; - - this.createForm = this.fb.group({ - KeysAndValues: this.fb.array([]), - }); - for (let i = 0; i < this.pretasks.length; i++) { - this.KeysAndValues.push( - this.fb.group({ - pretaskId: new FormControl(this.pretasks[i].pretaskId), - oldpriority: new FormControl(this.pretasks[i].priority), - priority: new FormControl('' || this.pretasks[i].priority), - delete: new FormControl('' || false), - }) - ); - } - - } + @Inject(MAT_DIALOG_DATA) + public data: { supertaskId: number; supertaskName: string } + ) {} - redirectPretask(id){ - this.router.navigate(['/tasks/preconfigured-tasks/', id,'edit']); - this.modal.close(); + get supertaskId(): number { + return this.data.supertaskId; } - ngAfterViewInit(){ - this.dtTrigger.next(null); + get supertaskName(): string { + return 'Subtasks of ' + this.data.supertaskName; } - - ngOnDestroy(): void { - this.dtTrigger.unsubscribe(); - } - - rerender(): void { - this.dtElement.dtInstance.then((dtInstance: DataTables.Api) => { - // Destroy the table first - dtInstance.destroy(); - // Call the dtTrigger to rerender again - setTimeout(() => { - this.dtTrigger['new'].next(); - }); - }); - } - - get KeysAndValues() { - return this.createForm.get('KeysAndValues') as FormArray; - } - - onSubmit(){ - let prepval = this.createForm.value; - for (let i = 0; i < prepval.KeysAndValues.length; i++) { - if(prepval.KeysAndValues[i].delete){ - const filter = this.pretasks.filter(u => u.pretaskId !== prepval.KeysAndValues[i].pretaskId); - const payload = []; - for(let i=0; i < filter.length; i++){ - payload.push(filter[i].pretaskId); - } - this.gs.update(SERV.SUPER_TASKS,this.supertaskid,{'pretasks': payload}).subscribe(); - } - if(prepval.KeysAndValues[i].oldpriority !== prepval.KeysAndValues[i].priority){ - console.log(prepval.KeysAndValues[i]) - this.gs.update(SERV.PRETASKS,prepval.KeysAndValues[i].pretaskId,{'priority': prepval.KeysAndValues[i].priority}).subscribe(); - } - this.modal.close() - setTimeout(() => { - window.location.reload(); - },500); - } - } - - } diff --git a/src/app/tasks/supertasks/supertasks.component.html b/src/app/tasks/supertasks/supertasks.component.html index 12329678..43d44e86 100644 --- a/src/app/tasks/supertasks/supertasks.component.html +++ b/src/app/tasks/supertasks/supertasks.component.html @@ -7,8 +7,3 @@ >
- - - diff --git a/src/app/tasks/supertasks/supertasks.component.ts b/src/app/tasks/supertasks/supertasks.component.ts index 7d2d8c50..17e822c3 100644 --- a/src/app/tasks/supertasks/supertasks.component.ts +++ b/src/app/tasks/supertasks/supertasks.component.ts @@ -11,13 +11,4 @@ export class SupertasksComponent { constructor(private titleService: AutoTitleService) { titleService.set(['Show Preconfigured Task']); } - - // getPretasks(id: number){ - // const ref = this.modalService.open(ModalPretasksComponent, { centered: true, size: 'xl' }); - // const _filter = this.allsupertasks.filter(u=> u.supertaskId == id); - // this.pretasks = _filter[0]['pretasks']; - // ref.componentInstance.prep = _filter[0]['pretasks']; - // ref.componentInstance.supertaskid = id; - // ref.componentInstance.title = 'Edit Pretaks' - // } } From 05e2796100d7623b61fdda20ebf55f200ed69c53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Wed, 10 Jan 2024 09:01:15 +0000 Subject: [PATCH 364/419] agent status --- .vscode/settings.json | 2 +- .../agent-status-modal.component.html | 10 + .../agent-status-modal.component.ts | 24 + .../agent-status/agent-status.component.html | 92 +-- .../agent-status/agent-status.component.ts | 289 +++++---- src/app/agents/agent.module.ts | 1 + .../agents-status-table.component.html | 18 + .../agents-status-table.component.ts | 562 ++++++++++++++++++ .../agents-status-table.constants.ts | 19 + .../_datasources/agents-status.datasource.ts | 76 +++ src/app/shared/components.module.ts | 3 + .../filter-textbox.component.html | 23 +- .../filter-textbox.component.ts | 32 +- .../filter-textbox/filter-textbox.module.ts | 6 +- 14 files changed, 948 insertions(+), 209 deletions(-) create mode 100644 src/app/agents/agent-status/agent-status-modal/agent-status-modal.component.html create mode 100644 src/app/agents/agent-status/agent-status-modal/agent-status-modal.component.ts create mode 100644 src/app/core/_components/tables/agents-status-table/agents-status-table.component.html create mode 100644 src/app/core/_components/tables/agents-status-table/agents-status-table.component.ts create mode 100644 src/app/core/_components/tables/agents-status-table/agents-status-table.constants.ts create mode 100644 src/app/core/_datasources/agents-status.datasource.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index a253d9c5..3a3071b2 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -16,6 +16,6 @@ }, "prettier.configPath": ".prettierrc.json", "editor.codeActionsOnSave": { - "source.fixAll.eslint": true + "source.fixAll.eslint": "explicit" } } \ No newline at end of file diff --git a/src/app/agents/agent-status/agent-status-modal/agent-status-modal.component.html b/src/app/agents/agent-status/agent-status-modal/agent-status-modal.component.html new file mode 100644 index 00000000..2b383d82 --- /dev/null +++ b/src/app/agents/agent-status/agent-status-modal/agent-status-modal.component.html @@ -0,0 +1,10 @@ +

+ {{ icon }} {{ title }} - + {{ content }} +

+ + + + + + diff --git a/src/app/agents/agent-status/agent-status-modal/agent-status-modal.component.ts b/src/app/agents/agent-status/agent-status-modal/agent-status-modal.component.ts new file mode 100644 index 00000000..93b37a94 --- /dev/null +++ b/src/app/agents/agent-status/agent-status-modal/agent-status-modal.component.ts @@ -0,0 +1,24 @@ +import { Component, Inject, Input, OnInit } from '@angular/core'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; + +@Component({ + selector: 'app-agent-status-modal', + templateUrl: './agent-status-modal.component.html' +}) +export class AgentStatusModalComponent { + @Input() title = ''; + @Input() icon = ''; + @Input() color = ''; + @Input() content = ''; + + constructor( + public activeModal: NgbActiveModal, + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: any + ) {} + + closeDialog(): void { + this.dialogRef.close(); + } +} diff --git a/src/app/agents/agent-status/agent-status.component.html b/src/app/agents/agent-status/agent-status.component.html index d26d8912..c9c42aad 100644 --- a/src/app/agents/agent-status/agent-status.component.html +++ b/src/app/agents/agent-status/agent-status.component.html @@ -1,60 +1,33 @@ -
-
-

- Agents Status - -

-
Location/Rack View
-
Agent View
-
-
-
- - + + +
+
+
+ + +
+
+
+
+ +
-
- -
-
- - -
-
+
-
- +
@@ -221,7 +194,7 @@
-
+
No Records Found @@ -232,10 +205,10 @@
@@ -459,12 +432,3 @@
diff --git a/src/app/tasks/edit-tasks/edit-tasks.component.ts b/src/app/tasks/edit-tasks/edit-tasks.component.ts index d0d3f95d..3d7c44cb 100644 --- a/src/app/tasks/edit-tasks/edit-tasks.component.ts +++ b/src/app/tasks/edit-tasks/edit-tasks.component.ts @@ -503,12 +503,13 @@ export class EditTasksComponent implements OnInit { }); }); } - + //To Delete onRefresh() { this.ngOnInit(); this.rerender(); // rerender datatables } + //To Delete rerender(): void { this.dtElement.dtInstance.then((dtInstance: DataTables.Api) => { // Destroy the table first @@ -562,6 +563,7 @@ export class EditTasksComponent implements OnInit { }); } + //To Delete onReset(id: number, state: number) { const path = state === 2 ? 'abortChunk' : 'resetChunk'; const title = state === 2 ? 'Chunk Abort!' : 'Chunk Reset!'; From eb7898dc11dc408ff5978c9e63161e4e3ade603a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Thu, 25 Jan 2024 14:08:21 +0000 Subject: [PATCH 391/419] Assign agents --- .../agents-table/agents-table.component.ts | 24 +- .../edit-tasks/edit-tasks.component.html | 75 +++--- .../tasks/edit-tasks/edit-tasks.component.ts | 241 ++++-------------- 3 files changed, 89 insertions(+), 251 deletions(-) diff --git a/src/app/core/_components/tables/agents-table/agents-table.component.ts b/src/app/core/_components/tables/agents-table/agents-table.component.ts index f7290123..9a18c265 100644 --- a/src/app/core/_components/tables/agents-table/agents-table.component.ts +++ b/src/app/core/_components/tables/agents-table/agents-table.component.ts @@ -61,7 +61,6 @@ export class AgentsTableComponent if (this.taskId) { this.dataSource.setTaskId(this.taskId); } - this.dataSource.reload(); } @@ -437,7 +436,6 @@ export class AgentsTableComponent case RowActionMenuAction.DEACTIVATE: this.bulkActionActivate([event.data], false); break; - case RowActionMenuAction.DELETE: this.openDialog({ rows: [event.data], @@ -543,13 +541,21 @@ export class AgentsTableComponent * @todo Implement error handling. */ private rowActionDelete(agent: Agent): void { - console.log(agent); - this.subscriptions.push( - this.gs.delete(SERV.AGENTS, agent[0]._id).subscribe(() => { - this.snackBar.open('Successfully deleted agent!', 'Close'); - this.dataSource.reload(); - }) - ); + if (this.taskId === 0) { + this.subscriptions.push( + this.gs.delete(SERV.AGENTS, agent[0]._id).subscribe(() => { + this.snackBar.open('Successfully deleted agent!', 'Close'); + this.dataSource.reload(); + }) + ); + } else { + this.subscriptions.push( + this.gs.delete(SERV.AGENT_ASSIGN, agent[0]._id).subscribe(() => { + this.snackBar.open('Successfully unassigned agent!', 'Close'); + this.dataSource.reload(); + }) + ); + } } private rowActionEdit(agent: Agent): void { diff --git a/src/app/tasks/edit-tasks/edit-tasks.component.html b/src/app/tasks/edit-tasks/edit-tasks.component.html index f5969082..caf3a020 100644 --- a/src/app/tasks/edit-tasks/edit-tasks.component.html +++ b/src/app/tasks/edit-tasks/edit-tasks.component.html @@ -110,13 +110,25 @@ >
- + - + +
+

+ + Assign Agents + + + {{ aval.agentName }} + + + No agents available + {{ availAgents.length }} agent(s) available + + +
- - - - - Assing - - -
- - - - -
-
-
-
-
- - -
-
+ + +
+
- - - - -
+ + + + Show All + +

+ +
+ diff --git a/src/app/tasks/edit-tasks/edit-tasks.component.ts b/src/app/tasks/edit-tasks/edit-tasks.component.ts index 3d7c44cb..e235b1a8 100644 --- a/src/app/tasks/edit-tasks/edit-tasks.component.ts +++ b/src/app/tasks/edit-tasks/edit-tasks.component.ts @@ -26,6 +26,7 @@ import Swal from 'sweetalert2/dist/sweetalert2.js'; import { Observable, Subject } from 'rxjs'; import * as echarts from 'echarts/core'; +import { AgentsTableComponent } from 'src/app/core/_components/tables/agents-table/agents-table.component'; import { PendingChangesGuard } from 'src/app/core/_guards/pendingchanges.guard'; import { UIConfigService } from 'src/app/core/_services/shared/storage.service'; import { AlertService } from 'src/app/core/_services/shared/alert.service'; @@ -33,13 +34,14 @@ import { GlobalService } from 'src/app/core/_services/main.service'; import { FileSizePipe } from 'src/app/core/_pipes/file-size.pipe'; import { PageTitle } from 'src/app/core/_decorators/autotitle'; import { SERV } from '../../core/_services/main.config'; +import { MatSnackBar } from '@angular/material/snack-bar'; +import { AutoTitleService } from 'src/app/core/_services/shared/autotitle.service'; @Component({ selector: 'app-edit-tasks', templateUrl: './edit-tasks.component.html', providers: [FileSizePipe] }) -@PageTitle(['Edit Task']) export class EditTasksComponent implements OnInit { editMode = false; editedTaskIndex: number; @@ -47,25 +49,21 @@ export class EditTasksComponent implements OnInit { editedTask: any; // Change to Model constructor( + private titleService: AutoTitleService, private uiService: UIConfigService, private route: ActivatedRoute, + private snackBar: MatSnackBar, private alert: AlertService, private gs: GlobalService, private fs: FileSizePipe, private router: Router - ) {} + ) { + this.titleService.set(['Edit Task']); + } updateForm: FormGroup; createForm: FormGroup; // Assign Agent color = ''; - - @ViewChild(DataTableDirective) - dtElement: DataTableDirective; - - dtTrigger: Subject = new Subject(); - dtTrigger1: Subject = new Subject(); - dtOptions: any = {}; - dtOptions1: any = {}; tusepreprocessor: any; hashlistDescrip: any; hashlistinform: any; @@ -76,6 +74,8 @@ export class EditTasksComponent implements OnInit { getchunks: any; getFiles: any; + @ViewChild('table') table: AgentsTableComponent; + ngOnInit() { this.onInitialize(); this.buildForm(); @@ -240,20 +240,8 @@ export class EditTasksComponent implements OnInit { ); return { ...mainObject, ...matchObject }; }); - this.dtTrigger1.next(void 0); }); }); - - this.dtOptions1 = { - dom: 'Bfrtip', - scrollY: '700px', - scrollCollapse: true, - paging: false, - destroy: true, - searching: false, - bInfo: false, - buttons: [] - }; } getAvalAgents(assing: any, agents: any) { @@ -262,62 +250,19 @@ export class EditTasksComponent implements OnInit { ); } - assignAgents() { + assignAgent() { if (this.createForm.valid) { const payload = { taskId: this.editedTaskIndex, agentId: this.createForm.value['agentId'] }; this.gs.create(SERV.AGENT_ASSIGN, payload).subscribe(() => { - this.alert.okAlert('Agent assigned!', ''); - this.rerender(); // rerender datatables - this.ngOnInit(); + this.snackBar.open('Agent assigned!', 'Close'); + this.table.reload(); }); } } - onDelete(id: number) { - this.gs.delete(SERV.AGENT_ASSIGN, id).subscribe(() => { - this.alert.okAlert('Deleted', ''); - this.rerender(); // rerender datatables - this.ngOnInit(); - }); - } - - onModalUpdate(title: string, id: number, cvalue: any, nameref: string) { - (async () => { - const { value: formValues } = await Swal.fire({ - title: title + ' - ' + nameref, - html: - '', - focusConfirm: false, - showCancelButton: true, - cancelButtonColor: this.alert.cancelButtonColor, - confirmButtonColor: this.alert.confirmButtonColor, - cancelButton: true, - preConfirm: () => { - return [ - (document.getElementById('project-input')).value - ]; - } - }); - - if (formValues) { - if (cvalue !== Number(formValues[0])) { - this.gs - .update(SERV.AGENT_ASSIGN, id, { benchmark: +formValues }) - .subscribe(() => { - this.alert.okAlert('Task saved!', ''); - this.ngOnInit(); - this.rerender(); // rerender datatables - }); - } - } - })(); - } - /** * This function calculates Keyspace searched, Time Spent and Estimated Time * @@ -373,91 +318,35 @@ export class EditTasksComponent implements OnInit { } }); - const self = this; - this.dtOptions = { - dom: 'Bfrtip', - scrollX: true, - pageLength: 25, - lengthMenu: [ - [10, 25, 50, 100, 250, -1], - [10, 25, 50, 100, 250, 'All'] - ], - scrollY: '700px', - scrollCollapse: true, - paging: false, - destroy: true, - buttons: { - dom: { - button: { - className: - 'dt-button buttons-collection btn btn-sm-dt btn-outline-gray-600-dt' - } - }, - buttons: [ - { - text: '↻', - autoClose: true, - action: function (e, dt, node, config) { - self.onRefresh(); - } - }, - { - text: self.chunkview === 0 ? 'Show Latest 100' : 'Show Live', - action: function () { - if (self.chunkview === 0) { - self.router.navigate([ - '/tasks/show-tasks', - id, - 'edit', - 'show-100-chunks' - ]); - } - if (self.chunkview === 1) { - self.router.navigate(['/tasks/show-tasks', id, 'edit']); - } - if (self.chunkview === 2) { - self.router.navigate(['/tasks/show-tasks', id, 'edit']); - } - } - }, - { - text: self.chunkview === 0 ? 'Show All' : 'Show Latest 100', - action: function () { - if (self.chunkview === 0) { - console.log(id); - self.router.navigate([ - '/tasks/show-tasks', - id, - 'edit', - 'show-all-chunks' - ]); - } - if (self.chunkview === 1) { - self.router.navigate([ - '/tasks/show-tasks', - id, - 'edit', - 'show-all-chunks' - ]); - } - if (self.chunkview === 2) { - self.router.navigate([ - '/tasks/show-tasks', - id, - 'edit', - 'show-100-chunks' - ]); - } - } - }, - { - extend: 'colvis', - text: 'Column View', - columns: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] - } - ] - } - }; + { + // text: self.chunkview === 0 ? 'Show All' : 'Show Latest 100', + // action: function () { + // if (self.chunkview === 0) { + // console.log(id); + // self.router.navigate([ + // '/tasks/show-tasks', + // id, + // 'edit', + // 'show-all-chunks' + // ]); + // } + // if (self.chunkview === 1) { + // self.router.navigate([ + // '/tasks/show-tasks', + // id, + // 'edit', + // 'show-all-chunks' + // ]); + // } + // if (self.chunkview === 2) { + // self.router.navigate([ + // '/tasks/show-tasks', + // id, + // 'edit', + // 'show-100-chunks' + // ]); + // } + } const params = { maxResults: this.chunkresults }; this.gs @@ -499,27 +388,11 @@ export class EditTasksComponent implements OnInit { } this.getchunks = resultArray; } - this.dtTrigger.next(void 0); }); }); } - //To Delete - onRefresh() { - this.ngOnInit(); - this.rerender(); // rerender datatables - } - //To Delete - rerender(): void { - this.dtElement.dtInstance.then((dtInstance: DataTables.Api) => { - // Destroy the table first - dtInstance.destroy(); - // Call the dtTrigger to rerender again - setTimeout(() => { - this.dtTrigger['new'].next(); - }); - }); - } + toggleIsAll(event) {} /** * Helper functions @@ -549,7 +422,6 @@ export class EditTasksComponent implements OnInit { this.gs.chelper(SERV.HELPER, 'purgeTask', payload).subscribe(() => { this.alert.okAlert('Deleted ' + name + '', ''); this.ngOnInit(); - this.rerender(); // rerender datatables }); } else { swalWithBootstrapButtons.fire({ @@ -563,18 +435,6 @@ export class EditTasksComponent implements OnInit { }); } - //To Delete - onReset(id: number, state: number) { - const path = state === 2 ? 'abortChunk' : 'resetChunk'; - const title = state === 2 ? 'Chunk Abort!' : 'Chunk Reset!'; - const payload = { chunkId: id }; - this.gs.chelper(SERV.HELPER, path, payload).subscribe(() => { - this.alert.okAlert('Resetted!', ''); - this.ngOnInit(); - this.rerender(); - }); - } - /** * Task Speed Grap * @@ -753,19 +613,4 @@ export class EditTasksComponent implements OnInit { return result; } - - // @HostListener allows us to also guard against browser refresh, close, etc. - @HostListener('window:beforeunload', ['$event']) - unloadNotification($event: any) { - if (!this.canDeactivate()) { - $event.returnValue = 'IE and Edge Message'; - } - } - - canDeactivate(): Observable | boolean { - if (this.updateForm.valid) { - return false; - } - return true; - } } From a95ff9e8046ff54b5f85dc23b3252b9614efaf76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Thu, 25 Jan 2024 15:38:06 +0000 Subject: [PATCH 392/419] toogle live or all chunks --- .../tasks-chunks-table.component.ts | 3 + .../_datasources/tasks-chunks.datasource.ts | 6 +- .../edit-tasks/edit-tasks.component.html | 6 +- .../tasks/edit-tasks/edit-tasks.component.ts | 125 +++++++----------- src/app/tasks/tasks-routing.module.ts | 10 -- 5 files changed, 57 insertions(+), 93 deletions(-) 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 6048ce87..9c331e0b 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 @@ -41,6 +41,8 @@ export class TasksChunksTableComponent { // Input property to specify an task ID for filtering chunks. @Input() taskId: number; + // Input property to specify to filter all chunks or only live. Live = 0, All = 1 + @Input() isChunksLive: number; tableColumns: HTTableColumn[] = []; dataSource: TasksChunksDataSource; @@ -56,6 +58,7 @@ export class TasksChunksTableComponent this.dataSource.setColumns(this.tableColumns); if (this.taskId) { this.dataSource.setTaskId(this.taskId); + this.dataSource.setIsChunksLive(this.isChunksLive); } this.dataSource.loadAll(); } diff --git a/src/app/core/_datasources/tasks-chunks.datasource.ts b/src/app/core/_datasources/tasks-chunks.datasource.ts index a88304e6..3c0abedb 100644 --- a/src/app/core/_datasources/tasks-chunks.datasource.ts +++ b/src/app/core/_datasources/tasks-chunks.datasource.ts @@ -8,14 +8,14 @@ import { SERV } from '../_services/main.config'; export class TasksChunksDataSource extends BaseDataSource { private _taskId = 0; - private _chunksLimit = 0; + private _isChunksLive = 0; setTaskId(taskId: number) { this._taskId = taskId; } - setChunksLimit(limit: number) { - this._chunksLimit = limit; + setIsChunksLive(number: number) { + this._isChunksLive = number; } loadAll(): void { diff --git a/src/app/tasks/edit-tasks/edit-tasks.component.html b/src/app/tasks/edit-tasks/edit-tasks.component.html index caf3a020..ef829a75 100644 --- a/src/app/tasks/edit-tasks/edit-tasks.component.html +++ b/src/app/tasks/edit-tasks/edit-tasks.component.html @@ -145,10 +145,10 @@ - - Show All + + {{ chunkview === 1 ? 'Live Chunks' : 'Show All' }}

- +
diff --git a/src/app/tasks/edit-tasks/edit-tasks.component.ts b/src/app/tasks/edit-tasks/edit-tasks.component.ts index e235b1a8..92571f0f 100644 --- a/src/app/tasks/edit-tasks/edit-tasks.component.ts +++ b/src/app/tasks/edit-tasks/edit-tasks.component.ts @@ -36,6 +36,7 @@ import { PageTitle } from 'src/app/core/_decorators/autotitle'; import { SERV } from '../../core/_services/main.config'; import { MatSnackBar } from '@angular/material/snack-bar'; import { AutoTitleService } from 'src/app/core/_services/shared/autotitle.service'; +import { MatSlideToggle } from '@angular/material/slide-toggle'; @Component({ selector: 'app-edit-tasks', @@ -48,19 +49,6 @@ export class EditTasksComponent implements OnInit { taskWrapperId: number; editedTask: any; // Change to Model - constructor( - private titleService: AutoTitleService, - private uiService: UIConfigService, - private route: ActivatedRoute, - private snackBar: MatSnackBar, - private alert: AlertService, - private gs: GlobalService, - private fs: FileSizePipe, - private router: Router - ) { - this.titleService.set(['Edit Task']); - } - updateForm: FormGroup; createForm: FormGroup; // Assign Agent color = ''; @@ -71,10 +59,34 @@ export class EditTasksComponent implements OnInit { availAgents: any; crackerinfo: any; tkeyspace: any; - getchunks: any; - getFiles: any; @ViewChild('table') table: AgentsTableComponent; + @ViewChild('slideToggle', { static: false }) slideToggle: MatSlideToggle; + + //Time calculation + cprogress: any; // Keyspace searched + ctimespent: any; // Time Spent + + // Chunk View + chunkview: number; + chunktitle: string; + isactive = 0; + currenspeed = 0; + chunkresults: Object; + activechunks: Object; + + constructor( + private titleService: AutoTitleService, + private uiService: UIConfigService, + private route: ActivatedRoute, + private snackBar: MatSnackBar, + private alert: AlertService, + private gs: GlobalService, + private fs: FileSizePipe, + private router: Router + ) { + this.titleService.set(['Edit Task']); + } ngOnInit() { this.onInitialize(); @@ -148,7 +160,6 @@ export class EditTasksComponent implements OnInit { .subscribe((result) => { console.log(result); this.color = result['color']; - this.getFiles = result.files; this.crackerinfo = result.crackerBinary; this.taskWrapperId - result.taskWrapperId; // Graph Speed @@ -267,10 +278,6 @@ export class EditTasksComponent implements OnInit { * This function calculates Keyspace searched, Time Spent and Estimated Time * **/ - // Keyspace searched - cprogress: any; - // Time Spent - ctimespent: any; timeCalc(chunks) { const cprogress = []; const timespent = []; @@ -287,14 +294,6 @@ export class EditTasksComponent implements OnInit { this.ctimespent = timespent.reduce((a, i) => a + i); } - // Chunk View - chunkview: number; - chunktitle: string; - isactive = 0; - currenspeed = 0; - chunkresults: Object; - activechunks: Object; - assignChunksInit(id: number) { this.route.data.subscribe((data) => { switch (data['kind']) { @@ -302,52 +301,17 @@ export class EditTasksComponent implements OnInit { this.chunkview = 0; this.chunktitle = 'Live Chunks'; this.chunkresults = 60000; + this.slideToggle.checked = false; break; - - case 'edit-task-c100': - this.chunkview = 1; - this.chunktitle = 'Latest 100 Chunks'; - this.chunkresults = 100; - break; - case 'edit-task-cAll': - this.chunkview = 2; + this.chunkview = 1; this.chunktitle = 'All Chunks'; this.chunkresults = 60000; + this.slideToggle.checked = true; break; } }); - { - // text: self.chunkview === 0 ? 'Show All' : 'Show Latest 100', - // action: function () { - // if (self.chunkview === 0) { - // console.log(id); - // self.router.navigate([ - // '/tasks/show-tasks', - // id, - // 'edit', - // 'show-all-chunks' - // ]); - // } - // if (self.chunkview === 1) { - // self.router.navigate([ - // '/tasks/show-tasks', - // id, - // 'edit', - // 'show-all-chunks' - // ]); - // } - // if (self.chunkview === 2) { - // self.router.navigate([ - // '/tasks/show-tasks', - // id, - // 'edit', - // 'show-100-chunks' - // ]); - // } - } - const params = { maxResults: this.chunkresults }; this.gs .getAll(SERV.CHUNKS, { @@ -358,7 +322,7 @@ export class EditTasksComponent implements OnInit { this.timeCalc(result.values); // this.initVisualGraph(result.values, 150, 150); // Get data for visual graph this.gs.getAll(SERV.AGENTS, params).subscribe((agents: any) => { - this.getchunks = result.values.map((mainObject) => { + const getchunks = result.values.map((mainObject) => { const matchObject = agents.values.find( (element) => element.agentId === mainObject.agentId ); @@ -368,31 +332,38 @@ export class EditTasksComponent implements OnInit { const chunktime = this.uiService.getUIsettings('chunktime').value; const resultArray = []; const cspeed = []; - for (let i = 0; i < this.getchunks.length; i++) { + for (let i = 0; i < getchunks.length; i++) { if ( Date.now() / 1000 - - Math.max( - this.getchunks[i].solveTime, - this.getchunks[i].dispatchTime - ) < + Math.max(getchunks[i].solveTime, getchunks[i].dispatchTime) < chunktime && - this.getchunks[i].progress < 10000 + getchunks[i].progress < 10000 ) { this.isactive = 1; - cspeed.push(this.getchunks[i].speed); - resultArray.push(this.getchunks[i]); + cspeed.push(getchunks[i].speed); + resultArray.push(getchunks[i]); } } if (cspeed.length > 0) { this.currenspeed = cspeed.reduce((a, i) => a + i); } - this.getchunks = resultArray; } }); }); } - toggleIsAll(event) {} + toggleIsAll(event) { + if (this.chunkview === 0) { + this.router.navigate([ + '/tasks/show-tasks', + this.editedTaskIndex, + 'edit', + 'show-all-chunks' + ]); + } else { + this.router.navigate(['/tasks/show-tasks', this.editedTaskIndex, 'edit']); + } + } /** * Helper functions diff --git a/src/app/tasks/tasks-routing.module.ts b/src/app/tasks/tasks-routing.module.ts index cfa2a982..de1a54ed 100644 --- a/src/app/tasks/tasks-routing.module.ts +++ b/src/app/tasks/tasks-routing.module.ts @@ -54,16 +54,6 @@ const routes: MyRoute[] = [ }, canActivate: [CheckPerm] }, - { - path: 'show-tasks/:id/edit/show-100-chunks', - component: EditTasksComponent, - data: { - kind: 'edit-task-c100', - breadcrumb: 'Edit Task > Show latest 100 chunks', - permission: 'Task' - }, - canActivate: [CheckPerm] - }, { path: 'show-tasks/:id/edit/show-all-chunks', component: EditTasksComponent, From d5094e8d69fdb51b83024dc7ebbf69581b9d0fc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Thu, 25 Jan 2024 16:09:04 +0000 Subject: [PATCH 393/419] Add live chunks in tasks --- .../_datasources/tasks-chunks.datasource.ts | 46 ++++++++++++++----- 1 file changed, 34 insertions(+), 12 deletions(-) diff --git a/src/app/core/_datasources/tasks-chunks.datasource.ts b/src/app/core/_datasources/tasks-chunks.datasource.ts index 3c0abedb..f347781f 100644 --- a/src/app/core/_datasources/tasks-chunks.datasource.ts +++ b/src/app/core/_datasources/tasks-chunks.datasource.ts @@ -39,22 +39,44 @@ export class TasksChunksDataSource extends BaseDataSource { ) .subscribe( ([c, a]: [ListResponseWrapper, ListResponseWrapper]) => { - const assignedChunks: Chunk[] = c.values; + const getchunks: Chunk[] = c.values; - assignedChunks.map((chunk: Chunk) => { - chunk.agent = a.values.find((e: Agent) => e._id === chunk.agentId); - // Flatten row so that we can access agent name and task name by key when rendering the table. - if (chunk.agent) { - chunk.agentName = chunk.agent.agentName; - } - if (chunk.task) { - chunk.taskName = chunk.task.taskName; + if (this._isChunksLive === 0) { + const chunktime = this.uiService.getUIsettings('chunktime').value; + const resultArray = []; + const cspeed = []; + + for (let i = 0; i < getchunks.length; i++) { + if ( + Date.now() / 1000 - + Math.max(getchunks[i].solveTime, getchunks[i].dispatchTime) < + chunktime && + getchunks[i].progress < 10000 + ) { + cspeed.push(getchunks[i].speed); + resultArray.push(getchunks[i]); + } } - return chunk; - }); + this.setData(resultArray); + } else { + const assignedChunks: Chunk[] = getchunks.map((chunk: Chunk) => { + chunk.agent = a.values.find( + (e: Agent) => e._id === chunk.agentId + ); + // Flatten row so that we can access agent name and task name by key when rendering the table. + if (chunk.agent) { + chunk.agentName = chunk.agent.agentName; + } + if (chunk.task) { + chunk.taskName = chunk.task.taskName; + } + + return chunk; + }); - this.setData(assignedChunks); + this.setData(assignedChunks); + } } ); } From 6c488226bbd524b97c27d27f4c4fb045d2f291fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Thu, 25 Jan 2024 16:52:55 +0000 Subject: [PATCH 394/419] Alert snackbar and hyperlink broken --- .../ui-settings/ui-settings.component.ts | 52 +++--- .../engine/crackers/crackers.component.ts | 154 +----------------- .../tables/base-table/base-table.component.ts | 4 +- .../core/_services/shared/alert.service.ts | 111 ++++++------- .../tasks/edit-tasks/edit-tasks.component.ts | 1 + 5 files changed, 88 insertions(+), 234 deletions(-) diff --git a/src/app/account/settings/ui-settings/ui-settings.component.ts b/src/app/account/settings/ui-settings/ui-settings.component.ts index 63070d5f..a7fef9ca 100644 --- a/src/app/account/settings/ui-settings/ui-settings.component.ts +++ b/src/app/account/settings/ui-settings/ui-settings.component.ts @@ -2,7 +2,12 @@ import { FormControl, FormGroup } from '@angular/forms'; import { Component, OnInit } from '@angular/core'; import { LocalStorageService } from 'src/app/core/_services/storage/local-storage.service'; import { UIConfig } from 'src/app/core/_models/config-ui.model'; -import { Setting, dateFormats, layouts, themes } from 'src/app/core/_constants/settings.config'; +import { + Setting, + dateFormats, + layouts, + themes +} from 'src/app/core/_constants/settings.config'; import { UISettingsUtilityClass } from 'src/app/shared/utils/config'; import { MatSnackBar } from '@angular/material/snack-bar'; @@ -11,53 +16,50 @@ import { MatSnackBar } from '@angular/material/snack-bar'; templateUrl: './ui-settings.component.html' }) export class UiSettingsComponent implements OnInit { + form!: FormGroup; + util: UISettingsUtilityClass; - form!: FormGroup - util: UISettingsUtilityClass - - formats: Setting[] = dateFormats - layouts: Setting[] = layouts - themes: Setting[] = themes + formats: Setting[] = dateFormats; + layouts: Setting[] = layouts; + themes: Setting[] = themes; constructor( private service: LocalStorageService, - private snackBar: MatSnackBar, + private snackBar: MatSnackBar ) { this.initForm(); } ngOnInit(): void { - this.util = new UISettingsUtilityClass(this.service) + this.util = new UISettingsUtilityClass(this.service); this.updateForm(); } private initForm(): void { this.form = new FormGroup({ - 'timefmt': new FormControl(''), - 'layout': new FormControl(''), - 'theme': new FormControl('') + timefmt: new FormControl(''), + layout: new FormControl(''), + theme: new FormControl('') }); } private updateForm(): void { this.form.patchValue({ - 'timefmt': this.util.uiConfig.timefmt, - 'layout': this.util.uiConfig.layout, - 'theme': this.util.uiConfig.theme - }) + timefmt: this.util.uiConfig.timefmt, + layout: this.util.uiConfig.layout, + theme: this.util.uiConfig.theme + }); } onSubmit(): void { setTimeout(() => { - window.location.reload() - }, 800) - - const changedValues = this.util.updateSettings(this.form.value) - const message = changedValues > 0 - ? 'Reloading settings ...' - : 'No changes were saved' + window.location.reload(); + }, 800); + const changedValues = this.util.updateSettings(this.form.value); + const message = + changedValues > 0 ? 'Reloading settings ...' : 'No changes were saved'; - this.snackBar.open(message, 'Close') + this.snackBar.open(message, 'Close'); } -} \ No newline at end of file +} diff --git a/src/app/config/engine/crackers/crackers.component.ts b/src/app/config/engine/crackers/crackers.component.ts index eddea297..901bcbc6 100644 --- a/src/app/config/engine/crackers/crackers.component.ts +++ b/src/app/config/engine/crackers/crackers.component.ts @@ -1,157 +1,13 @@ -import { faEdit, faTrash, faHomeAlt, faPlus, faEye } from '@fortawesome/free-solid-svg-icons'; -import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core'; -import { environment } from './../../../../environments/environment'; -import Swal from 'sweetalert2/dist/sweetalert2.js'; -import { Subject } from 'rxjs'; - -import { AlertService } from 'src/app/core/_services/shared/alert.service'; -import { GlobalService } from 'src/app/core/_services/main.service'; +import { AutoTitleService } from 'src/app/core/_services/shared/autotitle.service'; +import { Component } from '@angular/core'; import { PageTitle } from 'src/app/core/_decorators/autotitle'; -import { SERV } from '../../../core/_services/main.config'; -import { DataTableDirective } from 'angular-datatables'; @Component({ selector: 'app-crackers', templateUrl: './crackers.component.html' }) -@PageTitle(['Show Crackers']) -export class CrackersComponent implements OnInit, OnDestroy { - - faEdit=faEdit; - faTrash=faTrash; - faHome=faHomeAlt; - faPlus=faPlus; - faEye=faEye; - - @ViewChild(DataTableDirective, {static: false}) - dtElement: DataTableDirective; - - dtTrigger: Subject = new Subject(); - dtOptions: any = {}; - - ngOnDestroy(): void { - this.dtTrigger.unsubscribe(); - } - - crackerType: any = []; - - constructor( - private alert: AlertService, - private gs: GlobalService, - ) { } - - private maxResults = environment.config.prodApiMaxResults - - ngOnInit(): void { - const params = {'maxResults': this.maxResults, 'expand': 'crackerVersions'} - - this.gs.getAll(SERV.CRACKERS_TYPES,params).subscribe((type: any) => { - this.crackerType = type.values; - this.dtTrigger.next(void 0); - }); - const self = this; - this.dtOptions = { - dom: 'Bfrtip', - scrollX: true, - pageLength: 25, - lengthMenu: [ - [10, 25, 50, 100, 250, -1], - [10, 25, 50, 100, 250, 'All'] - ], - stateSave: true, - select: true, - buttons: { - dom: { - button: { - className: 'dt-button buttons-collection btn btn-sm-dt btn-outline-gray-600-dt', - } - }, - buttons: [ - { - text: '↻', - autoClose: true, - action: function (e, dt, node, config) { - self.onRefresh(); - } - }, - { - extend: 'collection', - text: 'Export', - buttons: [ - { - extend: 'excelHtml5', - exportOptions: { - columns: [0, 1, 2] - }, - }, - { - extend: 'print', - exportOptions: { - columns: [0, 1, 2] - }, - customize: function ( win ) { - $(win.document.body) - .css( 'font-size', '10pt' ) - $(win.document.body).find( 'table' ) - .addClass( 'compact' ) - .css( 'font-size', 'inherit' ); - } - }, - { - extend: 'csvHtml5', - exportOptions: {modifier: {selected: true}}, - select: true, - customize: function (dt, csv) { - let data = ""; - for (let i = 0; i < dt.length; i++) { - data = "Crackers\n\n"+ dt; - } - return data; - } - }, - 'copy' - ] - } - ], - } - }; - } - - onRefresh(){ - this.rerender(); - this.ngOnInit(); - } - - rerender(): void { - this.dtElement.dtInstance.then((dtInstance: DataTables.Api) => { - dtInstance.destroy(); - setTimeout(() => { - this.dtTrigger['new'].next(); - }); - }); +export class CrackersComponent { + constructor(private titleService: AutoTitleService) { + this.titleService.set(['Show Crackers']); } - - onDelete(id: number, name: string){ - this.alert.deleteConfirmation(name,'Crackers').then((confirmed) => { - if (confirmed) { - // Deletion - this.gs.delete(SERV.CRACKERS_TYPES, id).subscribe(() => { - // Successful deletion - this.alert.okAlert(`Deleted Cracker ${name}`, ''); - this.onRefreshTable(); // Refresh the table - }); - } else { - // Handle cancellation - this.alert.okAlert(`Cracker ${name} is safe!`,''); - } - }); - } - - onRefreshTable(){ - setTimeout(() => { - this.ngOnInit(); - this.rerender(); // rerender datatables - },2000); - } - } 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 11bd0ad3..1504d7ae 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 @@ -147,10 +147,10 @@ export class BaseTableComponent { @Cacheable(['userId']) async renderUserLink(obj: unknown): Promise { + console.log(obj); return [ { - routerLink: - obj && obj['userId'] ? ['/users', obj['userId'], 'edit'] : [] + routerLink: obj && obj['_id'] ? ['/users', obj['_id'], 'edit'] : [] } ]; } diff --git a/src/app/core/_services/shared/alert.service.ts b/src/app/core/_services/shared/alert.service.ts index a11f9ba8..df90373a 100644 --- a/src/app/core/_services/shared/alert.service.ts +++ b/src/app/core/_services/shared/alert.service.ts @@ -1,6 +1,6 @@ import Swal from 'sweetalert2/dist/sweetalert2.js'; import { Injectable } from '@angular/core'; - +import { MatSnackBar } from '@angular/material/snack-bar'; import { BulkService } from './bulk.service'; @Injectable({ @@ -9,6 +9,7 @@ import { BulkService } from './bulk.service'; export class AlertService { constructor( private bulk: BulkService, + private snackBar: MatSnackBar ) {} cancelButtonColor = '#8A8584'; @@ -24,18 +25,12 @@ export class AlertService { * @param {string} type - Type of warning, default success */ - okAlert(title: string, text: string, type: 'success' | 'error' | 'warning' = 'success') { - Swal.fire({ - title, - text, - icon: type, - position: 'top-end', - backdrop: false, - toast: true, - showConfirmButton: false, - timer: 2000, - timerProgressBar: true, - }); + okAlert( + title: string, + text: string, + type: 'success' | 'error' | 'warning' = 'success' + ) { + this.snackBar.open(title, 'Close'); } /** @@ -44,7 +39,7 @@ export class AlertService { * * @param {string} name - Item name * @param {string} title - Additional text - */ + */ deleteConfirmation(name: string, title: string): Promise { return Swal.fire({ @@ -68,7 +63,7 @@ export class AlertService { * @param {string} path - API path call to delete the items on the array */ - bulkDeleteAlert(items: any[], text: string, path: string ) { + bulkDeleteAlert(items: any[], text: string, path: string) { this.bulk.setItems(items); // Items to be deleted this.bulk.setPath(path); //Path route Swal.fire({ @@ -77,9 +72,10 @@ export class AlertService { showCancelButton: false, showConfirmButton: false, allowEscapeKey: false, //Dont let user escape modal until its finish - allowOutsideClick: false,//Dont let user close modal until its finish + allowOutsideClick: false, //Dont let user close modal until its finish didOpen: () => { - const progressBar = Swal.getHtmlContainer().querySelector('.progress-bar'); + const progressBar = + Swal.getHtmlContainer().querySelector('.progress-bar'); progressBar.style.width = '0%'; this.bulk @@ -88,12 +84,12 @@ export class AlertService { }) .then((success) => { if (success) { - this.okAlert(`${items.length} ${text} deleted`,'') + this.okAlert(`${items.length} ${text} deleted`, ''); } else { Swal.update({ icon: 'error', title: 'Error Deleting Items', - showConfirmButton: true, + showConfirmButton: true }); } }) @@ -102,10 +98,10 @@ export class AlertService { icon: 'error', title: 'Error Deleting Items', text: error.message, - showConfirmButton: true, + showConfirmButton: true }); }); - }, + } }); } @@ -119,47 +115,46 @@ export class AlertService { * @param {string} path - API path call to delete the items on the array */ - bulkUpdateAlert(items: any[], value: any, text: string, path: string ) { - this.bulk.setItems(items); // Items to be deleted - this.bulk.setValue(value); - this.bulk.setPath(path); //Path route - Swal.fire({ - title: `Updating ${items.length} ${text}`, - html: '
', - showCancelButton: false, - showConfirmButton: false, - allowEscapeKey: false, //Dont let user escape modal until its finish - allowOutsideClick: false,//Dont let user close modal until its finish - didOpen: () => { - const progressBar = Swal.getHtmlContainer().querySelector('.progress-bar'); - progressBar.style.width = '0%'; + bulkUpdateAlert(items: any[], value: any, text: string, path: string) { + this.bulk.setItems(items); // Items to be deleted + this.bulk.setValue(value); + this.bulk.setPath(path); //Path route + Swal.fire({ + title: `Updating ${items.length} ${text}`, + html: '
', + showCancelButton: false, + showConfirmButton: false, + allowEscapeKey: false, //Dont let user escape modal until its finish + allowOutsideClick: false, //Dont let user close modal until its finish + didOpen: () => { + const progressBar = + Swal.getHtmlContainer().querySelector('.progress-bar'); + progressBar.style.width = '0%'; - this.bulk - .performBulkUpdate((percentage) => { - progressBar.style.width = percentage + '%'; - }) - .then((success) => { - if (success) { - this.okAlert(`${items.length} ${text} updated`,'') - } else { - Swal.update({ - icon: 'error', - title: 'Error Updating Items', - showConfirmButton: true, - }); - } - }) - .catch((error) => { + this.bulk + .performBulkUpdate((percentage) => { + progressBar.style.width = percentage + '%'; + }) + .then((success) => { + if (success) { + this.okAlert(`${items.length} ${text} updated`, ''); + } else { Swal.update({ icon: 'error', title: 'Error Updating Items', - text: error.message, - showConfirmButton: true, + showConfirmButton: true }); + } + }) + .catch((error) => { + Swal.update({ + icon: 'error', + title: 'Error Updating Items', + text: error.message, + showConfirmButton: true }); - }, - }); - } - + }); + } + }); + } } - diff --git a/src/app/tasks/edit-tasks/edit-tasks.component.ts b/src/app/tasks/edit-tasks/edit-tasks.component.ts index 92571f0f..4f87bb98 100644 --- a/src/app/tasks/edit-tasks/edit-tasks.component.ts +++ b/src/app/tasks/edit-tasks/edit-tasks.component.ts @@ -312,6 +312,7 @@ export class EditTasksComponent implements OnInit { } }); + //TODO. It is repeting code to get the speed const params = { maxResults: this.chunkresults }; this.gs .getAll(SERV.CHUNKS, { From 5f064c8f9814eadc9bca5f1a1607d3bc0a99ad0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Fri, 26 Jan 2024 09:13:19 +0000 Subject: [PATCH 395/419] Small fixes and lint --- .../pretasks-table.component.html | 2 +- .../pretasks-table.component.ts | 152 +++++++++--- .../pretasks-table.constants.ts | 8 +- .../preconfigured-tasks.datasource.ts | 49 ++-- src/app/core/_models/config-ui.model.ts | 11 + src/app/core/_models/response.model.ts | 15 +- src/app/core/_pipes/keyspace-calc.pipe.ts | 233 ++++++++++-------- src/app/core/_pipes/task-dispatched.pipe.ts | 36 +-- src/app/core/_pipes/task-searched.pipe.ts | 37 +-- .../edit-supertasks.component.html | 7 +- .../edit-supertasks.component.ts | 36 +-- .../preconfigured-tasks.component.html | 2 +- src/config/default/app/tooltip.ts | 77 +++--- 13 files changed, 388 insertions(+), 277 deletions(-) diff --git a/src/app/core/_components/tables/pretasks-table/pretasks-table.component.html b/src/app/core/_components/tables/pretasks-table/pretasks-table.component.html index 795b3418..ce899d6f 100644 --- a/src/app/core/_components/tables/pretasks-table/pretasks-table.component.html +++ b/src/app/core/_components/tables/pretasks-table/pretasks-table.component.html @@ -1,6 +1,6 @@ this.renderEstimatedKeyspace(pretask), + icons: undefined, + isSortable: true, + export: async (pretask: Pretask) => (await pretask) + '' + }); + tableColumns.push({ + id: PretasksTableCol.ATTACK_RUNTIME, + dataKey: 'runtime', + icons: undefined, + isSortable: true, + export: async (pretask: Pretask) => pretask.maxAgents.toString() + }); + } + return tableColumns; } @@ -223,26 +248,57 @@ export class PretasksTableComponent * @todo Implement error handling. */ private bulkActionDelete(pretasks: Pretask[]): void { - const requests = pretasks.map((pretask: Pretask) => { - return this.gs.delete(SERV.PRETASKS, pretask._id); - }); + if (this.supertTaskId === 0) { + const requests = pretasks.map((pretask: Pretask) => { + return this.gs.delete(SERV.PRETASKS, pretask._id); + }); - this.subscriptions.push( - forkJoin(requests) - .pipe( - catchError((error) => { - console.error('Error during deletion:', error); - return []; + this.subscriptions.push( + forkJoin(requests) + .pipe( + catchError((error) => { + console.error('Error during deletion:', error); + return []; + }) + ) + .subscribe((results) => { + this.snackBar.open( + `Successfully deleted ${results.length} pretasks!`, + 'Close' + ); + this.reload(); }) - ) - .subscribe((results) => { - this.snackBar.open( - `Successfully deleted ${results.length} pretasks!`, - 'Close' - ); - this.reload(); - }) - ); + ); + } else { + const filter = this.dataSource['originalData'].filter( + (u) => u.pretaskId !== pretasks[0]._id + ); + const payload = []; + for (let i = 0; i < filter.length; i++) { + payload.push(filter[i].pretaskId); + } + + const requests = pretasks.map((pretask: Pretask) => { + return this.gs.delete(SERV.PRETASKS, pretask._id); + }); + + this.subscriptions.push( + forkJoin(requests) + .pipe( + catchError((error) => { + console.error('Error during deletion:', error); + return []; + }) + ) + .subscribe((results) => { + this.snackBar.open( + `Successfully deleted ${results.length} pretasks!`, + 'Close' + ); + this.reload(); + }) + ); + } } @Cacheable(['_id', 'isSecret']) @@ -274,24 +330,54 @@ export class PretasksTableComponent ]; } + @Cacheable(['_id']) + async renderEstimatedKeyspace(pretask: Pretask): Promise { + const html = '-'; + return this.sanitize(html); + } + /** * @todo Implement error handling. */ private rowActionDelete(pretasks: Pretask[]): void { - this.subscriptions.push( - this.gs - .delete(SERV.PRETASKS, pretasks[0]._id) - .pipe( - catchError((error) => { - console.error('Error during deletion:', error); - return []; + if (this.supertTaskId === 0) { + this.subscriptions.push( + this.gs + .delete(SERV.PRETASKS, pretasks[0]._id) + .pipe( + catchError((error) => { + console.error('Error during deletion:', error); + return []; + }) + ) + .subscribe(() => { + this.snackBar.open('Successfully deleted pretask!', 'Close'); + this.reload(); }) - ) - .subscribe(() => { - this.snackBar.open('Successfully deleted pretask!', 'Close'); - this.reload(); - }) - ); + ); + } else { + const filter = this.dataSource['originalData'].filter( + (u) => u.pretaskId !== pretasks[0]._id + ); + const payload = []; + for (let i = 0; i < filter.length; i++) { + payload.push(filter[i].pretaskId); + } + this.subscriptions.push( + this.gs + .update(SERV.SUPER_TASKS, this.supertTaskId, { pretasks: payload }) + .pipe( + catchError((error) => { + console.error('Error during deletion:', error); + return []; + }) + ) + .subscribe(() => { + this.snackBar.open('Successfully deleted pretask!', 'Close'); + this.reload(); + }) + ); + } } private rowActionCopyToTask(pretask: Pretask): void { diff --git a/src/app/core/_components/tables/pretasks-table/pretasks-table.constants.ts b/src/app/core/_components/tables/pretasks-table/pretasks-table.constants.ts index 788eb630..a9177183 100644 --- a/src/app/core/_components/tables/pretasks-table/pretasks-table.constants.ts +++ b/src/app/core/_components/tables/pretasks-table/pretasks-table.constants.ts @@ -5,7 +5,9 @@ export enum PretasksTableCol { FILES_TOTAL, FILES_SIZE, PRIORITY, - MAX_AGENTS + MAX_AGENTS, + ESTIMATED_KEYSPACE, + ATTACK_RUNTIME } export const PretasksTableColumnLabel = { @@ -15,5 +17,7 @@ export const PretasksTableColumnLabel = { [PretasksTableCol.FILES_TOTAL]: 'Overall count', [PretasksTableCol.FILES_SIZE]: 'Total Size', [PretasksTableCol.PRIORITY]: 'Priority', - [PretasksTableCol.MAX_AGENTS]: 'Max Agents' + [PretasksTableCol.MAX_AGENTS]: 'Max Agents', + [PretasksTableCol.ESTIMATED_KEYSPACE]: 'Estimated Keyspace', + [PretasksTableCol.ATTACK_RUNTIME]: 'Attack Runtime' }; diff --git a/src/app/core/_datasources/preconfigured-tasks.datasource.ts b/src/app/core/_datasources/preconfigured-tasks.datasource.ts index 04626855..f474fef8 100644 --- a/src/app/core/_datasources/preconfigured-tasks.datasource.ts +++ b/src/app/core/_datasources/preconfigured-tasks.datasource.ts @@ -1,4 +1,5 @@ -import { catchError, finalize, of } from 'rxjs'; +import { catchError, finalize } from 'rxjs/operators'; +import { of } from 'rxjs'; import { BaseDataSource } from './base.datasource'; import { ListResponseWrapper } from '../_models/response.model'; @@ -10,17 +11,31 @@ export class PreTasksDataSource extends BaseDataSource< Pretask, MatTableDataSourcePaginator > { + private _superTaskId = 0; + + setSuperTaskId(superTaskId: number): void { + this._superTaskId = superTaskId; + } + loadAll(): void { this.loading = true; - const startAt = this.currentPage * this.pageSize; - const params = { - maxResults: this.pageSize, - startAt: startAt, - expand: 'pretaskFiles' - }; + let pretasks$; - const pretasks$ = this.service.getAll(SERV.PRETASKS, params); + if (this._superTaskId === 0) { + const startAt = this.currentPage * this.pageSize; + const params = { + maxResults: this.pageSize, + startAt: startAt, + expand: 'pretaskFiles' + }; + + pretasks$ = this.service.getAll(SERV.PRETASKS, params); + } else { + pretasks$ = this.service.get(SERV.SUPER_TASKS, this._superTaskId, { + expand: 'pretasks' + }); + } this.subscriptions.push( pretasks$ @@ -29,13 +44,19 @@ export class PreTasksDataSource extends BaseDataSource< finalize(() => (this.loading = false)) ) .subscribe((response: ListResponseWrapper) => { - const pretasks: Pretask[] = response.values; + let pretasks: Pretask[]; + if (this._superTaskId === 0) { + pretasks = response.values; + + this.setPaginationConfig( + this.pageSize, + this.currentPage, + response.total + ); + } else { + pretasks = response.pretasks || []; + } - this.setPaginationConfig( - this.pageSize, - this.currentPage, - response.total - ); this.setData(pretasks); }) ); diff --git a/src/app/core/_models/config-ui.model.ts b/src/app/core/_models/config-ui.model.ts index 5c3942c9..96f36697 100644 --- a/src/app/core/_models/config-ui.model.ts +++ b/src/app/core/_models/config-ui.model.ts @@ -228,6 +228,17 @@ export const uiConfigDefault: UIConfig = { SupertasksPretasksTableCol.PRIORITY, SupertasksPretasksTableCol.MAX_AGENTS ], + superTasksPretasksEditTable: [ + PretasksTableCol.ID, + PretasksTableCol.NAME, + PretasksTableCol.ATTACK_COMMAND, + PretasksTableCol.ESTIMATED_KEYSPACE, + PretasksTableCol.ATTACK_RUNTIME, + PretasksTableCol.FILES_TOTAL, + PretasksTableCol.FILES_SIZE, + PretasksTableCol.PRIORITY, + PretasksTableCol.MAX_AGENTS + ], hashlistTasksTable: [ TaskTableCol.ID, TaskTableCol.NAME, diff --git a/src/app/core/_models/response.model.ts b/src/app/core/_models/response.model.ts index 57039c7a..1cf85baf 100644 --- a/src/app/core/_models/response.model.ts +++ b/src/app/core/_models/response.model.ts @@ -1,8 +1,9 @@ export interface ListResponseWrapper { - _expandable: string - startAt: number - maxResults: number - total: number - isLast: number - values: T[] -} \ No newline at end of file + _expandable: string; + startAt: number; + maxResults: number; + total: number; + isLast: number; + values: T[]; + pretasks?: T[]; +} diff --git a/src/app/core/_pipes/keyspace-calc.pipe.ts b/src/app/core/_pipes/keyspace-calc.pipe.ts index 1f62dea0..7a387c6e 100644 --- a/src/app/core/_pipes/keyspace-calc.pipe.ts +++ b/src/app/core/_pipes/keyspace-calc.pipe.ts @@ -1,7 +1,4 @@ -import { - PipeTransform, - Pipe -} from '@angular/core'; +import { Pipe, PipeTransform } from '@angular/core'; /** * This function calculates the keyspace using the asset files optparse located in assets folder @@ -14,7 +11,7 @@ import { * Example: * {{ object | keyspace:'lineCount':'cmd' }} * @returns number -**/ + **/ declare let options: any; declare let defaultOptions: any; declare let parser: any; @@ -22,113 +19,147 @@ declare let parser: any; @Pipe({ name: 'keyspace' }) - export class KeyspaceCalcPipe implements PipeTransform { + transform(value: any[], name: string, cmd: any, attcktype?: boolean) { + if (!cmd || !name) { + return 'Wrong Command'; + } + // Iterate over files and get file count + const arr = []; + let mpow = 0; + if (value.length !== 0) { + for (let i = 0; i < value.length; i++) { + arr.push(Number(value[i][name])); + } + mpow = arr.reduce((a, i) => a * i); + } + // resetting the options + options = defaultOptions; + options.ruleFiles = []; + options.posArgs = []; + options.unrecognizedFlag = []; + // example cmd = "hashcat #HL# -a3 ?d?d?d?d" + // Ideally the opt-parser itself works for '-a3' instead of requiring a space as in '-a 3' to parse attackType + let args: any = cmd.replace('hashcat', ''); + args = args.replace(/(-a)(\d)(\s)/, '-a $2 '); // ensures that "-a3" becomes a valid attack mode: the opt-parser does not approve it (yet) + args = args.replace(/(-\d)(\S+)(\s)/, '$1 $2 '); // ensures that "-1?l?d" becomes a valid customCharset: the opt-parser does not approve it (yet) + args = args.replace(/\s+/g, ' '); // ensures that multiple consecutive spaces are reduced to a single space + args = args.trim(); + args = args.split(/ |=/g); + parser.parse(args); + function customCharsetToOptions(mask: string) { + const numA = (mask.match(/\?a/g) || []).length; + const numD = (mask.match(/\?d/g) || []).length; + const numL = (mask.match(/\?l/g) || []).length; + const numU = (mask.match(/\?u/g) || []).length; + const numS = (mask.match(/\?s/g) || []).length; + const numLH = (mask.match(/\?h/g) || []).length; + const numUH = (mask.match(/\?H/g) || []).length; + const numB = (mask.match(/\?b/g) || []).length; + let charsetOptions = 95 * Math.min(1, numA); + charsetOptions = charsetOptions + 10 * Math.min(1, numD); + charsetOptions = charsetOptions + 26 * Math.min(1, numL); + charsetOptions = charsetOptions + 26 * Math.min(1, numU); + charsetOptions = charsetOptions + 33 * Math.min(1, numS); + charsetOptions = charsetOptions + 16 * Math.min(1, numLH); + charsetOptions = charsetOptions + 16 * Math.min(1, numUH); + charsetOptions = charsetOptions + 256 * Math.min(1, numB); + // Add single characters that are part of the custom charset + // we assume no duplicate single characters are present in the custom charset! + // i.e. -1 abbc is considered to be a charset of 4 different characters in the calculation + charsetOptions = + charsetOptions + + mask.length - + 2 * (numA + numD + numL + numU + numS + numLH + numUH + numB); + return charsetOptions; + } - transform(value: any[], name: string , cmd: any, attcktype?: boolean) { - if (!cmd || !name) { - return 'Wrong Command'; + function maskToKeyspace(mask: string) { + let keyspaceCustomMask = 1; + // The size of the custom charset equals the result of customCharsetToOptions. + // This number is multiplied by the number occurrences of the custom mask to get the size of the keyspace formed by the custom masks alone + if (options.customCharset1 !== '') { + keyspaceCustomMask = + keyspaceCustomMask * + Math.pow( + customCharsetToOptions(options.customCharset1), + (mask.match(/\?1/g) || []).length + ); } - // Iterate over files and get file count - const arr = []; - let mpow = 0; - if(value.length !== 0){ - for(let i=0; i < value.length; i++){ - arr.push(Number(value[i][name])); - } - mpow = arr.reduce((a, i) => a * i); + if (options.customCharset2 !== '') { + keyspaceCustomMask = + keyspaceCustomMask * + Math.pow( + customCharsetToOptions(options.customCharset2), + (mask.match(/\?2/g) || []).length + ); } - // resetting the options - options = defaultOptions; - options.ruleFiles = []; - options.posArgs = []; - options.unrecognizedFlag = []; - // example cmd = "hashcat #HL# -a3 ?d?d?d?d" - // Ideally the opt-parser itself works for '-a3' instead of requiring a space as in '-a 3' to parse attackType - let args: any = cmd.replace('hashcat', ''); - args = args.replace(/(-a)(\d)(\s)/,"-a $2 "); // ensures that "-a3" becomes a valid attack mode: the opt-parser does not approve it (yet) - args = args.replace(/(-\d)(\S+)(\s)/,"$1 $2 "); // ensures that "-1?l?d" becomes a valid customCharset: the opt-parser does not approve it (yet) - args = args.replace(/\s+/g, ' '); // ensures that multiple consecutive spaces are reduced to a single space - args = args.trim(); - args = args.split(/ |=/g); - parser.parse(args); - function customCharsetToOptions(mask: string) { - const numA = (mask.match(/\?a/g) || []).length; - const numD = (mask.match(/\?d/g) || []).length; - const numL = (mask.match(/\?l/g) || []).length; - const numU = (mask.match(/\?u/g) || []).length; - const numS = (mask.match(/\?s/g) || []).length; - const numLH = (mask.match(/\?h/g) || []).length; - const numUH = (mask.match(/\?H/g) || []).length; - const numB = (mask.match(/\?b/g) || []).length; - let charsetOptions = 95 * Math.min(1, numA); - charsetOptions = charsetOptions + 10 * Math.min(1, numD); - charsetOptions = charsetOptions + 26 * Math.min(1, numL); - charsetOptions = charsetOptions + 26 * Math.min(1, numU); - charsetOptions = charsetOptions + 33 * Math.min(1, numS); - charsetOptions = charsetOptions + 16 * Math.min(1, numLH); - charsetOptions = charsetOptions + 16 * Math.min(1, numUH); - charsetOptions = charsetOptions + 256 * Math.min(1, numB); - // Add single characters that are part of the custom charset - // we assume no duplicate single characters are present in the custom charset! - // i.e. -1 abbc is considered to be a charset of 4 different characters in the calculation - charsetOptions = charsetOptions + mask.length - 2 * (numA + numD + numL + numU + numS + numLH + numUH + numB); - return charsetOptions; + if (options.customCharset3 !== '') { + keyspaceCustomMask = + keyspaceCustomMask * + Math.pow( + customCharsetToOptions(options.customCharset3), + (mask.match(/\?3/g) || []).length + ); + } + if (options.customCharset4 !== '') { + keyspaceCustomMask = + keyspaceCustomMask * + Math.pow( + customCharsetToOptions(options.customCharset4), + (mask.match(/\?4/g) || []).length + ); } - function maskToKeyspace(mask: string) { - let keyspaceCustomMask = 1; - // The size of the custom charset equals the result of customCharsetToOptions. - // This number is multiplied by the number occurrences of the custom mask to get the size of the keyspace formed by the custom masks alone - if (options.customCharset1 !== "") - { - keyspaceCustomMask = keyspaceCustomMask * Math.pow(customCharsetToOptions(options.customCharset1), (mask.match(/\?1/g) || []).length);} - if (options.customCharset2 !== "") - { - keyspaceCustomMask = keyspaceCustomMask * Math.pow(customCharsetToOptions(options.customCharset2), (mask.match(/\?2/g) || []).length);} - if (options.customCharset3 !== "") - { - keyspaceCustomMask = keyspaceCustomMask * Math.pow(customCharsetToOptions(options.customCharset3), (mask.match(/\?3/g) || []).length);} - if (options.customCharset4 !== "") - { - keyspaceCustomMask = keyspaceCustomMask * Math.pow(customCharsetToOptions(options.customCharset4), (mask.match(/\?4/g) || []).length);} - - let keyspaceRegularMask = 1; + let keyspaceRegularMask = 1; - // compute the keyspace size for the custom charsets separately, and multiply with the keyspace size formed by the regular mask part - keyspaceRegularMask = Math.pow(95, (mask.match(/\?a/g) || []).length); - keyspaceRegularMask = keyspaceRegularMask * Math.pow(10, (mask.match(/\?d/g) || []).length); - keyspaceRegularMask = keyspaceRegularMask * Math.pow(26, (mask.match(/\?l/g) || []).length); - keyspaceRegularMask = keyspaceRegularMask * Math.pow(26, (mask.match(/\?u/g) || []).length); - keyspaceRegularMask = keyspaceRegularMask * Math.pow(33, (mask.match(/\?s/g) || []).length); - keyspaceRegularMask = keyspaceRegularMask * Math.pow(16, (mask.match(/\?h/g) || []).length); - keyspaceRegularMask = keyspaceRegularMask * Math.pow(16, (mask.match(/\?H/g) || []).length); - keyspaceRegularMask = keyspaceRegularMask * Math.pow(256, (mask.match(/\?b/g) || []).length); + // compute the keyspace size for the custom charsets separately, and multiply with the keyspace size formed by the regular mask part + keyspaceRegularMask = Math.pow(95, (mask.match(/\?a/g) || []).length); + keyspaceRegularMask = + keyspaceRegularMask * Math.pow(10, (mask.match(/\?d/g) || []).length); + keyspaceRegularMask = + keyspaceRegularMask * Math.pow(26, (mask.match(/\?l/g) || []).length); + keyspaceRegularMask = + keyspaceRegularMask * Math.pow(26, (mask.match(/\?u/g) || []).length); + keyspaceRegularMask = + keyspaceRegularMask * Math.pow(33, (mask.match(/\?s/g) || []).length); + keyspaceRegularMask = + keyspaceRegularMask * Math.pow(16, (mask.match(/\?h/g) || []).length); + keyspaceRegularMask = + keyspaceRegularMask * Math.pow(16, (mask.match(/\?H/g) || []).length); + keyspaceRegularMask = + keyspaceRegularMask * Math.pow(256, (mask.match(/\?b/g) || []).length); - return keyspaceRegularMask * keyspaceCustomMask; - } + return keyspaceRegularMask * keyspaceCustomMask; + } - let keyspace: number; - if (options.attackType === 3 && mpow >= 0) { - // compute keyspace for bruteforce attacks - for (let i = 0; i < options.posArgs.length; i++) { - const posArg = options.posArgs[i]; - if (posArg.includes("?")) { - const mask = posArg; - keyspace = maskToKeyspace(mask); - // return [keyspace, options.attackType]; // return also attack type - if(attcktype == true){keyspace = options.attackType} - return [keyspace]; - } + let keyspace: number; + if (options.attackType === 3 && mpow >= 0) { + // compute keyspace for bruteforce attacks + for (let i = 0; i < options.posArgs.length; i++) { + const posArg = options.posArgs[i]; + if (posArg.includes('?')) { + const mask = posArg; + keyspace = maskToKeyspace(mask); + // return [keyspace, options.attackType]; // return also attack type + if (attcktype == true) { + keyspace = options.attackType; } + return [keyspace]; + } + } + } + if (mpow > 0 && options.attackType !== 3) { + if (attcktype == true) { + mpow = options.attackType; } - if(mpow > 0 && options.attackType !== 3){ - if(attcktype == true){mpow = options.attackType} - return mpow; - }else{ - let result = null; - if(attcktype == true){result = options.attackType} - return result + return mpow; + } else { + let result = null; + if (attcktype == true) { + result = options.attackType; } + return result; + } } } diff --git a/src/app/core/_pipes/task-dispatched.pipe.ts b/src/app/core/_pipes/task-dispatched.pipe.ts index f63150c0..97a6afd9 100644 --- a/src/app/core/_pipes/task-dispatched.pipe.ts +++ b/src/app/core/_pipes/task-dispatched.pipe.ts @@ -1,7 +1,4 @@ -import { - PipeTransform, - Pipe -} from '@angular/core'; +import { Pipe, PipeTransform } from '@angular/core'; import { environment } from './../../../environments/environment'; import { GlobalService } from '../_services/main.service'; @@ -12,19 +9,15 @@ import { firstValueFrom } from 'rxjs'; * Returns dispatched * @param id - Task Id * @param keyspace - Keyspace -**/ + **/ @Pipe({ name: 'tdispatched' }) export class TaskDispatchedPipe implements PipeTransform { + constructor(private gs: GlobalService) {} - constructor( - private gs: GlobalService - ) { } - - transform(id: number, keyspace: number, type?:boolean) { - + transform(id: number, keyspace: number, type?: boolean) { if (!id) { return null; } @@ -34,25 +27,22 @@ export class TaskDispatchedPipe implements PipeTransform { const dispatched = []; let params: any; - if(type){ - params = {'maxResults': maxResults, 'filter': 'agentId='+id+''}; - }else{ - params = {'maxResults': maxResults, 'filter': 'taskId='+id+''}; + if (type) { + params = { maxResults: maxResults, filter: 'agentId=' + id + '' }; + } else { + params = { maxResults: maxResults, filter: 'taskId=' + id + '' }; } - return firstValueFrom(this.gs.getAll(SERV.CHUNKS,params)) - .then((res) => { + return firstValueFrom(this.gs.getAll(SERV.CHUNKS, params)).then((res) => { const ch = res.values; - for(let i=0; i < ch.length; i++){ - if(ch[i].progress >= 10000){ + for (let i = 0; i < ch.length; i++) { + if (ch[i].progress >= 10000) { dispatched.push(ch[i]['length']); - }else{ + } else { dispatched.push(ch[i]['length']); } } - return dispatched?.reduce((a, i) => a + i,0)/keyspace; + return dispatched?.reduce((a, i) => a + i, 0) / keyspace; }); } - } - diff --git a/src/app/core/_pipes/task-searched.pipe.ts b/src/app/core/_pipes/task-searched.pipe.ts index a82d490e..58044e15 100644 --- a/src/app/core/_pipes/task-searched.pipe.ts +++ b/src/app/core/_pipes/task-searched.pipe.ts @@ -1,7 +1,4 @@ -import { - PipeTransform, - Pipe -} from '@angular/core'; +import { Pipe, PipeTransform } from '@angular/core'; import { environment } from './../../../environments/environment'; import { GlobalService } from '../_services/main.service'; @@ -12,19 +9,15 @@ import { firstValueFrom } from 'rxjs'; * Returns search value over chunks * @param id - Task Id * @param keyspace - Keyspace -**/ + **/ @Pipe({ name: 'tdsearched' }) export class TaskSearchedPipe implements PipeTransform { + constructor(private gs: GlobalService) {} - constructor( - private gs: GlobalService - ) { } - - transform(id: number, keyspace: number, type?:boolean) { - + transform(id: number, keyspace: number, type?: boolean) { if (!id) { return null; } @@ -34,24 +27,20 @@ export class TaskSearchedPipe implements PipeTransform { const searched = []; let params: any; - if(type){ - params = {'maxResults': maxResults, 'filter': 'agentId='+id+''}; - }else{ - params = {'maxResults': maxResults, 'filter': 'taskId='+id+''}; + if (type) { + params = { maxResults: maxResults, filter: 'agentId=' + id + '' }; + } else { + params = { maxResults: maxResults, filter: 'taskId=' + id + '' }; } - return firstValueFrom(this.gs.getAll(SERV.CHUNKS,params)) - .then((res) => { - - const ch = res.values; + return firstValueFrom(this.gs.getAll(SERV.CHUNKS, params)).then((res) => { + const ch = res.values; - for(let i=0; i < ch.length; i++){ + for (let i = 0; i < ch.length; i++) { searched.push(ch[i].checkpoint - ch[i].skip); - } - - return searched?.reduce((a, i) => a + i,0)/keyspace; + } + return searched?.reduce((a, i) => a + i, 0) / keyspace; }); } } - diff --git a/src/app/tasks/edit-supertasks/edit-supertasks.component.html b/src/app/tasks/edit-supertasks/edit-supertasks.component.html index eb6a4eaa..99f457b1 100644 --- a/src/app/tasks/edit-supertasks/edit-supertasks.component.html +++ b/src/app/tasks/edit-supertasks/edit-supertasks.component.html @@ -66,6 +66,11 @@
Estimate Time + + + + +

Pretasks

@@ -94,7 +99,7 @@

Pretasks

{{ p.pretaskFiles | keyspace:'lineCount':p.attackCmd:false }} - + diff --git a/src/app/tasks/edit-supertasks/edit-supertasks.component.ts b/src/app/tasks/edit-supertasks/edit-supertasks.component.ts index f65ef019..4cbcf452 100644 --- a/src/app/tasks/edit-supertasks/edit-supertasks.component.ts +++ b/src/app/tasks/edit-supertasks/edit-supertasks.component.ts @@ -49,12 +49,6 @@ export class EditSupertasksComponent implements OnInit { pretasksFiles: any = []; assignPretasks: any; - @ViewChild(DataTableDirective) - dtElement: DataTableDirective; - - dtTrigger: Subject = new Subject(); - dtOptions: any = {}; - constructor( private unsubscribeService: UnsubscribeService, private changeDetectorRef: ChangeDetectorRef, @@ -217,27 +211,12 @@ export class EditSupertasksComponent implements OnInit { }); } - /** - * Handles the removal of a pre-task from the assigned pre-tasks of a super task. - * Updates the super task with the modified pre-task payload after deletion. - * - * @param {number} pretaskId - The ID of the pre-task to be removed. - */ - removeAssignedPretask(id: number) { - const filter = this.assignPretasks.filter((u) => u.pretaskId !== id); - const payload = []; - for (let i = 0; i < filter.length; i++) { - payload.push(filter[i].pretaskId); - } - const updateSubscription$ = this.gs - .update(SERV.SUPER_TASKS, this.editedSTIndex, { pretasks: payload }) - .subscribe((result) => { - this.alert.okAlert('Deleted supertask', ''); - this.updateForm.reset(); // success, we reset form - this.onRefresh(); - }); - this.unsubscribeService.add(updateSubscription$); - } + //To delete + @ViewChild(DataTableDirective) + dtElement: DataTableDirective; + + dtTrigger: Subject = new Subject(); + dtOptions: any = {}; loadTableData() { const matchObjectFiles = []; @@ -289,9 +268,6 @@ export class EditSupertasksComponent implements OnInit { } onRefresh() { - // this.rerender(); - // this.ngOnInit(); - // Todo using window reload as some issues clearing filter when adding pretask window.location.reload(); } diff --git a/src/app/tasks/preconfigured-tasks/preconfigured-tasks.component.html b/src/app/tasks/preconfigured-tasks/preconfigured-tasks.component.html index 74829a87..49a0c212 100644 --- a/src/app/tasks/preconfigured-tasks/preconfigured-tasks.component.html +++ b/src/app/tasks/preconfigured-tasks/preconfigured-tasks.component.html @@ -5,5 +5,5 @@ [buttonlink]="'/tasks/new-preconfigured-tasks'" [subbutton]="true" > - +
diff --git a/src/config/default/app/tooltip.ts b/src/config/default/app/tooltip.ts index 1d7022d1..42c9ed71 100644 --- a/src/config/default/app/tooltip.ts +++ b/src/config/default/app/tooltip.ts @@ -1,14 +1,13 @@ - /* * Tooltip generator * 0 - Concise * 1 - Precise * 2 - Exhaustive * -*/ + */ export const DEFAULT_CONFIG_TOOLTIP = { - tasks:{ - 0:{ + tasks: { + 0: { chunkTime: 'chunk size Level 0', statusTimer: 'status timer level 0', useNewBench: 'Concise infomation', @@ -17,9 +16,9 @@ export const DEFAULT_CONFIG_TOOLTIP = { isSmall: 'Only one agent is assigned to the task', preprocessorId: 'Preprocessor Level 0', staticChunks: 'Static chunk level 0', - forcePipe: 'To apply rules before reject', + forcePipe: 'To apply rules before reject' }, - 1:{ + 1: { chunkTime: 'chunk size Level 1', statusTimer: 'status timer level 1', useNewBench: 'Detailed infomation', @@ -30,7 +29,7 @@ export const DEFAULT_CONFIG_TOOLTIP = { staticChunks: 'Static chunk level 1', forcePipe: 'To apply rules before reject' }, - 2:{ + 2: { chunkTime: 'chunk size Level 2', statusTimer: 'status timer level 2', skipKeyspace: 'skipKeyspace level 2', @@ -42,9 +41,9 @@ export const DEFAULT_CONFIG_TOOLTIP = { forcePipe: 'To apply rules before reject' } }, - config:{ - 0:{ - agent:{ + config: { + 0: { + agent: { agenttimeout: 'Level 0', benchtime: 'Level 0', statustimer: 'Level 0', @@ -56,9 +55,9 @@ export const DEFAULT_CONFIG_TOOLTIP = { agentTempThreshold1: 'Level 0', agentTempThreshold2: 'Level 0', agentUtilThreshold1: 'Level 0', - agentUtilThreshold2: 'Level 0', + agentUtilThreshold2: 'Level 0' }, - tc:{ + tc: { chunktime: 'Level 0', disptolerance: 'Level 0', defaultBenchmark: 'Level 0', @@ -71,7 +70,7 @@ export const DEFAULT_CONFIG_TOOLTIP = { ruleSplitAlways: 'Level 0', ruleSplitDisable: 'Level 0' }, - hch:{ + hch: { maxHashlistSize: 'Level 0', pagingSize: 'Level 0', hashesPerPage: 'Level 0', @@ -79,18 +78,18 @@ export const DEFAULT_CONFIG_TOOLTIP = { hashlistImportCheck: 'Level 0', batchSize: 'Level 0', plainTextMaxLength: 'Level 0', - hashMaxLength: 'Level 0', + hashMaxLength: 'Level 0' }, - notif:{ + notif: { emailSender: 'Level 0', emailSenderName: 'Level 0', telegramBotToken: 'Level 0', notificationsProxyEnable: 'Level 0', notificationsProxyServer: 'Level 0', notificationsProxyPort: 'Level 0', - notificationsProxyType: 'Level 0', + notificationsProxyType: 'Level 0' }, - gs:{ + gs: { hashcatBrainEnable: 'Once enable, new options will show in hashlists', hashcatBrainHost: 'Level 0', hashcatBrainPort: 'Level 0', @@ -101,11 +100,11 @@ export const DEFAULT_CONFIG_TOOLTIP = { maxSessionLength: 'Level 0', baseHost: 'Level 0', contactEmail: 'Level 0', - serverLogLevel: 'Level 0', + serverLogLevel: 'Level 0' } }, - 1:{ - agent:{ + 1: { + agent: { agenttimeout: 'Level 1', benchtime: 'Level 1', statustimer: 'Level 1', @@ -117,9 +116,9 @@ export const DEFAULT_CONFIG_TOOLTIP = { agentTempThreshold1: 'Level 1', agentTempThreshold2: 'Level 1', agentUtilThreshold1: 'Level 1', - agentUtilThreshold2: 'Level 1', + agentUtilThreshold2: 'Level 1' }, - tc:{ + tc: { chunktime: 'Level 1', disptolerance: 'Level 1', defaultBenchmark: 'Level 1', @@ -132,7 +131,7 @@ export const DEFAULT_CONFIG_TOOLTIP = { ruleSplitAlways: 'Level 1', ruleSplitDisable: 'Level 1' }, - hch:{ + hch: { maxHashlistSize: 'Level 1', pagingSize: 'Level 1', hashesPerPage: 'Level 1', @@ -140,18 +139,18 @@ export const DEFAULT_CONFIG_TOOLTIP = { hashlistImportCheck: 'Level 1', batchSize: 'Level 1', plainTextMaxLength: 'Level 1', - hashMaxLength: 'Level 1', + hashMaxLength: 'Level 1' }, - notif:{ + notif: { emailSender: 'Level 1', emailSenderName: 'Level 1', telegramBotToken: 'Level 1', notificationsProxyEnable: 'Level 1', notificationsProxyServer: 'Level 1', notificationsProxyPort: 'Level 1', - notificationsProxyType: 'Level 1', + notificationsProxyType: 'Level 1' }, - gs:{ + gs: { hashcatBrainEnable: 'Once enable, new options will show in hashlists', hashcatBrainHost: 'Level 1', hashcatBrainPort: 'Level 1', @@ -162,11 +161,11 @@ export const DEFAULT_CONFIG_TOOLTIP = { maxSessionLength: 'Level 1', baseHost: 'Level 1', contactEmail: 'Level 1', - serverLogLevel: 'Level 1', + serverLogLevel: 'Level 1' } }, - 2:{ - agent:{ + 2: { + agent: { agenttimeout: 'Level 2', benchtime: 'Level 2', statustimer: 'Level 2', @@ -178,9 +177,9 @@ export const DEFAULT_CONFIG_TOOLTIP = { agentTempThreshold1: 'Level 2', agentTempThreshold2: 'Level 2', agentUtilThreshold1: 'Level 2', - agentUtilThreshold2: 'Level 2', + agentUtilThreshold2: 'Level 2' }, - tc:{ + tc: { chunktime: 'Level 2', disptolerance: 'Level 2', defaultBenchmark: 'Level 2', @@ -193,7 +192,7 @@ export const DEFAULT_CONFIG_TOOLTIP = { ruleSplitAlways: 'Level 2', ruleSplitDisable: 'Level 2' }, - hch:{ + hch: { maxHashlistSize: 'Level 2', pagingSize: 'Level 2', hashesPerPage: 'Level 2', @@ -201,18 +200,18 @@ export const DEFAULT_CONFIG_TOOLTIP = { hashlistImportCheck: 'Level 2', batchSize: 'Level 2', plainTextMaxLength: 'Level 2', - hashMaxLength: 'Level 2', + hashMaxLength: 'Level 2' }, - notif:{ + notif: { emailSender: 'Level 2', emailSenderName: 'Level 2', telegramBotToken: 'Level 2', notificationsProxyEnable: 'Level 2', notificationsProxyServer: 'Level 2', notificationsProxyPort: 'Level 2', - notificationsProxyType: 'Level 2', + notificationsProxyType: 'Level 2' }, - gs:{ + gs: { hashcatBrainEnable: 'Once enable, new options will show in hashlists', hashcatBrainHost: 'Level 2', hashcatBrainPort: 'Level 2', @@ -223,10 +222,8 @@ export const DEFAULT_CONFIG_TOOLTIP = { maxSessionLength: 'Level 2', baseHost: 'Level 2', contactEmail: 'Level 2', - serverLogLevel: 'Level 2', + serverLogLevel: 'Level 2' } } } }; - - From 16dc3146687843fa6e872e1cd4eef0fd3d53fa18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Fri, 26 Jan 2024 14:23:26 +0000 Subject: [PATCH 396/419] render estimated keyspace --- .../pretasks-table.component.ts | 21 ++- .../preconfigured-tasks.datasource.ts | 27 ++- src/app/shared/utils/estkeyspace_attack.ts | 172 ++++++++++++++++++ 3 files changed, 209 insertions(+), 11 deletions(-) create mode 100644 src/app/shared/utils/estkeyspace_attack.ts diff --git a/src/app/core/_components/tables/pretasks-table/pretasks-table.component.ts b/src/app/core/_components/tables/pretasks-table/pretasks-table.component.ts index 8bbe7e3f..29b23ad8 100644 --- a/src/app/core/_components/tables/pretasks-table/pretasks-table.component.ts +++ b/src/app/core/_components/tables/pretasks-table/pretasks-table.component.ts @@ -15,15 +15,16 @@ import { ActionMenuEvent } from '../../menus/action-menu/action-menu.model'; import { BaseTableComponent } from '../base-table/base-table.component'; import { BulkActionMenuAction } from '../../menus/bulk-action-menu/bulk-action-menu.constants'; import { Cacheable } from 'src/app/core/_decorators/cacheable'; +import { calculateKeyspace } from 'src/app/shared/utils/estkeyspace_attack'; import { DialogData } from '../table-dialog/table-dialog.model'; import { ExportMenuAction } from '../../menus/export-menu/export-menu.constants'; import { PreTasksDataSource } from 'src/app/core/_datasources/preconfigured-tasks.datasource'; import { Pretask } from 'src/app/core/_models/pretask.model'; import { RowActionMenuAction } from '../../menus/row-action-menu/row-action-menu.constants'; +import { SafeHtml } from '@angular/platform-browser'; import { SERV } from 'src/app/core/_services/main.config'; import { TableDialogComponent } from '../table-dialog/table-dialog.component'; import { formatFileSize } from 'src/app/shared/utils/util'; -import { SafeHtml } from '@angular/platform-browser'; @Component({ selector: 'pretasks-table', @@ -129,14 +130,14 @@ export class PretasksTableComponent async: (pretask: Pretask) => this.renderEstimatedKeyspace(pretask), icons: undefined, isSortable: true, - export: async (pretask: Pretask) => (await pretask) + '' + export: async (pretask: Pretask) => + Promise.resolve(this.renderEstimatedKeyspace(pretask).toString()) }); tableColumns.push({ id: PretasksTableCol.ATTACK_RUNTIME, - dataKey: 'runtime', + dataKey: 'attackRuntime', icons: undefined, - isSortable: true, - export: async (pretask: Pretask) => pretask.maxAgents.toString() + isSortable: true }); } @@ -331,9 +332,13 @@ export class PretasksTableComponent } @Cacheable(['_id']) - async renderEstimatedKeyspace(pretask: Pretask): Promise { - const html = '-'; - return this.sanitize(html); + async renderEstimatedKeyspace(pretask: any): Promise { + return calculateKeyspace( + pretask.pretaskFiles[0].pretaskFiles, + 'lineCount', + pretask.attackCmd, + false + ); } /** diff --git a/src/app/core/_datasources/preconfigured-tasks.datasource.ts b/src/app/core/_datasources/preconfigured-tasks.datasource.ts index f474fef8..83311357 100644 --- a/src/app/core/_datasources/preconfigured-tasks.datasource.ts +++ b/src/app/core/_datasources/preconfigured-tasks.datasource.ts @@ -17,6 +17,8 @@ export class PreTasksDataSource extends BaseDataSource< this._superTaskId = superTaskId; } + // ToDo supertasks expand pretask doesnt include pretasfiles, so currently we need to make + // and additional call to pretasks and join arrays. API call expand is needed loadAll(): void { this.loading = true; @@ -53,11 +55,30 @@ export class PreTasksDataSource extends BaseDataSource< this.currentPage, response.total ); + + this.setData(pretasks); } else { - pretasks = response.pretasks || []; - } + const superTaskPretasks = response.pretasks || []; + + // Make another request to get pretaskFiles + this.service + .getAll(SERV.PRETASKS, { + expand: 'pretaskFiles' + }) + .subscribe((pretaskFilesResponse: ListResponseWrapper) => { + const pretaskFiles = pretaskFilesResponse.values || []; - this.setData(pretasks); + // Merge pretasks with pretaskFiles + const pretasks = superTaskPretasks.map((superTaskPretask) => ({ + ...superTaskPretask, + pretaskFiles: pretaskFiles.filter( + (pf) => pf.pretaskId === superTaskPretask.pretaskId + ) + })); + + this.setData(pretasks); + }); + } }) ); } diff --git a/src/app/shared/utils/estkeyspace_attack.ts b/src/app/shared/utils/estkeyspace_attack.ts new file mode 100644 index 00000000..231a679a --- /dev/null +++ b/src/app/shared/utils/estkeyspace_attack.ts @@ -0,0 +1,172 @@ +/** + * Section for Estimated Keyspace and Attack Runtime + * + * Used in Edit supertask, table column estimated keyspace and attack runtime + */ +declare let options: any; +declare let defaultOptions: any; +declare let parser: any; + +/** + * This function calculates the keyspace using the asset files optparse located in assets folder + * @param value - Object + * @param name - files object to extract line count + * @param cmd - Attack + * @param attcktype - If true, returns attack type + * @returns number + **/ +export function calculateKeyspace( + value: any[], + name: string, + cmd: any, + attcktype?: boolean +): number | number[] | string { + if (!cmd || !name) { + return 'Wrong Command'; + } + + // Extract line count values from the array of objects + const arr = []; + let mpow = 0; + if (value.length !== 0) { + for (let i = 0; i < value.length; i++) { + arr.push(Number(value[i][name])); + } + mpow = arr.reduce((a, i) => a * i); + } + + // Reset options to default values + options = defaultOptions; + options.ruleFiles = []; + options.posArgs = []; + options.unrecognizedFlag = []; + + // Parse the attack command using the specified parser + let args: any = cmd.replace('hashcat', ''); + args = args.replace(/(-a)(\d)(\s)/, '-a $2 '); + args = args.replace(/(-\d)(\S+)(\s)/, '$1 $2 '); + args = args.replace(/\s+/g, ' '); + args = args.trim(); + args = args.split(/ |=/g); + parser.parse(args); + + // Helper function: Calculate options for custom character set + function customCharsetToOptions(mask: string) { + const numA = (mask.match(/\?a/g) || []).length; + const numD = (mask.match(/\?d/g) || []).length; + const numL = (mask.match(/\?l/g) || []).length; + const numU = (mask.match(/\?u/g) || []).length; + const numS = (mask.match(/\?s/g) || []).length; + const numLH = (mask.match(/\?h/g) || []).length; + const numUH = (mask.match(/\?H/g) || []).length; + const numB = (mask.match(/\?b/g) || []).length; + let charsetOptions = 95 * Math.min(1, numA); + charsetOptions = charsetOptions + 10 * Math.min(1, numD); + charsetOptions = charsetOptions + 26 * Math.min(1, numL); + charsetOptions = charsetOptions + 26 * Math.min(1, numU); + charsetOptions = charsetOptions + 33 * Math.min(1, numS); + charsetOptions = charsetOptions + 16 * Math.min(1, numLH); + charsetOptions = charsetOptions + 16 * Math.min(1, numUH); + charsetOptions = charsetOptions + 256 * Math.min(1, numB); + // Add single characters that are part of the custom charset + // we assume no duplicate single characters are present in the custom charset! + // i.e. -1 abbc is considered to be a charset of 4 different characters in the calculation + charsetOptions = + charsetOptions + + mask.length - + 2 * (numA + numD + numL + numU + numS + numLH + numUH + numB); + return charsetOptions; + } + + // Helper function: Calculate keyspace based on the mask + function maskToKeyspace(mask: string) { + let keyspaceCustomMask = 1; + // The size of the custom charset equals the result of customCharsetToOptions. + // This number is multiplied by the number occurrences of the custom mask to get the size of the keyspace formed by the custom masks alone + if (options.customCharset1 !== '') { + keyspaceCustomMask = + keyspaceCustomMask * + Math.pow( + customCharsetToOptions(options.customCharset1), + (mask.match(/\?1/g) || []).length + ); + } + if (options.customCharset2 !== '') { + keyspaceCustomMask = + keyspaceCustomMask * + Math.pow( + customCharsetToOptions(options.customCharset2), + (mask.match(/\?2/g) || []).length + ); + } + if (options.customCharset3 !== '') { + keyspaceCustomMask = + keyspaceCustomMask * + Math.pow( + customCharsetToOptions(options.customCharset3), + (mask.match(/\?3/g) || []).length + ); + } + if (options.customCharset4 !== '') { + keyspaceCustomMask = + keyspaceCustomMask * + Math.pow( + customCharsetToOptions(options.customCharset4), + (mask.match(/\?4/g) || []).length + ); + } + + let keyspaceRegularMask = 1; + + // compute the keyspace size for the custom charsets separately, and multiply with the keyspace size formed by the regular mask part + keyspaceRegularMask = Math.pow(95, (mask.match(/\?a/g) || []).length); + keyspaceRegularMask = + keyspaceRegularMask * Math.pow(10, (mask.match(/\?d/g) || []).length); + keyspaceRegularMask = + keyspaceRegularMask * Math.pow(26, (mask.match(/\?l/g) || []).length); + keyspaceRegularMask = + keyspaceRegularMask * Math.pow(26, (mask.match(/\?u/g) || []).length); + keyspaceRegularMask = + keyspaceRegularMask * Math.pow(33, (mask.match(/\?s/g) || []).length); + keyspaceRegularMask = + keyspaceRegularMask * Math.pow(16, (mask.match(/\?h/g) || []).length); + keyspaceRegularMask = + keyspaceRegularMask * Math.pow(16, (mask.match(/\?H/g) || []).length); + keyspaceRegularMask = + keyspaceRegularMask * Math.pow(256, (mask.match(/\?b/g) || []).length); + + return keyspaceRegularMask * keyspaceCustomMask; + } + + let keyspace: number; + // Check if it's a bruteforce attack and calculate keyspace accordingly + if (options.attackType === 3 && mpow >= 0) { + // compute keyspace for bruteforce attacks + for (let i = 0; i < options.posArgs.length; i++) { + const posArg = options.posArgs[i]; + if (posArg.includes('?')) { + const mask = posArg; + keyspace = maskToKeyspace(mask); + // return [keyspace, options.attackType]; // return also attack type + if (attcktype == true) { + keyspace = options.attackType; + } + return [keyspace]; + } + } + } + // Check if it's a non-bruteforce attack with a positive mpow + if (mpow > 0 && options.attackType !== 3) { + if (attcktype == true) { + mpow = options.attackType; + } + return mpow; + } else { + // Return null if conditions are not met + let result = null; + if (attcktype == true) { + result = options.attackType; + } + return result; + } +} From ea4b3ab0ee34571c22d94adbd51a91c7efe83d65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Fri, 26 Jan 2024 15:44:24 +0000 Subject: [PATCH 397/419] Add table color for pretasks --- .../tables/ht-table/ht-table.component.html | 10 ++-------- .../_datasources/preconfigured-tasks.datasource.ts | 1 - 2 files changed, 2 insertions(+), 9 deletions(-) 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 d603b750..78b84df5 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 @@ -90,14 +90,8 @@ mat-cell *matCellDef="let row" [ngStyle]="{ - 'border-left': - row.tasks && row.tasks.length > 0 && row.tasks[0].color - ? '8px solid ' + row.tasks[0].color - : '', - 'padding-left': - row.tasks && row.tasks.length > 0 && row.tasks[0].color - ? '8px' - : '16px' + 'border-left': (row.tasks && row.tasks.length > 0 && row.tasks[0].color) || (row && row.color) ? '8px solid ' + (row.tasks && row.tasks.length > 0 ? row.tasks[0].color : row.color) : '', + 'padding-left': (row.tasks && row.tasks.length > 0 && row.tasks[0].color) || (row.color) ? '8px' : '16px' }" > diff --git a/src/app/core/_datasources/preconfigured-tasks.datasource.ts b/src/app/core/_datasources/preconfigured-tasks.datasource.ts index 83311357..6b4689bf 100644 --- a/src/app/core/_datasources/preconfigured-tasks.datasource.ts +++ b/src/app/core/_datasources/preconfigured-tasks.datasource.ts @@ -55,7 +55,6 @@ export class PreTasksDataSource extends BaseDataSource< this.currentPage, response.total ); - this.setData(pretasks); } else { const superTaskPretasks = response.pretasks || []; From 66b6b6148f65b1b6006b21e83bd38786df72e721 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Sat, 27 Jan 2024 13:08:25 +0000 Subject: [PATCH 398/419] is running agent task in Agent status --- .../agents-status-table.component.ts | 12 ++- .../tables/base-table/base-table.component.ts | 4 +- src/app/core/_pipes/agents-speed.pipe.ts | 65 ---------------- src/app/core/_services/shared/bulk.service.ts | 75 +++++++++---------- src/app/core/_services/shared/util.service.ts | 49 ++++++++++++ src/app/shared/pipes.module.ts | 3 - 6 files changed, 94 insertions(+), 114 deletions(-) delete mode 100644 src/app/core/_pipes/agents-speed.pipe.ts create mode 100644 src/app/core/_services/shared/util.service.ts diff --git a/src/app/core/_components/tables/agents-status-table/agents-status-table.component.ts b/src/app/core/_components/tables/agents-status-table/agents-status-table.component.ts index f555c1b4..ae6095d8 100644 --- a/src/app/core/_components/tables/agents-status-table/agents-status-table.component.ts +++ b/src/app/core/_components/tables/agents-status-table/agents-status-table.component.ts @@ -87,7 +87,7 @@ export class AgentsStatusTableComponent id: AgentsStatusTableCol.STATUS, dataKey: 'status', isSortable: true, - render: (agent: Agent): SafeHtml => this.renderActiveSpinner(agent), + async: (agent: Agent) => this.renderActiveAgent(agent), export: async (agent: Agent) => (agent.isActive ? 'Active' : 'Inactive') }, { @@ -162,9 +162,13 @@ export class AgentsStatusTableComponent // --- Render functions --- - renderActiveSpinner(agent: Agent): SafeHtml { - const htmlContent = ``; - return this.sanitizer.bypassSecurityTrustHtml(htmlContent); + @Cacheable(['_id']) + async renderActiveAgent(agent: Agent): Promise { + const agentSpeed = await this.utilService + .calculateSpeed(agent.agentId, true) + .toPromise(); + const result = agentSpeed > 0 ? 'Running task' : 'Stopped task'; + return this.sanitize(`${result}`); } @Cacheable(['_id', 'agentName']) 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 1504d7ae..1c9114af 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 @@ -27,6 +27,7 @@ import { Router } from '@angular/router'; import { Subscription } from 'rxjs'; import { UIConfigService } from 'src/app/core/_services/shared/storage.service'; import { UISettingsUtilityClass } from 'src/app/shared/utils/config'; +import { UtilService } from 'src/app/core/_services/shared/util.service'; @Component({ selector: 'base-table', @@ -65,7 +66,8 @@ export class BaseTableComponent { protected uiService: UIConfigService, protected exportService: ExportService, protected cdr: ChangeDetectorRef, - public dialog: MatDialog + public dialog: MatDialog, + public utilService: UtilService ) { this.uiSettings = new UISettingsUtilityClass(settingsService); this.dateFormat = this.getDateFormat(); diff --git a/src/app/core/_pipes/agents-speed.pipe.ts b/src/app/core/_pipes/agents-speed.pipe.ts deleted file mode 100644 index af118fdd..00000000 --- a/src/app/core/_pipes/agents-speed.pipe.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { Pipe, PipeTransform } from '@angular/core'; - -import { UIConfigService } from '../_services/shared/storage.service'; -import { environment } from './../../../environments/environment'; -import { GlobalService } from '../_services/main.service'; -import { SERV } from '../../core/_services/main.config'; -import { firstValueFrom } from 'rxjs'; - -/** - * This function calculates the agent current speed - * @param id - Task Id or agent Id - * @param type - True check speed for Agent False for Task - * Usage: - * object | aspeed:true - * Example: - * {{ number | aspeed:'1' }} - * @returns number - **/ - -@Pipe({ - name: 'aspeed' -}) -export class AgentsSpeedPipe implements PipeTransform { - constructor( - private uiService: UIConfigService, - private gs: GlobalService - ) {} - - currenspeed = 0; - isactive = false; - - transform(id: number, type?: boolean) { - const maxResults = environment.config.prodApiMaxResults; - const chunktime = this.uiService.getUIsettings('chunktime').value; - const cspeed = []; - let params: any; - - let currenspeed: any; - - if (type) { - params = { maxResults: maxResults, filter: 'agentId=' + id + '' }; - } else { - params = { maxResults: maxResults, filter: 'taskId=' + id + '' }; - } - - return firstValueFrom(this.gs.getAll(SERV.CHUNKS, params)).then((res) => { - for (let i = 0; i < res.values.length; i++) { - if ( - Date.now() / 1000 - - Math.max(res.values[i].solveTime, res.values[i].dispatchTime) < - chunktime && - res.values[i].progress < 10000 - ) { - cspeed.push(res.values[i].speed); - } - } - currenspeed = cspeed.reduce((a, i) => a + i, 0); - if (currenspeed > 0) { - return currenspeed; - } else { - return 0; - } - }); - } -} diff --git a/src/app/core/_services/shared/bulk.service.ts b/src/app/core/_services/shared/bulk.service.ts index 8a3868e9..cc9f1831 100644 --- a/src/app/core/_services/shared/bulk.service.ts +++ b/src/app/core/_services/shared/bulk.service.ts @@ -1,5 +1,5 @@ import { catchError, finalize, map, mergeMap, toArray } from 'rxjs/operators'; -import { from, of, Observable, Subscription, forkJoin } from 'rxjs'; +import { Observable, Subscription, forkJoin, from, of } from 'rxjs'; import { Injectable } from '@angular/core'; import { GlobalService } from '../main.service'; @@ -8,10 +8,7 @@ import { GlobalService } from '../main.service'; providedIn: 'root' }) export class BulkService { - - constructor( - private gs: GlobalService - ) {} + constructor(private gs: GlobalService) {} Items: any[]; Value: any; @@ -80,48 +77,44 @@ export class BulkService { * Displays a progress bar and one is complete return confirmation of action * * @param {number} percentage - Progress value - */ - - async performBulkUpdate( - progressCallback: (percentage: number) => void - ): Promise { - const Items = this.Items; - const Value = this.Value; - const totalItems = Items.length; - let deletedItems = 0; - - // Create an array to collect the results (true for success, false for failure) - const results: boolean[] = []; + */ - const itemObservables: Observable[] = []; + async performBulkUpdate( + progressCallback: (percentage: number) => void + ): Promise { + const Items = this.Items; + const Value = this.Value; + const totalItems = Items.length; + let deletedItems = 0; - console.log(this.path) + // Create an array to collect the results (true for success, false for failure) + const results: boolean[] = []; - Items.forEach((item) => { - const observable = this.gs.update(this.path, item, Value).pipe( - map(() => { - deletedItems++; - const progress = (deletedItems / totalItems) * 100; - progressCallback(progress); - return true; // Indicate success for each item - }), - catchError((error) => { - // Handle errors - return of(false); // Indicate failure for each item - }) - ); + const itemObservables: Observable[] = []; - itemObservables.push(observable); - }); + Items.forEach((item) => { + const observable = this.gs.update(this.path, item, Value).pipe( + map(() => { + deletedItems++; + const progress = (deletedItems / totalItems) * 100; + progressCallback(progress); + return true; // Indicate success for each item + }), + catchError((error) => { + // Handle errors + return of(false); // Indicate failure for each item + }) + ); - // Use forkJoin to wait for all item observables to complete - const resultsArray = await forkJoin(itemObservables).toPromise(); + itemObservables.push(observable); + }); - // Check if any item deletion has failed - const hasFailure = resultsArray.some((result) => result === false); + // Use forkJoin to wait for all item observables to complete + const resultsArray = await forkJoin(itemObservables).toPromise(); - return !hasFailure; // Return true if there are no failures - } + // Check if any item deletion has failed + const hasFailure = resultsArray.some((result) => result === false); + return !hasFailure; // Return true if there are no failures + } } - diff --git a/src/app/core/_services/shared/util.service.ts b/src/app/core/_services/shared/util.service.ts new file mode 100644 index 00000000..6a4aebfa --- /dev/null +++ b/src/app/core/_services/shared/util.service.ts @@ -0,0 +1,49 @@ +import { Injectable } from '@angular/core'; +import { environment } from 'src/environments/environment'; +import { SERV } from '../main.config'; +import { Observable, firstValueFrom, from, map } from 'rxjs'; +import { UIConfigService } from './storage.service'; +import { GlobalService } from '../main.service'; + +@Injectable({ + providedIn: 'root' +}) +export class UtilService { + constructor( + private uiService: UIConfigService, + private gs: GlobalService + ) {} + + calculateSpeed(id: number, type?: boolean): Observable { + const maxResults = environment.config.prodApiMaxResults; + const chunktime = this.uiService.getUIsettings('chunktime').value; + const cspeed = []; + let params: any; + let currenspeed: any; + + if (type) { + params = { maxResults: maxResults, filter: 'agentId=' + id + '' }; + } else { + params = { maxResults: maxResults, filter: 'taskId=' + id + '' }; + } + + return from(firstValueFrom(this.gs.getAll(SERV.CHUNKS, params))).pipe( + map((res) => { + for (let i = 0; i < res.values.length; i++) { + if ( + Date.now() / 1000 - + Math.max(res.values[i].solveTime, res.values[i].dispatchTime) < + chunktime && + res.values[i].progress < 10000 + ) { + cspeed.push(res.values[i].speed); + } + } + + currenspeed = cspeed.reduce((a, i) => a + i, 0); + + return currenspeed > 0 ? currenspeed : 0; + }) + ); + } +} diff --git a/src/app/shared/pipes.module.ts b/src/app/shared/pipes.module.ts index 29e4dd74..ef55e0ac 100644 --- a/src/app/shared/pipes.module.ts +++ b/src/app/shared/pipes.module.ts @@ -15,7 +15,6 @@ import { KeyspaceCalcPipe } from '../core/_pipes/keyspace-calc.pipe'; import { StaticArrayPipe } from '../core/_pipes/static-array.pipe'; import { MaximizePipe } from '../core/_pipes/maximize-object.pipe'; import { TaskCrackedPipe } from '../core/_pipes/task-cracked.pipe'; -import { AgentsSpeedPipe } from '../core/_pipes/agents-speed.pipe'; import { ArraySortPipe } from '../core/_pipes/orderby-item.pipe'; import { AveragePipe } from '../core/_pipes/average-object.pipe'; import { FilterItemPipe } from '../core/_pipes/filter-item.pipe'; @@ -42,7 +41,6 @@ import { uiDatePipe } from '../core/_pipes/date.pipe'; StaticArrayPipe, AgentSColorPipe, TaskCrackedPipe, - AgentsSpeedPipe, FilterItemPipe, ArraySortPipe, FileSizePipe, @@ -70,7 +68,6 @@ import { uiDatePipe } from '../core/_pipes/date.pipe'; StaticArrayPipe, AgentSColorPipe, TaskCrackedPipe, - AgentsSpeedPipe, FilterItemPipe, ArraySortPipe, FileSizePipe, From fe944f84d744e6d9116ecae4c8bd5bf1416d9075 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Wed, 31 Jan 2024 09:25:02 +0000 Subject: [PATCH 399/419] Agent status working on column --- .../agents-status-table.component.ts | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/app/core/_components/tables/agents-status-table/agents-status-table.component.ts b/src/app/core/_components/tables/agents-status-table/agents-status-table.component.ts index ae6095d8..b6adfe6f 100644 --- a/src/app/core/_components/tables/agents-status-table/agents-status-table.component.ts +++ b/src/app/core/_components/tables/agents-status-table/agents-status-table.component.ts @@ -108,8 +108,9 @@ export class AgentsStatusTableComponent { id: AgentsStatusTableCol.WORKING_ON, dataKey: 'status', - render: (agent: Agent) => this.renderWorkingOn(agent), - isSortable: true + async: (agent: Agent) => this.renderWorkingOn(agent), + isSortable: false, + export: async (agent: Agent) => (await this.renderWorkingOn(agent)) + '' }, { id: AgentsStatusTableCol.ASSIGNED, @@ -257,18 +258,17 @@ export class AgentsStatusTableComponent } @Cacheable(['_id', 'speed']) - renderWorkingOn(agent: Agent): SafeHtml { + async renderWorkingOn(agent: Agent): Promise { let html = ''; - - if (agent.agentId > 0) { - html = ` `; - // html = ` - //
- // ${agent.taskId ? `` : ''} - // ${agent.speed ? `
at ${agent.speed | fileSize: false} H/s,
` : ''} - // ${agent.chunkId ? `
working on chunk ${agent.chunkId}
` : ''} - //
- // `; + const speed = await this.getSpeed(agent); + if (speed) { + html = ` +
+ +
at ${speed} H/s,
+
working on chunk ${agent.chunkId}
+
+ `; } return this.sanitize(html); From 8316eb46aef370ae9cd45f16b1e3cf2c70002847 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Wed, 31 Jan 2024 16:44:58 +0000 Subject: [PATCH 400/419] Hashes table --- .../_components/core-components.module.ts | 3 + .../hashes-table/hashes-table.component.html | 16 ++ .../hashes-table/hashes-table.component.ts | 155 ++++++++++++++++++ .../hashes-table/hashes-table.constants.ts | 17 ++ .../tables/ht-table/ht-table.models.ts | 1 + .../core/_datasources/hashes.datasource.ts | 67 ++++++++ .../hashlists/hashes/hashes.component.html | 30 +--- src/app/hashlists/hashes/hashes.component.ts | 61 +------ src/app/shared/utils/datetime.ts | 3 + .../hexconvertor/hexconvertor.component.html | 10 +- 10 files changed, 273 insertions(+), 90 deletions(-) create mode 100644 src/app/core/_components/tables/hashes-table/hashes-table.component.html create mode 100644 src/app/core/_components/tables/hashes-table/hashes-table.component.ts create mode 100644 src/app/core/_components/tables/hashes-table/hashes-table.constants.ts create mode 100644 src/app/core/_datasources/hashes.datasource.ts diff --git a/src/app/core/_components/core-components.module.ts b/src/app/core/_components/core-components.module.ts index 6a6cfb20..93310455 100644 --- a/src/app/core/_components/core-components.module.ts +++ b/src/app/core/_components/core-components.module.ts @@ -23,6 +23,7 @@ import { HTTableTypeDefaultComponent } from './tables/ht-table/type/default/ht-t import { HTTableTypeEditableComponent } from './tables/ht-table/type/editable/ht-table-type-editable.component'; import { HTTableTypeLinkComponent } from './tables/ht-table/type/link/ht-table-type-link.component'; import { HashlistsTableComponent } from './tables/hashlists-table/hashlists-table.component'; +import { HashesTableComponent } from './tables/hashes-table/hashes-table.component'; import { HashtypesTableComponent } from './tables/hashtypes-table/hashtypes-table.component'; import { HealthChecksTableComponent } from './tables/health-checks-table/health-checks-table.component'; import { HealthCheckAgentsTableComponent } from './tables/health-check-agents-table/health-check-agents-table.component'; @@ -84,6 +85,7 @@ import { TasksChunksTableComponent } from './tables/tasks-chunks-table/tasks-chu ChunksTableComponent, HashtypesTableComponent, HashlistsTableComponent, + HashesTableComponent, SuperHashlistsTableComponent, FilesAttackTableComponent, FilesTableComponent, @@ -149,6 +151,7 @@ import { TasksChunksTableComponent } from './tables/tasks-chunks-table/tasks-chu AgentsStatusTableComponent, ChunksTableComponent, HashlistsTableComponent, + HashesTableComponent, HashtypesTableComponent, SuperHashlistsTableComponent, FilesAttackTableComponent, diff --git a/src/app/core/_components/tables/hashes-table/hashes-table.component.html b/src/app/core/_components/tables/hashes-table/hashes-table.component.html new file mode 100644 index 00000000..5957856c --- /dev/null +++ b/src/app/core/_components/tables/hashes-table/hashes-table.component.html @@ -0,0 +1,16 @@ + diff --git a/src/app/core/_components/tables/hashes-table/hashes-table.component.ts b/src/app/core/_components/tables/hashes-table/hashes-table.component.ts new file mode 100644 index 00000000..5a8a4304 --- /dev/null +++ b/src/app/core/_components/tables/hashes-table/hashes-table.component.ts @@ -0,0 +1,155 @@ +/* eslint-disable @angular-eslint/component-selector */ +import { Component, Input, OnDestroy, OnInit } from '@angular/core'; +import { + HTTableColumn, + HTTableIcon, + HTTableRouterLink +} from '../ht-table/ht-table.models'; +import { + HashesTableCol, + HashesTableColColumnLabel +} from './hashes-table.constants'; + +import { ActionMenuEvent } from '../../menus/action-menu/action-menu.model'; +import { BaseTableComponent } from '../base-table/base-table.component'; +import { ExportMenuAction } from '../../menus/export-menu/export-menu.constants'; +import { Hashlist } from 'src/app/core/_models/hashlist.model'; +import { HashesDataSource } from 'src/app/core/_datasources/hashes.datasource'; +import { RowActionMenuAction } from '../../menus/row-action-menu/row-action-menu.constants'; +import { Hash } from 'src/app/core/_models/hash.model'; +import { formatUnixTimestamp } from 'src/app/shared/utils/datetime'; + +@Component({ + selector: 'hashes-table', + templateUrl: './hashes-table.component.html' +}) +export class HashesTableComponent + extends BaseTableComponent + implements OnInit, OnDestroy +{ + @Input() id: number; + @Input() dataType: string; + + tableColumns: HTTableColumn[] = []; + dataSource: HashesDataSource; + + ngOnInit(): void { + this.setColumnLabels(HashesTableColColumnLabel); + this.tableColumns = this.getColumns(); + this.dataSource = new HashesDataSource(this.cdr, this.gs, this.uiService); + this.dataSource.setColumns(this.tableColumns); + if (this.id) { + this.dataSource.setId(this.id); + this.dataSource.setDataType(this.dataType); + } + this.dataSource.loadAll(); + } + + ngOnDestroy(): void { + for (const sub of this.subscriptions) { + sub.unsubscribe(); + } + } + + filter(item: Hash, filterValue: string): boolean { + if ( + item.hash.toLowerCase().includes(filterValue) || + item.isCracked.toString().includes(filterValue) + ) { + return true; + } + + return false; + } + + getColumns(): HTTableColumn[] { + const tableColumns = [ + { + id: HashesTableCol.HASHES, + dataKey: 'hash', + isSortable: true, + export: async (hash: Hash) => hash.hash + '' + }, + { + id: HashesTableCol.PLAINTEXT, + dataKey: 'plaintext', + isSortable: true, + export: async (hash: Hash) => hash.plaintext + '' + }, + { + id: HashesTableCol.SALT, + dataKey: 'salt', + isSortable: true, + export: async (hash: Hash) => hash.salt + '' + }, + { + id: HashesTableCol.CRACK_POSITION, + dataKey: 'crackPos', + isSortable: true, + export: async (hash: Hash) => hash.crackPos + '' + }, + { + id: HashesTableCol.ISCRACKED, + dataKey: 'isCracked', + isSortable: true, + export: async (hash: Hash) => hash.isCracked + '' + }, + { + id: HashesTableCol.TIMECRACKED, + dataKey: 'timeCracked', + isSortable: true, + render: (hash: Hash) => + formatUnixTimestamp(hash.timeCracked, this.dateFormat), + export: async (hash: Hash) => + formatUnixTimestamp(hash.timeCracked, this.dateFormat) + '' + } + ]; + + return tableColumns; + } + + // --- Action functions --- + + exportActionClicked(event: ActionMenuEvent): void { + switch (event.menuItem.action) { + case ExportMenuAction.EXCEL: + this.exportService.toExcel( + 'hashtopolis-hashlists', + this.tableColumns, + event.data, + HashesTableColColumnLabel + ); + break; + case ExportMenuAction.CSV: + this.exportService.toCsv( + 'hashtopolis-hashlists', + this.tableColumns, + event.data, + HashesTableColColumnLabel + ); + break; + case ExportMenuAction.COPY: + this.exportService + .toClipboard( + this.tableColumns, + event.data, + HashesTableColColumnLabel + ) + .then(() => { + this.snackBar.open( + 'The selected rows are copied to the clipboard', + 'Close' + ); + }); + break; + } + } + + rowActionClicked(event: ActionMenuEvent): void { + switch (event.menuItem.action) { + case RowActionMenuAction.EDIT: + // this.rowActionEdit(event.data); + break; + } + } +} diff --git a/src/app/core/_components/tables/hashes-table/hashes-table.constants.ts b/src/app/core/_components/tables/hashes-table/hashes-table.constants.ts new file mode 100644 index 00000000..3131b1bf --- /dev/null +++ b/src/app/core/_components/tables/hashes-table/hashes-table.constants.ts @@ -0,0 +1,17 @@ +export enum HashesTableCol { + HASHES, + PLAINTEXT, + SALT, + CRACK_POSITION, + ISCRACKED, + TIMECRACKED +} + +export const HashesTableColColumnLabel = { + [HashesTableCol.HASHES]: 'Hashes', + [HashesTableCol.PLAINTEXT]: 'Plaintext', + [HashesTableCol.SALT]: 'Salt', + [HashesTableCol.CRACK_POSITION]: 'Crack position', + [HashesTableCol.ISCRACKED]: 'Cracked', + [HashesTableCol.TIMECRACKED]: 'Time cracked' +}; diff --git a/src/app/core/_components/tables/ht-table/ht-table.models.ts b/src/app/core/_components/tables/ht-table/ht-table.models.ts index e0b2cb55..bda566fa 100644 --- a/src/app/core/_components/tables/ht-table/ht-table.models.ts +++ b/src/app/core/_components/tables/ht-table/ht-table.models.ts @@ -5,6 +5,7 @@ export type DataType = | 'agents' | 'agents-status' | 'hashlists' + | 'hashes' | 'search-hash' | 'chunks' | 'hashtypes' diff --git a/src/app/core/_datasources/hashes.datasource.ts b/src/app/core/_datasources/hashes.datasource.ts new file mode 100644 index 00000000..eeb2f57a --- /dev/null +++ b/src/app/core/_datasources/hashes.datasource.ts @@ -0,0 +1,67 @@ +import { catchError, finalize, of } from 'rxjs'; + +import { BaseDataSource } from './base.datasource'; +import { HashListFormat } from '../_constants/hashlist.config'; +import { Hashlist } from '../_models/hashlist.model'; +import { ListResponseWrapper } from '../_models/response.model'; +import { SERV } from '../_services/main.config'; +import { Hash } from '../_models/hash.model'; + +export class HashesDataSource extends BaseDataSource { + private _id = 0; + private _dataType: string; + + setId(id: number): void { + this._id = id; + } + + setDataType(type: string): void { + this._dataType = type; + } + + loadAll(): void { + this.loading = true; + + const params: any = { + maxResults: this.pageSize, + startAt: this.currentPage * this.pageSize, + expand: 'hashlist,chunk' + }; + + // Add additional params based on _dataType + if (this._dataType === 'chunks') { + params.filter = 'chunkId=' + this._id; + } else if (this._dataType === 'tasks') { + // Add params for tasks if needed + } else if (this._dataType === 'hashlists') { + params.filter = 'hashlistId=' + this._id; + } + + const hashes$ = this.service.getAll(SERV.HASHES, params); + + this.subscriptions.push( + hashes$ + .pipe( + catchError(() => of([])), + finalize(() => (this.loading = false)) + ) + .subscribe((response: ListResponseWrapper) => { + const rows: Hash[] = response.values; + + console.log(rows); + + this.setPaginationConfig( + this.pageSize, + this.currentPage, + response.total + ); + this.setData(rows); + }) + ); + } + + reload(): void { + this.clearSelection(); + this.loadAll(); + } +} diff --git a/src/app/hashlists/hashes/hashes.component.html b/src/app/hashlists/hashes/hashes.component.html index efb847ad..326c2e93 100644 --- a/src/app/hashlists/hashes/hashes.component.html +++ b/src/app/hashlists/hashes/hashes.component.html @@ -9,8 +9,8 @@

{{ titleName }}

- -
+ + - - + - - - - - - - - - - - -
Matching hashes
- {{ mh | hashesf:displaying:filtering:crackPos }} - - faCopy - -
+
diff --git a/src/app/hashlists/hashes/hashes.component.ts b/src/app/hashlists/hashes/hashes.component.ts index c5216c42..672ce71e 100644 --- a/src/app/hashlists/hashes/hashes.component.ts +++ b/src/app/hashlists/hashes/hashes.component.ts @@ -1,6 +1,5 @@ import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core'; import { ActivatedRoute, Params, Router } from '@angular/router'; -import { faCopy } from '@fortawesome/free-solid-svg-icons'; import { DataTableDirective } from 'angular-datatables'; import { FormControl, FormGroup } from '@angular/forms'; import { Subject, Subscription } from 'rxjs'; @@ -67,7 +66,6 @@ export class HashesComponent implements OnInit, OnDestroy { */ ngOnInit(): void { this.loadHashes(); - this.setupTable(); } /** @@ -204,7 +202,7 @@ export class HashesComponent implements OnInit, OnDestroy { this.gs.getAll(SERV.HASHES, nwparams).subscribe((hashes: any) => { let res = hashes.values; - console.log(this.whichView); + // console.log(this.whichView); if (this.whichView === 'tasks') { res = res.filter((u) => u.chunk?.taskId == this.editedIndex); } @@ -272,61 +270,4 @@ export class HashesComponent implements OnInit, OnDestroy { return undefined; } - - /** - * Sets up the DataTable options and buttons. - * Customizes DataTable appearance and behavior. - */ - setupTable(): void { - // DataTables options - this.dtOptions = { - dom: 'Bfrtip', - scrollX: true, - pageLength: 25, - lengthMenu: [ - [10, 25, 50, 100, 250, -1], - [10, 25, 50, 100, 250, 'All'] - ], - searching: false, - buttons: { - dom: { - button: { - className: - 'dt-button buttons-collection btn btn-sm-dt btn-outline-gray-600-dt' - } - }, - buttons: [ - { - extend: 'collection', - text: 'Export', - buttons: [ - { - extend: 'print', - customize: function (win) { - $(win.document.title).css('font-size', '14pt'); - $(win.document.body).css('font-size', '10pt'); - $(win.document.body) - .find('table') - .addClass('compact') - .css('font-size', 'inherit'); - } - }, - { - extend: 'csvHtml5', - exportOptions: { modifier: { selected: true } }, - select: true, - customize: function (dt, csv) { - let data = ''; - for (let i = 0; i < dt.length; i++) { - data = 'Hashes Information\n\n' + dt; - } - return data; - } - } - ] - } - ] - } - }; - } } diff --git a/src/app/shared/utils/datetime.ts b/src/app/shared/utils/datetime.ts index 56822207..6d3821ec 100644 --- a/src/app/shared/utils/datetime.ts +++ b/src/app/shared/utils/datetime.ts @@ -37,6 +37,9 @@ export function formatUnixTimestamp( unixTimestamp: number, fmt: string ): string { + if (unixTimestamp === 0) { + return 'N/A'; + } //return moment.unix(unixTimestamp).format(fmt) const date = new Date(unixTimestamp * 1000); diff --git a/src/app/shared/utils/hexconvertor/hexconvertor.component.html b/src/app/shared/utils/hexconvertor/hexconvertor.component.html index bb7f432c..6abe7dd1 100644 --- a/src/app/shared/utils/hexconvertor/hexconvertor.component.html +++ b/src/app/shared/utils/hexconvertor/hexconvertor.component.html @@ -1,19 +1,19 @@ - + + HEX Value:
{{ hexVal }}
- + -
+ From 8c03d0c6da5cdbe9a869e91d5fd60c7fbbb38aa1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Wed, 31 Jan 2024 19:34:21 +0000 Subject: [PATCH 401/419] Temp column strech --- .../shared/dynamic-form-builder/dynamicform.component.html | 6 ++++-- src/app/shared/dynamic-form-builder/dynamicform.module.ts | 2 ++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/app/shared/dynamic-form-builder/dynamicform.component.html b/src/app/shared/dynamic-form-builder/dynamicform.component.html index 69711dc2..6bf2d001 100644 --- a/src/app/shared/dynamic-form-builder/dynamicform.component.html +++ b/src/app/shared/dynamic-form-builder/dynamicform.component.html @@ -1,7 +1,8 @@ - +
+
@@ -108,6 +109,7 @@

{{ field.label }}

[disabled]="!formIsValid()" > - +
+ diff --git a/src/app/shared/dynamic-form-builder/dynamicform.module.ts b/src/app/shared/dynamic-form-builder/dynamicform.module.ts index 83064fde..6b556f2d 100644 --- a/src/app/shared/dynamic-form-builder/dynamicform.module.ts +++ b/src/app/shared/dynamic-form-builder/dynamicform.module.ts @@ -26,6 +26,7 @@ import { MatChipsModule } from '@angular/material/chips'; import { MatAutocompleteModule } from '@angular/material/autocomplete'; import { MatOptionModule } from '@angular/material/core'; import { PipesModule } from '../pipes.module'; +import { CoreFormsModule } from '../forms.module'; @NgModule({ declarations: [DynamicFormComponent, FormConfigComponent, FormComponent], @@ -42,6 +43,7 @@ import { PipesModule } from '../pipes.module'; MatChipsModule, MatOptionModule, CommonModule, + CoreFormsModule, MatAutocompleteModule, FontAwesomeModule, FormsModule, From 18ce86deef14a41149389a3aba7e230b113dd43f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Wed, 31 Jan 2024 20:26:15 +0000 Subject: [PATCH 402/419] change color nav component --- src/styles/components/_button.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/styles/components/_button.scss b/src/styles/components/_button.scss index 044561e5..eb389fd4 100644 --- a/src/styles/components/_button.scss +++ b/src/styles/components/_button.scss @@ -252,8 +252,8 @@ mat-button-toggle-group { } .btn-toogle-select { - background-color: $accent-200; + background-color: $primary-300; border-top: 2px solid $color-grey-border; border-bottom: 2px solid $color-grey-border; - font-weight: bold; + // font-weight: bold; } From bf9dd0b388dd0c1fb2ae92b43ed766a20c1c1a18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Thu, 1 Feb 2024 14:14:11 +0000 Subject: [PATCH 403/419] Interceptor and table filters --- .../health-check-agents-table.component.ts | 5 ++++- .../tables/logs-table/logs-table.component.ts | 6 +++++- src/app/core/_interceptors/http-res.interceptor.ts | 12 +++++++++--- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/app/core/_components/tables/health-check-agents-table/health-check-agents-table.component.ts b/src/app/core/_components/tables/health-check-agents-table/health-check-agents-table.component.ts index 86f2f8ff..640fb33d 100644 --- a/src/app/core/_components/tables/health-check-agents-table/health-check-agents-table.component.ts +++ b/src/app/core/_components/tables/health-check-agents-table/health-check-agents-table.component.ts @@ -57,7 +57,10 @@ export class HealthCheckAgentsTableComponent } filter(item: HealthCheckAgent, filterValue: string): boolean { - if (item.agentName.toLowerCase().includes(filterValue)) { + if ( + item.agentName.toLowerCase().includes(filterValue) || + item.status.toString().includes(filterValue) + ) { return true; } diff --git a/src/app/core/_components/tables/logs-table/logs-table.component.ts b/src/app/core/_components/tables/logs-table/logs-table.component.ts index 1a502c1e..e2c1db1d 100644 --- a/src/app/core/_components/tables/logs-table/logs-table.component.ts +++ b/src/app/core/_components/tables/logs-table/logs-table.component.ts @@ -36,7 +36,11 @@ export class LogsTableComponent } filter(item: Log, filterValue: string): boolean { - if (item.message.toLowerCase().includes(filterValue)) { + if ( + item.message.toLowerCase().includes(filterValue) || + item.level.toLowerCase().includes(filterValue) || + item.issuer.toLowerCase().includes(filterValue) + ) { return true; } diff --git a/src/app/core/_interceptors/http-res.interceptor.ts b/src/app/core/_interceptors/http-res.interceptor.ts index cf7459c3..3bcbbff5 100644 --- a/src/app/core/_interceptors/http-res.interceptor.ts +++ b/src/app/core/_interceptors/http-res.interceptor.ts @@ -79,9 +79,15 @@ export class HttpResInterceptor implements HttpInterceptor { status = error?.status || 0; } - this.modalRef = this.dialog.open(ErrorModalComponent, { - data: { status, message: errmsg } - }); + if (errmsg === 'No token found!') { + // Redirect to the login page + this.router.navigate(['/login']); // Adjust the route accordingly + } else { + // Display error modal for other cases + this.modalRef = this.dialog.open(ErrorModalComponent, { + data: { status, message: errmsg } + }); + } return throwError(() => errmsg); }) From 677847270bf2fd93b3606eec2a42dbc8a2eb8297 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Thu, 1 Feb 2024 15:03:44 +0000 Subject: [PATCH 404/419] Request typo missing s, pagination was not working --- .../_datasources/access-groups.datasource.ts | 2 +- .../_datasources/agent-binaries.datasource.ts | 2 +- .../_datasources/agents-status.datasource.ts | 2 +- .../core/_datasources/agents.datasource.ts | 4 +-- .../core/_datasources/chunks.datasource.ts | 2 +- .../core/_datasources/crackers.datasource.ts | 2 +- .../core/_datasources/cracks.datasource.ts | 2 +- src/app/core/_datasources/files.datasource.ts | 2 +- .../core/_datasources/hashes.datasource.ts | 2 +- .../core/_datasources/hashlists.datasource.ts | 2 +- .../core/_datasources/hashtypes.datasource.ts | 2 +- src/app/core/_datasources/logs.datasource.ts | 3 +- .../_datasources/notifications.datasource.ts | 2 +- .../_datasources/permissions.datasource.ts | 2 +- .../preconfigured-tasks.datasource.ts | 2 +- .../_datasources/preprocessors.datasource.ts | 2 +- .../_datasources/search-hash.datasource.ts | 2 +- .../super-hashlists.datasource.ts | 2 +- .../_datasources/supertasks.datasource.ts | 2 +- .../_datasources/tasks-chunks.datasource.ts | 2 +- src/app/core/_datasources/tasks.datasource.ts | 2 +- src/app/core/_datasources/users.datasource.ts | 2 +- .../core/_datasources/vouchers.datasource.ts | 2 +- src/app/core/_models/notification.model.ts | 32 +++++++++---------- 24 files changed, 41 insertions(+), 40 deletions(-) diff --git a/src/app/core/_datasources/access-groups.datasource.ts b/src/app/core/_datasources/access-groups.datasource.ts index d74feb5b..9e1e02b1 100644 --- a/src/app/core/_datasources/access-groups.datasource.ts +++ b/src/app/core/_datasources/access-groups.datasource.ts @@ -12,7 +12,7 @@ export class AccessGroupsDataSource extends BaseDataSource { const startAt = this.currentPage * this.pageSize; const params = { maxResults: this.pageSize, - startAt: startAt + startsAt: startAt }; const accessGroups$ = this.service.getAll(SERV.ACCESS_GROUPS, params); diff --git a/src/app/core/_datasources/agent-binaries.datasource.ts b/src/app/core/_datasources/agent-binaries.datasource.ts index 2de27f63..e0202287 100644 --- a/src/app/core/_datasources/agent-binaries.datasource.ts +++ b/src/app/core/_datasources/agent-binaries.datasource.ts @@ -12,7 +12,7 @@ export class AgentBinariesDataSource extends BaseDataSource { const startAt = this.currentPage * this.pageSize; const params = { maxResults: this.pageSize, - startAt: startAt + startsAt: startAt }; const agentBinaries$ = this.service.getAll(SERV.AGENT_BINARY, params); diff --git a/src/app/core/_datasources/agents-status.datasource.ts b/src/app/core/_datasources/agents-status.datasource.ts index 44d68255..e4ec2479 100644 --- a/src/app/core/_datasources/agents-status.datasource.ts +++ b/src/app/core/_datasources/agents-status.datasource.ts @@ -17,7 +17,7 @@ export class AgentsStatusDataSource extends BaseDataSource { const startAt = this.currentPage * this.pageSize; const agentParams = { maxResults: this.pageSize, - startAt: startAt, + startsAt: startAt, expand: 'accessGroups,agentstats' }; diff --git a/src/app/core/_datasources/agents.datasource.ts b/src/app/core/_datasources/agents.datasource.ts index c96eba81..086c93a2 100644 --- a/src/app/core/_datasources/agents.datasource.ts +++ b/src/app/core/_datasources/agents.datasource.ts @@ -23,7 +23,7 @@ export class AgentsDataSource extends BaseDataSource { const startAt = this.currentPage * this.pageSize; const agentParams = { maxResults: this.pageSize, - startAt: startAt, + startsAt: startAt, expand: 'accessGroups' }; @@ -82,7 +82,7 @@ export class AgentsDataSource extends BaseDataSource { const startAt = this.currentPage * this.pageSize; const assignParams = { maxResults: this.pageSize, - startAt: startAt, + startsAt: startAt, expand: 'agent,task', filter: `taskId=${this._taskId}` }; diff --git a/src/app/core/_datasources/chunks.datasource.ts b/src/app/core/_datasources/chunks.datasource.ts index 194ec890..d56a9c04 100644 --- a/src/app/core/_datasources/chunks.datasource.ts +++ b/src/app/core/_datasources/chunks.datasource.ts @@ -13,7 +13,7 @@ export class ChunksDataSource extends BaseDataSource { const startAt = this.currentPage * this.pageSize; const chunkParams = { maxResults: this.pageSize, - startAt: startAt, + startsAt: startAt, expand: 'task' }; const agentParams = { maxResults: this.maxResults }; diff --git a/src/app/core/_datasources/crackers.datasource.ts b/src/app/core/_datasources/crackers.datasource.ts index 9580ad46..137304e2 100644 --- a/src/app/core/_datasources/crackers.datasource.ts +++ b/src/app/core/_datasources/crackers.datasource.ts @@ -12,7 +12,7 @@ export class CrackersDataSource extends BaseDataSource { const startAt = this.currentPage * this.pageSize; const params = { maxResults: this.pageSize, - startAt: startAt, + startsAt: startAt, expand: 'crackerVersions' }; diff --git a/src/app/core/_datasources/cracks.datasource.ts b/src/app/core/_datasources/cracks.datasource.ts index 74cc02cf..e79ce95c 100644 --- a/src/app/core/_datasources/cracks.datasource.ts +++ b/src/app/core/_datasources/cracks.datasource.ts @@ -12,7 +12,7 @@ export class CracksDataSource extends BaseDataSource { const startAt = this.currentPage * this.pageSize; const params = { maxResults: this.pageSize, - startAt: startAt, + startsAt: startAt, filter: 'isCracked=1', expand: 'hashlist,chunk' }; diff --git a/src/app/core/_datasources/files.datasource.ts b/src/app/core/_datasources/files.datasource.ts index 6d12f58c..6255b750 100644 --- a/src/app/core/_datasources/files.datasource.ts +++ b/src/app/core/_datasources/files.datasource.ts @@ -42,7 +42,7 @@ export class FilesDataSource extends BaseDataSource { } else { const params = { maxResults: this.pageSize, - startAt: startAt, + startsAt: startAt, expand: 'accessGroup', filter: `fileType=${this.fileType}` }; diff --git a/src/app/core/_datasources/hashes.datasource.ts b/src/app/core/_datasources/hashes.datasource.ts index eeb2f57a..06f704ac 100644 --- a/src/app/core/_datasources/hashes.datasource.ts +++ b/src/app/core/_datasources/hashes.datasource.ts @@ -24,7 +24,7 @@ export class HashesDataSource extends BaseDataSource { const params: any = { maxResults: this.pageSize, - startAt: this.currentPage * this.pageSize, + startsAt: this.currentPage * this.pageSize, expand: 'hashlist,chunk' }; diff --git a/src/app/core/_datasources/hashlists.datasource.ts b/src/app/core/_datasources/hashlists.datasource.ts index 5ecfa6df..53a2e9d1 100644 --- a/src/app/core/_datasources/hashlists.datasource.ts +++ b/src/app/core/_datasources/hashlists.datasource.ts @@ -19,7 +19,7 @@ export class HashlistsDataSource extends BaseDataSource { const startAt = this.currentPage * this.pageSize; const params = { maxResults: this.pageSize, - startAt: startAt, + startsAt: startAt, expand: 'hashType,accessGroup', filter: `isArchived=${this.isArchived}` }; diff --git a/src/app/core/_datasources/hashtypes.datasource.ts b/src/app/core/_datasources/hashtypes.datasource.ts index e270fd36..1ff0d1eb 100644 --- a/src/app/core/_datasources/hashtypes.datasource.ts +++ b/src/app/core/_datasources/hashtypes.datasource.ts @@ -12,7 +12,7 @@ export class HashtypesDataSource extends BaseDataSource { const startAt = this.currentPage * this.pageSize; const params = { maxResults: this.pageSize, - startAt: startAt + startsAt: startAt }; const hashtypes$ = this.service.getAll(SERV.HASHTYPES, params); diff --git a/src/app/core/_datasources/logs.datasource.ts b/src/app/core/_datasources/logs.datasource.ts index 599154b6..129ca321 100644 --- a/src/app/core/_datasources/logs.datasource.ts +++ b/src/app/core/_datasources/logs.datasource.ts @@ -10,9 +10,10 @@ export class LogsDataSource extends BaseDataSource { this.loading = true; const startAt = this.currentPage * this.pageSize; + const params = { maxResults: this.pageSize, - startAt: startAt + startsAt: startAt }; const logs$ = this.service.getAll(SERV.LOGS, params); diff --git a/src/app/core/_datasources/notifications.datasource.ts b/src/app/core/_datasources/notifications.datasource.ts index 163b8448..277139c2 100644 --- a/src/app/core/_datasources/notifications.datasource.ts +++ b/src/app/core/_datasources/notifications.datasource.ts @@ -12,7 +12,7 @@ export class NotificationsDataSource extends BaseDataSource { const startAt = this.currentPage * this.pageSize; const params = { maxResults: this.pageSize, - startAt: startAt + startsAt: startAt }; const notifications$ = this.service.getAll(SERV.NOTIFICATIONS, params); diff --git a/src/app/core/_datasources/permissions.datasource.ts b/src/app/core/_datasources/permissions.datasource.ts index b732e0d2..da30a483 100644 --- a/src/app/core/_datasources/permissions.datasource.ts +++ b/src/app/core/_datasources/permissions.datasource.ts @@ -12,7 +12,7 @@ export class PermissionsDataSource extends BaseDataSource const startAt = this.currentPage * this.pageSize; const params = { maxResults: this.pageSize, - startAt: startAt, + startsAt: startAt, expand: 'user' }; diff --git a/src/app/core/_datasources/preconfigured-tasks.datasource.ts b/src/app/core/_datasources/preconfigured-tasks.datasource.ts index 6b4689bf..100b9b86 100644 --- a/src/app/core/_datasources/preconfigured-tasks.datasource.ts +++ b/src/app/core/_datasources/preconfigured-tasks.datasource.ts @@ -28,7 +28,7 @@ export class PreTasksDataSource extends BaseDataSource< const startAt = this.currentPage * this.pageSize; const params = { maxResults: this.pageSize, - startAt: startAt, + startsAt: startAt, expand: 'pretaskFiles' }; diff --git a/src/app/core/_datasources/preprocessors.datasource.ts b/src/app/core/_datasources/preprocessors.datasource.ts index 33920d00..be407fe2 100644 --- a/src/app/core/_datasources/preprocessors.datasource.ts +++ b/src/app/core/_datasources/preprocessors.datasource.ts @@ -12,7 +12,7 @@ export class PreprocessorsDataSource extends BaseDataSource { const startAt = this.currentPage * this.pageSize; const params = { maxResults: this.pageSize, - startAt: startAt + startsAt: startAt }; const preprocessors$ = this.service.getAll(SERV.PREPROCESSORS, params); diff --git a/src/app/core/_datasources/search-hash.datasource.ts b/src/app/core/_datasources/search-hash.datasource.ts index 0f53c04b..cb647a5d 100644 --- a/src/app/core/_datasources/search-hash.datasource.ts +++ b/src/app/core/_datasources/search-hash.datasource.ts @@ -20,7 +20,7 @@ export class SearchHashDataSource extends BaseDataSource { for (let i = 0; i < this._search.length; i++) { const params = { maxResults: this.pageSize, - startAt: startAt, + startsAt: startAt, filter: `hash=${this._search[i]}` }; diff --git a/src/app/core/_datasources/super-hashlists.datasource.ts b/src/app/core/_datasources/super-hashlists.datasource.ts index 5d1a03ea..28bb5c24 100644 --- a/src/app/core/_datasources/super-hashlists.datasource.ts +++ b/src/app/core/_datasources/super-hashlists.datasource.ts @@ -19,7 +19,7 @@ export class SuperHashlistsDataSource extends BaseDataSource { const startAt = this.currentPage * this.pageSize; const params = { maxResults: this.pageSize, - startAt: startAt, + startsAt: startAt, expand: 'hashType,hashlists', filter: `format=${HashListFormat.SUPERHASHLIST}` }; diff --git a/src/app/core/_datasources/supertasks.datasource.ts b/src/app/core/_datasources/supertasks.datasource.ts index 6329fbe0..0da993ae 100644 --- a/src/app/core/_datasources/supertasks.datasource.ts +++ b/src/app/core/_datasources/supertasks.datasource.ts @@ -16,7 +16,7 @@ export class SuperTasksDataSource extends BaseDataSource< const startAt = this.currentPage * this.pageSize; const params = { maxResults: this.pageSize, - startAt: startAt, + startsAt: startAt, expand: 'pretasks' }; diff --git a/src/app/core/_datasources/tasks-chunks.datasource.ts b/src/app/core/_datasources/tasks-chunks.datasource.ts index f347781f..034d760b 100644 --- a/src/app/core/_datasources/tasks-chunks.datasource.ts +++ b/src/app/core/_datasources/tasks-chunks.datasource.ts @@ -24,7 +24,7 @@ export class TasksChunksDataSource extends BaseDataSource { const startAt = this.currentPage * this.pageSize; const chunkParams = { maxResults: this.pageSize, - startAt: startAt, + startsAt: startAt, expand: 'task', filter: 'taskId=' + this._taskId + '' }; diff --git a/src/app/core/_datasources/tasks.datasource.ts b/src/app/core/_datasources/tasks.datasource.ts index 8a7678d1..8e56a168 100644 --- a/src/app/core/_datasources/tasks.datasource.ts +++ b/src/app/core/_datasources/tasks.datasource.ts @@ -32,7 +32,7 @@ export class TasksDataSource extends BaseDataSource< : ''; const params = { maxResults: this.pageSize, - startAt: startAt, + startsAt: startAt, expand: 'accessGroup,tasks', filter: `isArchived=${this._isArchived}`, //${additionalFilter}` order: 'priority=ASC' diff --git a/src/app/core/_datasources/users.datasource.ts b/src/app/core/_datasources/users.datasource.ts index a83e85db..add97422 100644 --- a/src/app/core/_datasources/users.datasource.ts +++ b/src/app/core/_datasources/users.datasource.ts @@ -12,7 +12,7 @@ export class UsersDataSource extends BaseDataSource { const startAt = this.currentPage * this.pageSize; const params = { maxResults: this.pageSize, - startAt: startAt, + startsAt: startAt, expand: 'globalPermissionGroup' }; diff --git a/src/app/core/_datasources/vouchers.datasource.ts b/src/app/core/_datasources/vouchers.datasource.ts index bf9618e7..08b67301 100644 --- a/src/app/core/_datasources/vouchers.datasource.ts +++ b/src/app/core/_datasources/vouchers.datasource.ts @@ -12,7 +12,7 @@ export class VouchersDataSource extends BaseDataSource { const startAt = this.currentPage * this.pageSize; const params = { maxResults: this.pageSize, - startAt: startAt + startsAt: startAt }; const vouchers$ = this.service.getAll(SERV.VOUCHER, params); diff --git a/src/app/core/_models/notification.model.ts b/src/app/core/_models/notification.model.ts index bef179a8..485ba006 100644 --- a/src/app/core/_models/notification.model.ts +++ b/src/app/core/_models/notification.model.ts @@ -1,20 +1,20 @@ export interface NotificationListResponse { - _expandable: string - startAt: number - maxResults: number - total: number - isLast: boolean - values: Notification[] + _expandable: string; + startAt: number; + maxResults: number; + total: number; + isLast: boolean; + values: Notification[]; } export interface Notification { - _id: number - _self: string - action: string - isActive: boolean - notification: string - receiver: string - userId: number - notificationSettingId?: number - objectId?: number -} \ No newline at end of file + _id: number; + _self: string; + action: string; + isActive: boolean; + notification: string; + receiver: string; + userId: number; + notificationSettingId?: number; + objectId?: number; +} From 333ca8357c17dc320447481fe3e07d8e9f0a4134 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Fri, 2 Feb 2024 09:55:10 +0000 Subject: [PATCH 405/419] Keyspace calculation --- .../pretasks-table.component.ts | 99 ++++++++- .../edit-supertasks.component.html | 160 ++++++--------- .../edit-supertasks.component.ts | 193 ++++++++---------- 3 files changed, 243 insertions(+), 209 deletions(-) diff --git a/src/app/core/_components/tables/pretasks-table/pretasks-table.component.ts b/src/app/core/_components/tables/pretasks-table/pretasks-table.component.ts index 29b23ad8..ff40d039 100644 --- a/src/app/core/_components/tables/pretasks-table/pretasks-table.component.ts +++ b/src/app/core/_components/tables/pretasks-table/pretasks-table.component.ts @@ -26,6 +26,10 @@ import { SERV } from 'src/app/core/_services/main.config'; import { TableDialogComponent } from '../table-dialog/table-dialog.component'; import { formatFileSize } from 'src/app/shared/utils/util'; +declare let options: any; +declare let defaultOptions: any; +declare let parser: any; + @Component({ selector: 'pretasks-table', templateUrl: './pretasks-table.component.html' @@ -36,6 +40,9 @@ export class PretasksTableComponent { // Input property to specify an supertask ID for filtering pretasks. @Input() supertTaskId = 0; + // Estimate runtime attack + @Input() benchmarkA0 = 0; + @Input() benchmarkA3 = 0; tableColumns: HTTableColumn[] = []; dataSource: PreTasksDataSource; @@ -126,7 +133,7 @@ export class PretasksTableComponent if (this.supertTaskId !== 0) { tableColumns.push({ id: PretasksTableCol.ESTIMATED_KEYSPACE, - dataKey: 'priority', + dataKey: 'keyspaceSize', async: (pretask: Pretask) => this.renderEstimatedKeyspace(pretask), icons: undefined, isSortable: true, @@ -135,9 +142,11 @@ export class PretasksTableComponent }); tableColumns.push({ id: PretasksTableCol.ATTACK_RUNTIME, - dataKey: 'attackRuntime', + dataKey: 'keyspaceTime', icons: undefined, isSortable: true + // render: (pretask: Pretask) => + // this.renderKeyspaceTime(this.benchmarkA0, this.benchmarkA3, pretask) }); } @@ -341,6 +350,16 @@ export class PretasksTableComponent ); } + @Cacheable(['_id']) + async renderKeyspaceTime( + a0: number, + a3: number, + pretask: any + ): Promise { + const result = await this.calculateKeyspaceTime(a0, a3, pretask); + return result as unknown as SafeHtml; + } + /** * @todo Implement error handling. */ @@ -398,4 +417,80 @@ export class PretasksTableComponent this.router.navigate(links[0].routerLink); }); } + + calculateKeyspaceTime(a0: number, a3: number, pretask: Pretask): void { + { + if (a0 !== 0 && a3 !== 0) { + let totalSecondsSupertask = 0; + let unknown_runtime_included = 0; + const benchmarka0 = a0; + const benchmarka3 = a3; + + // Iterate over each task in the supertask + $('.taskInSuper').each(function (index) { + // Extract keyspace size from the table cell + const keyspace_size = $(this).find('td:nth-child(4)').text(); + let seconds = null; + let runtime = null; + + // Set default options for the attack + options = defaultOptions; + options.ruleFiles = []; + options.posArgs = []; + options.unrecognizedFlag = []; + + // Check if keyspace size is available + if (keyspace_size === null || !keyspace_size) { + unknown_runtime_included = 1; + runtime = 'Unknown'; + } else if (options.attackType === 3) { + // Calculate seconds based on benchmarka3 for attackType 3 + seconds = Math.floor(Number(keyspace_size) / Number(benchmarka3)); + } else if (options.attackType === 0) { + // Calculate seconds based on benchmarka0 for attackType 0 + seconds = Math.floor(Number(keyspace_size) / Number(benchmarka0)); + } + + // Convert seconds to human-readable runtime format + if (Number.isInteger(seconds)) { + totalSecondsSupertask += seconds; + const days = Math.floor(seconds / (3600 * 24)); + seconds -= days * 3600 * 24; + const hrs = Math.floor(seconds / 3600); + seconds -= hrs * 3600; + const mins = Math.floor(seconds / 60); + seconds -= mins * 60; + + runtime = days + 'd, ' + hrs + 'h, ' + mins + 'm, ' + seconds + 's'; + } else { + unknown_runtime_included = 1; + runtime = 'Unknown'; + } + + // Update the HTML content with the calculated runtime + $(this).find('td:nth-child(5)').html(runtime); + }); + + // Reduce total runtime to a human-readable format + let seconds = totalSecondsSupertask; + const days = Math.floor(seconds / (3600 * 24)); + seconds -= days * 3600 * 24; + const hrs = Math.floor(seconds / 3600); + seconds -= hrs * 3600; + const mins = Math.floor(seconds / 60); + seconds -= mins * 60; + + let totalRuntimeSupertask = + days + 'd, ' + hrs + 'h, ' + mins + 'm, ' + seconds + 's'; + + // Append additional information if unknown runtime is included + if (unknown_runtime_included === 1) { + totalRuntimeSupertask += ', plus additional unknown runtime'; + } + + // Update the HTML content with the total runtime of the supertask + $('.runtimeOfSupertask').html(totalRuntimeSupertask); + } + } + } } diff --git a/src/app/tasks/edit-supertasks/edit-supertasks.component.html b/src/app/tasks/edit-supertasks/edit-supertasks.component.html index 99f457b1..a1afc9f6 100644 --- a/src/app/tasks/edit-supertasks/edit-supertasks.component.html +++ b/src/app/tasks/edit-supertasks/edit-supertasks.component.html @@ -1,109 +1,77 @@ -
-
- - -
+ + + -
- - -
+ + - - + + - -
-
Add Pretask
-
+
+
+
Add Pretask
+
-
- -
+
+ +
- -
-
-
-
- -
Estimate Time - -
-
+ +
+
+ + + + + + Attack Runtime Estimation + + +

+ The benchmark number to enter below is the total amount of hashes per second per attack type of all the agents combined that will run the supertask. +

+

+ For example, if you have two agents running -a0 attacks and three agents running -a3 attacks, and each -a0 agent processes 100 hashes per second, and each -a3 agent processes 150 hashes per second, you would enter 200 for "Benchmark for -a0 attacks" and 450 for "Benchmark for -a3 attacks". +

+

+ Note: For this tool to work correctly, ensure that the columns "Estimated Keyspace" and "Runtime Attack" are displayed in your application. +

+
+
-
- - -
+
+ + +
- + -
-   - - - Estimated total runtime of supertask: - - - - -
-
- -
-
+
+   + + + Estimated total runtime of supertask: + + + + +
+ + + - - -

Pretasks

- - - - - - - - - - - - - - - - - - - - - -
IDNameAttack CommandEstimated KeyspaceAttack runtimeAction
- {{ p.pretaskId }} - - - {{ p.taskName | shortenString:35 }} - - {{ p.attackCmd }}{{ p.pretaskFiles | keyspace:'lineCount':p.attackCmd:false }} - - - -
-
+ + diff --git a/src/app/tasks/edit-supertasks/edit-supertasks.component.ts b/src/app/tasks/edit-supertasks/edit-supertasks.component.ts index 4cbcf452..31d99298 100644 --- a/src/app/tasks/edit-supertasks/edit-supertasks.component.ts +++ b/src/app/tasks/edit-supertasks/edit-supertasks.component.ts @@ -1,21 +1,14 @@ -import { FormArray, FormControl, FormGroup } from '@angular/forms'; import { ActivatedRoute, Params, Router } from '@angular/router'; -import { Component, OnInit, ViewChild } from '@angular/core'; -import { DataTableDirective } from 'angular-datatables'; - -import { Subject } from 'rxjs'; - -import { SUPER_TASK_FIELD_MAPPING } from 'src/app/core/_constants/select.config'; import { AlertService } from 'src/app/core/_services/shared/alert.service'; +import { AutoTitleService } from 'src/app/core/_services/shared/autotitle.service'; +import { ChangeDetectorRef, Component, OnInit } from '@angular/core'; +import { FormControl, FormGroup } from '@angular/forms'; import { GlobalService } from 'src/app/core/_services/main.service'; -import { environment } from './../../../environments/environment'; -import { PageTitle } from 'src/app/core/_decorators/autotitle'; -import { SERV } from '../../core/_services/main.config'; import { ListResponseWrapper } from 'src/app/core/_models/response.model'; +import { SERV } from '../../core/_services/main.config'; +import { SUPER_TASK_FIELD_MAPPING } from 'src/app/core/_constants/select.config'; import { transformSelectOptions } from 'src/app/shared/utils/forms'; import { UnsubscribeService } from 'src/app/core/_services/unsubscribe.service'; -import { ChangeDetectorRef } from '@angular/core'; -import { AutoTitleService } from 'src/app/core/_services/shared/autotitle.service'; declare let options: any; declare let defaultOptions: any; @@ -45,8 +38,6 @@ export class EditSupertasksComponent implements OnInit { // Edit editedSTIndex: number; editedST: any; // Change to Model - pretasks: any = []; - pretasksFiles: any = []; assignPretasks: any; constructor( @@ -77,7 +68,6 @@ export class EditSupertasksComponent implements OnInit { */ ngOnInit(): void { this.loadData(); - this.loadTableData(); } /** @@ -92,15 +82,18 @@ export class EditSupertasksComponent implements OnInit { * Builds the form for creating a new SuperHashlist. */ buildForm(): void { + // Form details this.viewForm = new FormGroup({ supertaskId: new FormControl({ value: '', disabled: true }), supertaskName: new FormControl({ value: '', disabled: true }) }); + // Form add pretasks this.updateForm = new FormGroup({ pretasks: new FormControl('') }); + // Form calculate benchmark this.etForm = new FormGroup({ benchmarka0: new FormControl(null || 0), benchmarka3: new FormControl(null || 0) @@ -211,62 +204,6 @@ export class EditSupertasksComponent implements OnInit { }); } - //To delete - @ViewChild(DataTableDirective) - dtElement: DataTableDirective; - - dtTrigger: Subject = new Subject(); - dtOptions: any = {}; - - loadTableData() { - const matchObjectFiles = []; - this.gs - .getAll(SERV.SUPER_TASKS, { - expand: 'pretasks', - filter: 'supertaskId=' + this.editedSTIndex + '' - }) - .subscribe((result) => { - this.gs - .getAll(SERV.PRETASKS, { expand: 'pretaskFiles' }) - .subscribe((pretasks: any) => { - this.pretasks = result.values.map((mainObject) => { - for ( - let i = 0; - i < Object.keys(result.values[0].pretasks).length; - i++ - ) { - matchObjectFiles.push( - pretasks.values.find( - (element: any) => - element?.pretaskId === mainObject.pretasks[i]?.pretaskId - ) - ); - } - return { ...mainObject, matchObjectFiles }; - }); - this.dtTrigger.next(void 0); - }); - }); - - this.dtOptions[0] = { - dom: 'Bfrtip', - scrollY: '700px', - scrollCollapse: true, - paging: false, - autoWidth: false, - searching: false, - buttons: { - dom: { - button: { - className: - 'dt-button buttons-collection btn btn-sm-dt btn-outline-gray-600-dt' - } - }, - buttons: [] - } - }; - } - onRefresh() { window.location.reload(); } @@ -286,48 +223,82 @@ export class EditSupertasksComponent implements OnInit { const benchmarka3 = this.etForm.value.benchmarka3; // Iterate over each task in the supertask - $('.taskInSuper').each(function (index) { - // Extract keyspace size from the table cell - const keyspace_size = $(this).find('td:nth-child(4)').text(); - let seconds = null; - let runtime = null; - - // Set default options for the attack - options = defaultOptions; - options.ruleFiles = []; - options.posArgs = []; - options.unrecognizedFlag = []; - - // Check if keyspace size is available - if (keyspace_size === null || !keyspace_size) { - unknown_runtime_included = 1; - runtime = 'Unknown'; - } else if (options.attackType === 3) { - // Calculate seconds based on benchmarka3 for attackType 3 - seconds = Math.floor(Number(keyspace_size) / Number(benchmarka3)); - } else if (options.attackType === 0) { - // Calculate seconds based on benchmarka0 for attackType 0 - seconds = Math.floor(Number(keyspace_size) / Number(benchmarka0)); - } - // Convert seconds to human-readable runtime format - if (Number.isInteger(seconds)) { - totalSecondsSupertask += seconds; - const days = Math.floor(seconds / (3600 * 24)); - seconds -= days * 3600 * 24; - const hrs = Math.floor(seconds / 3600); - seconds -= hrs * 3600; - const mins = Math.floor(seconds / 60); - seconds -= mins * 60; - - runtime = days + 'd, ' + hrs + 'h, ' + mins + 'm, ' + seconds + 's'; - } else { - unknown_runtime_included = 1; - runtime = 'Unknown'; - } + $('.hashtopolis-table').each((index, table) => { + // Find the header row and get the column index with the label "Attack Runtime" + const headerRow = $(table).find('tr').first(); + const attackEstimatedKeyspaceColumnIndex = headerRow + .find('th .mat-sort-header-content:contains("Estimated Keyspace")') + .closest('th') + .index(); + + // Get the number of rows in the table + const numRows = $(table).find('tr').length; + + // Iterate through each row + for (let i = 0; i < numRows; i++) { + // Extract the value from the "Attack Runtime" column + const keyspace_size_raw = $(table) + .find('tr') + .eq(i) + .find('td') + .eq(attackEstimatedKeyspaceColumnIndex) + .text(); + + // Extract keyspace size from the table cell + let seconds = null; + let runtime = null; + + // Remove special characters and convert to a valid number + const keyspace_size = parseFloat( + keyspace_size_raw.replace(/[^0-9.-]/g, '') + ); + + // Set default options for the attack + options = defaultOptions; + options.ruleFiles = []; + options.posArgs = []; + options.unrecognizedFlag = []; + + // Check if keyspace size is available + if (keyspace_size === null || !keyspace_size) { + unknown_runtime_included = 1; + runtime = 'Unknown'; + } else if (options.attackType === 3) { + // Calculate seconds based on benchmarka3 for attackType 3 + seconds = Math.floor(Number(keyspace_size) / Number(benchmarka3)); + } else if (options.attackType === 0) { + // Calculate seconds based on benchmarka0 for attackType 0 + seconds = Math.floor(Number(keyspace_size) / Number(benchmarka0)); + } - // Update the HTML content with the calculated runtime - $(this).find('td:nth-child(5)').html(runtime); + // Convert seconds to human-readable runtime format + if (Number.isInteger(seconds)) { + totalSecondsSupertask += seconds; + const days = Math.floor(seconds / (3600 * 24)); + seconds -= days * 3600 * 24; + const hrs = Math.floor(seconds / 3600); + seconds -= hrs * 3600; + const mins = Math.floor(seconds / 60); + seconds -= mins * 60; + + runtime = days + 'd, ' + hrs + 'h, ' + mins + 'm, ' + seconds + 's'; + } else { + unknown_runtime_included = 1; + runtime = 'Unknown'; + } + // Update the "Attack Runtime" column with the calculated runtime + const attackRuntimeColumnIndex = headerRow + .find('th .mat-sort-header-content:contains("Attack Runtime")') + .closest('th') + .index(); + const attackRuntimeCell = $(table) + .find('tr') + .eq(i) + .find('td') + .eq(attackRuntimeColumnIndex); + attackRuntimeCell.html(runtime); + } }); // Reduce total runtime to a human-readable format From 318cf36a516d78282d852cb7fa430628f8a0d6ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Fri, 2 Feb 2024 14:04:43 +0000 Subject: [PATCH 406/419] Refresh button assign agents --- .../_interceptors/http-res.interceptor.ts | 2 +- .../edit-tasks/edit-tasks.component.html | 9 +++++++- .../tasks/edit-tasks/edit-tasks.component.ts | 23 +++++++++++-------- 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/src/app/core/_interceptors/http-res.interceptor.ts b/src/app/core/_interceptors/http-res.interceptor.ts index 3bcbbff5..77bebb36 100644 --- a/src/app/core/_interceptors/http-res.interceptor.ts +++ b/src/app/core/_interceptors/http-res.interceptor.ts @@ -78,7 +78,7 @@ export class HttpResInterceptor implements HttpInterceptor { errmsg = error.error.exception[0].message; status = error?.status || 0; } - + console.log(errmsg); if (errmsg === 'No token found!') { // Redirect to the login page this.router.navigate(['/login']); // Adjust the route accordingly diff --git a/src/app/tasks/edit-tasks/edit-tasks.component.html b/src/app/tasks/edit-tasks/edit-tasks.component.html index ef829a75..16bebf4a 100644 --- a/src/app/tasks/edit-tasks/edit-tasks.component.html +++ b/src/app/tasks/edit-tasks/edit-tasks.component.html @@ -114,7 +114,7 @@
-

+

Assign Agents @@ -124,9 +124,16 @@ No agents available {{ availAgents.length }} agent(s) available + + + + +
{ - this.snackBar.open('Agent assigned!', 'Close'); - this.table.reload(); - }); + this.gs + .create(SERV.AGENT_ASSIGN, payload) + .pipe( + finalize(() => { + this.assingAgentInit(); + this.table.reload(); + }) + ) + .subscribe(() => { + this.createForm.reset(); + this.snackBar.open('Agent assigned!', 'Close'); + }); } } From c0c78513e4f7d4c9f2ad2fcc474ae9b0ff471a1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Fri, 2 Feb 2024 14:58:09 +0000 Subject: [PATCH 407/419] Add agent name in modal and in agent status icon show active/inactive --- .../agent-status-modal.component.html | 3 +++ .../agent-status-modal.component.ts | 18 +++++++++++--- .../agent-status/agent-status.component.html | 24 ++++--------------- 3 files changed, 22 insertions(+), 23 deletions(-) diff --git a/src/app/agents/agent-status/agent-status-modal/agent-status-modal.component.html b/src/app/agents/agent-status/agent-status-modal/agent-status-modal.component.html index a484779b..85825a77 100644 --- a/src/app/agents/agent-status/agent-status-modal/agent-status-modal.component.html +++ b/src/app/agents/agent-status/agent-status-modal/agent-status-modal.component.html @@ -4,6 +4,9 @@

+

+ {{data.form.agentName}} +

diff --git a/src/app/agents/agent-status/agent-status-modal/agent-status-modal.component.ts b/src/app/agents/agent-status/agent-status-modal/agent-status-modal.component.ts index 299fdbcb..9adce9d4 100644 --- a/src/app/agents/agent-status/agent-status-modal/agent-status-modal.component.ts +++ b/src/app/agents/agent-status/agent-status-modal/agent-status-modal.component.ts @@ -10,9 +10,9 @@ export class AgentStatusModalComponent implements OnInit { @Input() title = ''; @Input() icon = ''; @Input() content = ''; - @Input() thresholdType: string; // 'temp', 'device' or 'util' + @Input() thresholdType: string; // Options are 'temp', 'device' or 'util' - // Threshold values from the database + // Threshold values from the config database threshold1: string; threshold2: string; unitLabel: string; @@ -30,7 +30,6 @@ export class AgentStatusModalComponent implements OnInit { ) {} ngOnInit(): void { - console.log('Data received in modal:', this.data); this.title = this.data.title; this.icon = this.data.icon; this.content = this.data.content; @@ -43,6 +42,10 @@ export class AgentStatusModalComponent implements OnInit { this.dialogRef.close(); } + /** + * Fetches and sets threshold values based on the threshold type. + * @returns {void} + */ private fetchThresholds(): void { if (this.thresholdType === 'temp') { this.threshold1 = this.getThresholdValue('agentTempThreshold1'); @@ -56,10 +59,19 @@ export class AgentStatusModalComponent implements OnInit { } } + /** + * Retrieves the threshold value for a given setting key from the UI settings. + * @param {string} settingKey - The key for the UI setting. + * @returns {string} The threshold value associated with the specified setting key. + */ private getThresholdValue(settingKey: string): string { return this.uiService.getUIsettings(settingKey).value; } + /** + * Generates text labels and settings based on the threshold type. + * @returns {void} + */ private generateText(): void { if (this.thresholdType === 'temp') { this.statusNumber = 2; diff --git a/src/app/agents/agent-status/agent-status.component.html b/src/app/agents/agent-status/agent-status.component.html index 104abc0d..015b0ec4 100644 --- a/src/app/agents/agent-status/agent-status.component.html +++ b/src/app/agents/agent-status/agent-status.component.html @@ -25,6 +25,8 @@

+ check_circle + highlight_off {{ a.agentName | shortenString:15 }}
@@ -71,6 +73,8 @@
+ check_circle + highlight_off {{ gname.k | shortenString:15 }}
@@ -113,27 +117,7 @@
- - - - - From d2f34cd1327282e8f1f7433add24ae0d59b8e2e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Fri, 2 Feb 2024 15:15:30 +0000 Subject: [PATCH 408/419] Edit hashlists layout gap --- src/app/hashlists/edit-hashlist/edit-hashlist.component.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/hashlists/edit-hashlist/edit-hashlist.component.html b/src/app/hashlists/edit-hashlist/edit-hashlist.component.html index 7dc39158..c28fd18d 100644 --- a/src/app/hashlists/edit-hashlist/edit-hashlist.component.html +++ b/src/app/hashlists/edit-hashlist/edit-hashlist.component.html @@ -2,11 +2,11 @@
-
+ -
+
From 2d85a540d7dcb906ff0d1c6eb1699332ec5e18b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Thu, 8 Feb 2024 16:56:09 +0000 Subject: [PATCH 409/419] Sorting saved --- .../tables/ht-table/ht-table.component.html | 5 +- .../tables/ht-table/ht-table.component.ts | 86 +- .../_datasources/access-groups.datasource.ts | 10 +- .../_datasources/agent-binaries.datasource.ts | 10 +- .../_datasources/agents-status.datasource.ts | 11 +- .../core/_datasources/agents.datasource.ts | 13 +- src/app/core/_datasources/base.datasource.ts | 14 + .../core/_datasources/chunks.datasource.ts | 13 +- .../core/_datasources/crackers.datasource.ts | 10 +- .../core/_datasources/cracks.datasource.ts | 10 +- src/app/core/_datasources/files.datasource.ts | 8 +- .../core/_datasources/hashes.datasource.ts | 17 +- .../core/_datasources/hashlists.datasource.ts | 10 +- .../core/_datasources/hashtypes.datasource.ts | 10 +- .../_datasources/health-checks.datasource.ts | 21 +- src/app/core/_datasources/logs.datasource.ts | 15 +- .../_datasources/notifications.datasource.ts | 10 +- .../_datasources/permissions.datasource.ts | 10 +- .../preconfigured-tasks.datasource.ts | 10 +- .../_datasources/preprocessors.datasource.ts | 10 +- .../_datasources/search-hash.datasource.ts | 9 +- .../super-hashlists.datasource.ts | 10 +- .../_datasources/supertasks.datasource.ts | 10 +- .../_datasources/tasks-chunks.datasource.ts | 11 +- src/app/core/_datasources/tasks.datasource.ts | 15 +- src/app/core/_datasources/users.datasource.ts | 10 +- .../core/_datasources/vouchers.datasource.ts | 10 +- src/app/core/_models/config-ui.model.ts | 781 +++++++++++++----- src/app/core/_models/request-params.model.ts | 7 + src/app/core/_models/settings.model.ts | 26 +- .../storage/local-storage.service.ts | 15 +- src/app/shared/utils/config.ts | 70 +- 32 files changed, 984 insertions(+), 293 deletions(-) create mode 100644 src/app/core/_models/request-params.model.ts 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 78b84df5..fd50d693 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 @@ -142,9 +142,7 @@ mat-header-cell *matHeaderCellDef [mat-sort-header]="tableColumn.id + ''" - [arrowPosition]=" - tableColumn.position === 'right' ? 'before' : 'after' - " + (click)="onColumnHeaderClick(tableColumn)" > {{ columnLabels[tableColumn.id] }} @@ -232,6 +230,7 @@ [length]="dataSource.totalItems" [pageSize]="dataSource.pageSize" [pageSizeOptions]="paginationSizes" + [pageIndex]="dataSource.currentPage" (page)="onPageChange($event)" > diff --git a/src/app/core/_components/tables/ht-table/ht-table.component.ts b/src/app/core/_components/tables/ht-table/ht-table.component.ts index dbe00bcc..ec89cac9 100644 --- a/src/app/core/_components/tables/ht-table/ht-table.component.ts +++ b/src/app/core/_components/tables/ht-table/ht-table.component.ts @@ -147,11 +147,14 @@ export class HTTableComponent implements OnInit, AfterViewInit { @Input() hasBulkActions = true; /** Pagination sizes to choose from. */ - @Input() paginationSizes: number[] = [3, 25, 50, 100, 250]; + @Input() paginationSizes: number[] = [5, 25, 50, 100, 250, 500, 1000, 5000]; /** Default page size for pagination. */ @Input() defaultPageSize = this.paginationSizes[1]; + /** Default start page. */ + @Input() defaultStartPage = 0; + /** Event emitter for when the user triggers a row action */ @Output() rowActionClicked: EventEmitter> = new EventEmitter>(); @@ -175,6 +178,8 @@ export class HTTableComponent implements OnInit, AfterViewInit { /** Fetches user customizations */ private uiSettings: UISettingsUtilityClass; + private sortingColumn; + @ViewChild('bulkMenu') bulkMenu: BulkActionMenuComponent; constructor( @@ -186,18 +191,37 @@ export class HTTableComponent implements OnInit, AfterViewInit { ngOnInit(): void { this.uiSettings = new UISettingsUtilityClass(this.storage); const displayedColumns = this.uiSettings.getTableSettings(this.name); - if (displayedColumns) { + this.defaultPageSize = + this.uiSettings['uiConfig']['tableSettings'][this.name]['page']; + this.defaultStartPage = + this.uiSettings['uiConfig']['tableSettings'][this.name]['start']; + + if (Array.isArray(displayedColumns)) { this.setDisplayedColumns(displayedColumns); } else { - this.setDisplayedColumns(uiConfigDefault.tableSettings[this.name]); + // Handle the case when the retrieved value is neither an array nor a TableConfig + console.error(`Unexpected table configuration for key: ${this.name}`); } } ngAfterViewInit(): void { // Configure paginator and sorting this.dataSource.paginator = this.matPaginator; - this.dataSource.sort = this.matSort; + // Get saved Pagesize from lcoal storage, otherwise use default value this.dataSource.pageSize = this.defaultPageSize; + // Get saved start page + this.dataSource.currentPage = this.defaultStartPage; + // Sorted header arrow and sorting initialization + this.dataSource.sortingColumn = + this.uiSettings['uiConfig']['tableSettings'][this.name]['order']; + if (this.dataSource.sortingColumn) { + this.matSort.sort({ + id: this.dataSource.sortingColumn.id, + start: this.dataSource.sortingColumn.direction, + disableClear: false + }); + } + this.dataSource.sort = this.matSort; this.matSort.sortChange.subscribe(() => { this.dataSource.sortData(); }); @@ -221,12 +245,32 @@ export class HTTableComponent implements OnInit, AfterViewInit { dialogRef.afterClosed().subscribe((selectedColumns: number[]) => { if (selectedColumns) { this.setDisplayedColumns(selectedColumns); - this.uiSettings.updateTableSettings(this.name, selectedColumns); + this.uiSettings.updateTableSettings(this.name, { + columns: selectedColumns + }); this.cd.detectChanges(); } }); } + /** + * Handles the click event when a column header is clicked for sorting. + * Updates the sorting order in the UI settings based on the clicked column. + * + * @param {any} column - The column that was clicked for sorting. + * @returns {void} + */ + onColumnHeaderClick(column: any): void { + const sorting = { + ...column, + direction: this.dataSource.sort['_direction'] + }; + this.dataSource.sortingColumn = sorting; + this.uiSettings.updateTableSettings(this.name, { + order: sorting + }); + } + private filterKeys( original: { [key: string]: string }, include: string[] @@ -275,6 +319,30 @@ export class HTTableComponent implements OnInit, AfterViewInit { } } + /** + * Determines the position of the sorting arrow for the specified column. + * If the column is currently sorted, returns the position based on the saved sorting order. + * If the column is not sorted, returns null. + * + * @param {any} tableColumn - The column to determine the sorting arrow position for. + * @returns {'before' | 'after' | null} The position of the sorting arrow. + * - 'before': The arrow should be displayed before the column label. + * - 'after': The arrow should be displayed after the column label. + * - null: No sorting arrow should be displayed for the column. + */ + setColumnSorting(tableColumn: any): 'before' | 'after' { + if ( + this.dataSource.sortingColumn && + this.dataSource.sortingColumn.id === tableColumn.id + ) { + return this.dataSource.sortingColumn.direction === 'asc' + ? 'before' + : 'after'; + } else { + return null; // or set a default arrow position if no saved sorting + } + } + /** * Applies a filter to the table based on user input. */ @@ -371,11 +439,19 @@ export class HTTableComponent implements OnInit, AfterViewInit { * @param event - The `PageEvent` object containing information about the new page configuration. */ onPageChange(event: PageEvent): void { + this.uiSettings.updateTableSettings(this.name, { + start: event.pageIndex, // Store the new page index + page: event.pageSize // Store the new page size + }); + + // Update pagination configuration in the data source this.dataSource.setPaginationConfig( event.pageSize, event.pageIndex, this.dataSource.totalItems ); + + // Reload data with updated pagination settings this.dataSource.reload(); } diff --git a/src/app/core/_datasources/access-groups.datasource.ts b/src/app/core/_datasources/access-groups.datasource.ts index 9e1e02b1..055399c7 100644 --- a/src/app/core/_datasources/access-groups.datasource.ts +++ b/src/app/core/_datasources/access-groups.datasource.ts @@ -3,6 +3,7 @@ import { catchError, finalize, of } from 'rxjs'; import { AccessGroup } from '../_models/access-group.model'; import { BaseDataSource } from './base.datasource'; import { ListResponseWrapper } from '../_models/response.model'; +import { RequestParams } from '../_models/request-params.model'; import { SERV } from '../_services/main.config'; export class AccessGroupsDataSource extends BaseDataSource { @@ -10,11 +11,18 @@ export class AccessGroupsDataSource extends BaseDataSource { this.loading = true; const startAt = this.currentPage * this.pageSize; - const params = { + const sorting = this.sortingColumn; + + const params: RequestParams = { maxResults: this.pageSize, startsAt: startAt }; + if (sorting.dataKey && sorting.isSortable) { + const order = this.buildSortingParams(sorting); + params.ordering = order; + } + const accessGroups$ = this.service.getAll(SERV.ACCESS_GROUPS, params); this.subscriptions.push( diff --git a/src/app/core/_datasources/agent-binaries.datasource.ts b/src/app/core/_datasources/agent-binaries.datasource.ts index e0202287..3058aa42 100644 --- a/src/app/core/_datasources/agent-binaries.datasource.ts +++ b/src/app/core/_datasources/agent-binaries.datasource.ts @@ -3,6 +3,7 @@ import { catchError, finalize, of } from 'rxjs'; import { AgentBinary } from '../_models/agent-binary.model'; import { BaseDataSource } from './base.datasource'; import { ListResponseWrapper } from '../_models/response.model'; +import { RequestParams } from '../_models/request-params.model'; import { SERV } from '../_services/main.config'; export class AgentBinariesDataSource extends BaseDataSource { @@ -10,11 +11,18 @@ export class AgentBinariesDataSource extends BaseDataSource { this.loading = true; const startAt = this.currentPage * this.pageSize; - const params = { + const sorting = this.sortingColumn; + + const params: RequestParams = { maxResults: this.pageSize, startsAt: startAt }; + if (sorting.dataKey && sorting.isSortable) { + const order = this.buildSortingParams(sorting); + params.ordering = order; + } + const agentBinaries$ = this.service.getAll(SERV.AGENT_BINARY, params); this.subscriptions.push( diff --git a/src/app/core/_datasources/agents-status.datasource.ts b/src/app/core/_datasources/agents-status.datasource.ts index e4ec2479..f5cd0336 100644 --- a/src/app/core/_datasources/agents-status.datasource.ts +++ b/src/app/core/_datasources/agents-status.datasource.ts @@ -5,22 +5,29 @@ import { Agent } from '../_models/agent.model'; import { AgentAssignment } from '../_models/agent-assignment.model'; import { BaseDataSource } from './base.datasource'; import { ListResponseWrapper } from '../_models/response.model'; +import { RequestParams } from '../_models/request-params.model'; import { SERV } from '../_services/main.config'; import { Task } from '../_models/task.model'; import { User } from '../_models/user.model'; -import { environment } from 'src/environments/environment'; export class AgentsStatusDataSource extends BaseDataSource { loadAll(): void { this.loading = true; const startAt = this.currentPage * this.pageSize; - const agentParams = { + const sorting = this.sortingColumn; + + const agentParams: RequestParams = { maxResults: this.pageSize, startsAt: startAt, expand: 'accessGroups,agentstats' }; + if (sorting.dataKey && sorting.isSortable) { + const order = this.buildSortingParams(sorting); + agentParams.ordering = order; + } + const params = { maxResults: this.maxResults }; const agents$ = this.service.getAll(SERV.AGENTS, agentParams); const users$ = this.service.getAll(SERV.USERS, params); diff --git a/src/app/core/_datasources/agents.datasource.ts b/src/app/core/_datasources/agents.datasource.ts index 086c93a2..dfa17b2d 100644 --- a/src/app/core/_datasources/agents.datasource.ts +++ b/src/app/core/_datasources/agents.datasource.ts @@ -1,14 +1,14 @@ import { Chunk, ChunkData } from '../_models/chunk.model'; -import { catchError, finalize, firstValueFrom, forkJoin, of } from 'rxjs'; +import { catchError, finalize, forkJoin, of } from 'rxjs'; import { Agent } from '../_models/agent.model'; import { AgentAssignment } from '../_models/agent-assignment.model'; import { BaseDataSource } from './base.datasource'; import { ListResponseWrapper } from '../_models/response.model'; +import { RequestParams } from '../_models/request-params.model'; import { SERV } from '../_services/main.config'; import { Task } from '../_models/task.model'; import { User } from '../_models/user.model'; -import { environment } from 'src/environments/environment'; export class AgentsDataSource extends BaseDataSource { private _taskId = 0; @@ -21,12 +21,19 @@ export class AgentsDataSource extends BaseDataSource { this.loading = true; const startAt = this.currentPage * this.pageSize; - const agentParams = { + const sorting = this.sortingColumn; + + const agentParams: RequestParams = { maxResults: this.pageSize, startsAt: startAt, expand: 'accessGroups' }; + if (sorting.dataKey && sorting.isSortable) { + const order = this.buildSortingParams(sorting); + agentParams.ordering = order; + } + const params = { maxResults: this.maxResults }; const agents$ = this.service.getAll(SERV.AGENTS, agentParams); const users$ = this.service.getAll(SERV.USERS, params); diff --git a/src/app/core/_datasources/base.datasource.ts b/src/app/core/_datasources/base.datasource.ts index 7ce9fd2c..8a94c871 100644 --- a/src/app/core/_datasources/base.datasource.ts +++ b/src/app/core/_datasources/base.datasource.ts @@ -35,6 +35,7 @@ export abstract class BaseDataSource< public pageSize = 10; public currentPage = 0; public totalItems = 0; + public sortingColumn; /** * Copy of the original dataSubject data used for filtering @@ -190,6 +191,19 @@ export abstract class BaseDataSource< this.dataSubject.next(sortedData); } + /** + * Builds sorting parameters based on the provided sorting column. + * @param sortingColumn - The sorting column configuration. + * @returns {string} The sorting parameter string. + */ + buildSortingParams(sortingColumn): string { + if (sortingColumn && sortingColumn.isSortable) { + const direction = sortingColumn.direction === 'asc' ? '' : '-'; + return `${direction}${sortingColumn.dataKey}`; + } + return ''; + } + /** * Filters the data based on a filter function. * diff --git a/src/app/core/_datasources/chunks.datasource.ts b/src/app/core/_datasources/chunks.datasource.ts index d56a9c04..90f2b1a2 100644 --- a/src/app/core/_datasources/chunks.datasource.ts +++ b/src/app/core/_datasources/chunks.datasource.ts @@ -5,19 +5,28 @@ import { BaseDataSource } from './base.datasource'; import { Chunk } from '../_models/chunk.model'; import { ListResponseWrapper } from '../_models/response.model'; import { SERV } from '../_services/main.config'; +import { RequestParams } from '../_models/request-params.model'; export class ChunksDataSource extends BaseDataSource { loadAll(): void { this.loading = true; const startAt = this.currentPage * this.pageSize; - const chunkParams = { + const sorting = this.sortingColumn; + + const params: RequestParams = { maxResults: this.pageSize, startsAt: startAt, expand: 'task' }; + + if (sorting.dataKey && sorting.isSortable) { + const order = this.buildSortingParams(sorting); + params.ordering = order; + } + const agentParams = { maxResults: this.maxResults }; - const chunks$ = this.service.getAll(SERV.CHUNKS, chunkParams); + const chunks$ = this.service.getAll(SERV.CHUNKS, params); const agents$ = this.service.getAll(SERV.AGENTS, agentParams); forkJoin([chunks$, agents$]) diff --git a/src/app/core/_datasources/crackers.datasource.ts b/src/app/core/_datasources/crackers.datasource.ts index 137304e2..5e82f6d0 100644 --- a/src/app/core/_datasources/crackers.datasource.ts +++ b/src/app/core/_datasources/crackers.datasource.ts @@ -3,6 +3,7 @@ import { catchError, finalize, of } from 'rxjs'; import { BaseDataSource } from './base.datasource'; import { CrackerBinaryType } from '../_models/cracker-binary.model'; import { ListResponseWrapper } from '../_models/response.model'; +import { RequestParams } from '../_models/request-params.model'; import { SERV } from '../_services/main.config'; export class CrackersDataSource extends BaseDataSource { @@ -10,12 +11,19 @@ export class CrackersDataSource extends BaseDataSource { this.loading = true; const startAt = this.currentPage * this.pageSize; - const params = { + const sorting = this.sortingColumn; + + const params: RequestParams = { maxResults: this.pageSize, startsAt: startAt, expand: 'crackerVersions' }; + if (sorting.dataKey && sorting.isSortable) { + const order = this.buildSortingParams(sorting); + params.ordering = order; + } + const crackers$ = this.service.getAll(SERV.CRACKERS_TYPES, params); this.subscriptions.push( diff --git a/src/app/core/_datasources/cracks.datasource.ts b/src/app/core/_datasources/cracks.datasource.ts index e79ce95c..a14485f7 100644 --- a/src/app/core/_datasources/cracks.datasource.ts +++ b/src/app/core/_datasources/cracks.datasource.ts @@ -3,6 +3,7 @@ import { catchError, finalize, of } from 'rxjs'; import { BaseDataSource } from './base.datasource'; import { Hash } from '../_models/hash.model'; import { ListResponseWrapper } from '../_models/response.model'; +import { RequestParams } from '../_models/request-params.model'; import { SERV } from '../_services/main.config'; export class CracksDataSource extends BaseDataSource { @@ -10,13 +11,20 @@ export class CracksDataSource extends BaseDataSource { this.loading = true; const startAt = this.currentPage * this.pageSize; - const params = { + const sorting = this.sortingColumn; + + const params: RequestParams = { maxResults: this.pageSize, startsAt: startAt, filter: 'isCracked=1', expand: 'hashlist,chunk' }; + if (sorting.dataKey && sorting.isSortable) { + const order = this.buildSortingParams(sorting); + params.ordering = order; + } + const cracks$ = this.service.getAll(SERV.HASHES, params); this.subscriptions.push( diff --git a/src/app/core/_datasources/files.datasource.ts b/src/app/core/_datasources/files.datasource.ts index 6255b750..56a614d7 100644 --- a/src/app/core/_datasources/files.datasource.ts +++ b/src/app/core/_datasources/files.datasource.ts @@ -1,6 +1,7 @@ import { catchError, finalize, forkJoin, of } from 'rxjs'; import { BaseDataSource } from './base.datasource'; import { ListResponseWrapper } from '../_models/response.model'; +import { RequestParams } from '../_models/request-params.model'; import { SERV } from '../_services/main.config'; import { File, FileType } from '../_models/file.model'; @@ -26,6 +27,7 @@ export class FilesDataSource extends BaseDataSource { this.loading = true; const startAt = this.currentPage * this.pageSize; + const sorting = this.sortingColumn; let files$; @@ -40,12 +42,16 @@ export class FilesDataSource extends BaseDataSource { }); } } else { - const params = { + const params: RequestParams = { maxResults: this.pageSize, startsAt: startAt, expand: 'accessGroup', filter: `fileType=${this.fileType}` }; + if (sorting.dataKey && sorting.isSortable) { + const order = this.buildSortingParams(sorting); + params.ordering = order; + } files$ = this.service.getAll(SERV.FILES, params); } diff --git a/src/app/core/_datasources/hashes.datasource.ts b/src/app/core/_datasources/hashes.datasource.ts index 06f704ac..548cdbd2 100644 --- a/src/app/core/_datasources/hashes.datasource.ts +++ b/src/app/core/_datasources/hashes.datasource.ts @@ -1,11 +1,10 @@ import { catchError, finalize, of } from 'rxjs'; import { BaseDataSource } from './base.datasource'; -import { HashListFormat } from '../_constants/hashlist.config'; -import { Hashlist } from '../_models/hashlist.model'; +import { Hash } from '../_models/hash.model'; import { ListResponseWrapper } from '../_models/response.model'; +import { RequestParams } from '../_models/request-params.model'; import { SERV } from '../_services/main.config'; -import { Hash } from '../_models/hash.model'; export class HashesDataSource extends BaseDataSource { private _id = 0; @@ -22,12 +21,20 @@ export class HashesDataSource extends BaseDataSource { loadAll(): void { this.loading = true; - const params: any = { + const startAt = this.currentPage * this.pageSize; + const sorting = this.sortingColumn; + + const params: RequestParams = { maxResults: this.pageSize, - startsAt: this.currentPage * this.pageSize, + startsAt: startAt, expand: 'hashlist,chunk' }; + if (sorting.dataKey && sorting.isSortable) { + const order = this.buildSortingParams(sorting); + params.ordering = order; + } + // Add additional params based on _dataType if (this._dataType === 'chunks') { params.filter = 'chunkId=' + this._id; diff --git a/src/app/core/_datasources/hashlists.datasource.ts b/src/app/core/_datasources/hashlists.datasource.ts index 53a2e9d1..ed1334a0 100644 --- a/src/app/core/_datasources/hashlists.datasource.ts +++ b/src/app/core/_datasources/hashlists.datasource.ts @@ -4,6 +4,7 @@ import { BaseDataSource } from './base.datasource'; import { HashListFormat } from '../_constants/hashlist.config'; import { Hashlist } from '../_models/hashlist.model'; import { ListResponseWrapper } from '../_models/response.model'; +import { RequestParams } from '../_models/request-params.model'; import { SERV } from '../_services/main.config'; export class HashlistsDataSource extends BaseDataSource { @@ -17,13 +18,20 @@ export class HashlistsDataSource extends BaseDataSource { this.loading = true; const startAt = this.currentPage * this.pageSize; - const params = { + const sorting = this.sortingColumn; + + const params: RequestParams = { maxResults: this.pageSize, startsAt: startAt, expand: 'hashType,accessGroup', filter: `isArchived=${this.isArchived}` }; + if (sorting.dataKey && sorting.isSortable) { + const order = this.buildSortingParams(sorting); + params.ordering = order; + } + const hashLists$ = this.service.getAll(SERV.HASHLISTS, params); this.subscriptions.push( diff --git a/src/app/core/_datasources/hashtypes.datasource.ts b/src/app/core/_datasources/hashtypes.datasource.ts index 1ff0d1eb..f35bea6a 100644 --- a/src/app/core/_datasources/hashtypes.datasource.ts +++ b/src/app/core/_datasources/hashtypes.datasource.ts @@ -3,6 +3,7 @@ import { catchError, finalize, of } from 'rxjs'; import { BaseDataSource } from './base.datasource'; import { Hashtype } from '../_models/hashtype.model'; import { ListResponseWrapper } from '../_models/response.model'; +import { RequestParams } from '../_models/request-params.model'; import { SERV } from '../_services/main.config'; export class HashtypesDataSource extends BaseDataSource { @@ -10,11 +11,18 @@ export class HashtypesDataSource extends BaseDataSource { this.loading = true; const startAt = this.currentPage * this.pageSize; - const params = { + const sorting = this.sortingColumn; + + const params: RequestParams = { maxResults: this.pageSize, startsAt: startAt }; + if (sorting.dataKey && sorting.isSortable) { + const order = this.buildSortingParams(sorting); + params.ordering = order; + } + const hashtypes$ = this.service.getAll(SERV.HASHTYPES, params); this.subscriptions.push( diff --git a/src/app/core/_datasources/health-checks.datasource.ts b/src/app/core/_datasources/health-checks.datasource.ts index 423cefc3..d4deedb4 100644 --- a/src/app/core/_datasources/health-checks.datasource.ts +++ b/src/app/core/_datasources/health-checks.datasource.ts @@ -4,18 +4,27 @@ import { BaseDataSource } from './base.datasource'; import { Hashtype } from '../_models/hashtype.model'; import { HealthCheck } from '../_models/health-check.model'; import { ListResponseWrapper } from '../_models/response.model'; +import { RequestParams } from '../_models/request-params.model'; import { SERV } from '../_services/main.config'; export class HealthChecksDataSource extends BaseDataSource { loadAll(): void { this.loading = true; - /** - * @todo Extend health checks api response with hashtype - */ - const healthChecks$ = this.service.getAll(SERV.HEALTH_CHECKS, { - maxResults: this.pageSize - }); + const startAt = this.currentPage * this.pageSize; + const sorting = this.sortingColumn; + + const params: RequestParams = { + maxResults: this.pageSize, + startsAt: startAt + }; + + if (sorting.dataKey && sorting.isSortable) { + const order = this.buildSortingParams(sorting); + params.ordering = order; + } + + const healthChecks$ = this.service.getAll(SERV.HEALTH_CHECKS, params); const hashTypes$ = this.service.getAll(SERV.HASHTYPES, { maxResults: this.maxResults diff --git a/src/app/core/_datasources/logs.datasource.ts b/src/app/core/_datasources/logs.datasource.ts index 129ca321..9e3270ce 100644 --- a/src/app/core/_datasources/logs.datasource.ts +++ b/src/app/core/_datasources/logs.datasource.ts @@ -3,6 +3,7 @@ import { catchError, finalize, of } from 'rxjs'; import { BaseDataSource } from './base.datasource'; import { ListResponseWrapper } from '../_models/response.model'; import { Log } from '../_models/log.model'; +import { RequestParams } from '../_models/request-params.model'; import { SERV } from '../_services/main.config'; export class LogsDataSource extends BaseDataSource { @@ -10,12 +11,18 @@ export class LogsDataSource extends BaseDataSource { this.loading = true; const startAt = this.currentPage * this.pageSize; + const sorting = this.sortingColumn; - const params = { + const params: RequestParams = { maxResults: this.pageSize, startsAt: startAt }; + if (sorting.dataKey && sorting.isSortable) { + const order = this.buildSortingParams(sorting); + params.ordering = order; + } + const logs$ = this.service.getAll(SERV.LOGS, params); this.subscriptions.push( @@ -27,6 +34,12 @@ export class LogsDataSource extends BaseDataSource { .subscribe((response: ListResponseWrapper) => { const logs: Log[] = response.values; + if (startAt >= response.total) { + this.currentPage = 0; + this.loadAll(); + return; + } + this.setPaginationConfig( this.pageSize, this.currentPage, diff --git a/src/app/core/_datasources/notifications.datasource.ts b/src/app/core/_datasources/notifications.datasource.ts index 277139c2..cc77df64 100644 --- a/src/app/core/_datasources/notifications.datasource.ts +++ b/src/app/core/_datasources/notifications.datasource.ts @@ -3,6 +3,7 @@ import { catchError, finalize, of } from 'rxjs'; import { BaseDataSource } from './base.datasource'; import { ListResponseWrapper } from '../_models/response.model'; import { Notification } from '../_models/notification.model'; +import { RequestParams } from '../_models/request-params.model'; import { SERV } from '../_services/main.config'; export class NotificationsDataSource extends BaseDataSource { @@ -10,11 +11,18 @@ export class NotificationsDataSource extends BaseDataSource { this.loading = true; const startAt = this.currentPage * this.pageSize; - const params = { + const sorting = this.sortingColumn; + + const params: RequestParams = { maxResults: this.pageSize, startsAt: startAt }; + if (sorting.dataKey && sorting.isSortable) { + const order = this.buildSortingParams(sorting); + params.ordering = order; + } + const notifications$ = this.service.getAll(SERV.NOTIFICATIONS, params); this.subscriptions.push( diff --git a/src/app/core/_datasources/permissions.datasource.ts b/src/app/core/_datasources/permissions.datasource.ts index da30a483..7ca55df7 100644 --- a/src/app/core/_datasources/permissions.datasource.ts +++ b/src/app/core/_datasources/permissions.datasource.ts @@ -3,6 +3,7 @@ import { catchError, finalize, of } from 'rxjs'; import { BaseDataSource } from './base.datasource'; import { GlobalPermissionGroup } from '../_models/global-permission-group.model'; import { ListResponseWrapper } from '../_models/response.model'; +import { RequestParams } from '../_models/request-params.model'; import { SERV } from '../_services/main.config'; export class PermissionsDataSource extends BaseDataSource { @@ -10,12 +11,19 @@ export class PermissionsDataSource extends BaseDataSource this.loading = true; const startAt = this.currentPage * this.pageSize; - const params = { + const sorting = this.sortingColumn; + + const params: RequestParams = { maxResults: this.pageSize, startsAt: startAt, expand: 'user' }; + if (sorting.dataKey && sorting.isSortable) { + const order = this.buildSortingParams(sorting); + params.ordering = order; + } + const permissions$ = this.service.getAll( SERV.ACCESS_PERMISSIONS_GROUPS, params diff --git a/src/app/core/_datasources/preconfigured-tasks.datasource.ts b/src/app/core/_datasources/preconfigured-tasks.datasource.ts index 100b9b86..8ef34a01 100644 --- a/src/app/core/_datasources/preconfigured-tasks.datasource.ts +++ b/src/app/core/_datasources/preconfigured-tasks.datasource.ts @@ -5,6 +5,7 @@ import { BaseDataSource } from './base.datasource'; import { ListResponseWrapper } from '../_models/response.model'; import { MatTableDataSourcePaginator } from '@angular/material/table'; import { Pretask } from '../_models/pretask.model'; +import { RequestParams } from '../_models/request-params.model'; import { SERV } from '../_services/main.config'; export class PreTasksDataSource extends BaseDataSource< @@ -26,12 +27,19 @@ export class PreTasksDataSource extends BaseDataSource< if (this._superTaskId === 0) { const startAt = this.currentPage * this.pageSize; - const params = { + const sorting = this.sortingColumn; + + const params: RequestParams = { maxResults: this.pageSize, startsAt: startAt, expand: 'pretaskFiles' }; + if (sorting.dataKey && sorting.isSortable) { + const order = this.buildSortingParams(sorting); + params.ordering = order; + } + pretasks$ = this.service.getAll(SERV.PRETASKS, params); } else { pretasks$ = this.service.get(SERV.SUPER_TASKS, this._superTaskId, { diff --git a/src/app/core/_datasources/preprocessors.datasource.ts b/src/app/core/_datasources/preprocessors.datasource.ts index be407fe2..8ff5a807 100644 --- a/src/app/core/_datasources/preprocessors.datasource.ts +++ b/src/app/core/_datasources/preprocessors.datasource.ts @@ -3,6 +3,7 @@ import { catchError, finalize, of } from 'rxjs'; import { BaseDataSource } from './base.datasource'; import { ListResponseWrapper } from '../_models/response.model'; import { Preprocessor } from '../_models/preprocessor.model'; +import { RequestParams } from '../_models/request-params.model'; import { SERV } from '../_services/main.config'; export class PreprocessorsDataSource extends BaseDataSource { @@ -10,11 +11,18 @@ export class PreprocessorsDataSource extends BaseDataSource { this.loading = true; const startAt = this.currentPage * this.pageSize; - const params = { + const sorting = this.sortingColumn; + + const params: RequestParams = { maxResults: this.pageSize, startsAt: startAt }; + if (sorting.dataKey && sorting.isSortable) { + const order = this.buildSortingParams(sorting); + params.ordering = order; + } + const preprocessors$ = this.service.getAll(SERV.PREPROCESSORS, params); this.subscriptions.push( diff --git a/src/app/core/_datasources/search-hash.datasource.ts b/src/app/core/_datasources/search-hash.datasource.ts index cb647a5d..76b1d196 100644 --- a/src/app/core/_datasources/search-hash.datasource.ts +++ b/src/app/core/_datasources/search-hash.datasource.ts @@ -3,6 +3,7 @@ import { BaseDataSource } from './base.datasource'; import { ListResponseWrapper } from '../_models/response.model'; import { Log } from '../_models/log.model'; import { SERV } from '../_services/main.config'; +import { RequestParams } from '../_models/request-params.model'; export class SearchHashDataSource extends BaseDataSource { private _search: string[]; @@ -15,15 +16,21 @@ export class SearchHashDataSource extends BaseDataSource { this.loading = true; const startAt = this.currentPage * this.pageSize; + const sorting = this.sortingColumn; const arr = []; for (let i = 0; i < this._search.length; i++) { - const params = { + const params: RequestParams = { maxResults: this.pageSize, startsAt: startAt, filter: `hash=${this._search[i]}` }; + if (sorting.dataKey && sorting.isSortable) { + const order = this.buildSortingParams(sorting); + params.ordering = order; + } + const hashs$ = this.service.getAll(SERV.HASHES, params); this.subscriptions.push( diff --git a/src/app/core/_datasources/super-hashlists.datasource.ts b/src/app/core/_datasources/super-hashlists.datasource.ts index 28bb5c24..2ef9220e 100644 --- a/src/app/core/_datasources/super-hashlists.datasource.ts +++ b/src/app/core/_datasources/super-hashlists.datasource.ts @@ -4,6 +4,7 @@ import { BaseDataSource } from './base.datasource'; import { HashListFormat } from '../_constants/hashlist.config'; import { Hashlist } from '../_models/hashlist.model'; import { ListResponseWrapper } from '../_models/response.model'; +import { RequestParams } from '../_models/request-params.model'; import { SERV } from '../_services/main.config'; export class SuperHashlistsDataSource extends BaseDataSource { @@ -17,13 +18,20 @@ export class SuperHashlistsDataSource extends BaseDataSource { this.loading = true; const startAt = this.currentPage * this.pageSize; - const params = { + const sorting = this.sortingColumn; + + const params: RequestParams = { maxResults: this.pageSize, startsAt: startAt, expand: 'hashType,hashlists', filter: `format=${HashListFormat.SUPERHASHLIST}` }; + if (sorting.dataKey && sorting.isSortable) { + const order = this.buildSortingParams(sorting); + params.ordering = order; + } + const hashLists$ = this.service.getAll(SERV.HASHLISTS, params); this.subscriptions.push( diff --git a/src/app/core/_datasources/supertasks.datasource.ts b/src/app/core/_datasources/supertasks.datasource.ts index 0da993ae..2d383143 100644 --- a/src/app/core/_datasources/supertasks.datasource.ts +++ b/src/app/core/_datasources/supertasks.datasource.ts @@ -3,6 +3,7 @@ import { catchError, finalize, of } from 'rxjs'; import { BaseDataSource } from './base.datasource'; import { ListResponseWrapper } from '../_models/response.model'; import { MatTableDataSourcePaginator } from '@angular/material/table'; +import { RequestParams } from '../_models/request-params.model'; import { SERV } from '../_services/main.config'; import { SuperTask } from '../_models/supertask.model'; @@ -14,12 +15,19 @@ export class SuperTasksDataSource extends BaseDataSource< this.loading = true; const startAt = this.currentPage * this.pageSize; - const params = { + const sorting = this.sortingColumn; + + const params: RequestParams = { maxResults: this.pageSize, startsAt: startAt, expand: 'pretasks' }; + if (sorting.dataKey && sorting.isSortable) { + const order = this.buildSortingParams(sorting); + params.ordering = order; + } + const supertasks$ = this.service.getAll(SERV.SUPER_TASKS, params); this.subscriptions.push( diff --git a/src/app/core/_datasources/tasks-chunks.datasource.ts b/src/app/core/_datasources/tasks-chunks.datasource.ts index 034d760b..53b19d32 100644 --- a/src/app/core/_datasources/tasks-chunks.datasource.ts +++ b/src/app/core/_datasources/tasks-chunks.datasource.ts @@ -4,6 +4,7 @@ import { Agent } from '../_models/agent.model'; import { BaseDataSource } from './base.datasource'; import { Chunk } from '../_models/chunk.model'; import { ListResponseWrapper } from '../_models/response.model'; +import { RequestParams } from '../_models/request-params.model'; import { SERV } from '../_services/main.config'; export class TasksChunksDataSource extends BaseDataSource { @@ -22,12 +23,20 @@ export class TasksChunksDataSource extends BaseDataSource { this.loading = true; const startAt = this.currentPage * this.pageSize; - const chunkParams = { + const sorting = this.sortingColumn; + + const chunkParams: RequestParams = { maxResults: this.pageSize, startsAt: startAt, expand: 'task', filter: 'taskId=' + this._taskId + '' }; + + if (sorting.dataKey && sorting.isSortable) { + const order = this.buildSortingParams(sorting); + chunkParams.ordering = order; + } + const agentParams = { maxResults: this.maxResults }; const chunks$ = this.service.getAll(SERV.CHUNKS, chunkParams); const agents$ = this.service.getAll(SERV.AGENTS, agentParams); diff --git a/src/app/core/_datasources/tasks.datasource.ts b/src/app/core/_datasources/tasks.datasource.ts index 8e56a168..ccb1953c 100644 --- a/src/app/core/_datasources/tasks.datasource.ts +++ b/src/app/core/_datasources/tasks.datasource.ts @@ -6,6 +6,7 @@ import { MatTableDataSourcePaginator } from '@angular/material/table'; import { SERV } from '../_services/main.config'; import { TaskWrapper } from '../_models/task-wrapper.model'; import { Hashtype } from '../_models/hashtype.model'; +import { RequestParams } from '../_models/request-params.model'; export class TasksDataSource extends BaseDataSource< TaskWrapper, @@ -26,18 +27,26 @@ export class TasksDataSource extends BaseDataSource< this.loading = true; const startAt = this.currentPage * this.pageSize; + const sorting = this.sortingColumn; // @todo Implement hashlist filter in API const additionalFilter = this._hashlistId ? `;hashlistId=${this._hashlistId}` : ''; - const params = { + + const params: RequestParams = { maxResults: this.pageSize, startsAt: startAt, expand: 'accessGroup,tasks', - filter: `isArchived=${this._isArchived}`, //${additionalFilter}` - order: 'priority=ASC' + filter: `isArchived=${this._isArchived}` //${additionalFilter}` }; + console.log(sorting); + + if (sorting.dataKey && sorting.isSortable) { + const order = this.buildSortingParams(sorting); + params.ordering = order; + } + const wrappers$ = this.service.getAll(SERV.TASKS_WRAPPER, params); const hashLists$ = this.service.getAll(SERV.HASHLISTS, { maxResults: this.maxResults diff --git a/src/app/core/_datasources/users.datasource.ts b/src/app/core/_datasources/users.datasource.ts index add97422..7c4440be 100644 --- a/src/app/core/_datasources/users.datasource.ts +++ b/src/app/core/_datasources/users.datasource.ts @@ -2,6 +2,7 @@ import { catchError, finalize, of } from 'rxjs'; import { BaseDataSource } from './base.datasource'; import { ListResponseWrapper } from '../_models/response.model'; +import { RequestParams } from '../_models/request-params.model'; import { SERV } from '../_services/main.config'; import { User } from '../_models/user.model'; @@ -10,12 +11,19 @@ export class UsersDataSource extends BaseDataSource { this.loading = true; const startAt = this.currentPage * this.pageSize; - const params = { + const sorting = this.sortingColumn; + + const params: RequestParams = { maxResults: this.pageSize, startsAt: startAt, expand: 'globalPermissionGroup' }; + if (sorting.dataKey && sorting.isSortable) { + const order = this.buildSortingParams(sorting); + params.ordering = order; + } + const users$ = this.service.getAll(SERV.USERS, params); this.subscriptions.push( diff --git a/src/app/core/_datasources/vouchers.datasource.ts b/src/app/core/_datasources/vouchers.datasource.ts index 08b67301..d317b170 100644 --- a/src/app/core/_datasources/vouchers.datasource.ts +++ b/src/app/core/_datasources/vouchers.datasource.ts @@ -2,6 +2,7 @@ import { catchError, finalize, of } from 'rxjs'; import { BaseDataSource } from './base.datasource'; import { ListResponseWrapper } from '../_models/response.model'; +import { RequestParams } from '../_models/request-params.model'; import { SERV } from '../_services/main.config'; import { Voucher } from '../_models/voucher.model'; @@ -10,11 +11,18 @@ export class VouchersDataSource extends BaseDataSource { this.loading = true; const startAt = this.currentPage * this.pageSize; - const params = { + const sorting = this.sortingColumn; + + const params: RequestParams = { maxResults: this.pageSize, startsAt: startAt }; + if (sorting.dataKey && sorting.isSortable) { + const order = this.buildSortingParams(sorting); + params.ordering = order; + } + const vouchers$ = this.service.getAll(SERV.VOUCHER, params); this.subscriptions.push( diff --git a/src/app/core/_models/config-ui.model.ts b/src/app/core/_models/config-ui.model.ts index 96f36697..3062fb30 100644 --- a/src/app/core/_models/config-ui.model.ts +++ b/src/app/core/_models/config-ui.model.ts @@ -30,7 +30,15 @@ export type Layout = 'full' | 'fixed'; export type Theme = 'light' | 'dark'; export interface TableSettings { - [key: string]: number[]; + [key: string]: number[] | TableConfig; +} + +export interface TableConfig { + columns: number[]; // Columns + start: number; // Pagination value, start + order: Sorting; // Column sorting + page: number; //Page number of records + search: string | []; //Saved search } export interface UIConfig { @@ -42,228 +50,565 @@ export interface UIConfig { refreshInterval: number; } +export interface Sorting { + id: number; + dataKey: string; + isSortable: boolean; + direction: string; +} + export const uiConfigDefault: UIConfig = { layout: 'fixed', theme: 'light', timefmt: 'dd/MM/yyyy h:mm:ss', tableSettings: { - notificationsTable: [ - NotificationsTableCol.ID, - NotificationsTableCol.STATUS, - NotificationsTableCol.APPLIED_TO, - NotificationsTableCol.ACTION, - NotificationsTableCol.NOTIFICATION, - NotificationsTableCol.RECEIVER - ], - vouchersTable: [ - VouchersTableCol.ID, - VouchersTableCol.KEY, - VouchersTableCol.CREATED - ], - permissionsTable: [ - PermissionsTableCol.ID, - PermissionsTableCol.NAME, - PermissionsTableCol.MEMBERS - ], - cracksTable: [ - CracksTableCol.FOUND, - CracksTableCol.PLAINTEXT, - CracksTableCol.HASH, - CracksTableCol.AGENT, - CracksTableCol.TASK, - CracksTableCol.CHUNK, - CracksTableCol.TYPE - ], - agentsTable: [ - AgentsTableCol.ID, - AgentsTableCol.NAME, - AgentsTableCol.STATUS, - AgentsTableCol.CURRENT_TASK, - AgentsTableCol.CLIENT, - AgentsTableCol.GPUS_CPUS, - AgentsTableCol.LAST_ACTIVITY - ], - agentStatusTable: [ - AgentsStatusTableCol.ID, - AgentsStatusTableCol.STATUS, - AgentsStatusTableCol.NAME, - AgentsStatusTableCol.AGENT_STATUS, - AgentsStatusTableCol.WORKING_ON, - AgentsStatusTableCol.ASSIGNED, - AgentsStatusTableCol.LAST_ACTIVITY - ], - assignedAgentsTable: [ - AgentsTableCol.ID, - AgentsTableCol.NAME, - AgentsTableCol.STATUS, - AgentsTableCol.TASK_SPEED, - AgentsTableCol.LAST_ACTIVITY, - AgentsTableCol.TIME_SPENT, - AgentsTableCol.BENCHMARK, - AgentsTableCol.CRACKED, - AgentsTableCol.SEARCHED - ], - chunksTable: [ - ChunksTableCol.ID, - ChunksTableCol.PROGRESS, - ChunksTableCol.TASK, - ChunksTableCol.AGENT, - ChunksTableCol.DISPATCH_TIME, - ChunksTableCol.LAST_ACTIVITY, - ChunksTableCol.TIME_SPENT, - ChunksTableCol.STATE, - ChunksTableCol.CRACKED - ], - hashlistsTable: [ - HashlistsTableCol.ID, - HashlistsTableCol.NAME, - HashlistsTableCol.HASHTYPE, - HashlistsTableCol.FORMAT, - HashlistsTableCol.CRACKED, - HashlistsTableCol.HASH_COUNT - ], - superHashlistsTable: [ - SuperHashlistsTableCol.ID, - SuperHashlistsTableCol.NAME, - SuperHashlistsTableCol.HASHTYPE, - SuperHashlistsTableCol.CRACKED, - SuperHashlistsTableCol.HASHLISTS - ], - hashtypesTable: [ - HashtypesTableCol.HASHTYPE, - HashtypesTableCol.DESCRIPTION, - HashtypesTableCol.SALTED, - HashtypesTableCol.SLOW_HASH - ], - filesTable: [ - FilesTableCol.ID, - FilesTableCol.NAME, - FilesTableCol.SIZE, - FilesTableCol.LINE_COUNT, - FilesTableCol.ACCESS_GROUP - ], - filesAttackTable: [ - FilesAttackTableCol.ID, - FilesAttackTableCol.NAME, - FilesAttackTableCol.SIZE - ], - crackersTable: [ - CrackersTableCol.ID, - CrackersTableCol.NAME, - CrackersTableCol.VERSIONS - ], - preprocessorsTable: [PreprocessorsTableCol.ID, PreprocessorsTableCol.NAME], - agentBinariesTable: [ - AgentBinariesTableCol.ID, - AgentBinariesTableCol.FILENAME, - AgentBinariesTableCol.OS, - AgentBinariesTableCol.TYPE, - AgentBinariesTableCol.UPDATE_TRACK, - AgentBinariesTableCol.VERSION - ], - healthChecksTable: [ - HealthChecksTableCol.ID, - HealthChecksTableCol.CREATED, - HealthChecksTableCol.STATUS, - HealthChecksTableCol.TYPE - ], - healthCheckAgentsTable: [ - HealthCheckAgentsTableCol.AGENT_ID, - HealthCheckAgentsTableCol.AGENT_NAME, - HealthCheckAgentsTableCol.STATUS, - HealthCheckAgentsTableCol.START, - HealthCheckAgentsTableCol.GPUS, - HealthCheckAgentsTableCol.CRACKED, - HealthCheckAgentsTableCol.ERRORS - ], - pretasksTable: [ - PretasksTableCol.ID, - PretasksTableCol.NAME, - PretasksTableCol.ATTACK_COMMAND, - PretasksTableCol.FILES_TOTAL, - PretasksTableCol.FILES_SIZE, - PretasksTableCol.PRIORITY, - PretasksTableCol.MAX_AGENTS - ], - tasksTable: [ - TaskTableCol.ID, - TaskTableCol.TASK_TYPE, - TaskTableCol.NAME, - TaskTableCol.STATUS, - TaskTableCol.HASHTYPE, - TaskTableCol.HASHLISTS, - TaskTableCol.PRIORITY, - TaskTableCol.AGENTS, - TaskTableCol.MAX_AGENTS, - TaskTableCol.DISPATCHED_SEARCHED, - TaskTableCol.CRACKED - ], - tasksChunksTable: [ - TasksChunksTableCol.ID, - TasksChunksTableCol.PROGRESS, - TasksChunksTableCol.AGENT, - TasksChunksTableCol.DISPATCH_TIME, - TasksChunksTableCol.LAST_ACTIVITY, - TasksChunksTableCol.TIME_SPENT, - TasksChunksTableCol.STATE, - TasksChunksTableCol.CRACKED - ], - tasksSupertasksTable: [ - TasksSupertasksDataSourceTableCol.ID, - TasksSupertasksDataSourceTableCol.NAME, - TasksSupertasksDataSourceTableCol.DISPATCHED_SEARCHED, - TasksSupertasksDataSourceTableCol.CRACKED, - TasksSupertasksDataSourceTableCol.AGENTS, - TasksSupertasksDataSourceTableCol.PRIORITY, - TasksSupertasksDataSourceTableCol.MAX_AGENTS - ], - supertasksTable: [ - SupertasksTableCol.ID, - SupertasksTableCol.NAME, - SupertasksTableCol.PRETASKS - ], - supertasksPretasksTable: [ - SupertasksPretasksTableCol.ID, - SupertasksPretasksTableCol.NAME, - SupertasksPretasksTableCol.PRIORITY, - SupertasksPretasksTableCol.MAX_AGENTS - ], - superTasksPretasksEditTable: [ - PretasksTableCol.ID, - PretasksTableCol.NAME, - PretasksTableCol.ATTACK_COMMAND, - PretasksTableCol.ESTIMATED_KEYSPACE, - PretasksTableCol.ATTACK_RUNTIME, - PretasksTableCol.FILES_TOTAL, - PretasksTableCol.FILES_SIZE, - PretasksTableCol.PRIORITY, - PretasksTableCol.MAX_AGENTS - ], - hashlistTasksTable: [ - TaskTableCol.ID, - TaskTableCol.NAME, - TaskTableCol.DISPATCHED_SEARCHED, - TaskTableCol.CRACKED - ], - searchHashTable: [SearchHashTableCol.HASH, SearchHashTableCol.INFO], - usersTable: [ - UsersTableCol.ID, - UsersTableCol.NAME, - UsersTableCol.REGISTERED, - UsersTableCol.LAST_LOGIN, - UsersTableCol.EMAIL, - UsersTableCol.STATUS, - UsersTableCol.SESSION, - UsersTableCol.PERM_GROUP - ], - logsTable: [ - LogsTableCol.ID, - LogsTableCol.ISSUER, - LogsTableCol.LEVEL, - LogsTableCol.MESSAGE, - LogsTableCol.TIME - ], - accessGroupsTable: [AccessGroupsTableCol.ID, AccessGroupsTableCol.NAME] + notificationsTable: { + start: 0, + page: 25, + columns: [ + NotificationsTableCol.ID, + NotificationsTableCol.STATUS, + NotificationsTableCol.APPLIED_TO, + NotificationsTableCol.ACTION, + NotificationsTableCol.NOTIFICATION, + NotificationsTableCol.RECEIVER + ], + order: { + id: NotificationsTableCol.ID, + dataKey: '', + isSortable: true, + direction: 'asc' + }, + search: '' + }, + vouchersTable: { + start: 0, + page: 25, + columns: [ + VouchersTableCol.ID, + VouchersTableCol.KEY, + VouchersTableCol.CREATED + ], + order: { + id: VouchersTableCol.ID, + dataKey: '', + isSortable: true, + direction: 'asc' + }, + search: '' + }, + permissionsTable: { + start: 0, + page: 25, + columns: [ + PermissionsTableCol.ID, + PermissionsTableCol.NAME, + PermissionsTableCol.MEMBERS + ], + order: { + id: PermissionsTableCol.ID, + dataKey: '', + isSortable: true, + direction: 'asc' + }, + search: '' + }, + cracksTable: { + start: 0, + page: 25, + columns: [ + CracksTableCol.FOUND, + CracksTableCol.PLAINTEXT, + CracksTableCol.HASH, + CracksTableCol.AGENT, + CracksTableCol.TASK, + CracksTableCol.CHUNK, + CracksTableCol.TYPE + ], + order: { + id: CracksTableCol.ID, + dataKey: '', + isSortable: true, + direction: 'asc' + }, + search: '' + }, + agentsTable: { + start: 0, + page: 25, + columns: [ + AgentsTableCol.ID, + AgentsTableCol.NAME, + AgentsTableCol.STATUS, + AgentsTableCol.CURRENT_TASK, + AgentsTableCol.CLIENT, + AgentsTableCol.GPUS_CPUS, + AgentsTableCol.LAST_ACTIVITY + ], + order: { + id: AgentsTableCol.ID, + dataKey: '', + isSortable: true, + direction: 'asc' + }, + search: '' + }, + agentStatusTable: { + start: 0, + page: 25, + columns: [ + AgentsStatusTableCol.ID, + AgentsStatusTableCol.STATUS, + AgentsStatusTableCol.NAME, + AgentsStatusTableCol.AGENT_STATUS, + AgentsStatusTableCol.WORKING_ON, + AgentsStatusTableCol.ASSIGNED, + AgentsStatusTableCol.LAST_ACTIVITY + ], + order: { + id: AgentsStatusTableCol.ID, + dataKey: '', + isSortable: true, + direction: 'asc' + }, + search: '' + }, + assignedAgentsTable: { + start: 0, + page: 25, + columns: [ + AgentsTableCol.ID, + AgentsTableCol.NAME, + AgentsTableCol.STATUS, + AgentsTableCol.TASK_SPEED, + AgentsTableCol.LAST_ACTIVITY, + AgentsTableCol.TIME_SPENT, + AgentsTableCol.BENCHMARK, + AgentsTableCol.CRACKED, + AgentsTableCol.SEARCHED + ], + order: { + id: AgentsTableCol.ID, + dataKey: '', + isSortable: true, + direction: 'asc' + }, + search: '' + }, + chunksTable: { + start: 0, + page: 25, + columns: [ + ChunksTableCol.ID, + ChunksTableCol.PROGRESS, + ChunksTableCol.TASK, + ChunksTableCol.AGENT, + ChunksTableCol.DISPATCH_TIME, + ChunksTableCol.LAST_ACTIVITY, + ChunksTableCol.TIME_SPENT, + ChunksTableCol.STATE, + ChunksTableCol.CRACKED + ], + order: { + id: ChunksTableCol.ID, + dataKey: '', + isSortable: true, + direction: 'asc' + }, + search: '' + }, + hashlistsTable: { + start: 0, + page: 25, + columns: [ + HashlistsTableCol.ID, + HashlistsTableCol.NAME, + HashlistsTableCol.HASHTYPE, + HashlistsTableCol.FORMAT, + HashlistsTableCol.CRACKED, + HashlistsTableCol.HASH_COUNT + ], + order: { + id: HashlistsTableCol.ID, + dataKey: '', + isSortable: true, + direction: 'asc' + }, + search: '' + }, + superHashlistsTable: { + start: 0, + page: 25, + columns: [ + SuperHashlistsTableCol.ID, + SuperHashlistsTableCol.NAME, + SuperHashlistsTableCol.HASHTYPE, + SuperHashlistsTableCol.CRACKED, + SuperHashlistsTableCol.HASHLISTS + ], + order: { + id: SuperHashlistsTableCol.ID, + dataKey: '', + isSortable: true, + direction: 'asc' + }, + search: '' + }, + hashtypesTable: { + start: 0, + page: 25, + columns: [ + HashtypesTableCol.HASHTYPE, + HashtypesTableCol.DESCRIPTION, + HashtypesTableCol.SALTED, + HashtypesTableCol.SLOW_HASH + ], + order: { + id: HashtypesTableCol.HASHTYPE, + dataKey: '', + isSortable: true, + direction: 'asc' + }, + search: '' + }, + filesTable: { + start: 0, + page: 25, + columns: [ + FilesTableCol.ID, + FilesTableCol.NAME, + FilesTableCol.SIZE, + FilesTableCol.LINE_COUNT, + FilesTableCol.ACCESS_GROUP + ], + order: { + id: FilesTableCol.ID, + dataKey: '', + isSortable: true, + direction: 'asc' + }, + search: '' + }, + filesAttackTable: { + start: 0, + page: 25, + columns: [ + FilesAttackTableCol.ID, + FilesAttackTableCol.NAME, + FilesAttackTableCol.SIZE + ], + order: { + id: FilesAttackTableCol.ID, + dataKey: '', + isSortable: true, + direction: 'asc' + }, + search: '' + }, + crackersTable: { + start: 0, + page: 25, + columns: [ + CrackersTableCol.ID, + CrackersTableCol.NAME, + CrackersTableCol.VERSIONS + ], + order: { + id: CrackersTableCol.ID, + dataKey: '', + isSortable: true, + direction: 'asc' + }, + search: '' + }, + preprocessorsTable: { + start: 0, + page: 25, + columns: [PreprocessorsTableCol.ID, PreprocessorsTableCol.NAME], + order: { + id: PreprocessorsTableCol.ID, + dataKey: '', + isSortable: true, + direction: 'asc' + }, + search: '' + }, + agentBinariesTable: { + start: 0, + page: 25, + columns: [ + AgentBinariesTableCol.ID, + AgentBinariesTableCol.FILENAME, + AgentBinariesTableCol.OS, + AgentBinariesTableCol.TYPE, + AgentBinariesTableCol.UPDATE_TRACK, + AgentBinariesTableCol.VERSION + ], + order: { + id: AgentBinariesTableCol.ID, + dataKey: '', + isSortable: true, + direction: 'asc' + }, + search: '' + }, + healthChecksTable: { + start: 0, + page: 25, + columns: [ + HealthChecksTableCol.ID, + HealthChecksTableCol.CREATED, + HealthChecksTableCol.STATUS, + HealthChecksTableCol.TYPE + ], + order: { + id: HealthChecksTableCol.ID, + dataKey: '', + isSortable: true, + direction: 'asc' + }, + search: '' + }, + healthCheckAgentsTable: { + start: 0, + page: 25, + columns: [ + HealthCheckAgentsTableCol.AGENT_ID, + HealthCheckAgentsTableCol.AGENT_NAME, + HealthCheckAgentsTableCol.STATUS, + HealthCheckAgentsTableCol.START, + HealthCheckAgentsTableCol.GPUS, + HealthCheckAgentsTableCol.CRACKED, + HealthCheckAgentsTableCol.ERRORS + ], + order: { + id: HealthCheckAgentsTableCol.AGENT_ID, + dataKey: '', + isSortable: true, + direction: 'asc' + }, + search: '' + }, + pretasksTable: { + start: 0, + page: 25, + columns: [ + PretasksTableCol.ID, + PretasksTableCol.NAME, + PretasksTableCol.ATTACK_COMMAND, + PretasksTableCol.FILES_TOTAL, + PretasksTableCol.FILES_SIZE, + PretasksTableCol.PRIORITY, + PretasksTableCol.MAX_AGENTS + ], + order: { + id: PretasksTableCol.PRIORITY, + dataKey: '', + isSortable: true, + direction: 'asc' + }, + search: '' + }, + tasksTable: { + start: 0, + page: 25, + columns: [ + TaskTableCol.ID, + TaskTableCol.TASK_TYPE, + TaskTableCol.NAME, + TaskTableCol.STATUS, + TaskTableCol.HASHTYPE, + TaskTableCol.HASHLISTS, + TaskTableCol.PRIORITY, + TaskTableCol.AGENTS, + TaskTableCol.MAX_AGENTS, + TaskTableCol.DISPATCHED_SEARCHED, + TaskTableCol.CRACKED + ], + order: { + id: TaskTableCol.PRIORITY, + dataKey: '', + isSortable: true, + direction: 'asc' + }, + search: '' + }, + tasksChunksTable: { + start: 0, + page: 25, + columns: [ + TasksChunksTableCol.ID, + TasksChunksTableCol.PROGRESS, + TasksChunksTableCol.AGENT, + TasksChunksTableCol.DISPATCH_TIME, + TasksChunksTableCol.LAST_ACTIVITY, + TasksChunksTableCol.TIME_SPENT, + TasksChunksTableCol.STATE, + TasksChunksTableCol.CRACKED + ], + order: { + id: TasksChunksTableCol.ID, + dataKey: '', + isSortable: true, + direction: 'asc' + }, + search: '' + }, + tasksSupertasksTable: { + start: 0, + page: 25, + columns: [ + TasksSupertasksDataSourceTableCol.ID, + TasksSupertasksDataSourceTableCol.NAME, + TasksSupertasksDataSourceTableCol.DISPATCHED_SEARCHED, + TasksSupertasksDataSourceTableCol.CRACKED, + TasksSupertasksDataSourceTableCol.AGENTS, + TasksSupertasksDataSourceTableCol.PRIORITY, + TasksSupertasksDataSourceTableCol.MAX_AGENTS + ], + order: { + id: TasksSupertasksDataSourceTableCol.ID, + dataKey: '', + isSortable: true, + direction: 'asc' + }, + search: '' + }, + supertasksTable: { + start: 0, + page: 25, + columns: [ + SupertasksTableCol.ID, + SupertasksTableCol.NAME, + SupertasksTableCol.PRETASKS + ], + order: { + id: SupertasksTableCol.ID, + dataKey: '', + isSortable: true, + direction: 'asc' + }, + search: '' + }, + supertasksPretasksTable: { + start: 0, + page: 25, + columns: [ + SupertasksPretasksTableCol.ID, + SupertasksPretasksTableCol.NAME, + SupertasksPretasksTableCol.PRIORITY, + SupertasksPretasksTableCol.MAX_AGENTS + ], + order: { + id: SupertasksPretasksTableCol.ID, + dataKey: '', + isSortable: true, + direction: 'asc' + }, + search: '' + }, + superTasksPretasksEditTable: { + start: 0, + page: 25, + columns: [ + PretasksTableCol.ID, + PretasksTableCol.NAME, + PretasksTableCol.ATTACK_COMMAND, + PretasksTableCol.ESTIMATED_KEYSPACE, + PretasksTableCol.ATTACK_RUNTIME, + PretasksTableCol.FILES_TOTAL, + PretasksTableCol.FILES_SIZE, + PretasksTableCol.PRIORITY, + PretasksTableCol.MAX_AGENTS + ], + order: { + id: PretasksTableCol.ID, + dataKey: '', + isSortable: true, + direction: 'asc' + }, + search: '' + }, + hashlistTasksTable: { + start: 0, + page: 25, + columns: [ + TaskTableCol.ID, + TaskTableCol.NAME, + TaskTableCol.DISPATCHED_SEARCHED, + TaskTableCol.CRACKED + ], + order: { + id: TaskTableCol.ID, + dataKey: '', + isSortable: true, + direction: 'asc' + }, + search: '' + }, + searchHashTable: { + start: 0, + page: 25, + columns: [SearchHashTableCol.HASH, SearchHashTableCol.INFO], + order: { + id: SearchHashTableCol.HASH, + dataKey: '', + isSortable: true, + direction: 'asc' + }, + search: '' + }, + usersTable: { + start: 0, + page: 25, + columns: [ + UsersTableCol.ID, + UsersTableCol.NAME, + UsersTableCol.REGISTERED, + UsersTableCol.LAST_LOGIN, + UsersTableCol.EMAIL, + UsersTableCol.STATUS, + UsersTableCol.SESSION, + UsersTableCol.PERM_GROUP + ], + order: { + id: UsersTableCol.ID, + dataKey: '', + isSortable: true, + direction: 'asc' + }, + search: '' + }, + logsTable: { + start: 0, + page: 25, + columns: [ + LogsTableCol.ID, + LogsTableCol.ISSUER, + LogsTableCol.LEVEL, + LogsTableCol.MESSAGE, + LogsTableCol.TIME + ], + order: { + id: LogsTableCol.ID, + dataKey: '', + isSortable: true, + direction: 'asc' + }, + search: '' + }, + accessGroupsTable: { + start: 0, + page: 25, + columns: [AccessGroupsTableCol.ID, AccessGroupsTableCol.NAME], + order: { + id: AccessGroupsTableCol.ID, + dataKey: '', + isSortable: true, + direction: 'asc' + }, + search: '' + } }, refreshPage: false, refreshInterval: 10 diff --git a/src/app/core/_models/request-params.model.ts b/src/app/core/_models/request-params.model.ts new file mode 100644 index 00000000..5f5b8532 --- /dev/null +++ b/src/app/core/_models/request-params.model.ts @@ -0,0 +1,7 @@ +export interface RequestParams { + maxResults: number; + startsAt: number; + expand?: string; + filter?: string; + ordering?: string; +} diff --git a/src/app/core/_models/settings.model.ts b/src/app/core/_models/settings.model.ts index 8fe91e19..edc18e63 100644 --- a/src/app/core/_models/settings.model.ts +++ b/src/app/core/_models/settings.model.ts @@ -1,15 +1,15 @@ export interface HashtopolisSettings { - hashcatBrainEnable: boolean - hashlistAlias: string - blacklistChars: string - chunktime: number - agentStatLimit: number - agentStatTension: number - agentTempThreshold1: number - agentTempThreshold2: number - agentUtilThreshold1: number - agentUtilThreshold2: number - statustimer: number - agenttimeout: number - maxSessionLength: number + hashcatBrainEnable: boolean; + hashlistAlias: string; + blacklistChars: string; + chunktime: number; + agentStatLimit: number; + agentStatTension: number; + agentTempThreshold1: number; + agentTempThreshold2: number; + agentUtilThreshold1: number; + agentUtilThreshold2: number; + statustimer: number; + agenttimeout: number; + maxSessionLength: number; } diff --git a/src/app/core/_services/storage/local-storage.service.ts b/src/app/core/_services/storage/local-storage.service.ts index 80a63163..6b0afe28 100644 --- a/src/app/core/_services/storage/local-storage.service.ts +++ b/src/app/core/_services/storage/local-storage.service.ts @@ -1,5 +1,5 @@ -import { Injectable } from "@angular/core"; -import { BaseStorageService, StorageWrapper } from "./base-storage.service"; +import { Injectable } from '@angular/core'; +import { BaseStorageService, StorageWrapper } from './base-storage.service'; /** * A storage service implementation that uses browser's local storage to store and retrieve data @@ -8,10 +8,9 @@ import { BaseStorageService, StorageWrapper } from "./base-storage.service"; * @template T - The type of data to be stored. */ @Injectable({ - providedIn: 'root', + providedIn: 'root' }) export class LocalStorageService extends BaseStorageService { - /** * Retrieves the stored data associated with the specified key from local storage, * and checks if it has expired. If the data has expired, it is removed from local storage. @@ -20,7 +19,7 @@ export class LocalStorageService extends BaseStorageService { * @returns The stored data if found and not expired, or `null` if not found or expired. */ getItem(key: string): T | null { - key = this.decode(key) + key = this.decode(key); const storedValue = localStorage.getItem(key); if (!storedValue) { return null; // Data not found. @@ -49,7 +48,7 @@ export class LocalStorageService extends BaseStorageService { const storedValue: StorageWrapper = { expires: expiresInMs ? new Date(Date.now() + expiresInMs).getTime() : 0, value: value - } + }; localStorage.setItem(this.decode(key), JSON.stringify(storedValue)); } @@ -60,7 +59,7 @@ export class LocalStorageService extends BaseStorageService { * @param key - The key under which the data is stored. */ removeItem(key: string): void { - key = this.decode(key) + key = this.decode(key); localStorage.removeItem(key); } -} \ No newline at end of file +} diff --git a/src/app/shared/utils/config.ts b/src/app/shared/utils/config.ts index 5b9e1509..fa21ea3b 100644 --- a/src/app/shared/utils/config.ts +++ b/src/app/shared/utils/config.ts @@ -30,12 +30,56 @@ export class UISettingsUtilityClass { /** * Updates the table settings for a specific key in the UI configuration. * - * @param key - The key for the table settings. - * @param columns - An array of column names to set as table settings for the key. + * @param {string} key - The key for the table settings. + * @param {object} settings - The settings object containing properties to update. + * @param {number} [settings.page] - The page number to set. + * @param {number} [settings.start] - The start index to set. + * @param {number[]} [settings.columns] - An array of column numbers to set. + * @param {any[]} [settings.order] - An array defining the order of columns. */ - updateTableSettings(key: string, columns: number[]): void { - this.uiConfig.tableSettings[key] = columns; - this.storage.setItem(UISettingsUtilityClass.KEY, this.uiConfig, 0); + updateTableSettings( + key: string, + settings: { + page?: number; + start?: number; + columns?: number[]; + order?: any[]; + } + ): void { + try { + const existingTableSettings = this.uiConfig.tableSettings[key]; + + if (existingTableSettings) { + // Update the settings based on the provided parameters + if (settings.page !== undefined) { + existingTableSettings['page'] = settings.page; + } + if (settings.start !== undefined) { + existingTableSettings['start'] = settings.start; + } + if (settings.columns !== undefined) { + // Save columns + existingTableSettings['columns'] = settings.columns; + // Check if the value saved in order is visible; if not, remove it + const orderValue: number = existingTableSettings['order']['id']; + const numericColumns: number[] = + existingTableSettings['columns'].map(Number); + if (!numericColumns.includes(orderValue)) { + existingTableSettings['order'] = undefined; + } + } + if (settings.order !== undefined) { + existingTableSettings['order'] = settings.order; + } + + this.storage.setItem(UISettingsUtilityClass.KEY, this.uiConfig, 0); + } else { + // If the key doesn't exist, log an error or handle it accordingly + console.error(`Table settings not found for key: ${key}`); + } + } catch (error) { + console.error(error); + } } /** @@ -46,9 +90,21 @@ export class UISettingsUtilityClass { */ getTableSettings(key: string): number[] { try { - return this.uiConfig.tableSettings[key]; + const tableConfig = this.uiConfig.tableSettings[key]; + + if (Array.isArray(tableConfig)) { + // If it's an array (number[]), return it + return tableConfig; + } else if ('columns' in tableConfig) { + // If it's a TableConfig, extract and return the columns property + return tableConfig.columns; + } else { + // If it's neither, log an error and return an empty array + console.error(`Unexpected table configuration for key: ${key}`); + return []; + } } catch (error) { - console.log(error); + console.error(error); return []; } } From 84b9c864d959f81dbe048299c658798131736eb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Fri, 9 Feb 2024 16:49:57 +0000 Subject: [PATCH 410/419] Save changes --- .../tables/ht-table/ht-table.component.html | 3 +++ .../tables/ht-table/ht-table.component.ts | 22 +++++++++++++++++++ src/app/shared/utils/config.ts | 5 +++++ 3 files changed, 30 insertions(+) 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 fd50d693..08e50ee1 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 @@ -60,6 +60,9 @@ [(ngModel)]="dataSource.filter" placeholder="Filter data" /> +
diff --git a/src/app/core/_components/tables/ht-table/ht-table.component.ts b/src/app/core/_components/tables/ht-table/ht-table.component.ts index ec89cac9..8dde1246 100644 --- a/src/app/core/_components/tables/ht-table/ht-table.component.ts +++ b/src/app/core/_components/tables/ht-table/ht-table.component.ts @@ -211,6 +211,9 @@ export class HTTableComponent implements OnInit, AfterViewInit { this.dataSource.pageSize = this.defaultPageSize; // Get saved start page this.dataSource.currentPage = this.defaultStartPage; + // Search item + this.dataSource.filter = + this.uiSettings['uiConfig']['tableSettings'][this.name]['search']; // Sorted header arrow and sorting initialization this.dataSource.sortingColumn = this.uiSettings['uiConfig']['tableSettings'][this.name]['order']; @@ -349,9 +352,26 @@ export class HTTableComponent implements OnInit, AfterViewInit { applyFilter() { if (this.filterFn) { this.dataSource.filterData(this.filterFn); + this.uiSettings.updateTableSettings(this.name, { + search: this.dataSource.filter + }); } } + /** + * Clears a filter to the table based on user input. + */ + clearFilter() { + // Reset the filter function to a default that passes all items + const defaultFilterFn = (item: any, filterValue: '') => true; + // Reapply the default filter function + this.dataSource.filterData(defaultFilterFn); + this.dataSource.filter = ''; + this.uiSettings.updateTableSettings(this.name, { + search: '' + }); + } + /** * Checks if a row is selected. * @@ -439,6 +459,8 @@ export class HTTableComponent implements OnInit, AfterViewInit { * @param event - The `PageEvent` object containing information about the new page configuration. */ onPageChange(event: PageEvent): void { + this.clearFilter(); + this.uiSettings.updateTableSettings(this.name, { start: event.pageIndex, // Store the new page index page: event.pageSize // Store the new page size diff --git a/src/app/shared/utils/config.ts b/src/app/shared/utils/config.ts index fa21ea3b..01fbafd5 100644 --- a/src/app/shared/utils/config.ts +++ b/src/app/shared/utils/config.ts @@ -35,6 +35,7 @@ export class UISettingsUtilityClass { * @param {number} [settings.page] - The page number to set. * @param {number} [settings.start] - The start index to set. * @param {number[]} [settings.columns] - An array of column numbers to set. + * @param {number[]} [settings.search] - An array of column numbers to set. * @param {any[]} [settings.order] - An array defining the order of columns. */ updateTableSettings( @@ -44,6 +45,7 @@ export class UISettingsUtilityClass { start?: number; columns?: number[]; order?: any[]; + search?: string; } ): void { try { @@ -71,6 +73,9 @@ export class UISettingsUtilityClass { if (settings.order !== undefined) { existingTableSettings['order'] = settings.order; } + if (settings.search !== undefined) { + existingTableSettings['search'] = settings.search; + } this.storage.setItem(UISettingsUtilityClass.KEY, this.uiConfig, 0); } else { From a859874b03127476bfa432ec1e1e40641f3b7f15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Mon, 12 Feb 2024 09:37:08 +0000 Subject: [PATCH 411/419] Typo --- src/app/tasks/new-tasks/new-tasks.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/tasks/new-tasks/new-tasks.component.html b/src/app/tasks/new-tasks/new-tasks.component.html index 152a5ba3..c509bcaf 100644 --- a/src/app/tasks/new-tasks/new-tasks.component.html +++ b/src/app/tasks/new-tasks/new-tasks.component.html @@ -92,7 +92,7 @@ Wordlists - > + From d4287dc42bf569c514b4006f54a118faae371b16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Mon, 12 Feb 2024 14:59:45 +0000 Subject: [PATCH 412/419] Missing question mark --- .../_components/tables/tasks-table/tasks-table.component.ts | 1 - src/app/core/_datasources/tasks.datasource.ts | 4 +--- 2 files changed, 1 insertion(+), 4 deletions(-) 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 9c1a6fdf..dc02d8f5 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 @@ -13,7 +13,6 @@ import { } from './tasks-table.constants'; import { catchError, forkJoin } from 'rxjs'; - import { ActionMenuEvent } from '../../menus/action-menu/action-menu.model'; import { BaseTableComponent } from '../base-table/base-table.component'; import { BulkActionMenuAction } from '../../menus/bulk-action-menu/bulk-action-menu.constants'; diff --git a/src/app/core/_datasources/tasks.datasource.ts b/src/app/core/_datasources/tasks.datasource.ts index ccb1953c..e3c26348 100644 --- a/src/app/core/_datasources/tasks.datasource.ts +++ b/src/app/core/_datasources/tasks.datasource.ts @@ -40,8 +40,6 @@ export class TasksDataSource extends BaseDataSource< filter: `isArchived=${this._isArchived}` //${additionalFilter}` }; - console.log(sorting); - if (sorting.dataKey && sorting.isSortable) { const order = this.buildSortingParams(sorting); params.ordering = order; @@ -73,7 +71,7 @@ export class TasksDataSource extends BaseDataSource< ); wrapper.hashlists = [matchingHashList]; wrapper.hashtypes = [matchingHashTypes]; - wrapper.taskName = wrapper.tasks[0].taskName; + wrapper.taskName = wrapper.tasks[0]?.taskName; wrapper.accessGroupName = wrapper.accessGroup?.groupName; return wrapper; } From 4dddcb2a4e127e066d1df3db425778564d63baa9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Mon, 12 Feb 2024 15:12:54 +0000 Subject: [PATCH 413/419] Show preprocessor --- src/app/tasks/new-tasks/new-tasks.component.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/app/tasks/new-tasks/new-tasks.component.html b/src/app/tasks/new-tasks/new-tasks.component.html index c509bcaf..f9a2999e 100644 --- a/src/app/tasks/new-tasks/new-tasks.component.html +++ b/src/app/tasks/new-tasks/new-tasks.component.html @@ -92,7 +92,7 @@ Wordlists - + @@ -101,7 +101,7 @@ Rules - + @@ -110,7 +110,7 @@ Other - +
From ff8d5a83fd5c884a0785255383ea074d4f384abe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Tue, 13 Feb 2024 13:27:05 +0000 Subject: [PATCH 414/419] Default table sorting --- src/app/core/_models/config-ui.model.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/core/_models/config-ui.model.ts b/src/app/core/_models/config-ui.model.ts index 3062fb30..4f266de1 100644 --- a/src/app/core/_models/config-ui.model.ts +++ b/src/app/core/_models/config-ui.model.ts @@ -429,7 +429,7 @@ export const uiConfigDefault: UIConfig = { id: TaskTableCol.PRIORITY, dataKey: '', isSortable: true, - direction: 'asc' + direction: 'desc' }, search: '' }, From 77f4538a0ff78a385f4feba376b08a8091f6edce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Thu, 15 Feb 2024 14:11:50 +0000 Subject: [PATCH 415/419] Unassigning task agent --- .../row-action-menu.component.ts | 12 +++++++--- .../row-action-menu.constants.ts | 1 + .../agents-table/agents-table.component.ts | 22 ++++++++++++++----- .../core/_datasources/agents.datasource.ts | 8 +++++++ src/app/core/_models/agent.model.ts | 1 + src/app/shared/table/table.component.ts | 8 +++---- .../edit-tasks/edit-tasks.component.html | 1 + .../tasks/edit-tasks/edit-tasks.component.ts | 2 +- 8 files changed, 41 insertions(+), 14 deletions(-) diff --git a/src/app/core/_components/menus/row-action-menu/row-action-menu.component.ts b/src/app/core/_components/menus/row-action-menu/row-action-menu.component.ts index f1ca1ad2..38dc5e88 100644 --- a/src/app/core/_components/menus/row-action-menu/row-action-menu.component.ts +++ b/src/app/core/_components/menus/row-action-menu/row-action-menu.component.ts @@ -156,9 +156,15 @@ export class RowActionMenuComponent this.getActivateMenuItem(RowActionMenuLabel.ACTIVATE_AGENT) ); } - this.setActionMenuItems(1, [ - this.getDeleteMenuItem(RowActionMenuLabel.DELETE_AGENT) - ]); + if (this.data['assignmentId']) { + this.setActionMenuItems(1, [ + this.getDeleteMenuItem(RowActionMenuLabel.UNASSIGN_AGENT) + ]); + } else { + this.setActionMenuItems(1, [ + this.getDeleteMenuItem(RowActionMenuLabel.DELETE_AGENT) + ]); + } } /** 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 4b180061..b0d7d863 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 @@ -48,6 +48,7 @@ export const RowActionMenuLabel = { DOWNLOAD_AGENT: 'Download Agent Binary', COPY_LINK_BINARY: 'Copy link Agent Binary', UNARCHIVE_TASK: 'Unarchive Task', + UNASSIGN_AGENT: 'Unassign Agent', RESET_CHUNK: 'Reset Task Chunk' }; diff --git a/src/app/core/_components/tables/agents-table/agents-table.component.ts b/src/app/core/_components/tables/agents-table/agents-table.component.ts index 9a18c265..3193823d 100644 --- a/src/app/core/_components/tables/agents-table/agents-table.component.ts +++ b/src/app/core/_components/tables/agents-table/agents-table.component.ts @@ -41,6 +41,7 @@ export class AgentsTableComponent implements OnInit, OnDestroy { @Input() taskId = 0; + @Input() assignAgents? = false; tableColumns: HTTableColumn[] = []; dataSource: AgentsDataSource; @@ -61,6 +62,9 @@ export class AgentsTableComponent if (this.taskId) { this.dataSource.setTaskId(this.taskId); } + if (this.assignAgents) { + this.dataSource.setAssignAgents(this.assignAgents); + } this.dataSource.reload(); } @@ -439,9 +443,13 @@ export class AgentsTableComponent case RowActionMenuAction.DELETE: this.openDialog({ rows: [event.data], - title: `Deleting ${event.data.agentName} ...`, + title: `${this.assignAgents ? 'Unassigning' : 'Deleting'} ${ + event.data.agentName + } ...`, icon: 'warning', - body: `Are you sure you want to delete ${event.data.agentName}? Note that this action cannot be undone.`, + body: `Are you sure you want to ${ + this.assignAgents ? 'unassign' : 'delete' + } ${event.data.agentName}? Note that this action cannot be undone.`, warn: true, action: event.menuItem.action }); @@ -550,10 +558,12 @@ export class AgentsTableComponent ); } else { this.subscriptions.push( - this.gs.delete(SERV.AGENT_ASSIGN, agent[0]._id).subscribe(() => { - this.snackBar.open('Successfully unassigned agent!', 'Close'); - this.dataSource.reload(); - }) + this.gs + .delete(SERV.AGENT_ASSIGN, agent[0].assignmentId) + .subscribe(() => { + this.snackBar.open('Successfully unassigned agent!', 'Close'); + this.dataSource.reload(); + }) ); } } diff --git a/src/app/core/_datasources/agents.datasource.ts b/src/app/core/_datasources/agents.datasource.ts index dfa17b2d..ed4e5fef 100644 --- a/src/app/core/_datasources/agents.datasource.ts +++ b/src/app/core/_datasources/agents.datasource.ts @@ -12,11 +12,16 @@ import { User } from '../_models/user.model'; export class AgentsDataSource extends BaseDataSource { private _taskId = 0; + private _assignAgents = false; setTaskId(taskId: number): void { this._taskId = taskId; } + setAssignAgents(assign: boolean): void { + this._assignAgents = assign; + } + loadAll(): void { this.loading = true; @@ -123,6 +128,9 @@ export class AgentsDataSource extends BaseDataSource { agent.taskName = agent.task.taskName; agent.taskId = agent.task._id; agent.chunk = chunks.find((e) => e.agentId === agent.agentId); + agent.assignmentId = assignments.find( + (e) => e.agentId === agent._id + )?.assignmentId; if (agent.chunk) { agent.chunkId = agent.chunk._id; } diff --git a/src/app/core/_models/agent.model.ts b/src/app/core/_models/agent.model.ts index 9aa99126..77fbb5b3 100644 --- a/src/app/core/_models/agent.model.ts +++ b/src/app/core/_models/agent.model.ts @@ -41,6 +41,7 @@ export interface Agent { chunk?: Chunk; chunkId?: number; benchmark?: string; + assignmentId?: number; } export interface IAgents { diff --git a/src/app/shared/table/table.component.ts b/src/app/shared/table/table.component.ts index d4da15e2..a96199c7 100644 --- a/src/app/shared/table/table.component.ts +++ b/src/app/shared/table/table.component.ts @@ -9,10 +9,10 @@ import { Router } from '@angular/router';
- `, - host: { - '(window:resize)': 'onWindowResize($event)' - } + ` + // host: { + // '(window:resize)': 'onWindowResize($event)' + // } }) export class TableComponent { constructor(private router: Router) {} diff --git a/src/app/tasks/edit-tasks/edit-tasks.component.html b/src/app/tasks/edit-tasks/edit-tasks.component.html index 16bebf4a..419b6de8 100644 --- a/src/app/tasks/edit-tasks/edit-tasks.component.html +++ b/src/app/tasks/edit-tasks/edit-tasks.component.html @@ -140,6 +140,7 @@ name="assignedAgentsTable" [taskId]="editedTaskIndex" [hasBulkActions]="false" + [assignAgents]="true" [isSelectable]="false" [isFilterable]="false" > diff --git a/src/app/tasks/edit-tasks/edit-tasks.component.ts b/src/app/tasks/edit-tasks/edit-tasks.component.ts index 27d75efb..a0e03191 100644 --- a/src/app/tasks/edit-tasks/edit-tasks.component.ts +++ b/src/app/tasks/edit-tasks/edit-tasks.component.ts @@ -53,7 +53,7 @@ export class EditTasksComponent implements OnInit { hashlistDescrip: any; hashlistinform: any; assigAgents: any; - availAgents: any; + availAgents = []; crackerinfo: any; tkeyspace: any; From c5fe65337349a8911661070bf25e73564729490b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Thu, 15 Feb 2024 15:54:04 +0000 Subject: [PATCH 416/419] agents assign and bulk actions --- .../bulk-action-menu.component.ts | 6 ++ .../bulk-action-menu.constants.ts | 3 +- .../agents-table/agents-table.component.html | 2 +- .../agents-table/agents-table.component.ts | 23 +++++-- .../tables/ht-table/ht-table.models.ts | 1 + .../input/text-area/text-area.component.html | 2 +- .../edit-tasks/edit-tasks.component.html | 69 +++++++++---------- .../tasks/new-tasks/new-tasks.component.html | 6 +- src/styles/components/_form.scss | 2 +- 9 files changed, 63 insertions(+), 51 deletions(-) diff --git a/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.component.ts b/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.component.ts index c894f11a..d96f56e6 100644 --- a/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.component.ts +++ b/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.component.ts @@ -40,6 +40,12 @@ export class BulkActionMenuComponent BulkActionMenuLabel.DEACTIVATE_AGENTS, BulkActionMenuLabel.DELETE_AGENTS ); + } else if (this.dataType === 'agents-assign') { + this.setActivateDeleteMenu( + BulkActionMenuLabel.ACTIVATE_AGENTS, + BulkActionMenuLabel.DEACTIVATE_AGENTS, + BulkActionMenuLabel.UNASSIGN_AGENTS + ); } else if (this.dataType === 'users') { this.setActivateDeleteMenu( BulkActionMenuLabel.ACTIVATE_USERS, diff --git a/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.constants.ts b/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.constants.ts index 2feb4f71..89dc3929 100644 --- a/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.constants.ts +++ b/src/app/core/_components/menus/bulk-action-menu/bulk-action-menu.constants.ts @@ -23,7 +23,8 @@ export const BulkActionMenuLabel = { DEACTIVATE_USERS: 'Deactivate Users', ARCHIVE_TASKS: 'Archive Tasks', ARCHIVE_HASHLISTS: 'Archive Hashlists', - RESET_CHUNKS: 'Reset chunks' + RESET_CHUNKS: 'Reset chunks', + UNASSIGN_AGENTS: 'Unassign agents' }; export const BulkActionMenuAction = { diff --git a/src/app/core/_components/tables/agents-table/agents-table.component.html b/src/app/core/_components/tables/agents-table/agents-table.component.html index 1bf827fc..3aebf2a5 100644 --- a/src/app/core/_components/tables/agents-table/agents-table.component.html +++ b/src/app/core/_components/tables/agents-table/agents-table.component.html @@ -2,7 +2,7 @@ #table [name]="name" [columnLabels]="columnLabels" - dataType="agents" + [dataType]="datatype" [dataSource]="dataSource" [tableColumns]="tableColumns" [isSelectable]="isSelectable" diff --git a/src/app/core/_components/tables/agents-table/agents-table.component.ts b/src/app/core/_components/tables/agents-table/agents-table.component.ts index 3193823d..1fd7c324 100644 --- a/src/app/core/_components/tables/agents-table/agents-table.component.ts +++ b/src/app/core/_components/tables/agents-table/agents-table.component.ts @@ -6,6 +6,7 @@ import { /* eslint-disable @angular-eslint/component-selector */ import { Component, Input, OnDestroy, OnInit } from '@angular/core'; import { + DataType, HTTableColumn, HTTableEditable, HTTableIcon, @@ -40,6 +41,7 @@ export class AgentsTableComponent extends BaseTableComponent implements OnInit, OnDestroy { + @Input() datatype: DataType = 'agents'; @Input() taskId = 0; @Input() assignAgents? = false; @@ -480,9 +482,13 @@ export class AgentsTableComponent case BulkActionMenuAction.DELETE: this.openDialog({ rows: event.data, - title: `Deleting ${event.data.length} agents ...`, + title: `${this.assignAgents ? 'Unassigning' : 'Deleting'} ${ + event.data.length + } agents ...`, icon: 'warning', - body: `Are you sure you want to delete the above agents? Note that this action cannot be undone.`, + body: `Are you sure you want to ${ + this.assignAgents ? 'unassign' : 'delete' + } the above agents? Note that this action cannot be undone.`, warn: true, listAttribute: 'agentName', action: event.menuItem.action @@ -523,9 +529,16 @@ export class AgentsTableComponent * @todo Implement error handling. */ private bulkActionDelete(agents: Agent[]): void { - const requests = agents.map((agent: Agent) => { - return this.gs.delete(SERV.AGENTS, agent._id); - }); + let requests; + if (this.taskId === 0) { + requests = agents.map((agent: Agent) => { + return this.gs.delete(SERV.AGENTS, agent._id); + }); + } else { + requests = agents.map((agent: Agent) => { + return this.gs.delete(SERV.AGENT_ASSIGN, agent.assignmentId); + }); + } this.subscriptions.push( forkJoin(requests) diff --git a/src/app/core/_components/tables/ht-table/ht-table.models.ts b/src/app/core/_components/tables/ht-table/ht-table.models.ts index bda566fa..8435e2dd 100644 --- a/src/app/core/_components/tables/ht-table/ht-table.models.ts +++ b/src/app/core/_components/tables/ht-table/ht-table.models.ts @@ -4,6 +4,7 @@ import { SafeHtml } from '@angular/platform-browser'; export type DataType = | 'agents' | 'agents-status' + | 'agents-assign' | 'hashlists' | 'hashes' | 'search-hash' diff --git a/src/app/shared/input/text-area/text-area.component.html b/src/app/shared/input/text-area/text-area.component.html index c8fdf324..cb933c15 100644 --- a/src/app/shared/input/text-area/text-area.component.html +++ b/src/app/shared/input/text-area/text-area.component.html @@ -1,4 +1,4 @@ - + {{ title }} -
+
+
- @@ -46,43 +46,43 @@ - - - - - + + + + + - - - - + + + + - + - - - - + + + + - - - - + + + + - - - - + + + + - + - + - - - - + + + + @@ -136,14 +136,7 @@ - + diff --git a/src/app/tasks/new-tasks/new-tasks.component.html b/src/app/tasks/new-tasks/new-tasks.component.html index f9a2999e..3fa0e174 100644 --- a/src/app/tasks/new-tasks/new-tasks.component.html +++ b/src/app/tasks/new-tasks/new-tasks.component.html @@ -58,7 +58,7 @@
-
+

@@ -66,11 +66,9 @@

-
- +
-
diff --git a/src/styles/components/_form.scss b/src/styles/components/_form.scss index 7bf60f46..2976739d 100644 --- a/src/styles/components/_form.scss +++ b/src/styles/components/_form.scss @@ -287,7 +287,7 @@ progress::-webkit-progress-value { */ .custom-chip-grid { - width: 250%; + width: 282%; } .highlight-text { From 46179c4bc39df1e3c9025d50adcb354b7bf27fd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Thu, 15 Feb 2024 16:21:54 +0000 Subject: [PATCH 417/419] 12feb-feedback --- .../agents/new-agent/new-agent.component.html | 10 +-- .../masks/masks.component.html | 5 ++ .../wrbulk/wrbulk.component.html | 5 ++ .../modal-subtasks.component.html | 81 ------------------- 4 files changed, 15 insertions(+), 86 deletions(-) diff --git a/src/app/agents/new-agent/new-agent.component.html b/src/app/agents/new-agent/new-agent.component.html index b5890ad3..4aee8643 100644 --- a/src/app/agents/new-agent/new-agent.component.html +++ b/src/app/agents/new-agent/new-agent.component.html @@ -14,7 +14,7 @@

- +
@@ -37,7 +37,7 @@ - +

Generate a voucher to register the agent. Note that @@ -45,8 +45,8 @@

- - + +
@@ -58,7 +58,7 @@

Register the agent using the above URL

- +
diff --git a/src/app/tasks/import-supertasks/masks/masks.component.html b/src/app/tasks/import-supertasks/masks/masks.component.html index c0b82068..c8226361 100644 --- a/src/app/tasks/import-supertasks/masks/masks.component.html +++ b/src/app/tasks/import-supertasks/masks/masks.component.html @@ -1,5 +1,10 @@ +
+

+ The following features or functionalities are currently not operational. +

+
diff --git a/src/app/tasks/import-supertasks/wrbulk/wrbulk.component.html b/src/app/tasks/import-supertasks/wrbulk/wrbulk.component.html index 2db78e08..4586583a 100644 --- a/src/app/tasks/import-supertasks/wrbulk/wrbulk.component.html +++ b/src/app/tasks/import-supertasks/wrbulk/wrbulk.component.html @@ -1,4 +1,9 @@ +
+

+ The following features or functionalities are currently not operational. +

+
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 473ee94b..f1ca5406 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 @@ -2,84 +2,3 @@ - - - - - From 353eae682bc3f360971df1c1bb5956544f25c98a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Fri, 16 Feb 2024 13:15:37 +0000 Subject: [PATCH 418/419] Agent status boxes --- .../agent-status/agent-status.component.html | 176 +++++++++--------- .../edit-hashlist.component.html | 7 +- src/styles/components/_card.scss | 10 +- 3 files changed, 102 insertions(+), 91 deletions(-) diff --git a/src/app/agents/agent-status/agent-status.component.html b/src/app/agents/agent-status/agent-status.component.html index 015b0ec4..05b438ad 100644 --- a/src/app/agents/agent-status/agent-status.component.html +++ b/src/app/agents/agent-status/agent-status.component.html @@ -21,100 +21,100 @@
-
-
- - - check_circle - highlight_off - {{ a.agentName | shortenString:15 }} - -
-
-
-
- - {{ result === 'No data'? result:result +'%' }} -
-
- - {{ result === 'No data'? result:result +'°' }} -
-
- - {{ result === 'No data'? result:result +'%' }} -
-
-
-
-
-
- No Records Found -
+
+
+ + + check_circle + highlight_off + {{ a.agentName | shortenString:15 }} + +
+
+
+
+ + {{ result === 'No data'? result:result +'%' }} +
+
+ + {{ result === 'No data'? result:result +'°' }} +
+
+ + {{ result === 'No data'? result:result +'%' }} +
+
+
+
+
+
+ No Records Found +
-
-
- - - check_circle - highlight_off - {{ gname.k | shortenString:15 }} - -
-
- - {{ a.agentName | split:'-':1 | shortenString:15 }} - -
- - {{ result === 'No data'? result:result +'%' }} -
-
- - {{ result === 'No data'? result:result +'°' }} -
-
- - {{ result === 'No data'? result:result +'%' }} -
-
+
+
+ + + check_circle + highlight_off + {{ gname.k | shortenString:15 }} + +
+
+ + {{ a.agentName | split:'-':1 | shortenString:15 }} + +
+ + {{ result === 'No data'? result:result +'%' }} +
+
+ + {{ result === 'No data'? result:result +'°' }}
+
+ + {{ result === 'No data'? result:result +'%' }} +
+
- -
+
+
+
diff --git a/src/app/hashlists/edit-hashlist/edit-hashlist.component.html b/src/app/hashlists/edit-hashlist/edit-hashlist.component.html index c28fd18d..08578384 100644 --- a/src/app/hashlists/edit-hashlist/edit-hashlist.component.html +++ b/src/app/hashlists/edit-hashlist/edit-hashlist.component.html @@ -6,13 +6,16 @@ -
+
+
+ +
+
- diff --git a/src/styles/components/_card.scss b/src/styles/components/_card.scss index 92565585..9876a9b7 100644 --- a/src/styles/components/_card.scss +++ b/src/styles/components/_card.scss @@ -38,7 +38,15 @@ .card-ag { background-color: #fff; border: 1px solid #d4d4d4; - height: 190px; + height: 173px; + margin-bottom: 10px; + position: relative; +} + +.card-ag-rack { + background-color: #fff; + border: 1px solid #d4d4d4; + height: 220px; margin-bottom: 10px; position: relative; } From 3d89bd6e0c3478275b49032a2230640a73e0f38c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benito=20D=C3=ADaz-Medina?= Date: Tue, 20 Feb 2024 15:23:19 +0000 Subject: [PATCH 419/419] Report builder --- package-lock.json | 284 ++++++------ package.json | 2 +- src/app/core/_models/report.model.ts | 19 + .../edit-hashlist.component.html | 11 + .../edit-hashlist/edit-hashlist.component.ts | 132 ++++++ src/app/shared/components.module.ts | 3 + .../report-builder.component.html | 36 ++ .../report-builder.component.ts | 413 ++++++++++++++++++ src/assets/report-templates/default.json | 20 + src/assets/report-templates/hashlists.json | 20 + 10 files changed, 799 insertions(+), 141 deletions(-) create mode 100644 src/app/core/_models/report.model.ts create mode 100644 src/app/shared/report-builder/report-builder.component.html create mode 100644 src/app/shared/report-builder/report-builder.component.ts create mode 100644 src/assets/report-templates/default.json create mode 100644 src/assets/report-templates/hashlists.json diff --git a/package-lock.json b/package-lock.json index e466bd33..368e5239 100644 --- a/package-lock.json +++ b/package-lock.json @@ -63,7 +63,7 @@ "ngx-moment": "^6.0.2", "opentracing": "^0.14.7", "papaparse": "^5.4.1", - "pdfmake": "^0.2.7", + "pdfmake": "^0.2.9", "popper.js": "^1.16.1", "rxjs": "~7.8.1", "sweetalert2": "^11.7.32", @@ -3141,16 +3141,19 @@ } }, "node_modules/@foliojs-fork/fontkit/node_modules/deep-equal": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz", - "integrity": "sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.2.tgz", + "integrity": "sha512-5tdhKF6DbU7iIzrIOa1AOUt39ZRm13cmL1cGEh//aqR8x9+tNfbywRf0n5FD/18OKMdo7DNEtrX2t22ZAkI+eg==", "dependencies": { - "is-arguments": "^1.0.4", - "is-date-object": "^1.0.1", - "is-regex": "^1.0.4", - "object-is": "^1.0.1", + "is-arguments": "^1.1.1", + "is-date-object": "^1.0.5", + "is-regex": "^1.1.4", + "object-is": "^1.1.5", "object-keys": "^1.1.1", - "regexp.prototype.flags": "^1.2.0" + "regexp.prototype.flags": "^1.5.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -3172,13 +3175,13 @@ "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==" }, "node_modules/@foliojs-fork/pdfkit": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/@foliojs-fork/pdfkit/-/pdfkit-0.13.0.tgz", - "integrity": "sha512-YXeG1fml9k97YNC9K8e292Pj2JzGt9uOIiBFuQFxHsdQ45BlxW+JU3RQK6JAvXU7kjhjP8rCcYvpk36JLD33sQ==", + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@foliojs-fork/pdfkit/-/pdfkit-0.14.0.tgz", + "integrity": "sha512-nMOiQAv6id89MT3tVTCgc7HxD5ZMANwio2o5yvs5sexQkC0KI3BLaLakpsrHmFfeGFAhqPmZATZGbJGXTUebpg==", "dependencies": { "@foliojs-fork/fontkit": "^1.9.1", "@foliojs-fork/linebreak": "^1.1.1", - "crypto-js": "^4.0.0", + "crypto-js": "^4.2.0", "png-js": "^1.0.0" } }, @@ -7506,12 +7509,18 @@ } }, "node_modules/call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -8312,9 +8321,9 @@ } }, "node_modules/crypto-js": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.1.1.tgz", - "integrity": "sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw==" + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==" }, "node_modules/css-line-break": { "version": "2.1.0", @@ -8806,6 +8815,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/define-lazy-prop": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", @@ -8816,10 +8841,11 @@ } }, "node_modules/define-properties": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", - "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", "dependencies": { + "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" }, @@ -9308,6 +9334,25 @@ "is-arrayish": "^0.2.1" } }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-get-iterator": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", @@ -9476,7 +9521,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", - "dev": true, "dependencies": { "esprima": "^4.0.1", "estraverse": "^5.2.0", @@ -9497,7 +9541,6 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, "optional": true, "engines": { "node": ">=0.10.0" @@ -9905,7 +9948,6 @@ "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, "engines": { "node": ">=4.0" } @@ -10693,9 +10735,12 @@ } }, "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/functions-have-names": { "version": "1.2.3", @@ -10746,14 +10791,18 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", - "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", "has-proto": "^1.0.1", - "has-symbols": "^1.0.3" + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -10898,7 +10947,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dev": true, "dependencies": { "get-intrinsic": "^1.1.3" }, @@ -10970,11 +11018,11 @@ } }, "node_modules/has-property-descriptors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", - "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", "dependencies": { - "get-intrinsic": "^1.1.1" + "es-define-property": "^1.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -11026,6 +11074,17 @@ "resolved": "https://registry.npmjs.org/hashtype-detector/-/hashtype-detector-0.0.6.tgz", "integrity": "sha512-lhK3N1DDTHCpMRgZFRVUTrx55+DyKs6w21TEiMCv18OeRcd/GR2lC77pR6IetWzPH1Dsu+bxAqwFanzyM0BfUg==" }, + "node_modules/hasown": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.1.tgz", + "integrity": "sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/hdr-histogram-js": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/hdr-histogram-js/-/hdr-histogram-js-2.0.3.tgz", @@ -15326,12 +15385,12 @@ } }, "node_modules/pdfmake": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/pdfmake/-/pdfmake-0.2.7.tgz", - "integrity": "sha512-ClLpgx30H5G3EDvRW1MrA1Xih6YxEaSgIVFrOyBMgAAt62V+hxsyWAi6JNP7u1Fc5JKYAbpb4RRVw8Rhvmz5cQ==", + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/pdfmake/-/pdfmake-0.2.9.tgz", + "integrity": "sha512-LAtYwlR8cCQqbxESK2d50DYaVAzAC9Id9NjilRte6Tb9pyHUB+Z50nhD0imuBL0eDyXQKvEYSNjo3P5AOc2ZCg==", "dependencies": { "@foliojs-fork/linebreak": "^1.1.1", - "@foliojs-fork/pdfkit": "^0.13.0", + "@foliojs-fork/pdfkit": "^0.14.0", "iconv-lite": "^0.6.3", "xmldoc": "^1.1.2" }, @@ -16268,13 +16327,14 @@ "dev": true }, "node_modules/regexp.prototype.flags": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz", - "integrity": "sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==", + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", + "integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "functions-have-names": "^1.2.3" + "call-bind": "^1.0.6", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "set-function-name": "^2.0.1" }, "engines": { "node": ">= 0.4" @@ -16864,6 +16924,35 @@ "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" }, + "node_modules/set-function-length": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.1.tgz", + "integrity": "sha512-j4t6ccc+VsKwYHso+kElc5neZpjtq9EnRICFZtWyBsLojhmeF/ZBd/elqm22WJh/BziDe/SBiOeAt0m2mfLD0g==", + "dependencies": { + "define-data-property": "^1.1.2", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.3", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.1.tgz", + "integrity": "sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA==", + "dependencies": { + "define-data-property": "^1.0.1", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/setimmediate": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", @@ -17552,96 +17641,11 @@ } }, "node_modules/static-eval": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/static-eval/-/static-eval-2.1.0.tgz", - "integrity": "sha512-agtxZ/kWSsCkI5E4QifRwsaPs0P0JmZV6dkLz6ILYfFYQGn+5plctanRN+IC8dJRiFkyXHrwEE3W9Wmx67uDbw==", - "dependencies": { - "escodegen": "^1.11.1" - } - }, - "node_modules/static-eval/node_modules/escodegen": { - "version": "1.14.3", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", - "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", - "dependencies": { - "esprima": "^4.0.1", - "estraverse": "^4.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1" - }, - "bin": { - "escodegen": "bin/escodegen.js", - "esgenerate": "bin/esgenerate.js" - }, - "engines": { - "node": ">=4.0" - }, - "optionalDependencies": { - "source-map": "~0.6.1" - } - }, - "node_modules/static-eval/node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/static-eval/node_modules/levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", - "dependencies": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/static-eval/node_modules/optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "dependencies": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/static-eval/node_modules/prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/static-eval/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-eval/node_modules/type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/static-eval/-/static-eval-2.1.1.tgz", + "integrity": "sha512-MgWpQ/ZjGieSVB3eOJVs4OA2LT/q1vx98KPCTTQPzq/aLr0YUXTsgryTXr4SLfR0ZfUUCiedM9n/ABeDIyy4mA==", "dependencies": { - "prelude-ls": "~1.1.2" - }, - "engines": { - "node": ">= 0.8.0" + "escodegen": "^2.1.0" } }, "node_modules/static-module": { diff --git a/package.json b/package.json index 9cd3d654..cf9c3ebc 100644 --- a/package.json +++ b/package.json @@ -69,7 +69,7 @@ "ngx-moment": "^6.0.2", "opentracing": "^0.14.7", "papaparse": "^5.4.1", - "pdfmake": "^0.2.7", + "pdfmake": "^0.2.9", "popper.js": "^1.16.1", "rxjs": "~7.8.1", "sweetalert2": "^11.7.32", diff --git a/src/app/core/_models/report.model.ts b/src/app/core/_models/report.model.ts new file mode 100644 index 00000000..a998979a --- /dev/null +++ b/src/app/core/_models/report.model.ts @@ -0,0 +1,19 @@ +export class Report { + project_id: number; + project_reference: string; + project_description: string; + project_created_by: string; + inputfiles: InputFiles[] = []; + + constructor() { + this.inputfiles.push(new InputFiles()); + } +} + +export class InputFiles { + name: string; + hashtypeId: number; + hashCount: number; + cracked: string; + dispatched_keyspace: number; +} diff --git a/src/app/hashlists/edit-hashlist/edit-hashlist.component.html b/src/app/hashlists/edit-hashlist/edit-hashlist.component.html index 08578384..c864a396 100644 --- a/src/app/hashlists/edit-hashlist/edit-hashlist.component.html +++ b/src/app/hashlists/edit-hashlist/edit-hashlist.component.html @@ -39,6 +39,16 @@ + + + + + Report Builder + + + + +
@@ -46,3 +56,4 @@
+ diff --git a/src/app/hashlists/edit-hashlist/edit-hashlist.component.ts b/src/app/hashlists/edit-hashlist/edit-hashlist.component.ts index dbf5d6b0..ddb1dbf0 100644 --- a/src/app/hashlists/edit-hashlist/edit-hashlist.component.ts +++ b/src/app/hashlists/edit-hashlist/edit-hashlist.component.ts @@ -234,6 +234,138 @@ export class EditHashlistComponent this.unsubscribeService.add(updateSubscription$); } + // Actions + + importCrackedHashes() { + const helperImportCrackedSubscription$ = this.gs + .chelper(SERV.HELPER, 'importCrackedHashes', this.editedHashlistIndex) + .subscribe(() => { + this.alert.okAlert('Imported Cracked Hashes!', ''); + }); + + this.unsubscribeService.add(helperImportCrackedSubscription$); + } + + exportCrackedHashes() { + const helperExportedCrackedSubscription$ = this.gs + .chelper(SERV.HELPER, 'exportCrackedHashes', this.editedHashlistIndex) + .subscribe(() => { + this.alert.okAlert('Exported Cracked Hashes!', ''); + }); + + this.unsubscribeService.add(helperExportedCrackedSubscription$); + } + + exportLeftHashes() { + const helperExportedLeftSubscription$ = this.gs + .chelper(SERV.HELPER, 'exportLeftHashes', this.editedHashlistIndex) + .subscribe(() => { + this.alert.okAlert('Exported Left Hashes!', ''); + }); + + this.unsubscribeService.add(helperExportedLeftSubscription$); + } + + exportWordlist() { + const helperExportedWordlistSubscription$ = this.gs + .chelper(SERV.HELPER, 'exportWordlist', this.editedHashlistIndex) + .subscribe(() => { + this.alert.okAlert('Exported Wordlist!', ''); + }); + + this.unsubscribeService.add(helperExportedWordlistSubscription$); + } + + //Report data + prepareReport() { + let sum = 0; + const workflow = []; + let preCommand; + const files = []; + this.editedHashlist.tasks.forEach((item) => { + if (item.keyspace && typeof item.keyspace === 'number') { + sum += item.keyspace; + } + if (item.preprocessorCommand) { + preCommand.push({ + subtitle: `Preprocessor Command: ${item.preprocessorCommand}` + }); + } + + // Extract file names using regular expressions + const fileNames = item.attackCmd.match(/\b\w+\.\w+\b/g); + + if (fileNames && fileNames.length > 0) { + fileNames.forEach((fileName) => { + files.push({ + text: `File: ${fileName}`, + margin: [0, 0, 0, 5] + }); + }); + } + workflow.push({ + subtitle: `Command: ${item.attackCmd}`, + ...preCommand, + ul: [ + { + text: `Keyspace: ${item.keyspace} (Progress: ${( + (item.keyspaceProgress / item.keyspace) * + 100 + ).toFixed(2)}%)`, + margin: [0, 0, 0, 5] + }, + { + text: `Cracked entries: ${item.taskId}`, + margin: [0, 0, 0, 5] + }, + ...files + ] + }); + }); + + const report = [ + { + title: 'Input Fields', + table: { + tableColumns: [ + 'Name', + 'Notes', + 'Hash Mode', + 'Hash Count', + 'Retrieved', + 'Total Keyspace explored' + ], + tableValues: [ + this.editedHashlist.name, + this.editedHashlist.notes, + this.editedHashlist.hashType.hashTypeId, + this.editedHashlist.hashCount, + this.editedHashlist.cracked, + sum + ] + } + }, + { break: 1 }, + { + title: 'WorkFlow Completed' + }, + { break: 1 }, + ...workflow + ]; + return report; + } + + getTaskInfo(id: number) { + // const taskInfoSubscription$ = this.gs + // .get(SERV.TASKS, id, { + // expand: 'files' + // }) + // .subscribe((result) => { + // console.log(result); + // }); + // this.unsubscribeService.add(taskInfoSubscription$); + } + canDeactivate(): boolean { const hasUnsavedChanges = this.updateForm.dirty; diff --git a/src/app/shared/components.module.ts b/src/app/shared/components.module.ts index 87dee3f8..dd633c80 100644 --- a/src/app/shared/components.module.ts +++ b/src/app/shared/components.module.ts @@ -30,6 +30,7 @@ import { CoreFormsModule } from './forms.module'; import { AlertNavModule } from './alert/alert.module'; import { HashtypeDetectorComponent } from './hashtype-detector/hashtype-detector.component'; import { WordlisGeneratorComponent } from './wordlist-generator/wordlist-generatorcomponent'; +import { ReportBuilderComponent } from './report-builder/report-builder.component'; @NgModule({ declarations: [ @@ -38,6 +39,7 @@ import { WordlisGeneratorComponent } from './wordlist-generator/wordlist-generat LoadingSpinnerComponent, TimeoutDialogComponent, ActiveSpinnerComponent, + ReportBuilderComponent, HexconvertorComponent, PassStrenghtComponent, CheatsheetComponent, @@ -70,6 +72,7 @@ import { WordlisGeneratorComponent } from './wordlist-generator/wordlist-generat WordlisGeneratorComponent, HashtypeDetectorComponent, LoadingSpinnerComponent, + ReportBuilderComponent, TimeoutDialogComponent, ActiveSpinnerComponent, HexconvertorComponent, diff --git a/src/app/shared/report-builder/report-builder.component.html b/src/app/shared/report-builder/report-builder.component.html new file mode 100644 index 00000000..8630de45 --- /dev/null +++ b/src/app/shared/report-builder/report-builder.component.html @@ -0,0 +1,36 @@ + + +
+
+
+ + + +
+ + + + +
+ +
+ + + + + +
+
+ + + +
+
+ +
+ + +
+ +
+
diff --git a/src/app/shared/report-builder/report-builder.component.ts b/src/app/shared/report-builder/report-builder.component.ts new file mode 100644 index 00000000..376755f8 --- /dev/null +++ b/src/app/shared/report-builder/report-builder.component.ts @@ -0,0 +1,413 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import pdfMake from 'pdfmake/build/pdfmake'; +import pdfFonts from 'pdfmake/build/vfs_fonts'; +import { HttpClient } from '@angular/common/http'; +pdfMake.vfs = pdfFonts.pdfMake.vfs; + +@Component({ + selector: 'app-report-builder', + templateUrl: './report-builder.component.html' +}) +export class ReportBuilderComponent implements OnInit { + @Input() templateName: string; + @Input() reportData; + reportForm: FormGroup; + templates: any = {}; + + constructor( + private formBuilder: FormBuilder, + private http: HttpClient + ) {} + + async ngOnInit(): Promise { + if (this.templateName) { + await this.loadTemplate(this.templateName); // Wait for the template to be loaded + } else { + console.error('Template name is required'); + } + } + + populateFormWithDefaults() { + const template = this.templates[this.templateName]; + if (!template) { + console.error('Template not found'); + return; + } + + const coverPageTemplate = template.cover_page; + + this.reportForm = this.formBuilder.group({ + cover_page: ['' || true], + cover_page_letter_head: ['' || true], + cover_page_background: ['' || true], + title: ['' || coverPageTemplate?.title], + location: ['' || coverPageTemplate?.location], + project_name: ['' || coverPageTemplate?.project_name], + project_description: ['' || coverPageTemplate?.project_description], + reference: ['' || coverPageTemplate?.reference], + info_header_text: ['' || coverPageTemplate?.info_header_text], + info_cover_body_1: ['' || coverPageTemplate?.info_cover_body_1], + info_cover_body_2: ['' || coverPageTemplate?.info_cover_body_2], + info_cover_body_3: ['' || coverPageTemplate?.info_cover_body_3], + info_cover_body_4: ['' || coverPageTemplate?.info_cover_body_4], + info_cover_body_5: ['' || coverPageTemplate?.info_cover_body_5], + info_cover_footer_1: ['' || coverPageTemplate?.info_cover_footer_1], + info_cover_footer_2: ['' || coverPageTemplate?.info_cover_footer_2], + info_cover_footer_3: ['' || coverPageTemplate?.info_cover_footer_3], + userpassword: ['' || template?.userpassword], + ownerpassword: ['' || template?.ownerpassword] + }); + } + + get formControls() { + return this.reportForm.controls; + } + + async loadTemplate(templateName: string): Promise { + try { + const template = await this.http + .get(`assets/report-templates/${templateName}.json`) + .toPromise(); + this.templates[templateName] = template; + this.populateFormWithDefaults(); + } catch (error) { + console.error('Error loading template:', error); + } + } + + onSubmit() { + // Check if the form is valid + if (this.reportForm.valid) { + // Call renderPDF method with form values + this.renderPDF(this.reportForm.value); + } else { + this.reportForm.markAllAsTouched(); + } + } + + async renderPDF(formValues: any) { + const coverpage = []; + const coverpage_letterhead = []; + const backgroundImage = []; + const projectContent = []; + const content = []; + + const coverPagetitleStyle = { + color: '#00275b', + bold: true, + fontSize: 26, + alignment: 'left', + margin: [0, 0, 0, 10] + }; + + const titleStyle = { + color: '#00275b', + bold: true, + fontSize: 14, + alignment: 'left', + margin: [0, 0, 0, 5] + }; + + const subtitleStyle = { + color: '#000000', + bold: true, + fontSize: 13, + alignment: 'left', + margin: [0, 0, 0, 3] + }; + + const textStyle = { + color: '#000000', + bold: false, + fontSize: 12, + alignment: 'left' + }; + + const tableHeaderStyle = { + fillColor: '#eaf2f5', + border: [false, true, false, true], + margin: [0, 5, 0, 5], + textTransform: 'uppercase' + }; + + const tableRowStyle = { + border: [false, true, false, true], + alignment: 'right', + fillColor: '#eaf2f5', + margin: [0, 5, 0, 5], + textTransform: 'uppercase' + }; + + if (formValues.cover_page) { + if (formValues.cover_page) { + if (formValues.info_header_text) { + // coverpage.push({ + // // Use unshift to add the text at the beginning of the array + // text: formValues.info_header_text, + // alignment: 'center', + // margin: [0, 0, 0, 0], // Adjust top margin as needed + // fontSize: 12, + // bold: true + // }); + } + } + + coverpage.push( + '\n\n\n\n', + { + columns: [{ text: formValues.title, ...coverPagetitleStyle }] + }, + { + columns: [{ text: formValues.info_cover_body_1, ...titleStyle }] + }, + { + columns: [{ text: formValues.info_cover_body_2, ...titleStyle }] + }, + { + columns: [{ text: formValues.reference, ...titleStyle }] + }, + { + columns: [{ text: formValues.info_cover_body_3, ...titleStyle }] + }, + '\n\n\n\n\n\n\n', + { + columns: [{ text: formValues.info_cover_body_4, ...titleStyle }] + }, + { + columns: [{ text: formValues.info_cover_body_5, ...titleStyle }] + }, + { + columns: [ + { + text: formValues.location + new Date().toDateString(), + ...titleStyle + } + ] + }, + '\n\n\n\n\n\n\n\n\n\n\n\n' + ); + + if ( + formValues.info_cover_footer_1 || + formValues.info_cover_footer_2 || + formValues.info_cover_footer_3 + ) { + const footerRow = []; + + if (formValues.info_cover_footer_1) { + footerRow.push({ + text: formValues.info_cover_footer_1, + color: '#aaaaab', + border: [true, true, false, true], + margin: [30, 10, 10, 5], + fontSize: 9, + alignment: 'center' + }); + } + + if (formValues.info_cover_footer_2) { + footerRow.push({ + text: formValues.info_cover_footer_2, + border: [false, true, false, true], + color: '#aaaaab', + margin: [70, 10, 10, 5], + fontSize: 9, + alignment: 'center' + }); + } + + if (formValues.info_cover_footer_3) { + footerRow.push({ + text: formValues.info_cover_footer_3, + border: [false, true, true, true], + color: '#aaaaab', + margin: [70, 10, 10, 5], + fontSize: 9, + alignment: 'center' + }); + } + + coverpage.push({ + table: { + body: [footerRow] + } + }); + } + } + + if (formValues.cover_page_letter_head && formValues.cover_page) { + coverpage_letterhead.push({ + image: await this.getBase64ImageFromURL( + '../../assets/img/letterhead.png' + ), + width: 600, + alignment: 'center', + margin: [0, -100, 0, 0] + }); + } + + if (formValues.cover_page_background && formValues.cover_page) { + // backgroundImage.push({ + // background: { + // image: await this.getBase64ImageFromURL( + // '../../assets/img/background.png' + // ), + // width: 600, + // margin: [0, 520, 0, 0] + // } + // }); + } + + // Check if project description exists + if (formValues.project_name && formValues.project_description) { + projectContent.push( + { + columns: [{ text: formValues.project_name, ...titleStyle }] + }, + '\n', + { + columns: [{ text: formValues.project_description, ...textStyle }] + }, + '\n' + ); + } + + // Iterate over each element in the report array + this.reportData.forEach((item) => { + if (item.title) { + content.push({ + text: item.title, + ...titleStyle + }); + } + + if (item.subtitle) { + content.push({ + text: item.subtitle, + ...subtitleStyle + }); + } + + if (item.break) { + // If item.break exists, add the appropriate number of line breaks + const breakline = '\n'.repeat(item.break); + content.push(breakline); + } + + if (item.ul) { + // if list exist + content.push({ + ul: item.ul + }); + } + + if (item.table) { + // If the item has a table, construct the table + const { tableColumns, tableValues } = item.table; + const tableHeaderRow = tableColumns.map((column) => ({ + text: column, + ...tableHeaderStyle + })); + const tableBodyRow = tableValues.map((value) => ({ + text: value, + border: [false, true, false, true] + })); + const table = { + table: { + headerRows: 1, + body: [tableHeaderRow, tableBodyRow] + } + }; + content.push(table); + } + }); + + const project = { + info: { + title: 'Hashtopolis Report', + author: 'xbenyx', + subject: 'Password Recovery' + }, + pageSize: 'A4', + pageMargins: [40, 80, 40, 60], + userPassword: formValues.userpassword, + ownerPassword: formValues.ownerpassword, + permissions: { + printing: 'highResolution', //'lowResolution' + modifying: false, + copying: false, + annotating: true, + fillingForms: true, + contentAccessibility: true, + documentAssembly: true + }, + footer: function (currentPage, pageCount) { + return { + margin: 10, + columns: [ + { + fontSize: 9, + italic: true, + text: [ + { + text: 'Page ' + currentPage.toString() + ' of ' + pageCount + } + ], + alignment: 'center' + } + ] + }; + }, + ...backgroundImage, + content: [ + ...coverpage_letterhead, + ...coverpage, + ...projectContent, + ...content + ], + styles: { + notesTitle: { + fontSize: 10, + bold: true, + margin: [0, 50, 0, 3] + }, + notesText: { + fontSize: 10 + } + }, + defaultStyle: { + columnGap: 20 + } + }; + + pdfMake.createPdf(project).open(); + } + + // Function creates converts the image in base64, so can be used in the report + getBase64ImageFromURL(url: string) { + return new Promise((resolve, reject) => { + const img = new Image(); + img.setAttribute('crossOrigin', 'anonymous'); + + img.onload = () => { + const canvas = document.createElement('canvas'); + canvas.width = img.width; + canvas.height = img.height; + + const ctx = canvas.getContext('2d'); + ctx.drawImage(img, 0, 0); + + const dataURL = canvas.toDataURL('image/png'); + + resolve(dataURL); + }; + + img.onerror = (error) => { + reject(error); + }; + + img.src = url; + }); + } +} diff --git a/src/assets/report-templates/default.json b/src/assets/report-templates/default.json new file mode 100644 index 00000000..36efb753 --- /dev/null +++ b/src/assets/report-templates/default.json @@ -0,0 +1,20 @@ +{ + "userpassword": "", + "ownerpassword": "hashtoadmin", + "cover_page": { + "title": "Hashlist Report", + "project_name": "Default Project Name", + "location": "Europe, ", + "project_description": "Default Project Description", + "info_header_text": "", + "info_cover_body_1": "Default Body 1", + "info_cover_body_2": "Default Body 2", + "reference": "Default Reference", + "info_cover_body_3": "Default Body 3", + "info_cover_body_4": "Default Body 4", + "info_cover_body_5": "Default Body 5", + "info_cover_footer_1": "", + "info_cover_footer_2": "", + "info_cover_footer_3": "" + } +} diff --git a/src/assets/report-templates/hashlists.json b/src/assets/report-templates/hashlists.json new file mode 100644 index 00000000..36efb753 --- /dev/null +++ b/src/assets/report-templates/hashlists.json @@ -0,0 +1,20 @@ +{ + "userpassword": "", + "ownerpassword": "hashtoadmin", + "cover_page": { + "title": "Hashlist Report", + "project_name": "Default Project Name", + "location": "Europe, ", + "project_description": "Default Project Description", + "info_header_text": "", + "info_cover_body_1": "Default Body 1", + "info_cover_body_2": "Default Body 2", + "reference": "Default Reference", + "info_cover_body_3": "Default Body 3", + "info_cover_body_4": "Default Body 4", + "info_cover_body_5": "Default Body 5", + "info_cover_footer_1": "", + "info_cover_footer_2": "", + "info_cover_footer_3": "" + } +}