Add support for build status tags, which link to the Quay.io repo
This commit is contained in:
parent
39eaca346d
commit
3f806b10c2
16 changed files with 228 additions and 34 deletions
1
buildstatus/building.svg
Normal file
1
buildstatus/building.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="146" height="18"><linearGradient id="a" x2="0" y2="100%"><stop offset="0" stop-color="#fff" stop-opacity=".7"/><stop offset=".1" stop-color="#aaa" stop-opacity=".1"/><stop offset=".9" stop-opacity=".3"/><stop offset="1" stop-opacity=".5"/></linearGradient><rect rx="4" width="146" height="18" fill="#555"/><rect rx="4" x="92" width="54" height="18" fill="#dfb317"/><path fill="#dfb317" d="M92 0h4v18h-4z"/><rect rx="4" width="146" height="18" fill="url(#a)"/><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11"><text x="47" y="13" fill="#010101" fill-opacity=".3">Docker Image</text><text x="47" y="12">Docker Image</text><text x="118" y="13" fill="#010101" fill-opacity=".3">building</text><text x="118" y="12">building</text></g></svg>
|
After Width: | Height: | Size: 835 B |
1
buildstatus/failed.svg
Normal file
1
buildstatus/failed.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="164" height="18"><linearGradient id="a" x2="0" y2="100%"><stop offset="0" stop-color="#fff" stop-opacity=".7"/><stop offset=".1" stop-color="#aaa" stop-opacity=".1"/><stop offset=".9" stop-opacity=".3"/><stop offset="1" stop-opacity=".5"/></linearGradient><rect rx="4" width="164" height="18" fill="#555"/><rect rx="4" x="92" width="72" height="18" fill="#e05d44"/><path fill="#e05d44" d="M92 0h4v18h-4z"/><rect rx="4" width="164" height="18" fill="url(#a)"/><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11"><text x="47" y="13" fill="#010101" fill-opacity=".3">Docker Image</text><text x="47" y="12">Docker Image</text><text x="127" y="13" fill="#010101" fill-opacity=".3">build failed</text><text x="127" y="12">build failed</text></g></svg>
|
After Width: | Height: | Size: 843 B |
1
buildstatus/none.svg
Normal file
1
buildstatus/none.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="130" height="18"><linearGradient id="a" x2="0" y2="100%"><stop offset="0" stop-color="#fff" stop-opacity=".7"/><stop offset=".1" stop-color="#aaa" stop-opacity=".1"/><stop offset=".9" stop-opacity=".3"/><stop offset="1" stop-opacity=".5"/></linearGradient><rect rx="4" width="130" height="18" fill="#555"/><rect rx="4" x="92" width="38" height="18" fill="#9f9f9f"/><path fill="#9f9f9f" d="M92 0h4v18h-4z"/><rect rx="4" width="130" height="18" fill="url(#a)"/><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11"><text x="47" y="13" fill="#010101" fill-opacity=".3">Docker Image</text><text x="47" y="12">Docker Image</text><text x="110" y="13" fill="#010101" fill-opacity=".3">none</text><text x="110" y="12">none</text></g></svg>
|
After Width: | Height: | Size: 827 B |
1
buildstatus/ready.svg
Normal file
1
buildstatus/ready.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="135" height="18"><linearGradient id="a" x2="0" y2="100%"><stop offset="0" stop-color="#fff" stop-opacity=".7"/><stop offset=".1" stop-color="#aaa" stop-opacity=".1"/><stop offset=".9" stop-opacity=".3"/><stop offset="1" stop-opacity=".5"/></linearGradient><rect rx="4" width="135" height="18" fill="#555"/><rect rx="4" x="92" width="43" height="18" fill="#4c1"/><path fill="#4c1" d="M92 0h4v18h-4z"/><rect rx="4" width="135" height="18" fill="url(#a)"/><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11"><text x="47" y="13" fill="#010101" fill-opacity=".3">Docker Image</text><text x="47" y="12">Docker Image</text><text x="112.5" y="13" fill="#010101" fill-opacity=".3">ready</text><text x="112.5" y="12">ready</text></g></svg>
|
After Width: | Height: | Size: 827 B |
|
@ -101,6 +101,7 @@ class Repository(BaseModel):
|
||||||
name = CharField()
|
name = CharField()
|
||||||
visibility = ForeignKeyField(Visibility)
|
visibility = ForeignKeyField(Visibility)
|
||||||
description = TextField(null=True)
|
description = TextField(null=True)
|
||||||
|
badge_token = CharField(default=uuid_generator)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
database = db
|
database = db
|
||||||
|
|
|
@ -1421,6 +1421,21 @@ def list_repository_builds(namespace_name, repository_name,
|
||||||
return query
|
return query
|
||||||
|
|
||||||
|
|
||||||
|
def get_recent_repository_build(namespace_name, repository_name):
|
||||||
|
query = (RepositoryBuild.select(RepositoryBuild)
|
||||||
|
.join(Repository)
|
||||||
|
.where(Repository.name == repository_name,
|
||||||
|
Repository.namespace == namespace_name)
|
||||||
|
.order_by(RepositoryBuild.started.desc())
|
||||||
|
.limit(1))
|
||||||
|
|
||||||
|
results = list(query)
|
||||||
|
if results:
|
||||||
|
return results[0]
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def create_repository_build(repo, access_token, job_config_obj, dockerfile_id,
|
def create_repository_build(repo, access_token, job_config_obj, dockerfile_id,
|
||||||
display_name, trigger=None):
|
display_name, trigger=None):
|
||||||
return RepositoryBuild.create(repository=repo, access_token=access_token,
|
return RepositoryBuild.create(repository=repo, access_token=access_token,
|
||||||
|
|
|
@ -1117,7 +1117,8 @@ def get_repo(namespace, repository):
|
||||||
'can_admin': can_admin,
|
'can_admin': can_admin,
|
||||||
'is_public': is_public,
|
'is_public': is_public,
|
||||||
'is_building': len(list(active_builds)) > 0,
|
'is_building': len(list(active_builds)) > 0,
|
||||||
'is_organization': bool(organization)
|
'is_organization': bool(organization),
|
||||||
|
'status_token': repo.badge_token if not is_public else ''
|
||||||
})
|
})
|
||||||
|
|
||||||
abort(404) # Not found
|
abort(404) # Not found
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import logging
|
import logging
|
||||||
import stripe
|
import stripe
|
||||||
|
import os
|
||||||
|
|
||||||
from flask import (abort, redirect, request, url_for, make_response, Response,
|
from flask import (abort, redirect, request, url_for, make_response, Response,
|
||||||
Blueprint)
|
Blueprint)
|
||||||
|
@ -13,7 +14,7 @@ from util.invoice import renderInvoiceToPdf
|
||||||
from util.seo import render_snapshot
|
from util.seo import render_snapshot
|
||||||
from util.cache import no_cache
|
from util.cache import no_cache
|
||||||
from endpoints.common import common_login, render_page_template
|
from endpoints.common import common_login, render_page_template
|
||||||
|
from util.names import parse_repository_name
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -196,3 +197,39 @@ def confirm_recovery():
|
||||||
return redirect(url_for('web.user'))
|
return redirect(url_for('web.user'))
|
||||||
else:
|
else:
|
||||||
abort(403)
|
abort(403)
|
||||||
|
|
||||||
|
|
||||||
|
@web.route('/repository/<path:repository>/status', methods=['GET'])
|
||||||
|
@parse_repository_name
|
||||||
|
@no_cache
|
||||||
|
def build_status_badge(namespace, repository):
|
||||||
|
token = request.args.get('token', None)
|
||||||
|
is_public = model.repository_is_public(namespace, repository)
|
||||||
|
if not is_public:
|
||||||
|
repo = model.get_repository(namespace, repository)
|
||||||
|
if not repo or token != repo.badge_token:
|
||||||
|
abort(404)
|
||||||
|
|
||||||
|
# Lookup the tags for the repository.
|
||||||
|
tags = model.list_repository_tags(namespace, repository)
|
||||||
|
|
||||||
|
# Lookup the current/recent build.
|
||||||
|
status = 'none'
|
||||||
|
build = model.get_recent_repository_build(namespace, repository)
|
||||||
|
if build:
|
||||||
|
if build.phase == 'error':
|
||||||
|
status = 'failed'
|
||||||
|
elif build.phase == 'complete':
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
status = 'building'
|
||||||
|
else:
|
||||||
|
if list(tags):
|
||||||
|
status = 'ready'
|
||||||
|
|
||||||
|
with open(os.path.join(app.root_path, 'buildstatus', status + '.svg')) as f:
|
||||||
|
svg = f.read()
|
||||||
|
|
||||||
|
response = make_response(svg)
|
||||||
|
response.content_type = 'image/svg+xml'
|
||||||
|
return response
|
||||||
|
|
|
@ -282,21 +282,6 @@ i.toggle-icon:hover {
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
#copyClipboard {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
#copyClipboard.zeroclipboard-is-hover {
|
|
||||||
background: #428bca;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
#clipboardCopied.hovering {
|
|
||||||
position: absolute;
|
|
||||||
right: 0px;
|
|
||||||
top: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content-container {
|
.content-container {
|
||||||
padding-bottom: 70px;
|
padding-bottom: 70px;
|
||||||
}
|
}
|
||||||
|
@ -1651,7 +1636,38 @@ p.editable:hover i {
|
||||||
margin-top: 28px;
|
margin-top: 28px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#clipboardCopied {
|
.copy-box-element {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.global-zeroclipboard-container embed {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
#copyClipboard.zeroclipboard-is-hover, .copy-box-element .zeroclipboard-is-hover {
|
||||||
|
background: #428bca;
|
||||||
|
color: white;
|
||||||
|
cursor: pointer !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#clipboardCopied.hovering, .copy-box-element .hovering {
|
||||||
|
position: absolute;
|
||||||
|
right: 0px;
|
||||||
|
top: 40px;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
.copy-box-element .id-container {
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.copy-box-element input {
|
||||||
|
background-color: white !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#clipboardCopied, .clipboard-copied-message {
|
||||||
font-size: 0.8em;
|
font-size: 0.8em;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
|
@ -1662,7 +1678,7 @@ p.editable:hover i {
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#clipboardCopied.animated {
|
#clipboardCopied.animated, .clipboard-copied-message {
|
||||||
-webkit-animation: fadeOut 4s ease-in-out 0s 1 forwards;
|
-webkit-animation: fadeOut 4s ease-in-out 0s 1 forwards;
|
||||||
-moz-animation: fadeOut 4s ease-in-out 0s 1 forwards;
|
-moz-animation: fadeOut 4s ease-in-out 0s 1 forwards;
|
||||||
-ms-animation: fadeOut 4s ease-in-out 0s 1 forwards;
|
-ms-animation: fadeOut 4s ease-in-out 0s 1 forwards;
|
||||||
|
|
14
static/directives/copy-box.html
Normal file
14
static/directives/copy-box.html
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
<div class="copy-box-element">
|
||||||
|
<div class="id-container">
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="text" class="form-control" value="{{ value }}" readonly>
|
||||||
|
<span class="input-group-addon" title="Copy to Clipboard">
|
||||||
|
<i class="fa fa-copy"></i>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="clipboard-copied-message" ng-class="hoveringMessage ? 'hovering' : ''" style="display: none">
|
||||||
|
Copied to clipboard
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -963,6 +963,55 @@ quayApp.directive('repoCircle', function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
quayApp.directive('copyBox', function () {
|
||||||
|
var directiveDefinitionObject = {
|
||||||
|
priority: 0,
|
||||||
|
templateUrl: '/static/directives/copy-box.html',
|
||||||
|
replace: false,
|
||||||
|
transclude: false,
|
||||||
|
restrict: 'C',
|
||||||
|
scope: {
|
||||||
|
'value': '=value',
|
||||||
|
'hoveringMessage': '=hoveringMessage'
|
||||||
|
},
|
||||||
|
controller: function($scope, $element, $rootScope) {
|
||||||
|
var number = $rootScope.__copyBoxIdCounter || 0;
|
||||||
|
$rootScope.__copyBoxIdCounter = number + 1;
|
||||||
|
$scope.inputId = "copy-box-input-" + number;
|
||||||
|
|
||||||
|
var button = $($element).find('.input-group-addon');
|
||||||
|
var input = $($element).find('input');
|
||||||
|
|
||||||
|
input.attr('id', $scope.inputId);
|
||||||
|
button.attr('data-clipboard-target', $scope.inputId);
|
||||||
|
|
||||||
|
var clip = new ZeroClipboard($(button), { 'moviePath': 'static/lib/ZeroClipboard.swf' });
|
||||||
|
clip.on('complete', function(e) {
|
||||||
|
var message = $(this.parentNode.parentNode.parentNode).find('.clipboard-copied-message')[0];
|
||||||
|
|
||||||
|
// Resets the animation.
|
||||||
|
var elem = message;
|
||||||
|
elem.style.display = 'none';
|
||||||
|
elem.classList.remove('animated');
|
||||||
|
|
||||||
|
// Show the notification.
|
||||||
|
setTimeout(function() {
|
||||||
|
elem.style.display = 'inline-block';
|
||||||
|
elem.classList.add('animated');
|
||||||
|
}, 10);
|
||||||
|
|
||||||
|
// Reset the notification.
|
||||||
|
setTimeout(function() {
|
||||||
|
elem.style.display = 'none';
|
||||||
|
}, 5000);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return directiveDefinitionObject;
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
quayApp.directive('userSetup', function () {
|
quayApp.directive('userSetup', function () {
|
||||||
var directiveDefinitionObject = {
|
var directiveDefinitionObject = {
|
||||||
priority: 0,
|
priority: 0,
|
||||||
|
|
|
@ -1162,6 +1162,30 @@ function RepoAdminCtrl($scope, Restangular, ApiService, KeyService, $routeParams
|
||||||
$scope.githubRedirectUri = KeyService.githubRedirectUri;
|
$scope.githubRedirectUri = KeyService.githubRedirectUri;
|
||||||
$scope.githubClientId = KeyService.githubClientId;
|
$scope.githubClientId = KeyService.githubClientId;
|
||||||
|
|
||||||
|
$scope.getBadgeFormat = function(format, repo) {
|
||||||
|
if (!repo) { return; }
|
||||||
|
|
||||||
|
var imageUrl = 'https://quay.io/repository/' + namespace + '/' + name + '/status';
|
||||||
|
if (!$scope.repo.is_public) {
|
||||||
|
imageUrl += '?token=' + $scope.repo.status_token;
|
||||||
|
}
|
||||||
|
|
||||||
|
var linkUrl = 'https://quay.io/repository/' + namespace + '/' + name;
|
||||||
|
|
||||||
|
switch (format) {
|
||||||
|
case 'svg':
|
||||||
|
return imageUrl;
|
||||||
|
|
||||||
|
case 'md':
|
||||||
|
return '[![Docker Repository on Quay.io](' + imageUrl + ')](' + linkUrl + ')';
|
||||||
|
|
||||||
|
case 'asciidoc':
|
||||||
|
return 'image:' + imageUrl + '["Docker Repository on Quay.io", link="' + linkUrl + '"]';
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
};
|
||||||
|
|
||||||
$scope.buildEntityForPermission = function(name, permission, kind) {
|
$scope.buildEntityForPermission = function(name, permission, kind) {
|
||||||
var key = name + ':' + kind;
|
var key = name + ':' + kind;
|
||||||
if ($scope.permissionCache[key]) {
|
if ($scope.permissionCache[key]) {
|
||||||
|
|
|
@ -17,20 +17,7 @@
|
||||||
<dl class="dl-normal">
|
<dl class="dl-normal">
|
||||||
<dt>Full Image ID</dt>
|
<dt>Full Image ID</dt>
|
||||||
<dd>
|
<dd>
|
||||||
<div>
|
<div class="copy-box" value="image.value.id"></div>
|
||||||
<div class="id-container">
|
|
||||||
<div class="input-group">
|
|
||||||
<input id="full-id" type="text" class="form-control" value="{{ image.value.id }}" readonly>
|
|
||||||
<span id="copyClipboard" class="input-group-addon" title="Copy to Clipboard" data-clipboard-target="full-id">
|
|
||||||
<i class="fa fa-copy"></i>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="clipboardCopied" style="display: none">
|
|
||||||
Copied to clipboard
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</dd>
|
</dd>
|
||||||
<dt>Created</dt>
|
<dt>Created</dt>
|
||||||
<dd am-time-ago="parseDate(image.value.created)"></dd>
|
<dd am-time-ago="parseDate(image.value.created)"></dd>
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
<ul class="nav nav-pills nav-stacked">
|
<ul class="nav nav-pills nav-stacked">
|
||||||
<li class="active"><a href="javascript:void(0)" data-toggle="tab" data-target="#permissions">Permissions</a></li>
|
<li class="active"><a href="javascript:void(0)" data-toggle="tab" data-target="#permissions">Permissions</a></li>
|
||||||
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#trigger" ng-click="loadTriggers()">Build Triggers</a></li>
|
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#trigger" ng-click="loadTriggers()">Build Triggers</a></li>
|
||||||
|
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#badge">Status Badge</a></li>
|
||||||
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#webhook" ng-click="loadWebhooks()">Webhooks</a></li>
|
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#webhook" ng-click="loadWebhooks()">Webhooks</a></li>
|
||||||
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#publicprivate">Public/Private</a></li>
|
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#publicprivate">Public/Private</a></li>
|
||||||
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#delete">Delete</a></li>
|
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#delete">Delete</a></li>
|
||||||
|
@ -35,6 +36,52 @@
|
||||||
<div class="logs-view" repository="repo" visible="logsShown"></div>
|
<div class="logs-view" repository="repo" visible="logsShown"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Badge tab -->
|
||||||
|
<div id="badge" class="tab-pane">
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-heading">Status Badge
|
||||||
|
<i class="info-icon fa fa-info-circle" data-placement="left" data-content="Embeddable widget for displaying the status of the repository"></i>
|
||||||
|
</div>
|
||||||
|
<div class="panel-body">
|
||||||
|
<div class="alert alert-warning" ng-if="!repo.is_public">
|
||||||
|
Note: This repository is currently <b>private</b>. Publishing this badge will reveal the status information of your repository (and links may
|
||||||
|
not work for unregistered users).
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Status Image -->
|
||||||
|
<a ng-href="/repository/{{ repo.namespace }}/{{ repo.name }}" ng-if="repo && repo.name">
|
||||||
|
<img ng-src="/repository/{{ repo.namespace }}/{{ repo.name }}/status?token={{ repo.status_token }}" title="Docker Repository on Quay.io">
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<!-- Embed formats -->
|
||||||
|
<table style="margin-top: 20px; width: 600px;">
|
||||||
|
<thead>
|
||||||
|
<th style="width: 150px"></th>
|
||||||
|
<th></th>
|
||||||
|
</thead>
|
||||||
|
<tr>
|
||||||
|
<td>Image (SVG):</td>
|
||||||
|
<td>
|
||||||
|
<div class="copy-box" hovering-message="true" value="getBadgeFormat('svg', repo)"></div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Markdown:</td>
|
||||||
|
<td>
|
||||||
|
<div class="copy-box" hovering-message="true" value="getBadgeFormat('md', repo)"></div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>AsciiDoc:</td>
|
||||||
|
<td>
|
||||||
|
<div class="copy-box" hovering-message="true" value="getBadgeFormat('asciidoc', repo)"></div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Permissions tab -->
|
<!-- Permissions tab -->
|
||||||
<div id="permissions" class="tab-pane active">
|
<div id="permissions" class="tab-pane active">
|
||||||
<!-- User Access Permissions -->
|
<!-- User Access Permissions -->
|
||||||
|
|
|
@ -68,7 +68,6 @@
|
||||||
Copied to clipboard
|
Copied to clipboard
|
||||||
</div>
|
</div>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
Binary file not shown.
Reference in a new issue