diff --git a/horizon/static/framework/util/filters/filters.js b/horizon/static/framework/util/filters/filters.js
index f6c8a728fc..3efcc36fdb 100644
--- a/horizon/static/framework/util/filters/filters.js
+++ b/horizon/static/framework/util/filters/filters.js
@@ -19,6 +19,7 @@
angular
.module('horizon.framework.util.filters')
.filter('yesno', yesNoFilter)
+ .filter('simpleDate', simpleDateFilter)
.filter('gb', gbFilter)
.filter('mb', mbFilter)
.filter('title', titleFilter)
@@ -45,6 +46,19 @@
};
}
+ /**
+ * @ngdoc filter
+ * @name simpleDate
+ * @description
+ * Evaluates given for display as a short date, returning '-' if empty.
+ */
+ simpleDateFilter.$inject = ['$filter'];
+ function simpleDateFilter($filter) {
+ return function (input) {
+ return $filter('noValue')($filter('date')(input, 'short'));
+ };
+ }
+
/**
* @ngdoc filter
* @name gb
diff --git a/horizon/static/framework/util/filters/filters.spec.js b/horizon/static/framework/util/filters/filters.spec.js
index 789c59a76e..2ed211fcd8 100644
--- a/horizon/static/framework/util/filters/filters.spec.js
+++ b/horizon/static/framework/util/filters/filters.spec.js
@@ -38,6 +38,21 @@
});
});
+ describe('simpleDate', function () {
+ var simpleDateFilter;
+ beforeEach(inject(function (_simpleDateFilter_) {
+ simpleDateFilter = _simpleDateFilter_;
+ }));
+
+ it('returns blank if nothing', function () {
+ expect(simpleDateFilter()).toBe('-');
+ });
+
+ it('returns the expected time', function() {
+ expect(simpleDateFilter('2016-06-24T04:19:07')).toBe('6/24/16 4:19 AM');
+ });
+ });
+
describe('gb', function () {
var gbFilter;
beforeEach(inject(function (_gbFilter_) {
diff --git a/openstack_dashboard/static/app/core/images/admin-panel.html b/openstack_dashboard/static/app/core/images/admin-panel.html
new file mode 100644
index 0000000000..444e4ec919
--- /dev/null
+++ b/openstack_dashboard/static/app/core/images/admin-panel.html
@@ -0,0 +1,5 @@
+
+
+
diff --git a/openstack_dashboard/static/app/core/images/details/details.module.js b/openstack_dashboard/static/app/core/images/details/details.module.js
index 8bf853221c..299a98bdbe 100644
--- a/openstack_dashboard/static/app/core/images/details/details.module.js
+++ b/openstack_dashboard/static/app/core/images/details/details.module.js
@@ -30,27 +30,23 @@
registerImageDetails.$inject = [
'horizon.app.core.images.basePath',
'horizon.app.core.images.resourceType',
- 'horizon.app.core.openstack-service-api.glance',
+ 'horizon.app.core.images.service',
'horizon.framework.conf.resource-type-registry.service'
];
function registerImageDetails(
basePath,
imageResourceType,
- glanceApi,
+ imageService,
registry
) {
registry.getResourceType(imageResourceType)
- .setLoadFunction(loadFunction)
+ .setLoadFunction(imageService.getImagePromise)
.detailsViews.append({
id: 'imageDetailsOverview',
name: gettext('Overview'),
template: basePath + 'details/overview.html'
});
-
- function loadFunction(identifier) {
- return glanceApi.getImage(identifier);
- }
}
})();
diff --git a/openstack_dashboard/static/app/core/images/details/drawer.html b/openstack_dashboard/static/app/core/images/details/drawer.html
index 1d2d4a856f..2432938c0c 100644
--- a/openstack_dashboard/static/app/core/images/details/drawer.html
+++ b/openstack_dashboard/static/app/core/images/details/drawer.html
@@ -5,7 +5,7 @@
item="item"
property-groups="[
['name', 'id'],
- ['is_public', 'protected'],
+ ['visibility', 'protected'],
['disk_format', 'size'],
['min_disk', 'min_ram']]">
diff --git a/openstack_dashboard/static/app/core/images/details/overview.controller.js b/openstack_dashboard/static/app/core/images/details/overview.controller.js
index df8a5a45c8..73691f033a 100644
--- a/openstack_dashboard/static/app/core/images/details/overview.controller.js
+++ b/openstack_dashboard/static/app/core/images/details/overview.controller.js
@@ -23,12 +23,14 @@
ImageOverviewController.$inject = [
'horizon.app.core.images.resourceType',
'horizon.framework.conf.resource-type-registry.service',
+ 'horizon.app.core.openstack-service-api.userSession',
'$scope'
];
function ImageOverviewController(
imageResourceTypeCode,
registry,
+ userSession,
$scope
) {
var ctrl = this;
@@ -44,6 +46,12 @@
ctrl.image.properties = Object.keys(ctrl.image.properties).map(function mapProps(prop) {
return {name: prop, value: ctrl.image.properties[prop]};
});
+
+ userSession.get().then(setProject);
+
+ function setProject(session) {
+ ctrl.projectId = session.project_id;
+ }
}
}
diff --git a/openstack_dashboard/static/app/core/images/details/overview.controller.spec.js b/openstack_dashboard/static/app/core/images/details/overview.controller.spec.js
index 5639b30ee5..e1672e80a4 100644
--- a/openstack_dashboard/static/app/core/images/details/overview.controller.spec.js
+++ b/openstack_dashboard/static/app/core/images/details/overview.controller.spec.js
@@ -25,10 +25,14 @@
beforeEach(module('horizon.app.core.images'));
beforeEach(module('horizon.framework.conf'));
- beforeEach(inject(function($controller, $q) {
+ beforeEach(inject(function($controller, $q, $injector) {
+ var session = $injector.get('horizon.app.core.openstack-service-api.userSession');
var deferred = $q.defer();
+ var sessionDeferred = $q.defer();
deferred.resolve({data: {properties: {'a': 'apple'}}});
+ deferred.resolve({project_id: '12'});
spyOn(glance, 'getNamespaces').and.returnValue(deferred.promise);
+ spyOn(session, 'get').and.returnValue(sessionDeferred.promise);
ctrl = $controller('ImageOverviewController',
{
'$scope': {context: {loadPromise: deferred.promise}}
diff --git a/openstack_dashboard/static/app/core/images/details/overview.html b/openstack_dashboard/static/app/core/images/details/overview.html
index 6bd05a9870..d4eb5c63a2 100644
--- a/openstack_dashboard/static/app/core/images/details/overview.html
+++ b/openstack_dashboard/static/app/core/images/details/overview.html
@@ -8,45 +8,35 @@
cls="dl-horizontal"
item="ctrl.image"
property-groups="[[
- 'type', 'status', 'size', 'min_disk', 'min_ram', 'disk_format',
- 'container_format']]">
+ 'id', 'type', 'status', 'size', 'min_disk', 'min_ram', 'disk_format',
+ 'container_format', 'created_at', 'updated_at']]">
{$ 'Security' | translate $}
+ - Owner
+ - {$ ctrl.image.owner $}
- Filename
- - {$ ctrl.image.properties.filename $}
+ - {$ ctrl.image.properties.filename | noValue $}
- Visibility
- - {$ ctrl.image | imageVisibility $}
+ - {$ ctrl.image | imageVisibility:ctrl.projectId $}
- Protected
- {$ ctrl.image.protected | yesno $}
- Checksum
- - {$ ctrl.image.checksum $}
+ - {$ ctrl.image.checksum | noValue $}
-
-
Record Properties
-
-
- - Created
- - {$ ctrl.image.created_at | date:'short' $}
- - Updated
- - {$ ctrl.image.updated_at | date:'short' $}
- - ID
- - {$ ctrl.image.id $}
-
-
Custom Properties
- {$ ctrl.resourceType.label(prop.name) $}
- - {$ ctrl.resourceType.format(prop.name, prop.value) $}
+ - {$ prop.value $}
diff --git a/openstack_dashboard/static/app/core/images/images.module.js b/openstack_dashboard/static/app/core/images/images.module.js
index c0a7e6acee..14a4e21e81 100644
--- a/openstack_dashboard/static/app/core/images/images.module.js
+++ b/openstack_dashboard/static/app/core/images/images.module.js
@@ -81,7 +81,8 @@
filters: ['uppercase']
})
.setProperty('created_at', {
- label: gettext('Created At')
+ label: gettext('Created At'),
+ filters: ['simpleDate']
})
.setProperty('disk_format', {
label: gettext('Disk Format'),
@@ -90,10 +91,6 @@
.setProperty('id', {
label: gettext('ID')
})
- .setProperty('is_public', {
- label: gettext('Is Public'),
- filters: ['yesno']
- })
.setProperty('type', {
label: gettext('Type'),
filters: [imagesService.imageType]
@@ -129,7 +126,8 @@
label: gettext('Tags')
})
.setProperty('updated_at', {
- label: gettext('Updated At')
+ label: gettext('Updated At'),
+ filters: ['simpleDate']
})
.setProperty('virtual_size', {
label: gettext('Virtual Size')
@@ -172,6 +170,10 @@
priority: 1,
itemInTransitionFunction: imagesService.isInTransition
})
+ .append({
+ id: 'visibility',
+ priority: 1
+ })
.append({
id: 'protected',
priority: 1
@@ -208,6 +210,18 @@
{label: gettext('Deleted'), key: 'deleted'}
]
})
+ .append({
+ label: gettext('Visibility'),
+ name: 'visibility',
+ isServer: false,
+ singleton: true,
+ options: [
+ {label: gettext('Public'), key: gettext('Public')},
+ {label: gettext('Private'), key: gettext('Private')},
+ {label: gettext('Shared With Project'), key: gettext('Shared With Project')},
+ {label: gettext('Unknown'), key: 'unknown'}
+ ]
+ })
.append({
label: gettext('Protected'),
name: 'protected',
@@ -330,7 +344,7 @@
});
$routeProvider.when('/admin/images/', {
- templateUrl: path + 'panel.html'
+ templateUrl: path + 'admin-panel.html'
});
function goToAngularDetails(params) {
diff --git a/openstack_dashboard/static/app/core/images/images.service.js b/openstack_dashboard/static/app/core/images/images.service.js
index ef4afe854f..23b0c9b59c 100644
--- a/openstack_dashboard/static/app/core/images/images.service.js
+++ b/openstack_dashboard/static/app/core/images/images.service.js
@@ -20,7 +20,9 @@
.factory('horizon.app.core.images.service', imageService);
imageService.$inject = [
+ '$filter',
'horizon.app.core.openstack-service-api.glance',
+ 'horizon.app.core.openstack-service-api.userSession',
'horizon.app.core.images.transitional-statuses'
];
@@ -34,9 +36,10 @@
* but do not need to be restricted to such use. Each exposed function
* is documented below.
*/
- function imageService(glance, transitionalStatuses) {
+ function imageService($filter, glance, userSession, transitionalStatuses) {
return {
getDetailsPath: getDetailsPath,
+ getImagePromise: getImagePromise,
getImagesPromise: getImagesPromise,
imageType: imageType,
isInTransition: isInTransition
@@ -99,17 +102,34 @@
* 'trackBy' to assist the display mechanism when updating rows.
*/
function getImagesPromise(params) {
- return glance.getImages(params).then(modifyResponse);
+ var projectId;
+ return userSession.get().then(getImages);
+
+ function getImages(userSession) {
+ projectId = userSession.project_id;
+ return glance.getImages(params).then(modifyResponse);
+ }
function modifyResponse(response) {
- return {data: {items: response.data.items.map(addTrackBy)}};
+ return {data: {items: response.data.items.map(modifyImage)}};
- function addTrackBy(image) {
+ function modifyImage(image) {
image.trackBy = image.id + image.updated_at;
+ image.visibility = $filter('imageVisibility')(image, projectId);
return image;
}
}
}
+
+ /*
+ * @ngdoc function
+ * @name getImagePromise
+ * @description
+ * Given an id, returns a promise for the image data.
+ */
+ function getImagePromise(identifier) {
+ return glance.getImage(identifier);
+ }
}
})();
diff --git a/openstack_dashboard/static/app/core/images/images.service.spec.js b/openstack_dashboard/static/app/core/images/images.service.spec.js
index f8ac6db19b..d298b022b3 100644
--- a/openstack_dashboard/static/app/core/images/images.service.spec.js
+++ b/openstack_dashboard/static/app/core/images/images.service.spec.js
@@ -86,14 +86,30 @@
describe('getImagesPromise', function() {
it("provides a promise that gets translated", inject(function($q, $injector, $timeout) {
var glance = $injector.get('horizon.app.core.openstack-service-api.glance');
+ var session = $injector.get('horizon.app.core.openstack-service-api.userSession');
var deferred = $q.defer();
+ var deferredSession = $q.defer();
spyOn(glance, 'getImages').and.returnValue(deferred.promise);
+ spyOn(session, 'get').and.returnValue(deferredSession.promise);
var result = service.getImagesPromise({});
deferred.resolve({data: {items: [{id: 1, updated_at: 'jul1'}]}});
+ deferredSession.resolve({project_id: '12'});
$timeout.flush();
expect(result.$$state.value.data.items[0].trackBy).toBe('1jul1');
}));
});
+
+ describe('getImagePromise', function() {
+ it("provides a promise", inject(function($q, $injector) {
+ var glance = $injector.get('horizon.app.core.openstack-service-api.glance');
+ var deferred = $q.defer();
+ spyOn(glance, 'getImage').and.returnValue(deferred.promise);
+ var result = service.getImagePromise({});
+ deferred.resolve({id: 1, updated_at: 'jul1'});
+ expect(glance.getImage).toHaveBeenCalled();
+ expect(result.$$state.value.updated_at).toBe('jul1');
+ }));
+ });
});
})();
diff --git a/openstack_dashboard/static/app/core/images/steps/create-volume/create-volume.controller.js b/openstack_dashboard/static/app/core/images/steps/create-volume/create-volume.controller.js
index 440901c58b..c140d7ca00 100644
--- a/openstack_dashboard/static/app/core/images/steps/create-volume/create-volume.controller.js
+++ b/openstack_dashboard/static/app/core/images/steps/create-volume/create-volume.controller.js
@@ -155,7 +155,14 @@
}
function onGetAvailabilityZones(response) {
- ctrl.availabilityZones = response.items;
+ ctrl.availabilityZones = response.items.map(justNames);
+ if (ctrl.availabilityZones.length > 0) {
+ ctrl.volume.availability_zone = ctrl.availabilityZones[0];
+ }
+
+ function justNames(item) {
+ return item.zoneName;
+ }
}
function onGetAbsoluteLimits(response) {
diff --git a/openstack_dashboard/static/app/core/images/steps/create-volume/create-volume.controller.spec.js b/openstack_dashboard/static/app/core/images/steps/create-volume/create-volume.controller.spec.js
index 8ac3f2cd42..3f0abd9dc9 100644
--- a/openstack_dashboard/static/app/core/images/steps/create-volume/create-volume.controller.spec.js
+++ b/openstack_dashboard/static/app/core/images/steps/create-volume/create-volume.controller.spec.js
@@ -17,7 +17,7 @@
describe('horizon.app.core.images.controller.CreateVolumeController', function () {
- var controller, quotaChartDefaults, $scope, $filter, getAbsoluteLimitsSpy;
+ var controller, quotaChartDefaults, $scope, $filter, getAbsoluteLimitsSpy, nova;
var cinder = {
getVolumeTypes: function() {
@@ -39,21 +39,21 @@
}
};
- var nova = {
- getAvailabilityZones: function() {
- return {
- success: function(callback) {
- return callback({ items: ['zone1'] });
- }
- };
- }
- };
-
beforeEach(module('horizon.app.core.images'));
beforeEach(module('horizon.framework.widgets.charts'));
beforeEach(module('horizon.framework.util.filters'));
beforeEach(inject(function ($injector, _$rootScope_, _$filter_) {
+
+ nova = {
+ getAvailabilityZones: function() {
+ return {
+ success: function(callback) {
+ return callback({ items: [{zoneName: 'zone1'}] });
+ }
+ };
+ }
+ };
$scope = _$rootScope_.$new();
$scope.image = {
name: 'ImageName',
@@ -364,6 +364,37 @@
ctrl.volume.size = 100;
+ var emittedEventArgs = $scope.$emit.calls.argsFor(0);
+ var expectedVolume = {
+ size: 100,
+ name: ctrl.image.name,
+ description: '',
+ volume_type: 'lvmdriver-1',
+ availability_zone: 'zone1', // pre-selects first
+ metadata: {},
+ image_id: ctrl.image.id,
+ snapshot_id: null,
+ source_volid: null
+ };
+
+ expect(emittedEventArgs[0]).toEqual('horizon.app.core.images.VOLUME_CHANGED');
+ expect(emittedEventArgs[1]).toEqual(expectedVolume);
+ });
+
+ it('not default the availability_zone if none present', function() {
+
+ nova.getAvailabilityZones = function() {
+ return {
+ success: function(callback) {
+ return callback({ items: [] });
+ }
+ };
+ };
+ var ctrl = createController();
+ $scope.$apply();
+
+ ctrl.volume.size = 100;
+
var emittedEventArgs = $scope.$emit.calls.argsFor(0);
var expectedVolume = {
size: 100,
diff --git a/openstack_dashboard/static/app/core/images/steps/create-volume/create-volume.help.html b/openstack_dashboard/static/app/core/images/steps/create-volume/create-volume.help.html
new file mode 100644
index 0000000000..0f23c7f741
--- /dev/null
+++ b/openstack_dashboard/static/app/core/images/steps/create-volume/create-volume.help.html
@@ -0,0 +1,4 @@
+
+
Description
+
This page allows you to create a volume based off of the image.
+
diff --git a/openstack_dashboard/static/app/core/images/steps/create-volume/create-volume.html b/openstack_dashboard/static/app/core/images/steps/create-volume/create-volume.html
index 78562210eb..0b94aab18d 100644
--- a/openstack_dashboard/static/app/core/images/steps/create-volume/create-volume.html
+++ b/openstack_dashboard/static/app/core/images/steps/create-volume/create-volume.html
@@ -49,12 +49,11 @@
Availability Zone
-
diff --git a/openstack_dashboard/static/app/core/images/workflows/create-volume.service.js b/openstack_dashboard/static/app/core/images/workflows/create-volume.service.js
index 6c3e055433..58f838ff08 100644
--- a/openstack_dashboard/static/app/core/images/workflows/create-volume.service.js
+++ b/openstack_dashboard/static/app/core/images/workflows/create-volume.service.js
@@ -43,6 +43,7 @@
{
title: gettext('Volume Details'),
templateUrl: basePath + 'steps/create-volume/create-volume.html',
+ helpUrl: basePath + 'steps/create-volume/create-volume.help.html',
formName: 'volumeForm'
}
]