Work in progress: new image view
This commit is contained in:
parent
3959ea2ff9
commit
049148cb87
9 changed files with 325 additions and 92 deletions
|
@ -9,7 +9,7 @@ from data import model
|
||||||
from util.cache import cache_control_flask_restful
|
from util.cache import cache_control_flask_restful
|
||||||
|
|
||||||
|
|
||||||
def image_view(image, image_map):
|
def image_view(image, image_map, include_locations=True, include_ancestors=True):
|
||||||
extended_props = image
|
extended_props = image
|
||||||
if image.storage and image.storage.id:
|
if image.storage and image.storage.id:
|
||||||
extended_props = image.storage
|
extended_props = image.storage
|
||||||
|
@ -20,24 +20,35 @@ def image_view(image, image_map):
|
||||||
if not aid or not aid in image_map:
|
if not aid or not aid in image_map:
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
return image_map[aid]
|
return image_map[aid].docker_image_id
|
||||||
|
|
||||||
# Calculate the ancestors string, with the DBID's replaced with the docker IDs.
|
image_data = {
|
||||||
ancestors = [docker_id(a) for a in image.ancestors.split('/')]
|
|
||||||
ancestors_string = '/'.join(ancestors)
|
|
||||||
|
|
||||||
return {
|
|
||||||
'id': image.docker_image_id,
|
'id': image.docker_image_id,
|
||||||
'created': format_date(extended_props.created),
|
'created': format_date(extended_props.created),
|
||||||
'comment': extended_props.comment,
|
'comment': extended_props.comment,
|
||||||
'command': json.loads(command) if command else None,
|
'command': json.loads(command) if command else None,
|
||||||
'size': extended_props.image_size,
|
'size': extended_props.image_size,
|
||||||
'locations': list(image.storage.locations),
|
|
||||||
'uploading': image.storage.uploading,
|
'uploading': image.storage.uploading,
|
||||||
'ancestors': ancestors_string,
|
'sort_index': len(image.ancestors),
|
||||||
'sort_index': len(image.ancestors)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if include_locations:
|
||||||
|
image_data['locations'] = list(image.storage.locations)
|
||||||
|
|
||||||
|
if include_ancestors:
|
||||||
|
# Calculate the ancestors string, with the DBID's replaced with the docker IDs.
|
||||||
|
ancestors = [docker_id(a) for a in image.ancestors.split('/')]
|
||||||
|
image_data['ancestors'] = '/'.join(ancestors)
|
||||||
|
|
||||||
|
return image_data
|
||||||
|
|
||||||
|
|
||||||
|
def historical_image_view(image, image_map):
|
||||||
|
ancestors = [image_map[a] for a in image.ancestors.split('/')[1:-1]]
|
||||||
|
normal_view = image_view(image, image_map)
|
||||||
|
normal_view['history'] = [image_view(parent, image_map, False, False) for parent in ancestors]
|
||||||
|
return normal_view
|
||||||
|
|
||||||
|
|
||||||
@resource('/v1/repository/<repopath:repository>/image/')
|
@resource('/v1/repository/<repopath:repository>/image/')
|
||||||
@path_param('repository', 'The full path of the repository. e.g. namespace/name')
|
@path_param('repository', 'The full path of the repository. e.g. namespace/name')
|
||||||
|
@ -62,7 +73,7 @@ class RepositoryImageList(RepositoryParamResource):
|
||||||
filtered_images = []
|
filtered_images = []
|
||||||
for image in all_images:
|
for image in all_images:
|
||||||
if str(image.id) in found_image_ids:
|
if str(image.id) in found_image_ids:
|
||||||
image_map[str(image.id)] = image.docker_image_id
|
image_map[str(image.id)] = image
|
||||||
filtered_images.append(image)
|
filtered_images.append(image)
|
||||||
|
|
||||||
def add_tags(image_json):
|
def add_tags(image_json):
|
||||||
|
@ -90,9 +101,9 @@ class RepositoryImage(RepositoryParamResource):
|
||||||
# Lookup all the ancestor images for the image.
|
# Lookup all the ancestor images for the image.
|
||||||
image_map = {}
|
image_map = {}
|
||||||
for current_image in model.get_parent_images(namespace, repository, image):
|
for current_image in model.get_parent_images(namespace, repository, image):
|
||||||
image_map[str(current_image.id)] = image.docker_image_id
|
image_map[str(current_image.id)] = current_image
|
||||||
|
|
||||||
return image_view(image, image_map)
|
return historical_image_view(image, image_map)
|
||||||
|
|
||||||
|
|
||||||
@resource('/v1/repository/<repopath:repository>/image/<image_id>/changes')
|
@resource('/v1/repository/<repopath:repository>/image/<image_id>/changes')
|
||||||
|
|
|
@ -92,7 +92,7 @@ class RepositoryTagImages(RepositoryParamResource):
|
||||||
parent_images = model.get_parent_images(namespace, repository, tag_image)
|
parent_images = model.get_parent_images(namespace, repository, tag_image)
|
||||||
image_map = {}
|
image_map = {}
|
||||||
for image in parent_images:
|
for image in parent_images:
|
||||||
image_map[str(image.id)] = image.docker_image_id
|
image_map[str(image.id)] = image
|
||||||
|
|
||||||
parents = list(parent_images)
|
parents = list(parent_images)
|
||||||
parents.reverse()
|
parents.reverse()
|
||||||
|
|
76
static/css/directives/ui/image-view-layer.css
Normal file
76
static/css/directives/ui/image-view-layer.css
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
.image-view-layer-element {
|
||||||
|
position: relative;
|
||||||
|
padding: 10px;
|
||||||
|
padding-left: 170px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-view-layer-element .image-id {
|
||||||
|
font-family: monospace;
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
left: 10px;
|
||||||
|
width: 110px;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-view-layer-element .image-id a {
|
||||||
|
color: #aaa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-view-layer-element.first .image-id {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 110%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-view-layer-element.first .image-id a {
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-view-layer-element .nondocker-command {
|
||||||
|
font-family: monospace;
|
||||||
|
padding: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-view-layer-element .nondocker-command:before {
|
||||||
|
content: "\f120";
|
||||||
|
font-family: "FontAwesome";
|
||||||
|
font-size: 16px;
|
||||||
|
margin-right: 6px;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-view-layer-element .image-layer-line {
|
||||||
|
position: absolute;
|
||||||
|
top: 0px;
|
||||||
|
bottom: 0px;
|
||||||
|
left: 140px;
|
||||||
|
|
||||||
|
border-left: 2px solid #428bca;
|
||||||
|
width: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-view-layer-element.first .image-layer-line {
|
||||||
|
top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-view-layer-element.last .image-layer-line {
|
||||||
|
bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-view-layer-element .image-layer-dot {
|
||||||
|
position: absolute;
|
||||||
|
top: 14px;
|
||||||
|
left: 135px;
|
||||||
|
border: 2px solid #428bca;
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
background: white;
|
||||||
|
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.image-view-layer-element.first .image-layer-dot {
|
||||||
|
background: #428bca;
|
||||||
|
}
|
16
static/css/pages/image-view.css
Normal file
16
static/css/pages/image-view.css
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
.image-view .image-view-header {
|
||||||
|
padding: 10px;
|
||||||
|
background: #e8f1f6;
|
||||||
|
margin: -10px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-view .image-view-header .section-icon {
|
||||||
|
margin-right: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-view .image-view-header .section {
|
||||||
|
padding: 4px;
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: 20px;
|
||||||
|
}
|
15
static/directives/image-view-layer.html
Normal file
15
static/directives/image-view-layer.html
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
<div class="image-view-layer-element" ng-class="getClass()">
|
||||||
|
<div class="image-id">
|
||||||
|
<a href="/repository/{{ repository.namespace }}/{{ repository.name }}/image/{{ image.id }}">
|
||||||
|
{{ image.id.substr(0, 12) }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="image-comment" ng-if="image.comment">{{ image.comment }}</div>
|
||||||
|
<div class="image-command">
|
||||||
|
<div class="nondocker-command" ng-if="!getDockerfileCommand(image.command) && image.command.length">{{ image.command.join(' ') }}</div>
|
||||||
|
<div class="dockerfile-command" command="getDockerfileCommand(image.command)"
|
||||||
|
ng-if="getDockerfileCommand(image.command)"></div>
|
||||||
|
</div>
|
||||||
|
<div class="image-layer-dot"></div>
|
||||||
|
<div class="image-layer-line"></div>
|
||||||
|
</div>
|
48
static/js/directives/ui/image-view-layer.js
Normal file
48
static/js/directives/ui/image-view-layer.js
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
/**
|
||||||
|
* An element which displays a single layer representing an image in the image view.
|
||||||
|
*/
|
||||||
|
angular.module('quay').directive('imageViewLayer', function () {
|
||||||
|
var directiveDefinitionObject = {
|
||||||
|
priority: 0,
|
||||||
|
templateUrl: '/static/directives/image-view-layer.html',
|
||||||
|
replace: false,
|
||||||
|
transclude: true,
|
||||||
|
restrict: 'C',
|
||||||
|
scope: {
|
||||||
|
'repository': '=repository',
|
||||||
|
'image': '=image',
|
||||||
|
'images': '=images'
|
||||||
|
},
|
||||||
|
controller: function($scope, $element) {
|
||||||
|
$scope.getDockerfileCommand = function(command) {
|
||||||
|
if (!command) { return ''; }
|
||||||
|
|
||||||
|
// ["/bin/sh", "-c", "#(nop) RUN foo"]
|
||||||
|
var commandPrefix = '#(nop)'
|
||||||
|
|
||||||
|
if (command.length != 3) { return ''; }
|
||||||
|
if (command[0] != '/bin/sh' || command[1] != '-c') { return ''; }
|
||||||
|
|
||||||
|
var cmd = command[2];
|
||||||
|
|
||||||
|
if (cmd.substring(0, commandPrefix.length) != commandPrefix) { return ''; }
|
||||||
|
|
||||||
|
return command[2].substr(commandPrefix.length + 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.getClass = function() {
|
||||||
|
var index = $.inArray($scope.image, $scope.images);
|
||||||
|
if (index < 0) {
|
||||||
|
return 'first';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index == $scope.images.length - 1) {
|
||||||
|
return 'last';
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return directiveDefinitionObject;
|
||||||
|
});
|
|
@ -3,10 +3,48 @@
|
||||||
* Page to view the details of a single image.
|
* Page to view the details of a single image.
|
||||||
*/
|
*/
|
||||||
angular.module('quayPages').config(['pages', function(pages) {
|
angular.module('quayPages').config(['pages', function(pages) {
|
||||||
pages.create('image-view', 'image-view.html', ImageViewCtrl);
|
pages.create('image-view', 'image-view.html', ImageViewCtrl, {
|
||||||
|
'newLayout': true,
|
||||||
|
'title': '{{ image.id }}',
|
||||||
|
'description': 'Image {{ image.id }}'
|
||||||
|
}, ['layout'])
|
||||||
|
|
||||||
|
pages.create('image-view', 'old-image-view.html', OldImageViewCtrl, {
|
||||||
|
}, ['old-layout']);
|
||||||
}]);
|
}]);
|
||||||
|
|
||||||
function ImageViewCtrl($scope, $routeParams, $rootScope, $timeout, ApiService, ImageMetadataService) {
|
function ImageViewCtrl($scope, $routeParams, $rootScope, ApiService, ImageMetadataService) {
|
||||||
|
var namespace = $routeParams.namespace;
|
||||||
|
var name = $routeParams.name;
|
||||||
|
var imageid = $routeParams.image;
|
||||||
|
|
||||||
|
var loadImage = function() {
|
||||||
|
var params = {
|
||||||
|
'repository': namespace + '/' + name,
|
||||||
|
'image_id': imageid
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.imageResource = ApiService.getImageAsResource(params).get(function(image) {
|
||||||
|
$scope.image = image;
|
||||||
|
$scope.reversedHistory = image.history.reverse();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
var loadRepository = function() {
|
||||||
|
var params = {
|
||||||
|
'repository': namespace + '/' + name
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.repositoryResource = ApiService.getRepoAsResource(params).get(function(repo) {
|
||||||
|
$scope.repository = repo;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
loadImage();
|
||||||
|
loadRepository();
|
||||||
|
}
|
||||||
|
|
||||||
|
function OldImageViewCtrl($scope, $routeParams, $rootScope, $timeout, ApiService, ImageMetadataService) {
|
||||||
var namespace = $routeParams.namespace;
|
var namespace = $routeParams.namespace;
|
||||||
var name = $routeParams.name;
|
var name = $routeParams.name;
|
||||||
var imageid = $routeParams.image;
|
var imageid = $routeParams.image;
|
||||||
|
|
|
@ -1,82 +1,29 @@
|
||||||
<div class="resource-view" resource="image" error-message="'No image found'">
|
<div class="resource-view image-view"
|
||||||
<div class="cor-container repo repo-image-view">
|
resources="[repositoryResource, imageResource]"
|
||||||
<div class="header">
|
error-message="'Image not found'">
|
||||||
<a href="{{ '/repository/' + repo.namespace + '/' + repo.name }}" class="back"><i class="fa fa-chevron-left"></i></a>
|
<div class="page-content">
|
||||||
<h3>
|
<div class="cor-title">
|
||||||
<span class="repo-circle no-background" repo="repo"></span>
|
<span class="cor-title-link">
|
||||||
<span class="repo-breadcrumb" repo="repo" image="image.value"></span>
|
<a class="back-link" href="/repository/{{ repository.namespace }}/{{ repository.name }}?tab=tags">
|
||||||
</h3>
|
<i class="fa fa-hdd-o" style="margin-right: 4px"></i>
|
||||||
</div>
|
{{ repository.namespace }}/{{ repository.name }}
|
||||||
|
</a>
|
||||||
<!-- Comment -->
|
</span>
|
||||||
<blockquote ng-show="image.value.comment">
|
<span class="cor-title-content">
|
||||||
<span class="markdown-view" content="image.value.comment"></span>
|
<i class="fa fa-database fa-lg" style="margin-right: 10px"></i>
|
||||||
</blockquote>
|
{{ image.id.substr(0, 12) }}
|
||||||
|
|
||||||
<!-- Information -->
|
|
||||||
<dl class="dl-normal">
|
|
||||||
<dt>Full Image ID</dt>
|
|
||||||
<dd>
|
|
||||||
<div class="copy-box" value="image.value.id"></div>
|
|
||||||
</dd>
|
|
||||||
<dt>Created</dt>
|
|
||||||
<dd am-time-ago="parseDate(image.value.created)"></dd>
|
|
||||||
<dt>Compressed Image Size</dt>
|
|
||||||
<dd><span class="context-tooltip"
|
|
||||||
data-title="The amount of data sent between Docker and the registry when pushing/pulling"
|
|
||||||
bs-tooltip="tooltip.title" data-container="body">{{ image.value.size | bytes }}</span>
|
|
||||||
</dd>
|
|
||||||
|
|
||||||
<dt ng-show="image.value.command && image.value.command.length">Command</dt>
|
|
||||||
<dd ng-show="image.value.command && image.value.command.length">
|
|
||||||
<pre class="formatted-command">{{ getFormattedCommand(image.value) }}</pre>
|
|
||||||
</dd>
|
|
||||||
</dl>
|
|
||||||
|
|
||||||
<!-- Changes tabs -->
|
|
||||||
<div ng-show="combinedChanges.length > 0">
|
|
||||||
<b>File Changes:</b>
|
|
||||||
<br>
|
|
||||||
<br>
|
|
||||||
<ul class="nav nav-tabs">
|
|
||||||
<li class="active"><a href="javascript:void(0)" data-toggle="tab" data-target="#filterable">Filterable View</a></li>
|
|
||||||
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#tree" ng-click="initializeTree()">Tree View</a></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Changes tab content -->
|
|
||||||
<div class="tab-content" ng-show="combinedChanges.length > 0">
|
|
||||||
<!-- Filterable view -->
|
|
||||||
<div class="tab-pane active" id="filterable">
|
|
||||||
<div class="changes-container full-changes-container">
|
|
||||||
<div class="change-side-controls">
|
|
||||||
<div class="result-count">
|
|
||||||
Showing {{(combinedChanges | filter:search | limitTo:50).length}} of {{(combinedChanges | filter:search).length}} results
|
|
||||||
</div>
|
|
||||||
<div class="filter-input">
|
|
||||||
<input id="change-filter" class="form-control" placeholder="Filter Changes" type="text" ng-model="search.$">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div style="height: 28px;"></div>
|
|
||||||
<div class="changes-list well well-sm">
|
|
||||||
<div ng-show="(combinedChanges | filter:search | limitTo:1).length == 0">
|
|
||||||
No matching changes
|
|
||||||
</div>
|
|
||||||
<div class="change" ng-repeat="change in combinedChanges | filter:search | limitTo:50">
|
|
||||||
<i ng-class="{'added': 'fa fa-plus-square', 'removed': 'fa fa-minus-square', 'changed': 'fa fa-pencil-square'}[change.kind]"></i>
|
|
||||||
<span data-title="{{change.file}}">
|
|
||||||
<span style="color: #888;">
|
|
||||||
<span ng-repeat="folder in getFolders(change.file) track by $index"><a href="javascript:void(0)" ng-click="setFolderFilter(getFolder(change.file), $index)">{{folder}}</a>/</span></span><span>{{getFilename(change.file)}}</span>
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
<div class="co-main-content-panel">
|
||||||
|
<div class="image-view-header">
|
||||||
|
<div class="image-id section"><i class="fa fa-code section-icon" bs-tooltip="tooltip.title" data-title="Full Image ID"></i> {{ image.id }}</div>
|
||||||
|
<div class="created section"><i class="fa fa-calendar section-icon" bs-tooltip="tooltip.title" data-title="Full Image ID"></i> {{ image.created | amDateFormat:'dddd, MMMM Do YYYY, h:mm:ss a' }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Tree view -->
|
<div class="image-view-layer" repository="repository" image="image" images="image.history"></div>
|
||||||
<div class="tab-pane" id="tree">
|
<div class="image-view-layer" repository="repository" image="parent" images="image.history"
|
||||||
<div id="changes-tree-container" class="changes-container" onresize="tree && tree.notifyResized()"></div>
|
ng-repeat="parent in reversedHistory"></div>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
82
static/partials/old-image-view.html
Normal file
82
static/partials/old-image-view.html
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
<div class="resource-view" resource="image" error-message="'No image found'">
|
||||||
|
<div class="cor-container repo repo-image-view">
|
||||||
|
<div class="header">
|
||||||
|
<a href="{{ '/repository/' + repo.namespace + '/' + repo.name }}" class="back"><i class="fa fa-chevron-left"></i></a>
|
||||||
|
<h3>
|
||||||
|
<span class="repo-circle no-background" repo="repo"></span>
|
||||||
|
<span class="repo-breadcrumb" repo="repo" image="image.value"></span>
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Comment -->
|
||||||
|
<blockquote ng-show="image.value.comment">
|
||||||
|
<span class="markdown-view" content="image.value.comment"></span>
|
||||||
|
</blockquote>
|
||||||
|
|
||||||
|
<!-- Information -->
|
||||||
|
<dl class="dl-normal">
|
||||||
|
<dt>Full Image ID</dt>
|
||||||
|
<dd>
|
||||||
|
<div class="copy-box" value="image.value.id"></div>
|
||||||
|
</dd>
|
||||||
|
<dt>Created</dt>
|
||||||
|
<dd am-time-ago="parseDate(image.value.created)"></dd>
|
||||||
|
<dt>Compressed Image Size</dt>
|
||||||
|
<dd><span class="context-tooltip"
|
||||||
|
data-title="The amount of data sent between Docker and the registry when pushing/pulling"
|
||||||
|
bs-tooltip="tooltip.title" data-container="body">{{ image.value.size | bytes }}</span>
|
||||||
|
</dd>
|
||||||
|
|
||||||
|
<dt ng-show="image.value.command && image.value.command.length">Command</dt>
|
||||||
|
<dd ng-show="image.value.command && image.value.command.length">
|
||||||
|
<pre class="formatted-command">{{ getFormattedCommand(image.value) }}</pre>
|
||||||
|
</dd>
|
||||||
|
</dl>
|
||||||
|
|
||||||
|
<!-- Changes tabs -->
|
||||||
|
<div ng-show="combinedChanges.length > 0">
|
||||||
|
<b>File Changes:</b>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<ul class="nav nav-tabs">
|
||||||
|
<li class="active"><a href="javascript:void(0)" data-toggle="tab" data-target="#filterable">Filterable View</a></li>
|
||||||
|
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#tree" ng-click="initializeTree()">Tree View</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Changes tab content -->
|
||||||
|
<div class="tab-content" ng-show="combinedChanges.length > 0">
|
||||||
|
<!-- Filterable view -->
|
||||||
|
<div class="tab-pane active" id="filterable">
|
||||||
|
<div class="changes-container full-changes-container">
|
||||||
|
<div class="change-side-controls">
|
||||||
|
<div class="result-count">
|
||||||
|
Showing {{(combinedChanges | filter:search | limitTo:50).length}} of {{(combinedChanges | filter:search).length}} results
|
||||||
|
</div>
|
||||||
|
<div class="filter-input">
|
||||||
|
<input id="change-filter" class="form-control" placeholder="Filter Changes" type="text" ng-model="search.$">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="height: 28px;"></div>
|
||||||
|
<div class="changes-list well well-sm">
|
||||||
|
<div ng-show="(combinedChanges | filter:search | limitTo:1).length == 0">
|
||||||
|
No matching changes
|
||||||
|
</div>
|
||||||
|
<div class="change" ng-repeat="change in combinedChanges | filter:search | limitTo:50">
|
||||||
|
<i ng-class="{'added': 'fa fa-plus-square', 'removed': 'fa fa-minus-square', 'changed': 'fa fa-pencil-square'}[change.kind]"></i>
|
||||||
|
<span data-title="{{change.file}}">
|
||||||
|
<span style="color: #888;">
|
||||||
|
<span ng-repeat="folder in getFolders(change.file) track by $index"><a href="javascript:void(0)" ng-click="setFolderFilter(getFolder(change.file), $index)">{{folder}}</a>/</span></span><span>{{getFilename(change.file)}}</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Tree view -->
|
||||||
|
<div class="tab-pane" id="tree">
|
||||||
|
<div id="changes-tree-container" class="changes-container" onresize="tree && tree.notifyResized()"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
Reference in a new issue