diff --git a/addon/controllers/api-keys/index.js b/addon/controllers/api-keys/index.js index 9e52881..b49b2e6 100644 --- a/addon/controllers/api-keys/index.js +++ b/addon/controllers/api-keys/index.js @@ -11,6 +11,7 @@ import { format as formatDate } from 'date-fns'; import getWithDefault from '@fleetbase/ember-core/utils/get-with-default'; export default class ApiKeysIndexController extends Controller { + @service apiKeyActions; @service currentUser; @service intl; @service modalsManager; @@ -261,83 +262,13 @@ export default class ApiKeysIndexController extends Controller { this.currentUser.setOption('testKey', value); } - /** - * Toggles modal to create a new API key - * - * @void - */ - @action createApiKey() { - const formPermission = 'developers create api-key'; - const apiKey = this.store.createRecord('api-credential', { - test_mode: this.testMode, - }); - - this.editApiKey(apiKey, { - title: this.intl.t('developers.api-keys.index.new-api-key-title'), - acceptButtonIcon: 'check', - acceptButtonIconPrefix: 'fas', - acceptButtonDisabled: this.abilities.cannot(formPermission), - acceptButtonHelpText: this.abilities.cannot(formPermission) ? this.intl.t('common.unauthorized') : null, - successMessage: this.intl.t('developers.api-keys.index.new-api-key-message'), - formPermission, - apiKey, - confirm: async (modal) => { - modal.startLoading(); - - if (this.abilities.cannot(formPermission)) { - return this.notifications.warning(this.intl.t('common.permissions-required-for-changes')); - } - - try { - await apiKey.save(); - this.notifications.success(modal.getOption('successMessage')); - return this.hostRouter.refresh(); - } catch (error) { - this.notifications.serverError(error); - modal.stopLoading(); - } - }, - }); + // Creating and editing keys lives in the api-key-actions service so other engines can open these dialogs too. + @action createApiKey(...args) { + return this.apiKeyActions.createApiKey(...args); } - /** - * Toggles modal to create a new API key - * - * @void - */ - @action editApiKey(apiKey, options = {}) { - const formPermission = 'developers update api-key'; - this.modalsManager.show('modals/api-key-form', { - title: this.intl.t('developers.api-keys.index.edit-api-key-title'), - acceptButtonIcon: 'save', - acceptButtonDisabled: this.abilities.cannot(formPermission), - acceptButtonHelpText: this.abilities.cannot(formPermission) ? this.intl.t('common.unauthorized') : null, - successMessage: this.intl.t('developers.api-keys.index.edit-api-key-message'), - expirationOptions: this.expirationOptions, - testMode: this.currentUser.getOption('sandbox') || false, - apiKey, - formPermission, - setExpiration: ({ target }) => { - apiKey.expires_at = target.value || null; - }, - confirm: async (modal) => { - modal.startLoading(); - - if (this.abilities.cannot(formPermission)) { - return this.notifications.warning(this.intl.t('common.permissions-required-for-changes')); - } - - try { - await apiKey.save(); - this.notifications.success(modal.getOption('successMessage')); - return this.hostRouter.refresh(); - } catch (error) { - this.notifications.serverError(error); - modal.stopLoading(); - } - }, - ...options, - }); + @action editApiKey(...args) { + return this.apiKeyActions.editApiKey(...args); } /** diff --git a/addon/controllers/webhooks/index.js b/addon/controllers/webhooks/index.js index 17ae163..83f2ebe 100644 --- a/addon/controllers/webhooks/index.js +++ b/addon/controllers/webhooks/index.js @@ -10,6 +10,7 @@ import fromStore from '@fleetbase/ember-core/decorators/legacy-from-store'; import fetchFrom from '@fleetbase/ember-core/decorators/legacy-fetch-from'; export default class WebhooksIndexController extends BaseController { + @service webhookActions; @service currentUser; @service intl; @service modalsManager; @@ -177,154 +178,17 @@ export default class WebhooksIndexController extends BaseController { this.query = value; } - /** - * Toggles modal to create a new API key - * - * @void - */ - @action createWebhook() { - const formPermission = 'developers create webhook'; - const webhook = this.store.createRecord('webhook-endpoint', { - events: [], - mode: this.currentUser.getOption('sandbox') ? 'test' : 'live', - }); - - this.editWebhook(webhook, { - title: this.intl.t('developers.webhooks.index.add-webhook'), - acceptButtonText: this.intl.t('developers.webhooks.index.add-webhook-button-text'), - acceptButtonIcon: 'check', - acceptButtonIconPrefix: 'fas', - acceptButtonDisabled: this.abilities.cannot(formPermission), - acceptButtonHelpText: this.abilities.cannot(formPermission) ? this.intl.t('common.unauthorized') : null, - formPermission, - webhook, - confirm: async (modal) => { - modal.startLoading(); - - if (this.abilities.cannot(formPermission)) { - return this.notifications.warning(this.intl.t('common.permissions-required-for-changes')); - } - - try { - await webhook.save(); - this.notifications.success(this.intl.t('developers.webhooks.index.new-webhook-success-message')); - return this.hostRouter.refresh(); - } catch (error) { - this.notifications.serverError(error); - modal.stopLoading(); - } - }, - }); + // Webhook dialogs live in the webhook-actions service so other engines can open them too. + @action createWebhook(...args) { + return this.webhookActions.createWebhook(...args); } - /** - * Triggers dialog to edit webhook - * - * @param {WebhookEndpointModel} webhook - * @param {Object} options - * @void - */ - @action async editWebhook(webhook, options = {}) { - await this.apiCredentials; - - const formPermission = 'developers update webhook'; - this.modalsManager.show('modals/webhook-form', { - title: this.intl.t('developers.webhooks.index.edit-webhook-endpoint'), - acceptButtonText: this.intl.t('developers.webhooks.index.edit-webhook-endpoint-button-text'), - acceptButtonIcon: 'save', - acceptButtonDisabled: this.abilities.cannot(formPermission), - acceptButtonHelpText: this.abilities.cannot(formPermission) ? this.intl.t('common.unauthorized') : null, - formPermission, - declineButtonIcon: 'times', - declineButtonIconPrefix: 'fas', - eventOptions: this.groupedApiEvents, - versionOptions: this.apiVersions, - apiCredentialOptions: this.apiCredentials, - webhook, - setVersion: ({ target }) => { - webhook.version = target.value || null; - }, - setApiCredential: ({ target }) => { - webhook.api_credential_uuid = target.value || null; - }, - searchEvents: (query) => { - if (typeof query !== 'string') { - return; - } - const resources = Object.keys(this.groupedApiEvents); - const filteredEvents = {}; - resources.forEach((eventResource) => { - filteredEvents[eventResource] = this.groupedApiEvents[eventResource].filter((event) => { - return event.toLowerCase().includes(query.toLowerCase()); - }); - // if 0 events remove from filter - if (filteredEvents[eventResource].length === 0) { - delete filteredEvents[eventResource]; - } - }); - this.modalsManager.setOption('eventOptions', filteredEvents); - }, - addEvent: (event) => { - if (webhook.events.includes(event)) { - return; - } - - webhook.events.pushObject(event); - }, - removeEvent: (event) => { - webhook.events.removeObject(event); - }, - clearEvents: () => { - webhook.events.clear(); - }, - receiveAllEvents: () => { - webhook.events.pushObjects(this.webhookEvents); - }, - confirm: async (modal) => { - modal.startLoading(); - - if (this.abilities.cannot(formPermission)) { - return this.notifications.warning(this.intl.t('common.permissions-required-for-changes')); - } - - try { - await webhook.save(); - this.notifications.success(this.intl.t('developers.webhooks.index.new-webhook-success-message')); - return this.hostRouter.refresh(); - } catch (error) { - this.notifications.serverError(error); - modal.stopLoading(); - } - }, - ...options, - }); + @action editWebhook(...args) { + return this.webhookActions.editWebhook(...args); } - /** - * Toggles dialog to delete webhook - * - * @param {WebhookEndpointModel} webhook - * @param {Object} options - * @void - */ - @action deleteWebhook(webhook, options = {}) { - this.modalsManager.confirm({ - title: this.intl.t('developers.webhooks.index.delete-webhook-endpoint'), - body: this.intl.t('developers.webhooks.index.delete-webhook-endpoint-body'), - confirm: async (modal) => { - modal.startLoading(); - - try { - await webhook.destroyRecord(); - this.notifications.success(this.intl.t('developers.webhooks.index.delete-webhook-success-message')); - return this.hostRouter.refresh(); - } catch (error) { - this.notifications.serverError(error); - modal.stopLoading(); - } - }, - ...options, - }); + @action deleteWebhook(...args) { + return this.webhookActions.deleteWebhook(...args); } /** diff --git a/addon/services/api-key-actions.js b/addon/services/api-key-actions.js new file mode 100644 index 0000000..a76abea --- /dev/null +++ b/addon/services/api-key-actions.js @@ -0,0 +1,102 @@ +import ResourceActionService from '@fleetbase/ember-core/services/resource-action'; +import { action } from '@ember/object'; + +/** + * API key actions shared by the Developers console and other engines (for example Fleetbase AI). + */ +export default class ApiKeyActionsService extends ResourceActionService { + expirationOptions = ['never', 'immediately', 'in 1 hour', 'in 24 hours', 'in 3 days', 'in 7 days']; + + constructor() { + super(...arguments); + this.initialize('api-credential', { permissionPrefix: 'developers', mountPrefix: 'console.developers' }); + } + + transition = { + list: () => this.transitionTo('api-keys.index'), + }; + + modal = { + create: (...args) => this.createApiKey(...args), + edit: (...args) => this.editApiKey(...args), + }; + + /** + * Toggles modal to create a new API key + * + * @void + */ + @action createApiKey() { + const formPermission = 'developers create api-key'; + const apiKey = this.store.createRecord('api-credential', { + test_mode: this.currentUser.getOption('sandbox') || false, + }); + + this.editApiKey(apiKey, { + title: this.intl.t('developers.api-keys.index.new-api-key-title'), + acceptButtonIcon: 'check', + acceptButtonIconPrefix: 'fas', + acceptButtonDisabled: this.abilities.cannot(formPermission), + acceptButtonHelpText: this.abilities.cannot(formPermission) ? this.intl.t('common.unauthorized') : null, + successMessage: this.intl.t('developers.api-keys.index.new-api-key-message'), + formPermission, + apiKey, + confirm: async (modal) => { + modal.startLoading(); + + if (this.abilities.cannot(formPermission)) { + return this.notifications.warning(this.intl.t('common.permissions-required-for-changes')); + } + + try { + await apiKey.save(); + this.notifications.success(modal.getOption('successMessage')); + return this.hostRouter.refresh(); + } catch (error) { + this.notifications.serverError(error); + modal.stopLoading(); + } + }, + }); + } + + /** + * Opens the dialog to edit an API key + * + * @void + */ + @action editApiKey(apiKey, options = {}) { + const formPermission = 'developers update api-key'; + this.modalsManager.show('modals/api-key-form', { + title: this.intl.t('developers.api-keys.index.edit-api-key-title'), + acceptButtonIcon: 'save', + acceptButtonDisabled: this.abilities.cannot(formPermission), + acceptButtonHelpText: this.abilities.cannot(formPermission) ? this.intl.t('common.unauthorized') : null, + successMessage: this.intl.t('developers.api-keys.index.edit-api-key-message'), + expirationOptions: this.expirationOptions, + testMode: this.currentUser.getOption('sandbox') || false, + apiKey, + formPermission, + setExpiration: ({ target }) => { + apiKey.expires_at = target.value || null; + }, + confirm: async (modal) => { + modal.startLoading(); + + if (this.abilities.cannot(formPermission)) { + return this.notifications.warning(this.intl.t('common.permissions-required-for-changes')); + } + + try { + await apiKey.save(); + this.notifications.success(modal.getOption('successMessage')); + return this.hostRouter.refresh(); + } catch (error) { + this.notifications.serverError(error); + modal.stopLoading(); + } + }, + ...options, + }); + } +} diff --git a/addon/services/webhook-actions.js b/addon/services/webhook-actions.js new file mode 100644 index 0000000..d15d7e2 --- /dev/null +++ b/addon/services/webhook-actions.js @@ -0,0 +1,185 @@ +import ResourceActionService from '@fleetbase/ember-core/services/resource-action'; +import { action } from '@ember/object'; +import groupApiEvents from '@fleetbase/ember-core/utils/group-api-events'; + +/** + * Webhook endpoint actions shared by the Developers console and other engines (for example Fleetbase AI). + */ +export default class WebhookActionsService extends ResourceActionService { + constructor() { + super(...arguments); + this.initialize('webhook-endpoint', { permissionPrefix: 'developers', mountPrefix: 'console.developers' }); + } + + transition = { + list: () => this.transitionTo('webhooks.index'), + }; + + modal = { + create: (...args) => this.createWebhook(...args), + edit: (...args) => this.editWebhook(...args), + }; + + /** + * Loads the event, version, and credential options the webhook form needs. + */ + async loadWebhookOptions() { + const [webhookEvents, apiVersions, apiCredentials] = await Promise.all([ + this.fetch.get('webhook-endpoints/events'), + this.fetch.get('webhook-endpoints/versions'), + this.store.query('api-credential', { limit: -1 }), + ]); + + return { webhookEvents, groupedApiEvents: groupApiEvents(webhookEvents), apiVersions, apiCredentials }; + } + + /** + * Opens the dialog to add a webhook endpoint + * + * @void + */ + @action createWebhook() { + const formPermission = 'developers create webhook'; + const webhook = this.store.createRecord('webhook-endpoint', { + events: [], + mode: this.currentUser.getOption('sandbox') ? 'test' : 'live', + }); + + this.editWebhook(webhook, { + title: this.intl.t('developers.webhooks.index.add-webhook'), + acceptButtonText: this.intl.t('developers.webhooks.index.add-webhook-button-text'), + acceptButtonIcon: 'check', + acceptButtonIconPrefix: 'fas', + acceptButtonDisabled: this.abilities.cannot(formPermission), + acceptButtonHelpText: this.abilities.cannot(formPermission) ? this.intl.t('common.unauthorized') : null, + formPermission, + webhook, + confirm: async (modal) => { + modal.startLoading(); + + if (this.abilities.cannot(formPermission)) { + return this.notifications.warning(this.intl.t('common.permissions-required-for-changes')); + } + + try { + await webhook.save(); + this.notifications.success(this.intl.t('developers.webhooks.index.new-webhook-success-message')); + return this.hostRouter.refresh(); + } catch (error) { + this.notifications.serverError(error); + modal.stopLoading(); + } + }, + }); + } + + /** + * Triggers dialog to edit webhook + * + * @param {WebhookEndpointModel} webhook + * @param {Object} options + * @void + */ + @action async editWebhook(webhook, options = {}) { + const { webhookEvents, groupedApiEvents, apiVersions, apiCredentials } = await this.loadWebhookOptions(); + + const formPermission = 'developers update webhook'; + this.modalsManager.show('modals/webhook-form', { + title: this.intl.t('developers.webhooks.index.edit-webhook-endpoint'), + acceptButtonText: this.intl.t('developers.webhooks.index.edit-webhook-endpoint-button-text'), + acceptButtonIcon: 'save', + acceptButtonDisabled: this.abilities.cannot(formPermission), + acceptButtonHelpText: this.abilities.cannot(formPermission) ? this.intl.t('common.unauthorized') : null, + formPermission, + declineButtonIcon: 'times', + declineButtonIconPrefix: 'fas', + eventOptions: groupedApiEvents, + versionOptions: apiVersions, + apiCredentialOptions: apiCredentials, + webhook, + setVersion: ({ target }) => { + webhook.version = target.value || null; + }, + setApiCredential: ({ target }) => { + webhook.api_credential_uuid = target.value || null; + }, + searchEvents: (query) => { + if (typeof query !== 'string') { + return; + } + const resources = Object.keys(groupedApiEvents); + const filteredEvents = {}; + resources.forEach((eventResource) => { + filteredEvents[eventResource] = groupedApiEvents[eventResource].filter((event) => { + return event.toLowerCase().includes(query.toLowerCase()); + }); + // if 0 events remove from filter + if (filteredEvents[eventResource].length === 0) { + delete filteredEvents[eventResource]; + } + }); + this.modalsManager.setOption('eventOptions', filteredEvents); + }, + addEvent: (event) => { + if (webhook.events.includes(event)) { + return; + } + + webhook.events.pushObject(event); + }, + removeEvent: (event) => { + webhook.events.removeObject(event); + }, + clearEvents: () => { + webhook.events.clear(); + }, + receiveAllEvents: () => { + webhook.events.pushObjects(webhookEvents); + }, + confirm: async (modal) => { + modal.startLoading(); + + if (this.abilities.cannot(formPermission)) { + return this.notifications.warning(this.intl.t('common.permissions-required-for-changes')); + } + + try { + await webhook.save(); + this.notifications.success(this.intl.t('developers.webhooks.index.new-webhook-success-message')); + return this.hostRouter.refresh(); + } catch (error) { + this.notifications.serverError(error); + modal.stopLoading(); + } + }, + ...options, + }); + } + + /** + * Toggles dialog to delete webhook + * + * @param {WebhookEndpointModel} webhook + * @param {Object} options + * @void + */ + @action deleteWebhook(webhook, options = {}) { + this.modalsManager.confirm({ + title: this.intl.t('developers.webhooks.index.delete-webhook-endpoint'), + body: this.intl.t('developers.webhooks.index.delete-webhook-endpoint-body'), + confirm: async (modal) => { + modal.startLoading(); + + try { + await webhook.destroyRecord(); + this.notifications.success(this.intl.t('developers.webhooks.index.delete-webhook-success-message')); + return this.hostRouter.refresh(); + } catch (error) { + this.notifications.serverError(error); + modal.stopLoading(); + } + }, + ...options, + }); + } +} diff --git a/app/services/api-key-actions.js b/app/services/api-key-actions.js new file mode 100644 index 0000000..1be5bde --- /dev/null +++ b/app/services/api-key-actions.js @@ -0,0 +1 @@ +export { default } from '@fleetbase/dev-engine/services/api-key-actions'; diff --git a/app/services/webhook-actions.js b/app/services/webhook-actions.js new file mode 100644 index 0000000..6cc10d7 --- /dev/null +++ b/app/services/webhook-actions.js @@ -0,0 +1 @@ +export { default } from '@fleetbase/dev-engine/services/webhook-actions';