Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

kubernetes: Update the image layer display designs #4334

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions pkg/kubernetes/Makefile.am
Expand Up @@ -9,6 +9,7 @@ shared_VIEWS = \
pkg/kubernetes/views/image-body.html \
pkg/kubernetes/views/image-config.html \
pkg/kubernetes/views/image-delete.html \
pkg/kubernetes/views/image-layers.html \
pkg/kubernetes/views/image-meta.html \
pkg/kubernetes/views/image-listing.html \
pkg/kubernetes/views/image-page.html \
Expand Down
210 changes: 33 additions & 177 deletions pkg/kubernetes/scripts/layers.js
Expand Up @@ -17,199 +17,61 @@
* along with Cockpit; If not, see <http://www.gnu.org/licenses/>.
*/

/* globals d3 */
/* globals cockpit */

(function() {
"use strict";


function layer_graph(selector, options) {
var outer = d3.select(selector);

/* Data we've been fed */
var layers = [ ];
var settings = {
height: 125,
width: null,
bar: 14,
gap: 2,
max: 90,
title: "Image layers",
};

var timeout;
var scale = null;
var selection;
var width;

var svg = outer.append("svg")
.attr("class", "image-layers")
.on("click", function() {
var datum = d3.select(d3.event.target).datum() || { id: null };
select(datum.id);
});

var label = svg.append("text")
.attr("class", "label");
var title = svg.append("text")
.attr("text-anchor", "end")
.attr("y", "1em");

var line = svg.append("polyline")
.attr("class", "line");

function select(id) {
if (id === null)
return;

selection = id;

var value = "";
var index = null;

svg.selectAll("g")
.classed("selected", function(d, i) {
if (d.id === id) {
value = d.label;
index = i;
return true;
}
return false;
});

label
.text(value)
.attr("y", settings.height);

var y = settings.max + 15;
var points = [];
points.push("0," + y);
if (index !== null) {
points.push((index * settings.bar) + "," + y);
points.push(((index * settings.bar) + (settings.bar / 2)) + "," + (y - 5));
points.push(((index + 1) * settings.bar) + "," + y);
}
points.push(width + "," + y);
line.attr("points", points.join(" "));
}

function digest() {
timeout = null;

width = settings.width;
if (width === null)
width = outer.node().clientWidth;

svg
.attr("width", width)
.attr("height", settings.height);

var max = d3.max(layers, function(d) { return d.size; });
scale = d3.scale.linear()
.domain([0, max])
.range([2, settings.max]);

var bar = svg.selectAll("g")
.data(layers)
.enter().append("g")
.attr("transform", function(d, i) {
return "translate(" + i * settings.bar + ",0)";
});

bar.append("rect")
.attr("class", "layer")
.attr("x", settings.gap)
.attr("y", function(d) { return scale(max - d.size); })
.attr("width", settings.bar - (settings.gap * 2))
.attr("height", function(d) { return scale(d.size); });
bar.append("rect")
.attr("class", "column")
.attr("width", settings.bar)
.attr("height", scale(max) + 15);

title
.text(settings.title)
.attr("x", width - 5);

select(selection);
}

function resized() {
window.clearTimeout(timeout);
timeout = window.setTimeout(digest, 150);
}

window.addEventListener('resize', resized);

digest();
resized();

return {
data: function(new_layers, options) {
if (!new_layers)
new_layers = [];
layers = new_layers.slice();
layers.reverse();
if (selection === undefined && layers.length)
selection = layers[0].id;
angular.extend(settings, options);
digest();
},
close: function() {
window.removeEventListener('resize', resized);
window.clearTimeout(timeout);
}
};
}

function configDiff(layer, lower) {
var x, ret = { };
for (x in layer) {
if (!angular.equals(layer[x], lower[x]))
ret[x] = layer[x];
}
return ret;
}

function v1CompatibilityLabel(layer, lower) {
var cmd, last;
if (layer.v1Compatibility.container_config) {
cmd = layer.v1Compatibility.container_config.Cmd;
if (cmd) {
last = cmd[cmd.length - 1];
if (last.indexOf("#(nop)") === 0)
return last.substring(6);
return last.substring(6).trim();
}
}

return layer.v1Compatibility.id;
}

function prepareLayer(layer, index, layers) {
var result;
/* DockerImageManifest */
if (layer.v1Compatibility) {
return {
result = {
id: layer.v1Compatibility.id,
size: layer.v1Compatibility.Size || 0,
label: v1CompatibilityLabel(layer, layers[index + 1])
};

/* DockerImageLayers */
} else if (layer.name && layer.size) {
return {
result = {
id: layer.name,
size: layer.size || 0,
label: layer.name,
};

/* Unsupported layer type */
} else {
return {
result = {
size: 0,
id: index,
label: "Unknown layer",
};
}

/* Some hints for coloring the display */
if (result.label.indexOf("RUN ") === 0)
result.hint = "run";
else if (result.label.indexOf("ADD ") === 0 || result.size > 8192)
result.hint = "add";
else
result.hint = "other";

return result;
}

return angular.module('registry.layers', [])
Expand All @@ -219,38 +81,32 @@
return {
restrict: 'E',
scope: {
layers: '=',
settings: '=',
prevent: '=',
data: '=layers',
},
templateUrl: 'views/image-layers.html',
link: function($scope, element, attributes) {

$scope.formatSize = function(bytes) {
if (!bytes)
return "";
else if (bytes > 1024 && typeof cockpit != "undefined")
return cockpit.format_bytes(bytes);
else
return bytes + " B";
};

/*
element.css("display", "block");

var outer = angular.element("<div/>");
element.append(outer);

var graph;

function update(layers, settings) {
*/
$scope.$watch('data', function(layers) {
if (layers && layers.length)
layers = layers.map(prepareLayer);
graph.data(layers, settings);
}

$scope.$watchCollection('[layers, settings]', function(values) {
if (graph)
update(values[0], values[1] || { });
});

$scope.$watch("prevent", function(prevent) {
if (!prevent && !graph) {
graph = layer_graph(outer[0]);
update($scope.layers, $scope.settings || { });
}
});

element.on("$destroy", function() {
graph.close();
layers = layers.map(prepareLayer).reverse();
$scope.layers = layers;
});
}
};
Expand Down
9 changes: 9 additions & 0 deletions pkg/kubernetes/styles/images.less
Expand Up @@ -106,3 +106,12 @@ pre {
color: @metadata-color;
padding: 0px 5px;
}

.image-metadata-layers {
padding-top: 15px;
clear: both;

p {
margin-bottom: 3px;
}
}
69 changes: 51 additions & 18 deletions pkg/kubernetes/styles/layers.less
@@ -1,34 +1,67 @@
image-layers {
border: 1px solid @sidebar-pf-border-color;
box-sizing: border-box;
padding: 10px 15px;
margin-bottom: 15px;
display: block;
overflow-y: auto;
max-height: 200px;
}

.image-layers {
overflow: hidden;
ul.image-layers {
list-style: none;
background-image: -webkit-linear-gradient(top, #fff 12px, #d3d3d3 15px);
background-image: -moz-linear-gradient(top, #fff 12px, #d3d3d3 15px);
background-image: -o-linear-gradient(top, #fff 12px, #d3d3d3 15px);
background-image: linear-gradient(top, #fff 12px, #d3d3d3 15px);
background-size: 2px 100%;
background-repeat: no-repeat;
margin: 8px 10px 15px 15px;
padding-left: 0px;
font-size: 13px;

.column {
fill-opacity: 0;
cursor: pointer;
li {
clear: both;
}

.layer {
fill: #4d4d4d;
li:before {
content: "\f111"; /* circle */
font-family: FontAwesome;
font-size: 19px;
line-height: 19px;
float: left;
display: block;
color: #0099ff;
position: relative;
left: -7px;
top: 1px;
padding-bottom: 9px;
}

.selected .layer {
fill: @link-color;
li.hint-add:before {
color: #555753;
}

.line {
stroke: @link-color;
stroke-width: 1px;
fill: transparent;
li.hint-run:before {
color: #4e9a06;
}

.label {
li:first-child:before {
content: "\f0ab"; /* fa-circle-arrow */
}

li:last-child:before {
top: 2px;
background-color: white;
}

span {
display: block;
float: right;
}

p {
font-family: monospace;
font-weight: normal;
margin-bottom: 0px;
overflow: hidden;
text-overflow: ellipsis;
padding-right: 15px;
}
}