提交 612f0dd2 编写于 作者: P Piotr Bryk

Merge pull request #290 from bryk/misc-ux-fixes

Misc UX changes for the entire UI
......@@ -15,5 +15,5 @@ limitations under the License.
-->
<div class="kd-labels" ng-repeat="(key, value) in ::labelsCtrl.labels">
{{key}}={{value}}
{{key}}: {{value}}
</div>
......@@ -16,7 +16,7 @@ limitations under the License.
<div layout="column" layout-padding layout-align="center center">
<md-whiteframe class="kd-deploy-whiteframe md-whiteframe-5dp" flex flex-gt-md>
<h3 class="md-headline">Deploy a Containerized App</h3>
<h3 class="md-headline kd-deploy-form-title">Deploy a Containerized App</h3>
<form name="ctrl.deployForm" ng-submit="ctrl.deployBySelection()" novalidate>
<kd-help-section>
......
......@@ -16,8 +16,12 @@
.kd-deploy-whiteframe {
background: $content-background;
margin: 1em;
margin: $baseline-grid;
// TODO(bryk): Find a better, application wide constant for the min width.
max-width: 960px;
min-width: 600px;
}
.kd-deploy-form-title {
margin-top: $baseline-grid;
}
......@@ -27,6 +27,6 @@ md-progress-linear {
}
.kd-deploy-input-row {
margin: 0;
margin-bottom: $baseline-grid;
margin-top: $baseline-grid;
}
......@@ -12,9 +12,15 @@
// See the License for the specific language governing permissions and
// limitations under the License.
@import '../../variables';
// This rule is meant to align items only in the current layout (kd-help-section).
.kd-help-section > * {
flex: 1;
margin-bottom: 20px;
padding-right: 50px;
margin-bottom: 0;
padding-right: 4 * $baseline-grid;
&:last-child {
padding-right: 0;
}
}
<!--
Copyright 2015 Google Inc. All Rights Reserved.
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.
-->
<md-dialog aria-label="Delete Replica Set" layout="column" layout-padding>
<md-content layout-padding>
<h4 class="md-title">Delete Replica Set</h4>
<p>
Delete replica set {{::ctrl.replicaSet}} in namespace {{::ctrl.namespace}}.<br>
Pods managed by the replica set will be also deleted.
</p>
<md-dialog-actions>
<md-button class="md-primary" ng-click="ctrl.cancel()">Cancel</md-button>
<md-button class="md-primary" ng-click="ctrl.remove()">Delete</md-button>
</md-dialog-actions>
</md-content>
</md-dialog>
// Copyright 2015 Google Inc. All Rights Reserved.
//
// 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.
import {StateParams} from './replicasetdetail_state';
import {getReplicaSetDetailsResource} from './replicasetdetail_stateconfig';
/**
* Controller for the delete replica set dialog.
*
* @final
*/
export default class DeleteReplicaSetDialogController {
/**
* @param {!md.$dialog} $mdDialog
* @param {!angular.$resource} $resource
* @param {string} namespace
* @param {string} replicaSet
* @ngInject
*/
constructor($mdDialog, $resource, namespace, replicaSet) {
/** @export {string} */
this.replicaSet = replicaSet;
/** @export {string} */
this.namespace = namespace;
/** @private {!md.$dialog} */
this.mdDialog_ = $mdDialog;
/** @private {!angular.$resource} */
this.resource_ = $resource;
}
/**
* Deletes the replica set and closes the dialog.
*
* @export
*/
remove() {
let resource = getReplicaSetDetailsResource(
new StateParams(this.namespace, this.replicaSet), this.resource_);
resource.remove(() => { this.mdDialog_.hide(); }, () => { this.mdDialog_.cancel(); });
}
/**
* Cancels and closes the dialog.
*
* @export
*/
cancel() { this.mdDialog_.cancel(); }
}
// Copyright 2015 Google Inc. All Rights Reserved.
//
// 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.
import DeleteReplicaSetDialogController from './deletereplicaset_controller';
/**
* @param {!md.$dialog} mdDialog
* @param {string} namespace
* @param {string} replicaSet
* @return {!angular.$q.Promise}
*/
export default function showDeleteReplicaSetDialog(mdDialog, namespace, replicaSet) {
return mdDialog.show({
controller: DeleteReplicaSetDialogController,
controllerAs: 'ctrl',
clickOutsideToClose: true,
templateUrl: 'replicasetdetail/deletereplicaset.html',
locals: {
'namespace': namespace,
'replicaSet': replicaSet,
},
});
}
......@@ -12,8 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import {StateParams} from './replicasetdetail_state';
import {getReplicaSetDetailsResource} from './replicasetdetail_stateconfig';
import showDeleteReplicaSetDialog from './deletereplicaset_dialog';
import showUpdateReplicasDialog from './updatereplicas_dialog';
/**
......@@ -24,19 +23,11 @@ import showUpdateReplicasDialog from './updatereplicas_dialog';
export class ReplicaSetService {
/**
* @param {!md.$dialog} $mdDialog
* @param {!angular.$resource} $resource
* @param {!angular.$q} $q
* @ngInject
*/
constructor($mdDialog, $resource, $q) {
constructor($mdDialog) {
/** @private {!md.$dialog} */
this.mdDialog_ = $mdDialog;
/** @private {!angular.$resource} */
this.resource_ = $resource;
/** @private {!angular.$q} */
this.q_ = $q;
}
/**
......@@ -48,22 +39,7 @@ export class ReplicaSetService {
* @return {!angular.$q.Promise}
*/
showDeleteDialog(namespace, replicaSet) {
let resource =
getReplicaSetDetailsResource(new StateParams(namespace, replicaSet), this.resource_);
let deferred = this.q_.defer();
this.mdDialog_
.show(
this.mdDialog_.confirm()
.title('Delete Replica Set')
.textContent(
`Delete replica set ${replicaSet} in namespace ${namespace}. Pods managed by ` +
`the replica set will be also deleted. Are you sure?`)
.ok('Ok')
.cancel('Cancel'))
.then(() => { resource.remove(deferred.resolve, deferred.reject); });
return deferred.promise;
return showDeleteReplicaSetDialog(this.mdDialog_, namespace, replicaSet);
}
/**
......
......@@ -208,7 +208,7 @@ limitations under the License.
<td class="kd-replicasetdetail-table-cell">
<span ng-if="::pod.startTime">
{{::pod.startTime | relativeTime}}
<md-tooltip>{{::(pod.startTime | date:'short')}}</md-tooltip>
<md-tooltip>Started at {{::(pod.startTime | date:'short')}}</md-tooltip>
</span>
<span ng-if="::!pod.startTime">
-
......@@ -238,7 +238,7 @@ limitations under the License.
</td>
<td class="kd-replicasetdetail-table-cell">
<span>
<a ng-href="{{::ctrl.getPodLogsHref(pod)}}" class="md-primary">
<a ng-href="{{::ctrl.getPodLogsHref(pod)}}" target="_blank">
Logs
<i class="material-icons kd-text-icon">open_in_new</i>
</a>
......@@ -327,8 +327,12 @@ limitations under the License.
</td>
<td class="kd-replicasetdetail-table-cell">{{event.object}}</td>
<td class="kd-replicasetdetail-table-cell">{{event.count}}</td>
<td class="kd-replicasetdetail-table-cell">{{event.firstSeen | date:'short'}}</td>
<td class="kd-replicasetdetail-table-cell">{{event.lastSeen | date:'short'}}</td>
<td class="kd-replicasetdetail-table-cell">
First seen at {{event.firstSeen | date:'short'}}
</td>
<td class="kd-replicasetdetail-table-cell">
Last seen at {{event.lastSeen | date:'short'}}
</td>
</tr>
</tbody>
</table>
......
......@@ -186,7 +186,7 @@ export default class ReplicaSetDetailController {
handleDeleteReplicaSetDialog() {
this.kdReplicaSetService_
.showDeleteDialog(this.stateParams_.namespace, this.stateParams_.replicaSet)
.then(this.onReplicaSetDeleteSuccess_.bind(this), this.onReplicaSetDeleteError_.bind(this));
.then(this.onReplicaSetDeleteSuccess_.bind(this));
}
/**
......@@ -202,11 +202,4 @@ export default class ReplicaSetDetailController {
this.log_.info('Replica set successfully deleted.');
this.state_.go(replicasets);
}
/**
* Logs error after replica set deletion failure.
* @param {!angular.$http.Response} err
* @private
*/
onReplicaSetDeleteError_(err) { this.log_.error(err); }
}
......@@ -16,7 +16,7 @@ limitations under the License.
<md-dialog aria-label="Create a new namespace" layout="column" layout-padding>
<md-content layout-padding>
<h4 class="md-title" >Set desired number of pods</h4>
<h4 class="md-title">Set desired number of pods</h4>
<p>Replica set {{ctrl.replicaSet}} will be updated to reflect the desired count.
<br/>
<span class="kd-updatereplicas-pod-status">
......
......@@ -12,6 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
@import '../variables';
.kd-updatereplicas-pod-status {
font-size: 14px;
font-size: $body-font-size-base;
}
......@@ -15,25 +15,30 @@ limitations under the License.
-->
<md-menu>
<md-button ng-click="ctrl.openMenu($mdOpenMenu, $event)" class="kd-replicaset-card-logs-button">Logs</md-button>
<md-menu-content>
<md-button ng-click="ctrl.openMenu($mdOpenMenu, $event)" class="kd-replicaset-card-logs-button">
Logs
</md-button>
<md-menu-content class="kd-replicaset-card-logs-menu" width="6">
<md-menu-item class="kd-menu-logs-md-menu-item">
<div>Logs</div>
</md-menu-item>
<md-menu-item class="kd-menu-logs-md-menu-item">
<div class="kd-menu-logs-item-header kd-menu-logs-item-pods">Pod</div>
<div class="kd-menu-logs-item-header kd-menu-logs-item-since">Running since</div>
<div class="kd-menu-logs-item-header kd-menu-logs-item-prior">Prior restart</div>
<md-menu-item class="kd-menu-logs-md-menu-item" layout>
<div class="kd-menu-logs-item-header">Pod</div>
<div class="kd-menu-logs-item-header kd-menu-logs-item-since" flex="none">
Running since
</div>
</md-menu-item>
<md-menu-item ng-repeat="pod in ::ctrl.replicaSetPodsList" class="kd-menu-logs-md-menu-item">
<div class="kd-menu-logs-item kd-menu-logs-item-pods">
{{::(pod.name | middleEllipsis:10)}}
<md-menu-item ng-repeat="pod in ::ctrl.replicaSetPodsList"
class="kd-menu-logs-md-menu-item" layout>
<div class="kd-menu-logs-item">
{{::pod.name}}
</div>
<div class="kd-menu-logs-item kd-menu-logs-item-since">
<div class="kd-menu-logs-item kd-menu-logs-item-since" flex="none">
<a ng-href="{{::ctrl.getLogsHref(pod.name)}}"
ng-if="::ctrl.podContainerExists(pod)">
ng-if="::ctrl.podContainerExists(pod)"
target="_blank">
<span ng-if="::pod.startTime">
{{pod.startTime | date:"short"}}
{{::(pod.startTime | date:"short")}}
</span>
<span ng-if="::!pod.startTime">
Not running
......@@ -42,12 +47,6 @@ limitations under the License.
</a>
<span ng-if="::!ctrl.podContainerExists(pod)">-</span>
</div>
<div class="kd-menu-logs-item kd-menu-logs-item-prior">
<a ng-if="::ctrl.podContainersRestarted(pod)">
Logs <i class="material-icons kd-text-icon">open_in_new</i>
</a>
<span ng-if="::!ctrl.podContainersRestarted(pod)">-</span>
</div>
</md-menu-item>
</md-menu-content>
</md-menu>
// Copyright 2015 Google Inc. All Rights Reserved.
//
// 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.
@import '../variables';
.kd-replicaset-card-logs-menu {
font-size: $body-font-size-base;
}
.kd-menu-logs-item-header {
box-sizing: inherit;
color: $foreground-2;
font-size: $body-font-size-base;
white-space: nowrap;
}
.kd-menu-logs-item {
box-sizing: inherit;
font-size: $body-font-size-base;
white-space: nowrap;
}
.kd-menu-logs-item-since {
width: 19 * $baseline-grid;
}
......@@ -19,11 +19,11 @@ limitations under the License.
<div layout="column">
<div flex layout="row" layout-align="space-between center"
class="kd-replicaset-card-title-row">
<h3 class="md-title kd-replicaset-card-title">
<a ng-href="{{::ctrl.getReplicaSetDetailHref()}}" class="kd-replicaset-card-name" flex>
{{::ctrl.replicaSet.name}}
<h3 class="md-title kd-replicaset-card-title">
{{::ctrl.replicaSet.name}}
</h3>
</a>
</h3>
<kd-replica-set-card-menu replica-set="::ctrl.replicaSet"></kd-replica-set-card-menu>
</div>
<div flex class="md-caption">
......@@ -66,24 +66,24 @@ limitations under the License.
<div flex>
<div ng-repeat="image in ::ctrl.replicaSet.containerImages track by $index"
class="kd-replicaset-card-section-image">
<md-tooltip>{{::image}}</md-tooltip>
{{::(image | middleEllipsis:32)}}
<md-tooltip ng-if="::ctrl.shouldTruncate(image)">{{::image}}</md-tooltip>
{{::(image | middleEllipsis:ctrl.imageMaxLength)}}
</div>
</div>
</div>
<div flex="40" layout="column" class="kd-replicaset-card-section">
<span flex="initial" class="kd-replicaset-card-section-title">Age</span>
<span flex>
<span flex="nogrow">
{{::ctrl.replicaSet.creationTime | relativeTime}}
<md-tooltip>
{{::ctrl.replicaSet.creationTime | date:'short'}}
Created at {{::ctrl.replicaSet.creationTime | date:'short'}}
</md-tooltip>
</span>
</div>
<div flex="60" layout="column" class="kd-replicaset-card-section">
<span flex="initial" class="kd-replicaset-card-section-title">Internal Endpoint</span>
<span flex="initial" class="kd-replicaset-card-section-title">Internal endpoint</span>
<div flex>
<div ng-repeat="endpoint in ::ctrl.replicaSet.internalEndpoints track by $index">
<kd-service-endpoint endpoint="::endpoint"></kd-service-endpoint>
......@@ -95,7 +95,7 @@ limitations under the License.
</div>
<div flex="40" layout="column" class="kd-replicaset-card-section">
<span flex="initial" class="kd-replicaset-card-section-title">External Endpoint</span>
<span flex="initial" class="kd-replicaset-card-section-title">External endpoint</span>
<div flex>
<div ng-repeat="endpoint in ::ctrl.replicaSet.externalEndpoints track by $index">
<kd-service-endpoint endpoint="::endpoint"></kd-service-endpoint>
......
......@@ -15,14 +15,13 @@
@import '../variables';
.kd-replicaset-card {
margin: $baseline-grid;
width: ($layout-breakpoint-lg - (2 * $baseline-grid) * 2) / 3;
margin: 0;
width: ($layout-breakpoint-lg - (2 * $baseline-grid) * 4) / 3;
}
.kd-replicaset-card-name {
color: inherit;
display: inline-block;
font-weight: $regular-font-weight;
display: block;
text-decoration: none;
&:visited {
......@@ -31,6 +30,7 @@
}
.kd-replicaset-card-title {
font-weight: $regular-font-weight;
margin: 0;
}
......@@ -85,28 +85,3 @@
.kd-replicase-card-pods-stat {
white-space: nowrap;
}
.kd-menu-logs-item-header {
box-sizing: inherit;
color: $foreground-2;
font-size: $body-font-size-base;
white-space: nowrap;
}
.kd-menu-logs-item {
box-sizing: inherit;
font-size: $body-font-size-base;
white-space: nowrap;
}
.kd-menu-logs-item-pods {
width: 10 * $baseline-grid;
}
.kd-menu-logs-item-prior {
width: 10 * $baseline-grid;
}
.kd-menu-logs-item-since {
width: 15 * $baseline-grid;
}
......@@ -32,10 +32,24 @@ export default class ReplicaSetCardController {
*/
this.replicaSet;
/**
* Maximum length of container image before it is truncated.
* @const
* @export {number}
*/
this.imageMaxLength = 32;
/** @private {!ui.router.$state} */
this.state_ = $state;
}
/**
* @param {string} imageName
* @return {boolean}
* @export
*/
shouldTruncate(imageName) { return imageName.length > this.imageMaxLength; }
/**
* @return {string}
* @export
......
......@@ -63,7 +63,7 @@ export default class ReplicaSetCardMenuController {
*/
showDeleteDialog() {
this.kdReplicaSetService_.showDeleteDialog(this.replicaSet.namespace, this.replicaSet.name)
.then(() => this.state_.reload(), () => this.log_.error('Error deleting replica set'));
.then(() => this.state_.reload());
}
/**
......
......@@ -14,4 +14,4 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
<div class="kd-replica-set-list-container" ng-transclude></div>
<div class="kd-replica-set-list-container" layout-padding ng-transclude></div>
// Copyright 2015 Google Inc. All Rights Reserved.
//
// 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.
import DeleteReplicaSetDialogController from 'replicasetdetail/deletereplicaset_controller';
import replicaSetDetailModule from 'replicasetdetail/replicasetdetail_module';
describe('Delete replica set dialog controller', () => {
/** @type {!DeleteReplicaSetDialogController} */
let ctrl;
/** @type {!md.$dialog} */
let mdDialog;
/** @type {!angular.$httpBackend} */
let httpBackend;
let namespaceMock = 'foo-namespace';
let replicaSetMock = 'foo-name';
beforeEach(() => {
angular.mock.module(replicaSetDetailModule.name);
angular.mock.inject(($log, $mdDialog, $controller, $httpBackend) => {
mdDialog = $mdDialog;
httpBackend = $httpBackend;
ctrl = $controller(DeleteReplicaSetDialogController, {
namespace: namespaceMock,
replicaSet: replicaSetMock,
});
});
});
it('should cancel', () => {
// given
spyOn(mdDialog, 'cancel');
// when
ctrl.cancel();
// then
expect(mdDialog.cancel).toHaveBeenCalled();
});
it('should delete', () => {
// given
spyOn(mdDialog, 'hide');
// when
httpBackend.whenDELETE('api/replicasets/foo-namespace/foo-name').respond(200, {});
ctrl.remove();
httpBackend.flush();
// then
expect(mdDialog.hide).toHaveBeenCalled();
});
it('should cancel on delete failure', () => {
// given
spyOn(mdDialog, 'cancel');
// when
httpBackend.whenDELETE('api/replicasets/foo-namespace/foo-name').respond(503, {});
ctrl.remove();
httpBackend.flush();
// then
expect(mdDialog.cancel).toHaveBeenCalled();
});
});
......@@ -37,4 +37,11 @@ describe('Logs menu controller', () => {
// then
expect(ctrl.getReplicaSetDetailHref()).toEqual('#/replicasets/foo-namespace/foo-name');
});
it('should truncate image name', () => {
expect(ctrl.shouldTruncate('x')).toBe(false);
expect(ctrl.shouldTruncate('x'.repeat(32))).toBe(false);
expect(ctrl.shouldTruncate('x'.repeat(33))).toBe(true);
expect(ctrl.shouldTruncate('x'.repeat(100))).toBe(true);
});
});
......@@ -79,25 +79,6 @@ describe('Replica set card menu controller', () => {
expect(state.reload).toHaveBeenCalled();
}));
it('should log on delete error', angular.mock.inject(($q, $log) => {
// given
let deferred = $q.defer();
spyOn($log, 'error');
spyOn(state, 'reload');
spyOn(kdReplicaSetService, 'showDeleteDialog').and.returnValue(deferred.promise);
// when
ctrl.showDeleteDialog();
deferred.reject();
// then
expect(state.reload).not.toHaveBeenCalled();
expect($log.error).not.toHaveBeenCalled();
scope.$apply();
expect(state.reload).not.toHaveBeenCalled();
expect($log.error).toHaveBeenCalled();
}));
it('should show update replicas dialog', () => {
// given
ctrl.replicaSet = {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册