diff --git a/app/adapters/service-account.ts b/app/adapters/service-account.ts new file mode 100644 index 0000000000..02dc338d06 --- /dev/null +++ b/app/adapters/service-account.ts @@ -0,0 +1,15 @@ +import CommonDRFAdapter from './commondrf'; +import { underscore } from '@ember/string'; +import type ModelRegistry from 'ember-data/types/registries/model'; + +export default class ServiceAccountAdapter extends CommonDRFAdapter { + pathForType(type: keyof ModelRegistry) { + return underscore(super.pathForType(type)); + } +} + +declare module 'ember-data/types/registries/adapter' { + export default interface AdapterRegistry { + 'service-account': ServiceAccountAdapter; + } +} diff --git a/app/components/ak-tabs/item/index.hbs b/app/components/ak-tabs/item/index.hbs index 59db62d807..7ed7512e74 100644 --- a/app/components/ak-tabs/item/index.hbs +++ b/app/components/ak-tabs/item/index.hbs @@ -14,12 +14,11 @@ data-test-ak-tab-item as |It| > - - - {{#if (has-block 'tabIcon')}} + {{#if (has-block 'tabIcon')}} + {{yield to='tabIcon'}} - {{/if}} - + + {{/if}} {{yield}} diff --git a/app/components/organization-name-header/index.js b/app/components/organization-name-header/index.js deleted file mode 100644 index 7fdcfb3513..0000000000 --- a/app/components/organization-name-header/index.js +++ /dev/null @@ -1,46 +0,0 @@ -import Component from '@glimmer/component'; -import { inject as service } from '@ember/service'; -import { action } from '@ember/object'; -import { tracked } from '@glimmer/tracking'; - -class OrganizationNameHeader extends Component { - @service me; - - @tracked showAddEditModal = false; - @tracked editModal = false; - - get orgNameDoesNotExist() { - return this.args.organization.name === ''; - } - - get isAddBtnDisabled() { - return !this.me.get('org.is_owner'); - } - - get userType() { - return this.me.get('org.is_owner') - ? 'owner' - : this.me.get('org.is_admin') - ? 'admin' - : 'member'; - } - - @action - handleAddOrgNameClick() { - this.editModal = false; - this.showAddEditModal = true; - } - - @action - handleEditOrgName() { - this.editModal = true; - this.showAddEditModal = true; - } - - @action - handleCancel() { - this.showAddEditModal = false; - } -} - -export default OrganizationNameHeader; diff --git a/app/components/organization-name-header/index.scss b/app/components/organization-name-header/index.scss index a6fc9fa055..5d9b50c277 100644 --- a/app/components/organization-name-header/index.scss +++ b/app/components/organization-name-header/index.scss @@ -3,6 +3,7 @@ align-items: center; justify-content: space-between; padding: 1em; + background-color: var(--organization-name-header-background-color); border: 1px solid var(--organization-name-header-border-color); border-radius: var(--organization-name-header-border-radius); box-sizing: border-box; diff --git a/app/components/organization-name-header/index.ts b/app/components/organization-name-header/index.ts new file mode 100644 index 0000000000..7d0ab09e9c --- /dev/null +++ b/app/components/organization-name-header/index.ts @@ -0,0 +1,62 @@ +import Component from '@glimmer/component'; +import { inject as service } from '@ember/service'; +import { action } from '@ember/object'; +import { tracked } from '@glimmer/tracking'; + +import type MeService from 'irene/services/me'; +import type OrganizationModel from 'irene/models/organization'; + +interface OrganizationNameHeaderSignature { + Args: { + organization: OrganizationModel; + }; + Blocks: { + actionBtn: [{ openEditOrgNameModal: () => void }]; + }; +} + +export default class OrganizationNameHeaderComponent extends Component { + @service declare me: MeService; + + @tracked showAddEditModal = false; + @tracked editModal = false; + + get orgNameDoesNotExist() { + return this.args.organization.name === ''; + } + + get isAddBtnDisabled() { + return !this.me.get('org')?.get('is_owner'); + } + + get userType() { + return this.me.get('org')?.get('is_owner') + ? 'owner' + : this.me.get('org')?.get('is_admin') + ? 'admin' + : 'member'; + } + + @action + handleAddOrgNameClick() { + this.editModal = false; + this.showAddEditModal = true; + } + + @action + handleEditOrgName() { + this.editModal = true; + this.showAddEditModal = true; + } + + @action + handleCancel() { + this.showAddEditModal = false; + } +} + +declare module '@glint/environment-ember-loose/registry' { + export default interface Registry { + OrganizationNameHeader: typeof OrganizationNameHeaderComponent; + } +} diff --git a/app/components/organization-settings/index.js b/app/components/organization-settings/index.js deleted file mode 100644 index 50523eb439..0000000000 --- a/app/components/organization-settings/index.js +++ /dev/null @@ -1,14 +0,0 @@ -import Component from '@glimmer/component'; -import { inject as service } from '@ember/service'; - -class OrganizationSettings extends Component { - @service me; - - get showOrgNameActionBtn() { - return ( - this.args.model.organization.name !== '' && this.me.get('org.is_owner') - ); - } -} - -export default OrganizationSettings; diff --git a/app/components/organization-settings/index.scss b/app/components/organization-settings/index.scss deleted file mode 100644 index e70c8c9567..0000000000 --- a/app/components/organization-settings/index.scss +++ /dev/null @@ -1,4 +0,0 @@ -.org-settings-container { - padding: 1em 2em 5em 2em; - box-sizing: border-box; -} diff --git a/app/components/organization/service-account/empty/index.hbs b/app/components/organization/service-account/empty/index.hbs new file mode 100644 index 0000000000..fc9526120b --- /dev/null +++ b/app/components/organization/service-account/empty/index.hbs @@ -0,0 +1,18 @@ + + + + + No service account has created + + + + Create a service account by clicking on "Create" button + + + \ No newline at end of file diff --git a/app/components/organization/service-account/empty/index.ts b/app/components/organization/service-account/empty/index.ts new file mode 100644 index 0000000000..228d430594 --- /dev/null +++ b/app/components/organization/service-account/empty/index.ts @@ -0,0 +1,9 @@ +import Component from '@glimmer/component'; + +export default class OrganizationServiceAccountEmptyComponent extends Component {} + +declare module '@glint/environment-ember-loose/registry' { + export default interface Registry { + 'Organization::ServiceAccount::Empty': typeof OrganizationServiceAccountEmptyComponent; + } +} diff --git a/app/components/organization/service-account/list/action/index.hbs b/app/components/organization/service-account/list/action/index.hbs new file mode 100644 index 0000000000..74ce8b2a55 --- /dev/null +++ b/app/components/organization/service-account/list/action/index.hbs @@ -0,0 +1,11 @@ + + + + + +
+ + + + + \ No newline at end of file diff --git a/app/components/organization/service-account/list/action/index.scss b/app/components/organization/service-account/list/action/index.scss new file mode 100644 index 0000000000..6dc9374877 --- /dev/null +++ b/app/components/organization/service-account/list/action/index.scss @@ -0,0 +1,7 @@ +.divider { + width: 1px; + height: 22px; + background-color: var( + --file-details-vulnerability-analysis-details-edit-analysis-button-divider-color + ); +} diff --git a/app/components/organization/service-account/list/action/index.ts b/app/components/organization/service-account/list/action/index.ts new file mode 100644 index 0000000000..ac01882ac7 --- /dev/null +++ b/app/components/organization/service-account/list/action/index.ts @@ -0,0 +1,17 @@ +import Component from '@glimmer/component'; +import type ServiceAccountModel from 'irene/models/service-account'; + +export interface OrganizationServiceAccountListExpiryOnSignature { + Args: { + serviceAccount: ServiceAccountModel; + }; +} + +export default class OrganizationServiceAccountListExpiryOnComponent extends Component {} + +declare module '@glint/environment-ember-loose/registry' { + export default interface Registry { + 'Organization::ServiceAccount::List::Action': typeof OrganizationServiceAccountListExpiryOnComponent; + 'organization/service-account/list/action': typeof OrganizationServiceAccountListExpiryOnComponent; + } +} diff --git a/app/components/organization/service-account/list/expiry-on/index.hbs b/app/components/organization/service-account/list/expiry-on/index.hbs new file mode 100644 index 0000000000..47bbd8aeae --- /dev/null +++ b/app/components/organization/service-account/list/expiry-on/index.hbs @@ -0,0 +1,9 @@ + + <:icon> + + + \ No newline at end of file diff --git a/app/components/organization/service-account/list/expiry-on/index.ts b/app/components/organization/service-account/list/expiry-on/index.ts new file mode 100644 index 0000000000..eb62694273 --- /dev/null +++ b/app/components/organization/service-account/list/expiry-on/index.ts @@ -0,0 +1,31 @@ +import Component from '@glimmer/component'; +import dayjs from 'dayjs'; +import type ServiceAccountModel from 'irene/models/service-account'; + +export interface OrganizationServiceAccountListExpiryOnSignature { + Args: { + serviceAccount: ServiceAccountModel; + }; +} + +export default class OrganizationServiceAccountListExpiryOnComponent extends Component { + get expiryChipDetails() { + const serviceAccount = this.args.serviceAccount; + const noExpiry = serviceAccount.expiry === null; + + return { + label: noExpiry + ? 'No Expiry' + : dayjs(serviceAccount.expiry).format('DD MMM YYYY'), + icon: noExpiry ? 'warning' : 'event', + color: noExpiry ? ('warn' as const) : ('default' as const), + }; + } +} + +declare module '@glint/environment-ember-loose/registry' { + export default interface Registry { + 'Organization::ServiceAccount::List::ExpiryOn': typeof OrganizationServiceAccountListExpiryOnComponent; + 'organization/service-account/list/expiry-on': typeof OrganizationServiceAccountListExpiryOnComponent; + } +} diff --git a/app/components/organization/service-account/list/index.hbs b/app/components/organization/service-account/list/index.hbs new file mode 100644 index 0000000000..d90af93ca3 --- /dev/null +++ b/app/components/organization/service-account/list/index.hbs @@ -0,0 +1,95 @@ +
+ + + + All Service Account + + + + Lorem ipsum dolor sit amet consectetur. Amet habitant + + + + + + + + +
+ + + Create + + + + + + {{#if this.fetchServiceAccounts.isRunning}} + + + + {{else if this.hasNoServiceAccount}} + + + + {{else}} +
+ + + + + + {{#if r.columnValue.component}} + {{#let (component r.columnValue.component) as |Component|}} + + {{/let}} + {{else}} + + {{value}} + + {{/if}} + + + + +
+ + + {{/if}} +
+
\ No newline at end of file diff --git a/app/components/organization/service-account/list/index.scss b/app/components/organization/service-account/list/index.scss new file mode 100644 index 0000000000..c55da4ed36 --- /dev/null +++ b/app/components/organization/service-account/list/index.scss @@ -0,0 +1,14 @@ +.service-account-container { + margin-top: 1.5em; + background-color: var(--background-main); + border: 1px solid var(--border-color-1); + box-sizing: border-box; + + .divider { + width: 1px; + height: 34px; + background-color: var( + --file-details-vulnerability-analysis-details-edit-analysis-button-divider-color + ); + } +} diff --git a/app/components/organization/service-account/list/index.ts b/app/components/organization/service-account/list/index.ts new file mode 100644 index 0000000000..720a88b308 --- /dev/null +++ b/app/components/organization/service-account/list/index.ts @@ -0,0 +1,165 @@ +import { service } from '@ember/service'; +import { action } from '@ember/object'; +import Component from '@glimmer/component'; +import { task } from 'ember-concurrency'; +import { tracked } from '@glimmer/tracking'; +import type IntlService from 'ember-intl/services/intl'; +import type Store from '@ember-data/store'; +import type RouterService from '@ember/routing/router-service'; + +// eslint-disable-next-line ember/use-ember-data-rfc-395-imports +import type { DS } from 'ember-data'; + +import parseError from 'irene/utils/parse-error'; +import { ServiceAccountType } from 'irene/models/service-account'; +import type ServiceAccountModel from 'irene/models/service-account'; +import type { OrganizationSettingsServiceAccountRouteQueryParams } from 'irene/routes/authenticated/dashboard/organization-settings/service-account'; + +export interface OrganizationServiceAccountListSignature { + Args: { + queryParams: OrganizationSettingsServiceAccountRouteQueryParams; + }; +} + +type ServiceAccountQueryResponse = + DS.AdapterPopulatedRecordArray & { + meta: { count: number }; + }; + +export default class OrganizationServiceAccountListComponent extends Component { + @service declare intl: IntlService; + @service declare store: Store; + @service declare router: RouterService; + @service('notifications') declare notify: NotificationService; + + @tracked serviceAccountResponse: ServiceAccountQueryResponse | null = null; + + constructor( + owner: unknown, + args: OrganizationServiceAccountListSignature['Args'] + ) { + super(owner, args); + + this.fetchServiceAccounts.perform( + this.limit, + this.offset, + this.showSystemCreated, + false + ); + } + + get columns() { + return [ + { + name: 'Account Name', + valuePath: 'name', + }, + { + name: 'Account ID', + valuePath: 'accessKeyId', + width: 150, + }, + { + name: 'Expiry On', + component: 'organization/service-account/list/expiry-on', + }, + { + name: 'Created By', + valuePath: 'createdByUser.username', + }, + { + name: 'Action', + textAlign: 'center', + width: 60, + component: 'organization/service-account/list/action', + }, + ]; + } + + get limit() { + return this.args.queryParams.sa_limit; + } + + get offset() { + return this.args.queryParams.sa_offset; + } + + get showSystemCreated() { + return this.args.queryParams.show_system_created; + } + + get serviceAccountList() { + return this.serviceAccountResponse?.slice() || []; + } + + get totalServiceAccountCount() { + return this.serviceAccountResponse?.meta?.count || 0; + } + + get hasNoServiceAccount() { + return this.totalServiceAccountCount === 0; + } + + @action + handleShowSystemCreated(event: Event, checked: boolean) { + this.fetchServiceAccounts.perform(this.limit, 0, checked); + } + + @action + handleNextPrevAction({ limit, offset }: { limit: number; offset: number }) { + this.fetchServiceAccounts.perform(limit, offset, this.showSystemCreated); + } + + @action + handleItemPerPageChange({ limit }: { limit: number }) { + this.fetchServiceAccounts.perform(limit, 0, this.showSystemCreated); + } + + setRouteQueryParams( + limit?: number, + offset?: number, + showSystemCreated?: boolean + ) { + this.router.transitionTo({ + queryParams: { + sa_limit: limit, + sa_offset: offset, + show_system_created: showSystemCreated, + }, + }); + } + + fetchServiceAccounts = task( + async ( + limit: number, + offset: number, + showSystemCreated: boolean, + setQueryParams = true + ) => { + if (setQueryParams) { + this.setRouteQueryParams(limit, offset, showSystemCreated); + } + + const data: Record = { limit, offset }; + + if (!showSystemCreated) { + data['service_account_type'] = ServiceAccountType.USER; + } + + try { + this.serviceAccountResponse = (await this.store.query( + 'service-account', + data + )) as ServiceAccountQueryResponse; + } catch (error) { + this.notify.error(parseError(error, this.intl.t('pleaseTryAgain'))); + } + } + ); +} + +declare module '@glint/environment-ember-loose/registry' { + export default interface Registry { + 'Organization::ServiceAccount::List': typeof OrganizationServiceAccountListComponent; + } +} diff --git a/app/components/organization/service-account/loading/index.hbs b/app/components/organization/service-account/loading/index.hbs new file mode 100644 index 0000000000..ab7c57491a --- /dev/null +++ b/app/components/organization/service-account/loading/index.hbs @@ -0,0 +1,17 @@ + + + + + + + + {{t 'loading'}}... + + + \ No newline at end of file diff --git a/app/components/organization/service-account/loading/index.ts b/app/components/organization/service-account/loading/index.ts new file mode 100644 index 0000000000..0030a35339 --- /dev/null +++ b/app/components/organization/service-account/loading/index.ts @@ -0,0 +1,9 @@ +import Component from '@glimmer/component'; + +export default class OrganizationServiceAccountLoadingComponent extends Component {} + +declare module '@glint/environment-ember-loose/registry' { + export default interface Registry { + 'Organization::ServiceAccount::Loading': typeof OrganizationServiceAccountLoadingComponent; + } +} diff --git a/app/components/organization/settings-wrapper/index.hbs b/app/components/organization/settings-wrapper/index.hbs new file mode 100644 index 0000000000..5f56ddc105 --- /dev/null +++ b/app/components/organization/settings-wrapper/index.hbs @@ -0,0 +1,54 @@ +
+
+
+ + {{#each this.breadcrumbItems as |item|}} + + {{/each}} + +
+ + + <:actionBtn as |ab|> + {{#if this.showOrgNameActionBtn}} + + <:leftIcon> + + + + <:default> + {{t 'editName'}} + + + {{/if}} + + + +
+ + {{#each this.tabItems as |item|}} + + {{item.label}} + + {{/each}} + + + {{yield}} +
+
+
\ No newline at end of file diff --git a/app/components/organization/settings-wrapper/index.scss b/app/components/organization/settings-wrapper/index.scss new file mode 100644 index 0000000000..f17dae4f9e --- /dev/null +++ b/app/components/organization/settings-wrapper/index.scss @@ -0,0 +1,29 @@ +$container-width: 1200px; + +.organization-settings-root { + padding: 0 1.5em 1.5em; + margin: -0.5em; + background-color: var(--file-details-api-scan-background-color); + min-height: calc(100vh - 56px); + + .organization-settings-container { + max-width: $container-width; + margin: 0 auto; + + .organization-settings-breadcrumbs-container, + .organization-settings-tabs-container { + position: sticky; + z-index: 100; + background-color: var(--file-details-api-scan-background-color); + } + + .organization-settings-breadcrumbs-container { + padding: 1.5em 0; + top: -0.5em; + } + + .organization-settings-tabs-container { + top: 3.5em; + } + } +} diff --git a/app/components/organization/settings-wrapper/index.ts b/app/components/organization/settings-wrapper/index.ts new file mode 100644 index 0000000000..a4db632e7d --- /dev/null +++ b/app/components/organization/settings-wrapper/index.ts @@ -0,0 +1,63 @@ +import { service } from '@ember/service'; +import Component from '@glimmer/component'; +import type IntlService from 'ember-intl/services/intl'; + +import type OrganizationModel from 'irene/models/organization'; +import type MeService from 'irene/services/me'; + +export interface OrganizationSettingsWrapperSignature { + Args: { + organization: OrganizationModel; + }; + Blocks: { + default: []; + }; +} + +export default class OrganizationSettingsWrapperComponent extends Component { + @service declare intl: IntlService; + @service declare me: MeService; + + get breadcrumbItems() { + return [ + { + route: 'authenticated.dashboard.organization.namespaces', + linkTitle: this.intl.t('organization'), + }, + { + route: 'authenticated.dashboard.organization-settings', + linkTitle: this.intl.t('settings'), + }, + ]; + } + + get tabItems() { + return [ + { + id: 'organization-settings', + label: this.intl.t('organizationSettings'), + route: 'authenticated.dashboard.organization-settings.index', + currentWhen: 'authenticated.dashboard.organization-settings.index', + }, + { + id: 'service-account', + label: 'Service Account', + route: 'authenticated.dashboard.organization-settings.service-account', + currentWhen: + 'authenticated.dashboard.organization-settings.service-account', + }, + ]; + } + + get showOrgNameActionBtn() { + return ( + this.args.organization.name !== '' && this.me.get('org')?.get('is_owner') + ); + } +} + +declare module '@glint/environment-ember-loose/registry' { + export default interface Registry { + 'Organization::SettingsWrapper': typeof OrganizationSettingsWrapperComponent; + } +} diff --git a/app/components/organization-settings/index.hbs b/app/components/organization/settings/index.hbs similarity index 55% rename from app/components/organization-settings/index.hbs rename to app/components/organization/settings/index.hbs index aa53628d04..8212db17ee 100644 --- a/app/components/organization-settings/index.hbs +++ b/app/components/organization/settings/index.hbs @@ -1,34 +1,4 @@
- - <:leftIcon> - - - - <:default>{{t 'back'}} - - -
- - <:actionBtn as |ab|> - {{#if this.showOrgNameActionBtn}} - - <:leftIcon> - - - - <:default> - {{t 'editName'}} - - - {{/if}} - - -
- {{#if this.me.org.is_owner}} diff --git a/app/components/organization/settings/index.js b/app/components/organization/settings/index.js new file mode 100644 index 0000000000..a866970306 --- /dev/null +++ b/app/components/organization/settings/index.js @@ -0,0 +1,6 @@ +import Component from '@glimmer/component'; +import { inject as service } from '@ember/service'; + +export default class OrganizationSettings extends Component { + @service me; +} diff --git a/app/components/organization/settings/index.scss b/app/components/organization/settings/index.scss new file mode 100644 index 0000000000..5bfc454f1d --- /dev/null +++ b/app/components/organization/settings/index.scss @@ -0,0 +1,6 @@ +.org-settings-container { + background-color: var(--background-main); + margin-top: 1.5em; + padding: 1.5em 2em 5em 2em; + box-sizing: border-box; +} diff --git a/app/controllers/authenticated/dashboard/organization-settings/service-account.ts b/app/controllers/authenticated/dashboard/organization-settings/service-account.ts new file mode 100644 index 0000000000..75728b6daf --- /dev/null +++ b/app/controllers/authenticated/dashboard/organization-settings/service-account.ts @@ -0,0 +1,9 @@ +import Controller from '@ember/controller'; + +export default class AuthenticatedDashboardOrganizationSettingsServiceAccountController extends Controller { + queryParams = ['sa_limit', 'sa_offset', 'show_system_created']; + + sa_limit = 10; + sa_offset = 0; + show_system_created = false; +} diff --git a/app/models/service-account.ts b/app/models/service-account.ts new file mode 100644 index 0000000000..e2639bc5e8 --- /dev/null +++ b/app/models/service-account.ts @@ -0,0 +1,63 @@ +import Model, { attr, belongsTo, type AsyncBelongsTo } from '@ember-data/model'; +import type UserModel from './user'; + +export enum ServiceAccountType { + USER = 1, + SYSTEM = 2, +} + +export default class ServiceAccountModel extends Model { + @attr('boolean') + declare scopePublicApiUserRead: boolean; + + @attr('boolean') + declare scopePublicApiProjectRead: boolean; + + @attr('boolean') + declare scopePublicApiScanResultVa: boolean; + + @attr('boolean') + declare isExpired: boolean; + + @attr('boolean') + declare isActive: boolean; + + @attr('boolean') + declare allProjects: boolean; + + @attr('date') + declare expiry: Date; + + @attr('string') + declare name: string; + + @attr('string') + declare description: string; + + @attr('string') + declare accessKeyId: string; + + @attr('string') + declare secretAccessKey: string; + + @attr('number') + declare serviceAccountType: ServiceAccountType; + + @attr('date') + declare updatedOn: Date; + + @attr('date') + declare createdOn: Date; + + @belongsTo('user') + declare updatedByUser: AsyncBelongsTo | null; + + @belongsTo('user') + declare createdByUser: AsyncBelongsTo; +} + +declare module 'ember-data/types/registries/model' { + export default interface ModelRegistry { + 'service-account': ServiceAccountModel; + } +} diff --git a/app/router.ts b/app/router.ts index 99688302ee..de6b68741e 100644 --- a/app/router.ts +++ b/app/router.ts @@ -231,7 +231,14 @@ Router.map(function () { this.route('teams'); }); - this.route('organization-settings', { path: '/organization/settings' }); + this.route( + 'organization-settings', + { path: '/organization/settings' }, + function () { + this.route('index', { path: '/' }); + this.route('service-account'); + } + ); }); } ); diff --git a/app/routes/authenticated/dashboard/organization-settings.ts b/app/routes/authenticated/dashboard/organization-settings.ts index cde29c1fe7..5295b76cd8 100644 --- a/app/routes/authenticated/dashboard/organization-settings.ts +++ b/app/routes/authenticated/dashboard/organization-settings.ts @@ -1,21 +1,16 @@ -import Store from '@ember-data/store'; -import Route from '@ember/routing/route'; -import RouterService from '@ember/routing/router-service'; import { inject as service } from '@ember/service'; +import Route from '@ember/routing/route'; +import type RouterService from '@ember/routing/router-service'; -import UserModel from 'irene/models/user'; -import MeService from 'irene/services/me'; -import OrganizationService from 'irene/services/organization'; - -type AjaxError = { status: number }; +import type UserModel from 'irene/models/user'; +import type OrganizationModel from 'irene/models/organization'; +import type MeService from 'irene/services/me'; +import type OrganizationService from 'irene/services/organization'; export default class AuthenticatedOrganizationSettingsRoute extends Route { @service declare me: MeService; @service declare organization: OrganizationService; - @service('notifications') declare notify: NotificationService; @service declare router: RouterService; - @service declare store: Store; - @service ajax!: any; beforeModel(): void { if (!this.me.org?.get('is_admin')) { @@ -26,30 +21,9 @@ export default class AuthenticatedOrganizationSettingsRoute extends Route { } async model() { - const url = `/api/organizations/${this.organization.selected?.id}/github`; - let integratedUser: unknown | null = null; - let reconnect = false; - - try { - const data = await this.ajax.request(url); - - if (data) { - integratedUser = data; - } - } catch (err) { - if ((err as AjaxError).status === 400) { - reconnect = true; - } - } - - await this.store.query('organization', { id: null }); - return { - integratedUser, - reconnect, - user: (await this.modelFor('authenticated')) as UserModel, - organization: await this.organization.selected, - me: this.me, + user: this.modelFor('authenticated') as UserModel, + organization: this.organization.selected as OrganizationModel, }; } } diff --git a/app/routes/authenticated/dashboard/organization-settings/index.ts b/app/routes/authenticated/dashboard/organization-settings/index.ts new file mode 100644 index 0000000000..a031ad4a64 --- /dev/null +++ b/app/routes/authenticated/dashboard/organization-settings/index.ts @@ -0,0 +1,41 @@ +import Route from '@ember/routing/route'; +import { service } from '@ember/service'; + +import type MeService from 'irene/services/me'; +import type { ModelFrom } from 'irene/utils/types'; +import type AuthenticatedOrganizationSettingsRoute from '../organization-settings'; + +export default class AuthenticatedDashboardOrganizationSettingsIndexRoute extends Route { + @service declare me: MeService; + @service declare ajax: any; + + async model() { + const { organization, user } = this.modelFor( + 'authenticated.dashboard.organization-settings' + ) as ModelFrom; + + const url = `/api/organizations/${organization.get('id')}/github`; + let integratedUser: unknown | null = null; + let reconnect = false; + + try { + const data = await this.ajax.request(url); + + if (data) { + integratedUser = data; + } + } catch (err) { + if ((err as AjaxError).status === 400) { + reconnect = true; + } + } + + return { + integratedUser, + reconnect, + user, + organization, + me: this.me, + }; + } +} diff --git a/app/routes/authenticated/dashboard/organization-settings/service-account.ts b/app/routes/authenticated/dashboard/organization-settings/service-account.ts new file mode 100644 index 0000000000..034bb49682 --- /dev/null +++ b/app/routes/authenticated/dashboard/organization-settings/service-account.ts @@ -0,0 +1,29 @@ +import Route from '@ember/routing/route'; + +export interface OrganizationSettingsServiceAccountRouteQueryParams { + sa_limit: number; + sa_offset: number; + show_system_created: boolean; +} + +export default class AuthenticatedDashboardOrganizationSettingsServiceAccountRoute extends Route { + queryParams = { + sa_limit: { + refreshModel: true, + }, + sa_offset: { + refreshModel: true, + }, + show_system_created: { + refreshModel: true, + }, + }; + + model( + queryParams: Partial + ) { + return { + queryParams, + }; + } +} diff --git a/app/styles/_component-variables.scss b/app/styles/_component-variables.scss index d43197f860..8336373db7 100644 --- a/app/styles/_component-variables.scss +++ b/app/styles/_component-variables.scss @@ -193,14 +193,26 @@ body { --ak-chip-color-outlined-warn: var(--warn-main); --ak-chip-color-outlined-info: var(--info-main); - --ak-chip-color-semi-filled-outlined-default-background: var(--neutral-grey-200); - --ak-chip-color-semi-filled-outlined-default-contrast-text: var(--neutral-grey-700); + --ak-chip-color-semi-filled-outlined-default-background: var( + --neutral-grey-200 + ); + --ak-chip-color-semi-filled-outlined-default-contrast-text: var( + --neutral-grey-700 + ); --ak-chip-color-semi-filled-outlined-primary-background: var(--primary-light); - --ak-chip-color-semi-filled-outlined-primary-contrast-text: var(--primary-dark); - --ak-chip-color-semi-filled-outlined-secondary-background: var(--neutral-grey-200); - --ak-chip-color-semi-filled-outlined-secondary-contrast-text: var(--secondary-main); + --ak-chip-color-semi-filled-outlined-primary-contrast-text: var( + --primary-dark + ); + --ak-chip-color-semi-filled-outlined-secondary-background: var( + --neutral-grey-200 + ); + --ak-chip-color-semi-filled-outlined-secondary-contrast-text: var( + --secondary-main + ); --ak-chip-color-semi-filled-outlined-success-background: var(--success-light); - --ak-chip-color-semi-filled-outlined-success-contrast-text: var(--success-main); + --ak-chip-color-semi-filled-outlined-success-contrast-text: var( + --success-main + ); --ak-chip-color-semi-filled-outlined-error-background: var(--error-light); --ak-chip-color-semi-filled-outlined-error-contrast-text: var(--error-main); --ak-chip-color-semi-filled-outlined-warn-background: var(--warn-light); @@ -512,7 +524,8 @@ body { --github-account-border-color: var(--border-color-1); // variables for organization-name-header - --organization-name-header-border-color: var(--neutral-grey-100); + --organization-name-header-background-color: var(--background-main); + --organization-name-header-border-color: var(--border-color-1); --organization-name-header-border-radius: var(--border-radius); --organization-name-color-primary-light: var(--primary-light); --organization-name-color-text-disabled: var(--text-disabled); diff --git a/app/templates/authenticated/dashboard/organization-settings.hbs b/app/templates/authenticated/dashboard/organization-settings.hbs index b2ba986aa0..8bdde51f9b 100644 --- a/app/templates/authenticated/dashboard/organization-settings.hbs +++ b/app/templates/authenticated/dashboard/organization-settings.hbs @@ -1,3 +1,5 @@ {{page-title 'Organization Settings'}} - \ No newline at end of file + + {{outlet}} + \ No newline at end of file diff --git a/app/templates/authenticated/dashboard/organization-settings/index.hbs b/app/templates/authenticated/dashboard/organization-settings/index.hbs new file mode 100644 index 0000000000..81fe8b94d3 --- /dev/null +++ b/app/templates/authenticated/dashboard/organization-settings/index.hbs @@ -0,0 +1,3 @@ +{{page-title 'Settings'}} + + \ No newline at end of file diff --git a/app/templates/authenticated/dashboard/organization-settings/service-account.hbs b/app/templates/authenticated/dashboard/organization-settings/service-account.hbs new file mode 100644 index 0000000000..fedcaf47c7 --- /dev/null +++ b/app/templates/authenticated/dashboard/organization-settings/service-account.hbs @@ -0,0 +1,3 @@ +{{page-title 'Service Account'}} + + \ No newline at end of file diff --git a/tests/unit/adapters/service-account-test.js b/tests/unit/adapters/service-account-test.js new file mode 100644 index 0000000000..ac5a824cdb --- /dev/null +++ b/tests/unit/adapters/service-account-test.js @@ -0,0 +1,13 @@ +import { module, test } from 'qunit'; + +import { setupTest } from 'irene/tests/helpers'; + +module('Unit | Adapter | service account', function (hooks) { + setupTest(hooks); + + // Replace this with your real tests. + test('it exists', function (assert) { + let adapter = this.owner.lookup('adapter:service-account'); + assert.ok(adapter); + }); +}); diff --git a/tests/unit/controllers/authenticated/dashboard/organization-settings/service-account-test.js b/tests/unit/controllers/authenticated/dashboard/organization-settings/service-account-test.js new file mode 100644 index 0000000000..847e45a62f --- /dev/null +++ b/tests/unit/controllers/authenticated/dashboard/organization-settings/service-account-test.js @@ -0,0 +1,17 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'irene/tests/helpers'; + +module( + 'Unit | Controller | authenticated/dashboard/organization-settings/service-account', + function (hooks) { + setupTest(hooks); + + // TODO: Replace this with your real tests. + test('it exists', function (assert) { + let controller = this.owner.lookup( + 'controller:authenticated/dashboard/organization-settings/service-account' + ); + assert.ok(controller); + }); + } +); diff --git a/tests/unit/models/service-account-test.js b/tests/unit/models/service-account-test.js new file mode 100644 index 0000000000..bc21a5c218 --- /dev/null +++ b/tests/unit/models/service-account-test.js @@ -0,0 +1,14 @@ +import { module, test } from 'qunit'; + +import { setupTest } from 'irene/tests/helpers'; + +module('Unit | Model | service account', 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('service-account', {}); + assert.ok(model); + }); +});