@fleetbase/dev-engine
Version:
Fleetbase Developers extension provides a module for managing developer resources such as API keys, webhooks, sockets, events and logs.
516 lines (474 loc) • 16.7 kB
JavaScript
import Controller from '@ember/controller';
import { tracked } from '@glimmer/tracking';
import { inject as service } from '@ember/service';
import { action, computed } from '@ember/object';
import { isBlank } from '@ember/utils';
import { not } from '@ember/object/computed';
import { timeout } from 'ember-concurrency';
import { later } from '@ember/runloop';
import { task } from 'ember-concurrency-decorators';
import { format as formatDate } from 'date-fns';
import getWithDefault from '@fleetbase/ember-core/utils/get-with-default';
export default class ApiKeysIndexController extends Controller {
currentUser;
intl;
modalsManager;
notifications;
store;
crud;
fetch;
theme;
hostRouter;
universe;
abilities;
/**
* Queryable parameters for this controller's model
*
* @var {Array}
*/
queryParams = ['page', 'limit', 'sort'];
/**
* Expiration options for api keys
*
* @var {Array}
*/
expirationOptions = ['never', 'immediately', 'in 1 hour', 'in 24 hours', 'in 3 days', 'in 7 days'];
/**
* Tracks the current console environment mode
*
* @memberof ApiKeysIndexController
*/
testMode = false;
/**
* The current page of data being viewed
*
* @var {Integer}
*/
page = 1;
/**
* The maximum number of items to show per page
*
* @var {Integer}
*/
limit;
/**
* The param to sort the data on, the param with prepended `-` is descending
*
* @var {String}
*/
sort;
/**
* The param to query the data by
*
* @var {String}
*/
query;
/**
* Checks if console environment is in live mode
*
* @memberof ApiKeysIndexController
*/
isLiveMode;
/**
* Checks if console environment is in test mode
*
* @memberof ApiKeysIndexController
*/
get isTestMode() {
return this.testMode === true;
}
/**
* gets the user selected key to view data from
*
* @memberof ApiKeysIndexController
*/
get testKey() {
return this.currentUser.getOption('testKey');
}
/**
* Columns for table component.
*
* @var {Array}
*/
columns = [
{
label: this.intl.t('developers.common.name'),
valuePath: 'name',
cellComponent: 'table/cell/anchor',
permission: 'developers view api-key',
action: this.editApiKey,
resizable: true,
width: '10%',
sortable: false,
},
{
label: this.intl.t('developers.api-keys.index.public-key'),
valuePath: 'key',
width: '18%',
sortable: false,
resizable: true,
cellComponent: 'click-to-copy',
},
{
label: this.intl.t('developers.api-keys.index.secret-key'),
valuePath: 'secret',
width: '18%',
sortable: false,
resizable: true,
cellComponent: 'click-to-reveal',
cellComponentArgs: {
clickToCopy: true,
},
},
{
label: this.intl.t('developers.api-keys.index.enviroment'),
valuePath: 'environment',
width: '10%',
sortable: false,
resizable: true,
cellComponent: 'table/cell/status',
},
{
label: this.intl.t('developers.api-keys.index.expiry'),
valuePath: 'expiresAt',
sortable: false,
width: '8%',
tooltip: true,
resizable: true,
cellClassNames: 'overflow-visible',
},
{
label: this.intl.t('developers.api-keys.index.last-used'),
valuePath: 'lastUsed',
sortable: false,
width: '14%',
tooltip: true,
resizable: true,
cellClassNames: 'overflow-visible',
},
{
label: this.intl.t('developers.common.created'),
valuePath: 'createdAt',
sortable: false,
width: '14%',
tooltip: true,
resizable: true,
cellClassNames: 'overflow-visible',
},
{
label: '',
cellComponent: 'table/cell/dropdown',
ddButtonText: false,
ddButtonIcon: 'ellipsis-h',
ddButtonIconPrefix: 'fas',
ddMenuLabel: 'API Key Actions',
cellClassNames: 'overflow-visible',
wrapperClass: 'flex items-center justify-end mx-2',
width: '10%',
align: 'right',
actions: [
{
label: this.intl.t('developers.api-keys.index.edit-key'),
fn: this.editApiKey,
permission: 'developers view api-key',
},
{
label: this.intl.t('developers.api-keys.index.roll-key'),
fn: this.rollApiKey,
permission: 'developers roll api-key',
},
{
label: this.intl.t('developers.api-keys.index.view-logs'),
fn: this.viewRequestLogs,
permission: 'developers view log',
},
{
label: this.intl.t('developers.api-keys.index.delete-key'),
fn: this.deleteApiKey,
className: 'text-red-700 hover:text-red-800',
permission: 'developers delete api-key',
},
],
},
];
/**
* The search task.
*
* @void
*/
*search({ target: { value } }) {
// if no query don't search
if (isBlank(value)) {
this.query = null;
return;
}
// timeout for typing
yield timeout(250);
// reset page for results
if (this.page > 1) {
this.page = 1;
}
// update the query param
this.query = value;
}
/**
* Toggle test mode
*
* @void
*/
toggleTestMode(testMode = false) {
this.currentUser.setOption('sandbox', testMode);
this.testMode = testMode;
this.theme.setEnvironment();
this.store.unloadAll();
this.hostRouter.refresh().then(() => {
this.currentUser.setOption('testKey', this.model?.firstObject?.key);
});
}
/**
* Toggle test key
*
* @void
*/
toggleTestKey({ target: { value } }) {
if (isBlank(value)) {
this.currentUser.setOption('testKey', null);
return;
}
this.currentUser.setOption('testKey', value);
}
/**
* Toggles modal to create a new API key
*
* @void
*/
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();
}
},
});
}
/**
* Toggles modal to create a new API key
*
* @void
*/
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,
});
}
/**
* Toggles modal to create a new API key
*
* @void
*/
renameApiKey(apiKey) {
const formPermission = 'developers update api-key';
const apiKeyName = getWithDefault(apiKey, 'name', this.intl.t('developers.api-keys.index.untitled'));
this.modalsManager.show('modals/rename-api-key-form', {
title: this.intl.t('developers.api-keys.index.rename-api-key-title', { apiKeyName }),
acceptButtonDisabled: this.abilities.cannot(formPermission),
acceptButtonHelpText: this.abilities.cannot(formPermission) ? this.intl.t('common.unauthorized') : null,
apiKey,
formPermission,
confirm: async (modal) => {
modal.startLoading();
try {
await apiKey.save();
this.notifications.success(this.intl.t('developers.api-keys.index.rename-api-key-success-message', { apiKeyName }));
modal.done();
} catch (error) {
this.notifications.serverError(error);
modal.stopLoading();
}
},
});
}
/**
* Toggles dialog to delete API key
*
* @void
*/
deleteApiKey(apiKey) {
const apiKeyName = getWithDefault(apiKey, 'name', this.intl.t('developers.api-keys.index.untitled'));
this.modalsManager.confirm({
title: this.intl.t('developers.api-keys.index.delete-api-key-title', { apiKeyName }),
body: this.intl.t('developers.api-keys.index.delete-api-key-body'),
confirm: async (modal) => {
modal.startLoading();
try {
await apiKey.destroyRecord();
this.notifications.success(this.intl.t('developers.api-keys.index.delete-api-key-title', { apiKeyName }));
return this.hostRouter.refresh();
} catch (error) {
this.notifications.serverError(error);
modal.stopLoading();
}
},
});
}
/**
* Bulk deletes selected `api-credential` via confirm prompt
*
* @param {Array} selected an array of selected models
* @void
*/
bulkDeleteApiCredentials() {
const selected = this.table.selectedRows;
this.crud.bulkDelete(selected, {
acceptButtonText: this.intl.t('developers.api-keys.index.delete-accept-button-text'),
onSuccess: () => {
this.hostRouter.refresh();
},
});
}
/**
* Toggles dialog to roll API key
*
* @void
*/
rollApiKey(apiKey) {
const formPermission = 'developers roll api-key';
const apiKeyName = getWithDefault(apiKey, 'name', this.intl.t('developers.api-keys.index.untitled'));
this.modalsManager.show('modals/roll-api-key-form', {
title: this.intl.t('developers.api-keys.index.roll-api-key', { apiKeyName }),
modalClass: 'roll-key-modal',
acceptButtonText: this.intl.t('developers.api-keys.index.roll-api-key-button-text'),
acceptButtonDisabled: this.abilities.cannot(formPermission),
acceptButtonHelpText: this.abilities.cannot(formPermission) ? this.intl.t('common.unauthorized') : null,
user: this.currentUser.user,
expirationOptions: this.expirationOptions,
setExpiration: ({ target }) => {
apiKey.expires_at = target.value || null;
},
viewRequestLogs: this.viewRequestLogs,
password: null,
formPermission,
apiKey,
confirm: async (modal) => {
modal.startLoading();
try {
await this.fetch.patch(
`api-credentials/roll/${apiKey.id}`,
{ password: modal.getOption('password'), expiration: apiKey.get('expires_at') },
{ normalizeToEmberData: true }
);
this.notifications.success(this.intl.t('developers.api-keys.index.roll-api-key-success-message', { apiKeyName }));
modal.done();
} catch (error) {
this.notifications.serverError(error, this.intl.t('developers.api-keys.index.roll-api-key-error-message'));
modal.stopLoading();
}
},
});
}
/**
* Redirects user to request logs for this api key
*
* @void
*/
viewRequestLogs(apiKey) {
return this.hostRouter.transitionTo('console.developers.logs.index', {
queryParams: { key: apiKey.id },
});
}
/**
* Toggles dialog to export API credentials
*
* @void
*/
exportApiKeys() {
this.modalsManager.show('modals/export-form', {
title: this.intl.t('developers.api-keys.index.export-api'),
acceptButtonText: this.intl.t('developers.api-keys.index.export-api-accept-button-text'),
formatOptions: ['csv', 'xlsx', 'xls', 'html', 'pdf'],
format: 'xlsx',
setFormat: ({ target }) => {
this.modalsManager.setOption('format', target.value || null);
},
confirm: async (modal) => {
modal.startLoading();
const format = modal.getOption('format', 'xlsx');
try {
await this.fetch.download(
`api-credentials/export`,
{
format,
},
{
fileName: `api-credentials-${formatDate(new Date(), 'yyyy-MM-dd-HH:mm')}.${format}`,
}
);
later(
this,
() => {
return modal.done();
},
600
);
} catch (error) {
this.notifications.serverError(error, this.intl.t('developers.api-keys.index.export-api-error-message'));
modal.stopLoading();
}
},
});
}
/**
* Reload data.
*/
reload() {
return this.hostRouter.refresh();
}
}