Skip to content

Commit

Permalink
frontend: cleanup pod detail page
Browse files Browse the repository at this point in the history
fixes: openshift#340

This commit addresses many of the points in openshift#340. Notably missing is the
panel showing pod resource utilization. This was intentionally left out
due to Heapster's unreliability. Additionally, this commit adds a
modified pod volume section that lists volumes by name and by
mount-permissions; this can lead to situations where the same volume is
shown twice. Finally, the `Host Location` column was taken out since
most volume types will not provide this field. Instead, this information
is surfaced the `Type` column in parentheses for applicable types.
  • Loading branch information
squat committed Aug 29, 2016
1 parent 667e062 commit 5a3ab12
Show file tree
Hide file tree
Showing 10 changed files with 189 additions and 72 deletions.
8 changes: 7 additions & 1 deletion frontend/public/module/k8s/enum.js
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ angular.module('k8s').constant('k8sEnum', {
},
gitRepo: {
weight: 300,
id: 'gitRepot',
id: 'gitRepo',
label: 'Git Repo',
description: 'Git repository at a particular revision.',
},
Expand Down Expand Up @@ -264,6 +264,12 @@ angular.module('k8s').constant('k8sEnum', {
label: 'iSCSI',
description: 'iSCSI disk attached to host machine on demand',
},
configMap: {
weight: 1000,
id: 'configMap',
label: 'ConfigMap',
description: 'ConfigMap to be consumed in volume.',
},
},

});
44 changes: 41 additions & 3 deletions frontend/public/module/k8s/pods.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,15 +82,49 @@ angular.module('k8s')
return null;
}
return _.find(k8sEnum.VolumeSource, function(v) {
return !pkg.isEmpty(volume[v.id]);
return !!volume[v.id];
});
};

this.getVolumeMountPermissions = function(v) {
if (!v){
return null;
}

return v.readOnly ? 'Read-only' : 'Read/Write';
}.bind(this);

this.getVolumeMountsByPermissions = function(pod) {
var m = {};

if (!pod || !pod.spec) {
return [];
}

var volumes = pod.spec.volumes.reduce((p, v) => {
p[v.name] = v;
return p;
}, {});

_.forEach(pod.spec.containers, function(c) {
_.forEach(c.volumeMounts, function(v) {
let k = `${v.name}_${v.readOnly ? 'ro' : 'rw'}`;
let mount = {container: c.name, mountPath: v.mountPath};
if ( k in m) {
return m[k].mounts.push(mount);
}
m[k] = {mounts: [mount], name: v.name, readOnly: !!v.readOnly, volume: volumes[v.name]};
});
});

return _.values(m);
}.bind(this);

this.getVolumeLocation = function(volume) {
var vtype = this.getVolumeType(volume), info, typeID;

if (!vtype) {
return '';
return null;
}

function readOnlySuffix(readonly) {
Expand All @@ -108,7 +142,7 @@ angular.module('k8s')
if (keys.indexOf('readOnly') !== -1) {
parts.push(readOnlySuffix(volInfo.readOnly));
}
return parts.join(' ');
return parts.join(' ') || null;
}

typeID = vtype.id;
Expand All @@ -117,6 +151,10 @@ angular.module('k8s')
// Override any special formatting cases.
case k8sEnum.VolumeSource.gitRepo.id:
return info.repository + ':' + info.revision;
case k8sEnum.VolumeSource.configMap.id:
case k8sEnum.VolumeSource.emptyDir.id:
case k8sEnum.VolumeSource.secret.id:
return null;
// Defaults to space separated sorted keys.
default:
return genericFormatter(info);
Expand Down
4 changes: 4 additions & 0 deletions frontend/public/module/ui/icons/_resource-icon.scss
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ $height: 18px;
margin: 0 4px;
}

.co-m-resource-icon-wrapper {
display: inline-block;
}

.co-m-resource-icon--service {
background-color: $color-service-dark;
}
Expand Down
3 changes: 3 additions & 0 deletions frontend/public/module/ui/icons/_volume-icon.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.co-m-volume-icon .fa {
vertical-align: middle;
}
44 changes: 44 additions & 0 deletions frontend/public/module/ui/icons/volume-icon.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/**
* @fileoverview
* Displays a different icon for different types of volumes, e.g. empty directories, secrets, etc.
*/

angular.module('bridge.ui')
.directive('coVolumeIcon', function(k8s) {
'use strict';
const volumeKind = k8s.enum.VolumeSource;

return {
template: `
<span class="co-m-volume-icon">
<i ng-if="faClass" class="fa{{faClass}}"></i>
<span ng-bind="label"></span>
</span>
`,
restrict: 'E',
replace: true,
link: function(scope, elem, attrs) {
var kind = (attrs.kind || '');
if (kind) {
elem.addClass('co-m-volume-icon--' + kind);
}
scope.label = volumeKind[kind] ? volumeKind[kind].label : '';
scope.faClass = faClass(volumeKind, kind);
}
};

});


function faClass(volumeKind, kind) {
switch (kind) {
case volumeKind.emptyDir.id:
return ' fa-folder-open-o';
case volumeKind.hostPath.id:
return ' fa-files-o';
case volumeKind.secret.id:
return ' fa-lock';
default:
return '';
}
}
3 changes: 1 addition & 2 deletions frontend/public/module/ui/overflow/_overflow.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ $width: 35px;

.co-m-overflow {
position: relative;
width: 100%;


&__gradient {
background: linear-gradient(to left, rgba(255,255,255,1) 0%,rgba(255,255,255,1) 31%,rgba(255,255,255,0) 100%);
height: 100%;
Expand Down
1 change: 1 addition & 0 deletions frontend/public/module/ui/uis.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import './cog/user-cog';
import './containers/container-input';
import './containers/multi-container-input';
import './icons/resource-icon';
import './icons/volume-icon';
import './icons/status';
import './forms/click-select';
import './forms/number-spinner';
Expand Down
8 changes: 5 additions & 3 deletions frontend/public/page/pods/pod-ctrl.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ angular.module('bridge.page')
k8s.pods.get($routeParams.name, $routeParams.ns)
.then(function(pod) {
$scope.pod = pod;
$scope.volumeMounts = k8s.pods.getVolumeMountsByPermissions(pod);
$scope.loadError = false;
})
.catch(function() {
Expand All @@ -22,13 +23,14 @@ angular.module('bridge.page')
return k8s.docker.getState(cinfo);
};

$scope.volumeTypeLabel = function(v) {
$scope.volumeType = function(v) {
var vtype = k8s.pods.getVolumeType(v);
return vtype ? vtype.label : '';
return vtype ? vtype.id : '';
};

$scope.volumeLocation = k8s.pods.getVolumeLocation;

$scope.getRestartPolicyLabel = k8s.pods.getRestartPolicyLabelById;
$scope.volumeMountPermissions = k8s.pods.getVolumeMountPermissions;

$scope.getRestartPolicyLabel = k8s.pods.getRestartPolicyLabelById;
});
145 changes: 82 additions & 63 deletions frontend/public/page/pods/pod.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,50 +14,34 @@
<div class="co-m-pane__body">
<div cos-tristate="pod" cos-tristate-error="loadError" class="co-m-pane__body-section--bordered">
<div ng-show="pod && !loadError" class="row">
<div class="col-lg-4 col-md-4 col-sm-4 col-xs-12">
<dl>
<dt>Pod Name</dt>
<dd ng-bind="pod.metadata.name"></dd>
<dt>Pod Labels</dt>
<dd><co-label-list kind="pod" expand="true" labels="pod.metadata.labels"></co-label-list></dd>
<dt>Created At</dt>
<dd><co-timestamp timestamp="{{pod.metadata.creationTimestamp}}" ></co-timestamp></dd>
</dl>
</div>
<div class="col-lg-4 col-md-4 col-sm-4 col-xs-12">
<dl>
<dt>Status</dt>
<dd ng-bind="pod|podPhase"></dd>
<dt>Pod IP</dt>
<dd ng-bind="pod.status.podIP | emptyPlaceholder"></dd>
<dt>Node</dt>
<dd>
<a ng-if="pod.spec.nodeName" ng-href="nodes/{{pod.spec.nodeName}}" ng-bind="pod.spec.nodeName"></a>
<span ng-if="!pod.spec.nodeName" ng-bind="CONST.placeholderText"></span>
</dd>
<dt>Restart Policy</dt>
<dd ng-bind="getRestartPolicyLabel(pod.spec.restartPolicy)"></dd>
</dl>
</div>
<div class="col-lg-4 col-md-4 col-sm-4 col-xs-12">
<h1 class="co-section-title">Pod Volumes</h1>
<div class="co-table-container">
<table class="table">
<thead>
<tr>
<th>Type</th>
<th>Name</th>
<th>Location</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="v in pod.spec.volumes">
<td ng-bind="volumeTypeLabel(v)"></td>
<td ng-bind="v.name"></td>
<td ng-bind="volumeLocation(v)"></td>
</tr>
</tbody>
</table>
<div class="col-lg-8 col-md-8 col-sm-8 col-xs-12">
<h1 class="co-section-title">Pod Overview</h1>
<div class="row">
<div class="col-lg-6 col-md-6 col-sm-6 col-xs-12">
<dl>
<dt>Pod Name</dt>
<dd ng-bind="pod.metadata.name"></dd>
<dt>Pod Labels</dt>
<dd><co-label-list kind="pod" expand="true" labels="pod.metadata.labels"></co-label-list></dd>
<dt>Created At</dt>
<dd><co-timestamp timestamp="{{pod.metadata.creationTimestamp}}" ></co-timestamp></dd>
</dl>
</div>
<div class="col-lg-6 col-md-6 col-sm-6 col-xs-12">
<dl>
<dt>Status</dt>
<dd ng-bind="pod|podPhase"></dd>
<dt>Pod IP</dt>
<dd ng-bind="pod.status.podIP | emptyPlaceholder"></dd>
<dt>Node</dt>
<dd>
<a ng-if="pod.spec.nodeName" ng-href="nodes/{{pod.spec.nodeName}}" ng-bind="pod.spec.nodeName"></a>
<span ng-if="!pod.spec.nodeName" ng-bind="CONST.placeholderText"></span>
</dd>
<dt>Restart Policy</dt>
<dd ng-bind="getRestartPolicyLabel(pod.spec.restartPolicy)"></dd>
</dl>
</div>
</div>
</div>
</div>
Expand All @@ -71,33 +55,33 @@ <h1 class="co-section-title">Pod Volumes</h1>
</cos-status-box>
</div>
</div>
<div class="co-m-pane">
<div class="co-m-pane__heading">
<div class="row">
<div class="col-xs-12">
<h1 class="co-m-pane__title">Containers</h1>
</div>
<div class="co-m-pane__heading">
<div class="row">
<div class="col-xs-12">
<h1 class="co-m-pane__title">Containers</h1>
</div>
</div>
</div>

<div class="co-m-pane__body">
<div class="co-m-table-grid co-m-table-grid--bordered">
<div class="row co-m-table-grid__head">
<div class="col-lg-2 col-md-2 col-sm-2 col-xs-4">Name</div>
<div class="col-lg-3 col-md-3 col-sm-3 hidden-xs">Id</div>
<div class="col-lg-3 col-md-3 col-sm-3 col-xs-8">Image</div>
<div class="col-lg-1 col-md-1 col-sm-2 hidden-xs">State</div>
<div class="col-lg-1 col-md-1 col-sm-2 hidden-xs">Restart Count</div>
<div class="col-lg-2 col-md-2 hidden-sm hidden-xs">Started At</div>
</div>
<div class="co-m-table-grid__body">
<div class="row" ng-repeat="c in pod.spec.containers" ng-init="cstate = getContainerState(c.name); cstatus = getStatus(c.name);">
<div class="co-m-pane__body">
<div class="co-m-table-grid co-m-table-grid--bordered">
<div class="row co-m-table-grid__head">
<div class="col-lg-2 col-md-2 col-sm-2 col-xs-4">Name</div>
<div class="col-lg-3 col-md-3 col-sm-3 hidden-xs">Id</div>
<div class="col-lg-3 col-md-3 col-sm-3 col-xs-8">Image</div>
<div class="col-lg-1 col-md-1 col-sm-2 hidden-xs">State</div>
<div class="col-lg-1 col-md-1 col-sm-2 hidden-xs">Restart Count</div>
<div class="col-lg-2 col-md-2 hidden-sm hidden-xs">Started At</div>
</div>
<div class="co-m-table-grid__body">
<div class="row" ng-repeat="c in pod.spec.containers" ng-init="cstate = getContainerState(c.name); cstatus = getStatus(c.name);">
<div class="middler">
<div class="col-lg-2 col-md-2 col-sm-2 col-xs-4">
<co-resource-icon kind="container"></co-resource-icon>
<a ng-href="ns/{{pod.metadata.namespace}}/pods/{{pod.metadata.name}}/containers/{{c.name}}"
ng-bind="c.name"></a>
</div>
<div class="col-lg-3 col-md-3 col-sm-3 hidden-xs co-truncate" ng-bind="cstatus.containerID | emptyPlaceholder"></div>
<co-overflow class="col-lg-3 col-md-3 col-sm-3 hidden-xs" value="cstatus.containerID | emptyPlaceholder"></co-overflow>
<div class="col-lg-3 col-md-3 col-sm-3 col-xs-8" ng-bind="c.image"></div>
<div class="col-lg-1 col-md-1 col-sm-2 hidden-xs" ng-bind="cstate.label | emptyPlaceholder"></div>
<div class="col-lg-1 col-md-1 col-sm-2 hidden-xs" ng-bind="cstatus.restartCount | emptyPlaceholder"></div>
Expand All @@ -107,6 +91,41 @@ <h1 class="co-m-pane__title">Containers</h1>
</div>
</div>
</div>
<div class="co-m-pane__heading">
<div class="row">
<div class="col-xs-12">
<h1 class="co-m-pane__title">Pod Volumes</h1>
</div>
</div>
</div>
<div class="co-m-pane__body">
<div class="co-m-table-grid co-m-table-grid--bordered">
<div class="row co-m-table-grid__head">
<div class="col-lg-3 col-md-3 col-sm-3 col-xs-4">Name</div>
<div class="col-lg-3 col-md-3 col-sm-3 col-xs-4">Type</div>
<div class="col-lg-3 col-md-3 col-sm-3 hidden-xs">Permissions</div>
<div class="col-lg-3 col-md-3 col-sm-3 col-xs-4">Utilized By</div>
</div>
<div class="co-m-table-grid__body">
<div class="row" ng-repeat="v in volumeMounts">
<div class="middler">
<co-overflow class="col-lg-3 col-md-3 col-sm-3 col-xs-4 co-truncate" value="v.name | emptyPlaceholder"></co-overflow>
<div class="col-lg-3 col-md-3 col-sm-3 col-xs-4">
<co-volume-icon kind="{{volumeType(v.volume)}}"></co-volume-icon>
<span ng-if="volumeLocation(v.volume)" ng-bind="'(' + volumeLocation(v.volume) + ')'"></span>
</div>
<div class="col-lg-3 col-md-3 col-sm-3 hidden-xs" ng-bind="volumeMountPermissions(v)"></div>
<div class="col-lg-3 col-md-3 col-sm-3 col-xs-4">
<div class="co-m-resource-icon-wrapper" ng-repeat="m in v.mounts">
<co-resource-icon kind="container"></co-resource-icon>
<a ng-href="ns/{{pod.metadata.namespace}}/pods/{{pod.metadata.name}}/containers/{{m.container}}" ng-bind="m.container"></a>{{$last ? '' : ',&nbsp;'}}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
1 change: 1 addition & 0 deletions frontend/public/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
// UI Modules
@import "module/ui/containers/container-input";
@import "module/ui/icons/resource-icon";
@import "module/ui/icons/volume-icon";
@import "module/ui/icons/status";
@import "module/ui/forms/number-spinner";
@import "module/ui/labels/label";
Expand Down

0 comments on commit 5a3ab12

Please sign in to comment.