mirror of
https://github.com/ansible/awx.git
synced 2026-01-12 10:30:03 -03:30
Merge pull request #1437 from jlmitch5/appCrudUi
implements application crud ui
This commit is contained in:
commit
04bc044340
@ -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;
|
||||
@ -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>
|
||||
32
awx/ui/client/features/applications/applications.strings.js
Normal file
32
awx/ui/client/features/applications/applications.strings.js
Normal 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;
|
||||
@ -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;
|
||||
325
awx/ui/client/features/applications/index.js
Normal file
325
awx/ui/client/features/applications/index.js
Normal 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;
|
||||
3
awx/ui/client/features/applications/index.view.html
Normal file
3
awx/ui/client/features/applications/index.view.html
Normal file
@ -0,0 +1,3 @@
|
||||
<div ui-view="edit"></div>
|
||||
<div ui-view="add"></div>
|
||||
<div ui-view="list"></div>
|
||||
@ -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;
|
||||
@ -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>
|
||||
@ -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;
|
||||
@ -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>
|
||||
@ -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
|
||||
]);
|
||||
|
||||
@ -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.')
|
||||
|
||||
@ -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 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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',
|
||||
};
|
||||
|
||||
51
awx/ui/client/lib/models/Application.js
Normal file
51
awx/ui/client/lib/models/Application.js
Normal 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;
|
||||
@ -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;
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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 = {};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user