Merge "Allow wiring of <hz-dynamic-table> into <transfer-table>"
This commit is contained in:
commit
5401d9245a
|
@ -46,7 +46,7 @@ module.exports = function (config) {
|
|||
// NOTE: the templates must also be listed in the files section below.
|
||||
'./**/*.html': ['ng-html2js'],
|
||||
// Used to indicate files requiring coverage reports.
|
||||
'./**/!(*.spec).js': ['coverage']
|
||||
'./**/!(*.spec|*.borrowed-from-underscore).js': ['coverage']
|
||||
},
|
||||
|
||||
// Sets up module to process templates.
|
||||
|
|
|
@ -0,0 +1,93 @@
|
|||
/**
|
||||
* Copyright 2016, Mirantis, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License. You may obtain
|
||||
* a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
angular
|
||||
.module('horizon.framework.util.filters')
|
||||
.factory('horizon.framework.util.filters.$memoize', $memoize);
|
||||
|
||||
/**
|
||||
* @ngdoc factory
|
||||
* @name horizon.framework.util.filters.$memoize
|
||||
* @module horizon.framework.util.filters
|
||||
* @kind function
|
||||
* @description
|
||||
*
|
||||
* Provides a decorator service to memoize results of function calls.
|
||||
*
|
||||
*/
|
||||
function $memoize() {
|
||||
/**
|
||||
* Memoizes a given function by caching the computed result. Useful for
|
||||
* speeding up slow-running computations. If passed an optional hashFunction,
|
||||
* it will be used to compute the hash key for storing the result, based on
|
||||
* the arguments to the original function. The default hashFunction just uses
|
||||
* the first argument to the memoized function as the key. The cache of
|
||||
* memoized values is available as the cache property on the returned
|
||||
* function.
|
||||
*
|
||||
* @param {function} func
|
||||
* The function calls to which are need to be memoized (i.e., cached).
|
||||
*
|
||||
* @param {function} hasher
|
||||
* Function which is used to calculate a key under which the memoized result
|
||||
* is stored in cache. Can be omitted for functions that take only a single
|
||||
* argument of a scalar type (string, number, boolean). For any function that
|
||||
* takes at least one argument of {array} or {object} type, or more than one
|
||||
* argument providing this function is crucial. Hasher function should
|
||||
* provide unique keys for a set of input arguments which produce unique
|
||||
* output.
|
||||
*
|
||||
* @return {function}
|
||||
* The decorated version of function func, which calls are cached.
|
||||
*
|
||||
* @example
|
||||
* ```
|
||||
* function getFactorials(numbers) {
|
||||
* if (!angular.isArray(numbers)) {
|
||||
* return 0;
|
||||
* } else {
|
||||
* return numbers.map(function(number) {
|
||||
* var acc = 1;
|
||||
* for (var n = number; n > 0; n--) {
|
||||
* acc *= n;
|
||||
* }
|
||||
* return acc;
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* function hasher(numbers) {
|
||||
* return numbers.join(',')
|
||||
* }
|
||||
*
|
||||
* var memoizedGetFactorials = $memoize(getFactorials, hasher);
|
||||
*/
|
||||
return function(func, hasher) {
|
||||
var memoize = function(key) {
|
||||
var cache = memoize.cache;
|
||||
var address = '' + (hasher ? hasher.apply(this, arguments) : key);
|
||||
if (!cache.hasOwnProperty(address)) {
|
||||
cache[address] = func.apply(this, arguments);
|
||||
}
|
||||
return cache[address];
|
||||
};
|
||||
memoize.cache = {};
|
||||
return memoize;
|
||||
};
|
||||
}
|
||||
}());
|
|
@ -48,6 +48,8 @@
|
|||
* selectAll {boolean} set to true if you want to enable select all checkbox
|
||||
* expand {boolean} set to true if you want to inline details
|
||||
* trackId {string} passed into ngRepeat's track by to identify objects
|
||||
* noItemsMessage {string} message to be displayed when the table is empty. If
|
||||
* not provided, the default message is used.
|
||||
* columns {Array} of objects to describe each column. Each object
|
||||
* requires: 'id', 'title', 'priority' (responsive priority when table resized)
|
||||
* optional: 'sortDefault', 'filters' (to apply to the column cells),
|
||||
|
@ -103,16 +105,20 @@
|
|||
resultHandler: '=?'
|
||||
},
|
||||
templateUrl: basePath + 'table/hz-dynamic-table.html',
|
||||
link: link
|
||||
link: {
|
||||
pre: preLink,
|
||||
post: postLink
|
||||
}
|
||||
};
|
||||
|
||||
return directive;
|
||||
|
||||
function link(scope) {
|
||||
function preLink(scope) {
|
||||
scope.items = [];
|
||||
}
|
||||
|
||||
function postLink(scope) {
|
||||
// if selectAll and expand are not set in the config, default set to true
|
||||
|
||||
if (angular.isUndefined(scope.config.selectAll)) {
|
||||
scope.config.selectAll = true;
|
||||
}
|
||||
|
|
|
@ -93,7 +93,7 @@
|
|||
</hz-detail-row>
|
||||
</td>
|
||||
</tr>
|
||||
<tr hz-no-items items="items"></tr>
|
||||
<tr hz-no-items message="config.noItemsMessage" items="items"></tr>
|
||||
</tbody>
|
||||
|
||||
<!--
|
||||
|
|
|
@ -123,6 +123,19 @@
|
|||
expect($element.find('tbody tr:eq(2) td:eq(4)').text()).toContain('mice');
|
||||
});
|
||||
|
||||
it('displays the default no items message if noItemsMessage is not set', function() {
|
||||
$scope.safeTableData = [];
|
||||
var $element = digestMarkup($scope, $compile, markup);
|
||||
expect($element.find('tbody td.no-rows-help').text()).toBe('No items to display.');
|
||||
});
|
||||
|
||||
it('displays a custom no items message if noItemsMessage is provided', function() {
|
||||
$scope.safeTableData = [];
|
||||
$scope.config.noItemsMessage = 'A sample message';
|
||||
var $element = digestMarkup($scope, $compile, markup);
|
||||
expect($element.find('tbody td.no-rows-help').text()).toBe('A sample message');
|
||||
});
|
||||
|
||||
it('has no search or action buttons if none configured', function() {
|
||||
var $element = digestMarkup($scope, $compile, markup);
|
||||
expect($element.find('.hz-dynamic-table-preamble').length).toBe(1);
|
||||
|
|
|
@ -0,0 +1,112 @@
|
|||
/**
|
||||
* Copyright 2016, Mirantis, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License. You may obtain
|
||||
* a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
angular
|
||||
.module('horizon.framework.widgets.transfer-table')
|
||||
.filter('filterAvailable', filterAvailable);
|
||||
|
||||
filterAvailable.$inject = ['horizon.framework.util.filters.$memoize'];
|
||||
/**
|
||||
* @ngdoc filter
|
||||
* @name filterAvailable
|
||||
*
|
||||
* @param {array} available
|
||||
* List of objects being filtered.
|
||||
*
|
||||
* @param {object} allocatedKeys
|
||||
* Dictionary with object keys that should be excluded from filtered output.
|
||||
*
|
||||
* @param {string} primaryKey (Optional)
|
||||
* Attribute name to use as primary key, defaults to 'id'.
|
||||
*
|
||||
* @returns {array}
|
||||
* Filtered list of objects whose keys are NOT present in the dictionary.
|
||||
*
|
||||
* @description
|
||||
*
|
||||
* The filter works nicely when used inside ng-repeat directive and does not
|
||||
* lead to an infinite digest loop error, thanks to memoizing its results on
|
||||
* both the initial list, keys dictionary and key name. For more details see
|
||||
* http://stackoverflow.com/a/24213626/4414610
|
||||
* Since the filter cache is shared between filter invocations in different
|
||||
* contexts, one must namespace entities key values. Consider the following
|
||||
* example: there are, two security groups with keys 'test1' and 'test2' and
|
||||
* two key pairs with same keys, the combined key for both datasets will be
|
||||
* the same. Consequently we will get key pairs in a filter output while
|
||||
* expecting security groups, or vice versa. To avoid subtle bugs like that
|
||||
* entity keys must be namespaced; a good id of a key pair from Launch Instance
|
||||
* wizard transfer table would be 'li_keypair:<keypair_name>'.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* var available = [{
|
||||
* id: 1,
|
||||
* attr: 'one'
|
||||
* }, {
|
||||
* id: 2,
|
||||
* attr: 'two'
|
||||
* }, {
|
||||
* id: 3,
|
||||
* attr: 'three'
|
||||
* }]
|
||||
*
|
||||
* console.log(filterAvailable(available, [1]))
|
||||
* console.log(filterAvailable(available, ['one'], 'attr')) // same result as above
|
||||
*
|
||||
*/
|
||||
function filterAvailable($memoize) {
|
||||
return $memoize($filterAvailable, $hasher);
|
||||
|
||||
function $idKeyOrDefault(primaryKey) {
|
||||
return primaryKey || 'id';
|
||||
}
|
||||
|
||||
function arrayIsEmpty(array) {
|
||||
return angular.isUndefined(array) || !array.length;
|
||||
}
|
||||
|
||||
function emptyObj(obj) {
|
||||
return angular.isUndefined(obj) || !Object.keys(obj).length;
|
||||
}
|
||||
|
||||
function $hasher(available, allocatedIds, primaryKey) {
|
||||
if (arrayIsEmpty(available)) {
|
||||
return '';
|
||||
}
|
||||
primaryKey = $idKeyOrDefault(primaryKey);
|
||||
var key = available.map(function(item) {
|
||||
return item[primaryKey];
|
||||
}).sort().join('_');
|
||||
return key + '_' + Object.keys(allocatedIds).sort().join('_');
|
||||
}
|
||||
|
||||
function $filterAvailable(available, allocatedKeys, primaryKey) {
|
||||
if (arrayIsEmpty(available)) {
|
||||
return [];
|
||||
} else if (emptyObj(allocatedKeys)) {
|
||||
return available;
|
||||
}
|
||||
primaryKey = $idKeyOrDefault(primaryKey);
|
||||
return available.filter(function isItemAvailable(item) {
|
||||
return !(item[primaryKey] in allocatedKeys);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
})();
|
|
@ -0,0 +1,89 @@
|
|||
/**
|
||||
* Copyright 2016, Mirantis, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License. You may obtain
|
||||
* a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
describe('filterAvailable filter', function () {
|
||||
var filterAvailable;
|
||||
var input = [{
|
||||
id: 'one', content: 'item1'
|
||||
}, {
|
||||
id: 'two', content: 'item2'
|
||||
}, {
|
||||
id: 'three', content: 'item3'
|
||||
}];
|
||||
var ids = {
|
||||
'two': true
|
||||
};
|
||||
var filtered = [{
|
||||
id: 'one', content: 'item1'
|
||||
}, {
|
||||
id: 'three', content: 'item3'
|
||||
}];
|
||||
|
||||
beforeEach(function() {
|
||||
module('horizon.framework.widgets.transfer-table');
|
||||
module('horizon.framework.util.filters');
|
||||
inject(function(_$filter_) {
|
||||
filterAvailable = _$filter_('filterAvailable');
|
||||
});
|
||||
});
|
||||
|
||||
it('is defined', function() {
|
||||
expect(filterAvailable).toBeDefined();
|
||||
});
|
||||
|
||||
it('returns an empty list for empty input', function() {
|
||||
expect(filterAvailable([])).toEqual([]);
|
||||
expect(filterAvailable(undefined)).toEqual([]);
|
||||
});
|
||||
|
||||
it('returns the same list if ids dictionary is empty', function() {
|
||||
expect(filterAvailable(input, {})).toEqual(input);
|
||||
});
|
||||
|
||||
it('subsequent applications to the untouched output are idempotent', function() {
|
||||
var output = filterAvailable(input, {});
|
||||
expect(filterAvailable(output, {})).toBe(output);
|
||||
});
|
||||
|
||||
it('id mentioned in a dictionary is removed from output', function() {
|
||||
expect(filterAvailable(input, ids)).toEqual(filtered);
|
||||
});
|
||||
|
||||
it('two successive calls with same args return the same value', function() {
|
||||
var output = filterAvailable(input, ids);
|
||||
expect(filterAvailable(input, ids)).toBe(output);
|
||||
});
|
||||
|
||||
it('calls on the filtered output after the second call are idempotent', function() {
|
||||
var output = filterAvailable(input, ids);
|
||||
var output2 = filterAvailable(output, ids);
|
||||
expect(output2).not.toBe(output);
|
||||
expect(filterAvailable(output2, ids)).toBe(output2);
|
||||
});
|
||||
|
||||
it('third argument changes ids dictionary interpretation', function() {
|
||||
expect(filterAvailable(input, ids, 'content')).not.toEqual(filtered);
|
||||
});
|
||||
|
||||
it('third argument default value is "id"', function() {
|
||||
expect(filterAvailable(input, ids, 'id')).toEqual(filtered);
|
||||
});
|
||||
|
||||
});
|
||||
})();
|
|
@ -30,7 +30,8 @@
|
|||
'$log',
|
||||
'horizon.framework.widgets.transfer-table.events',
|
||||
'horizon.framework.widgets.transfer-table.helpText',
|
||||
'horizon.framework.widgets.transfer-table.limits'
|
||||
'horizon.framework.widgets.transfer-table.limits',
|
||||
'horizon.framework.util.q.extensions'
|
||||
];
|
||||
|
||||
/**
|
||||
|
@ -67,7 +68,8 @@
|
|||
$log,
|
||||
events,
|
||||
helpText,
|
||||
limits
|
||||
limits,
|
||||
qExtensions
|
||||
) {
|
||||
var trModel = $parse($attrs.trModel)($scope);
|
||||
var trHelpText = $parse($attrs.helpText)($scope);
|
||||
|
@ -76,6 +78,7 @@
|
|||
var ctrl = this;
|
||||
ctrl.allocate = allocate;
|
||||
ctrl.deallocate = deallocate;
|
||||
ctrl.itemActions = getItemActions();
|
||||
ctrl.toggleView = toggleView;
|
||||
ctrl.updateAllocated = updateAllocated;
|
||||
ctrl.numAllocated = numAllocated;
|
||||
|
@ -97,6 +100,40 @@
|
|||
|
||||
//////////
|
||||
|
||||
function getItemActions() {
|
||||
return [{
|
||||
template: {
|
||||
text: '',
|
||||
actionClasses: 'fa fa-plus'
|
||||
},
|
||||
service: {
|
||||
allowed: function allocationAllowed(item) {
|
||||
var allocatable = item && !ctrl.allocatedIds[item.id];
|
||||
return qExtensions.booleanAsPromise(allocatable);
|
||||
},
|
||||
perform: function performAllocation(item) {
|
||||
allocate(item);
|
||||
return qExtensions.booleanAsPromise(true);
|
||||
}
|
||||
}
|
||||
}, {
|
||||
template: {
|
||||
text: '',
|
||||
actionClasses: 'fa fa-minus'
|
||||
},
|
||||
service: {
|
||||
allowed: function deallocationAllowed(item) {
|
||||
var deallocatable = item && ctrl.allocatedIds[item.id];
|
||||
return qExtensions.booleanAsPromise(deallocatable);
|
||||
},
|
||||
perform: function performDeallocation(item) {
|
||||
deallocate(item);
|
||||
return qExtensions.booleanAsPromise(true);
|
||||
}
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
function init(model) {
|
||||
|
||||
if (!angular.isArray(model.available)) {
|
||||
|
|
|
@ -69,14 +69,14 @@
|
|||
allocatedScope.$sourceItems = ctrl.allocated.sourceItems;
|
||||
allocatedScope.$isAllocatedTable = true;
|
||||
transclude(allocatedScope, function(clone) {
|
||||
allocated.append(clone.filter('table'));
|
||||
allocated.append(extractClonableTable(clone));
|
||||
});
|
||||
var availableScope = scope.$new();
|
||||
availableScope.$displayedItems = ctrl.available.displayedItems;
|
||||
availableScope.$sourceItems = ctrl.available.sourceItems;
|
||||
availableScope.$isAvailableTable = true;
|
||||
transclude(availableScope, function(clone) {
|
||||
available.append(clone.filter('table'));
|
||||
available.append(extractClonableTable(clone));
|
||||
});
|
||||
} else {
|
||||
transclude(scope, function(clone) {
|
||||
|
@ -84,6 +84,26 @@
|
|||
available.append(clone.filter('available'));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds in a given DOM either an <hz-dynamic-table> internal node or (as a
|
||||
* fallback) a <table> node.
|
||||
*
|
||||
* @param {object} element
|
||||
* The jqLite/jQuery wrapper around DOM node where the table node is
|
||||
* being searched.
|
||||
*
|
||||
* @return {object}
|
||||
* The jqLite/jQuery wrapper around the table DOM node that was found.
|
||||
*/
|
||||
function extractClonableTable(element) {
|
||||
var table = element.filter('hz-dynamic-table');
|
||||
if (!table.length) {
|
||||
table = element.filter('table');
|
||||
}
|
||||
return table;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<dl class="key-pair-details">
|
||||
<dt translate>Public Key</dt>
|
||||
<dd>
|
||||
<pre><code>{$ row.public_key $}</code></pre>
|
||||
<pre><code>{$ item.public_key $}</code></pre>
|
||||
</dd>
|
||||
</dl>
|
||||
|
|
|
@ -62,12 +62,32 @@
|
|||
|
||||
ctrl.tableData = {
|
||||
available: launchInstanceModel.keypairs,
|
||||
allocated: launchInstanceModel.newInstanceSpec.key_pair,
|
||||
displayedAvailable: [],
|
||||
displayedAllocated: []
|
||||
allocated: launchInstanceModel.newInstanceSpec.key_pair
|
||||
};
|
||||
|
||||
ctrl.tableDetails = basePath + 'keypair/keypair-details.html';
|
||||
ctrl.availableTableConfig = {
|
||||
selectAll: false,
|
||||
trackId: 'id',
|
||||
detailsTemplateUrl: basePath + 'keypair/keypair-details.html',
|
||||
columns: [
|
||||
{id: 'name', title: gettext('Name'), priority: 1},
|
||||
{id: 'fingerprint', title: gettext('Fingerprint'), priority: 2}
|
||||
]
|
||||
};
|
||||
|
||||
ctrl.allocatedTableConfig = angular.copy(ctrl.availableTableConfig);
|
||||
ctrl.allocatedTableConfig.noItemsMessage = gettext(
|
||||
'Select a key pair from the available key pairs below.');
|
||||
|
||||
ctrl.filterFacets = [{
|
||||
label: gettext('Name'),
|
||||
name: 'name',
|
||||
singleton: true
|
||||
}, {
|
||||
label: gettext('Fingerprint'),
|
||||
name: 'fingerprint',
|
||||
singleton: true
|
||||
}];
|
||||
|
||||
ctrl.tableLimits = {
|
||||
maxAllocation: 1
|
||||
|
|
|
@ -66,15 +66,17 @@
|
|||
|
||||
it('sets table data to appropriate scoped items', function() {
|
||||
expect(ctrl.tableData).toBeDefined();
|
||||
expect(Object.keys(ctrl.tableData).length).toBe(4);
|
||||
expect(Object.keys(ctrl.tableData).length).toBe(2);
|
||||
expect(ctrl.tableData.available).toEqual([{name: 'key1'}, {name: 'key2'}]);
|
||||
expect(ctrl.tableData.allocated).toEqual(['key1']);
|
||||
expect(ctrl.tableData.displayedAvailable).toEqual([]);
|
||||
expect(ctrl.tableData.displayedAllocated).toEqual([]);
|
||||
});
|
||||
|
||||
it('defines table details template', function() {
|
||||
expect(ctrl.tableDetails).toBeDefined();
|
||||
expect(ctrl.availableTableConfig.detailsTemplateUrl).toBeDefined();
|
||||
});
|
||||
|
||||
it('defines a custom no items message for allocated table', function() {
|
||||
expect(ctrl.allocatedTableConfig.noItemsMessage).toBeDefined();
|
||||
});
|
||||
|
||||
it('allows allocation of only one', function() {
|
||||
|
|
|
@ -27,102 +27,13 @@
|
|||
<translate>Import Key Pair</translate>
|
||||
</button>
|
||||
|
||||
<transfer-table tr-model="ctrl.tableData"
|
||||
limits="ctrl.tableLimits">
|
||||
|
||||
<!-- Key Pairs Allocated-->
|
||||
<allocated validate-number-min="ctrl.isKeypairRequired" ng-model="ctrl.tableData.allocated.length">
|
||||
<table st-table="ctrl.tableData.displayedAllocated"
|
||||
st-safe-src="ctrl.tableData.allocated" hz-table
|
||||
class="table table-striped table-rsp table-detail">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="expander"></th>
|
||||
<th class="rsp-p1" translate>Name</th>
|
||||
<th class="rsp-p2" translate>Fingerprint</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-if="ctrl.tableData.allocated.length === 0">
|
||||
<td colspan="8">
|
||||
<div class="no-rows-help" translate>
|
||||
Select a key pair from the available key pairs below.
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-repeat-start="row in ctrl.tableData.displayedAllocated track by row.id">
|
||||
<td class="expander">
|
||||
<span class="fa fa-chevron-right" hz-expand-detail
|
||||
title="{$ ::trCtrl.helpText.expandDetailsText $}"></span>
|
||||
</td>
|
||||
<td class="rsp-p1">{$ row.name $}</td>
|
||||
<td class="rsp-p2">{$ row.fingerprint $}</td>
|
||||
<td class="actions_column">
|
||||
<action-list>
|
||||
<action action-classes="'btn btn-default'"
|
||||
callback="trCtrl.deallocate" item="row">
|
||||
<span class="fa fa-minus"></span>
|
||||
</action>
|
||||
</action-list>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-repeat-end class="detail-row">
|
||||
<td class="detail" colspan="4" ng-include="ctrl.tableDetails"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</allocated>
|
||||
|
||||
<!-- Key Pairs Available -->
|
||||
<available>
|
||||
<table st-table="ctrl.tableData.displayedAvailable"
|
||||
st-safe-src="ctrl.tableData.available"
|
||||
hz-table class="table table-striped table-rsp table-detail">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="search-header" colspan="7">
|
||||
<hz-search-bar icon-classes="fa-search"></hz-search-bar>
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th class="expander"></th>
|
||||
<th st-sort="name" st-sort-default class="rsp-p1" translate>Name</th>
|
||||
<th st-sort="fingerprint" class="rsp-p1" translate>Fingerprint</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-if="trCtrl.numAvailable() === 0">
|
||||
<td colspan="8">
|
||||
<div class="no-rows-help">
|
||||
{$ ::trCtrl.helpText.noneAvailText $}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-repeat-start="row in ctrl.tableData.displayedAvailable track by row.id"
|
||||
ng-if="!trCtrl.allocatedIds[row.id]">
|
||||
<td class="expander">
|
||||
<span class="fa fa-chevron-right" hz-expand-detail
|
||||
title="{$ ::trCtrl.helpText.expandDetailsText $}"></span>
|
||||
</td>
|
||||
<td class="rsp-p1">{$ row.name$}</td>
|
||||
<td class="rsp-p1">{$ row.fingerprint $}</td>
|
||||
<td class="actions_column">
|
||||
<action-list>
|
||||
<action action-classes="'btn btn-default'"
|
||||
callback="trCtrl.allocate" item="row">
|
||||
<span class="fa fa-plus"></span>
|
||||
</action>
|
||||
</action-list>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-repeat-end class="detail-row" ng-if="!trCtrl.allocatedIds[row.id]">
|
||||
<td class="detail" colspan="4" ng-include="ctrl.tableDetails">
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</available>
|
||||
<transfer-table tr-model="ctrl.tableData" limits="ctrl.tableLimits" clone-content>
|
||||
<hz-dynamic-table
|
||||
config="$isAvailableTable ? ctrl.availableTableConfig : ctrl.allocatedTableConfig"
|
||||
items="$isAvailableTable ? ($sourceItems | filterAvailable:trCtrl.allocatedIds) : $sourceItems"
|
||||
item-actions="trCtrl.itemActions"
|
||||
filter-facets="$isAvailableTable && ctrl.filterFacets"
|
||||
table="ctrl">
|
||||
</hz-dynamic-table>
|
||||
</transfer-table> <!-- End Key Pairs Table -->
|
||||
</div> <!-- End Controller -->
|
||||
|
|
|
@ -346,7 +346,7 @@
|
|||
angular.extend(
|
||||
model.keypairs,
|
||||
data.data.items.map(function (e) {
|
||||
e.keypair.id = e.keypair.name;
|
||||
e.keypair.id = 'li_keypair:' + e.keypair.name;
|
||||
return e.keypair;
|
||||
}));
|
||||
if (data.data.items.length === 1) {
|
||||
|
|
Loading…
Reference in New Issue