diff --git a/app/adapters/sk-add-to-inventory.ts b/app/adapters/sk-add-to-inventory.ts new file mode 100644 index 000000000..20889a84e --- /dev/null +++ b/app/adapters/sk-add-to-inventory.ts @@ -0,0 +1,15 @@ +import CommonDRFAdapter from './commondrf'; + +export default class SkAddToInventoryAdapter extends CommonDRFAdapter { + _buildURL() { + const baseurl = `${this.namespace_v2}/sk_app`; + + return this.buildURLFromBase(baseurl); + } +} + +declare module 'ember-data/types/registries/adapter' { + export default interface AdapterRegistry { + 'sk-add-to-inventory': SkAddToInventoryAdapter; + } +} diff --git a/app/adapters/sk-app-version.ts b/app/adapters/sk-app-version.ts new file mode 100644 index 000000000..eafaab603 --- /dev/null +++ b/app/adapters/sk-app-version.ts @@ -0,0 +1,45 @@ +import commondrf from './commondrf'; + +export type SkAppVersionQuery = { + skAppId?: string | number; + is_latest?: boolean; +}; + +export default class SkAppVersionAdapter extends commondrf { + _buildURL(_: string, id: string | number) { + const baseurl = `${this.namespace_v2}/sk_app_version`; + + if (id) { + return this.buildURLFromBase(`${baseurl}/${encodeURIComponent(id)}`); + } + + return this.buildURLFromBase(baseurl); + } + + _buildNestedURL(_: string | number, skAppId: string | number) { + const baseURL = `${this.namespace_v2}/sk_app/${skAppId}/sk_app_version`; + + return this.buildURLFromBase(baseURL); + } + + urlForQuery( + query: SkAppVersionQuery, + modelName: K + ) { + if (query.skAppId) { + const skAppId = query.skAppId; + + delete query.skAppId; + + return this._buildNestedURL(modelName, skAppId); + } + + return super.urlForQuery(query, modelName); + } +} + +declare module 'ember-data/types/registries/adapter' { + export default interface AdapterRegistry { + 'sk-app-version': SkAppVersionAdapter; + } +} diff --git a/app/adapters/sk-app.ts b/app/adapters/sk-app.ts index e046d189b..316dd800c 100644 --- a/app/adapters/sk-app.ts +++ b/app/adapters/sk-app.ts @@ -9,6 +9,11 @@ export interface ApprovalStatusResponse { approval_status_display: string; } +export interface ToggleSkAppMonitoringStatusResponse { + id: number; + monitoring_enabled: boolean; +} + export default class SkAppAdapter extends CommonDRFAdapter { _buildURL() { const baseurl = `${this.namespace_v2}/sk_app`; @@ -60,6 +65,21 @@ export default class SkAppAdapter extends CommonDRFAdapter { return await this.ajax(url, 'GET', { data }); } + + async toggleMonitoring( + id: string, + checked: boolean + ): Promise { + const url = this.buildURL().concat( + `/${id}/update_monitoring_enabled_status` + ); + + const data = { + monitoring_enabled: checked, + }; + + return await this.ajax(url, 'PUT', { data }); + } } declare module 'ember-data/types/registries/adapter' { diff --git a/app/adapters/sk-inventory-app.ts b/app/adapters/sk-inventory-app.ts new file mode 100644 index 000000000..e613a90ad --- /dev/null +++ b/app/adapters/sk-inventory-app.ts @@ -0,0 +1,19 @@ +import CommonDRFAdapter from './commondrf'; + +export default class SkInventoryAppAdapter extends CommonDRFAdapter { + _buildURL(_: string | number, id?: string | number) { + const baseURL = `${this.namespace_v2}/sk_app_detail`; + + if (id) { + return this.buildURLFromBase(`${baseURL}/${encodeURIComponent(id)}`); + } + + return this.buildURLFromBase(baseURL); + } +} + +declare module 'ember-data/types/registries/adapter' { + export default interface AdapterRegistry { + 'sk-inventory-app': SkInventoryAppAdapter; + } +} diff --git a/app/adapters/sk-inventory-approval-status.ts b/app/adapters/sk-inventory-approval-status.ts new file mode 100644 index 000000000..2587b0049 --- /dev/null +++ b/app/adapters/sk-inventory-approval-status.ts @@ -0,0 +1,15 @@ +import CommonDRFAdapter from './commondrf'; + +export default class UnknownAnalysisStatus extends CommonDRFAdapter { + urlForQueryRecord() { + const baseurl = `${this.namespace_v2}/sk_app/check_approval_status`; + + return this.buildURLFromBase(baseurl); + } +} + +declare module 'ember-data/types/registries/adapter' { + export default interface AdapterRegistry { + 'sk-inventory-approval-status': UnknownAnalysisStatus; + } +} diff --git a/app/adapters/sk-organization.ts b/app/adapters/sk-organization.ts new file mode 100644 index 000000000..9da7e910e --- /dev/null +++ b/app/adapters/sk-organization.ts @@ -0,0 +1,37 @@ +import CommonDRFAdapter from './commondrf'; +import type SkOrganizationModel from 'irene/models/sk-organization'; + +export default class SkOrganizationAdapter extends CommonDRFAdapter { + _buildURL(_: string, id: string | number) { + const baseurl = `${this.namespace_v2}/sk_organization`; + + if (id) { + return this.buildURLFromBase(`${baseurl}/${encodeURIComponent(id)}`); + } + + return this.buildURLFromBase(baseurl); + } + + async toggleAddToInventoryByDefault( + modelName: string, + id: string | number, + addToInventoryByDefault: boolean + ) { + const url = this._buildURL(modelName, id); + + const data = { + add_appknox_project_to_inventory_by_default: addToInventoryByDefault, + }; + + const response = await this.ajax(url, 'PATCH', { data }); + const normalized = this.store.normalize('sk-organnization', response); + + return this.store.push(normalized) as SkOrganizationModel; + } +} + +declare module 'ember-data/types/registries/adapter' { + export default interface AdapterRegistry { + 'sk-organization': SkOrganizationAdapter; + } +} diff --git a/app/components/ak-accordion/index.hbs b/app/components/ak-accordion/index.hbs index 21761b5e2..ab27e5769 100644 --- a/app/components/ak-accordion/index.hbs +++ b/app/components/ak-accordion/index.hbs @@ -23,8 +23,8 @@ summary {{if @disabled "disabled"}} {{this.variant}} - {{@customSumaryClass}} ' + class='{{@customSumaryClass}}' {{on 'click' this.onSummaryClick}} data-test-ak-accordion-summary > diff --git a/app/components/ak-accordion/index.scss b/app/components/ak-accordion/index.scss index b90cb0364..c4a763e43 100644 --- a/app/components/ak-accordion/index.scss +++ b/app/components/ak-accordion/index.scss @@ -30,6 +30,16 @@ border-color: var(--ak-accordion-summary-secondary-border-color); } + &.light { + background-color: var(--common-white); + border: none; + box-shadow: + 1px 0px 0px 0px #f8f8f8 inset, + -1px 0px 0px 0px #f8f8f8 inset, + 0px 1px 0px 0px #f8f8f8 inset, + -1px -1px 0px 0px #f8f8f8 inset; + } + &.disabled { cursor: not-allowed; } diff --git a/app/components/ak-accordion/index.ts b/app/components/ak-accordion/index.ts index bd67899ac..ccd9e08e6 100644 --- a/app/components/ak-accordion/index.ts +++ b/app/components/ak-accordion/index.ts @@ -11,7 +11,7 @@ import { } from '../ak-typography'; import { AccordionCtxProps } from './group'; -export type AkAccordionVariant = 'primary' | 'secondary'; +export type AkAccordionVariant = 'primary' | 'secondary' | 'light'; export type AkAccordionTypographyVariant = TypographyVariant; export type AkAccordionFontWeightVariant = TypographyFontWeight; @@ -54,6 +54,7 @@ export default class AkAccordionComponent extends Component { Element: HTMLElementTagNameMap[T]; diff --git a/app/components/ak-checkbox/index.hbs b/app/components/ak-checkbox/index.hbs index 3d919b5c9..f14f78aa6 100644 --- a/app/components/ak-checkbox/index.hbs +++ b/app/components/ak-checkbox/index.hbs @@ -6,7 +6,7 @@ data-test-checkbox ...attributes data-indeterminate='{{@indeterminate}}' - local-class='ak-checkbox' + local-class='ak-checkbox {{if @checked "checked"}}' @checked={{@checked}} disabled={{@disabled}} readonly={{@readonly}} diff --git a/app/components/ak-checkbox/index.scss b/app/components/ak-checkbox/index.scss index 50876f90c..1e9a085a4 100644 --- a/app/components/ak-checkbox/index.scss +++ b/app/components/ak-checkbox/index.scss @@ -48,7 +48,7 @@ } } - &:checked + .ak-svg-checkbox-icon { + &.checked + .ak-svg-checkbox-icon { color: var(--ak-checkbox-color-checked); &::before { diff --git a/app/components/ak-divider/index.scss b/app/components/ak-divider/index.scss index 5d474cd6c..e515a17b0 100644 --- a/app/components/ak-divider/index.scss +++ b/app/components/ak-divider/index.scss @@ -1,11 +1,11 @@ .ak-divider-root { flex-shrink: 0; border-style: solid; - + &.ak-divider-direction-horizontal { border-width: 0px 0px thin; } - + &.ak-divider-direction-vertical { flex-shrink: unset; border-width: 0px thin 0px 0px; @@ -43,4 +43,21 @@ .ak-divider-color-dark { border-color: var(--ak-divider-color-dark); -} \ No newline at end of file +} + +.ak-divider-variant-vertical { + display: block; + width: 1px; + min-height: 15px; + height: 100%; + border: none; + margin: 0px; + + &.ak-divider-color-light { + background-color: var(--ak-divider-color-light); + } + + &.ak-divider-color-dark { + background-color: var(--ak-divider-color-dark); + } +} diff --git a/app/components/ak-divider/index.ts b/app/components/ak-divider/index.ts index db566a57c..dd6ade12f 100644 --- a/app/components/ak-divider/index.ts +++ b/app/components/ak-divider/index.ts @@ -4,7 +4,7 @@ export interface AkDividerSignature { Element: HTMLElement; Args: { tag?: string; - variant?: 'fullWidth' | 'middle'; + variant?: 'fullWidth' | 'middle' | 'vertical'; color?: 'light' | 'dark'; direction?: 'horizontal' | 'vertical'; }; diff --git a/app/components/ak-svg/aox-icon.hbs b/app/components/ak-svg/aox-icon.hbs new file mode 100644 index 000000000..7cd166635 --- /dev/null +++ b/app/components/ak-svg/aox-icon.hbs @@ -0,0 +1,13 @@ + + + + \ No newline at end of file diff --git a/app/components/ak-svg/disabled-vapt-indicator.hbs b/app/components/ak-svg/disabled-vapt-indicator.hbs new file mode 100644 index 000000000..1ecdb9f1c --- /dev/null +++ b/app/components/ak-svg/disabled-vapt-indicator.hbs @@ -0,0 +1,32 @@ + + + + + + \ No newline at end of file diff --git a/app/components/ak-svg/info-indicator.hbs b/app/components/ak-svg/info-indicator.hbs index 6a369112f..6b4314e72 100644 --- a/app/components/ak-svg/info-indicator.hbs +++ b/app/components/ak-svg/info-indicator.hbs @@ -4,6 +4,7 @@ viewBox='0 0 34 34' fill='none' xmlns='http://www.w3.org/2000/svg' + ...attributes > + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/components/ak-svg/sox-icon.hbs b/app/components/ak-svg/sox-icon.hbs new file mode 100644 index 000000000..68b6b551b --- /dev/null +++ b/app/components/ak-svg/sox-icon.hbs @@ -0,0 +1,13 @@ + + + + \ No newline at end of file diff --git a/app/components/ak-svg/sox-initiate-upload.hbs b/app/components/ak-svg/sox-initiate-upload.hbs new file mode 100644 index 000000000..383ca9485 --- /dev/null +++ b/app/components/ak-svg/sox-initiate-upload.hbs @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/components/ak-svg/sox-insufficient-credits.hbs b/app/components/ak-svg/sox-insufficient-credits.hbs new file mode 100644 index 000000000..2d5660ded --- /dev/null +++ b/app/components/ak-svg/sox-insufficient-credits.hbs @@ -0,0 +1,48 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/components/ak-svg/sox-malware-feature-absence.hbs b/app/components/ak-svg/sox-malware-feature-absence.hbs new file mode 100644 index 000000000..5e77bf07c --- /dev/null +++ b/app/components/ak-svg/sox-malware-feature-absence.hbs @@ -0,0 +1,112 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/components/ak-svg/sox-monitoring-pending.hbs b/app/components/ak-svg/sox-monitoring-pending.hbs new file mode 100644 index 000000000..00036292b --- /dev/null +++ b/app/components/ak-svg/sox-monitoring-pending.hbs @@ -0,0 +1,390 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/components/ak-svg/am-table-empty.hbs b/app/components/ak-svg/sox-unscanned-details-table-empty.hbs similarity index 100% rename from app/components/ak-svg/am-table-empty.hbs rename to app/components/ak-svg/sox-unscanned-details-table-empty.hbs diff --git a/app/components/ak-svg/sox-unscanned-history-table-empty.hbs b/app/components/ak-svg/sox-unscanned-history-table-empty.hbs new file mode 100644 index 000000000..3b2cc47ed --- /dev/null +++ b/app/components/ak-svg/sox-unscanned-history-table-empty.hbs @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/components/ak-svg/sox-upload-completed.hbs b/app/components/ak-svg/sox-upload-completed.hbs new file mode 100644 index 000000000..db3c21984 --- /dev/null +++ b/app/components/ak-svg/sox-upload-completed.hbs @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/components/ak-svg/sox-upload-processing.hbs b/app/components/ak-svg/sox-upload-processing.hbs new file mode 100644 index 000000000..75e468a19 --- /dev/null +++ b/app/components/ak-svg/sox-upload-processing.hbs @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/components/app-monitoring/empty/index.hbs b/app/components/app-monitoring/empty/index.hbs index 7316a6043..edb9475b6 100644 --- a/app/components/app-monitoring/empty/index.hbs +++ b/app/components/app-monitoring/empty/index.hbs @@ -1,4 +1,3 @@ -{{! @glint-nocheck: AkSvg::AmTableEmpty}} - + {{#if @isHistoryTable}} + + {{else}} + + {{/if}} + + + + + {{#if column.headerComponent}} + {{#let (component column.headerComponent) as |Component|}} + + {{/let}} + {{else}} + {{column.name}} + {{/if}} + + + + + + + {{#let (component r.columnValue.cellComponent) as |Component|}} + + {{/let}} + + + + + + + \ No newline at end of file diff --git a/app/components/storeknox/discover/requested-apps/table/index.scss b/app/components/storeknox/discover/requested-apps/table/index.scss new file mode 100644 index 000000000..e1718b08d --- /dev/null +++ b/app/components/storeknox/discover/requested-apps/table/index.scss @@ -0,0 +1,8 @@ +.requested-table { + tr { + background-color: var(--storeknox-discover-requested-apps-table-row-color); + border: 1px solid + var(--storeknox-discover-requested-apps-table-row-border-color); + border-top: 0; + } +} diff --git a/app/components/storeknox/discover/requested-apps/table/index.ts b/app/components/storeknox/discover/requested-apps/table/index.ts new file mode 100644 index 000000000..46c63d270 --- /dev/null +++ b/app/components/storeknox/discover/requested-apps/table/index.ts @@ -0,0 +1,120 @@ +import { action } from '@ember/object'; +import Component from '@glimmer/component'; +import { tracked } from '@glimmer/tracking'; +import { inject as service } from '@ember/service'; +import RouterService from '@ember/routing/router-service'; +import IntlService from 'ember-intl/services/intl'; + +interface LimitOffset { + limit: number; + offset: number; +} + +export default class StoreknoxDiscoverRequestedAppsTableComponent extends Component { + @service declare router: RouterService; + @service declare intl: IntlService; + + @tracked requestedApps = [ + { + isAndroid: true, + iconUrl: + 'https://appknox-production-public.s3.amazonaws.com/908e507e-1148-4f4d-9939-6dba3d645abc.png', + name: 'Shell Asia', + packageName: 'com.shellasia.android', + companyName: 'Shell Information Technology International', + mailId: 'asiashell@shell.com', + status: 'pending', + }, + { + isAndroid: true, + iconUrl: + 'https://appknox-production-public.s3.amazonaws.com/908e507e-1148-4f4d-9939-6dba3d645abc.png', + name: 'Shell Recharge India', + packageName: 'com.shellrecharge.india', + companyName: 'Shell Information Technology International', + mailId: null, + status: 'approved', + actionTakenBy: 'subho', + }, + { + isIos: true, + iconUrl: + 'https://appknox-production-public.s3.amazonaws.com/908e507e-1148-4f4d-9939-6dba3d645abc.png', + name: 'Shell Mobility Site Manager', + packageName: 'com.shellmobility.ios', + companyName: 'Shell Information Technology International', + mailId: null, + requested: true, + status: 'rejected', + actionTakenBy: 'subho', + }, + ]; + + // Table Actions + @action goToPage(args: LimitOffset) { + const { limit, offset } = args; + this.router.transitionTo('authenticated.storeknox.discover.requested', { + queryParams: { app_limit: limit, app_offset: offset }, + }); + } + + @action onItemPerPageChange(args: LimitOffset) { + const { limit } = args; + const offset = 0; + + this.router.transitionTo('authenticated.storeknox.discover.requested', { + queryParams: { app_limit: limit, app_offset: offset }, + }); + } + + get totalCount() { + return this.requestedApps.length || 0; + } + + get tableData() { + return this.requestedApps; + } + + get itemPerPageOptions() { + return [10, 25, 50]; + } + + get limit() { + return 10; + } + + get offset() { + return 0; + } + + get columns() { + return [ + { + headerComponent: 'storeknox/table-columns/store-header', + cellComponent: 'storeknox/table-columns/store', + minWidth: 30, + width: 30, + textAlign: 'center', + }, + { + name: this.intl.t('application'), + cellComponent: 'storeknox/table-columns/application', + }, + { + name: this.intl.t('developer'), + cellComponent: 'storeknox/table-columns/developer', + }, + { + name: this.intl.t('status'), + cellComponent: 'storeknox/discover/requested-apps/table/status', + width: 80, + }, + ]; + } +} + +declare module '@glint/environment-ember-loose/registry' { + export default interface Registry { + 'Storeknox::Discover::RequestedApps::Table': typeof StoreknoxDiscoverRequestedAppsTableComponent; + } +} diff --git a/app/components/storeknox/discover/requested-apps/table/status/index.hbs b/app/components/storeknox/discover/requested-apps/table/status/index.hbs new file mode 100644 index 000000000..86e9f97de --- /dev/null +++ b/app/components/storeknox/discover/requested-apps/table/status/index.hbs @@ -0,0 +1,69 @@ +{{#if (eq @data.status 'pending')}} + + <:icon> + + + +{{else if (eq @data.status 'approved')}} + + + {{t 'storeknox.approved'}} + + + + + {{t 'by'}} + {{@data.actionTakenBy}} + + + + <:tooltipContent> + + + + + June 17, 2024, 12:51 + + + + + <:default> + + + + + +{{else if (eq @data.status 'rejected')}} + + + {{t 'rejected'}} + + + + + {{t 'by'}} + {{@data.actionTakenBy}} + + + + <:tooltipContent> + + + + + June 17, 2024, 12:51 + + + + + <:default> + + + + + +{{/if}} \ No newline at end of file diff --git a/app/components/storeknox/discover/requested-apps/table/status/index.scss b/app/components/storeknox/discover/requested-apps/table/status/index.scss new file mode 100644 index 000000000..761831828 --- /dev/null +++ b/app/components/storeknox/discover/requested-apps/table/status/index.scss @@ -0,0 +1,5 @@ +.info-icon { + font-size: 1em !important; + height: 12px; + color: var(--storeknox-discover-requested-apps-table-status-info-icon-color); +} diff --git a/app/components/storeknox/discover/welcome-modal/index.hbs b/app/components/storeknox/discover/welcome-modal/index.hbs index b626496cc..59e27333c 100644 --- a/app/components/storeknox/discover/welcome-modal/index.hbs +++ b/app/components/storeknox/discover/welcome-modal/index.hbs @@ -40,7 +40,7 @@
- + Take Action diff --git a/app/components/storeknox/index.hbs b/app/components/storeknox/index.hbs new file mode 100644 index 000000000..e2147cab0 --- /dev/null +++ b/app/components/storeknox/index.hbs @@ -0,0 +1 @@ +{{outlet}} \ No newline at end of file diff --git a/app/components/storeknox/index.ts b/app/components/storeknox/index.ts new file mode 100644 index 000000000..7f4685839 --- /dev/null +++ b/app/components/storeknox/index.ts @@ -0,0 +1,9 @@ +import Component from '@glimmer/component'; + +export default class StoreknoxComponent extends Component {} + +declare module '@glint/environment-ember-loose/registry' { + export default interface Registry { + Storeknox: typeof StoreknoxComponent; + } +} diff --git a/app/components/storeknox/inventory-details/app-details/actions-list/button/index.hbs b/app/components/storeknox/inventory-details/app-details/actions-list/button/index.hbs new file mode 100644 index 000000000..107cae124 --- /dev/null +++ b/app/components/storeknox/inventory-details/app-details/actions-list/button/index.hbs @@ -0,0 +1,30 @@ + + <:leftIcon> + + + + <:default> + {{@label}} + + + <:rightIcon> + {{#unless @hideRightIcon}} + + {{/unless}} + + \ No newline at end of file diff --git a/app/components/storeknox/inventory-details/app-details/actions-list/button/index.scss b/app/components/storeknox/inventory-details/app-details/actions-list/button/index.scss new file mode 100644 index 000000000..bd1af974f --- /dev/null +++ b/app/components/storeknox/inventory-details/app-details/actions-list/button/index.scss @@ -0,0 +1,37 @@ +.action-button-root { + border-radius: 3px; + background-color: #f6fff7; + color: var(--success-main) !important; + border-color: var(--success-main) !important; + + &:focus, + &:hover { + box-shadow: inset 0 0 0 0.5px var(--success-main); + } + + &.needs-action { + color: var(--primary-main) !important; + border-color: var(--primary-main) !important; + background-color: #fff5f5; + + &:focus, + &:hover { + box-shadow: inset 0 0 0 0.5px var(--primary-main); + } + } + + &.feature-in-progress { + color: var(--storeknox-feature-in-progress-button-color) !important; + border-color: var(--storeknox-feature-in-progress-button-color) !important; + + background-color: var( + --storeknox-feature-in-progress-button-bg-color + ) !important; + + &:focus, + &:hover { + box-shadow: inset 0 0 0 0.5px + var(--storeknox-feature-in-progress-button-color); + } + } +} diff --git a/app/components/storeknox/inventory-details/app-details/actions-list/button/index.ts b/app/components/storeknox/inventory-details/app-details/actions-list/button/index.ts new file mode 100644 index 000000000..53c3da90c --- /dev/null +++ b/app/components/storeknox/inventory-details/app-details/actions-list/button/index.ts @@ -0,0 +1,21 @@ +import Component from '@glimmer/component'; +import { ButtonTags } from 'irene/components/ak-button'; + +interface StoreknoxInventoryDetailsAppDetailsActionsListButtonSignature { + Element: HTMLElementTagNameMap[ButtonTags]; + Args: { + needsAction?: boolean; + featureInProgress?: boolean; + disabled?: boolean; + label: string; + hideRightIcon?: boolean; + }; +} + +export default class StoreknoxInventoryDetailsAppDetailsActionsListButtonComponent extends Component {} + +declare module '@glint/environment-ember-loose/registry' { + export default interface Registry { + 'Storeknox::InventoryDetails::AppDetails::ActionsList::Button': typeof StoreknoxInventoryDetailsAppDetailsActionsListButtonComponent; + } +} diff --git a/app/components/storeknox/inventory-details/app-details/actions-list/index.hbs b/app/components/storeknox/inventory-details/app-details/actions-list/index.hbs new file mode 100644 index 000000000..ea8ae7620 --- /dev/null +++ b/app/components/storeknox/inventory-details/app-details/actions-list/index.hbs @@ -0,0 +1,50 @@ + + + <:icon> + + + + <:default> + + + {{t 'storeknox.actionNeeded'}} + ({{this.actionableItemsCount}}) + + + + + + + + {{this.lastMonitoredDate}} + + + + + + + + + {{#each this.actionsList as |action|}} + + + + {{/each}} + + \ No newline at end of file diff --git a/app/components/storeknox/inventory-details/app-details/actions-list/index.scss b/app/components/storeknox/inventory-details/app-details/actions-list/index.scss new file mode 100644 index 000000000..24179e800 --- /dev/null +++ b/app/components/storeknox/inventory-details/app-details/actions-list/index.scss @@ -0,0 +1,4 @@ +.last-monitored-date { + background-color: var(--neutral-grey-100); + padding: 3px 6px; +} diff --git a/app/components/storeknox/inventory-details/app-details/actions-list/index.ts b/app/components/storeknox/inventory-details/app-details/actions-list/index.ts new file mode 100644 index 000000000..837f96171 --- /dev/null +++ b/app/components/storeknox/inventory-details/app-details/actions-list/index.ts @@ -0,0 +1,71 @@ +import Component from '@glimmer/component'; +import { service } from '@ember/service'; +import dayjs from 'dayjs'; +import type IntlService from 'ember-intl/services/intl'; + +import ENUMS from 'irene/enums'; +import type SkInventoryAppModel from 'irene/models/sk-inventory-app'; + +interface StoreknoxInventoryDetailsAppDetailsActionsListSignature { + Args: { + app?: SkInventoryAppModel; + }; +} + +export default class StoreknoxInventoryDetailsAppDetailsActionsListComponent extends Component { + @service declare intl: IntlService; + + get skInventoryApp() { + return this.args.app; + } + + get actionsList() { + const skInventoryAppId = this.skInventoryApp?.id as string; + + return [ + { + id: 'unscanned-version', + disabled: false, + label: this.intl.t('storeknox.unscannedVersion'), + needsAction: + this.skInventoryApp?.storeMonitoringStatus === + ENUMS.SK_APP_MONITORING_STATUS.UNSCANNED, + route: 'authenticated.storeknox.inventory-details.unscanned-version', + model: skInventoryAppId, + }, + { + id: 'brand-abuse', + disabled: false, + label: this.intl.t('storeknox.brandAbuse'), + featureInProgress: true, + route: 'authenticated.storeknox.inventory-details.brand-abuse', + model: skInventoryAppId, + }, + { + id: 'malware-detected', + disabled: false, + label: this.intl.t('storeknox.malwareDetected'), + featureInProgress: true, + route: 'authenticated.storeknox.inventory-details.malware-detected', + model: skInventoryAppId, + }, + ]; + } + + get actionableItemsCount() { + return this.actionsList.reduce( + (count, action) => (action.needsAction ? count + 1 : count + 0), + 0 + ); + } + + get lastMonitoredDate() { + return dayjs(this.skInventoryApp?.updatedOn).format('MMM DD, YYYY'); + } +} + +declare module '@glint/environment-ember-loose/registry' { + export default interface Registry { + 'Storeknox::InventoryDetails::AppDetails::ActionsList': typeof StoreknoxInventoryDetailsAppDetailsActionsListComponent; + } +} diff --git a/app/components/storeknox/inventory-details/app-details/index.hbs b/app/components/storeknox/inventory-details/app-details/index.hbs new file mode 100644 index 000000000..8e1021906 --- /dev/null +++ b/app/components/storeknox/inventory-details/app-details/index.hbs @@ -0,0 +1,328 @@ + + {{#if this.skInventoryApp.monitoringStatusIsPending}} + + + + + Our platform will monitor for any unscanned versions of your + applications automatically at 24 hour intervals. Which has been added to + the inventory. Nisl iaculis tincidunt sit interdum ullamcorper. At elit + turpis aliquam sagittis velit. + + + {{else}} + + {{/if}} + + + + <:default> + + {{t 'storeknox.appDetails'}} + + + + + + + {{#each this.appDetailsInfo as |info idx|}} + {{#if (gt idx 0)}} + + {{/if}} + + + + {{info.title}} + + + + {{or info.value '-'}} + + + {{/each}} + + + + + {{#if this.skInventoryApp.appIsNotAvailableOnAppknox}} + + + <:default> + + {{t 'storeknox.latestVAResults'}} + + + + + + + + + + This app has never been uploaded to Appknox + + + + It is strongly recommended to upload this to Appknox to perform the + Vulnerability Assessment. + + + + + <:leftIcon> + + + + <:default> + {{t 'appMonitoringModule.initiateUpload'}} + + + + + + {{else if this.skInventoryApp.coreProjectLatestVersion}} + + + <:default> + + Latest VA Results + + + + + + + + + {{t 'summary'}} + + + + + + + + + + {{t 'fileID'}} + + + + {{this.skInventoryApp.coreProjectLatestVersion.id}} + + + + {{#each this.vaResultsData as |info|}} + + + + + {{info.title}} + + + + {{info.value}} + + + {{/each}} + + + + {{/if}} + + {{! LOADING STATE FOR VA RESULT }} + {{! TODO: Remove is unneeded }} + {{!-- + + <:default> + + {{t 'storeknox.latestVAResults'}} + + + + + + + + + {{t 'summary'}} + + + + + + + + + + {{t 'fileID'}} + + + + + + {{#each this.vaResultsData as |info|}} + + + + + {{info.title}} + + + + + {{/each}} + + + --}} + + {{! UPLOAD COMPLETE STATE - INITIATE UPLOAD }} + {{!-- + + <:default> + + {{t 'storeknox.latestVAResults'}} + + + + + + + + + Completed... + 100% + + + + + --}} + + {{! UPLOAD IN PROGRESS STATE - INITIATE UPLOAD }} + {{!-- + + <:default> + + {{t 'storeknox.latestVAResults'}} + + + + + + + + + Processing... + 70% + + + + + + --}} + + {{! INSUFFICIENT CREDITS - INITIATE UPLOAD }} + {{!-- + + <:default> + + {{t 'storeknox.latestVAResults'}} + + + + + + + + + + Insufficient Credits + + + + We tried to upload, due to Insufficient Credits. The upload has been + cancelled. + + + + + Contact Support + + + --}} + \ No newline at end of file diff --git a/app/components/storeknox/inventory-details/app-details/index.ts b/app/components/storeknox/inventory-details/app-details/index.ts new file mode 100644 index 000000000..070f4287d --- /dev/null +++ b/app/components/storeknox/inventory-details/app-details/index.ts @@ -0,0 +1,69 @@ +import Component from '@glimmer/component'; +import dayjs from 'dayjs'; +import { inject as service } from '@ember/service'; +import type IntlService from 'ember-intl/services/intl'; +import type Store from '@ember-data/store'; + +import type SkInventoryAppModel from 'irene/models/sk-inventory-app'; + +interface StoreknoxInventoryDetailsAppDetailsSignature { + Args: { + skInventoryApp: SkInventoryAppModel; + }; +} + +export default class StoreknoxInventoryDetailsAppDetailsComponent extends Component { + @service declare intl: IntlService; + @service declare store: Store; + @service('notifications') declare notify: NotificationService; + + get skInventoryApp() { + return this.args.skInventoryApp; + } + + get coreProjectLatestVersion() { + return this.skInventoryApp?.coreProjectLatestVersion; + } + + get appDetailsInfo() { + return [ + { + title: this.intl.t('storeknox.developer'), + value: this.skInventoryApp?.devName, + }, + { + title: this.intl.t('emailId'), + value: this.skInventoryApp?.devEmail, + }, + { + title: this.intl.t('storeknox.addedToInventoryOn'), + value: dayjs(this.skInventoryApp?.addedOn).format('DD, MMM YYYY'), + }, + ]; + } + + get vaResultsData() { + return [ + { + title: 'Version', + value: this.coreProjectLatestVersion?.get('version'), + }, + { + title: 'Version Code', + value: this.coreProjectLatestVersion?.get('versionCode'), + }, + { + title: 'Last Scanned Date', + value: dayjs(this.coreProjectLatestVersion?.get('createdOn')).format( + 'DD MMM YYYY' + ), + }, + ]; + } +} + +declare module '@glint/environment-ember-loose/registry' { + export default interface Registry { + 'Storeknox::InventoryDetails::AppDetails': typeof StoreknoxInventoryDetailsAppDetailsComponent; + } +} diff --git a/app/components/storeknox/inventory-details/app-details/va-results/index.hbs b/app/components/storeknox/inventory-details/app-details/va-results/index.hbs new file mode 100644 index 000000000..d243924fe --- /dev/null +++ b/app/components/storeknox/inventory-details/app-details/va-results/index.hbs @@ -0,0 +1,17 @@ + + {{#each this.vaResultsCategories as |reCat|}} + + + + + + {{t reCat}} + + + + + {{this.getVaCategoryResultCount reCat}} + + + {{/each}} + \ No newline at end of file diff --git a/app/components/storeknox/inventory-details/app-details/va-results/index.scss b/app/components/storeknox/inventory-details/app-details/va-results/index.scss new file mode 100644 index 000000000..53ff6eac6 --- /dev/null +++ b/app/components/storeknox/inventory-details/app-details/va-results/index.scss @@ -0,0 +1,30 @@ +.indicator { + display: flex; + width: 10px; + height: 10px; + border-radius: 100%; + + &.critical { + background-color: var(--severity-critical); + } + + &.high { + background-color: var(--severity-high); + } + + &.medium { + background-color: var(--severity-medium); + } + + &.low { + background-color: var(--severity-low); + } + + &.passed { + background-color: var(--severity-passed); + } + + &.untested { + background-color: var(--severity-untested); + } +} diff --git a/app/components/storeknox/inventory-details/app-details/va-results/index.ts b/app/components/storeknox/inventory-details/app-details/va-results/index.ts new file mode 100644 index 000000000..c0d25aef8 --- /dev/null +++ b/app/components/storeknox/inventory-details/app-details/va-results/index.ts @@ -0,0 +1,43 @@ +import { action } from '@ember/object'; +import Component from '@glimmer/component'; + +import type SkInventoryAppModel from 'irene/models/sk-inventory-app'; + +interface StoreknoxInventoryDetailsAppDetailsVaResultsSignature { + Args: { + app?: SkInventoryAppModel; + }; +} + +export default class StoreknoxInventoryDetailsAppDetailsVaResultsComponent extends Component { + get coreProjectLatestVersion() { + return this.args.app?.coreProjectLatestVersion; + } + + get vaResults() { + return { + critical: this.coreProjectLatestVersion?.get('countRiskCritical'), + high: this.coreProjectLatestVersion?.get('countRiskHigh'), + medium: this.coreProjectLatestVersion?.get('countRiskMedium'), + low: this.coreProjectLatestVersion?.get('countRiskLow'), + passed: this.coreProjectLatestVersion?.get('countRiskNone'), + untested: this.coreProjectLatestVersion?.get('countRiskUnknown'), + }; + } + + get vaResultsCategories() { + return Object.keys(this.vaResults) as Array< + 'medium' | 'critical' | 'high' | 'low' | 'passed' | 'untested' + >; + } + + @action getVaCategoryResultCount(category: keyof typeof this.vaResults) { + return this.vaResults[category]; + } +} + +declare module '@glint/environment-ember-loose/registry' { + export default interface Registry { + 'Storeknox::InventoryDetails::AppDetails::VaResults': typeof StoreknoxInventoryDetailsAppDetailsVaResultsComponent; + } +} diff --git a/app/components/storeknox/inventory-details/brand-abuse/index.hbs b/app/components/storeknox/inventory-details/brand-abuse/index.hbs new file mode 100644 index 000000000..02661e76d --- /dev/null +++ b/app/components/storeknox/inventory-details/brand-abuse/index.hbs @@ -0,0 +1,75 @@ + + <:illustration> + + + + +{{!-- + + + <:default> + + Brand Abuse + + + + + + + + + Phishing Detected + + + + + Yes (Some Details) + + + + + + 48390 + + + + + {{#each this.brandAbuseInfoData as |info|}} + + + + + {{info.title}} + + + + {{info.value}} + + + {{/each}} + + + + + + Action + + + + Report to Google + + + + + + --}} \ No newline at end of file diff --git a/app/components/storeknox/inventory-details/brand-abuse/index.scss b/app/components/storeknox/inventory-details/brand-abuse/index.scss new file mode 100644 index 000000000..05e5e979a --- /dev/null +++ b/app/components/storeknox/inventory-details/brand-abuse/index.scss @@ -0,0 +1,32 @@ +.summary-bars-container { + width: 500px; + display: flex; + align-items: center; + gap: 2px; + + .bar { + display: flex; + height: 15px; + min-width: 15px; + } + + .critical { + background-color: var(--severity-critical); + } + + .high { + background-color: var(--severity-high); + } + + .medium { + background-color: var(--severity-medium); + } + + .low { + background-color: var(--severity-low); + } + + .passed { + background-color: var(--severity-passed); + } +} diff --git a/app/components/storeknox/inventory-details/brand-abuse/index.ts b/app/components/storeknox/inventory-details/brand-abuse/index.ts new file mode 100644 index 000000000..5b7e7316f --- /dev/null +++ b/app/components/storeknox/inventory-details/brand-abuse/index.ts @@ -0,0 +1,31 @@ +import Component from '@glimmer/component'; +import dayjs from 'dayjs'; + +export default class StoreknoxInventoryDetailsBrandAbuseComponent extends Component { + get brandAbuseInfoData() { + return [ + { + title: 'Threat Detected', + value: 'Spoofing', + }, + { + title: 'Detected on', + value: dayjs().format('DD, MMM YYYY'), + }, + { + title: 'No of Downloads', + value: '1500 Download', + }, + { + title: 'Publisher', + value: 'XYZ Teams', + }, + ]; + } +} + +declare module '@glint/environment-ember-loose/registry' { + export default interface Registry { + 'Storeknox::InventoryDetails::BrandAbuse': typeof StoreknoxInventoryDetailsBrandAbuseComponent; + } +} diff --git a/app/components/storeknox/inventory-details/feature-unavailable/index.hbs b/app/components/storeknox/inventory-details/feature-unavailable/index.hbs new file mode 100644 index 000000000..6cf1951dd --- /dev/null +++ b/app/components/storeknox/inventory-details/feature-unavailable/index.hbs @@ -0,0 +1,25 @@ + + {{yield to='illustration'}} + + + + {{@headerTitle}} + + + + {{@headerDescription}} + + + \ No newline at end of file diff --git a/app/components/storeknox/inventory-details/feature-unavailable/index.scss b/app/components/storeknox/inventory-details/feature-unavailable/index.scss new file mode 100644 index 000000000..c007a19bc --- /dev/null +++ b/app/components/storeknox/inventory-details/feature-unavailable/index.scss @@ -0,0 +1,3 @@ +.feature-unavailable-root { + background-color: var(--background-main); +} diff --git a/app/components/storeknox/inventory-details/header/index.hbs b/app/components/storeknox/inventory-details/header/index.hbs new file mode 100644 index 000000000..d84e1c13a --- /dev/null +++ b/app/components/storeknox/inventory-details/header/index.hbs @@ -0,0 +1,131 @@ +
+ + + + + {{#if this.skInventoryApp.appIsNotAvailableOnAppknox}} + + <:icon> + + + + <:default> + + {{t 'storeknox.appNotPartOfAppknox'}} + + + + {{/if}} + + + + {{@skInventoryApp.appMetadata.title}} + + + {{@skInventoryApp.appMetadata.title}} + + + + {{@skInventoryApp.appMetadata.packageName}} + + + + + + + + + {{#if this.skInventoryApp.isAndroid}} + + {{else}} + + {{/if}} + + + {{#unless this.skInventoryApp.appIsNotAvailableOnAppknox}} + + + + {{/unless}} + + {{! TODO: Uncomment when feature is fully ready }} + {{! + + }} + + + + + {{#if this.activeRouteTagProps}} + + + {{else}} + + {{#if this.toggleSkInventoryAppMonitoring.isRunning}} + + {{/if}} + + + + + + Monitoring Action + + {{/if}} + + {{!-- + + + + --}} + + + + +
\ No newline at end of file diff --git a/app/components/storeknox/inventory-details/header/index.scss b/app/components/storeknox/inventory-details/header/index.scss new file mode 100644 index 000000000..185ef2c67 --- /dev/null +++ b/app/components/storeknox/inventory-details/header/index.scss @@ -0,0 +1,12 @@ +.app-inventory-header-root { + position: sticky; + z-index: 10; + top: -0.5em; + background-color: var(--background-light); + + .app-info-logo { + border-radius: 100%; + object-fit: cover; + border: 1px solid var(--app-logo-border-color); + } +} diff --git a/app/components/storeknox/inventory-details/header/index.ts b/app/components/storeknox/inventory-details/header/index.ts new file mode 100644 index 000000000..a6637c59a --- /dev/null +++ b/app/components/storeknox/inventory-details/header/index.ts @@ -0,0 +1,138 @@ +import Component from '@glimmer/component'; +import { service } from '@ember/service'; +import { task } from 'ember-concurrency'; +import { action } from '@ember/object'; +import type IntlService from 'ember-intl/services/intl'; +import type RouterService from '@ember/routing/router-service'; +import type Store from '@ember-data/store'; + +import ENUMS from 'irene/enums'; +import parseError from 'irene/utils/parse-error'; +import type SkInventoryAppModel from 'irene/models/sk-inventory-app'; +import type OrganizationService from 'irene/services/organization'; +import type MeService from 'irene/services/me'; + +interface StoreknoxInventoryDetailsHeaderSignature { + Args: { + skInventoryApp: SkInventoryAppModel; + }; +} + +export default class StoreknoxInventoryDetailsHeaderComponent extends Component { + @service declare organization: OrganizationService; + @service declare store: Store; + @service declare router: RouterService; + @service declare intl: IntlService; + @service declare me: MeService; + @service('notifications') declare notify: NotificationService; + + get skInventoryApp() { + return this.args.skInventoryApp; + } + + get canToggleMonitoring() { + const isOwner = this.me.org?.is_owner; + const isAdmin = this.me.org?.is_admin; + + return isOwner || isAdmin; + } + + get routeLocalName() { + return this.router.currentRoute.name; + } + + get isBrandAbuseRoute() { + return this.routeLocalName.includes('brand-abuse'); + } + + get isMalwareDetectedRoute() { + return this.routeLocalName.includes('malware-detected'); + } + + get isUnscannedVersionRoute() { + return this.routeLocalName.includes('unscanned-version'); + } + + get activeRouteTagId() { + if (this.isBrandAbuseRoute) { + return 'brand-abuse'; + } else if (this.isMalwareDetectedRoute) { + return 'malware-detected'; + } else if (this.isUnscannedVersionRoute) { + return 'unscanned-version'; + } + + return ''; + } + + get appCoreProjectLatestVersion() { + return this.args.skInventoryApp?.coreProjectLatestVersion; + } + + get routeTagList() { + const skInventoryAppId = this.skInventoryApp?.id as string; + const appCoreProjectLatestVersionId = this.appCoreProjectLatestVersion?.id; + + return [ + { + id: 'unscanned-version', + disabled: false, + label: this.intl.t('storeknox.unscannedVersion'), + needsAction: + this.skInventoryApp?.storeMonitoringStatus === + ENUMS.SK_APP_MONITORING_STATUS.UNSCANNED, + route: 'authenticated.storeknox.inventory-details.unscanned-version', + models: [skInventoryAppId, appCoreProjectLatestVersionId as string], + }, + { + id: 'brand-abuse', + disabled: false, + label: this.intl.t('storeknox.brandAbuse'), + featureInProgress: true, + route: 'authenticated.storeknox.inventory-details.brand-abuse', + models: [skInventoryAppId], + }, + { + id: 'malware-detected', + disabled: false, + label: this.intl.t('storeknox.malwareDetected'), + featureInProgress: true, + route: 'authenticated.storeknox.inventory-details.malware-detected', + models: [skInventoryAppId], + }, + ]; + } + + get activeRouteTagProps() { + return this.routeTagList.find((t) => t.id === this.activeRouteTagId); + } + + @action onMonitoringActionToggle(_: Event, checked?: boolean) { + if (this.canToggleMonitoring) { + this.toggleSkInventoryAppMonitoring.perform(!!checked); + } + } + + toggleSkInventoryAppMonitoring = task(async (checked: boolean) => { + try { + this.skInventoryApp?.set('monitoringEnabled', checked); + + await this.skInventoryApp?.toggleMonitoring(checked); + await this.skInventoryApp?.reload(); + + this.notify.success( + 'Monitoring ' + `${checked ? 'Enabled' : 'Disabled'}` + ); + } catch (error) { + this.notify.error(parseError(error)); + + this.skInventoryApp?.rollbackAttributes(); + } + }); +} + +declare module '@glint/environment-ember-loose/registry' { + export default interface Registry { + 'Storeknox::InventoryDetails::Header': typeof StoreknoxInventoryDetailsHeaderComponent; + } +} diff --git a/app/components/storeknox/inventory-details/malware-detected/index.hbs b/app/components/storeknox/inventory-details/malware-detected/index.hbs new file mode 100644 index 000000000..af450c3db --- /dev/null +++ b/app/components/storeknox/inventory-details/malware-detected/index.hbs @@ -0,0 +1,75 @@ + + <:illustration> + + + + +{{!-- + + + <:default> + + Malware Detected + + + + + + + + + Phishing Detected + + + + + Yes (Some Details) + + + + + + 48390 + + + + + {{#each this.malwareInfoData as |info|}} + + + + + {{info.title}} + + + + {{info.value}} + + + {{/each}} + + + + + + Action + + + + Report to Google + + + + + + --}} \ No newline at end of file diff --git a/app/components/storeknox/inventory-details/malware-detected/index.scss b/app/components/storeknox/inventory-details/malware-detected/index.scss new file mode 100644 index 000000000..1719f6e50 --- /dev/null +++ b/app/components/storeknox/inventory-details/malware-detected/index.scss @@ -0,0 +1,3 @@ +.malware-detected-content-root { + background-color: var(--background-main); +} diff --git a/app/components/storeknox/inventory-details/malware-detected/index.ts b/app/components/storeknox/inventory-details/malware-detected/index.ts new file mode 100644 index 000000000..a9a7d7902 --- /dev/null +++ b/app/components/storeknox/inventory-details/malware-detected/index.ts @@ -0,0 +1,31 @@ +import Component from '@glimmer/component'; +import dayjs from 'dayjs'; + +export default class StoreknoxInventoryDetailsMalwareDetectedComponent extends Component { + get malwareInfoData() { + return [ + { + title: 'Threat Detected', + value: 'Spoofing', + }, + { + title: 'Detected on', + value: dayjs().format('DD, MMM YYYY'), + }, + { + title: 'No of Downloads', + value: '1500 Download', + }, + { + title: 'Publisher', + value: 'XYZ Teams', + }, + ]; + } +} + +declare module '@glint/environment-ember-loose/registry' { + export default interface Registry { + 'Storeknox::InventoryDetails::MalwareDetected': typeof StoreknoxInventoryDetailsMalwareDetectedComponent; + } +} diff --git a/app/components/storeknox/inventory-details/section-header/index.hbs b/app/components/storeknox/inventory-details/section-header/index.hbs new file mode 100644 index 000000000..8925aa6ae --- /dev/null +++ b/app/components/storeknox/inventory-details/section-header/index.hbs @@ -0,0 +1,14 @@ + + {{#if (has-block 'icon')}} + {{yield to='icon'}} + {{/if}} + + {{yield}} + \ No newline at end of file diff --git a/app/components/storeknox/inventory-details/section-header/index.scss b/app/components/storeknox/inventory-details/section-header/index.scss new file mode 100644 index 000000000..61cf64a8d --- /dev/null +++ b/app/components/storeknox/inventory-details/section-header/index.scss @@ -0,0 +1,12 @@ +.app-header-info-banner { + border: 1px solid var(--border-color-1); + border-bottom: 0px; + + &.default { + background: var(--neutral-grey-100); + } + + &.white { + background-color: var(--common-white); + } +} diff --git a/app/components/storeknox/inventory-details/section-header/index.ts b/app/components/storeknox/inventory-details/section-header/index.ts new file mode 100644 index 000000000..9e6ae18b5 --- /dev/null +++ b/app/components/storeknox/inventory-details/section-header/index.ts @@ -0,0 +1,24 @@ +import Component from '@glimmer/component'; + +interface StoreknoxInventoryDetailsSectionHeaderSignature { + Element: HTMLElement; + Args: { + color?: 'default' | 'white'; + }; + Blocks: { + icon?: []; + default: []; + }; +} + +export default class StoreknoxInventoryDetailsSectionHeaderComponent extends Component { + get color() { + return this.args.color || 'default'; + } +} + +declare module '@glint/environment-ember-loose/registry' { + export default interface Registry { + 'Storeknox::InventoryDetails::SectionHeader': typeof StoreknoxInventoryDetailsSectionHeaderComponent; + } +} diff --git a/app/components/storeknox/inventory-details/section-info/index.hbs b/app/components/storeknox/inventory-details/section-info/index.hbs new file mode 100644 index 000000000..74a8553c5 --- /dev/null +++ b/app/components/storeknox/inventory-details/section-info/index.hbs @@ -0,0 +1,12 @@ + + {{yield}} + \ No newline at end of file diff --git a/app/components/storeknox/inventory-details/section-info/index.scss b/app/components/storeknox/inventory-details/section-info/index.scss new file mode 100644 index 000000000..5e6d516f4 --- /dev/null +++ b/app/components/storeknox/inventory-details/section-info/index.scss @@ -0,0 +1,4 @@ +.app-details-info-container { + background-color: var(--common-white); + border: 1px solid var(--border-color-1); +} diff --git a/app/components/storeknox/inventory-details/section-info/index.ts b/app/components/storeknox/inventory-details/section-info/index.ts new file mode 100644 index 000000000..551cb81d3 --- /dev/null +++ b/app/components/storeknox/inventory-details/section-info/index.ts @@ -0,0 +1,18 @@ +import Component from '@glimmer/component'; +import { AkStackArgs } from 'irene/components/ak-stack'; + +interface StoreknoxInventoryDetailsSectionInfoSignature { + Element: HTMLElement; + Args: AkStackArgs; + Blocks: { + default: []; + }; +} + +export default class StoreknoxInventoryDetailsSectionInfoComponent extends Component {} + +declare module '@glint/environment-ember-loose/registry' { + export default interface Registry { + 'Storeknox::InventoryDetails::SectionInfo': typeof StoreknoxInventoryDetailsSectionInfoComponent; + } +} diff --git a/app/components/storeknox/inventory-details/unscanned-version/header/index.hbs b/app/components/storeknox/inventory-details/unscanned-version/header/index.hbs new file mode 100644 index 000000000..2e477e6ed --- /dev/null +++ b/app/components/storeknox/inventory-details/unscanned-version/header/index.hbs @@ -0,0 +1,102 @@ + + + {{#each this.tabItems as |item|}} + + {{item.label}} + + {{/each}} + + + + <:content> + + + + + File ID + + + {{#if this.coreProjectLatestVersionId}} + + {{this.coreProjectLatestVersionId}} + + {{else}} + - + {{/if}} + + + + + + + Last Scanned Version + + + + {{or this.skInventoryCoreProjectLatestVersion.version '-'}} + + + + + + + + Last monitored on + + + + {{this.lastSyncedDate}} + + + + + + + + Monitoring Status + + + + + + + + + \ No newline at end of file diff --git a/app/components/storeknox/inventory-details/unscanned-version/header/index.scss b/app/components/storeknox/inventory-details/unscanned-version/header/index.scss new file mode 100644 index 000000000..df92e68a0 --- /dev/null +++ b/app/components/storeknox/inventory-details/unscanned-version/header/index.scss @@ -0,0 +1,28 @@ +.app-details-tabs { + width: 100%; + position: sticky; + top: 232px; + background: var(--background-light); + z-index: 1; + + &.not-appknox { + top: 282px; + } +} + +.sm-summary-overview { + border: none; +} + +.overview-summary { + padding: 1em 1.4285em !important; +} + +.status { + text-transform: uppercase; + width: fit-content; + + & > span { + font-weight: var(--appmonitoring-font-weight-medium); + } +} diff --git a/app/components/storeknox/inventory-details/unscanned-version/header/index.ts b/app/components/storeknox/inventory-details/unscanned-version/header/index.ts new file mode 100644 index 000000000..fb8e75244 --- /dev/null +++ b/app/components/storeknox/inventory-details/unscanned-version/header/index.ts @@ -0,0 +1,73 @@ +import Component from '@glimmer/component'; +import { inject as service } from '@ember/service'; +import { action } from '@ember/object'; +import { tracked } from 'tracked-built-ins'; +import dayjs from 'dayjs'; +import type IntlService from 'ember-intl/services/intl'; + +import styles from './index.scss'; +import type SkInventoryAppModel from 'irene/models/sk-inventory-app'; + +interface StoreknoxInventoryDetailsUnscannedVersionHeaderSignature { + Args: { + skInventoryApp?: SkInventoryAppModel | null; + }; +} + +export default class StoreknoxInventoryDetailsUnscannedVersionHeaderComponent extends Component { + @service declare intl: IntlService; + @service('notifications') declare notify: NotificationService; + + @tracked showSMDetails = false; + + get skInventoryApp() { + return this.args.skInventoryApp; + } + + get skInventoryCoreProjectLatestVersion() { + return this.args.skInventoryApp?.coreProjectLatestVersion; + } + + get coreProjectLatestVersionId() { + return this.skInventoryCoreProjectLatestVersion?.id; + } + + get monitoringEnabled() { + return this.skInventoryApp?.monitoringEnabled; + } + + get lastSyncedDate() { + return dayjs(this.skInventoryApp?.updatedOn).format('DD MMMM, YYYY'); + } + + get tabItems() { + return [ + { + id: 'monitoring-details', + route: + 'authenticated.storeknox.inventory-details.unscanned-version.index', + label: this.intl.t('appMonitoringModule.monitoringDetails'), + }, + { + id: 'monitoring-history', + route: + 'authenticated.storeknox.inventory-details.unscanned-version.history', + label: this.intl.t('appMonitoringModule.monitoringHistory'), + }, + ]; + } + + get classes() { + return { overviewSummary: styles['overview-summary'] }; + } + + @action handleToggleSMDetails() { + this.showSMDetails = !this.showSMDetails; + } +} + +declare module '@glint/environment-ember-loose/registry' { + export default interface Registry { + 'Storeknox::InventoryDetails::UnscannedVersion::Header': typeof StoreknoxInventoryDetailsUnscannedVersionHeaderComponent; + } +} diff --git a/app/components/storeknox/inventory-details/unscanned-version/table-empty/index.hbs b/app/components/storeknox/inventory-details/unscanned-version/table-empty/index.hbs new file mode 100644 index 000000000..edb9475b6 --- /dev/null +++ b/app/components/storeknox/inventory-details/unscanned-version/table-empty/index.hbs @@ -0,0 +1,33 @@ + + + {{#if @isHistoryTable}} + + {{else}} + + {{/if}} + + + {{@header}} + + + + {{@body}} + + \ No newline at end of file diff --git a/app/components/storeknox/inventory-details/unscanned-version/table-empty/index.scss b/app/components/storeknox/inventory-details/unscanned-version/table-empty/index.scss new file mode 100644 index 000000000..36af412ca --- /dev/null +++ b/app/components/storeknox/inventory-details/unscanned-version/table-empty/index.scss @@ -0,0 +1,13 @@ +.empty-container { + margin-top: 4.625em; + margin-bottom: 4.625em; + + .empty-state-vector { + margin-bottom: 1.5625em; + } + + .body-text { + text-align: center; + max-width: 302px; + } +} diff --git a/app/components/storeknox/inventory-details/unscanned-version/table-empty/index.ts b/app/components/storeknox/inventory-details/unscanned-version/table-empty/index.ts new file mode 100644 index 000000000..78e58921e --- /dev/null +++ b/app/components/storeknox/inventory-details/unscanned-version/table-empty/index.ts @@ -0,0 +1,18 @@ +import Component from '@glimmer/component'; + +interface StoreknoxInventoryDetailsUnscannedVersionTableEmptySignature { + Element: HTMLElement; + Args: { + header?: string; + body?: string; + isHistoryTable?: boolean; + }; +} + +export default class StoreknoxInventoryDetailsUnscannedVersionTableEmptyComponent extends Component {} + +declare module '@glint/environment-ember-loose/registry' { + export default interface Registry { + 'Storeknox::InventoryDetails::UnscannedVersion::TableEmpty': typeof StoreknoxInventoryDetailsUnscannedVersionTableEmptyComponent; + } +} diff --git a/app/components/storeknox/inventory-details/unscanned-version/table-loading/index.hbs b/app/components/storeknox/inventory-details/unscanned-version/table-loading/index.hbs new file mode 100644 index 000000000..2124f73b6 --- /dev/null +++ b/app/components/storeknox/inventory-details/unscanned-version/table-loading/index.hbs @@ -0,0 +1,23 @@ + + + + + + + {{#if @loadingText}} + + {{@loadingText}} + + {{/if}} + + \ No newline at end of file diff --git a/app/components/storeknox/inventory-details/unscanned-version/table-loading/index.scss b/app/components/storeknox/inventory-details/unscanned-version/table-loading/index.scss new file mode 100644 index 000000000..8f3fe4daa --- /dev/null +++ b/app/components/storeknox/inventory-details/unscanned-version/table-loading/index.scss @@ -0,0 +1,14 @@ +.loading { + border-radius: var(--appmonitoring-border-radius); + width: 100%; + min-height: 35em; + + .loading-state-vector { + margin-bottom: 1.5625em; + } + + .loading-text { + text-align: center; + max-width: 302px; + } +} diff --git a/app/components/storeknox/inventory-details/unscanned-version/table-loading/index.ts b/app/components/storeknox/inventory-details/unscanned-version/table-loading/index.ts new file mode 100644 index 000000000..2aa3c4b72 --- /dev/null +++ b/app/components/storeknox/inventory-details/unscanned-version/table-loading/index.ts @@ -0,0 +1,16 @@ +import Component from '@glimmer/component'; + +interface StoreknoxInventoryDetailsUnscannedVersionTableLoadingSignature { + Element: HTMLElement; + Args: { + loadingText?: string; + }; +} + +export default class StoreknoxInventoryDetailsUnscannedVersionTableLoadingComponent extends Component {} + +declare module '@glint/environment-ember-loose/registry' { + export default interface Registry { + 'Storeknox::InventoryDetails::UnscannedVersion::TableLoading': typeof StoreknoxInventoryDetailsUnscannedVersionTableLoadingComponent; + } +} diff --git a/app/components/storeknox/inventory-details/unscanned-version/table/actions-header/index.hbs b/app/components/storeknox/inventory-details/unscanned-version/table/actions-header/index.hbs new file mode 100644 index 000000000..7218f3e69 --- /dev/null +++ b/app/components/storeknox/inventory-details/unscanned-version/table/actions-header/index.hbs @@ -0,0 +1,34 @@ + + {{t 'action'}} + + + <:tooltipContent> +
+ + {{t 'appMonitoringModule.actionsInfo'}} + +
+ + + <:default> + + +
+
\ No newline at end of file diff --git a/app/components/storeknox/inventory-details/unscanned-version/table/actions-header/index.scss b/app/components/storeknox/inventory-details/unscanned-version/table/actions-header/index.scss new file mode 100644 index 000000000..1ebbe7708 --- /dev/null +++ b/app/components/storeknox/inventory-details/unscanned-version/table/actions-header/index.scss @@ -0,0 +1,6 @@ +.tooltip-content { + width: 260px; + padding: 0.5em; + box-sizing: border-box; + white-space: normal; +} diff --git a/app/components/storeknox/inventory-details/unscanned-version/table/actions-header/index.ts b/app/components/storeknox/inventory-details/unscanned-version/table/actions-header/index.ts new file mode 100644 index 000000000..cef8f01a4 --- /dev/null +++ b/app/components/storeknox/inventory-details/unscanned-version/table/actions-header/index.ts @@ -0,0 +1,10 @@ +import Component from '@glimmer/component'; +import type SkAppVersionModel from 'irene/models/sk-app-version'; + +interface AppMonitoringVersionTableActionsSignature { + Args: { + skAppVersion: SkAppVersionModel; + }; +} + +export default class AppMonitoringVersionTableActionsComponent extends Component {} diff --git a/app/components/storeknox/inventory-details/unscanned-version/table/actions/index.hbs b/app/components/storeknox/inventory-details/unscanned-version/table/actions/index.hbs new file mode 100644 index 000000000..fd48343b6 --- /dev/null +++ b/app/components/storeknox/inventory-details/unscanned-version/table/actions/index.hbs @@ -0,0 +1,190 @@ +{{#if this.showNoActionText}} + + {{t 'noActionRequired'}} + +{{else}} + {{#if (or this.showAppUploadState this.relatedVersionSubmissionRecord)}} + {{#if this.submissionStatus.failed}} + + + + + + {{t 'error'}} + + + + + + + {{t 'appMonitoringModule.checkDetails'}} + + + + {{else}} + + + + + {{if + this.appIsAnalyzing + (t 'completed') + (concat (t 'uploading') '...') + }} + + + {{/if}} + {{else if this.resolveVersionSubmission.isRunning}} + + {{else}} + + <:tooltipContent> +
+ + {{t 'appMonitoringModule.initiateUploadComingSoon'}} + +
+ + + <:default> + + + <:leftIcon> + + + + <:default> + {{t 'appMonitoringModule.initiateUpload'}} + + + + {{#unless this.isIOSApp}} + + {{/unless}} + + +
+ {{/if}} +{{/if}} + +{{#if this.showErrorDetailsModal}} + + <:default> + + + + + + {{this.relatedVersionSubmissionRecord.reason}} + + + + + + + + + {{t 'appMonitoringModule.lastUploadAttemptText'}} + - + {{this.getLastUploadAttemptDate + this.relatedVersionSubmissionRecord.createdOn + }} + + + + + + + <:footer> + + + + + {{t 'appMonitoringModule.retryUpload'}} + + + + {{t 'cancel'}} + + + + +{{/if}} \ No newline at end of file diff --git a/app/components/storeknox/inventory-details/unscanned-version/table/actions/index.scss b/app/components/storeknox/inventory-details/unscanned-version/table/actions/index.scss new file mode 100644 index 000000000..62cbd2a8e --- /dev/null +++ b/app/components/storeknox/inventory-details/unscanned-version/table/actions/index.scss @@ -0,0 +1,23 @@ +.horizontal-divider { + display: block; + height: 17px; + width: 1px; + background-color: var(--appmonitoring-version-table-divider-bg); + margin: 0 0.5em; +} + +.tooltip-content { + width: 260px; + padding: 0.5em; + box-sizing: border-box; + white-space: normal; +} + +.flip-icon { + transform: rotate(180deg); +} + +.beta-chip { + background-color: var(--appmonitoring-version-table-beta-tag-bg); + color: var(--appmonitoring-version-table-beta-tag-color) !important; +} diff --git a/app/components/storeknox/inventory-details/unscanned-version/table/actions/index.ts b/app/components/storeknox/inventory-details/unscanned-version/table/actions/index.ts new file mode 100644 index 000000000..f79485748 --- /dev/null +++ b/app/components/storeknox/inventory-details/unscanned-version/table/actions/index.ts @@ -0,0 +1,193 @@ +/* eslint-disable ember/no-observers */ +import { action } from '@ember/object'; +import { inject as service } from '@ember/service'; +import Component from '@glimmer/component'; +import { task } from 'ember-concurrency'; +import Store from '@ember-data/store'; +import IntlService from 'ember-intl/services/intl'; +import { addObserver, removeObserver } from '@ember/object/observers'; +import { tracked } from '@glimmer/tracking'; + +import dayjs from 'dayjs'; +import advancedFormat from 'dayjs/plugin/advancedFormat'; + +import parseError from 'irene/utils/parse-error'; +import SubmissionModel from 'irene/models/submission'; +import RealtimeService from 'irene/services/realtime'; +import ENUMS from 'irene/enums'; +import SkAppVersionModel from 'irene/models/sk-app-version'; + +dayjs.extend(advancedFormat); + +interface AppMonitoringVersionTableActionsSignature { + Args: { + skAppVersion: SkAppVersionModel; + }; +} + +export default class AppMonitoringVersionTableActionsComponent extends Component { + @service declare ajax: any; + @service('notifications') declare notify: NotificationService; + @service declare store: Store; + @service declare intl: IntlService; + @service declare realtime: RealtimeService; + + @tracked relatedVersionSubmissionRecord: SubmissionModel | null = null; + @tracked showAppUploadState = false; + @tracked showErrorDetailsModal = false; + + constructor( + owner: unknown, + args: AppMonitoringVersionTableActionsSignature['Args'] + ) { + super(owner, args); + + this.resolveVersionSubmission.perform(); + } + + get skAppVersion() { + return this.args.skAppVersion; + } + + get isIOSApp() { + return ( + this.skAppVersion?.get('skApp')?.get('appMetadata')?.platform === + ENUMS.PLATFORM.IOS + ); + } + + get isScanned() { + return this.skAppVersion.get('file'); + } + + get submissionStatus() { + const status = this.relatedVersionSubmissionRecord?.status; + + switch (status) { + case ENUMS.SUBMISSION_STATUS.DOWNLOAD_FAILED: + case ENUMS.SUBMISSION_STATUS.VALIDATE_FAILED: + case ENUMS.SUBMISSION_STATUS.STORE_URL_VALIDATION_FAILED: + case ENUMS.SUBMISSION_STATUS.STORE_DOWNLOAD_FAILED: + case ENUMS.SUBMISSION_STATUS.STORE_UPLOAD_FAILED: + return { + label: this.intl.t('failed'), + icon: 'error', + iconColor: 'error' as const, + running: false, + failed: true, + }; + + case ENUMS.SUBMISSION_STATUS.ANALYZING: + return { + label: this.intl.t('completed'), + icon: 'download-done', + iconColor: 'success' as const, + running: false, + failed: false, + }; + + default: + return { + label: this.intl.t('inProgress'), + icon: 'downloading', + iconColor: 'info' as const, + running: true, + failed: false, + }; + } + } + + get appIsAnalyzing() { + return !this.submissionStatus?.running && !this.submissionStatus?.failed; + } + + get showNoActionText() { + return !this.showAppUploadState && this.isScanned; + } + + @action triggerInitiateUpload() { + this.initiateUpload.perform(); + } + + @action toggleShowErrorDetailsModal() { + this.showErrorDetailsModal = !this.showErrorDetailsModal; + } + + @action reloadAmAppVersion() { + if (this.appIsAnalyzing) { + this.reloadVersion.perform(); + } + } + + @action getLastUploadAttemptDate(date?: Date) { + return dayjs(date).format('Do MMM YYYY'); + } + + retryAppVersionUpload = task(async () => { + await this.initiateUpload.perform(); + this.toggleShowErrorDetailsModal(); + }); + + reloadVersion = task(async () => { + await this.skAppVersion.reload(); + }); + + resolveVersionSubmission = task(async () => { + const uploadSubmission = this.skAppVersion?.get('uploadSubmission'); + + if (uploadSubmission?.get('id')) { + const relatedVersionSubmissionRecord = await uploadSubmission; + this.relatedVersionSubmissionRecord = relatedVersionSubmissionRecord; + + if (relatedVersionSubmissionRecord) { + addObserver( + this.relatedVersionSubmissionRecord, + 'status', + this, + this.reloadAmAppVersion + ); + } + } + }); + + initiateUpload = task(async () => { + if (this.isIOSApp) { + return; + } + + try { + // TODO: APIs not available yet + // const URL = `${ENV.endpoints['amAppVersions']}/${this.amAppVersion.id}/initiate_upload`; + // const data = (await this.ajax.post(URL)) as { + // id: number; + // status: number; + // }; + // this.relatedVersionSubmissionRecord = await this.store.findRecord( + // 'submission', + // data.id + // ); + // this.showAppUploadState = true; + // addObserver( + // this.relatedVersionSubmissionRecord, + // 'status', + // this, + // this.reloadAmAppVersion + // ); + } catch (error) { + this.notify.error(parseError(error)); + } + }); + + willDestroy(): void { + super.willDestroy(); + + if (this.relatedVersionSubmissionRecord) { + removeObserver( + this.relatedVersionSubmissionRecord, + 'status', + this, + this.reloadAmAppVersion + ); + } + } +} diff --git a/app/components/storeknox/inventory-details/unscanned-version/table/countries/index.hbs b/app/components/storeknox/inventory-details/unscanned-version/table/countries/index.hbs new file mode 100644 index 000000000..304e6539c --- /dev/null +++ b/app/components/storeknox/inventory-details/unscanned-version/table/countries/index.hbs @@ -0,0 +1,28 @@ +{{#if this.fetchAmAppVersionRecords.isRunning}} + +{{else}} + + {{#if this.showEmptyCountryList}} + - + {{else}} + {{#each this.countryCodes as |countryCode idx|}} + + + {{countryCode}}{{if + (not-eq (add idx 1) this.countryCodes.length) + ',' + }} + + + {{/each}} + {{/if}} + +{{/if}} \ No newline at end of file diff --git a/app/components/storeknox/inventory-details/unscanned-version/table/countries/index.ts b/app/components/storeknox/inventory-details/unscanned-version/table/countries/index.ts new file mode 100644 index 000000000..3d1d907b6 --- /dev/null +++ b/app/components/storeknox/inventory-details/unscanned-version/table/countries/index.ts @@ -0,0 +1,50 @@ +import Component from '@glimmer/component'; +import { task } from 'ember-concurrency'; +import { inject as service } from '@ember/service'; +import Store from '@ember-data/store'; +import { tracked } from '@glimmer/tracking'; + +import AmAppRecordModel from 'irene/models/am-app-record'; +import { COUNTRY_NAMES_MAP } from 'irene/utils/constants'; +import SkAppVersionModel from 'irene/models/sk-app-version'; + +interface AppMonitoringVersionTableCountriesSignature { + Args: { + skAppVersion: SkAppVersionModel; + }; +} + +export default class AppMonitoringVersionTableCountriesComponent extends Component { + @service declare store: Store; + @service('notifications') declare notify: NotificationService; + + @tracked storeRecords: AmAppRecordModel[] = []; + @tracked countryCodes: string[] = []; + + constructor( + owner: unknown, + args: AppMonitoringVersionTableCountriesSignature['Args'] + ) { + super(owner, args); + + this.compileVersionCountryCodes.perform(); + } + + get skAppVersion() { + return this.args.skAppVersion; + } + + get showEmptyCountryList() { + return this.countryCodes.length < 1; + } + + get countryNamesMap(): Record { + return COUNTRY_NAMES_MAP; + } + + compileVersionCountryCodes = task(async () => { + const storeInstances = this.skAppVersion?.skStoreInstances.slice(); + + this.countryCodes = storeInstances.map((si) => si.countryCode); + }); +} diff --git a/app/components/storeknox/inventory-details/unscanned-version/table/date-found/index.hbs b/app/components/storeknox/inventory-details/unscanned-version/table/date-found/index.hbs new file mode 100644 index 000000000..f9b6adfc3 --- /dev/null +++ b/app/components/storeknox/inventory-details/unscanned-version/table/date-found/index.hbs @@ -0,0 +1,3 @@ + + {{this.dateFound}} + \ No newline at end of file diff --git a/app/components/storeknox/inventory-details/unscanned-version/table/date-found/index.ts b/app/components/storeknox/inventory-details/unscanned-version/table/date-found/index.ts new file mode 100644 index 000000000..03f85102d --- /dev/null +++ b/app/components/storeknox/inventory-details/unscanned-version/table/date-found/index.ts @@ -0,0 +1,18 @@ +import Component from '@glimmer/component'; +import dayjs from 'dayjs'; +import advancedFormat from 'dayjs/plugin/advancedFormat'; +import type SkAppVersionModel from 'irene/models/sk-app-version'; + +dayjs.extend(advancedFormat); + +interface AppMonitoringVersionTableDateFoundSignature { + Args: { + skAppVersion: SkAppVersionModel; + }; +} + +export default class AppMonitoringVersionTableDateFoundComponent extends Component { + get dateFound() { + return dayjs(this.args.skAppVersion.get('createdOn')).format('Do MMM YYYY'); + } +} diff --git a/app/components/storeknox/inventory-details/unscanned-version/table/index.hbs b/app/components/storeknox/inventory-details/unscanned-version/table/index.hbs new file mode 100644 index 000000000..ca583e333 --- /dev/null +++ b/app/components/storeknox/inventory-details/unscanned-version/table/index.hbs @@ -0,0 +1,108 @@ + + + + {{#if this.fetchAllSkAppVersions.isRunning}} + + + {{else if this.hasNoSkAppVersions}} + + + {{else if this.skAppVersionsToDisplay.length}} +
+
+ + + + + {{#if column.headerComponent}} + {{#let (component column.headerComponent) as |Component|}} + + {{/let}} + {{else}} + {{column.name}} + {{/if}} + + + + + + + + {{#if r.columnValue.component}} + {{#let (component r.columnValue.component) as |Component|}} + + {{/let}} + {{else}} + + {{value}} + + {{/if}} + + + + +
+
+ + {{#if this.showPagination}} + + {{/if}} + {{/if}} +
+
\ No newline at end of file diff --git a/app/components/storeknox/inventory-details/unscanned-version/table/index.scss b/app/components/storeknox/inventory-details/unscanned-version/table/index.scss new file mode 100644 index 000000000..ac769a9ba --- /dev/null +++ b/app/components/storeknox/inventory-details/unscanned-version/table/index.scss @@ -0,0 +1,23 @@ +.app-details-table-root { + background-color: var(--background-main); + + .app-details-table-wrapper { + overflow-x: auto; + + .app-details-table-container { + .app-details-table { + td:first-child { + border-left: none !important; + } + + td:last-child { + border-right: none !important; + } + + .table-row { + height: 47px !important; + } + } + } + } +} diff --git a/app/components/storeknox/inventory-details/unscanned-version/table/index.ts b/app/components/storeknox/inventory-details/unscanned-version/table/index.ts new file mode 100644 index 000000000..d507dde12 --- /dev/null +++ b/app/components/storeknox/inventory-details/unscanned-version/table/index.ts @@ -0,0 +1,163 @@ +// eslint-disable-next-line ember/use-ember-data-rfc-395-imports +import { DS } from 'ember-data'; +import Component from '@glimmer/component'; +import { tracked } from '@glimmer/tracking'; +import { inject as service } from '@ember/service'; +import { task } from 'ember-concurrency'; +import IntlService from 'ember-intl/services/intl'; +import Store from '@ember-data/store'; +import { action } from '@ember/object'; + +import { PaginationProviderActionsArgs } from 'irene/components/ak-pagination-provider'; +import parseError from 'irene/utils/parse-error'; +import SkAppVersionModel from 'irene/models/sk-app-version'; +import SkInventoryAppModel from 'irene/models/sk-inventory-app'; + +type SkAppVersionsModelArray = + DS.AdapterPopulatedRecordArray & { + meta: { count: number }; + }; + +interface StoreknoxInventoryDetailsUnscannedVersionTableSignature { + Args: { + skInventoryApp: SkInventoryAppModel | null; + isHistoryTable?: boolean; + }; +} + +export default class StoreknoxInventoryDetailsUnscannedVersionTableComponent extends Component { + @service declare intl: IntlService; + @service declare store: Store; + @service('notifications') declare notify: NotificationService; + + @tracked skAppVersionsToDisplay: SkAppVersionModel[] = []; + @tracked totalStoreVersions = 0; + @tracked limit = 5; + @tracked offset = 0; + + constructor( + owner: unknown, + args: StoreknoxInventoryDetailsUnscannedVersionTableSignature['Args'] + ) { + super(owner, args); + + this.fetchSkAppVersions(); + } + + get columns() { + return [ + this.isHistoryTable + ? { + name: this.intl.t('appMonitoringModule.foundOn'), + width: 150, + component: + 'storeknox/inventory-details/unscanned-version/table/date-found', + } + : null, + { + name: this.intl.t('appMonitoringModule.storeVersion'), + width: this.isHistoryTable ? 100 : 150, + component: + 'storeknox/inventory-details/unscanned-version/table/store-version', + }, + { + name: this.intl.t('appMonitoringModule.appStatus'), + width: this.isHistoryTable ? 250 : 200, + textAlign: 'center', + component: + 'storeknox/inventory-details/unscanned-version/table/scan-status', + }, + { + name: this.intl.t('appMonitoringModule.locatedIn'), + width: 150, + textAlign: 'center', + component: + 'storeknox/inventory-details/unscanned-version/table/countries', + }, + { + name: this.intl.t('action'), + textAlign: 'center', + width: 150, + + component: + 'storeknox/inventory-details/unscanned-version/table/actions', + + headerComponent: + 'storeknox/inventory-details/unscanned-version/table/actions-header', + }, + ].filter(Boolean); + } + + get skApp() { + return this.args.skInventoryApp; + } + + get isHistoryTable() { + return this.args.isHistoryTable; + } + + get showPagination() { + return this.isHistoryTable && this.totalStoreVersions > 5; + } + + get noVersionsFoundMsg() { + if (this.isHistoryTable) { + return this.intl.t('appMonitoringMessages.monitoringHistoryEmpty.body'); + } + + return this.intl.t('appMonitoringMessages.monitoringDetailsEmpty.body'); + } + + get hasNoSkAppVersions() { + return this.skAppVersionsToDisplay.length < 1; + } + + // Table Actions + @action goToPage({ limit, offset }: PaginationProviderActionsArgs) { + this.limit = limit; + this.offset = offset; + + this.fetchSkAppVersions(); + } + + @action onItemPerPageChange({ limit }: PaginationProviderActionsArgs) { + this.limit = limit; + this.offset = 0; + + this.fetchSkAppVersions(); + } + + @action fetchSkAppVersions() { + this.fetchAllSkAppVersions.perform(); + } + + fetchAllSkAppVersions = task(async () => { + const skAppId = this.skApp?.id; + + const query = this.isHistoryTable + ? { + limit: this.limit, + offset: this.offset, + } + : {}; + + try { + const skAppVersions = (await this.store.query('sk-app-version', { + skAppId, + is_latest: !this.isHistoryTable, + ...query, + })) as SkAppVersionsModelArray; + + this.totalStoreVersions = skAppVersions.meta.count; + this.skAppVersionsToDisplay = skAppVersions.slice(); + } catch (error) { + this.notify.error(parseError(error)); + } + }); +} + +declare module '@glint/environment-ember-loose/registry' { + export default interface Registry { + 'Storeknox::InventoryDetails::UnscannedVersion::Table': typeof StoreknoxInventoryDetailsUnscannedVersionTableComponent; + } +} diff --git a/app/components/storeknox/inventory-details/unscanned-version/table/scan-status/index.hbs b/app/components/storeknox/inventory-details/unscanned-version/table/scan-status/index.hbs new file mode 100644 index 000000000..2762b8d0d --- /dev/null +++ b/app/components/storeknox/inventory-details/unscanned-version/table/scan-status/index.hbs @@ -0,0 +1,48 @@ + + {{#if (or this.transitionedFromUnscannedToScanned this.isNotScanned)}} + + + {{#if this.transitionedFromUnscannedToScanned}} + + {{/if}} + {{/if}} + + {{#if this.isScanned}} + + + + - + + + {{t 'fileID'}} + - + {{this.skAppVersion.file.id}} + + + {{/if}} + \ No newline at end of file diff --git a/app/components/storeknox/inventory-details/unscanned-version/table/scan-status/index.ts b/app/components/storeknox/inventory-details/unscanned-version/table/scan-status/index.ts new file mode 100644 index 000000000..a9fbc64f3 --- /dev/null +++ b/app/components/storeknox/inventory-details/unscanned-version/table/scan-status/index.ts @@ -0,0 +1,49 @@ +import { inject as service } from '@ember/service'; +import Component from '@glimmer/component'; +import dayjs from 'dayjs'; +import type IntlService from 'ember-intl/services/intl'; +import type SkAppVersionModel from 'irene/models/sk-app-version'; + +interface AppMonitoringVersionTableScanStatusSignature { + Args: { + skAppVersion: SkAppVersionModel; + }; +} + +export default class AppMonitoringVersionTableScanStatusComponent extends Component { + @service declare intl: IntlService; + + get skAppVersion() { + return this.args.skAppVersion; + } + + get hasComparableVersion() { + return Boolean(this.skAppVersion?.version); + } + + get latestFile() { + return this.skAppVersion.get('file'); + } + + get hasLatestFile() { + return Boolean(this.latestFile?.get('id')); + } + + get isScanned() { + return this.hasLatestFile; + } + + get transitionedFromUnscannedToScanned() { + if (!this.latestFile?.get('createdOn')) { + return false; + } + + return dayjs(this.latestFile?.get('createdOn')).isAfter( + this.skAppVersion.get('createdOn') + ); + } + + get isNotScanned() { + return this.hasComparableVersion && !this.hasLatestFile; + } +} diff --git a/app/components/storeknox/inventory-details/unscanned-version/table/store-version/index.hbs b/app/components/storeknox/inventory-details/unscanned-version/table/store-version/index.hbs new file mode 100644 index 000000000..bfce54e59 --- /dev/null +++ b/app/components/storeknox/inventory-details/unscanned-version/table/store-version/index.hbs @@ -0,0 +1,7 @@ + + {{@skAppVersion.version}} + \ No newline at end of file diff --git a/app/components/storeknox/inventory-details/unscanned-version/table/store-version/index.ts b/app/components/storeknox/inventory-details/unscanned-version/table/store-version/index.ts new file mode 100644 index 000000000..89f9f959b --- /dev/null +++ b/app/components/storeknox/inventory-details/unscanned-version/table/store-version/index.ts @@ -0,0 +1,10 @@ +import Component from '@glimmer/component'; +import type SkAppVersionModel from 'irene/models/sk-app-version'; + +interface AppMonitoringVersionTableStoreVersionSignature { + Args: { + skAppVersion: SkAppVersionModel; + }; +} + +export default class AppMonitoringVersionTableStoreVersionComponent extends Component {} diff --git a/app/components/storeknox/inventory/app-list/index.hbs b/app/components/storeknox/inventory/app-list/index.hbs new file mode 100644 index 000000000..ba37b5244 --- /dev/null +++ b/app/components/storeknox/inventory/app-list/index.hbs @@ -0,0 +1,23 @@ +{{!-- +
+ + <:rightAdornment> + {{#if this.searchQuery}} + + + + {{else}} + + {{/if}} + + +
+
--}} + +
+ +
\ No newline at end of file diff --git a/app/components/storeknox/inventory/app-list/index.scss b/app/components/storeknox/inventory/app-list/index.scss new file mode 100644 index 000000000..19c1bded0 --- /dev/null +++ b/app/components/storeknox/inventory/app-list/index.scss @@ -0,0 +1,13 @@ +.app-list-header, +.app-list-table { + background-color: white; +} + +.search-input-container-width { + width: 170px; + margin: 15px 22px; + + .close-search-text { + cursor: pointer; + } +} diff --git a/app/components/storeknox/inventory/app-list/index.ts b/app/components/storeknox/inventory/app-list/index.ts new file mode 100644 index 000000000..4f3cd2e1d --- /dev/null +++ b/app/components/storeknox/inventory/app-list/index.ts @@ -0,0 +1,26 @@ +import Component from '@glimmer/component'; +import { tracked } from '@glimmer/tracking'; +import { action } from '@ember/object'; + +export default class StoreknoxInventoryAppListComponent extends Component { + @tracked searchQuery = ''; + @tracked discoverClicked = false; + + @action + discoverApp() { + this.searchQuery = 'Shell Test'; + this.discoverClicked = true; + } + + @action + clearSearch() { + this.searchQuery = ''; + this.discoverClicked = false; + } +} + +declare module '@glint/environment-ember-loose/registry' { + export default interface Registry { + 'Storeknox::Inventory::AppList': typeof StoreknoxInventoryAppListComponent; + } +} diff --git a/app/components/storeknox/inventory/app-list/table/availability-header/index.hbs b/app/components/storeknox/inventory/app-list/table/availability-header/index.hbs new file mode 100644 index 000000000..17b8da6ab --- /dev/null +++ b/app/components/storeknox/inventory/app-list/table/availability-header/index.hbs @@ -0,0 +1,26 @@ + + {{t 'storeknox.availability'}} + + + <:tooltipContent> +
+ + + + + {{t 'storeknox.info'}} + + + + + Lorem ipsum dolor sit amet consectetur. At suspendisse et orci risus + non sed. Mauris dui tincidunt + +
+ + + <:default> + + +
+
\ No newline at end of file diff --git a/app/components/storeknox/inventory/app-list/table/availability-header/index.scss b/app/components/storeknox/inventory/app-list/table/availability-header/index.scss new file mode 100644 index 000000000..297ed12d1 --- /dev/null +++ b/app/components/storeknox/inventory/app-list/table/availability-header/index.scss @@ -0,0 +1,14 @@ +.info-icon { + font-size: 1em !important; + height: 0.857em; + color: var( + --storeknox-discover-pending-review-table-availability-header-info-icon-color + ); +} + +.tooltip-content { + width: 17.857em; + padding: 0.5em; + box-sizing: border-box; + white-space: normal; +} diff --git a/app/components/storeknox/inventory/app-list/table/availability/index.hbs b/app/components/storeknox/inventory/app-list/table/availability/index.hbs new file mode 100644 index 000000000..98bfa6bfe --- /dev/null +++ b/app/components/storeknox/inventory/app-list/table/availability/index.hbs @@ -0,0 +1,8 @@ +{{#if @loading}} + +{{else}} + +{{/if}} \ No newline at end of file diff --git a/app/components/storeknox/inventory/app-list/table/availability/index.ts b/app/components/storeknox/inventory/app-list/table/availability/index.ts new file mode 100644 index 000000000..532afe216 --- /dev/null +++ b/app/components/storeknox/inventory/app-list/table/availability/index.ts @@ -0,0 +1,59 @@ +import Component from '@glimmer/component'; +import ENUMS from 'irene/enums'; +import type SkInventoryAppModel from 'irene/models/sk-inventory-app'; + +export interface StoreknoxInventoryAppListTableAvailabilitySignature { + Element: HTMLElement; + Args: { + app: SkInventoryAppModel; + loading: boolean; + }; +} + +export default class StoreknoxInventoryAppListTableAvailabilityComponent extends Component { + get skApp() { + return this.args.app; + } + + get appIsOnAppknoxOnly() { + return this.skApp?.availability?.appknox; + } + + get appIsOnStoreknoxOnly() { + return this.skApp?.availability?.storeknox; + } + + get appIsDisabled() { + return this.skApp?.appStatus === ENUMS.SK_APP_STATUS.INACTIVE; + } + + get svgComponent() { + if ( + this.appIsDisabled && + this.appIsOnAppknoxOnly && + !this.appIsOnStoreknoxOnly + ) { + return 'ak-svg/disabled-vapt-indicator'; + } + + if (this.appIsOnAppknoxOnly) { + return 'ak-svg/vapt-indicator'; + } + + if (this.appIsOnStoreknoxOnly) { + return 'ak-svg/sm-indicator'; + } + + if (this.appIsDisabled) { + return 'ak-svg/info-indicator'; + } + + return 'ak-svg/vapt-indicator'; + } +} + +declare module '@glint/environment-ember-loose/registry' { + export default interface Registry { + 'storeknox/inventory/app-list/table/availability': typeof StoreknoxInventoryAppListTableAvailabilityComponent; + } +} diff --git a/app/components/storeknox/inventory/app-list/table/checkbox/index.hbs b/app/components/storeknox/inventory/app-list/table/checkbox/index.hbs new file mode 100644 index 000000000..edffa32a4 --- /dev/null +++ b/app/components/storeknox/inventory/app-list/table/checkbox/index.hbs @@ -0,0 +1,11 @@ +{{#if @loading}} + + + +{{else}} + +{{/if}} \ No newline at end of file diff --git a/app/components/storeknox/inventory/app-list/table/checkbox/index.ts b/app/components/storeknox/inventory/app-list/table/checkbox/index.ts new file mode 100644 index 000000000..712a41cd8 --- /dev/null +++ b/app/components/storeknox/inventory/app-list/table/checkbox/index.ts @@ -0,0 +1,39 @@ +import { action } from '@ember/object'; +import Component from '@glimmer/component'; +import type SkInventoryAppModel from 'irene/models/sk-inventory-app'; + +interface StoreknoxInventoryAppListTableMonitoringCheckboxSignature { + Args: { + app?: SkInventoryAppModel; + loading: boolean; + disabledChecking: boolean; + appIsSelected: boolean; + selectedDisabledAppIds: string[]; + selectDisabledAppRow: (ulid: string, value: boolean) => void; + }; +} + +export default class StoreknoxInventoryAppListTableMonitoringCheckboxComponent extends Component { + get docUlid() { + return this.args.app?.appMetadata?.docUlid as string; + } + + get isChecked() { + return Boolean( + this.args.selectedDisabledAppIds.find((id) => id === this.docUlid) + ); + } + + @action handleChange(event: Event) { + this.args.selectDisabledAppRow( + this.docUlid, + (event.target as HTMLInputElement).checked + ); + } +} + +declare module '@glint/environment-ember-loose/registry' { + export default interface Registry { + 'storeknox/inventory/app-list/table/checkbox': typeof StoreknoxInventoryAppListTableMonitoringCheckboxComponent; + } +} diff --git a/app/components/storeknox/inventory/app-list/table/index.hbs b/app/components/storeknox/inventory/app-list/table/index.hbs new file mode 100644 index 000000000..b5668a500 --- /dev/null +++ b/app/components/storeknox/inventory/app-list/table/index.hbs @@ -0,0 +1,69 @@ + + + + + + {{#if column.headerComponent}} + {{#let (component column.headerComponent) as |Component|}} + + {{/let}} + {{else}} + {{column.name}} + {{/if}} + + + + + + + + {{#let (component r.columnValue.cellComponent) as |Component|}} + + {{/let}} + + + + + + {{#if this.showPagination}} + + {{/if}} + \ No newline at end of file diff --git a/app/components/storeknox/inventory/app-list/table/index.scss b/app/components/storeknox/inventory/app-list/table/index.scss new file mode 100644 index 000000000..cb230e6ef --- /dev/null +++ b/app/components/storeknox/inventory/app-list/table/index.scss @@ -0,0 +1,3 @@ +.inventory-app-list-table { + background-color: white; +} diff --git a/app/components/storeknox/inventory/app-list/table/index.ts b/app/components/storeknox/inventory/app-list/table/index.ts new file mode 100644 index 000000000..3a368d799 --- /dev/null +++ b/app/components/storeknox/inventory/app-list/table/index.ts @@ -0,0 +1,233 @@ +// eslint-disable-next-line ember/use-ember-data-rfc-395-imports +import DS from 'ember-data'; +import Component from '@glimmer/component'; +import { action } from '@ember/object'; +import { service } from '@ember/service'; +import { tracked } from '@glimmer/tracking'; +import { task } from 'ember-concurrency'; +import type RouterService from '@ember/routing/router-service'; +import type { Owner } from '@ember/test-helpers/build-owner'; + +import parseError from 'irene/utils/parse-error'; +import ENUMS from 'irene/enums'; +import type IntlService from 'ember-intl/services/intl'; +import type MeService from 'irene/services/me'; +import type Store from '@ember-data/store'; +import type SkInventoryAppModel from 'irene/models/sk-inventory-app'; +import type { StoreknoxCommonTableColumnsData } from 'irene/components/storeknox/table-columns'; +import type { PaginationProviderActionsArgs } from 'irene/components/ak-pagination-provider'; + +type SkAppsQueryResponse = + DS.AdapterPopulatedRecordArray & { + meta: { count: number }; + }; + +type StoreknoxInventoryTableDataItem = StoreknoxCommonTableColumnsData & { + appIsSelected: boolean; + app: SkInventoryAppModel; +}; + +export interface StoreknoxInventoryAppListTableSignature { + Element: HTMLElement; + Args: { + isAddingAppToInventory?: boolean; + loadDisabledApps?: boolean; + handleSelectedDisabledApps?: (apps: string[]) => void; + }; +} + +export default class StoreknoxInventoryAppListTableComponent extends Component { + @service declare router: RouterService; + @service declare intl: IntlService; + @service declare me: MeService; + @service declare store: Store; + @service('notifications') declare notify: NotificationService; + + @tracked skAppsResponse: SkAppsQueryResponse | null = null; + @tracked limit = 10; + @tracked offset = 0; + @tracked selectedDisabledAppSet = new Set(); + @tracked selectedDisabledAppIds: string[] = []; + + constructor( + owner: Owner, + args: StoreknoxInventoryAppListTableSignature['Args'] + ) { + super(owner, args); + + this.fetchSkInventoryApps.perform(); + } + + get loadDisabledApps() { + return this.args.loadDisabledApps; + } + + get disableRowClick() { + return this.loadDisabledApps || this.fetchSkInventoryApps.isRunning; + } + + get columns() { + return [ + this.loadDisabledApps + ? { + cellComponent: 'storeknox/inventory/app-list/table/checkbox', + minWidth: 20, + width: 20, + textAlign: 'center', + } + : null, + { + headerComponent: 'storeknox/table-columns/store-header', + cellComponent: 'storeknox/table-columns/store', + minWidth: 80, + width: 80, + textAlign: 'center', + }, + { + name: this.intl.t('application'), + cellComponent: 'storeknox/table-columns/application', + width: 300, + }, + { + name: this.intl.t('developer'), + cellComponent: 'storeknox/table-columns/developer', + width: 250, + }, + { + name: 'Monitoring Status', + cellComponent: 'storeknox/inventory/app-list/table/monitoring-status', + width: 150, + }, + // { + // headerComponent: + // 'storeknox/inventory/app-list/table/availability-header', + // cellComponent: 'storeknox/inventory/app-list/table/availability', + // textAlign: 'center', + // }, + ].filter(Boolean); + } + + get totalAppsCount() { + return this.skAppsResponse?.meta.count || 0; + } + + get tableData() { + if (this.isFetchingTableData) { + return this.mockLoadingData; + } + + return (this.skAppsResponse?.map((app) => { + const { appMetadata } = app; + + return { + title: appMetadata.title, + appUrl: appMetadata.url, + isAndroid: appMetadata?.platform === ENUMS.PLATFORM.ANDROID, + isIos: appMetadata?.platform === ENUMS.PLATFORM.IOS, + iconUrl: appMetadata?.iconUrl, + name: appMetadata.title, + packageName: appMetadata.packageName, + companyName: appMetadata.devEmail, + devName: appMetadata.devName, + devEmail: appMetadata.devEmail, + app, + appIsSelected: this.selectedDisabledAppIds.some( + (id) => id === (appMetadata.docUlid as string) + ), + }; + }) || []) as StoreknoxInventoryTableDataItem[]; + } + + get mockLoadingData() { + return Array.from(new Array(7)).map(() => ({})); + } + + get showPagination() { + return !this.isFetchingTableData && this.totalAppsCount > this.limit; + } + + get isFetchingTableData() { + return this.fetchSkInventoryApps.isRunning; + } + + @action selectDisabledAppRow(ulid: string, value: boolean) { + const selectedApsIds = [...this.selectedDisabledAppIds]; + + if (value) { + selectedApsIds.push(ulid); + this.selectedDisabledAppIds = selectedApsIds; + } else { + this.selectedDisabledAppIds = selectedApsIds.filter((id) => id !== ulid); + } + + this.args.handleSelectedDisabledApps?.(this.selectedDisabledAppIds); + } + + @action getRowIsSelected(ulid: string, selectedItems: string[]) { + return selectedItems.some((id) => id === ulid); + } + + @action goToPage(args: PaginationProviderActionsArgs) { + const { limit, offset } = args; + + this.limit = limit; + this.offset = offset; + + this.router.transitionTo('authenticated.storeknox.inventory.app-list', { + queryParams: { app_limit: limit, app_offset: offset }, + }); + + this.fetchSkInventoryApps.perform(); + } + + @action onItemPerPageChange(args: PaginationProviderActionsArgs) { + const { limit } = args; + + this.limit = limit; + this.offset = 0; + + this.router.transitionTo('authenticated.storeknox.inventory.app-list', { + queryParams: { app_limit: limit, app_offset: 0 }, + }); + + this.fetchSkInventoryApps.perform(); + } + + @action handleRowClick(app: StoreknoxInventoryTableDataItem) { + if (!this.disableRowClick) { + this.router.transitionTo( + 'authenticated.storeknox.inventory-details.index', + app.app.id + ); + } + } + + fetchSkInventoryApps = task(async () => { + const query = !this.loadDisabledApps + ? { + approval_status: ENUMS.SK_APPROVAL_STATUS.APPROVED, + app_status: ENUMS.SK_APP_STATUS.ACTIVE, + } + : { + app_status: ENUMS.SK_APP_STATUS.INACTIVE, + }; + + try { + const data = (await this.store.query('sk-app', { + limit: this.limit, + offset: this.offset, + ...query, + })) as SkAppsQueryResponse; + + this.skAppsResponse = data; + } catch (error) { + this.notify.error(parseError(error, this.intl.t('somethingWentWrong'))); + } + }); +} + +declare module '@glint/environment-ember-loose/registry' { + export default interface Registry { + 'Storeknox::Inventory::AppList::Table': typeof StoreknoxInventoryAppListTableComponent; + } +} diff --git a/app/components/storeknox/inventory/app-list/table/monitoring-status/index.hbs b/app/components/storeknox/inventory/app-list/table/monitoring-status/index.hbs new file mode 100644 index 000000000..e790d86f7 --- /dev/null +++ b/app/components/storeknox/inventory/app-list/table/monitoring-status/index.hbs @@ -0,0 +1,50 @@ + + <:tooltipContent> +
+ + + {{t 'reason'}} + + + + + {{this.tooltipMessage}} + {{t 'storeknox.haveBeenDetected'}} + +
+ + + <:default> + + {{#if @loading}} + + + + {{else}} + + + + {{#if this.appIsDisabled}} + {{t 'disabled'}} + {{else if this.needsAction}} + {{t 'storeknox.needsAction'}} + {{else}} + {{t 'storeknox.noActionNeeded'}} + {{/if}} + + {{/if}} + + +
\ No newline at end of file diff --git a/app/components/storeknox/inventory/app-list/table/monitoring-status/index.scss b/app/components/storeknox/inventory/app-list/table/monitoring-status/index.scss new file mode 100644 index 000000000..f928de661 --- /dev/null +++ b/app/components/storeknox/inventory/app-list/table/monitoring-status/index.scss @@ -0,0 +1,6 @@ +.tooltip-content { + width: 17.857em; + padding: 0.5em; + box-sizing: border-box; + white-space: normal; +} diff --git a/app/components/storeknox/inventory/app-list/table/monitoring-status/index.ts b/app/components/storeknox/inventory/app-list/table/monitoring-status/index.ts new file mode 100644 index 000000000..7a1e85485 --- /dev/null +++ b/app/components/storeknox/inventory/app-list/table/monitoring-status/index.ts @@ -0,0 +1,56 @@ +import Component from '@glimmer/component'; +import { service } from '@ember/service'; +import type IntlService from 'ember-intl/services/intl'; + +import ENUMS from 'irene/enums'; +import SkInventoryAppModel from 'irene/models/sk-inventory-app'; + +export interface StoreknoxInventoryAppListTableMonitoringStatusSignature { + Element: HTMLElement; + Args: { + app?: SkInventoryAppModel; + loading: boolean; + }; +} + +export default class StoreknoxInventoryAppListTableMonitoringStatusComponent extends Component { + @service declare intl: IntlService; + + // TODO: Confirm what defines when an app needs action + get needsAction() { + return ( + this.args.app?.storeMonitoringStatus === + ENUMS.SK_APP_MONITORING_STATUS.UNSCANNED + ); + } + + get appIsDisabled() { + return this.args.app?.appStatus === ENUMS.SK_APP_STATUS.INACTIVE; + } + + get tooltipMessage() { + if (this.needsAction) { + return this.intl.t('storeknox.actionsNeededMsg'); + } + + return this.intl.t('storeknox.noActionsNeededMsg'); + } + + get monitoringStatusIconDetails() { + if (this.appIsDisabled) { + return { name: 'do-not-disturb-on', color: 'textSecondary' as const }; + } + + if (this.needsAction) { + return { name: 'info', color: 'error' as const }; + } + + return { name: 'check-circle', color: 'success' as const }; + } +} + +declare module '@glint/environment-ember-loose/registry' { + export default interface Registry { + 'storeknox/inventory/app-list/table/monitoring-status': typeof StoreknoxInventoryAppListTableMonitoringStatusComponent; + } +} diff --git a/app/components/storeknox/inventory/disabled-apps/index.hbs b/app/components/storeknox/inventory/disabled-apps/index.hbs new file mode 100644 index 000000000..f08e7251f --- /dev/null +++ b/app/components/storeknox/inventory/disabled-apps/index.hbs @@ -0,0 +1,44 @@ + +
+ + <:rightAdornment> + {{#if this.searchQuery}} + + {{else}} + + {{/if}} + + +
+ + + + Move To Inventory + + + {{! + + Archived Apps + + }} + +
+ +
+ +
\ No newline at end of file diff --git a/app/components/storeknox/inventory/disabled-apps/index.scss b/app/components/storeknox/inventory/disabled-apps/index.scss new file mode 100644 index 000000000..4671236d6 --- /dev/null +++ b/app/components/storeknox/inventory/disabled-apps/index.scss @@ -0,0 +1,12 @@ +.app-list-header { + background-color: white; + padding: 15px 22px; +} + +.search-input-container-width { + width: 170px; + + .close-search-text { + cursor: pointer; + } +} diff --git a/app/components/storeknox/inventory/disabled-apps/index.ts b/app/components/storeknox/inventory/disabled-apps/index.ts new file mode 100644 index 000000000..405fb2de7 --- /dev/null +++ b/app/components/storeknox/inventory/disabled-apps/index.ts @@ -0,0 +1,41 @@ +import Component from '@glimmer/component'; +import { tracked } from '@glimmer/tracking'; +import { action } from '@ember/object'; +import { inject as service } from '@ember/service'; +import MeService from 'irene/services/me'; + +export default class StoreknoxInventoryDisabledAppsComponent extends Component { + @service declare me: MeService; + + @tracked searchQuery = ''; + @tracked selectedDisabledApps: string[] = []; + + get isOwner() { + return this.me.org?.is_owner; + } + + get showMoveToInventoryBtn() { + return this.isOwner && this.selectedDisabledApps.length > 0; + } + + @action + discoverApp() { + this.searchQuery = 'Shell Test'; + } + + @action + clearSearch() { + this.searchQuery = ''; + } + + @action + handleSelectedDisabledApps(apps: string[]) { + this.selectedDisabledApps = apps; + } +} + +declare module '@glint/environment-ember-loose/registry' { + export default interface Registry { + 'Storeknox::Inventory::DisabledApps': typeof StoreknoxInventoryDisabledAppsComponent; + } +} diff --git a/app/components/storeknox/inventory/disabled-apps/table/availability-header/index.hbs b/app/components/storeknox/inventory/disabled-apps/table/availability-header/index.hbs new file mode 100644 index 000000000..17b8da6ab --- /dev/null +++ b/app/components/storeknox/inventory/disabled-apps/table/availability-header/index.hbs @@ -0,0 +1,26 @@ + + {{t 'storeknox.availability'}} + + + <:tooltipContent> +
+ + + + + {{t 'storeknox.info'}} + + + + + Lorem ipsum dolor sit amet consectetur. At suspendisse et orci risus + non sed. Mauris dui tincidunt + +
+ + + <:default> + + +
+
\ No newline at end of file diff --git a/app/components/storeknox/inventory/disabled-apps/table/availability-header/index.scss b/app/components/storeknox/inventory/disabled-apps/table/availability-header/index.scss new file mode 100644 index 000000000..297ed12d1 --- /dev/null +++ b/app/components/storeknox/inventory/disabled-apps/table/availability-header/index.scss @@ -0,0 +1,14 @@ +.info-icon { + font-size: 1em !important; + height: 0.857em; + color: var( + --storeknox-discover-pending-review-table-availability-header-info-icon-color + ); +} + +.tooltip-content { + width: 17.857em; + padding: 0.5em; + box-sizing: border-box; + white-space: normal; +} diff --git a/app/components/storeknox/inventory/disabled-apps/table/availability/index.hbs b/app/components/storeknox/inventory/disabled-apps/table/availability/index.hbs new file mode 100644 index 000000000..ae106685d --- /dev/null +++ b/app/components/storeknox/inventory/disabled-apps/table/availability/index.hbs @@ -0,0 +1,4 @@ + \ No newline at end of file diff --git a/app/components/storeknox/inventory/disabled-apps/table/index.hbs b/app/components/storeknox/inventory/disabled-apps/table/index.hbs new file mode 100644 index 000000000..679267707 --- /dev/null +++ b/app/components/storeknox/inventory/disabled-apps/table/index.hbs @@ -0,0 +1,51 @@ + + + + + + {{#if column.headerComponent}} + {{#let (component column.headerComponent) as |Component|}} + + {{/let}} + {{else}} + {{column.name}} + {{/if}} + + + + + + + {{#let (component r.columnValue.cellComponent) as |Component|}} + + {{/let}} + + + + + + + \ No newline at end of file diff --git a/app/components/storeknox/inventory/disabled-apps/table/index.ts b/app/components/storeknox/inventory/disabled-apps/table/index.ts new file mode 100644 index 000000000..2733ac66a --- /dev/null +++ b/app/components/storeknox/inventory/disabled-apps/table/index.ts @@ -0,0 +1,126 @@ +import Component from '@glimmer/component'; +import { action } from '@ember/object'; +import { inject as service } from '@ember/service'; +import RouterService from '@ember/routing/router-service'; +import { tracked } from '@glimmer/tracking'; +import IntlService from 'ember-intl/services/intl'; +import MeService from 'irene/services/me'; + +interface LimitOffset { + limit: number; + offset: number; +} + +export default class StoreknoxInventoryDisabledAppsTableComponent extends Component { + @service declare router: RouterService; + @service declare intl: IntlService; + @service declare me: MeService; + + @tracked searchResults = [ + { + isAndroid: true, + iconUrl: + 'https://appknox-production-public.s3.amazonaws.com/908e507e-1148-4f4d-9939-6dba3d645abc.png', + name: 'Shell Asia', + packageName: 'com.shellasia.android', + companyName: 'Shell Information Technology International', + mailId: 'asiashell@shell.com', + svg: 'ak-svg/vapt-indicator', + }, + { + isAndroid: true, + iconUrl: + 'https://appknox-production-public.s3.amazonaws.com/908e507e-1148-4f4d-9939-6dba3d645abc.png', + name: 'Shell Recharge India', + packageName: 'com.shellrecharge.india', + companyName: 'Shell Information Technology International', + svg: 'ak-svg/sm-indicator', + }, + { + isIos: true, + iconUrl: + 'https://appknox-production-public.s3.amazonaws.com/908e507e-1148-4f4d-9939-6dba3d645abc.png', + name: 'Shell Mobility Site Manager', + packageName: 'com.shellmobility.ios', + companyName: 'Shell Information Technology International', + svg: 'ak-svg/info-indicator', + }, + ]; + + get columns() { + return [ + { + headerComponent: 'storeknox/table-columns/store-header', + cellComponent: 'storeknox/table-columns/store', + minWidth: 50, + width: 50, + textAlign: 'center', + }, + { + name: this.intl.t('application'), + cellComponent: 'storeknox/table-columns/application', + width: 200, + }, + { + name: this.intl.t('developer'), + cellComponent: 'storeknox/table-columns/developer', + width: 200, + }, + { + name: 'Monitoring Status', + cellComponent: + 'storeknox/inventory/disabled-apps/table/monitoring-status', + width: 200, + }, + { + headerComponent: + 'storeknox/inventory/disabled-apps/table/availability-header', + cellComponent: 'storeknox/inventory/disabled-apps/table/availability', + textAlign: 'center', + }, + ]; + } + + // Table Actions + @action goToPage(args: LimitOffset) { + const { limit, offset } = args; + this.router.transitionTo('authenticated.storeknox.inventory.app-list', { + queryParams: { app_limit: limit, app_offset: offset }, + }); + } + + @action onItemPerPageChange(args: LimitOffset) { + const { limit } = args; + const offset = 0; + + this.router.transitionTo('authenticated.storeknox.inventory.app-list', { + queryParams: { app_limit: limit, app_offset: offset }, + }); + } + + get totalCount() { + return this.searchResults.length || 0; + } + + get tableData() { + return this.searchResults; + } + + get itemPerPageOptions() { + return [10, 25, 50]; + } + + get limit() { + return 10; + } + + get offset() { + return 0; + } +} + +declare module '@glint/environment-ember-loose/registry' { + export default interface Registry { + 'Storeknox::Inventory::DisabledApps::Table': typeof StoreknoxInventoryDisabledAppsTableComponent; + } +} diff --git a/app/components/storeknox/inventory/disabled-apps/table/monitoring-status/index.hbs b/app/components/storeknox/inventory/disabled-apps/table/monitoring-status/index.hbs new file mode 100644 index 000000000..88f1b5f1b --- /dev/null +++ b/app/components/storeknox/inventory/disabled-apps/table/monitoring-status/index.hbs @@ -0,0 +1,24 @@ + + <:tooltipContent> +
+ + Lorem ipsum dolor sit amet consectetur. At suspendisse et orci risus non + sed. Mauris dui tincidunt + +
+ + + <:default> + + + + + Disabled + + + +
\ No newline at end of file diff --git a/app/components/storeknox/inventory/disabled-apps/table/monitoring-status/index.scss b/app/components/storeknox/inventory/disabled-apps/table/monitoring-status/index.scss new file mode 100644 index 000000000..f928de661 --- /dev/null +++ b/app/components/storeknox/inventory/disabled-apps/table/monitoring-status/index.scss @@ -0,0 +1,6 @@ +.tooltip-content { + width: 17.857em; + padding: 0.5em; + box-sizing: border-box; + white-space: normal; +} diff --git a/app/components/storeknox/inventory/index.hbs b/app/components/storeknox/inventory/index.hbs new file mode 100644 index 000000000..72dbf84f3 --- /dev/null +++ b/app/components/storeknox/inventory/index.hbs @@ -0,0 +1,107 @@ + + + + {{t 'storeknox.inventoryHeader'}} + + + + {{t 'storeknox.inventoryDescription'}} + + + + + + + {{t 'storeknox.discoverHeader'}} + + + + + + + + + + + + + {{#each this.tabItems as |item|}} + + {{item.label}} + + {{/each}} + + + + + + + Inventory Settings + + + + + + + + + + + Add Appknox Projects to Inventory by default + + + + Turning this toggle ON would ensure any new app/namespace uploaded in + your Appknox account, reflects in your Org Inventory automatically + + + + + + + + + \ No newline at end of file diff --git a/app/components/storeknox/inventory/index.scss b/app/components/storeknox/inventory/index.scss new file mode 100644 index 000000000..2301f9b45 --- /dev/null +++ b/app/components/storeknox/inventory/index.scss @@ -0,0 +1,6 @@ +.header-storeknox-discover-page { + background-color: white; + margin: 0.714em 0; + border: 1px solid var(--storeknox-discover-header-border-color); + padding: 1.428em; +} diff --git a/app/components/storeknox/inventory/index.ts b/app/components/storeknox/inventory/index.ts new file mode 100644 index 000000000..6e1922eaf --- /dev/null +++ b/app/components/storeknox/inventory/index.ts @@ -0,0 +1,99 @@ +// eslint-disable-next-line ember/use-ember-data-rfc-395-imports +import DS from 'ember-data'; +import Component from '@glimmer/component'; +import { inject as service } from '@ember/service'; +import { action } from '@ember/object'; +import { tracked } from 'tracked-built-ins'; +import { task } from 'ember-concurrency'; +import type Store from '@ember-data/store'; +import type IntlService from 'ember-intl/services/intl'; + +import ENUMS from 'irene/enums'; +import type SkPendingReviewService from 'irene/services/sk-pending-review'; +import type MeService from 'irene/services/me'; +import type SkInventoryAppModel from 'irene/models/sk-inventory-app'; + +type SkAppsQueryResponse = + DS.AdapterPopulatedRecordArray & { + meta: { count: number }; + }; + +export default class StoreknoxInventoryComponent extends Component { + @service declare intl: IntlService; + @service declare store: Store; + @service declare me: MeService; + @service declare skPendingReview: SkPendingReviewService; + + @tracked showWelcomeModal = false; + @tracked showSettingsDrawer = false; + + @tracked totalInventoryAppsCount = 0; + @tracked totalDisabledAppsCount = 0; + + constructor(owner: unknown, args: object) { + super(owner, args); + + this.getTabItemsCount.perform(); + this.getSkOrganization.perform(); + } + + get tabItems() { + return [ + { + id: 'app-inventory', + route: 'authenticated.storeknox.inventory.app-list', + label: this.intl.t('storeknox.appInventory'), + hasBadge: this.totalInventoryAppsCount > 0, + badgeCount: this.totalInventoryAppsCount, + }, + this.me.org?.is_admin && { + id: 'pending-review', + route: 'authenticated.storeknox.inventory.pending-reviews', + label: this.intl.t('storeknox.pendingReview'), + hasBadge: this.skPendingReview.totalCount > 0, + badgeCount: this.skPendingReview.totalCount, + }, + // { + // id: 'disabled-apps', + // route: 'authenticated.storeknox.inventory.disabled-apps', + // label: this.intl.t('storeknox.disabledApps'), + // activeRoutes: 'authenticated.storeknox.inventory.disabled-apps', + // hasBadge: this.totalDisabledAppsCount > 0, + // badgeCount: this.totalDisabledAppsCount, + // }, + ].filter(Boolean); + } + + @action closeWelcomeModal() { + this.showWelcomeModal = false; + } + + @action openSettingsDrawer() { + this.showSettingsDrawer = true; + } + + @action closeSettingsDrawer() { + this.showSettingsDrawer = false; + } + + getTabItemsCount = task(async () => { + this.totalInventoryAppsCount = ( + (await this.store.query('sk-app', { + approval_status: ENUMS.SK_APPROVAL_STATUS.APPROVED, + app_status: ENUMS.SK_APP_STATUS.ACTIVE, + })) as SkAppsQueryResponse + ).meta.count; + }); + + toggleAddToInventoryByDefault = task(async () => {}); + + getSkOrganization = task(async () => { + this.store.query('sk-organization', {}); + }); +} + +declare module '@glint/environment-ember-loose/registry' { + export default interface Registry { + 'Storeknox::Inventory': typeof StoreknoxInventoryComponent; + } +} diff --git a/app/components/storeknox/inventory/pending-review/index.ts b/app/components/storeknox/inventory/pending-review/index.ts index 0f2972f1a..f28c2aec4 100644 --- a/app/components/storeknox/inventory/pending-review/index.ts +++ b/app/components/storeknox/inventory/pending-review/index.ts @@ -3,12 +3,13 @@ import { action } from '@ember/object'; import { inject as service } from '@ember/service'; import type IntlService from 'ember-intl/services/intl'; -import type { StoreknoxDiscoveryReviewQueryParam } from 'irene/routes/authenticated/storeknox/discover/review'; +import type { StoreknoxInventoryPendingReviewsQueryParam } from 'irene/routes/authenticated/storeknox/inventory/pending-reviews'; import type SkPendingReviewService from 'irene/services/sk-pending-review'; +import type SkAppModel from 'irene/models/sk-app'; export interface StoreknoxInventoryResultsSignature { Args: { - queryParams: StoreknoxDiscoveryReviewQueryParam; + queryParams: StoreknoxInventoryPendingReviewsQueryParam; }; } @@ -75,7 +76,7 @@ export default class StoreknoxInventoryPendingReviewComponent extends Component< get reviewAppsData() { if (this.isFetchingData) { - return Array.from({ length: 5 }, () => ({})); + return Array.from({ length: 5 }, () => ({})) as SkAppModel[]; } else { return this.skPendingReview.skPendingReviewData?.slice() || []; } diff --git a/app/components/storeknox/inventory/pending-review/table/index.hbs b/app/components/storeknox/inventory/pending-review/table/index.hbs index d3a680053..5917bd83f 100644 --- a/app/components/storeknox/inventory/pending-review/table/index.hbs +++ b/app/components/storeknox/inventory/pending-review/table/index.hbs @@ -38,19 +38,21 @@ - + {{#if this.showPagination}} + + {{/if}} \ No newline at end of file diff --git a/app/components/storeknox/inventory/pending-review/table/index.ts b/app/components/storeknox/inventory/pending-review/table/index.ts index 05d3be5c5..9514e2aee 100644 --- a/app/components/storeknox/inventory/pending-review/table/index.ts +++ b/app/components/storeknox/inventory/pending-review/table/index.ts @@ -1,6 +1,6 @@ import Component from '@glimmer/component'; -import type { StoreknoxDiscoveryReviewQueryParam } from 'irene/routes/authenticated/storeknox/discover/review'; +import type { StoreknoxInventoryPendingReviewsQueryParam } from 'irene/routes/authenticated/storeknox/inventory/pending-reviews'; import type SkAppModel from 'irene/models/sk-app'; interface LimitOffset { @@ -17,7 +17,7 @@ export interface StoreknoxInventoryPendingReviewTableSignature { totalCount: number; goToPage: (args: LimitOffset) => void; onItemPerPageChange: (args: LimitOffset) => void; - queryParams: StoreknoxDiscoveryReviewQueryParam; + queryParams: StoreknoxInventoryPendingReviewsQueryParam; }; } @@ -25,6 +25,13 @@ export default class StoreknoxInventoryPendingReviewTableComponent extends Compo get itemPerPageOptions() { return [10, 25, 50]; } + + get showPagination() { + return ( + !this.args.loadingData && + this.args.totalCount > this.args.queryParams.app_limit + ); + } } declare module '@glint/environment-ember-loose/registry' { diff --git a/app/components/storeknox/inventory/pending-review/table/status/index.ts b/app/components/storeknox/inventory/pending-review/table/status/index.ts index 40b2ae29e..2408a8448 100644 --- a/app/components/storeknox/inventory/pending-review/table/status/index.ts +++ b/app/components/storeknox/inventory/pending-review/table/status/index.ts @@ -8,7 +8,7 @@ import parseError from 'irene/utils/parse-error'; import type IntlService from 'ember-intl/services/intl'; import type SkAppModel from 'irene/models/sk-app'; -import type { StoreknoxDiscoveryReviewQueryParam } from 'irene/routes/authenticated/storeknox/discover/review'; +import type { StoreknoxInventoryPendingReviewsQueryParam } from 'irene/routes/authenticated/storeknox/inventory/pending-reviews'; import type SkPendingReviewService from 'irene/services/sk-pending-review'; import ENUMS from 'irene/enums'; @@ -16,7 +16,7 @@ interface StoreknoxInventoryPendingReviewTableStatusSignature { Args: { data: SkAppModel; loading: boolean; - queryParams: StoreknoxDiscoveryReviewQueryParam; + queryParams: StoreknoxInventoryPendingReviewsQueryParam; }; } diff --git a/app/components/storeknox/product-icon/index.hbs b/app/components/storeknox/product-icon/index.hbs new file mode 100644 index 000000000..b19a659ed --- /dev/null +++ b/app/components/storeknox/product-icon/index.hbs @@ -0,0 +1,62 @@ +
+
+ {{yield}} +
+ + + + + + + + INFO + + + + {{t 'storeknox.appIsPartOf'}} + {{get this.productTitle @product}} + + + + {{#if this.isAStore}} + + + + {{t 'storeknox.checkOn'}} + {{get this.productTitle @product}} + + + + {{/if}} + + +
\ No newline at end of file diff --git a/app/components/storeknox/product-icon/index.scss b/app/components/storeknox/product-icon/index.scss new file mode 100644 index 000000000..354c6d49e --- /dev/null +++ b/app/components/storeknox/product-icon/index.scss @@ -0,0 +1,38 @@ +.product-icon { + width: 32px; + height: 32px; + display: flex; + align-items: center; + justify-content: center; + border: 1px solid var(--border-color-1); + border-radius: 100%; + cursor: pointer; + + &.not-clickable { + cursor: default; + } + + &.product-vapt, + &.product-sm { + border: none; + } + + p::first-letter { + color: inherit; + font-weight: 800; + } +} + +.product-info-tray { + background-color: var(--background-main); + border: 1px solid var(--border-color-1); + width: 260px; + margin-top: 10px; + box-shadow: 0px 4px 12px 0px rgba(64, 64, 64, 0.25); +} + +.store-link-container { + background-color: var(--neutral-grey-100); + padding-top: 12px; + padding-bottom: 15px; +} diff --git a/app/components/storeknox/product-icon/index.ts b/app/components/storeknox/product-icon/index.ts new file mode 100644 index 000000000..40a7edde3 --- /dev/null +++ b/app/components/storeknox/product-icon/index.ts @@ -0,0 +1,57 @@ +import { action } from '@ember/object'; +import { service } from '@ember/service'; +import Component from '@glimmer/component'; +import IntlService from 'ember-intl/services/intl'; +import { tracked } from 'tracked-built-ins'; + +interface StoreknoxProductIconSignature { + Args: { + product: 'sm' | 'vapt' | 'android' | 'ios'; + notClickable?: boolean; + storeLink?: string; + }; + Blocks: { default: [] }; +} + +export default class StoreknoxProductIcon extends Component { + @service declare intl: IntlService; + + @tracked anchorRef: HTMLElement | null = null; + + get productTitle() { + return { + vapt: this.intl.t('storeknox.vapt'), + sm: this.intl.t('appMonitoring'), + android: this.intl.t('storeknox.playStore'), + ios: this.intl.t('storeknox.appStore'), + }; + } + + get isAStore() { + return ['android', 'ios'].indexOf(String(this.args.product)) > -1; + } + + @action toggleProductInfo(event: Event) { + if (this.args.notClickable) { + return; + } + + if (this.anchorRef) { + this.closeProductInfo(); + + return; + } + + this.anchorRef = event.currentTarget as HTMLElement; + } + + @action closeProductInfo() { + this.anchorRef = null; + } +} + +declare module '@glint/environment-ember-loose/registry' { + export default interface Registry { + 'Storeknox::ProductIcon': typeof StoreknoxProductIcon; + } +} diff --git a/app/components/storeknox/review-logs/index.hbs b/app/components/storeknox/review-logs/index.hbs index d8d26bd1e..9a3ff3175 100644 --- a/app/components/storeknox/review-logs/index.hbs +++ b/app/components/storeknox/review-logs/index.hbs @@ -1,13 +1,3 @@ - - {{#each this.breadcrumbItems as |item|}} - - {{/each}} - - { + @action openAppLinkInNewTab(ev: Event) { + ev.stopPropagation(); + } +} + +declare module '@glint/environment-ember-loose/registry' { + export default interface Registry { + 'Storeknox::Discover::TableColumns::Application': typeof StoreknoxDiscoverTableColumnsApplicationComponent; + } +} diff --git a/app/components/storeknox/table-columns/developer/index.ts b/app/components/storeknox/table-columns/developer/index.ts new file mode 100644 index 000000000..0ce7397cc --- /dev/null +++ b/app/components/storeknox/table-columns/developer/index.ts @@ -0,0 +1,18 @@ +import Component from '@glimmer/component'; +import { StoreknoxCommonTableColumnsData } from '..'; + +export interface StoreknoxDiscoverTableColumnsDeveloperSignature { + Element: HTMLElement; + Args: { + data: StoreknoxCommonTableColumnsData; + loading?: boolean; + }; +} + +export default class StoreknoxDiscoverTableColumnsDeveloperComponent extends Component {} + +declare module '@glint/environment-ember-loose/registry' { + export default interface Registry { + 'Storeknox::Discover::TableColumns::Developer': typeof StoreknoxDiscoverTableColumnsDeveloperComponent; + } +} diff --git a/app/components/storeknox/table-columns/index.ts b/app/components/storeknox/table-columns/index.ts new file mode 100644 index 000000000..a09336d5d --- /dev/null +++ b/app/components/storeknox/table-columns/index.ts @@ -0,0 +1,26 @@ +import Component from '@glimmer/component'; + +export interface StoreknoxCommonTableColumnsData { + devName?: string; + devEmail?: string; + appUrl: string; + isAndroid?: boolean; + isIos?: boolean; + iconUrl: string; + name: string; + packageName: string; + companyName: string; + mailId?: string; + title: string; + reason: string; + svg: string; + docUlid: string; +} + +export default class StoreknoxDiscoverTableColumnsComponent extends Component {} + +declare module '@glint/environment-ember-loose/registry' { + export default interface Registry { + 'Storeknox::Discover::TableColumns': typeof StoreknoxDiscoverTableColumnsComponent; + } +} diff --git a/app/components/storeknox/table-columns/store/index.ts b/app/components/storeknox/table-columns/store/index.ts new file mode 100644 index 000000000..cdae364d6 --- /dev/null +++ b/app/components/storeknox/table-columns/store/index.ts @@ -0,0 +1,18 @@ +import Component from '@glimmer/component'; +import { StoreknoxCommonTableColumnsData } from '..'; + +export interface StoreknoxDiscoverTableColumnsStoreSignature { + Element: HTMLElement; + Args: { + data: StoreknoxCommonTableColumnsData; + loading?: boolean; + }; +} + +export default class StoreknoxDiscoverTableColumnsStoreComponent extends Component {} + +declare module '@glint/environment-ember-loose/registry' { + export default interface Registry { + 'Storeknox::Discover::TableColumns::Store': typeof StoreknoxDiscoverTableColumnsStoreComponent; + } +} diff --git a/app/controllers/authenticated/storeknox/discover/review.ts b/app/controllers/authenticated/storeknox/discover/review.ts deleted file mode 100644 index 8ff693f29..000000000 --- a/app/controllers/authenticated/storeknox/discover/review.ts +++ /dev/null @@ -1,10 +0,0 @@ -import Controller from '@ember/controller'; - -export default class AuthenticatedStoreknoxDiscoverReviewController extends Controller { - queryParams = ['app_limit', 'app_offset', 'app_search_id', 'app_query']; - - app_limit = 10; - app_offset = 0; - app_search_id = null; - app_query = ''; -} diff --git a/app/controllers/authenticated/storeknox/inventory-details.ts b/app/controllers/authenticated/storeknox/inventory-details.ts new file mode 100644 index 000000000..231e1bfc6 --- /dev/null +++ b/app/controllers/authenticated/storeknox/inventory-details.ts @@ -0,0 +1,26 @@ +import Controller from '@ember/controller'; +import { service } from '@ember/service'; +import type IntlService from 'ember-intl/services/intl'; + +import type SkInventoryAppModel from 'irene/models/sk-inventory-app'; +import type { AkBreadcrumbsItemProps } from 'irene/services/ak-breadcrumbs'; + +export default class AuthenticatedStoreknoxInventoryDetailsController extends Controller { + @service declare intl: IntlService; + + declare model: SkInventoryAppModel; + + get breadcrumbs(): AkBreadcrumbsItemProps { + return { + title: `Inventory Details (${this.model?.appMetadata?.packageName})`, + route: 'authenticated.storeknox.inventory-details', + routeGroup: 'storeknox/inventory', + + parentCrumb: { + title: this.intl.t('storeknox.appInventory'), + routeGroup: 'storeknox/inventory', + route: 'authenticated.storeknox.inventory.app-list', + }, + }; + } +} diff --git a/app/controllers/authenticated/storeknox/inventory-details/brand-abuse.ts b/app/controllers/authenticated/storeknox/inventory-details/brand-abuse.ts new file mode 100644 index 000000000..f5174ca99 --- /dev/null +++ b/app/controllers/authenticated/storeknox/inventory-details/brand-abuse.ts @@ -0,0 +1,43 @@ +import Controller from '@ember/controller'; +import { service } from '@ember/service'; +import type IntlService from 'ember-intl/services/intl'; + +import type SkInventoryAppModel from 'irene/models/sk-inventory-app'; +import type { AkBreadcrumbsItemProps } from 'irene/services/ak-breadcrumbs'; + +export default class AuthenticatedStoreknoxInventoryDetailsBrandAbuseController extends Controller { + @service declare intl: IntlService; + + declare model: SkInventoryAppModel; + + get breadcrumbs(): AkBreadcrumbsItemProps { + const crumb: AkBreadcrumbsItemProps = { + title: this.intl.t('storeknox.brandAbuse'), + route: 'authenticated.storeknox.inventory-details.brand-abuse', + models: [this.model.id], + routeGroup: 'storeknox/inventory', + }; + + const parentCrumb: AkBreadcrumbsItemProps['parentCrumb'] = { + title: `Inventory Details (${this.model?.appMetadata?.packageName})`, + route: 'authenticated.storeknox.inventory-details', + models: [this.model.id], + routeGroup: 'storeknox/inventory', + }; + + return { + ...crumb, + parentCrumb, + + fallbackCrumbs: [ + { + title: this.intl.t('storeknox.appInventory'), + routeGroup: 'storeknox/inventory', + route: 'authenticated.storeknox.inventory.app-list', + }, + parentCrumb, + crumb, + ], + }; + } +} diff --git a/app/controllers/authenticated/storeknox/inventory-details/index.ts b/app/controllers/authenticated/storeknox/inventory-details/index.ts new file mode 100644 index 000000000..3aff242bb --- /dev/null +++ b/app/controllers/authenticated/storeknox/inventory-details/index.ts @@ -0,0 +1,27 @@ +import Controller from '@ember/controller'; +import { service } from '@ember/service'; +import type IntlService from 'ember-intl/services/intl'; + +import type SkInventoryAppModel from 'irene/models/sk-inventory-app'; +import type { AkBreadcrumbsItemProps } from 'irene/services/ak-breadcrumbs'; + +export default class AuthenticatedStoreknoxInventoryDetailsController extends Controller { + @service declare intl: IntlService; + + declare model: SkInventoryAppModel; + + get breadcrumbs(): AkBreadcrumbsItemProps { + return { + title: `Inventory Details (${this.model?.appMetadata?.packageName})`, + route: 'authenticated.storeknox.inventory-details.index', + models: [this.model.id], + routeGroup: 'storeknox/inventory', + + parentCrumb: { + title: this.intl.t('storeknox.appInventory'), + routeGroup: 'storeknox/inventory', + route: 'authenticated.storeknox.inventory.app-list', + }, + }; + } +} diff --git a/app/controllers/authenticated/storeknox/inventory-details/malware-detected.ts b/app/controllers/authenticated/storeknox/inventory-details/malware-detected.ts new file mode 100644 index 000000000..942175ae0 --- /dev/null +++ b/app/controllers/authenticated/storeknox/inventory-details/malware-detected.ts @@ -0,0 +1,43 @@ +import Controller from '@ember/controller'; +import { service } from '@ember/service'; +import type IntlService from 'ember-intl/services/intl'; + +import type SkInventoryAppModel from 'irene/models/sk-inventory-app'; +import type { AkBreadcrumbsItemProps } from 'irene/services/ak-breadcrumbs'; + +export default class AuthenticatedStoreknoxInventoryDetailsMalwareDetectedController extends Controller { + @service declare intl: IntlService; + + declare model: SkInventoryAppModel; + + get breadcrumbs(): AkBreadcrumbsItemProps { + const crumb: AkBreadcrumbsItemProps = { + title: this.intl.t('storeknox.malwareDetected'), + route: 'authenticated.storeknox.inventory-details.malware-detected', + models: [this.model.id], + routeGroup: 'storeknox/inventory', + }; + + const parentCrumb: AkBreadcrumbsItemProps['parentCrumb'] = { + title: `Inventory Details (${this.model?.appMetadata?.packageName})`, + route: 'authenticated.storeknox.inventory-details', + models: [this.model.id], + routeGroup: 'storeknox/inventory', + }; + + return { + ...crumb, + parentCrumb, + + fallbackCrumbs: [ + { + title: this.intl.t('storeknox.appInventory'), + routeGroup: 'storeknox/inventory', + route: 'authenticated.storeknox.inventory.app-list', + }, + parentCrumb, + crumb, + ], + }; + } +} diff --git a/app/controllers/authenticated/storeknox/inventory-details/unscanned-version.ts b/app/controllers/authenticated/storeknox/inventory-details/unscanned-version.ts new file mode 100644 index 000000000..06570769e --- /dev/null +++ b/app/controllers/authenticated/storeknox/inventory-details/unscanned-version.ts @@ -0,0 +1,43 @@ +import Controller from '@ember/controller'; +import { service } from '@ember/service'; +import type IntlService from 'ember-intl/services/intl'; + +import type SkInventoryAppModel from 'irene/models/sk-inventory-app'; +import type { AkBreadcrumbsItemProps } from 'irene/services/ak-breadcrumbs'; + +export default class AuthenticatedStoreknoxInventoryDetailsController extends Controller { + @service declare intl: IntlService; + + declare model: SkInventoryAppModel; + + get breadcrumbs(): AkBreadcrumbsItemProps { + const crumb: AkBreadcrumbsItemProps = { + title: this.intl.t('storeknox.unscannedVersion'), + route: 'authenticated.storeknox.inventory-details.unscanned-version', + models: [this.model.id], + routeGroup: 'storeknox/inventory', + }; + + const parentCrumb: AkBreadcrumbsItemProps['parentCrumb'] = { + title: `Inventory Details (${this.model?.appMetadata?.packageName})`, + route: 'authenticated.storeknox.inventory-details.index', + models: [this.model.id], + routeGroup: 'storeknox/inventory', + }; + + return { + ...crumb, + parentCrumb, + + fallbackCrumbs: [ + { + title: this.intl.t('storeknox.appInventory'), + routeGroup: 'storeknox/inventory', + route: 'authenticated.storeknox.inventory.app-list', + }, + parentCrumb, + crumb, + ], + }; + } +} diff --git a/app/controllers/authenticated/storeknox/inventory/app-list.ts b/app/controllers/authenticated/storeknox/inventory/app-list.ts new file mode 100644 index 000000000..1a3b4ca18 --- /dev/null +++ b/app/controllers/authenticated/storeknox/inventory/app-list.ts @@ -0,0 +1,18 @@ +import Controller from '@ember/controller'; +import { service } from '@ember/service'; +import type IntlService from 'ember-intl/services/intl'; +import type { AkBreadcrumbsItemProps } from 'irene/services/ak-breadcrumbs'; + +export default class AuthenticatedStoreknoxAppListController extends Controller { + @service declare intl: IntlService; + + get breadcrumbs(): AkBreadcrumbsItemProps { + return { + title: this.intl.t('storeknox.appInventory'), + route: 'authenticated.storeknox.inventory.app-list', + routeGroup: 'storeknox/inventory', + stopCrumbGeneration: true, + isRootCrumb: true, + }; + } +} diff --git a/app/controllers/authenticated/storeknox/inventory/pending-reviews.ts b/app/controllers/authenticated/storeknox/inventory/pending-reviews.ts new file mode 100644 index 000000000..49b6b7936 --- /dev/null +++ b/app/controllers/authenticated/storeknox/inventory/pending-reviews.ts @@ -0,0 +1,28 @@ +import Controller from '@ember/controller'; +import { service } from '@ember/service'; +import type IntlService from 'ember-intl/services/intl'; +import type { AkBreadcrumbsItemProps } from 'irene/services/ak-breadcrumbs'; + +export default class AuthenticatedStoreknoxInventoryPendingReviewsController extends Controller { + @service declare intl: IntlService; + queryParams = ['app_limit', 'app_offset', 'app_search_id', 'app_query']; + + app_limit = 10; + app_offset = 0; + app_search_id = null; + app_query = ''; + + get breadcrumbs(): AkBreadcrumbsItemProps { + return { + title: this.intl.t('testCase'), + route: 'authenticated.storeknox.inventory.pending-reviews', + routeGroup: 'storeknox/inventory', + + parentCrumb: { + title: this.intl.t('storeknox.appInventory'), + routeGroup: 'storeknox/inventory', + route: 'authenticated.storeknox.inventory.app-list', + }, + }; + } +} diff --git a/app/enums.ts b/app/enums.ts index ed5fac080..a2827529a 100644 --- a/app/enums.ts +++ b/app/enums.ts @@ -260,6 +260,13 @@ const ENUMS = { ACTIVE: 1, ARCHIVED: 2, }, + + SK_APP_MONITORING_STATUS: { + PENDING: 0, + SCANNED: 1, + UNSCANNED: 2, + NOT_FOUND: 3, + }, }; export const ENUMS_DISPLAY = { diff --git a/app/models/sk-add-to-inventory.ts b/app/models/sk-add-to-inventory.ts new file mode 100644 index 000000000..13585dfa3 --- /dev/null +++ b/app/models/sk-add-to-inventory.ts @@ -0,0 +1,18 @@ +import Model, { attr } from '@ember-data/model'; + +export default class SkAddToInventoryModel extends Model { + @attr('string') + declare docUlid: string; + + @attr('number') + declare appDiscoveryQuery: number; + + @attr('number') + declare approvalStatus: number; +} + +declare module 'ember-data/types/registries/model' { + export default interface ModelRegistry { + 'sk-add-to-inventory': SkAddToInventoryModel; + } +} diff --git a/app/models/sk-app-version.ts b/app/models/sk-app-version.ts new file mode 100644 index 000000000..3937851de --- /dev/null +++ b/app/models/sk-app-version.ts @@ -0,0 +1,50 @@ +import Model, { + AsyncBelongsTo, + attr, + belongsTo, + hasMany, + SyncHasMany, +} from '@ember-data/model'; + +import type SkInventoryAppModel from './sk-inventory-app'; +import type FileModel from './file'; +import type SkStoreInstanceModel from './sk-store-instance'; +import type SubmissionModel from './submission'; + +export default class SkAppVersionModel extends Model { + @attr('string') + declare version: string; + + @attr('string') + declare versionCode: string; + + @attr('boolean') + declare isLatest: boolean; + + @attr('number') + declare appScanStatus: number; + + @attr('string') + declare appScanStatusDisplay: string; + + @attr('date') + declare createdOn: Date; + + @belongsTo('submission', { async: true, inverse: null }) + declare uploadSubmission: AsyncBelongsTo | null; + + @belongsTo('sk-inventory-app', { async: true, inverse: null }) + declare skApp: AsyncBelongsTo; + + @belongsTo('file', { async: true, inverse: null }) + declare file: AsyncBelongsTo; + + @hasMany('sk-store-instance', { async: false, inverse: null }) + declare skStoreInstances: SyncHasMany; +} + +declare module 'ember-data/types/registries/model' { + export default interface ModelRegistry { + 'sk-app-version': SkAppVersionModel; + } +} diff --git a/app/models/sk-app.ts b/app/models/sk-app.ts index 3e63ba02b..5888e32fa 100644 --- a/app/models/sk-app.ts +++ b/app/models/sk-app.ts @@ -39,7 +39,7 @@ export default class SkAppModel extends Model { declare monitoringEnabled: boolean; @attr('number') - declare monitoringStatus: number; + declare storeMonitoringStatus: number; @attr('number') declare skOrganization: number; @@ -56,9 +56,6 @@ export default class SkAppModel extends Model { @attr('date') declare rejectedOn: Date; - @attr('number', { allowNull: true }) - declare coreProject: number | null; - @belongsTo('sk-app-metadata', { async: false, inverse: null }) declare appMetadata: SkAppMetadataModel; @@ -110,6 +107,12 @@ export default class SkAppModel extends Model { return this.appMetadata.get('platform') === ENUMS.PLATFORM.IOS; } + get monitoringStatusIsPending() { + return ( + this.storeMonitoringStatus === ENUMS.SK_APP_MONITORING_STATUS.PENDING + ); + } + async approveApp(id: string) { const adapter = this.store.adapterFor('sk-app'); @@ -121,6 +124,12 @@ export default class SkAppModel extends Model { return await adapter.rejectApp(id); } + + async toggleMonitoring(checked: boolean) { + const adapter = this.store.adapterFor('sk-app'); + + return await adapter.toggleMonitoring(this.id, checked); + } } declare module 'ember-data/types/registries/model' { diff --git a/app/models/sk-inventory-app.ts b/app/models/sk-inventory-app.ts new file mode 100644 index 000000000..14dc76c4c --- /dev/null +++ b/app/models/sk-inventory-app.ts @@ -0,0 +1,22 @@ +import { belongsTo } from '@ember-data/model'; +import SkAppModel from './sk-app'; +import type ProjectModel from './project'; +import type FileModel from './file'; + +export default class SkInventoryAppModel extends SkAppModel { + @belongsTo('project', { async: true, inverse: null }) + declare coreProject: ProjectModel | null; + + @belongsTo('file', { async: true, inverse: null }) + declare coreProjectLatestVersion: FileModel | null; + + get appIsNotAvailableOnAppknox() { + return !this.coreProject?.id; + } +} + +declare module 'ember-data/types/registries/model' { + export default interface ModelRegistry { + 'sk-inventory-app': SkInventoryAppModel; + } +} diff --git a/app/models/sk-inventory-approval-status.ts b/app/models/sk-inventory-approval-status.ts new file mode 100644 index 000000000..8d3e907ec --- /dev/null +++ b/app/models/sk-inventory-approval-status.ts @@ -0,0 +1,15 @@ +import Model, { attr } from '@ember-data/model'; + +export default class SkInventoryApprovalStatusModel extends Model { + @attr('number') + declare approvalStatus: number; + + @attr('string') + declare approvalStatusDisplay: string; +} + +declare module 'ember-data/types/registries/model' { + export default interface ModelRegistry { + 'sk-inventory-approval-status': SkInventoryApprovalStatusModel; + } +} diff --git a/app/models/sk-organization-membership.ts b/app/models/sk-organization-membership.ts new file mode 100644 index 000000000..932ca1fe9 --- /dev/null +++ b/app/models/sk-organization-membership.ts @@ -0,0 +1,27 @@ +import Model, { AsyncBelongsTo, attr, belongsTo } from '@ember-data/model'; + +import type UserModel from './user'; +import type SkOrganizationModel from './sk-organization'; + +export default class SkOrganizationMembershipModel extends Model { + @attr('date') + declare createdOn: Date; + + @attr('date') + declare updatedOn: Date; + + @attr('number') + declare role: number; + + @belongsTo('user', { async: false, inverse: null }) + declare member: UserModel; + + @belongsTo('sk-organization', { async: true, inverse: 'members' }) + declare skOrganization: AsyncBelongsTo; +} + +declare module 'ember-data/types/registries/model' { + export default interface ModelRegistry { + 'sk-organization-membership': SkOrganizationMembershipModel; + } +} diff --git a/app/models/sk-organization.ts b/app/models/sk-organization.ts new file mode 100644 index 000000000..80d3500df --- /dev/null +++ b/app/models/sk-organization.ts @@ -0,0 +1,52 @@ +import Model, { + AsyncHasMany, + attr, + belongsTo, + hasMany, +} from '@ember-data/model'; + +import type SkOrganizationMembershipModel from './sk-organization-membership'; +import type OrganizationModel from './organization'; + +export type SkOrganizationModelName = 'sk-organization'; + +export default class SkOrganizationModel extends Model { + private modelName = SkOrganizationModel.modelName as SkOrganizationModelName; + + @attr('string') + declare createdOn: string; + + @attr('string') + declare updatedOn: string; + + @attr('boolean') + declare addAppknoxProjectToInventoryByDefault: boolean; + + @attr('boolean') + declare autodiscoveryOnboardingDone: boolean; + + @belongsTo('organization', { async: false, inverse: null }) + declare organization: OrganizationModel; + + @hasMany('sk-organization-membership', { + async: true, + inverse: 'skOrganization', + }) + declare members: AsyncHasMany; + + async toggleAddToInventoryByDefault(addToInventoryByDefault: boolean) { + const adapter = this.store.adapterFor('sk-organization'); + + return await adapter.toggleAddToInventoryByDefault( + this.modelName, + this.id, + addToInventoryByDefault + ); + } +} + +declare module 'ember-data/types/registries/model' { + export default interface ModelRegistry { + 'sk-organization': SkOrganizationModel; + } +} diff --git a/app/models/sk-store-instance.ts b/app/models/sk-store-instance.ts new file mode 100644 index 000000000..add603a4a --- /dev/null +++ b/app/models/sk-store-instance.ts @@ -0,0 +1,19 @@ +import Model, { AsyncBelongsTo, attr, belongsTo } from '@ember-data/model'; +import type SkStoreModel from './sk-store'; + +export default class SkStoreInstanceModel extends Model { + @attr('string') + declare icon: string; + + @attr('string') + declare countryCode: string; + + @belongsTo('sk-store', { async: true, inverse: null }) + declare skStore: AsyncBelongsTo; +} + +declare module 'ember-data/types/registries/model' { + export default interface ModelRegistry { + 'sk-store-instance': SkStoreInstanceModel; + } +} diff --git a/app/models/sk-store.ts b/app/models/sk-store.ts new file mode 100644 index 000000000..ab979b4d7 --- /dev/null +++ b/app/models/sk-store.ts @@ -0,0 +1,21 @@ +import Model, { attr } from '@ember-data/model'; + +export default class SkStoreModel extends Model { + @attr('string') + declare icon: string; + + @attr('string') + declare name: string; + + @attr('string') + declare identifier: string; + + @attr('string') + declare platform: number; +} + +declare module 'ember-data/types/registries/model' { + export default interface ModelRegistry { + 'sk-store': SkStoreModel; + } +} diff --git a/app/router.ts b/app/router.ts index edf8dc126..9ba8d3a41 100644 --- a/app/router.ts +++ b/app/router.ts @@ -307,13 +307,29 @@ Router.map(function () { this.route('discover', function () { this.route('result'); this.route('requested'); - this.route('review'); }); this.route('inventory', function () { this.route('app-list'); this.route('disabled-apps'); + this.route('pending-reviews'); }); + + this.route( + 'inventory-details', + { path: '/inventory-details/:id' }, + function () { + this.route('brand-abuse'); + this.route('malware-detected'); + + this.route('unscanned-version', function () { + this.route('history'); + }); + } + ); + + this.route('review-logs', { path: '/discover/review-logs' }); + this.route('archived-apps', { path: '/inventory/archived-apps' }); }); } ); diff --git a/app/routes/authenticated/storeknox/discover/review.ts b/app/routes/authenticated/storeknox/discover/review.ts deleted file mode 100644 index ea85b97b3..000000000 --- a/app/routes/authenticated/storeknox/discover/review.ts +++ /dev/null @@ -1,25 +0,0 @@ -import Route from '@ember/routing/route'; - -export interface StoreknoxDiscoveryReviewQueryParam { - app_limit: number; - app_offset: number; -} - -export default class AuthenticatedStoreknoxDiscoveryReviewRoute extends Route { - queryParams = { - app_limit: { - refreshModel: true, - }, - app_offset: { - refreshModel: true, - }, - }; - - model(params: Partial) { - const { app_limit, app_offset } = params; - - return { - queryParams: { app_limit, app_offset }, - }; - } -} diff --git a/app/routes/authenticated/storeknox/inventory-details.ts b/app/routes/authenticated/storeknox/inventory-details.ts new file mode 100644 index 000000000..b9da1cbf0 --- /dev/null +++ b/app/routes/authenticated/storeknox/inventory-details.ts @@ -0,0 +1,29 @@ +import { inject as service } from '@ember/service'; +import Store from '@ember-data/store'; +import RouterService from '@ember/routing/router-service'; + +import AkBreadcrumbsRoute from 'irene/utils/ak-breadcrumbs-route'; +import { ScrollToTop } from 'irene/utils/scroll-to-top'; +import type SkInventoryAppModel from 'irene/models/sk-inventory-app'; + +export interface InventoryDetailsIndexQueryParams { + id: string | number; +} + +export default class AuthenticatedStoreknoxInventoryDetailsIndexRoute extends ScrollToTop( + AkBreadcrumbsRoute +) { + @service declare store: Store; + @service declare router: RouterService; + + async model( + params: InventoryDetailsIndexQueryParams + ): Promise { + const skInventoryApp = await this.store.findRecord( + 'sk-inventory-app', + params.id + ); + + return skInventoryApp; + } +} diff --git a/app/routes/authenticated/storeknox/inventory-details/brand-abuse.ts b/app/routes/authenticated/storeknox/inventory-details/brand-abuse.ts new file mode 100644 index 000000000..00f86eda6 --- /dev/null +++ b/app/routes/authenticated/storeknox/inventory-details/brand-abuse.ts @@ -0,0 +1,10 @@ +import AkBreadcrumbsRoute from 'irene/utils/ak-breadcrumbs-route'; +import type SkInventoryAppModel from 'irene/models/sk-inventory-app'; + +export default class AuthenticatedStoreknoxInventoryDetailsBrandAbuseRoute extends AkBreadcrumbsRoute { + model() { + return this.modelFor( + 'authenticated.storeknox.inventory-details' + ) as SkInventoryAppModel; + } +} diff --git a/app/routes/authenticated/storeknox/inventory-details/index.ts b/app/routes/authenticated/storeknox/inventory-details/index.ts new file mode 100644 index 000000000..143cc1fa7 --- /dev/null +++ b/app/routes/authenticated/storeknox/inventory-details/index.ts @@ -0,0 +1,21 @@ +import { inject as service } from '@ember/service'; +import Store from '@ember-data/store'; +import type RouterService from '@ember/routing/router-service'; + +import AkBreadcrumbsRoute from 'irene/utils/ak-breadcrumbs-route'; +import type SkInventoryAppModel from 'irene/models/sk-inventory-app'; + +export interface InventoryDetailsIndexQueryParams { + id: string | number; +} + +export default class AuthenticatedStoreknoxInventoryDetailsIndexRoute extends AkBreadcrumbsRoute { + @service declare store: Store; + @service declare router: RouterService; + + async model(): Promise { + return this.modelFor( + 'authenticated.storeknox.inventory-details' + ) as SkInventoryAppModel; + } +} diff --git a/app/routes/authenticated/storeknox/inventory-details/malware-detected.ts b/app/routes/authenticated/storeknox/inventory-details/malware-detected.ts new file mode 100644 index 000000000..590b0d558 --- /dev/null +++ b/app/routes/authenticated/storeknox/inventory-details/malware-detected.ts @@ -0,0 +1,10 @@ +import AkBreadcrumbsRoute from 'irene/utils/ak-breadcrumbs-route'; +import type SkInventoryAppModel from 'irene/models/sk-inventory-app'; + +export default class AuthenticatedStoreknoxInventoryDetailsMalwareDetectedRoute extends AkBreadcrumbsRoute { + model() { + return this.modelFor( + 'authenticated.storeknox.inventory-details' + ) as SkInventoryAppModel; + } +} diff --git a/app/routes/authenticated/storeknox/inventory-details/unscanned-version.ts b/app/routes/authenticated/storeknox/inventory-details/unscanned-version.ts new file mode 100644 index 000000000..0f66a1ff4 --- /dev/null +++ b/app/routes/authenticated/storeknox/inventory-details/unscanned-version.ts @@ -0,0 +1,10 @@ +import AkBreadcrumbsRoute from 'irene/utils/ak-breadcrumbs-route'; +import type SkInventoryAppModel from 'irene/models/sk-inventory-app'; + +export default class AuthenticatedStoreknoxInventoryDetailsUnscannedVersionRoute extends AkBreadcrumbsRoute { + async model() { + return this.modelFor( + 'authenticated.storeknox.inventory-details' + ) as SkInventoryAppModel; + } +} diff --git a/app/routes/authenticated/storeknox/inventory-details/unscanned-version/history.ts b/app/routes/authenticated/storeknox/inventory-details/unscanned-version/history.ts new file mode 100644 index 000000000..67657a718 --- /dev/null +++ b/app/routes/authenticated/storeknox/inventory-details/unscanned-version/history.ts @@ -0,0 +1,10 @@ +import Route from '@ember/routing/route'; +import type SkInventoryAppModel from 'irene/models/sk-inventory-app'; + +export default class AuthenticatedStoreknoxInventoryDetailsUnscannedVersionsHistoryRoute extends Route { + parentRoute = 'authenticated.storeknox.inventory-details.unscanned-version'; + + model() { + return this.modelFor(this.parentRoute) as SkInventoryAppModel; + } +} diff --git a/app/routes/authenticated/storeknox/inventory-details/unscanned-version/index.ts b/app/routes/authenticated/storeknox/inventory-details/unscanned-version/index.ts new file mode 100644 index 000000000..592ce6feb --- /dev/null +++ b/app/routes/authenticated/storeknox/inventory-details/unscanned-version/index.ts @@ -0,0 +1,10 @@ +import Route from '@ember/routing/route'; +import type SkInventoryAppModel from 'irene/models/sk-inventory-app'; + +export default class AuthenticatedStoreknoxInventoryDetailsUnscannedVersionIndexRoute extends Route { + parentRoute = 'authenticated.storeknox.inventory-details.unscanned-version'; + + model() { + return this.modelFor(this.parentRoute) as SkInventoryAppModel; + } +} diff --git a/app/routes/authenticated/storeknox/inventory/app-list.ts b/app/routes/authenticated/storeknox/inventory/app-list.ts new file mode 100644 index 000000000..b8d7b7791 --- /dev/null +++ b/app/routes/authenticated/storeknox/inventory/app-list.ts @@ -0,0 +1,3 @@ +import AkBreadcrumbsRoute from 'irene/utils/ak-breadcrumbs-route'; + +export default class AuthenticatedStoreknoxInventoryAppListRoute extends AkBreadcrumbsRoute {} diff --git a/app/routes/authenticated/storeknox/inventory/pending-reviews.ts b/app/routes/authenticated/storeknox/inventory/pending-reviews.ts new file mode 100644 index 000000000..0408e67df --- /dev/null +++ b/app/routes/authenticated/storeknox/inventory/pending-reviews.ts @@ -0,0 +1,25 @@ +import AkBreadcrumbsRoute from 'irene/utils/ak-breadcrumbs-route'; + +export interface StoreknoxInventoryPendingReviewsQueryParam { + app_limit: number; + app_offset: number; +} + +export default class AuthenticatedStoreknoxInventoryPendingReviewsRoute extends AkBreadcrumbsRoute { + queryParams = { + app_limit: { + refreshModel: true, + }, + app_offset: { + refreshModel: true, + }, + }; + + model(params: Partial) { + const { app_limit, app_offset } = params; + + return { + queryParams: { app_limit, app_offset }, + }; + } +} diff --git a/app/serializers/sk-app-version.js b/app/serializers/sk-app-version.js new file mode 100644 index 000000000..5087b1950 --- /dev/null +++ b/app/serializers/sk-app-version.js @@ -0,0 +1,8 @@ +import DRFSerializer from './drf'; +import { EmbeddedRecordsMixin } from '@ember-data/serializer/rest'; + +export default DRFSerializer.extend(EmbeddedRecordsMixin, { + attrs: { + skStoreInstances: { embedded: 'always' }, + }, +}); diff --git a/app/serializers/sk-inventory-app.js b/app/serializers/sk-inventory-app.js new file mode 100644 index 000000000..895f024c3 --- /dev/null +++ b/app/serializers/sk-inventory-app.js @@ -0,0 +1,9 @@ +import DRFSerializer from './drf'; +import { EmbeddedRecordsMixin } from '@ember-data/serializer/rest'; + +export default DRFSerializer.extend(EmbeddedRecordsMixin, { + attrs: { + appMetadata: { embedded: 'always' }, + coreProject: { embedded: 'always' }, + }, +}); diff --git a/app/services/ak-breadcrumbs.ts b/app/services/ak-breadcrumbs.ts index 0b9ef2b43..82219611a 100644 --- a/app/services/ak-breadcrumbs.ts +++ b/app/services/ak-breadcrumbs.ts @@ -25,7 +25,9 @@ export interface AkBreadcrumbsItemProps { | 'app-monitoring' | 'sec-dashboard' | 'sbom' - | 'organization'; + | 'organization' + | 'storeknox/inventory' + | 'storeknox/discovery'; // Fallback crumbs when no context as to what route a user is coming from. Can also be useful when switching from one route group to another. fallbackCrumbs?: Array< diff --git a/app/styles/_icons.scss b/app/styles/_icons.scss index d06346697..9ec8cfc30 100644 --- a/app/styles/_icons.scss +++ b/app/styles/_icons.scss @@ -598,3 +598,19 @@ .ak-icon-add-box { @extend .mi-add-box; } + +.ak-icon-archive { + @extend .mi-archive; +} + +.ak-icon-report { + @extend .mi-report; +} + +.ak-icon-arrow-outward { + @extend .mi-arrow-outward; +} + +.ak-icon-auto-fix-high { + @extend .mi-auto-fix-high; +} diff --git a/app/styles/_theme.scss b/app/styles/_theme.scss index 4e0734cef..465b7a97f 100644 --- a/app/styles/_theme.scss +++ b/app/styles/_theme.scss @@ -120,6 +120,10 @@ // special case variable for project-settings/general-settings/dynamicscan-automation-settings/upselling-feature --upselling-feature-footer-bg-color: #f7f3ff; --upselling-feature-footer-color: #3e0e8c; + + // special case variable storeknox feature in progress for brand abuse and malware detected + --storeknox-feature-in-progress-button-color: #3e0e8c; + --storeknox-feature-in-progress-button-bg-color: #f7f3ff; } // TODO: need to remove this so do not use it diff --git a/app/templates/authenticated/storeknox/inventory-details-loading.hbs b/app/templates/authenticated/storeknox/inventory-details-loading.hbs new file mode 100644 index 000000000..cebe1b487 --- /dev/null +++ b/app/templates/authenticated/storeknox/inventory-details-loading.hbs @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {{#each (array 1 2 3) as |idx|}} + {{#if (gt idx 1)}} + + {{/if}} + + + + + + + {{/each}} + + + \ No newline at end of file diff --git a/app/templates/authenticated/storeknox/inventory-details.hbs b/app/templates/authenticated/storeknox/inventory-details.hbs new file mode 100644 index 000000000..2367064b7 --- /dev/null +++ b/app/templates/authenticated/storeknox/inventory-details.hbs @@ -0,0 +1,7 @@ +{{page-title 'Inventory Details'}} + + + + + {{outlet}} + \ No newline at end of file diff --git a/app/templates/authenticated/storeknox/inventory-details/brand-abuse.hbs b/app/templates/authenticated/storeknox/inventory-details/brand-abuse.hbs new file mode 100644 index 000000000..2b9291d03 --- /dev/null +++ b/app/templates/authenticated/storeknox/inventory-details/brand-abuse.hbs @@ -0,0 +1,3 @@ +{{page-title (t 'storeknox.brandAbuse')}} + + \ No newline at end of file diff --git a/app/templates/authenticated/storeknox/inventory-details/index.hbs b/app/templates/authenticated/storeknox/inventory-details/index.hbs new file mode 100644 index 000000000..7ab717457 --- /dev/null +++ b/app/templates/authenticated/storeknox/inventory-details/index.hbs @@ -0,0 +1,3 @@ +{{page-title 'Inventory Details'}} + + \ No newline at end of file diff --git a/app/templates/authenticated/storeknox/inventory-details/malware-detected.hbs b/app/templates/authenticated/storeknox/inventory-details/malware-detected.hbs new file mode 100644 index 000000000..8b81441d9 --- /dev/null +++ b/app/templates/authenticated/storeknox/inventory-details/malware-detected.hbs @@ -0,0 +1,3 @@ +{{page-title (t 'storeknox.malwareDetected')}} + + \ No newline at end of file diff --git a/app/templates/authenticated/storeknox/inventory-details/unscanned-version.hbs b/app/templates/authenticated/storeknox/inventory-details/unscanned-version.hbs new file mode 100644 index 000000000..9018b1a38 --- /dev/null +++ b/app/templates/authenticated/storeknox/inventory-details/unscanned-version.hbs @@ -0,0 +1,7 @@ +{{page-title (t 'storeknox.unscannedVersion')}} + + + +{{outlet}} \ No newline at end of file diff --git a/app/templates/authenticated/storeknox/inventory-details/unscanned-version/history.hbs b/app/templates/authenticated/storeknox/inventory-details/unscanned-version/history.hbs new file mode 100644 index 000000000..5fb8e569e --- /dev/null +++ b/app/templates/authenticated/storeknox/inventory-details/unscanned-version/history.hbs @@ -0,0 +1,4 @@ + \ No newline at end of file diff --git a/app/templates/authenticated/storeknox/inventory-details/unscanned-version/index.hbs b/app/templates/authenticated/storeknox/inventory-details/unscanned-version/index.hbs new file mode 100644 index 000000000..f40f8cee5 --- /dev/null +++ b/app/templates/authenticated/storeknox/inventory-details/unscanned-version/index.hbs @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/app/templates/authenticated/storeknox/inventory.hbs b/app/templates/authenticated/storeknox/inventory.hbs index 8b1378917..294b929b7 100644 --- a/app/templates/authenticated/storeknox/inventory.hbs +++ b/app/templates/authenticated/storeknox/inventory.hbs @@ -1 +1,7 @@ +{{page-title 'Discover'}} + + + + {{outlet}} + \ No newline at end of file diff --git a/app/templates/authenticated/storeknox/inventory/app-list.hbs b/app/templates/authenticated/storeknox/inventory/app-list.hbs new file mode 100644 index 000000000..545f2c70e --- /dev/null +++ b/app/templates/authenticated/storeknox/inventory/app-list.hbs @@ -0,0 +1,3 @@ +{{page-title 'Requested'}} + + \ No newline at end of file diff --git a/app/templates/authenticated/storeknox/inventory/disabled-apps.hbs b/app/templates/authenticated/storeknox/inventory/disabled-apps.hbs new file mode 100644 index 000000000..b5b23a921 --- /dev/null +++ b/app/templates/authenticated/storeknox/inventory/disabled-apps.hbs @@ -0,0 +1,3 @@ +{{page-title 'Requested'}} + + \ No newline at end of file diff --git a/app/templates/authenticated/storeknox/discover/review.hbs b/app/templates/authenticated/storeknox/inventory/pending-reviews.hbs similarity index 100% rename from app/templates/authenticated/storeknox/discover/review.hbs rename to app/templates/authenticated/storeknox/inventory/pending-reviews.hbs diff --git a/mirage/models/sk-inventory-app.ts b/mirage/models/sk-inventory-app.ts new file mode 100644 index 000000000..db502f142 --- /dev/null +++ b/mirage/models/sk-inventory-app.ts @@ -0,0 +1,3 @@ +import { Model } from 'miragejs'; + +export default Model.extend({}); diff --git a/tests/acceptance/home-page-test.js b/tests/acceptance/home-page-test.js index 54c90880e..1769b8492 100644 --- a/tests/acceptance/home-page-test.js +++ b/tests/acceptance/home-page-test.js @@ -107,6 +107,14 @@ module('Acceptance | home page', function (hooks) { }); test('it redirects to storeknox dashboard', async function (assert) { + const notify = this.owner.lookup('service:notifications'); + + notify.setDefaultClearDuration(0); + + this.server.get('v2/sk_app_detail', () => { + return { count: 0, next: null, previous: null, results: [] }; + }); + await visit('/dashboard/home'); assert.dom('[data-test-home-page-product-card]').exists({ diff --git a/tests/acceptance/side-nav-test.js b/tests/acceptance/side-nav-test.js index 609c896c1..6f696ab57 100644 --- a/tests/acceptance/side-nav-test.js +++ b/tests/acceptance/side-nav-test.js @@ -136,6 +136,14 @@ module('Acceptance | side nav test', function (hooks) { ], async function (assert, details) { + const notify = this.owner.lookup('service:notifications'); + + notify.setDefaultClearDuration(0); + + this.server.get('v2/sk_app_detail', () => { + return { count: 0, next: null, previous: null, results: [] }; + }); + const window = this.owner.lookup('service:browser/window'); window.location.href = details.visitUrl; diff --git a/tests/unit/adapters/sk-app-test.js b/tests/unit/adapters/sk-app-test.js new file mode 100644 index 000000000..c372e62d0 --- /dev/null +++ b/tests/unit/adapters/sk-app-test.js @@ -0,0 +1,12 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Adapter | sk app', function (hooks) { + setupTest(hooks); + + // Replace this with your real tests. + test('it exists', function (assert) { + let adapter = this.owner.lookup('adapter:sk-app'); + assert.ok(adapter); + }); +}); diff --git a/tests/unit/models/sk-app-test.js b/tests/unit/models/sk-app-test.js new file mode 100644 index 000000000..5754dfce4 --- /dev/null +++ b/tests/unit/models/sk-app-test.js @@ -0,0 +1,13 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Model | sk app', function (hooks) { + setupTest(hooks); + + // Replace this with your real tests. + test('it exists', function (assert) { + let store = this.owner.lookup('service:store'); + let model = store.createRecord('sk-app', {}); + assert.ok(model); + }); +}); diff --git a/translations/en.json b/translations/en.json index 2d965a10f..1f092aaa2 100644 --- a/translations/en.json +++ b/translations/en.json @@ -1518,6 +1518,7 @@ "appAlreadyRequested": "This app was already requested to be added to your Inventory. Please follow up with an Appknox Owner in your organization for reviewing the request.", "appRejected": "{appName} App has been rejected", "appRequested": "Request for {appName} has been successfully sent", + "appInventory": "App Inventory", "approved": "Approved", "autoDiscovery": "Auto Discovery", "availability": "Availability", @@ -1526,10 +1527,11 @@ "disclaimer": "Disclaimer", "disclaimerHeader": "Intended Solely for Your Organization’s Apps.", "disclaimerBody": "Discovery module is strictly meant for identifying and inventorying apps that your organization owns or has rights to. Responsibility for unauthorized tracking and any liability that results from such an action rests entirely with your organization.

Appknox will not be liable for any misuse involving apps outside of your organization’s ownership.", + "disabledApps": "Disabled Apps", + "errorSearchCharacter": "Minimum 2 characters required", "discoverHeader": "Discover", "discoverDescription": "Search for your apps on Google Play Store and Apple App Store and add them to your Organizations Inventory for automated monitoring.", "discoveryResults": "Discovery Results", - "errorSearchCharacter": "Minimum 2 characters required", "foundBy": "Found By", "homeTitle": "Home", "info": "INFO", @@ -1557,7 +1559,28 @@ "vapt": "VAPT", "vaptIndicatorText": "This App is part of VAPT", "waitingForApproval": "Waiting for approval", - "welcomeTitle": "Welcome to Storeknox" + "welcomeTitle": "Welcome to Storeknox", + "developer": "Developer", + "addedToInventoryOn": "Added to Inventory on", + "appNotPartOfAppknox": "This app is not a part of Appknox, upload to Appknox Dashboard for better results. Check the latest VA Results Section.", + "needsAction": "Needs Action", + "noActionNeeded": "No Action Needed", + "actionNeeded": "Action Needed", + "haveBeenDetected": "have been detected", + "appDetails": "App Details", + "latestVAResults": "Latest VA Results", + "playStore": "Play Store", + "appStore": "App Store", + "appIsPartOf": "This App is part of", + "checkOn": "Check on", + "actionsNeededMsg": "Unscanned versions", + "noActionsNeededMsg": "No unscanned versions", + "inventory": "Inventory", + "inventoryDescription": "This lists the mobile applications that belong to your Organization.", + "inventoryHeader": "Organization Inventory", + "malwareDetected": "Malware Detected", + "unscannedVersion": "Unscanned Version", + "brandAbuse": "Brand Abuse" }, "storeLowercase": "store", "submit": "Submit", diff --git a/translations/ja.json b/translations/ja.json index a64974cba..64041cf48 100644 --- a/translations/ja.json +++ b/translations/ja.json @@ -1518,6 +1518,7 @@ "appAlreadyRequested": "This app was already requested to be added to your Inventory. Please follow up with an Appknox Owner in your organization for reviewing the request.", "appRejected": "{appName} App has been rejected", "appRequested": "Request for {appName} has been successfully sent", + "appInventory": "App Inventory", "approved": "Approved", "autoDiscovery": "Auto Discovery", "availability": "Availability", @@ -1526,10 +1527,11 @@ "disclaimer": "Disclaimer", "disclaimerHeader": "Intended Solely for Your Organization’s Apps", "disclaimerBody": "Discovery module is strictly meant for identifying and inventorying apps that your organization owns or has rights to. Responsibility for unauthorized tracking and any liability that results from such an action rests entirely with your organization.

Appknox will not be liable for any misuse involving apps outside of your organization’s ownership.", + "disabledApps": "Disabled Apps", + "errorSearchCharacter": "Minimum 2 characters required", "discoverHeader": "Discover", "discoverDescription": "Search for your apps on Google Play Store and Apple App Store and add them to your Organizations Inventory for automated monitoring.", "discoveryResults": "Discovery Results", - "errorSearchCharacter": "Minimum 2 characters required", "foundBy": "Found By", "homeTitle": "Home", "info": "INFO", @@ -1557,7 +1559,28 @@ "vapt": "VAPT", "vaptIndicatorText": "This App is part of VAPT", "waitingForApproval": "Waiting for approval", - "welcomeTitle": "Welcome to Storeknox" + "welcomeTitle": "Welcome to Storeknox", + "developer": "Developer", + "addedToInventoryOn": "Added to Inventory on", + "appNotPartOfAppknox": "This app is not a part of Appknox, upload to Appknox Dashboard for better results. Check the latest VA Results Section.", + "needsAction": "Needs Action", + "noActionNeeded": "No Action Needed", + "actionNeeded": "Action Needed", + "haveBeenDetected": "have been detected", + "appDetails": "App Details", + "latestVAResults": "Latest VA Results", + "playStore": "Play Store", + "appStore": "App Store", + "appIsPartOf": "This App is part of", + "checkOn": "Check on", + "actionsNeededMsg": "Unscanned versions", + "noActionsNeededMsg": "No unscanned versions", + "inventory": "Inventory", + "inventoryDescription": "This lists the mobile applications that belong to your Organization.", + "inventoryHeader": "Organization Inventory", + "malwareDetected": "Malware Detected", + "unscannedVersion": "Unscanned Version", + "brandAbuse": "Brand Abuse" }, "storeLowercase": "store", "submit": "Submit", diff --git a/types/ak-svg.d.ts b/types/ak-svg.d.ts index 7a46f1031..548d2b704 100644 --- a/types/ak-svg.d.ts +++ b/types/ak-svg.d.ts @@ -45,8 +45,26 @@ export enum AkSvgComponentInvocationByNames { AppknoxBgImg, StoreknoxBgImg, SecurityBgImg, - VaptIndicator, + SecurityIndicator, + StoreknoxSearchApps, + StoreknoxPlaystoreLogo, SmIndicator, + VaptIndicator, + InfoIndicator, + NoPendingItems, + WelcomeToStoreknox, + InfoIndicator, + AoxIcon, + SoxIcon, + SoxInitiateUpload, + SoxUploadProcessing, + SoxUploadCompleted, + SoxInsufficientCredits, + AppknoxBgImg, + StoreknoxBgImg, + ConfigurationBgImg, + VpIndicator, + SecurityBgImg, SecurityIndicator, StoreknoxSearchApps, StoreknoxPlaystoreLogo, @@ -55,6 +73,11 @@ export enum AkSvgComponentInvocationByNames { InfoIndicator, NoPendingItems, WelcomeToStoreknox, + SoxBrandAbuseFeatureAbsence, + SoxMalwareFeatureAbsence, + SoxUnscannedDetailsTableEmpty, + SoxUnscannedHistoryTableEmpty, + SoxMonitoringPending, } export enum AkSvgComponentInvocationByPaths { @@ -64,6 +87,10 @@ export enum AkSvgComponentInvocationByPaths { 'csv-icon', 'public-api-icon', 'no-api-url-filter', + 'sm-indicator', + 'info-indicator', + 'vapt-indicator', + 'disabled-vapt-indicator', } type AkSvgComponent = ComponentLike<{