Merge pull request #1437 from jlmitch5/appCrudUi

implements application crud ui
This commit is contained in:
John Mitchell 2018-03-13 18:07:10 -04:00 committed by GitHub
commit 04bc044340
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 959 additions and 5 deletions

View File

@ -0,0 +1,78 @@
function AddApplicationsController (models, $state, strings) {
const vm = this || {};
const { application, me, organization } = models;
const omit = [
'authorization_grant_type',
'client_id',
'client_secret',
'client_type',
'created',
'modified',
'related',
'skip_authorization',
'summary_fields',
'type',
'url',
'user'
];
vm.mode = 'add';
vm.strings = strings;
vm.panelTitle = strings.get('add.PANEL_TITLE');
vm.tab = {
details: { _active: true },
users: { _disabled: true }
};
vm.form = application.createFormSchema('post', { omit });
vm.form.organization = {
type: 'field',
label: 'Organization',
id: 'organization'
};
vm.form.description = {
type: 'String',
label: 'Description',
id: 'description'
};
vm.form.disabled = !application.isCreatable();
vm.form.organization._resource = 'organization';
vm.form.organization._route = 'applications.add.organization';
vm.form.organization._model = organization;
vm.form.organization._placeholder = strings.get('SELECT AN ORGANIZATION');
vm.form.name.required = true;
vm.form.organization.required = true;
vm.form.redirect_uris.required = true;
delete vm.form.name.help_text;
vm.form.save = data => {
const hiddenData = {
authorization_grant_type: 'implicit',
user: me.get('id'),
client_type: 'public'
};
const payload = _.merge(data, hiddenData);
return application.request('post', { data: payload });
};
vm.form.onSaveSuccess = res => {
$state.go('applications.edit', { application_id: res.data.id }, { reload: true });
};
}
AddApplicationsController.$inject = [
'resolvedModels',
'$state',
'ApplicationsStrings'
];
export default AddApplicationsController;

View File

@ -0,0 +1,29 @@
<at-panel>
<at-panel-heading>
{{ vm.panelTitle }}
</at-panel-heading>
<at-tab-group>
<at-tab state="vm.tab.details">{{:: vm.strings.get('tab.DETAILS') }}</at-tab>
<at-tab state="vm.tab.users">{{:: vm.strings.get('tab.USERS') }}</at-tab>
</at-tab-group>
<at-panel-body ng-if="!$state.current.name.includes('users')">
<at-form state="vm.form" autocomplete="off">
<at-input-text col="4" tab="1" state="vm.form.name"></at-input-text>
<at-input-text col="4" tab="2" state="vm.form.description"></at-input-text>
<at-input-lookup col="4" tab="3" state="vm.form.organization"></at-input-lookup>
<at-divider></at-divider>
<at-input-text col="4" tab="4" state="vm.form.redirect_uris"></at-input-text>
<at-action-group col="12" pos="right">
<at-form-action type="cancel" to="applications"></at-form-action>
<at-form-action type="save"></at-form-action>
</at-action-group>
</at-form>
</at-panel-body>
<at-panel-body ng-if="$state.current.name.includes('users')">
<div ui-view="userList"></div>
</at-panel-body>
</at-panel>

View File

@ -0,0 +1,32 @@
function ApplicationsStrings (BaseString) {
BaseString.call(this, 'applications');
const { t } = this;
const ns = this.applications;
ns.state = {
LIST_BREADCRUMB_LABEL: t.s('APPLICATIONS'),
ADD_BREADCRUMB_LABEL: t.s('CREATE APPLICATION'),
EDIT_BREADCRUMB_LABEL: t.s('EDIT APPLICATION'),
USER_LIST_BREADCRUMB_LABEL: t.s('TOKENS')
};
ns.tab = {
DETAILS: t.s('Details'),
USERS: t.s('Tokens')
};
ns.add = {
PANEL_TITLE: t.s('NEW APPLICATION')
};
ns.list = {
ROW_ITEM_LABEL_EXPIRED: t.s('EXPIRATION'),
ROW_ITEM_LABEL_ORGANIZATION: t.s('ORG'),
ROW_ITEM_LABEL_MODIFIED: t.s('LAST MODIFIED')
};
}
ApplicationsStrings.$inject = ['BaseStringService'];
export default ApplicationsStrings;

View File

@ -0,0 +1,115 @@
function EditApplicationsController (models, $state, strings, $scope) {
const vm = this || {};
const { me, application, organization } = models;
const omit = [
'authorization_grant_type',
'client_id',
'client_secret',
'client_type',
'created',
'modified',
'related',
'skip_authorization',
'summary_fields',
'type',
'url',
'user'
];
const isEditable = application.isEditable();
vm.mode = 'edit';
vm.strings = strings;
vm.panelTitle = application.get('name');
vm.tab = {
details: {
_active: true,
_go: 'applications.edit',
_params: { application_id: application.get('id') }
},
users: {
_go: 'applications.edit.users',
_params: { application_id: application.get('id') }
}
};
$scope.$watch('$state.current.name', (value) => {
if (/applications.edit.users/.test(value)) {
vm.tab.details._active = false;
vm.tab.users._active = true;
} else {
vm.tab.details._active = true;
vm.tab.users._active = false;
}
});
if (isEditable) {
vm.form = application.createFormSchema('put', { omit });
} else {
vm.form = application.createFormSchema({ omit });
vm.form.disabled = !isEditable;
}
vm.form.disabled = !isEditable;
const isOrgAdmin = _.some(me.get('related.admin_of_organizations.results'), (org) => org.id === organization.get('id'));
const isSuperuser = me.get('is_superuser');
const isCurrentAuthor = Boolean(application.get('summary_fields.created_by.id') === me.get('id'));
vm.form.organization = {
type: 'field',
label: 'Organization',
id: 'organization'
};
vm.form.description = {
type: 'String',
label: 'Description',
id: 'description'
};
vm.form.organization._resource = 'organization';
vm.form.organization._route = 'applications.edit.organization';
vm.form.organization._model = organization;
vm.form.organization._placeholder = strings.get('SELECT AN ORGANIZATION');
// TODO: org not returned via api endpoint, check on this
vm.form.organization._value = application.get('organization');
vm.form.organization._disabled = true;
if (isSuperuser || isOrgAdmin || (application.get('organization') === null && isCurrentAuthor)) {
vm.form.organization._disabled = false;
}
vm.form.name.required = true;
vm.form.organization.required = true;
vm.form.redirect_uris.required = true;
delete vm.form.name.help_text;
vm.form.save = data => {
const hiddenData = {
authorization_grant_type: 'implicit',
user: me.get('id'),
client_type: 'public'
};
const payload = _.merge(data, hiddenData);
return application.request('put', { data: payload });
};
vm.form.onSaveSuccess = () => {
$state.go('applications.edit', { application_id: application.get('id') }, { reload: true });
};
}
EditApplicationsController.$inject = [
'resolvedModels',
'$state',
'ApplicationsStrings',
'$scope'
];
export default EditApplicationsController;

View File

@ -0,0 +1,325 @@
import AddController from './add-applications.controller';
import EditController from './edit-applications.controller';
import ListController from './list-applications.controller';
import UserListController from './list-applications-users.controller';
import ApplicationsStrings from './applications.strings';
const MODULE_NAME = 'at.features.applications';
const addEditTemplate = require('~features/applications/add-edit-applications.view.html');
const listTemplate = require('~features/applications/list-applications.view.html');
const indexTemplate = require('~features/applications/index.view.html');
const userListTemplate = require('~features/applications/list-applications-users.view.html');
function ApplicationsDetailResolve ($q, $stateParams, Me, Application, Organization) {
const id = $stateParams.application_id;
const promises = {
me: new Me('get').then((me) => me.extend('get', 'admin_of_organizations'))
};
if (!id) {
promises.application = new Application('options');
promises.organization = new Organization();
return $q.all(promises);
}
promises.application = new Application(['get', 'options'], [id, id]);
return $q.all(promises)
.then(models => {
const orgId = models.application.get('organization');
const dependents = {
organization: new Organization('get', orgId)
};
return $q.all(dependents)
.then(related => {
models.organization = related.organization;
return models;
});
});
}
ApplicationsDetailResolve.$inject = [
'$q',
'$stateParams',
'MeModel',
'ApplicationModel',
'OrganizationModel'
];
function ApplicationsRun ($stateExtender, strings) {
$stateExtender.addState({
name: 'applications',
route: '/applications',
ncyBreadcrumb: {
label: strings.get('state.LIST_BREADCRUMB_LABEL')
},
data: {
activityStream: true,
// TODO: double-check activity stream works
activityStreamTarget: 'application'
},
views: {
'@': {
templateUrl: indexTemplate,
},
'list@applications': {
templateUrl: listTemplate,
controller: ListController,
controllerAs: 'vm'
}
},
searchPrefix: 'application',
resolve: {
resolvedModels: [
'ApplicationModel',
(Application) => {
const app = new Application(['options']);
return app;
}
],
Dataset: [
'$stateParams',
'Wait',
'GetBasePath',
'QuerySet',
($stateParams, Wait, GetBasePath, qs) => {
const searchParam = $stateParams.application_search;
const searchPath = GetBasePath('applications');
Wait('start');
return qs.search(searchPath, searchParam)
.finally(() => {
Wait('stop');
});
}
],
}
});
$stateExtender.addState({
name: 'applications.add',
route: '/add',
ncyBreadcrumb: {
label: strings.get('state.ADD_BREADCRUMB_LABEL')
},
data: {
activityStream: true,
// TODO: double-check activity stream works
activityStreamTarget: 'application'
},
views: {
'add@applications': {
templateUrl: addEditTemplate,
controller: AddController,
controllerAs: 'vm'
}
},
resolve: {
resolvedModels: ApplicationsDetailResolve
}
});
$stateExtender.addState({
name: 'applications.edit',
route: '/:application_id',
ncyBreadcrumb: {
label: strings.get('state.EDIT_BREADCRUMB_LABEL')
},
data: {
activityStream: true,
activityStreamTarget: 'application',
activityStreamId: 'application_id'
},
views: {
'edit@applications': {
templateUrl: addEditTemplate,
controller: EditController,
controllerAs: 'vm'
}
},
resolve: {
resolvedModels: ApplicationsDetailResolve
}
});
$stateExtender.addState({
name: 'applications.add.organization',
url: '/organization?selected',
searchPrefix: 'organization',
params: {
organization_search: {
value: {
page_size: 5,
order_by: 'name',
role_level: 'admin_role'
},
dynamic: true,
squash: ''
}
},
data: {
basePath: 'organizations',
formChildState: true
},
ncyBreadcrumb: {
skip: true
},
views: {
'organization@applications.add': {
templateProvider: (ListDefinition, generateList) => {
const html = generateList.build({
mode: 'lookup',
list: ListDefinition,
input_type: 'radio'
});
return `<lookup-modal>${html}</lookup-modal>`;
}
}
},
resolve: {
ListDefinition: ['OrganizationList', list => list],
Dataset: ['ListDefinition', 'QuerySet', '$stateParams', 'GetBasePath',
(list, qs, $stateParams, GetBasePath) => qs.search(
GetBasePath('organizations'),
$stateParams[`${list.iterator}_search`]
)
]
},
onExit ($state) {
if ($state.transition) {
$('#form-modal').modal('hide');
$('.modal-backdrop').remove();
$('body').removeClass('modal-open');
}
}
});
$stateExtender.addState({
name: 'applications.edit.organization',
url: '/organization?selected',
searchPrefix: 'organization',
params: {
organization_search: {
value: {
page_size: 5,
order_by: 'name',
role_level: 'admin_role'
},
dynamic: true,
squash: ''
}
},
data: {
basePath: 'organizations',
formChildState: true
},
ncyBreadcrumb: {
skip: true
},
views: {
'organization@applications.edit': {
templateProvider: (ListDefinition, generateList) => {
const html = generateList.build({
mode: 'lookup',
list: ListDefinition,
input_type: 'radio'
});
return `<lookup-modal>${html}</lookup-modal>`;
}
}
},
resolve: {
ListDefinition: ['OrganizationList', list => list],
Dataset: ['ListDefinition', 'QuerySet', '$stateParams', 'GetBasePath',
(list, qs, $stateParams, GetBasePath) => qs.search(
GetBasePath('organizations'),
$stateParams[`${list.iterator}_search`]
)
]
},
onExit ($state) {
if ($state.transition) {
$('#form-modal').modal('hide');
$('.modal-backdrop').remove();
$('body').removeClass('modal-open');
}
}
});
$stateExtender.addState({
name: 'applications.edit.users',
route: '/users',
ncyBreadcrumb: {
label: strings.get('state.USER_LIST_BREADCRUMB_LABEL'),
parent: 'applications.edit'
},
data: {
activityStream: true,
// TODO: double-check activity stream works
activityStreamTarget: 'application'
},
views: {
'userList@applications.edit': {
templateUrl: userListTemplate,
controller: UserListController,
controllerAs: 'vm'
}
},
params: {
user_search: {
value: {
order_by: 'user',
page_size: 20
},
dynamic: true
}
},
searchPrefix: 'user',
resolve: {
resolvedModels: [
'ApplicationModel',
(Application) => {
const app = new Application(['options']);
return app;
}
],
Dataset: [
'$stateParams',
'Wait',
'GetBasePath',
'QuerySet',
($stateParams, Wait, GetBasePath, qs) => {
const searchParam = $stateParams.user_search;
const searchPath = `${GetBasePath('applications')}${$stateParams.application_id}/tokens`;
Wait('start');
return qs.search(searchPath, searchParam)
.finally(() => {
Wait('stop');
});
}
],
}
});
}
ApplicationsRun.$inject = [
'$stateExtender',
'ApplicationsStrings'
];
angular
.module(MODULE_NAME, [])
.service('ApplicationsStrings', ApplicationsStrings)
.run(ApplicationsRun);
export default MODULE_NAME;

View File

@ -0,0 +1,3 @@
<div ui-view="edit"></div>
<div ui-view="add"></div>
<div ui-view="list"></div>

View File

@ -0,0 +1,55 @@
/** ***********************************************
* Copyright (c) 2018 Ansible, Inc.
*
* All Rights Reserved
************************************************ */
function ListApplicationsUsersController (
$filter,
$scope,
Dataset,
resolvedModels,
strings
) {
const vm = this || {};
// const application = resolvedModels;
vm.strings = strings;
// smart-search
const name = 'users';
const iterator = 'user';
const key = 'user_dataset';
$scope.list = { iterator, name, basePath: 'applications' };
$scope.collection = { iterator };
$scope[key] = Dataset.data;
vm.usersCount = Dataset.data.count;
$scope[name] = Dataset.data.results;
vm.getLastUsed = user => {
const lastUsed = _.get(user, 'last_used');
if (!lastUsed) {
return undefined;
}
let html = $filter('longDate')(lastUsed);
const { username, id } = _.get(user, 'summary_fields.last_used', {});
if (username && id) {
html += ` by <a href="/#/users/${id}">${$filter('sanitize')(username)}</a>`;
}
return html;
};
}
ListApplicationsUsersController.$inject = [
'$filter',
'$scope',
'Dataset',
'resolvedModels',
'ApplicationsStrings'
];
export default ListApplicationsUsersController;

View File

@ -0,0 +1,40 @@
<div class="at-List-toolbar">
<smart-search
class="at-List-search"
django-model="users"
base-path="users"
iterator="user"
list="list"
dataset="user_dataset"
collection="collection"
search-tags="searchTags">
</smart-search>
</div>
<at-list results="users">
<at-row ng-repeat="user in users">
<div class="at-Row-items">
<at-row-item
header-value="{{ user.summary_fields.user.username }}"
header-link="/#/users/{{ user.summary_fields.user.id }}">
</at-row-item>
<at-row-item
label-value="{{:: vm.strings.get('list.ROW_ITEM_LABEL_DESCRIPTION') }}"
value="{{ user.description }}">
</at-row-item>
<at-row-item
label-value="{{:: vm.strings.get('list.ROW_ITEM_LABEL_ORGANIZATION') }}"
value="{{ user.summary_fields.organization.name }}"
value-link="/#/organizations/{{ user.summary_fields.organization.id }}">
</at-row-item>
<at-row-item
label-value="{{:: vm.strings.get('list.ROW_ITEM_LABEL_EXPIRED') }}"
value="{{ user.expriation | longDate }}">
</at-row-item>
<at-row-item
label-value="{{:: vm.strings.get('list.ROW_ITEM_LABEL_MODIFIED') }}"
value="{{ vm.getLastUsed(user) }}">
</at-row-item>
</div>
</at-row>
</at-list>

View File

@ -0,0 +1,117 @@
/** ***********************************************
* Copyright (c) 2018 Ansible, Inc.
*
* All Rights Reserved
************************************************ */
function ListApplicationsController (
$filter,
$scope,
$state,
Dataset,
ProcessErrors,
Prompt,
resolvedModels,
strings,
Wait,
) {
const vm = this || {};
const application = resolvedModels;
vm.strings = strings;
vm.activeId = $state.params.application_id;
$scope.canAdd = application.options('actions.POST');
// smart-search
const name = 'applications';
const iterator = 'application';
const key = 'application_dataset';
$scope.list = { iterator, name, basePath: 'applications' };
$scope.collection = { iterator };
$scope[key] = Dataset.data;
vm.applicationsCount = Dataset.data.count;
$scope[name] = Dataset.data.results;
$scope.$on('updateDataset', (e, dataset) => {
$scope[key] = dataset;
$scope[name] = dataset.results;
vm.applicationsCount = dataset.count;
});
vm.getModified = app => {
const modified = _.get(app, 'modified');
if (!modified) {
return undefined;
}
let html = $filter('longDate')(modified);
const { username, id } = _.get(app, 'summary_fields.modified_by', {});
if (username && id) {
html += ` by <a href="/#/users/${id}">${$filter('sanitize')(username)}</a>`;
}
return html;
};
vm.deleteApplication = (app) => {
const action = () => {
$('#prompt-modal').modal('hide');
Wait('start');
application.request('delete', app.id)
.then(() => {
let reloadListStateParams = null;
if ($scope.applications.length === 1 && $state.params.application_search &&
!_.isEmpty($state.params.application_search.page) &&
$state.params.application_search.page !== '1') {
const page = `${(parseInt(reloadListStateParams
.application_search.page, 10) - 1)}`;
reloadListStateParams = _.cloneDeep($state.params);
reloadListStateParams.application_search.page = page;
}
if (parseInt($state.params.application_id, 10) === app.id) {
$state.go('^', reloadListStateParams, { reload: true });
} else {
$state.go('.', reloadListStateParams, { reload: true });
}
})
.catch(({ data, status }) => {
ProcessErrors($scope, data, status, null, {
hdr: strings.get('error.HEADER'),
msg: strings.get('error.CALL', { path: `${application.path}${app.id}`, status })
});
})
.finally(() => {
Wait('stop');
});
};
const deleteModalBody = `<div class="Prompt-bodyQuery">${strings.get('deleteResource.CONFIRM', 'application')}</div>`;
Prompt({
hdr: strings.get('deleteResource.HEADER'),
resourceName: $filter('sanitize')(app.name),
body: deleteModalBody,
action,
actionText: 'DELETE'
});
};
}
ListApplicationsController.$inject = [
'$filter',
'$scope',
'$state',
'Dataset',
'ProcessErrors',
'Prompt',
'resolvedModels',
'ApplicationsStrings',
'Wait'
];
export default ListApplicationsController;

View File

@ -0,0 +1,69 @@
<at-panel>
<at-panel-heading hide-dismiss="true">
APPLICATIONS
<span class="badge List-titleBadge">
{{ vm.applicationsCount }}
</span>
</at-panel-heading>
<at-panel-body>
<div class="at-List-toolbar">
<smart-search
class="at-List-search"
django-model="applications"
base-path="applications"
iterator="application"
list="list"
dataset="application_dataset"
collection="collection"
search-tags="searchTags">
</smart-search>
<div class="at-List-toolbarAction">
<button
type="button"
ui-sref="applications.add"
class="at-Button--add"
aria-haspopup="true"
aria-expanded="false">
</button>
</div>
</div>
<at-list results="applications">
<at-row ng-repeat="application in applications"
ng-class="{'at-Row--active': (application.id === vm.activeId)}">
<div class="at-Row-items">
<at-row-item
header-value="{{ application.name }}"
header-link="/#/applications/{{ application.id }}">
</at-row-item>
<at-row-item
label-value="{{:: vm.strings.get('list.ROW_ITEM_LABEL_ORGANIZATION') }}"
value="{{ application.summary_fields.organization.name }}"
value-link="/#/organizations/{{ application.summary_fields.organization.id }}">
</at-row-item>
<at-row-item
label-value="{{:: vm.strings.get('list.ROW_ITEM_LABEL_MODIFIED') }}"
value="{{ vm.getModified(application) }}">
</at-row-item>
</div>
<div class="at-Row-actions">
<!-- TODO: delete application should be based on user capabilities but that's not
implemented in the api yet -->
<at-row-action icon="fa-trash" ng-click="vm.deleteApplication(application)">
</at-row-action>
<!-- <at-row-action icon="fa-trash" ng-click="vm.deleteApplication(application)"
ng-show="application.summary_fields.user_capabilities.delete">
</at-row-action> -->
</div>
</at-row>
</at-list>
</at-panel-body>
<paginate
base-path="applications"
iterator="application"
dataset="application_dataset"
collection="applications"
query-set="application_queryset">
</paginate>
</at-panel>

View File

@ -2,6 +2,7 @@ import atLibServices from '~services';
import atLibComponents from '~components';
import atLibModels from '~models';
import atFeaturesApplications from '~features/applications';
import atFeaturesCredentials from '~features/credentials';
import atFeaturesTemplates from '~features/templates';
@ -11,6 +12,7 @@ angular.module(MODULE_NAME, [
atLibServices,
atLibComponents,
atLibModels,
atFeaturesApplications,
atFeaturesCredentials,
atFeaturesTemplates
]);

View File

@ -73,6 +73,7 @@ function ComponentsStrings (BaseString) {
MANAGEMENT_JOBS: t.s('Management Jobs'),
INSTANCES: t.s('Instances'),
INSTANCE_GROUPS: t.s('Instance Groups'),
APPLICATIONS: t.s('Applications'),
SETTINGS: t.s('Settings'),
FOOTER_ABOUT: t.s('About'),
FOOTER_COPYRIGHT: t.s('Copyright © 2017 Red Hat, Inc.')

View File

@ -110,6 +110,10 @@
padding: @at-padding-side-nav-item-icon;
}
i.fa-cubes {
margin-left: -4px;
}
&:hover,
&.is-active {
background: @at-color-side-nav-item-background-hover;
@ -119,12 +123,16 @@
color: @at-color-side-nav-content;
margin-left: @at-highlight-left-border-margin-makeup;
}
i.fa-cubes {
margin-left: -9px;
}
}
}
.at-Layout-sideNavSpacer {
background: inherit;
height: @at-height-side-nav-spacer;
height: 5px;
}
&--expanded {
@ -213,4 +221,4 @@
}
}
}
}
}

View File

@ -71,6 +71,9 @@
system-admin-only="true">
</at-side-nav-item>
<div class="at-Layout-sideNavSpacer"></div>
<at-side-nav-item icon-class="fa-cubes" route="applications" name="APPLICATIONS"
system-admin-only="true">
</at-side-nav-item>
<at-side-nav-item icon-class="fa-cog" route="configuration" name="SETTINGS"
system-admin-only="true">
</at-side-nav-item>

View File

@ -1,6 +1,10 @@
const templateUrl = require('~components/list/list.partial.html');
// TODO: figure out emptyListReason scope property
function atListLink (scope, element, attrs) {
if (!attrs.results) {
throw new Error('at-list directive requires results attr to set up the empty list properly');
}
}
function AtListController (strings) {
this.strings = strings;
@ -17,6 +21,7 @@ function atList () {
scope: {
results: '=',
},
link: atListLink,
controller: AtListController,
controllerAs: 'vm',
};

View File

@ -0,0 +1,51 @@
let Base;
function createFormSchema (method, config) {
if (!config) {
config = method;
method = 'GET';
}
const schema = Object.assign({}, this.options(`actions.${method.toUpperCase()}`));
if (config && config.omit) {
config.omit.forEach(key => delete schema[key]);
}
Object.keys(schema).forEach(key => {
schema[key].id = key;
if (this.has(key)) {
schema[key]._value = this.get(key);
}
});
return schema;
}
function setDependentResources () {
this.dependentResources = [];
}
function ApplicationModel (method, resource, config) {
// TODO: change to applications
Base.call(this, 'applications');
this.Constructor = ApplicationModel;
this.createFormSchema = createFormSchema.bind(this);
this.setDependentResources = setDependentResources.bind(this);
return this.create(method, resource, config);
}
function ApplicationModelLoader (BaseModel) {
Base = BaseModel;
return ApplicationModel;
}
ApplicationModelLoader.$inject = [
'BaseModel',
];
export default ApplicationModelLoader;

View File

@ -361,7 +361,11 @@ function normalizePath (resource) {
}
function isEditable () {
const canEdit = this.get('summary_fields.user_capabilities.edit');
let canEdit = this.get('summary_fields.user_capabilities.edit');
if (canEdit === undefined) {
canEdit = true;
}
if (canEdit) {
return true;

View File

@ -1,5 +1,6 @@
import atLibServices from '~services';
import Application from '~models/Application';
import Base from '~models/Base';
import Config from '~models/Config';
import Credential from '~models/Credential';
@ -28,6 +29,7 @@ angular
.module(MODULE_NAME, [
atLibServices
])
.service('ApplicationModel', Application)
.service('BaseModel', Base)
.service('ConfigModel', Config)
.service('CredentialModel', Credential)

View File

@ -316,8 +316,23 @@ angular
activateTab();
});
$transitions.onSuccess({}, function(trans) {
$transitions.onCreate({}, function(trans) {
console.log('$onCreate ' +trans.to().name);
});
$transitions.onBefore({}, function(trans) {
console.log('$onBefore ' +trans.to().name);
});
$transitions.onError({}, function(trans) {
console.log('$onError ' +trans.to().name);
});
$transitions.onExit({}, function(trans) {
console.log('$onExit ' +trans.to().name);
});
$transitions.onSuccess({}, function(trans) {
console.log('$onSuccess ' +trans.to().name);
if(trans.to() === trans.from()) {
// check to see if something other than a search param has changed
let toParamsWithoutSearchKeys = {};