From f8c80f7d111bd14df35ef7c2f77f42887b934342 Mon Sep 17 00:00:00 2001 From: Joseph Schorr Date: Wed, 15 Apr 2015 15:21:09 -0400 Subject: [PATCH] Add a history view to the tags page. Next step will add the ability to revert back in time --- data/model/legacy.py | 10 ++ endpoints/api/tag.py | 37 ++++- initdb.py | 16 +- .../directives/repo-view/repo-panel-tags.css | 127 +++++++++++++++ static/css/directives/ui/image-link.css | 4 + static/directives/image-link.html | 2 + .../directives/repo-view/repo-panel-tags.html | 83 +++++++++- .../directives/repo-view/repo-panel-tags.js | 151 ++++++++++++++++++ static/js/directives/ui/image-link.js | 19 +++ test/test_api_security.py | 56 ++++++- 10 files changed, 492 insertions(+), 13 deletions(-) create mode 100644 static/css/directives/ui/image-link.css create mode 100644 static/directives/image-link.html create mode 100644 static/js/directives/ui/image-link.js diff --git a/data/model/legacy.py b/data/model/legacy.py index 6f8ded0a7..7ec27eed9 100644 --- a/data/model/legacy.py +++ b/data/model/legacy.py @@ -1762,6 +1762,16 @@ def _tag_alive(query, now_ts=None): (RepositoryTag.lifetime_end_ts > now_ts)) +def list_repository_tag_history(repository, limit=100): + query = (RepositoryTag + .select(RepositoryTag, Image) + .join(Image) + .where(RepositoryTag.repository == repository) + .order_by(RepositoryTag.lifetime_start_ts.desc()) + .limit(limit)) + return query + + def list_repository_tags(namespace_name, repository_name, include_hidden=False, include_storage=False): diff --git a/endpoints/api/tag.py b/endpoints/api/tag.py index d5d7c94df..f698be851 100644 --- a/endpoints/api/tag.py +++ b/endpoints/api/tag.py @@ -1,12 +1,45 @@ -from flask import request +from flask import request, abort from endpoints.api import (resource, nickname, require_repo_read, require_repo_write, RepositoryParamResource, log_action, NotFound, validate_json_request, - path_param) + path_param, format_date) from endpoints.api.image import image_view from data import model from auth.auth_context import get_authenticated_user +from datetime import datetime + + + +@resource('/v1/repository//tag/') +@path_param('repository', 'The full path of the repository. e.g. namespace/name') +class ListRepositoryTags(RepositoryParamResource): + """ Resource for listing repository tags. """ + + @require_repo_write + @nickname('listRepoTags') + def get(self, namespace, repository): + repo = model.get_repository(namespace, repository) + if not repo: + abort(404) + + def tag_view(tag): + tag_info = { + 'name': tag.name, + 'docker_image_id': tag.image.docker_image_id, + } + + if tag.lifetime_start_ts > 0: + tag_info['start_ts'] = tag.lifetime_start_ts + + if tag.lifetime_end_ts > 0: + tag_info['end_ts'] = tag.lifetime_end_ts + + return tag_info + + tags = model.list_repository_tag_history(repo, limit=100) + return {'tags': [tag_view(tag) for tag in tags]} + @resource('/v1/repository//tag/') @path_param('repository', 'The full path of the repository. e.g. namespace/name') diff --git a/initdb.py b/initdb.py index 104e0fc19..0788884cb 100644 --- a/initdb.py +++ b/initdb.py @@ -57,7 +57,7 @@ def __gen_image_uuid(repo, image_num): global_image_num = [0] -def __create_subtree(repo, structure, creator_username, parent): +def __create_subtree(repo, structure, creator_username, parent, tag_map): num_nodes, subtrees, last_node_tags = structure # create the nodes @@ -102,12 +102,18 @@ def __create_subtree(repo, structure, creator_username, parent): tag = model.create_or_update_tag(repo.namespace_user.username, repo.name, tag_name, new_image.docker_image_id) + tag_map[tag_name] = tag + + for tag_name in last_node_tags: if tag_name[0] == '#': - tag.lifetime_end_ts = get_epoch_timestamp() - 1 + tag = tag_map[tag_name] + tag.name = tag_name[1:] + tag.lifetime_end_ts = tag_map[tag_name[1:]].lifetime_start_ts + tag.lifetime_start_ts = tag.lifetime_end_ts - 10 tag.save() for subtree in subtrees: - __create_subtree(repo, subtree, creator_username, new_image) + __create_subtree(repo, subtree, creator_username, new_image, tag_map) def __generate_repository(user, name, description, is_public, permissions, @@ -127,9 +133,9 @@ def __generate_repository(user, name, description, is_public, permissions, if isinstance(structure, list): for s in structure: - __create_subtree(repo, s, user.username, None) + __create_subtree(repo, s, user.username, None, {}) else: - __create_subtree(repo, structure, user.username, None) + __create_subtree(repo, structure, user.username, None, {}) return repo diff --git a/static/css/directives/repo-view/repo-panel-tags.css b/static/css/directives/repo-view/repo-panel-tags.css index 9df9c2679..1a81a5604 100644 --- a/static/css/directives/repo-view/repo-panel-tags.css +++ b/static/css/directives/repo-view/repo-panel-tags.css @@ -68,4 +68,131 @@ .repo-panel-tags-element .options-col .fa-download { color: #999; cursor: pointer; +} + +.repo-panel-tags-element .history-list { + margin: 10px; + border-left: 2px solid #eee; +} + +.repo-panel-tags-element .history-entry { + position:relative; + margin-top: 20px; + padding-left: 26px; + + transition: all 350ms ease-in-out; +} + +.repo-panel-tags-element .history-entry .history-text { + transition: transform 350ms ease-in-out, opacity 350ms ease-in-out; + overflow: hidden; + height: 40px; +} + +.repo-panel-tags-element .history-entry.filtered-mismatch { + margin-top: 10px; +} + +.repo-panel-tags-element .history-entry.filtered-mismatch .history-text { + height: 18px; + opacity: 0; +} + +.repo-panel-tags-element .history-entry.filtered-mismatch .history-icon { + opacity: 0.5; + transform: scale(0.5, 0.5); +} + +.repo-panel-tags-element .history-entry .history-date-break { + font-size: 16px; +} + +.repo-panel-tags-element .history-entry .history-date-break:before { + content: ""; + position: absolute; + border-radius: 50%; + width: 12px; + height: 12px; + background: #ccc; + top: 4px; + left: -7px; +} + +.repo-panel-tags-element .history-entry .history-icon { + border-radius: 50%; + width: 32px; + height: 32px; + line-height: 33px; + text-align: center; + font-size: 20px; + color: white; + background: #ccc; + + position: absolute; + left: -17px; + top: -4px; + display: inline-block; + + transition: all 350ms ease-in-out; +} + +.repo-panel-tags-element .history-entry.move .history-icon:before { + content: "\f061"; + font-family: FontAwesome; +} + +.repo-panel-tags-element .history-entry.create .history-icon:before { + content: "\f02b"; + font-family: FontAwesome; +} + +.repo-panel-tags-element .history-entry.delete .history-icon:before { + content: "\f014"; + font-family: FontAwesome; +} + +.repo-panel-tags-element .history-entry.move .history-icon { + background-color: #1f77b4; +} + +.repo-panel-tags-element .history-entry.create .history-icon { + background-color: #98df8a; +} + +.repo-panel-tags-element .history-entry.delete .history-icon { + background-color: #ff9896; +} + +.repo-panel-tags-element .history-entry .history-icon .fa-tag { + margin-right: 0px; +} + +.repo-panel-tags-element .history-entry .tag-span { + display: inline-block; + border-radius: 4px; + padding: 2px; + background: #eee; + padding-right: 6px; + color: black; + cursor: pointer; +} + +.repo-panel-tags-element .history-entry .tag-span.checked { + background: #F6FCFF; +} + +.repo-panel-tags-element .history-entry .tag-span:before { + content: "\f02b"; + font-family: FontAwesome; + margin-left: 4px; + margin-right: 4px; +} + +.repo-panel-tags-element .history-entry .history-description { + color: #777; +} + +.repo-panel-tags-element .history-entry .history-datetime { + font-size: 12px; + color: #ccc; } \ No newline at end of file diff --git a/static/css/directives/ui/image-link.css b/static/css/directives/ui/image-link.css new file mode 100644 index 000000000..03e39f3d4 --- /dev/null +++ b/static/css/directives/ui/image-link.css @@ -0,0 +1,4 @@ +.image-link a { + font-family: Consolas, "Lucida Console", Monaco, monospace; + font-size: 12px; +} \ No newline at end of file diff --git a/static/directives/image-link.html b/static/directives/image-link.html new file mode 100644 index 000000000..a41bbd33d --- /dev/null +++ b/static/directives/image-link.html @@ -0,0 +1,2 @@ +{{ imageId.substr(0, 12) }} \ No newline at end of file diff --git a/static/directives/repo-view/repo-panel-tags.html b/static/directives/repo-view/repo-panel-tags.html index 402312c69..575d91dcc 100644 --- a/static/directives/repo-view/repo-panel-tags.html +++ b/static/directives/repo-view/repo-panel-tags.html @@ -1,6 +1,72 @@
+
+
+ + +
+
+

Repository Tags

-
+ + +
+
+
+
+ +
+ + + +
+ +
+
+
This repository is empty.
+
Push a tag or initiate a build to populate this repository.
+
+ +
+
+ {{ entry.date | amDateFormat:'dddd, MMMM Do YYYY' }} +
+
+
+
+
+ {{ entry.tag_name }} + + + was created pointing to image + + + was deleted + + + was moved to image + + from image + + + +
+
{{ entry.time | amDateFormat:'dddd, MMMM Do YYYY, h:mm:ss a' }}
+
+
+
+
+
+ + +
@@ -23,7 +89,12 @@ Visualize -