From 2db1b4d7a504f70e773d8077f22eccfca4a1c9cc Mon Sep 17 00:00:00 2001 From: Sam David Date: Sat, 14 Dec 2024 00:19:02 +0530 Subject: [PATCH] --wip-- [skip ci] --- app/adapters/available-automated-device.ts | 13 ++ app/adapters/commondrf-nested.ts | 30 +++ app/adapters/ds-automation-preference.ts | 17 ++ .../dynamic-scan/action/index.hbs | 113 +++------- .../file-details/dynamic-scan/action/index.ts | 50 ++++- .../dynamic-scan/automated/index.hbs | 3 +- .../dynamic-scan/automated/index.ts | 13 +- .../file-details/dynamic-scan/header/index.ts | 31 ++- .../file-details/dynamic-scan/manual/index.ts | 4 +- .../dynamic-scan/status-chip/index.ts | 4 +- .../device-preference/index.hbs | 68 ------ .../device-preference/index.ts | 24 --- app/components/project-preferences/index.hbs | 8 +- app/components/project-preferences/index.ts | 14 +- .../project-preferences/provider/index.hbs | 7 - .../project-preferences/provider/index.ts | 200 +----------------- .../index.hbs | 26 ++- .../index.ts | 41 +++- .../dynamicscan-automation-settings/index.hbs | 75 ++++--- .../dynamicscan-automation-settings/index.ts | 62 ++---- .../general-settings/index.hbs | 50 ++--- .../general-settings/index.ts | 16 +- app/components/vnc-viewer/index.hbs | 4 +- .../dashboard/file/dynamic-scan/automated.ts | 48 +++++ .../dashboard/file/dynamic-scan/manual.ts | 2 +- app/models/available-automated-device.ts | 9 + app/models/available-manual-device.ts | 9 + app/models/device.ts | 55 ++++- app/models/ds-automation-preference.ts | 12 ++ app/models/ds-manual-device-preference.ts | 18 ++ app/models/dynamicscan.ts | 99 ++++----- app/router.ts | 2 +- app/serializers/dynamicscan.js | 8 + .../dynamic-scan/action/drawer-test.js | 25 +-- .../available-automated-device-test.js | 12 ++ tests/unit/adapters/commondrf-nested-test.js | 13 ++ .../adapters/ds-automation-preference-test.js | 12 ++ .../file/dynamic-scan/automated-test.js | 17 ++ .../models/available-automated-device-test.js | 13 ++ .../models/available-manual-device-test.js | 13 ++ .../models/ds-automation-preference-test.js | 13 ++ .../ds-manual-device-preference-test.js | 13 ++ 42 files changed, 634 insertions(+), 632 deletions(-) create mode 100644 app/adapters/available-automated-device.ts create mode 100644 app/adapters/commondrf-nested.ts create mode 100644 app/adapters/ds-automation-preference.ts delete mode 100644 app/components/project-preferences/device-preference/index.hbs delete mode 100644 app/components/project-preferences/device-preference/index.ts create mode 100644 app/controllers/authenticated/dashboard/file/dynamic-scan/automated.ts create mode 100644 app/models/available-automated-device.ts create mode 100644 app/models/available-manual-device.ts create mode 100644 app/models/ds-automation-preference.ts create mode 100644 app/models/ds-manual-device-preference.ts create mode 100644 app/serializers/dynamicscan.js create mode 100644 tests/unit/adapters/available-automated-device-test.js create mode 100644 tests/unit/adapters/commondrf-nested-test.js create mode 100644 tests/unit/adapters/ds-automation-preference-test.js create mode 100644 tests/unit/controllers/authenticated/dashboard/file/dynamic-scan/automated-test.js create mode 100644 tests/unit/models/available-automated-device-test.js create mode 100644 tests/unit/models/available-manual-device-test.js create mode 100644 tests/unit/models/ds-automation-preference-test.js create mode 100644 tests/unit/models/ds-manual-device-preference-test.js diff --git a/app/adapters/available-automated-device.ts b/app/adapters/available-automated-device.ts new file mode 100644 index 000000000..73bbc3bda --- /dev/null +++ b/app/adapters/available-automated-device.ts @@ -0,0 +1,13 @@ +import CommondrfNestedAdapter from './commondrf-nested'; + +export default class AvailableAutomatedDeviceAdapter extends CommondrfNestedAdapter { + setNestedUrlNamespace(projectId: string) { + this.namespace = `${this.namespace_v2}/projects/${projectId}`; + } +} + +declare module 'ember-data/types/registries/adapter' { + export default interface AdapterRegistry { + 'available-automated-device': AvailableAutomatedDeviceAdapter; + } +} diff --git a/app/adapters/commondrf-nested.ts b/app/adapters/commondrf-nested.ts new file mode 100644 index 000000000..9cbd32301 --- /dev/null +++ b/app/adapters/commondrf-nested.ts @@ -0,0 +1,30 @@ +import CommonDRFAdapter from './commondrf'; +import { underscore } from '@ember/string'; +import type ModelRegistry from 'ember-data/types/registries/model'; + +export default class CommondrfNestedAdapter extends CommonDRFAdapter { + namespace = ''; + pathTypeName: keyof ModelRegistry | null = null; + + pathForType(type: keyof ModelRegistry) { + return underscore(super.pathForType(this.pathTypeName || type)); + } + + handleResponse( + status: number, + headers: object, + payload: object, + requestData: object + ) { + if (status >= 400 && this.namespace === '') { + throw new Error( + 'setNestUrlNamespace should be called before making a request' + ); + } + + // reset namespace + this.namespace = ''; + + return super.handleResponse(status, headers, payload, requestData); + } +} diff --git a/app/adapters/ds-automation-preference.ts b/app/adapters/ds-automation-preference.ts new file mode 100644 index 000000000..acc2787dc --- /dev/null +++ b/app/adapters/ds-automation-preference.ts @@ -0,0 +1,17 @@ +import CommondrfNestedAdapter from './commondrf-nested'; + +export default class DsAutomationPreferenceAdapter extends CommondrfNestedAdapter { + pathForType() { + return 'automation_preference'; + } + + setNestedUrlNamespace(profileId: string) { + this.namespace = `${this.namespace_v2}/profiles/${profileId}`; + } +} + +declare module 'ember-data/types/registries/adapter' { + export default interface AdapterRegistry { + 'ds-automation-preference': DsAutomationPreferenceAdapter; + } +} diff --git a/app/components/file-details/dynamic-scan/action/index.hbs b/app/components/file-details/dynamic-scan/action/index.hbs index 5348893c9..9b9b410f1 100644 --- a/app/components/file-details/dynamic-scan/action/index.hbs +++ b/app/components/file-details/dynamic-scan/action/index.hbs @@ -1,87 +1,36 @@ - {{! TODO: Logic should be replaced by comments when full DAST feature is ready }} - {{#if @dynamicScan.isReadyOrRunning}} - {{#if @isAutomatedScan}} - - <:leftIcon> - {{#if this.dynamicShutdown.isRunning}} - - {{else}} - - {{/if}} - - - <:default>{{t 'cancelScan'}} - - {{else}} - - <:leftIcon> - {{#if this.dynamicShutdown.isRunning}} - - {{else}} - - {{/if}} - - - <:default>{{t 'stop'}} - - {{/if}} + + <:leftIcon> + + - {{else if (or @file.isDynamicDone @dynamicScan.isDynamicStatusError)}} - - <:leftIcon> - - + <:default> + {{this.dynamicScanActionButton.text}} + + - <:default>{{@dynamicScanText}} - - {{else}} - - <:leftIcon> - - - - <:default>{{@dynamicScanText}} - + + {{/if}} - - -{{#if this.showDynamicScanDrawer}} - - - -{{/if}} \ No newline at end of file + \ No newline at end of file diff --git a/app/components/file-details/dynamic-scan/action/index.ts b/app/components/file-details/dynamic-scan/action/index.ts index 3e4b339a9..26c118b60 100644 --- a/app/components/file-details/dynamic-scan/action/index.ts +++ b/app/components/file-details/dynamic-scan/action/index.ts @@ -3,6 +3,7 @@ import { tracked } from '@glimmer/tracking'; import { inject as service } from '@ember/service'; import { task } from 'ember-concurrency'; import { action } from '@ember/object'; +import type IntlService from 'ember-intl/services/intl'; import ENV from 'irene/config/environment'; import triggerAnalytics from 'irene/utils/trigger-analytics'; @@ -21,7 +22,7 @@ export interface DynamicScanActionSignature { } export default class DynamicScanActionComponent extends Component { - @service declare ajax: any; + @service declare intl: IntlService; @service('notifications') declare notify: NotificationService; @tracked showDynamicScanDrawer = false; @@ -38,6 +39,49 @@ export default class DynamicScanActionComponent extends Component this.dynamicShutdown.perform(), + loading: this.dynamicShutdown.isRunning, + }; + } + + if (this.args.dynamicScan?.isReadyOrRunning) { + return { + icon: 'stop-circle', + text: this.intl.t('stop'), + testId: 'stopBtn', + loading: this.dynamicShutdown.isRunning, + onClick: () => this.dynamicShutdown.perform(), + }; + } + + if ( + this.args.dynamicScan?.isCompleted || + this.args.dynamicScan?.isStatusError + ) { + return { + icon: 'refresh', + text: this.args.dynamicScanText, + testId: 'restartBtn', + onClick: this.openDynamicScanDrawer, + }; + } + + return { + icon: 'play-arrow', + text: this.args.dynamicScanText, + testId: 'startBtn', + onClick: this.openDynamicScanDrawer, + }; + } + @action openDynamicScanDrawer() { triggerAnalytics( @@ -54,15 +98,11 @@ export default class DynamicScanActionComponent extends Component { - this.args.dynamicScan?.setShuttingDown(); - try { await this.args.dynamicScan?.destroyRecord(); this.args.onScanShutdown?.(); } catch (error) { - this.args.dynamicScan?.setNone(); - this.notify.error((error as AdapterError).payload.error); } }); diff --git a/app/components/file-details/dynamic-scan/automated/index.hbs b/app/components/file-details/dynamic-scan/automated/index.hbs index b93d06ea1..1e6c58ea8 100644 --- a/app/components/file-details/dynamic-scan/automated/index.hbs +++ b/app/components/file-details/dynamic-scan/automated/index.hbs @@ -1,4 +1,4 @@ -{{#if this.fetchDynamicscan.isRunning}} +{{#if (or this.getDynamicscanMode.isRunning this.fetchDynamicscan.isRunning)}} { try { const dynScanMode = await waitForPromise( @@ -69,10 +75,13 @@ export default class FileDetailsDastAutomated extends Component { - const id = this.args.profileId; + const file = this.args.file; try { - this.dynamicScan = await this.store.findRecord('dynamicscan', id); + this.dynamicScan = await file.getLastDynamicScan( + file.id, + ENUMS.DYNAMIC_MODE.AUTOMATED + ); } catch (e) { this.notify.error(parseError(e, this.intl.t('pleaseTryAgain'))); } diff --git a/app/components/file-details/dynamic-scan/header/index.ts b/app/components/file-details/dynamic-scan/header/index.ts index e6c216ba3..4f89e5bb4 100644 --- a/app/components/file-details/dynamic-scan/header/index.ts +++ b/app/components/file-details/dynamic-scan/header/index.ts @@ -6,6 +6,16 @@ import type Store from '@ember-data/store'; import type DynamicscanModel from 'irene/models/dynamicscan'; import type FileModel from 'irene/models/file'; +import type ConfigurationService from 'irene/services/configuration'; + +interface TabItem { + id: string; + label: string; + route: string; + activeRoutes: string; + inProgress?: boolean; + count?: number; +} export interface FileDetailsDastHeaderSignature { Args: { @@ -19,6 +29,7 @@ export default class FileDetailsDastHeader extends Component - {{#if (has-block 'title')}} - {{yield to='title'}} - {{else}} - - {{t 'devicePreferences'}} - - {{/if}} - - - {{t 'otherTemplates.selectPreferredDevice'}} - - - - -
- - {{t (device-type aks.value)}} - -
- -
- - {{#if (eq version '0')}} - {{t 'anyVersion'}} - {{else}} - {{version}} - {{/if}} - -
-
- -{{#if this.isPreferredDeviceNotAvailable}} - - - - - {{t 'modalCard.dynamicScan.preferredDeviceNotAvailable'}} - - -{{/if}} \ No newline at end of file diff --git a/app/components/project-preferences/device-preference/index.ts b/app/components/project-preferences/device-preference/index.ts deleted file mode 100644 index 093651bda..000000000 --- a/app/components/project-preferences/device-preference/index.ts +++ /dev/null @@ -1,24 +0,0 @@ -import Component from '@glimmer/component'; -import { DevicePreferenceContext } from '../provider'; - -export interface ProjectPreferencesDevicePreferenceSignature { - Args: { - dpContext: DevicePreferenceContext; - }; - Blocks: { - title: []; - }; -} - -export default class ProjectPreferencesDevicePreferenceComponent extends Component { - get isPreferredDeviceNotAvailable() { - return this.args.dpContext.isPreferredDeviceAvailable === false; - } -} - -declare module '@glint/environment-ember-loose/registry' { - export default interface Registry { - 'ProjectPreferences::DevicePreference': typeof ProjectPreferencesDevicePreferenceComponent; - 'project-preferences/device-preference': typeof ProjectPreferencesDevicePreferenceComponent; - } -} diff --git a/app/components/project-preferences/index.hbs b/app/components/project-preferences/index.hbs index 3e8656e94..2736e0f4e 100644 --- a/app/components/project-preferences/index.hbs +++ b/app/components/project-preferences/index.hbs @@ -4,11 +4,5 @@ @platform={{@platform}} as |dpContext| > - {{yield - (hash - DevicePreferenceComponent=(component - 'project-preferences/device-preference' dpContext=dpContext - ) - ) - }} + {{yield dpContext}} \ No newline at end of file diff --git a/app/components/project-preferences/index.ts b/app/components/project-preferences/index.ts index f56830e4b..468e94e0f 100644 --- a/app/components/project-preferences/index.ts +++ b/app/components/project-preferences/index.ts @@ -1,8 +1,7 @@ import Component from '@glimmer/component'; -import { WithBoundArgs } from '@glint/template'; -import ProjectModel from 'irene/models/project'; -import ProjectPreferencesDevicePreferenceComponent from './device-preference'; +import type ProjectModel from 'irene/models/project'; +import type { DevicePreferenceContext } from './provider'; export interface ProjectPreferencesSignature { Args: { @@ -11,14 +10,7 @@ export interface ProjectPreferencesSignature { platform?: number; }; Blocks: { - default: [ - { - DevicePreferenceComponent: WithBoundArgs< - typeof ProjectPreferencesDevicePreferenceComponent, - 'dpContext' - >; - }, - ]; + default: [dpContext: DevicePreferenceContext]; }; } diff --git a/app/components/project-preferences/provider/index.hbs b/app/components/project-preferences/provider/index.hbs index 6749e875f..71dea794f 100644 --- a/app/components/project-preferences/provider/index.hbs +++ b/app/components/project-preferences/provider/index.hbs @@ -1,14 +1,7 @@ {{yield (hash - deviceTypes=this.filteredDeviceTypes - selectedDeviceType=this.selectedDeviceType - handleSelectDeviceType=this.handleSelectDeviceType - selectedVersion=this.selectedVersion - devicePlatformVersions=this.devicePlatformVersionOptions - handleSelectVersion=this.handleSelectVersion handleDsAutomatedMinOSVersionSelect=this.handleDsAutomatedMinOSVersionSelect projectProfile=this.projectProfile - isPreferredDeviceAvailable=this.isPreferredDeviceAvailable dsManualDevicePreference=this.dsManualDevicePreference dsAutomatedDevicePreference=this.dsAutomatedDevicePreference handleSelectDsManualIdentifier=this.handleSelectDsManualIdentifier diff --git a/app/components/project-preferences/provider/index.ts b/app/components/project-preferences/provider/index.ts index 791d595b6..290193c5b 100644 --- a/app/components/project-preferences/provider/index.ts +++ b/app/components/project-preferences/provider/index.ts @@ -1,6 +1,3 @@ -// eslint-disable-next-line ember/use-ember-data-rfc-395-imports -import type DS from 'ember-data'; - import Component from '@glimmer/component'; import { inject as service } from '@ember/service'; import { tracked } from '@glimmer/tracking'; @@ -8,14 +5,11 @@ import { action } from '@ember/object'; import { task } from 'ember-concurrency'; import ENUMS from 'irene/enums'; -import ENV from 'irene/config/environment'; import type Store from '@ember-data/store'; import type IntlService from 'ember-intl/services/intl'; import type ProjectModel from 'irene/models/project'; -import type DevicePreferenceModel from 'irene/models/device-preference'; -import type ProjectAvailableDeviceModel from 'irene/models/project-available-device'; import ProfileModel, { type SetProfileDSAutomatedDevicePrefData, @@ -27,18 +21,11 @@ import ProfileModel, { type ProfileDsSelectFuncHandler = (option: { value: number }) => void; export interface DevicePreferenceContext { - deviceTypes: DeviceType[]; - selectedDeviceType?: DeviceType; - selectedVersion: string; - devicePlatformVersions: string[]; - isPreferredDeviceAvailable: boolean | null; projectProfile?: ProfileModel | null; dsAutomatedDevicePreference?: Partial; dsManualDevicePreference?: Partial; - handleSelectDeviceType: (deviceType: DeviceType) => void; - handleSelectVersion: (version: string) => void; handleDsAutomatedMinOSVersionSelect: (version: string) => void; handleSelectDsManualIdentifier(id: string): void; handleSelectDsAutomatedDeviceCapability(event: Event, checked: boolean): void; @@ -62,37 +49,17 @@ export interface ProjectPreferencesProviderSignature { }; } -type EnumObject = { key: string; value: number | string }; -type DeviceType = EnumObject; - export default class ProjectPreferencesProviderComponent extends Component { @service declare intl: IntlService; - @service declare ajax: any; @service('notifications') declare notify: NotificationService; @service declare store: Store; - @tracked selectedVersion = '0'; - - @tracked selectedDeviceType?: DeviceType; - @tracked deviceTypes = ENUMS.DEVICE_TYPE.CHOICES; - @tracked devicePreference?: DevicePreferenceModel; - @tracked projectProfile?: ProfileModel | null = null; @tracked dsManualDevicePreference?: Partial; - @tracked - dsManualDevicePreferenceCopy?: Partial; - @tracked dsAutomatedDevicePreference?: Partial; - @tracked - dsAutomatedDevicePreferenceCopy?: Partial; - - @tracked - devices: DS.AdapterPopulatedRecordArray | null = - null; - constructor( owner: unknown, args: ProjectPreferencesProviderSignature['Args'] @@ -100,102 +67,6 @@ export default class ProjectPreferencesProviderComponent extends Component ENUMS.DEVICE_TYPE.UNKNOWN !== type.value - ); - } - - get availableDevices() { - return this.devices?.filter( - (d) => d.platform === this.args.project?.get('platform') - ); - } - - get filteredDevices() { - return this.availableDevices?.filter((device) => { - switch (this.selectedDeviceType?.value) { - case ENUMS.DEVICE_TYPE.NO_PREFERENCE: - return true; - - case ENUMS.DEVICE_TYPE.TABLET_REQUIRED: - return device.isTablet; - - case ENUMS.DEVICE_TYPE.PHONE_REQUIRED: - return !device.isTablet; - - default: - return true; - } - }); - } - - get uniqueDevices() { - return this.filteredDevices?.uniqBy('platformVersion'); - } - - get devicePlatformVersionOptions() { - return ['0', ...(this.uniqueDevices?.map((d) => d.platformVersion) || [])]; - } - - get isPreferredDeviceAvailable() { - // check whether preferences & devices are resolved - if (this.devicePreference && this.uniqueDevices) { - const deviceType = Number(this.devicePreference.deviceType); - const version = this.devicePreference.platformVersion; - - // if both device type and os is any then return true - if (deviceType === 0 && version === '0') { - return true; - } - - // if os is any then return true - if (version === '0') { - return true; - } - - // check if preferred device type & os exists - return this.uniqueDevices.some((d) => { - // if only device type is any then just check version - if (deviceType === 0) { - return d.platformVersion === version; - } - - return ( - d.platformVersion === version && - (d.isTablet - ? deviceType === ENUMS.DEVICE_TYPE.TABLET_REQUIRED - : deviceType === ENUMS.DEVICE_TYPE.PHONE_REQUIRED) - ); - }); - } - - return null; - } - - @action - handleSelectDeviceType(deviceType: DeviceType) { - this.selectedDeviceType = deviceType; - this.selectedVersion = '0'; - - this.versionSelected.perform(); - } - - @action - handleSelectVersion(version: string) { - this.selectedVersion = version; - - this.versionSelected.perform(); } @action @@ -311,7 +182,6 @@ export default class ProjectPreferencesProviderComponent extends Component { + const dsManualDevicePreferenceCopy = this.dsManualDevicePreference; + try { const dsManualDevicePreference = await this.projectProfile?.setDSManualDevicePrefData(data); this.dsManualDevicePreference = dsManualDevicePreference; - this.dsManualDevicePreferenceCopy = dsManualDevicePreference; this.notify.success(this.intl.t('savedPreferences')); } catch (error) { - this.dsManualDevicePreference = this.dsManualDevicePreferenceCopy; + this.dsManualDevicePreference = dsManualDevicePreferenceCopy; this.notify.error(this.intl.t('errorFetchingDsManualDevicePref')); } @@ -337,16 +208,17 @@ export default class ProjectPreferencesProviderComponent extends Component { + const dsAutomatedDevicePreferenceCopy = this.dsAutomatedDevicePreference; + try { const dsAutomatedDevicePreference = await this.projectProfile?.setDSAutomatedDevicePrefData(data); this.dsAutomatedDevicePreference = dsAutomatedDevicePreference; - this.dsAutomatedDevicePreferenceCopy = dsAutomatedDevicePreference; this.notify.success(this.intl.t('savedPreferences')); } catch (error) { - this.dsAutomatedDevicePreference = this.dsAutomatedDevicePreferenceCopy; + this.dsAutomatedDevicePreference = dsAutomatedDevicePreferenceCopy; this.notify.error(this.intl.t('failedToUpdateDsAutomatedDevicePref')); } @@ -359,70 +231,10 @@ export default class ProjectPreferencesProviderComponent extends Component { - try { - const profileId = this.args.profileId; - - const devicePreferences = [ - ENV.endpoints['profiles'], - profileId, - ENV.endpoints['devicePreferences'], - ].join('/'); - - const data = { - device_type: this.selectedDeviceType?.value, - platform_version: this.selectedVersion, - }; - - await this.ajax.put(devicePreferences, { data }); - - if (!this.isDestroyed && this.devicePreference) { - this.devicePreference.deviceType = this.selectedDeviceType - ?.value as number; - - this.devicePreference.platformVersion = this.selectedVersion; - - this.notify.success(this.intl.t('savedPreferences')); - } - } catch (e) { - this.notify.error(this.intl.t('somethingWentWrong')); - } - }); - - fetchDevicePreference = task(async () => { - try { - this.devicePreference = await this.store.queryRecord( - 'device-preference', - { - id: this.args.profileId, - } - ); - - this.selectedDeviceType = this.filteredDeviceTypes.find( - (it) => it.value === this.devicePreference?.deviceType - ); - - this.selectedVersion = this.devicePreference.platformVersion; - } catch (error) { - this.notify.error(this.intl.t('errorFetchingDevicePreferences')); - } - }); - - fetchDevices = task(async () => { - try { - this.devices = await this.store.query('project-available-device', { - projectId: this.args.project?.get('id'), - }); - } catch (error) { - this.notify.error(this.intl.t('errorFetchingDevices')); - } - }); } declare module '@glint/environment-ember-loose/registry' { diff --git a/app/components/project-settings/general-settings/device-preferences-automated-dast/index.hbs b/app/components/project-settings/general-settings/device-preferences-automated-dast/index.hbs index c53ae2219..6b690e0dc 100644 --- a/app/components/project-settings/general-settings/device-preferences-automated-dast/index.hbs +++ b/app/components/project-settings/general-settings/device-preferences-automated-dast/index.hbs @@ -43,19 +43,27 @@ @direction='column' local-class='device-preference-table' > - {{!-- + {{t 'deviceType'}} {{#if dpContext}} - - - + + + + + + + - + {{else}} @@ -71,7 +79,7 @@ {{/if}} - --}} + - {{version}} + {{#if (eq version '')}} + {{t 'anyVersion'}} + {{else}} + {{version}} + {{/if}} {{/if}} diff --git a/app/components/project-settings/general-settings/device-preferences-automated-dast/index.ts b/app/components/project-settings/general-settings/device-preferences-automated-dast/index.ts index bdd0e7a38..a408810b6 100644 --- a/app/components/project-settings/general-settings/device-preferences-automated-dast/index.ts +++ b/app/components/project-settings/general-settings/device-preferences-automated-dast/index.ts @@ -1,11 +1,15 @@ import { action } from '@ember/object'; import Component from '@glimmer/component'; import { inject as service } from '@ember/service'; -import ENUMS from 'irene/enums'; +import { tracked } from '@glimmer/tracking'; +import { task } from 'ember-concurrency'; +import ENUMS from 'irene/enums'; import type IntlService from 'ember-intl/services/intl'; +import type Store from '@ember-data/store'; import type ProjectModel from 'irene/models/project'; import { type ProfileDSAutomatedDevicePrefData } from 'irene/models/profile'; +import type AvailableAutomatedDeviceModel from 'irene/models/available-automated-device'; export interface ProjectSettingsGeneralSettingsDevicePreferencesAutomatedDastSignature { Args: { @@ -18,19 +22,34 @@ export interface ProjectSettingsGeneralSettingsDevicePreferencesAutomatedDastSig export default class ProjectSettingsGeneralSettingsDevicePreferencesAutomatedDastComponent extends Component { @service declare intl: IntlService; + @service declare store: Store; + + @tracked availableAutomatedDevices: AvailableAutomatedDeviceModel[] = []; + + constructor( + owner: unknown, + args: ProjectSettingsGeneralSettingsDevicePreferencesAutomatedDastSignature['Args'] + ) { + super(owner, args); + + this.fetchAvailableAutomatedDevicesTask.perform(); + } deviceSelectionTypes = ENUMS.DS_AUTOMATED_DEVICE_SELECTION.BASE_CHOICES; filterDsAutomatedDeviceCriteria = ENUMS.DS_AUTOMATED_DEVICE_SELECTION.FILTER_CRITERIA; - get isIOSApp() { - return this.args.project?.platform === ENUMS.PLATFORM.IOS; + get minOSVersionOptions() { + const platformVersions = this.availableAutomatedDevices.map( + (it) => it.platformVersion + ); + + return ['', ...platformVersions.uniq()]; } - // TODO: Values to be updated in the future when DAST is supported on prem - get minOSVersionOptions() { - return this.isIOSApp ? ['13', '14', '15', '16'] : ['9', '10', '12', '13']; + get isIOSApp() { + return this.args.project?.platform === ENUMS.PLATFORM.IOS; } @action getChosenDeviceSelection(selectedDevice?: string | number) { @@ -61,6 +80,16 @@ export default class ProjectSettingsGeneralSettingsDevicePreferencesAutomatedDas }, ]; } + + fetchAvailableAutomatedDevicesTask = task(async () => { + const adapter = this.store.adapterFor('available-automated-device'); + + adapter.setNestedUrlNamespace(this.args.project?.id as string); + + const devices = await this.store.findAll('available-automated-device'); + + this.availableAutomatedDevices = devices.slice(); + }); } declare module '@glint/environment-ember-loose/registry' { diff --git a/app/components/project-settings/general-settings/dynamicscan-automation-settings/index.hbs b/app/components/project-settings/general-settings/dynamicscan-automation-settings/index.hbs index d970af041..6e3fd88f7 100644 --- a/app/components/project-settings/general-settings/dynamicscan-automation-settings/index.hbs +++ b/app/components/project-settings/general-settings/dynamicscan-automation-settings/index.hbs @@ -25,49 +25,48 @@ - {{! TODO: Uncomment when feature is available }} - {{!-- {{#if @featureAvailable}} --}} - + {{#if @featureAvailable}} + - - - + - - - + + + + - - - {{t 'dynScanAutoSchedNote'}} - + + + {{t 'dynScanAutoSchedNote'}} + + - - {{!-- {{else}} + {{else}} - {{/if}} --}} + {{/if}} \ No newline at end of file diff --git a/app/components/project-settings/general-settings/dynamicscan-automation-settings/index.ts b/app/components/project-settings/general-settings/dynamicscan-automation-settings/index.ts index 21a1f8cc6..9329a2f44 100644 --- a/app/components/project-settings/general-settings/dynamicscan-automation-settings/index.ts +++ b/app/components/project-settings/general-settings/dynamicscan-automation-settings/index.ts @@ -2,13 +2,12 @@ import Component from '@glimmer/component'; import { inject as service } from '@ember/service'; import { tracked } from '@glimmer/tracking'; import { task } from 'ember-concurrency'; -import Store from '@ember-data/store'; -import IntlService from 'ember-intl/services/intl'; -import { waitForPromise } from '@ember/test-waiters'; +import type Store from '@ember-data/store'; +import type IntlService from 'ember-intl/services/intl'; -import ENV from 'irene/config/environment'; -import ProjectModel from 'irene/models/project'; import parseError from 'irene/utils/parse-error'; +import type ProjectModel from 'irene/models/project'; +import type DsAutomationPreferenceModel from 'irene/models/ds-automation-preference'; export interface ProjectSettingsGeneralSettingsDyanmicscanAutomationSettingsSignature { Args: { @@ -21,10 +20,9 @@ export interface ProjectSettingsGeneralSettingsDyanmicscanAutomationSettingsSign export default class ProjectSettingsGeneralSettingsDyanmicscanAutomationSettingsComponent extends Component { @service declare store: Store; @service declare intl: IntlService; - @service declare ajax: any; @service('notifications') declare notify: NotificationService; - @tracked automationEnabled = false; + @tracked automationPreference: DsAutomationPreferenceModel | null = null; constructor( owner: unknown, @@ -57,58 +55,34 @@ export default class ProjectSettingsGeneralSettingsDyanmicscanAutomationSettings getDynamicscanMode = task(async () => { try { - const dynScanMode = await waitForPromise( - this.store.queryRecord('dynamicscan-mode', { - id: this.profileId, - }) - ); + const adapter = this.store.adapterFor('ds-automation-preference'); + + adapter.setNestedUrlNamespace(this.profileId as string); - this.automationEnabled = dynScanMode.dynamicscanMode === 'Automated'; + this.automationPreference = await this.store.queryRecord( + 'ds-automation-preference', + {} + ); } catch (error) { this.notify.error(parseError(error, this.tPleaseTryAgain)); } }); - toggleDynamicscanMode = task(async () => { + toggleDynamicscanMode = task(async (_: Event, enabled: boolean) => { try { - this.automationEnabled = !this.automationEnabled; - - const dynamicscanMode = [ - ENV.endpoints['profiles'], - this.profileId, - ENV.endpoints['dynamicscanMode'], - ].join('/'); - - const data = { - dynamicscan_mode: this.automationEnabled ? 'Automated' : 'Manual', - }; + this.automationPreference?.set('dynamicScanAutomationEnabled', enabled); - await waitForPromise(this.ajax.put(dynamicscanMode, { data })); + await this.automationPreference?.save(); - const successMsg = this.automationEnabled + const successMsg = enabled ? this.tAppiumScheduledAutomationSuccessOn : this.tAppiumScheduledAutomationSuccessOff; this.notify.success(successMsg); } catch (err) { - const error = err as AdapterError; - this.automationEnabled = !this.automationEnabled; - - if (error.payload) { - Object.keys(error.payload).forEach((p) => { - let errMsg = error.payload[p]; - - if (typeof errMsg !== 'string') { - errMsg = error.payload[p][0]; - } - - this.notify.error(errMsg); - }); - - return; - } + this.automationPreference?.rollbackAttributes(); - this.notify.error(parseError(error, this.tSomethingWentWrong)); + this.notify.error(parseError(err, this.tSomethingWentWrong)); } }); } diff --git a/app/components/project-settings/general-settings/index.hbs b/app/components/project-settings/general-settings/index.hbs index 3f51459e9..cadaf941c 100644 --- a/app/components/project-settings/general-settings/index.hbs +++ b/app/components/project-settings/general-settings/index.hbs @@ -5,25 +5,6 @@ local-class='general-settings-root' data-test-projectSettings-generalSettings-root > - - - - <:title> - - {{t 'devicePreferences'}} - - - - - - - - @@ -86,24 +67,25 @@ /> - + {{#unless this.orgIsAnEnterprise}} + - - - + + + - {{! TODO: uncomment when Automated DAST is ready}} - {{!-- - + - - --}} + + + + {{/unless}} diff --git a/app/components/project-settings/general-settings/index.ts b/app/components/project-settings/general-settings/index.ts index 87e77f5ab..d08fba1e7 100644 --- a/app/components/project-settings/general-settings/index.ts +++ b/app/components/project-settings/general-settings/index.ts @@ -2,13 +2,14 @@ import Component from '@glimmer/component'; import { inject as service } from '@ember/service'; import { tracked } from '@glimmer/tracking'; import { task } from 'ember-concurrency'; -import Store from '@ember-data/store'; import { waitForPromise } from '@ember/test-waiters'; +import type Store from '@ember-data/store'; -import MeService from 'irene/services/me'; -import ProjectModel from 'irene/models/project'; -import OrganizationService from 'irene/services/organization'; -import ProfileModel from 'irene/models/profile'; +import type ProjectModel from 'irene/models/project'; +import type ProfileModel from 'irene/models/profile'; +import type MeService from 'irene/services/me'; +import type OrganizationService from 'irene/services/organization'; +import type ConfigurationService from 'irene/services/configuration'; interface ProjectSettingsGeneralSettingsSignature { Args: { @@ -19,6 +20,7 @@ interface ProjectSettingsGeneralSettingsSignature { export default class ProjectSettingsGeneralSettingsComponent extends Component { @service declare me: MeService; @service declare organization: OrganizationService; + @service declare configuration: ConfigurationService; @service declare store: Store; @tracked profile: ProfileModel | null = null; @@ -40,6 +42,10 @@ export default class ProjectSettingsGeneralSettingsComponent extends Component

{ try { const profileId = this.args.project?.activeProfileId; diff --git a/app/components/vnc-viewer/index.hbs b/app/components/vnc-viewer/index.hbs index 90980f877..1d75bea2c 100644 --- a/app/components/vnc-viewer/index.hbs +++ b/app/components/vnc-viewer/index.hbs @@ -20,14 +20,14 @@ {{/if}} - {{#if @dynamicScan.isDynamicStatusInProgress}} + {{#if @dynamicScan.isStarting}} {{t 'note'}} - {{t 'dynamicScanText'}} {{/if}} - {{#if (and this.isAutomated @dynamicScan.isDynamicStatusInProgress)}} + {{#if (and this.isAutomated @dynamicScan.isStartingOrShuttingInProgress)}} diff --git a/app/controllers/authenticated/dashboard/file/dynamic-scan/automated.ts b/app/controllers/authenticated/dashboard/file/dynamic-scan/automated.ts new file mode 100644 index 000000000..6fe3aa2f2 --- /dev/null +++ b/app/controllers/authenticated/dashboard/file/dynamic-scan/automated.ts @@ -0,0 +1,48 @@ +import Controller from '@ember/controller'; +import { service } from '@ember/service'; + +import type IntlService from 'ember-intl/services/intl'; +import type FileModel from 'irene/models/file'; +import { type AkBreadcrumbsItemProps } from 'irene/services/ak-breadcrumbs'; + +export default class AuthenticatedDashboardFileDynamicScanAutomatedController extends Controller { + @service declare intl: IntlService; + + declare model: { file: FileModel; profileId: number }; + + get breadcrumbs(): AkBreadcrumbsItemProps { + const routeModels = [this.model?.file?.id]; + + const crumb: AkBreadcrumbsItemProps = { + title: this.intl.t('dastTabs.automatedDAST'), + route: 'authenticated.dashboard.file.dynamic-scan.automated', + models: routeModels, + routeGroup: 'project/files', + + siblingRoutes: [ + 'authenticated.dashboard.file.dynamic-scan.results', + 'authenticated.dashboard.file.dynamic-scan.manual', + ], + }; + + const parentCrumb: AkBreadcrumbsItemProps['parentCrumb'] = { + title: this.intl.t('scanDetails'), + route: 'authenticated.dashboard.file', + models: routeModels, + routeGroup: 'project/files', + }; + + return { + ...crumb, + parentCrumb, + fallbackCrumbs: [ + { + title: this.intl.t('allProjects'), + route: 'authenticated.dashboard.projects', + }, + parentCrumb, + crumb, + ], + }; + } +} diff --git a/app/controllers/authenticated/dashboard/file/dynamic-scan/manual.ts b/app/controllers/authenticated/dashboard/file/dynamic-scan/manual.ts index dad4cb67a..04345490d 100644 --- a/app/controllers/authenticated/dashboard/file/dynamic-scan/manual.ts +++ b/app/controllers/authenticated/dashboard/file/dynamic-scan/manual.ts @@ -14,7 +14,7 @@ export default class AuthenticatedDashboardFileDynamicScanManualController exten const routeModels = [this.model?.file?.id]; const crumb: AkBreadcrumbsItemProps = { - title: this.intl.t('dast'), + title: this.intl.t('dastTabs.manualDAST'), route: 'authenticated.dashboard.file.dynamic-scan.manual', models: routeModels, routeGroup: 'project/files', diff --git a/app/models/available-automated-device.ts b/app/models/available-automated-device.ts new file mode 100644 index 000000000..9ab15f85c --- /dev/null +++ b/app/models/available-automated-device.ts @@ -0,0 +1,9 @@ +import DeviceModel from './device'; + +export default class AvailableAutomatedDeviceModel extends DeviceModel {} + +declare module 'ember-data/types/registries/model' { + export default interface ModelRegistry { + 'available-automated-device': AvailableAutomatedDeviceModel; + } +} diff --git a/app/models/available-manual-device.ts b/app/models/available-manual-device.ts new file mode 100644 index 000000000..d5404247e --- /dev/null +++ b/app/models/available-manual-device.ts @@ -0,0 +1,9 @@ +import DeviceModel from './device'; + +export default class AvailableManualDeviceModel extends DeviceModel {} + +declare module 'ember-data/types/registries/model' { + export default interface ModelRegistry { + 'available-manual-device': AvailableManualDeviceModel; + } +} diff --git a/app/models/device.ts b/app/models/device.ts index c1bed7a25..7a2fe60c9 100644 --- a/app/models/device.ts +++ b/app/models/device.ts @@ -1,14 +1,65 @@ import Model, { attr } from '@ember-data/model'; export default class DeviceModel extends Model { + @attr('string') + declare state: string; + + @attr('string') + declare deviceIdentifier: string; + + @attr('string') + declare address: string; + + @attr('boolean') + declare isConnected: boolean; + + @attr('boolean') + declare isActive: boolean; + @attr('boolean') declare isTablet: boolean; - @attr('string') - declare version: string; + @attr('boolean') + declare isReserved: boolean; @attr('number') declare platform: number; + + @attr('string') + declare platformVersion: string; + + @attr('string') + declare cpuArchitecture: string; + + @attr('string') + declare model: string; + + @attr('boolean') + declare hasSim: boolean; + + @attr('string') + declare simNetwork: string; + + @attr('string') + declare simPhoneNumber: string; + + @attr('boolean') + declare hasPinLock: boolean; + + @attr('boolean') + declare hasVpn: boolean; + + @attr('string') + declare vpnPackageName: string; + + @attr('boolean') + declare hasPersistentApps: boolean; + + @attr() + declare persistentApps: unknown[]; + + @attr('boolean') + declare hasVnc: boolean; } declare module 'ember-data/types/registries/model' { diff --git a/app/models/ds-automation-preference.ts b/app/models/ds-automation-preference.ts new file mode 100644 index 000000000..8daf99b27 --- /dev/null +++ b/app/models/ds-automation-preference.ts @@ -0,0 +1,12 @@ +import Model, { attr } from '@ember-data/model'; + +export default class DsAutomationPreferenceModel extends Model { + @attr('boolean') + declare dynamicScanAutomationEnabled: boolean; +} + +declare module 'ember-data/types/registries/model' { + export default interface ModelRegistry { + 'ds-automation-preference': DsAutomationPreferenceModel; + } +} diff --git a/app/models/ds-manual-device-preference.ts b/app/models/ds-manual-device-preference.ts new file mode 100644 index 000000000..ab78cfd2d --- /dev/null +++ b/app/models/ds-manual-device-preference.ts @@ -0,0 +1,18 @@ +import Model, { attr } from '@ember-data/model'; + +export default class DsManualDevicePreferenceModel extends Model { + @attr('number') + declare dsManualDeviceSelection: number; + + @attr('string') + declare dsManualDeviceSelectionDisplay: string; + + @attr('string') + declare dsManualDeviceIdentifier: string; +} + +declare module 'ember-data/types/registries/model' { + export default interface ModelRegistry { + 'ds-manual-device-preference': DsManualDevicePreferenceModel; + } +} diff --git a/app/models/dynamicscan.ts b/app/models/dynamicscan.ts index bf9bfbe72..14453485a 100644 --- a/app/models/dynamicscan.ts +++ b/app/models/dynamicscan.ts @@ -1,12 +1,11 @@ -import Model, { attr, belongsTo, AsyncBelongsTo } from '@ember-data/model'; -import UserModel from './user'; -import ENUMS from 'irene/enums'; -// import DevicePreferenceModel from './device-preference'; -import AvailableDeviceModel from './available-device'; -import ScanParameterModel from './scan-parameter'; +import Model, { attr, belongsTo, type AsyncBelongsTo } from '@ember-data/model'; import { inject as service } from '@ember/service'; -import IntlService from 'ember-intl/services/intl'; -import FileModel from './file'; +import type IntlService from 'ember-intl/services/intl'; + +import ENUMS from 'irene/enums'; +import type UserModel from './user'; +import type FileModel from './file'; +import type DeviceModel from './device'; export default class DynamicscanModel extends Model { @service declare intl: IntlService; @@ -15,84 +14,59 @@ export default class DynamicscanModel extends Model { @belongsTo('file', { async: true, inverse: null }) declare file: AsyncBelongsTo; - @attr('number') - declare mode: number; - - @attr('number') - declare status: number; - - // User actions - @belongsTo('user', { async: true, inverse: null }) - declare startedByUser: AsyncBelongsTo; - - @belongsTo('user', { async: true, inverse: null }) - declare stoppedByUser: AsyncBelongsTo; - - // Scan user preferences - // @belongsTo('device-preference') - // declare devicePreference: AsyncBelongsTo; + @attr('string') + declare packageName: string; @attr('number') - declare deviceType: number; + declare mode: number; @attr('string') - declare platformVersion: string; - - @belongsTo('scan-parameter-group', { async: true, inverse: null }) - declare scanParameterGroups: AsyncBelongsTo; + declare modeDisplay: string; - @attr('boolean') - declare enableApiCapture: boolean; - - @attr() - declare apiCaptureFilters: unknown; //TODO: Check this type + @attr('number') + declare status: number; @attr('string') - declare proxyHost: string; + declare statusDisplay: string; @attr('string') - declare proxyPort: string; + declare moriartyDynamicscanrequestId: string; - // Devicefarm scan info @attr('string') declare moriartyDynamicscanId: string; @attr('string') declare moriartyDynamicscanToken: string; - @attr() - declare deviceUsed: unknown; //TODO: Check this type - - @attr('string') - declare errorCode: string; + // User actions + @belongsTo('user', { async: true, inverse: null }) + declare startedByUser: AsyncBelongsTo; - @attr('string') - declare errorMessage: string; + @belongsTo('user', { async: true, inverse: null }) + declare stoppedByUser: AsyncBelongsTo; @attr('date') declare createdOn: Date; - @attr('date') - declare updatedOn: Date; - @attr('date') declare endedOn: Date; - @attr('date') - declare timeoutOn: Date; - @attr('date') declare autoShutdownOn: Date; - // Post interaction - @attr('boolean') - declare isAnalysisDone: boolean; + // will be {} for device allocation failure state + // @belongsTo('device', { async: false, inverse: null }) + @attr() + declare deviceUsed: unknown; - @attr('number') - declare time: number; + @attr() + declare devicePreference: unknown; + + @attr('string') + declare errorCode: string; - @belongsTo('available-device', { async: true, inverse: null }) - declare availableDevice: AsyncBelongsTo; + @attr('string') + declare errorMessage: string; async extendTime(time: number) { const adapter = this.store.adapterFor('dynamicscan'); @@ -203,7 +177,6 @@ export default class DynamicscanModel extends Model { ENUMS.DYNAMIC_SCAN_STATUS.SHUTTING_DOWN, ENUMS.DYNAMIC_SCAN_STATUS.CLEANING_DEVICE, ENUMS.DYNAMIC_SCAN_STATUS.RUNTIME_DETECTION_COMPLETED, - ENUMS.DYNAMIC_SCAN_STATUS.TERMINATED, ].includes(this.status); } @@ -211,6 +184,7 @@ export default class DynamicscanModel extends Model { return [ ENUMS.DYNAMIC_SCAN_STATUS.ERROR, ENUMS.DYNAMIC_SCAN_STATUS.TIMED_OUT, + ENUMS.DYNAMIC_SCAN_STATUS.TERMINATED, ].includes(this.status); } @@ -225,17 +199,20 @@ export default class DynamicscanModel extends Model { return this.status === ENUMS.DYNAMIC_SCAN_STATUS.CANCELLED; } - get isDynamicStatusInProgress() { + get isStarting() { return ( this.isInqueue || this.isBooting || this.isInstalling || this.isLaunching || - this.isHooking || - this.isShuttingDown + this.isHooking ); } + get isStartingOrShuttingInProgress() { + return this.isStarting || this.isShuttingDown; + } + get isDynamicStatusNoneOrError() { return this.isNone || this.isStatusError; } diff --git a/app/router.ts b/app/router.ts index edf8dc126..12ccff798 100644 --- a/app/router.ts +++ b/app/router.ts @@ -203,7 +203,7 @@ Router.map(function () { this.route('dynamic-scan', function () { this.route('manual'); - // this.route('automated'); + this.route('automated'); this.route('results'); }); }); diff --git a/app/serializers/dynamicscan.js b/app/serializers/dynamicscan.js new file mode 100644 index 000000000..3d486536d --- /dev/null +++ b/app/serializers/dynamicscan.js @@ -0,0 +1,8 @@ +import DRFSerializer from './drf'; +import { EmbeddedRecordsMixin } from '@ember-data/serializer/rest'; + +export default DRFSerializer.extend(EmbeddedRecordsMixin, { + attrs: { + deviceUsed: { embedded: 'always' }, + }, +}); diff --git a/tests/integration/components/file-details/dynamic-scan/action/drawer-test.js b/tests/integration/components/file-details/dynamic-scan/action/drawer-test.js index ddb0bca0c..aeb79c8f1 100644 --- a/tests/integration/components/file-details/dynamic-scan/action/drawer-test.js +++ b/tests/integration/components/file-details/dynamic-scan/action/drawer-test.js @@ -31,17 +31,6 @@ const classes = { triggerError: styles['ak-select-trigger-error'], }; -// const dynamicScanStatusText = { -// [ENUMS.DYNAMIC_STATUS.INQUEUE]: t('deviceInQueue'), -// [ENUMS.DYNAMIC_STATUS.BOOTING]: t('deviceBooting'), -// [ENUMS.DYNAMIC_STATUS.DOWNLOADING]: t('deviceDownloading'), -// [ENUMS.DYNAMIC_STATUS.INSTALLING]: t('deviceInstalling'), -// [ENUMS.DYNAMIC_STATUS.LAUNCHING]: t('deviceLaunching'), -// [ENUMS.DYNAMIC_STATUS.HOOKING]: t('deviceHooking'), -// [ENUMS.DYNAMIC_STATUS.SHUTTING_DOWN]: t('deviceShuttingDown'), -// [ENUMS.DYNAMIC_STATUS.COMPLETED]: t('deviceCompleted'), -// }; - class NotificationsStub extends Service { errorMsg = null; successMsg = null; @@ -400,7 +389,7 @@ module( }; }); - await render(hbs` + await render(hbs`