Merge remote-tracking branch 'upstream/phase4-11-07-2015' into python-registry-v2

This commit is contained in:
Jake Moshenko 2015-11-06 18:18:29 -05:00
commit c2fcf8bead
177 changed files with 4354 additions and 1462 deletions

View file

@ -1,3 +1,44 @@
### v1.13.2
- Fixed 404 API calls redirecting to 404 page (#762)
### v1.13.1
- Fixed broken database migration (#759)
- Added OpenGraph preview image (#750, #758)
### v1.13.0
- Added new Quay Enterprise rebranding (#723, #738, #735, #745, #746, #748, #747, #751)
- Added a styled 404 page (#683)
- Hid the run button from users that haven't created a trigger (#727)
- Added timeouts to calls to GitLab, Bitbucket, GitHub APIs (#636, #633, #631, #722)
- Added more fields to responses from user API (#681)
- Fixed bug where every repository appeared private in repository listings (#680)
- Added an error when geo-replication is enabled with local storage (#667)
- Enabled asynchronous garbage collection for all repositories (#665)
- Improved UX uploading Dockerfiles (#656)
- Improved registry resiliancy to missing image sizes (#643)
- Improved Teams UI (#647)
- Added a limit to logs pagination API (#603)
- Upgrade docker search to use the new search system (#595)
- Fixed database hostname validation to include "." and "\" (#579)
- Improved build system's resiliancy if operating without redis (#571)
- Updated repository name and namespace validation to match new docker behavior (#535, #644)
- Refactored and improved Build Trigger validation (#478, #523, #524, #527, #544, #561, #657, #686, #693, #734)
- Optimized moving tags (#520)
- Optimized database usage (#517, #518, #519, #598, #601, #605, #615, #641, #675)
- Migrated all GitHub triggers to use deploy keys (#503)
- Added ability to 'RUN cat .git/HEAD' to get git SHAs in builds (#504)
- Improved repository count limitations UI (#492, #529)
- Added a releases table to database (#495)
- Made repository deletion more robust (#497)
- Optimized Swift storage to support direct downloads (#484)
- Improved build logs UX (#482, #507)
- Add basic Kubernetes secret-store support (#272)
- Improved internal test suite (#470, #511, #526, #514, #545, #570, #572, #573, #583, #711, #728, #730)
- Improved background worker stability (#471)
### v1.12.0 ### v1.12.0
- Added experimental Dex login support (#447, #468) - Added experimental Dex login support (#447, #468)

View file

@ -6,7 +6,7 @@ ENV DEBIAN_FRONTEND noninteractive
ENV HOME /root ENV HOME /root
# Install the dependencies. # Install the dependencies.
RUN apt-get update # 23OCT2015 RUN apt-get update # 26OCT2015
# New ubuntu packages should be added as their own apt-get install lines below the existing install commands # New ubuntu packages should be added as their own apt-get install lines below the existing install commands
RUN apt-get install -y git python-virtualenv python-dev libjpeg8 libjpeg62 libjpeg62-dev libevent-2.0.5 libevent-dev gdebi-core g++ libmagic1 phantomjs nodejs npm libldap-2.4-2 libldap2-dev libsasl2-modules libsasl2-dev libpq5 libpq-dev libfreetype6-dev libffi-dev libgpgme11 libgpgme11-dev RUN apt-get install -y git python-virtualenv python-dev libjpeg8 libjpeg62 libjpeg62-dev libevent-2.0.5 libevent-dev gdebi-core g++ libmagic1 phantomjs nodejs npm libldap-2.4-2 libldap2-dev libsasl2-modules libsasl2-dev libpq5 libpq-dev libfreetype6-dev libffi-dev libgpgme11 libgpgme11-dev

View file

@ -1,8 +1,8 @@
# Quay.io - container image registry # Quay - container image registry
`master` branch build status: ![Docker Repository on Quay.io](https://quay.io/repository/quay/quay/status?token=7bffbc13-8bb0-4fb4-8a70-684a0cf485d3 "Docker Repository on Quay.io") `master` branch build status: ![Docker Repository on Quay](https://quay.io/repository/quay/quay/status?token=7bffbc13-8bb0-4fb4-8a70-684a0cf485d3 "Docker Repository on Quay")
Quay.io is a container image registry with managements APIs, a Docker registry API, a container build system. Quay is a container image registry with managements APIs, a Docker registry API, a container build system.
The application is implemented as a set of API endpoints written in python and an Angular.js frontend. The application is implemented as a set of API endpoints written in python and an Angular.js frontend.
## Setup Development Environment ## Setup Development Environment

View file

@ -1,38 +1,61 @@
# Quay.io Roadmap # Quay Roadmap
**work in progress** ### Sprint 11/4 - 11/18 DockerCon
- Launch Registry v2.0 API
- Launch Quay-Sec
### Short Term ### Sprint 11/18 - 12/1 Builds (Planned 8 Days)
- Framework for microservice decomposition - Move build traffic to Packet
- Improve documentation - Preliminary tests reduce build start latency from 2 minutes to 20 seconds
- Ability to answer 80% of tickets with a link to the docs
- Eliminate old UI screenshots/references
- Auth provider as a service
- Registry v2 compatible
### Medium Term
- Registry v2 support
- Forward and backward compatible with registry v1
- Support ACI push spec
- Translate between ACI and docker images transparently
- Integrate docs with the search bar
- Full text search?
- Running on top of Tectonic
- BitTorrent distribution support
- Fully launch our API
- Versioned and backward compatible
- Adequate documentation
### Long Term
- Become the Tectonic app store
- Pods/apps as top level concept
- Builds as top level concept
- Multiple Quay.io repos from a single git push
- Multi-step builds - Multi-step builds
- build artifact - build artifact
- bundle artifact - bundle artifact
- test bundle - test bundle
- Docker Notary
- Support signed images with a known key
- Give thanks
### Sprint 12/2 - 12/15 eBay Labels (Planned)
- Labels
- Support for Midas Package Manager-like distribution
- Integrated with Docker labels
- Mutable and immutable
- Searchable and fleshed out API
- Integrate with tectonic.com sales pipeline
- Mirror Quay customers in tectonic (SVOC)?
- Callbacks to inform tectonic about quay events
- Accept and apply QE licenses to the stack
### Sprint 12/16 - 12/29 Distribution (Planned 8 Days)
- Tectonic care and feeding
- Build tools to give us a concrete/declarative cluster deploy story
- Build a tool to migrate an app between tectonic clusters
- Assess the feasibility of upgrading a running cluster
- Geo distribution through tectonic
- Spin up a tectonic cluster in another region
- Modify registry to run standalone on a tectonic cluster
- Read available Quay.io
- Ability to choose uptime of data-plane auditability
### Sprint 12/30 - 1/12 (Planned 8 Days)
- Launch our API GA
- Versioned and backward compatible
- Adequate documentation
### Unallocated
- Builds as top level concept
- Multiple Quay.io repos from a single git push
- Become the Tectonic app store
- Pods/apps as top level concept
- Distribution tool
- Help people to get their apps from quay to Tectonic
- Requires App manifest or adequate flexibility
- AppC support
- rkt push
- discovery
- Immediately consistent multi-region data availability - Immediately consistent multi-region data availability
- Cockroach? - Cockroach?
- 2 factor auth - 2 factor auth
- How to integrate with Docker CLI? - How to integrate with Docker CLI?
- Mirroring from another registry
- Mirroring to a dependent registry

2
app.py
View file

@ -37,6 +37,7 @@ from util.saas.metricqueue import MetricQueue
from util.config.provider import get_config_provider from util.config.provider import get_config_provider
from util.config.configutil import generate_secret_key from util.config.configutil import generate_secret_key
from util.config.superusermanager import SuperUserManager from util.config.superusermanager import SuperUserManager
from util.secscan.secscanendpoint import SecurityScanEndpoint
OVERRIDE_CONFIG_DIRECTORY = 'conf/stack/' OVERRIDE_CONFIG_DIRECTORY = 'conf/stack/'
OVERRIDE_CONFIG_YAML_FILENAME = 'conf/stack/config.yaml' OVERRIDE_CONFIG_YAML_FILENAME = 'conf/stack/config.yaml'
@ -156,6 +157,7 @@ image_replication_queue = WorkQueue(app.config['REPLICATION_QUEUE_NAME'], tf)
dockerfile_build_queue = WorkQueue(app.config['DOCKERFILE_BUILD_QUEUE_NAME'], tf, dockerfile_build_queue = WorkQueue(app.config['DOCKERFILE_BUILD_QUEUE_NAME'], tf,
reporter=MetricQueueReporter(metric_queue)) reporter=MetricQueueReporter(metric_queue))
notification_queue = WorkQueue(app.config['NOTIFICATION_QUEUE_NAME'], tf) notification_queue = WorkQueue(app.config['NOTIFICATION_QUEUE_NAME'], tf)
secscan_endpoint = SecurityScanEndpoint(app, config_provider)
# Check for a key in config. If none found, generate a new signing key for Docker V2 manifests. # Check for a key in config. If none found, generate a new signing key for Docker V2 manifests.
_v2_key_path = os.path.join(OVERRIDE_CONFIG_DIRECTORY, DOCKER_V2_SIGNINGKEY_FILENAME) _v2_key_path = os.path.join(OVERRIDE_CONFIG_DIRECTORY, DOCKER_V2_SIGNINGKEY_FILENAME)

View file

@ -1,4 +1,23 @@
TAG=$(git rev-parse --short HEAD)$(test -n "$(git status --porcelain)" && echo -dirty) #!/usr/bin/env bash
REPO=quay.io/quay/quay:$TAG
set -e
if [[ -n "$(git status --porcelain)" ]]; then
echo 'dirty build not supported' >&2
exit 1
fi
# get named head (ex: branch, tag, etc..)
NAME="$( git rev-parse --abbrev-ref HEAD )"
# get 7-character sha
SHA=$( git rev-parse --short HEAD )
# checkout commit so .git/HEAD points to full sha (used in Dockerfile)
git checkout $SHA
REPO=quay.io/quay/quay:$SHA
docker build -t $REPO . docker build -t $REPO .
echo $REPO echo $REPO
git checkout "$NAME"

View file

@ -200,8 +200,8 @@ def get_transformed_commit_info(bb_commit, ref, default_branch, repository_name,
match = _RAW_AUTHOR_REGEX.match(commit['raw_author']) match = _RAW_AUTHOR_REGEX.match(commit['raw_author'])
if match: if match:
email_address = match.group(1) author = lookup_author(match.group(1))
author_info = JSONPathDict(lookup_author(email_address)) author_info = JSONPathDict(author) if author is not None else None
if author_info: if author_info:
config['commit_info.author.username'] = author_info['user.username'] config['commit_info.author.username'] = author_info['user.username']
config['commit_info.author.url'] = 'https://bitbucket.org/%s/' % author_info['user.username'] config['commit_info.author.url'] = 'https://bitbucket.org/%s/' % author_info['user.username']
@ -267,7 +267,7 @@ class BitbucketBuildTrigger(BuildTriggerHandler):
trigger_uuid = self.trigger.uuid trigger_uuid = self.trigger.uuid
callback_url = '%s/oauth1/bitbucket/callback/trigger/%s' % (get_app_url(), trigger_uuid) callback_url = '%s/oauth1/bitbucket/callback/trigger/%s' % (get_app_url(), trigger_uuid)
return BitBucket(key, secret, callback_url, timeout=5) return BitBucket(key, secret, callback_url, timeout=15)
def _get_authorized_client(self): def _get_authorized_client(self):
""" Returns an authorized API client. """ """ Returns an authorized API client. """

View file

@ -206,7 +206,7 @@ class GithubBuildTrigger(BuildTriggerHandler):
hook = gh_repo.create_hook('web', webhook_config) hook = gh_repo.create_hook('web', webhook_config)
config['hook_id'] = hook.id config['hook_id'] = hook.id
config['master_branch'] = gh_repo.default_branch config['master_branch'] = gh_repo.default_branch
except GithubException: except GithubException as ghe:
default_msg = 'Unable to create webhook on repository: %s' % new_build_source default_msg = 'Unable to create webhook on repository: %s' % new_build_source
msg = GithubBuildTrigger._get_error_message(ghe, default_msg) msg = GithubBuildTrigger._get_error_message(ghe, default_msg)
raise TriggerActivationException(msg) raise TriggerActivationException(msg)

View file

@ -1,2 +0,0 @@
#!/bin/sh
exec logger -i -t diffsworker

View file

@ -1,8 +0,0 @@
#! /bin/bash
echo 'Starting diffs worker'
cd /
venv/bin/python -m workers.diffsworker
echo 'Diffs worker exited'

View file

@ -0,0 +1,2 @@
#!/bin/sh
exec logger -i -t securityworker

View file

@ -0,0 +1,8 @@
#! /bin/bash
echo 'Starting security scanner worker'
cd /
venv/bin/python -m workers.securityworker 2>&1
echo 'Security scanner worker exited'

View file

@ -64,6 +64,27 @@ location ~ ^/(v1|v2)/ {
client_max_body_size 20G; client_max_body_size 20G;
} }
location /v1/_ping {
add_header Content-Type text/plain;
add_header X-Docker-Registry-Version 0.6.0;
add_header X-Docker-Registry-Standalone 0;
return 200 'true';
}
location /v2/ {
proxy_buffering off;
proxy_request_buffering off;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_pass http://registry_app_server;
proxy_temp_path /tmp 1 2;
client_max_body_size 20G;
}
location /c1/ { location /c1/ {
proxy_buffering off; proxy_buffering off;
@ -78,13 +99,7 @@ location /c1/ {
location /static/ { location /static/ {
# checks for static file, if not found proxy to app # checks for static file, if not found proxy to app
alias /static/; alias /static/;
} error_page 404 /404;
location /v1/_ping {
add_header Content-Type text/plain;
add_header X-Docker-Registry-Version 0.6.0;
add_header X-Docker-Registry-Standalone 0;
return 200 'true';
} }
location ~ ^/b1/controller(/?)(.*) { location ~ ^/b1/controller(/?)(.*) {

View file

@ -45,8 +45,8 @@ class DefaultConfig(object):
PREFERRED_URL_SCHEME = 'http' PREFERRED_URL_SCHEME = 'http'
SERVER_HOSTNAME = 'localhost:5000' SERVER_HOSTNAME = 'localhost:5000'
REGISTRY_TITLE = 'CoreOS Enterprise Registry' REGISTRY_TITLE = 'Quay Enterprise'
REGISTRY_TITLE_SHORT = 'Enterprise Registry' REGISTRY_TITLE_SHORT = 'Quay Enterprise'
CONTACT_INFO = [ CONTACT_INFO = [
'mailto:support@quay.io', 'mailto:support@quay.io',
@ -253,3 +253,12 @@ class DefaultConfig(object):
# Experiment: Async garbage collection # Experiment: Async garbage collection
EXP_ASYNC_GARBAGE_COLLECTION = [] EXP_ASYNC_GARBAGE_COLLECTION = []
# Security scanner
FEATURE_SECURITY_SCANNER = False
SECURITY_SCANNER = {
'ENDPOINT': 'http://192.168.99.100:6060',
'ENGINE_VERSION_TARGET': 1,
'API_VERSION': 'v1',
'API_TIMEOUT_SECONDS': 10,
}

View file

@ -472,9 +472,6 @@ class RepositoryBuildTrigger(BaseModel):
pull_robot = QuayUserField(allows_robots=True, null=True, related_name='triggerpullrobot', pull_robot = QuayUserField(allows_robots=True, null=True, related_name='triggerpullrobot',
robot_null_delete=True) robot_null_delete=True)
# TODO(jschorr): Remove this column once we verify the backfill has succeeded.
used_legacy_github = BooleanField(null=True, default=False)
class EmailConfirmation(BaseModel): class EmailConfirmation(BaseModel):
code = CharField(default=random_string_generator(), unique=True, index=True) code = CharField(default=random_string_generator(), unique=True, index=True)
@ -487,11 +484,11 @@ class EmailConfirmation(BaseModel):
class ImageStorage(BaseModel): class ImageStorage(BaseModel):
uuid = CharField(default=uuid_generator, index=True, unique=True) uuid = CharField(default=uuid_generator, index=True, unique=True)
checksum = CharField(null=True)
image_size = BigIntegerField(null=True) image_size = BigIntegerField(null=True)
uncompressed_size = BigIntegerField(null=True) uncompressed_size = BigIntegerField(null=True)
uploading = BooleanField(default=True, null=True) uploading = BooleanField(default=True, null=True)
cas_path = BooleanField(default=True) cas_path = BooleanField(default=True)
content_checksum = CharField(null=True, index=True)
class ImageStorageTransformation(BaseModel): class ImageStorageTransformation(BaseModel):
@ -573,6 +570,11 @@ class Image(BaseModel):
command = TextField(null=True) command = TextField(null=True)
aggregate_size = BigIntegerField(null=True) aggregate_size = BigIntegerField(null=True)
v1_json_metadata = TextField(null=True) v1_json_metadata = TextField(null=True)
v1_checksum = CharField(null=True)
security_indexed = BooleanField(default=False)
security_indexed_engine = IntegerField(default=-1)
parent = ForeignKeyField('self', index=True, null=True, related_name='children')
class Meta: class Meta:
database = db database = db
@ -580,6 +582,8 @@ class Image(BaseModel):
indexes = ( indexes = (
# we don't really want duplicates # we don't really want duplicates
(('repository', 'docker_image_id'), True), (('repository', 'docker_image_id'), True),
(('security_indexed_engine', 'security_indexed'), False),
) )
@ -746,6 +750,7 @@ class RepositoryNotification(BaseModel):
method = ForeignKeyField(ExternalNotificationMethod) method = ForeignKeyField(ExternalNotificationMethod)
title = CharField(null=True) title = CharField(null=True)
config_json = TextField() config_json = TextField()
event_config_json = TextField(default='{}')
class RepositoryAuthorizedEmail(BaseModel): class RepositoryAuthorizedEmail(BaseModel):

View file

@ -0,0 +1,21 @@
"""Backfill parent id and v1 checksums
Revision ID: 22af01f81722
Revises: 2827d36939e4
Create Date: 2015-11-05 16:24:43.679323
"""
# revision identifiers, used by Alembic.
revision = '22af01f81722'
down_revision = '2827d36939e4'
from util.migrate.backfill_v1_checksums import backfill_checksums
from util.migrate.backfill_parent_id import backfill_parent_id
def upgrade(tables):
backfill_parent_id()
backfill_checksums()
def downgrade(tables):
pass

View file

@ -0,0 +1,30 @@
"""Separate v1 and v2 checksums.
Revision ID: 2827d36939e4
Revises: 73669db7e12
Create Date: 2015-11-04 16:29:48.905775
"""
# revision identifiers, used by Alembic.
revision = '2827d36939e4'
down_revision = '5cdc2d819c5'
from alembic import op
import sqlalchemy as sa
def upgrade(tables):
### commands auto generated by Alembic - please adjust! ###
op.add_column('image', sa.Column('v1_checksum', sa.String(length=255), nullable=True))
op.add_column('imagestorage', sa.Column('content_checksum', sa.String(length=255), nullable=True))
op.create_index('imagestorage_content_checksum', 'imagestorage', ['content_checksum'], unique=False)
### end Alembic commands ###
def downgrade(tables):
### commands auto generated by Alembic - please adjust! ###
op.drop_index('imagestorage_content_checksum', table_name='imagestorage')
op.drop_column('imagestorage', 'content_checksum')
op.drop_column('image', 'v1_checksum')
### end Alembic commands ###

View file

@ -15,28 +15,13 @@ import sqlalchemy as sa
from sqlalchemy.types import TypeDecorator, Text from sqlalchemy.types import TypeDecorator, Text
from sqlalchemy.dialects.mysql import LONGTEXT from sqlalchemy.dialects.mysql import LONGTEXT
import uuid
class EngineLongText(TypeDecorator): from util.migrate import UTF8LongText
"""Platform-independent LongText type.
Uses MySQL's LONGTEXT type, otherwise uses
Text, because other engines are not as limited
as MySQL.
"""
impl = Text
def load_dialect_impl(self, dialect):
if dialect.name == 'mysql':
return dialect.type_descriptor(LONGTEXT())
else:
return dialect.type_descriptor(Text())
def upgrade(tables): def upgrade(tables):
### commands auto generated by Alembic - please adjust! ### ### commands auto generated by Alembic - please adjust! ###
op.drop_column(u'tagmanifest', 'json_data') op.drop_column(u'tagmanifest', 'json_data')
op.add_column(u'tagmanifest', sa.Column('json_data', EngineLongText(), nullable=False)) op.add_column(u'tagmanifest', sa.Column('json_data', UTF8LongText(), nullable=False))
### end Alembic commands ### ### end Alembic commands ###

View file

@ -0,0 +1,27 @@
"""Add event-specific config
Revision ID: 50925110da8c
Revises: 2fb9492c20cc
Create Date: 2015-10-13 18:03:14.859839
"""
# revision identifiers, used by Alembic.
revision = '50925110da8c'
down_revision = '57dad559ff2d'
from alembic import op
import sqlalchemy as sa
from util.migrate import UTF8LongText
def upgrade(tables):
### commands auto generated by Alembic - please adjust! ###
op.add_column('repositorynotification', sa.Column('event_config_json', UTF8LongText, nullable=False))
### end Alembic commands ###
def downgrade(tables):
### commands auto generated by Alembic - please adjust! ###
op.drop_column('repositorynotification', 'event_config_json')
### end Alembic commands ###

View file

@ -13,14 +13,15 @@ down_revision = '3a3bb77e17d5'
from alembic import op from alembic import op
import sqlalchemy as sa import sqlalchemy as sa
from util.migrate import UTF8LongText
def upgrade(tables): def upgrade(tables):
### commands auto generated by Alembic - please adjust! ### ### commands auto generated by Alembic - please adjust! ###
op.add_column('image', sa.Column('aggregate_size', sa.BigInteger(), nullable=True)) op.add_column('image', sa.Column('aggregate_size', sa.BigInteger(), nullable=True))
op.add_column('image', sa.Column('command', sa.Text(), nullable=True)) op.add_column('image', sa.Column('command', sa.Text(), nullable=True))
op.add_column('image', sa.Column('comment', sa.Text(), nullable=True)) op.add_column('image', sa.Column('comment', UTF8LongText(), nullable=True))
op.add_column('image', sa.Column('created', sa.DateTime(), nullable=True)) op.add_column('image', sa.Column('created', sa.DateTime(), nullable=True))
op.add_column('image', sa.Column('v1_json_metadata', sa.Text(), nullable=True)) op.add_column('image', sa.Column('v1_json_metadata', UTF8LongText(), nullable=True))
### end Alembic commands ### ### end Alembic commands ###

View file

@ -0,0 +1,32 @@
"""add support for quay's security indexer
Revision ID: 57dad559ff2d
Revises: 154f2befdfbe
Create Date: 2015-07-13 16:51:41.669249
"""
# revision identifiers, used by Alembic.
revision = '57dad559ff2d'
down_revision = '73669db7e12'
from alembic import op
import sqlalchemy as sa
def upgrade(tables):
### commands auto generated by Alembic - please adjust! ###
op.add_column('image', sa.Column('parent_id', sa.Integer(), nullable=True))
op.add_column('image', sa.Column('security_indexed', sa.Boolean(), nullable=False, default=False, server_default=sa.sql.expression.false()))
op.add_column('image', sa.Column('security_indexed_engine', sa.Integer(), nullable=False, default=-1, server_default="-1"))
op.create_index('image_parent_id', 'image', ['parent_id'], unique=False)
op.create_foreign_key(op.f('fk_image_parent_id_image'), 'image', 'image', ['parent_id'], ['id'])
### end Alembic commands ###
op.create_index('image_security_indexed_engine_security_indexed', 'image', ['security_indexed_engine', 'security_indexed'])
def downgrade(tables):
### commands auto generated by Alembic - please adjust! ###
op.drop_index('image_security_indexed_engine_security_indexed', 'image')
op.drop_constraint(op.f('fk_image_parent_id_image'), 'image', type_='foreignkey')
op.drop_index('image_parent_id', table_name='image')
op.drop_column('image', 'security_indexed')
op.drop_column('image', 'security_indexed_engine')
op.drop_column('image', 'parent_id')
### end Alembic commands ###

View file

@ -0,0 +1,41 @@
"""Add vulnerability_found event
Revision ID: 5cdc2d819c5
Revises: 50925110da8c
Create Date: 2015-10-13 18:05:32.157858
"""
# revision identifiers, used by Alembic.
revision = '5cdc2d819c5'
down_revision = '50925110da8c'
from alembic import op
import sqlalchemy as sa
def upgrade(tables):
op.bulk_insert(tables.externalnotificationevent,
[
{'id':6, 'name':'vulnerability_found'},
])
op.bulk_insert(tables.notificationkind,
[
{'id':11, 'name':'vulnerability_found'},
])
def downgrade(tables):
op.execute(
(tables.externalnotificationevent.delete()
.where(tables.externalnotificationevent.c.name == op.inline_literal('vulnerability_found')))
)
op.execute(
(tables.notificationkind.delete()
.where(tables.notificationkind.c.name == op.inline_literal('vulnerability_found')))
)

View file

@ -0,0 +1,25 @@
"""Remove legacy github column
Revision ID: 73669db7e12
Revises: 35f538da62
Create Date: 2015-11-04 16:18:18.107314
"""
# revision identifiers, used by Alembic.
revision = '73669db7e12'
down_revision = '35f538da62'
from alembic import op
import sqlalchemy as sa
def upgrade(tables):
### commands auto generated by Alembic - please adjust! ###
op.drop_column('repositorybuildtrigger', 'used_legacy_github')
### end Alembic commands ###
def downgrade(tables):
### commands auto generated by Alembic - please adjust! ###
op.add_column('repositorybuildtrigger', sa.Column('used_legacy_github', sa.Boolean(), nullable=True))
### end Alembic commands ###

View file

@ -0,0 +1,25 @@
"""Drop v1 checksums from imagestorage
Revision ID: b0e4a7dd82e
Revises: 22af01f81722
Create Date: 2015-11-06 16:45:31.517503
"""
# revision identifiers, used by Alembic.
revision = 'b0e4a7dd82e'
down_revision = '22af01f81722'
from alembic import op
import sqlalchemy as sa
def upgrade(tables):
### commands auto generated by Alembic - please adjust! ###
op.drop_column('imagestorage', 'checksum')
### end Alembic commands ###
def downgrade(tables):
### commands auto generated by Alembic - please adjust! ###
op.add_column('imagestorage', sa.Column('checksum', sa.String(length=255), nullable=True))
### end Alembic commands ###

View file

@ -17,7 +17,8 @@ def get_repo_blob_by_digest(namespace, repo_name, blob_digest):
.join(Repository) .join(Repository)
.join(Namespace, on=(Namespace.id == Repository.namespace_user)) .join(Namespace, on=(Namespace.id == Repository.namespace_user))
.where(Repository.name == repo_name, Namespace.username == namespace, .where(Repository.name == repo_name, Namespace.username == namespace,
ImageStorage.checksum == blob_digest, ImageStorage.uploading == False)) ImageStorage.content_checksum == blob_digest,
ImageStorage.uploading == False))
if not placements: if not placements:
raise BlobDoesNotExist('Blob does not exist with digest: {0}'.format(blob_digest)) raise BlobDoesNotExist('Blob does not exist with digest: {0}'.format(blob_digest))
@ -35,13 +36,14 @@ def store_blob_record_and_temp_link(namespace, repo_name, blob_digest, location_
with db_transaction(): with db_transaction():
repo = _basequery.get_existing_repository(namespace, repo_name) repo = _basequery.get_existing_repository(namespace, repo_name)
try: try:
storage = ImageStorage.get(checksum=blob_digest) storage = ImageStorage.get(content_checksum=blob_digest)
storage.image_size = byte_count storage.image_size = byte_count
storage.save() storage.save()
ImageStoragePlacement.get(storage=storage, location=location_obj) ImageStoragePlacement.get(storage=storage, location=location_obj)
except ImageStorage.DoesNotExist: except ImageStorage.DoesNotExist:
storage = ImageStorage.create(checksum=blob_digest, uploading=False, image_size=byte_count) storage = ImageStorage.create(content_checksum=blob_digest, uploading=False,
image_size=byte_count)
ImageStoragePlacement.create(storage=storage, location=location_obj) ImageStoragePlacement.create(storage=storage, location=location_obj)
except ImageStoragePlacement.DoesNotExist: except ImageStoragePlacement.DoesNotExist:
ImageStoragePlacement.create(storage=storage, location=location_obj) ImageStoragePlacement.create(storage=storage, location=location_obj)

View file

@ -281,10 +281,7 @@ def set_image_metadata(docker_image_id, namespace_name, repository_name, created
except Image.DoesNotExist: except Image.DoesNotExist:
raise DataModelException('No image with specified id and repository') raise DataModelException('No image with specified id and repository')
# We cleanup any old checksum in case it's a retry after a fail
fetched.storage.checksum = None
fetched.created = datetime.now() fetched.created = datetime.now()
if created_date_str is not None: if created_date_str is not None:
try: try:
fetched.created = dateutil.parser.parse(created_date_str).replace(tzinfo=None) fetched.created = dateutil.parser.parse(created_date_str).replace(tzinfo=None)
@ -292,12 +289,17 @@ def set_image_metadata(docker_image_id, namespace_name, repository_name, created
# parse raises different exceptions, so we cannot use a specific kind of handler here. # parse raises different exceptions, so we cannot use a specific kind of handler here.
pass pass
# We cleanup any old checksum in case it's a retry after a fail
fetched.v1_checksum = None
fetched.storage.content_checksum = None
fetched.comment = comment fetched.comment = comment
fetched.command = command fetched.command = command
fetched.v1_json_metadata = v1_json_metadata fetched.v1_json_metadata = v1_json_metadata
if parent: if parent:
fetched.ancestors = '%s%s/' % (parent.ancestors, parent.id) fetched.ancestors = '%s%s/' % (parent.ancestors, parent.id)
fetched.parent = parent
fetched.save() fetched.save()
return fetched return fetched
@ -363,7 +365,8 @@ def get_repo_image_by_storage_checksum(namespace, repository_name, storage_check
.join(Repository) .join(Repository)
.join(Namespace, on=(Namespace.id == Repository.namespace_user)) .join(Namespace, on=(Namespace.id == Repository.namespace_user))
.where(Repository.name == repository_name, Namespace.username == namespace, .where(Repository.name == repository_name, Namespace.username == namespace,
ImageStorage.checksum == storage_checksum, ImageStorage.uploading == False) ImageStorage.content_checksum == storage_checksum,
ImageStorage.uploading == False)
.get()) .get())
except Image.DoesNotExist: except Image.DoesNotExist:
msg = 'Image with storage checksum {0} does not exist in repo {1}/{2}'.format(storage_checksum, msg = 'Image with storage checksum {0} does not exist in repo {1}/{2}'.format(storage_checksum,

View file

@ -113,12 +113,13 @@ def delete_matching_notifications(target, kind_name, **kwargs):
notification.delete_instance() notification.delete_instance()
def create_repo_notification(repo, event_name, method_name, config, title=None): def create_repo_notification(repo, event_name, method_name, method_config, event_config, title=None):
event = ExternalNotificationEvent.get(ExternalNotificationEvent.name == event_name) event = ExternalNotificationEvent.get(ExternalNotificationEvent.name == event_name)
method = ExternalNotificationMethod.get(ExternalNotificationMethod.name == method_name) method = ExternalNotificationMethod.get(ExternalNotificationMethod.name == method_name)
return RepositoryNotification.create(repository=repo, event=event, method=method, return RepositoryNotification.create(repository=repo, event=event, method=method,
config_json=json.dumps(config), title=title) config_json=json.dumps(method_config), title=title,
event_config_json=json.dumps(event_config))
def get_repo_notification(uuid): def get_repo_notification(uuid):

View file

@ -209,33 +209,18 @@ def get_storage_by_uuid(storage_uuid):
raise InvalidImageException('No storage found with uuid: %s', storage_uuid) raise InvalidImageException('No storage found with uuid: %s', storage_uuid)
def get_repo_storage_by_checksum(namespace, repository_name, checksum):
def filter_to_repo_and_checksum(query):
return (query
.join(Image)
.join(Repository)
.join(Namespace, on=(Repository.namespace_user == Namespace.id))
.where(Repository.name == repository_name, Namespace.username == namespace,
ImageStorage.checksum == checksum))
try:
return _get_storage(filter_to_repo_and_checksum)
except InvalidImageException:
raise InvalidImageException('No storage found with checksum {0}'.format(checksum))
def get_layer_path(storage_record): def get_layer_path(storage_record):
""" Returns the path in the storage engine to the layer data referenced by the storage row. """ """ Returns the path in the storage engine to the layer data referenced by the storage row. """
store = config.store store = config.store
if not storage_record.cas_path: if not storage_record.cas_path:
return store.v1_image_layer_path(storage_record.uuid) return store.v1_image_layer_path(storage_record.uuid)
return store.blob_path(storage_record.checksum) return store.blob_path(storage_record.content_checksum)
def lookup_repo_storages_by_checksum(repo, checksums): def lookup_repo_storages_by_content_checksum(repo, checksums):
""" Looks up repository storages (without placements) matching the given repository """ Looks up repository storages (without placements) matching the given repository
and checksum. """ and checksum. """
return (ImageStorage return (ImageStorage
.select() .select()
.join(Image) .join(Image)
.where(Image.repository == repo, ImageStorage.checksum << checksums)) .where(Image.repository == repo, ImageStorage.content_checksum << checksums))

View file

@ -50,11 +50,12 @@ class WorkQueue(object):
QueueItem.queue_name ** name_match_query)) QueueItem.queue_name ** name_match_query))
def _available_jobs(self, now, name_match_query): def _available_jobs(self, now, name_match_query):
return (QueueItem return self._available_jobs_where(QueueItem.select(), now, name_match_query)
.select()
.where(QueueItem.queue_name ** name_match_query, QueueItem.available_after <= now, def _available_jobs_where(self, query, now, name_match_query):
return query.where(QueueItem.queue_name ** name_match_query, QueueItem.available_after <= now,
((QueueItem.available == True) | (QueueItem.processing_expires <= now)), ((QueueItem.available == True) | (QueueItem.processing_expires <= now)),
QueueItem.retries_remaining > 0)) QueueItem.retries_remaining > 0)
def _available_jobs_not_running(self, now, name_match_query, running_query): def _available_jobs_not_running(self, now, name_match_query, running_query):
return (self return (self
@ -145,25 +146,30 @@ class WorkQueue(object):
item = None item = None
try: try:
db_item_candidate = avail.order_by(QueueItem.id).get() # The previous solution to this used a select for update in a
# transaction to prevent multiple instances from processing the
with self._transaction_factory(db): # same queue item. This suffered performance problems. This solution
still_available_query = (db_for_update(self # instead has instances attempt to update the potential queue item to be
._available_jobs(now, name_match_query) # unavailable. However, since their update clause is restricted to items
.where(QueueItem.id == db_item_candidate.id))) # that are available=False, only one instance's update will succeed, and
# it will have a changed row count of 1. Instances that have 0 changed
db_item = still_available_query.get() # rows know that another instance is already handling that item.
db_item.available = False db_item = avail.order_by(QueueItem.id).get()
db_item.processing_expires = now + timedelta(seconds=processing_time) changed_query = (QueueItem.update(
db_item.retries_remaining -= 1 available=False,
db_item.save() processing_expires=now + timedelta(seconds=processing_time),
retries_remaining=QueueItem.retries_remaining-1,
)
.where(QueueItem.id == db_item.id))
changed_query = self._available_jobs_where(changed_query, now, name_match_query)
changed = changed_query.execute()
if changed == 1:
item = AttrDict({ item = AttrDict({
'id': db_item.id, 'id': db_item.id,
'body': db_item.body, 'body': db_item.body,
'retries_remaining': db_item.retries_remaining 'retries_remaining': db_item.retries_remaining - 1,
}) })
self._currently_processing = True self._currently_processing = True
except QueueItem.DoesNotExist: except QueueItem.DoesNotExist:
self._currently_processing = False self._currently_processing = False

View file

@ -75,6 +75,14 @@ def simple_checksum_handler(json_data):
return h, fn return h, fn
def content_checksum_handler():
h = hashlib.sha256()
def fn(buf):
h.update(buf)
return h, fn
def compute_simple(fp, json_data): def compute_simple(fp, json_data):
data = json_data + '\n' data = json_data + '\n'
return 'sha256:{0}'.format(sha256_file(fp, data)) return 'sha256:{0}'.format(sha256_file(fp, data))

View file

@ -93,6 +93,11 @@ class NotFound(ApiException):
ApiException.__init__(self, None, 404, 'Not Found', payload) ApiException.__init__(self, None, 404, 'Not Found', payload)
class DownstreamIssue(ApiException):
def __init__(self, payload=None):
ApiException.__init__(self, None, 520, 'Downstream Issue', payload)
@api_bp.app_errorhandler(ApiException) @api_bp.app_errorhandler(ApiException)
@crossdomain(origin='*', headers=['Authorization', 'Content-Type']) @crossdomain(origin='*', headers=['Authorization', 'Content-Type'])
def handle_api_error(error): def handle_api_error(error):
@ -105,14 +110,6 @@ def handle_api_error(error):
return response return response
@api_bp.app_errorhandler(model.TooManyLoginAttemptsException)
@crossdomain(origin='*', headers=['Authorization', 'Content-Type'])
def handle_too_many_login_attempts(error):
response = make_response('Too many login attempts', 429)
response.headers['Retry-After'] = int(error.retry_after)
return response
def resource(*urls, **kwargs): def resource(*urls, **kwargs):
def wrapper(api_resource): def wrapper(api_resource):
if not api_resource: if not api_resource:
@ -426,4 +423,5 @@ import endpoints.api.tag
import endpoints.api.team import endpoints.api.team
import endpoints.api.trigger import endpoints.api.trigger
import endpoints.api.user import endpoints.api.user
import endpoints.api.secscan

View file

@ -241,10 +241,10 @@ def swagger_route_data(include_internal=False, compact=False):
], ],
'info': { 'info': {
'version': 'v1', 'version': 'v1',
'title': 'Quay.io Frontend', 'title': 'Quay Frontend',
'description': ('This API allows you to perform many of the operations required to work ' 'description': ('This API allows you to perform many of the operations required to work '
'with Quay.io repositories, users, and organizations. You can find out more ' 'with Quay repositories, users, and organizations. You can find out more '
'at <a href="https://quay.io">Quay.io</a>.'), 'at <a href="https://quay.io">Quay</a>.'),
'termsOfService': 'https://quay.io/tos', 'termsOfService': 'https://quay.io/tos',
'contact': { 'contact': {
'email': 'support@quay.io' 'email': 'support@quay.io'

View file

@ -57,6 +57,10 @@ class RepositoryNotificationList(RepositoryParamResource):
'type': 'object', 'type': 'object',
'description': 'JSON config information for the specific method of notification' 'description': 'JSON config information for the specific method of notification'
}, },
'eventConfig': {
'type': 'object',
'description': 'JSON config information for the specific event of notification',
},
'title': { 'title': {
'type': 'string', 'type': 'string',
'description': 'The human-readable title of the notification', 'description': 'The human-readable title of the notification',
@ -84,6 +88,7 @@ class RepositoryNotificationList(RepositoryParamResource):
new_notification = model.notification.create_repo_notification(repo, parsed['event'], new_notification = model.notification.create_repo_notification(repo, parsed['event'],
parsed['method'], parsed['config'], parsed['method'], parsed['config'],
parsed['eventConfig'],
parsed.get('title', None)) parsed.get('title', None))
resp = notification_view(new_notification) resp = notification_view(new_notification)

103
endpoints/api/secscan.py Normal file
View file

@ -0,0 +1,103 @@
""" List and manage repository vulnerabilities and other sec information. """
import logging
import features
import json
import requests
from app import secscan_endpoint
from data import model
from endpoints.api import (require_repo_read, NotFound, DownstreamIssue, path_param,
RepositoryParamResource, resource, nickname, show_if, parse_args,
query_param)
logger = logging.getLogger(__name__)
def _call_security_api(relative_url, *args, **kwargs):
""" Issues an HTTP call to the sec API at the given relative URL. """
try:
response = secscan_endpoint.call_api(relative_url, *args, **kwargs)
except requests.exceptions.Timeout:
raise DownstreamIssue(payload=dict(message='API call timed out'))
except requests.exceptions.ConnectionError:
raise DownstreamIssue(payload=dict(message='Could not connect to downstream service'))
if response.status_code == 404:
raise NotFound()
try:
response_data = json.loads(response.text)
except ValueError:
raise DownstreamIssue(payload=dict(message='Non-json response from downstream service'))
if response.status_code / 100 != 2:
logger.warning('Got %s status code to call: %s', response.status_code, response.text)
raise DownstreamIssue(payload=dict(message=response_data['Message']))
return response_data
@show_if(features.SECURITY_SCANNER)
@resource('/v1/repository/<repopath:repository>/tag/<tag>/vulnerabilities')
@path_param('repository', 'The full path of the repository. e.g. namespace/name')
@path_param('tag', 'The name of the tag')
class RepositoryTagVulnerabilities(RepositoryParamResource):
""" Operations for managing the vulnerabilities in a repository tag. """
@require_repo_read
@nickname('getRepoTagVulnerabilities')
@parse_args
@query_param('minimumPriority', 'Minimum vulnerability priority', type=str,
default='Low')
def get(self, args, namespace, repository, tag):
""" Fetches the vulnerabilities (if any) for a repository tag. """
try:
tag_image = model.tag.get_tag_image(namespace, repository, tag)
except model.DataModelException:
raise NotFound()
if not tag_image.security_indexed:
logger.debug('Image %s for tag %s under repository %s/%s not security indexed',
tag_image.docker_image_id, tag, namespace, repository)
return {
'security_indexed': False
}
data = _call_security_api('layers/%s/vulnerabilities', tag_image.docker_image_id,
minimumPriority=args.minimumPriority)
return {
'security_indexed': True,
'data': data,
}
@show_if(features.SECURITY_SCANNER)
@resource('/v1/repository/<repopath:repository>/image/<imageid>/packages')
@path_param('repository', 'The full path of the repository. e.g. namespace/name')
@path_param('imageid', 'The image ID')
class RepositoryImagePackages(RepositoryParamResource):
""" Operations for listing the packages added/removed in an image. """
@require_repo_read
@nickname('getRepoImagePackages')
def get(self, namespace, repository, imageid):
""" Fetches the packages added/removed in the given repo image. """
repo_image = model.image.get_repo_image(namespace, repository, imageid)
if repo_image is None:
raise NotFound()
if not repo_image.security_indexed:
return {
'security_indexed': False
}
data = _call_security_api('layers/%s/packages/diff', repo_image.docker_image_id)
return {
'security_indexed': True,
'data': data,
}

View file

@ -9,7 +9,7 @@ from flask import request
import features import features
from app import app, avatar, superusers, authentication from app import app, avatar, superusers, authentication, config_provider
from endpoints.api import (ApiResource, nickname, resource, validate_json_request, from endpoints.api import (ApiResource, nickname, resource, validate_json_request,
internal_only, require_scope, show_if, parse_args, internal_only, require_scope, show_if, parse_args,
query_param, abort, require_fresh_login, path_param, verify_not_prod) query_param, abort, require_fresh_login, path_param, verify_not_prod)
@ -131,6 +131,7 @@ class SuperUserLogs(ApiResource):
def org_view(org): def org_view(org):
return { return {
'name': org.username, 'name': org.username,
'email': org.email,
'avatar': avatar.get_data_for_org(org), 'avatar': avatar.get_data_for_org(org),
} }
@ -236,6 +237,10 @@ class SuperUserList(ApiResource):
@require_scope(scopes.SUPERUSER) @require_scope(scopes.SUPERUSER)
def post(self): def post(self):
""" Creates a new user. """ """ Creates a new user. """
# Ensure that we are using database auth.
if app.config['AUTHENTICATION_TYPE'] != 'Database':
abort(400)
user_information = request.get_json() user_information = request.get_json()
if SuperUserPermission().can(): if SuperUserPermission().can():
username = user_information['username'] username = user_information['username']
@ -274,6 +279,10 @@ class SuperUserSendRecoveryEmail(ApiResource):
@nickname('sendInstallUserRecoveryEmail') @nickname('sendInstallUserRecoveryEmail')
@require_scope(scopes.SUPERUSER) @require_scope(scopes.SUPERUSER)
def post(self, username): def post(self, username):
# Ensure that we are using database auth.
if app.config['AUTHENTICATION_TYPE'] != 'Database':
abort(400)
if SuperUserPermission().can(): if SuperUserPermission().can():
user = model.user.get_nonrobot_user(username) user = model.user.get_nonrobot_user(username)
if not user: if not user:
@ -370,9 +379,17 @@ class SuperUserManagement(ApiResource):
user_data = request.get_json() user_data = request.get_json()
if 'password' in user_data: if 'password' in user_data:
# Ensure that we are using database auth.
if app.config['AUTHENTICATION_TYPE'] != 'Database':
abort(400)
model.user.change_password(user, user_data['password']) model.user.change_password(user, user_data['password'])
if 'email' in user_data: if 'email' in user_data:
# Ensure that we are using database auth.
if app.config['AUTHENTICATION_TYPE'] != 'Database':
abort(400)
model.user.update_email(user, user_data['email'], auto_verify=True) model.user.update_email(user, user_data['email'], auto_verify=True)
if 'enabled' in user_data: if 'enabled' in user_data:
@ -380,6 +397,18 @@ class SuperUserManagement(ApiResource):
user.enabled = bool(user_data['enabled']) user.enabled = bool(user_data['enabled'])
user.save() user.save()
if 'superuser' in user_data:
config_object = config_provider.get_config()
superusers_set = set(config_object['SUPER_USERS'])
if user_data['superuser']:
superusers_set.add(username)
elif username in superusers_set:
superusers_set.remove(username)
config_object['SUPER_USERS'] = list(superusers_set)
config_provider.save_config(config_object)
return user_view(user, password=user_data.get('password')) return user_view(user, password=user_data.get('password'))
abort(403) abort(403)

View file

@ -4,7 +4,7 @@ from flask import request, abort
from endpoints.api import (resource, nickname, require_repo_read, require_repo_write, from endpoints.api import (resource, nickname, require_repo_read, require_repo_write,
RepositoryParamResource, log_action, NotFound, validate_json_request, RepositoryParamResource, log_action, NotFound, validate_json_request,
path_param, parse_args, query_param) path_param, parse_args, query_param, truthy_bool)
from endpoints.api.image import image_view from endpoints.api.image import image_view
from data import model from data import model
from auth.auth_context import get_authenticated_user from auth.auth_context import get_authenticated_user
@ -135,7 +135,10 @@ class RepositoryTagImages(RepositoryParamResource):
""" Resource for listing the images in a specific repository tag. """ """ Resource for listing the images in a specific repository tag. """
@require_repo_read @require_repo_read
@nickname('listTagImages') @nickname('listTagImages')
def get(self, namespace, repository, tag): @parse_args
@query_param('owned', 'If specified, only images wholely owned by this tag are returned.',
type=truthy_bool, default=False)
def get(self, args, namespace, repository, tag):
""" List the images for the specified repository tag. """ """ List the images for the specified repository tag. """
try: try:
tag_image = model.tag.get_tag_image(namespace, repository, tag) tag_image = model.tag.get_tag_image(namespace, repository, tag)
@ -144,15 +147,37 @@ class RepositoryTagImages(RepositoryParamResource):
parent_images = model.image.get_parent_images(namespace, repository, tag_image) parent_images = model.image.get_parent_images(namespace, repository, tag_image)
image_map = {} image_map = {}
image_map[str(tag_image.id)] = tag_image
for image in parent_images: for image in parent_images:
image_map[str(image.id)] = image image_map[str(image.id)] = image
image_map_all = dict(image_map)
parents = list(parent_images) parents = list(parent_images)
parents.reverse() parents.reverse()
all_images = [tag_image] + parents all_images = [tag_image] + parents
# Filter the images returned to those not found in the ancestry of any of the other tags in
# the repository.
if args['owned']:
all_tags = model.tag.list_repository_tags(namespace, repository)
for current_tag in all_tags:
if current_tag.name == tag:
continue
# Remove the tag's image ID.
tag_image_id = str(current_tag.image_id)
image_map.pop(tag_image_id, None)
# Remove any ancestors:
for ancestor_id in current_tag.image.ancestors.split('/'):
image_map.pop(ancestor_id, None)
return { return {
'images': [image_view(image, image_map) for image in all_images] 'images': [image_view(image, image_map_all) for image in all_images
if not args['owned'] or (str(image.id) in image_map)]
} }

View file

@ -5,6 +5,7 @@ from flask import make_response
from app import app from app import app
from util.useremails import CannotSendEmailException from util.useremails import CannotSendEmailException
from util.config.provider.baseprovider import CannotWriteConfigException from util.config.provider.baseprovider import CannotWriteConfigException
from flask.ext.restful.utils.cors import crossdomain
from data import model from data import model
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -26,3 +27,12 @@ def handle_configexception(ex):
'the setup process. \n\nIssue: %s' % ex) 'the setup process. \n\nIssue: %s' % ex)
return make_response(json.dumps({'message': message}), 400) return make_response(json.dumps({'message': message}), 400)
@app.errorhandler(model.TooManyLoginAttemptsException)
@crossdomain(origin='*', headers=['Authorization', 'Content-Type'])
def handle_too_many_login_attempts(error):
msg = 'Too many login attempts. \nPlease reset your Quay password and try again.'
response = make_response(msg, 429)
response.headers['Retry-After'] = int(error.retry_after)
return response

View file

@ -84,6 +84,40 @@ def _build_summary(event_data):
return summary return summary
class VulnerabilityFoundEvent(NotificationEvent):
@classmethod
def event_name(cls):
return 'vulnerability_found'
def get_level(self, event_data, notification_data):
priority = event_data['vulnerability']['priority']
if priority == 'Defcon1' or priority == 'Critical':
return 'error'
if priority == 'Medium' or priority == 'High':
return 'warning'
return 'info'
def get_sample_data(self, repository):
return build_event_data(repository, {
'tags': ['latest', 'prod'],
'image': 'some-image-id',
'vulnerability': {
'id': 'CVE-FAKE-CVE',
'description': 'A futurist vulnerability',
'link': 'https://security-tracker.debian.org/tracker/CVE-FAKE-CVE',
'priority': 'Critical',
},
})
def get_summary(self, event_data, notification_data):
msg = '%s vulnerability detected in repository %s in tags %s'
return msg % (event_data['vulnerability']['priority'],
event_data['repository'],
', '.join(event_data['tags']))
class BuildQueueEvent(NotificationEvent): class BuildQueueEvent(NotificationEvent):
@classmethod @classmethod
def event_name(cls): def event_name(cls):

View file

@ -152,7 +152,7 @@ def get_image_layer(namespace, repository, image_id, headers):
image_id=image_id) image_id=image_id)
try: try:
path = store.blob_path(repo_image.storage.checksum) path = store.blob_path(repo_image.storage.content_checksum)
if not repo_image.storage.cas_path: if not repo_image.storage.cas_path:
path = store.v1_image_layer_path(repo_image.storage.uuid) path = store.v1_image_layer_path(repo_image.storage.uuid)
logger.info('Serving legacy v1 image from path: %s', path) logger.info('Serving legacy v1 image from path: %s', path)
@ -233,6 +233,10 @@ def put_image_layer(namespace, repository, image_id):
h, sum_hndlr = checksums.simple_checksum_handler(json_data) h, sum_hndlr = checksums.simple_checksum_handler(json_data)
sr.add_handler(sum_hndlr) sr.add_handler(sum_hndlr)
# Add a handler which computes the content checksum only
ch, content_sum_hndlr = checksums.content_checksum_handler()
sr.add_handler(content_sum_hndlr)
# Stream write the data to storage. # Stream write the data to storage.
with database.CloseForLongOperation(app.config): with database.CloseForLongOperation(app.config):
try: try:
@ -258,13 +262,14 @@ def put_image_layer(namespace, repository, image_id):
except (IOError, checksums.TarError) as exc: except (IOError, checksums.TarError) as exc:
logger.debug('put_image_layer: Error when computing tarsum %s', exc) logger.debug('put_image_layer: Error when computing tarsum %s', exc)
if repo_image.storage.checksum is None: if repo_image.v1_checksum is None:
# We don't have a checksum stored yet, that's fine skipping the check. # We don't have a checksum stored yet, that's fine skipping the check.
# Not removing the mark though, image is not downloadable yet. # Not removing the mark though, image is not downloadable yet.
session['checksum'] = csums session['checksum'] = csums
session['content_checksum'] = 'sha256:{0}'.format(ch.hexdigest())
return make_response('true', 200) return make_response('true', 200)
checksum = repo_image.storage.checksum checksum = repo_image.v1_checksum
# We check if the checksums provided matches one the one we computed # We check if the checksums provided matches one the one we computed
if checksum not in csums: if checksum not in csums:
@ -323,8 +328,9 @@ def put_image_checksum(namespace, repository, image_id):
abort(409, 'Cannot set checksum for image %(image_id)s', abort(409, 'Cannot set checksum for image %(image_id)s',
issue='image-write-error', image_id=image_id) issue='image-write-error', image_id=image_id)
logger.debug('Storing image checksum') logger.debug('Storing image and content checksums')
err = store_checksum(repo_image.storage, checksum) content_checksum = session.get('content_checksum', None)
err = store_checksum(repo_image, checksum, content_checksum)
if err: if err:
abort(400, err) abort(400, err)
@ -398,14 +404,17 @@ def get_image_ancestry(namespace, repository, image_id, headers):
return response return response
def store_checksum(image_storage, checksum): def store_checksum(image_with_storage, checksum, content_checksum):
checksum_parts = checksum.split(':') checksum_parts = checksum.split(':')
if len(checksum_parts) != 2: if len(checksum_parts) != 2:
return 'Invalid checksum format' return 'Invalid checksum format'
# We store the checksum # We store the checksum
image_storage.checksum = checksum image_with_storage.storage.content_checksum = content_checksum
image_storage.save() image_with_storage.storage.save()
image_with_storage.v1_checksum = checksum
image_with_storage.save()
@v1_bp.route('/images/<image_id>/json', methods=['PUT']) @v1_bp.route('/images/<image_id>/json', methods=['PUT'])
@ -519,7 +528,7 @@ def process_image_changes(namespace, repository, image_id):
parent_trie.frombytes(parent_trie_bytes) parent_trie.frombytes(parent_trie_bytes)
# Read in the file entries from the layer tar file # Read in the file entries from the layer tar file
layer_path = store.blob_path(repo_image.storage.checksum) layer_path = store.blob_path(repo_image.storage.content_checksum)
if not repo_image.storage.cas_path: if not repo_image.storage.cas_path:
logger.info('Processing diffs for newly stored v1 image at %s', layer_path) logger.info('Processing diffs for newly stored v1 image at %s', layer_path)
layer_path = store.v1_image_layer_path(uuid) layer_path = store.v1_image_layer_path(uuid)

View file

@ -301,8 +301,8 @@ def _write_manifest(namespace, repo_name, manifest):
# Lookup the storages associated with each blob in the manifest. # Lookup the storages associated with each blob in the manifest.
checksums = [str(mdata.digest) for mdata in manifest.layers] checksums = [str(mdata.digest) for mdata in manifest.layers]
storage_query = model.storage.lookup_repo_storages_by_checksum(repo, checksums) storage_query = model.storage.lookup_repo_storages_by_content_checksum(repo, checksums)
storage_map = {storage.checksum: storage for storage in storage_query} storage_map = {storage.content_checksum: storage for storage in storage_query}
# Synthesize the V1 metadata for each layer. # Synthesize the V1 metadata for each layer.
manifest_digest = manifest.digest manifest_digest = manifest.digest
@ -387,10 +387,10 @@ def _generate_and_store_manifest(namespace, repo_name, tag_name):
builder = SignedManifestBuilder(namespace, repo_name, tag_name) builder = SignedManifestBuilder(namespace, repo_name, tag_name)
# Add the leaf layer # Add the leaf layer
builder.add_layer(image.storage.checksum, image.v1_json_metadata) builder.add_layer(image.storage.content_checksum, image.v1_json_metadata)
for parent in parents: for parent in parents:
builder.add_layer(parent.storage.checksum, parent.v1_json_metadata) builder.add_layer(parent.storage.content_checksum, parent.v1_json_metadata)
# Sign the manifest with our signing key. # Sign the manifest with our signing key.
manifest = builder.build(docker_v2_signing_key) manifest = builder.build(docker_v2_signing_key)

View file

@ -31,6 +31,10 @@ def _open_stream(formatter, namespace, repository, tag, synthetic_image_id, imag
image_list = list(model.image.get_parent_images(namespace, repository, repo_image)) image_list = list(model.image.get_parent_images(namespace, repository, repo_image))
image_list.append(repo_image) image_list.append(repo_image)
# Note: The image list ordering must be from top-level image, downward, so we reverse the order
# here.
image_list.reverse()
def get_next_image(): def get_next_image():
for current_image in image_list: for current_image in image_list:
yield current_image yield current_image
@ -42,7 +46,8 @@ def _open_stream(formatter, namespace, repository, tag, synthetic_image_id, imag
current_image_path) current_image_path)
current_image_id = current_image_entry.id current_image_id = current_image_entry.id
logger.debug('Returning image layer %s: %s', current_image_id, current_image_path) logger.debug('Returning image layer %s (%s): %s', current_image_id,
current_image_entry.docker_image_id, current_image_path)
yield current_image_stream yield current_image_stream
stream = formatter.build_stream(namespace, repository, tag, synthetic_image_id, image_json, stream = formatter.build_stream(namespace, repository, tag, synthetic_image_id, image_json,

View file

@ -58,6 +58,12 @@ def index(path, **kwargs):
def internal_error_display(): def internal_error_display():
return render_page_template('500.html') return render_page_template('500.html')
@web.errorhandler(404)
@web.route('/404', methods=['GET'])
def not_found_error_display(e = None):
resp = render_page_template('404.html')
resp.status_code = 404
return resp
@web.route('/organization/<path:path>', methods=['GET']) @web.route('/organization/<path:path>', methods=['GET'])
@no_cache @no_cache
@ -393,7 +399,6 @@ def confirm_recovery():
@web.route('/repository/<path:repository>/status', methods=['GET']) @web.route('/repository/<path:repository>/status', methods=['GET'])
@parse_repository_name @parse_repository_name
@no_cache
@anon_protect @anon_protect
def build_status_badge(namespace, repository): def build_status_badge(namespace, repository):
token = request.args.get('token', None) token = request.args.get('token', None)
@ -417,8 +422,13 @@ def build_status_badge(namespace, repository):
else: else:
status_name = 'none' status_name = 'none'
if request.headers.get('If-None-Match') == status_name:
return Response(status=304)
response = make_response(STATUS_TAGS[status_name]) response = make_response(STATUS_TAGS[status_name])
response.content_type = 'image/svg+xml' response.content_type = 'image/svg+xml'
response.headers['Cache-Control'] = 'no-cache'
response.headers['ETag'] = status_name
return response return response

View file

@ -0,0 +1,4 @@
A <a href="{{ event_data.vulnerability.link }}">{{ event_data.vulnerability.priority }} vulnerability</a> ({{ event_data.vulnerability.id }}) was detected in tags
{{ 'tags' | icon_image }}
{% for tag in event_data.tags %}{%if loop.index > 1 %}, {% endif %}{{ (event_data.repository, tag) | repository_tag_reference }}{% endfor %} in
repository {{ event_data.repository | repository_reference }}

View file

@ -44,4 +44,6 @@ class TarImageFormatter(object):
""" Returns TAR file header data for a folder with the given name. """ """ Returns TAR file header data for a folder with the given name. """
info = tarfile.TarInfo(name=name) info = tarfile.TarInfo(name=name)
info.type = tarfile.DIRTYPE info.type = tarfile.DIRTYPE
# allow the directory to be readable by non-root users
info.mode = 0755
return info.tobuf() return info.tobuf()

View file

@ -6,6 +6,7 @@ import calendar
import os import os
import argparse import argparse
from sys import maxsize
from datetime import datetime, timedelta from datetime import datetime, timedelta
from peewee import (SqliteDatabase, create_model_tables, drop_model_tables, savepoint_sqlite, from peewee import (SqliteDatabase, create_model_tables, drop_model_tables, savepoint_sqlite,
savepoint) savepoint)
@ -84,7 +85,7 @@ def __create_subtree(repo, structure, creator_username, parent, tag_map):
new_image_locations = new_image.storage.locations new_image_locations = new_image.storage.locations
new_image.storage.uuid = __gen_image_uuid(repo, image_num) new_image.storage.uuid = __gen_image_uuid(repo, image_num)
new_image.storage.uploading = False new_image.storage.uploading = False
new_image.storage.checksum = checksum new_image.storage.content_checksum = checksum
new_image.storage.save() new_image.storage.save()
# Write some data for the storage. # Write some data for the storage.
@ -96,6 +97,10 @@ def __create_subtree(repo, structure, creator_username, parent, tag_map):
path = path_builder(new_image.storage.uuid) path = path_builder(new_image.storage.uuid)
store.put_content('local_us', path, checksum) store.put_content('local_us', path, checksum)
new_image.security_indexed = False
new_image.security_indexed_engine = maxsize
new_image.save()
creation_time = REFERENCE_DATE + timedelta(weeks=image_num) + timedelta(days=model_num) creation_time = REFERENCE_DATE + timedelta(weeks=image_num) + timedelta(days=model_num)
command_list = SAMPLE_CMDS[image_num % len(SAMPLE_CMDS)] command_list = SAMPLE_CMDS[image_num % len(SAMPLE_CMDS)]
command = json.dumps(command_list) if command_list else None command = json.dumps(command_list) if command_list else None
@ -311,6 +316,7 @@ def initialize_database():
ExternalNotificationEvent.create(name='build_start') ExternalNotificationEvent.create(name='build_start')
ExternalNotificationEvent.create(name='build_success') ExternalNotificationEvent.create(name='build_success')
ExternalNotificationEvent.create(name='build_failure') ExternalNotificationEvent.create(name='build_failure')
ExternalNotificationEvent.create(name='vulnerability_found')
ExternalNotificationMethod.create(name='quay_notification') ExternalNotificationMethod.create(name='quay_notification')
ExternalNotificationMethod.create(name='email') ExternalNotificationMethod.create(name='email')
@ -325,6 +331,7 @@ def initialize_database():
NotificationKind.create(name='build_start') NotificationKind.create(name='build_start')
NotificationKind.create(name='build_success') NotificationKind.create(name='build_success')
NotificationKind.create(name='build_failure') NotificationKind.create(name='build_failure')
NotificationKind.create(name='vulnerability_found')
NotificationKind.create(name='password_required') NotificationKind.create(name='password_required')
NotificationKind.create(name='over_private_usage') NotificationKind.create(name='over_private_usage')

View file

@ -5,3 +5,4 @@ export TROLLIUSDEBUG=1
python -m unittest discover -f python -m unittest discover -f
python -m test.registry_tests -f python -m test.registry_tests -f
python -m test.queue_threads -f

View file

@ -108,6 +108,10 @@ casper.then(function() {
this.capture(outputDir + 'repo-settings.png'); this.capture(outputDir + 'repo-settings.png');
}); });
casper.thenOpen(rootUrl + 'repository/devtable/' + repo + '?tab=tags', function() {
this.wait(1000);
});
casper.then(function() { casper.then(function() {
this.log('Generating repository image diff screenshot.'); this.log('Generating repository image diff screenshot.');
}); });
@ -179,7 +183,7 @@ casper.then(function() {
}); });
casper.thenOpen(rootUrl + 'repository/' + buildrepo + '?tab=builds', function() { casper.thenOpen(rootUrl + 'repository/' + buildrepo + '?tab=builds', function() {
this.wait(1000); this.wait(10000);
this.waitForText('Triggered By'); this.waitForText('Triggered By');
}); });

View file

@ -0,0 +1,20 @@
#createNotificationModal .dropdown-select {
margin: 0px;
}
#createNotificationModal .options-table {
width: 100%;
margin-bottom: 10px;
}
#createNotificationModal .options-table td {
padding-bottom: 6px;
}
#createNotificationModal .options-table td.name {
width: 160px;
}
#createNotificationModal .options-table-wrapper {
padding: 10px;
}

View file

@ -11,3 +11,12 @@
width: 100%; width: 100%;
} }
.dockerfile-build-dialog-element .trigger-list td {
padding-bottom: 10px;
}
.dockerfile-build-dialog-element .trigger-list td .empty {
color: #ccc;
font-size: 12px;
}

View file

@ -0,0 +1,42 @@
.plan-manager-element .plans-list-table thead td {
color: #aaa;
font-weight: bold;
}
.plan-manager-element .plans-list-table td {
padding: 10px;
font-size: 16px;
vertical-align: middle;
}
.plan-manager-element .plans-list-table td.controls {
text-align: right;
}
.plan-manager-element .plans-list-table .plan-price {
font-size: 16px;
margin-bottom: 0px;
}
.plan-manager-element .plans-list-table .deprecated-plan {
color: #aaa;
}
.plan-manager-element .plans-list-table .deprecated-plan-label {
font-size: 0.7em;
}
.plan-manager-element .qe-shoutout {
float: right;
padding: 20px;
border: 1px solid #ddd;
border-radius: 4px;
margin-top: 20px;
max-width: 500px;
}
.plan-manager-element .qe-shoutout img {
max-width: 100%;
max-height: 28px;
margin-bottom: 10px;
}

View file

@ -0,0 +1,316 @@
.landing-page {
color: #555;
}
.landing-page #padding-container {
padding: 0px;
}
.landing-page .main-panel {
padding: 0px;
border: 0px;
padding-bottom: 10px;
}
.landing.jumbotron {
background: transparent;
position: relative;
color: white;
margin-bottom: 0px;
min-height: 440px;
}
.landing.jumbotron p {
font-size: 100%;
}
.landing.jumbotron h1 {
margin-bottom: 30px;
font-size: 50px;
line-height: 60px;
}
.landing.jumbotron h2 {
margin-bottom: 60px;
font-size: 26px;
font-weight: 300;
line-height: 36px;
}
.landing.jumbotron .buttons {
margin-bottom: 70px;
}
.landing.jumbotron .buttons .btn {
padding: 6px;
padding-left: 40px;
padding-right: 40px;
border: 1px solid #40B4E5;
border-radius: 6px;
margin-left: 30px;
background-color: transparent;
text-transform: uppercase;
font-size: 26px;
font-weight: 500;
color: white !important;
}
.landing.jumbotron .buttons .btn.highlighted {
background-color: rgba(64, 180, 229, 0.4);
}
.landing.jumbotron .buttons .btn:hover {
background-color: rgba(64, 180, 229, 0.8);
}
.landing.jumbotron .buttons .btn:first-child {
margin-left: 0px;
}
.landing-background {
z-index: 0;
position: absolute;
top: 0px;
bottom: 0px;
left: 0px;
right: 0px;
background: url('/static/img/quay_pattern.png') left top repeat, linear-gradient(30deg, #2277ad, #144768) no-repeat left top fixed;
background-color: #2277ad;
background-size: 100% 100%;
}
.landing-content {
z-index: 4;
padding-left: 20px;
padding-right: 20px;
position: relative;
}
.landing-content {
text-align: center;
}
.landing-content .works-with img {
max-height: 28px;
margin-left: 30px;
}
.landing-content .works-with .supports {
vertical-align: middle;
margin-left: 30px;
font-size: 12px;
line-height: 16px;
max-width: 400px;
display: inline-block;
padding-left: 30px;
border-left: 1px solid white;
height: 42px;
text-align: left;
padding-top: 4px;
}
.landing-section {
padding: 20px;
padding-bottom: 40px;
border-top: 1px solid #ddd;
display: block;
}
.landing-section:nth-child(even) {
background: #f7f7f7;
}
.landing-section h2 {
text-align: center;
display: block;
margin-bottom: 30px;
font-weight: 200;
}
.landing-page .tour-overview {
}
.landing-page .tour-overview img {
width: 500px;
padding: 10px;
display: inline-block;
}
.landing-signup-button {
margin-bottom: 10px;
}
.landing-page .twitter-tweet {
width: 100%;
margin: 0px;
border: 0px;
}
.landing-page .twitter-tweet p {
display: block;
text-align: center;
font-size: 20px;
}
.landing-page .twitter-tweet .attribute {
display: block;
text-align: center;
margin-bottom: 20px;
position: relative;
margin-top: 20px;
}
.landing-page .twitter-tweet .info-wrap {
display: inline-block;
padding-left: 70px;
}
.landing-page .twitter-tweet .twitter-avatar img {
border-radius: 4px;
border: 2px solid rgb(70, 70, 70);
width: 50px;
float: left;
}
.landing-page .twitter-tweet .info {
display: inline-block;
margin-left: 10px;
text-align: left;
margin-top: 4px;
}
.landing-page .twitter-tweet .reference {
display: block;
}
.landing-page .twitter-tweet .author {
display: block;
}
.landing-page .shoutout {
font-size: 14px;
text-align: center;
line-height: normal;
padding-top: 30px;
margin-bottom: 20px;
}
.landing-page .shoutout > i {
font-size: 50px;
display: inline-block;
width: 100px;
height: 92px;
text-align: center;
line-height: 92px;
background-image: url(/static/img/landing-icon-background.svg);
color: #337ab7;
}
.landing-page .shoutout .shoutout-link {
font-size: 20px;
display: block;
line-height: 31px;
margin-top: 20px;
margin-bottom: 20px;
}
.landing-page .shoutout .shoutout-expand {
font-family: Source Sans Pro;
font-size: 12px;
line-height: 16px;
color: #717273;
}
.landing-page .feature-shoutout {
margin-bottom: 20px;
}
.landing-page .feature-shoutout > a {
font-size: 22px;
display: block;
line-height: 31px;
padding-top: 10px;
padding-bottom: 10px;
text-align: center;
}
.landing-page .feature-shoutout .img-responsive {
border: 2px solid #eee;
padding: 6px;
}
.landing-page .landing-action {
text-align: center;
}
.landing-page .landing-action .btn {
font-size: 26px;
}
.landing-page .enterprise-logo {
display: inline-block;
background: white;
border: 1px solid transparent;
padding: 30px;
-webkit-box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.4);
-moz-box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.4);
-ms-box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.4);
-o-box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.4);
box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.4);
}
.landing-page .enterprise-logo img {
max-height: 50px;
}
@media (max-width: 989px) {
.landing-content .works-with .supports {
display: block;
margin: 0px;
margin-top: 20px;
padding: 0px;
border: 0px;
text-align: center;
max-width: 989px;
}
}
@media (max-width: 767px) {
.landing-page .buttons {
text-align: center;
}
.landing-page .btn {
margin: 0px !important;
display: block;
}
.landing-page .btn:last-child {
margin-top: 20px !important;
}
.landing-content .works-with {
text-align: center;
}
.landing-content .works-with .text {
display: block;
}
.landing-content .works-with .works-logo {
display: block;
}
.landing-content .works-with img {
margin-left: 0px;
margin-top: 10px;
}
}

442
static/css/pages/plans.css Normal file
View file

@ -0,0 +1,442 @@
.plans-panel {
padding: 0px;
padding-top: 50px;
}
.plans-panel .plans-header {
font-size: 24px;
color: #1D314F;
font-weight: 300;
margin-bottom: 40px;
text-align: center;
padding-left: 15px;
padding-right: 15px;
}
.plans-panel .plan-tabs {
text-align: center;
padding-left: 30px;
padding-right: 30px;
margin-bottom: 40px;
}
.plans-panel .plan-tabs li {
display: inline-block;
width: 49%;
}
.plans-panel .plan-tabs a {
padding-bottom: 25px;
}
.plans-panel .plan-tabs a:hover {
background-color: transparent;
border-color: transparent;
cursor: pointer !important; /* override very specific bootstrap style */
}
.plans-panel .plan-tabs a:hover svg [fill="#003764"] {
fill: #345D84;
}
.plans-panel .plan-tabs a .tab-logo {
display: block;
margin-bottom: 25px;
text-align: center;
margin-top: 25px;
}
.plans-panel .plan-tabs a .tab-logo svg {
height: 40px;
}
.plans-panel .plan-tabs a .tab-title {
display: block;
text-align: center;
font-size: 16px;
color: #1D314F;
font-weight: 300;
}
.plans-panel .plan-tabs a:hover .tab-title {
color: #51A3D9;
}
@media (max-width: 992px) {
.plans-panel .plan-tabs a .tab-logo svg {
height: 30px;
}
.plans-panel .plan-tabs a .tab-title {
font-size: 13px;
}
}
@media (max-width: 767px) {
.plans-panel .plan-tabs {
margin-bottom: 0px;
padding-right: 5px;
padding-left: 5px;
}
.plans-panel .plan-tabs a {
padding-bottom: 0px;
padding-left: 10px;
padding-right: 10px;
}
.plans-panel .plan-tabs a .tab-title {
display: none;
}
.plans-panel .plan-tabs a .tab-logo {
margin-top: 5px;
margin-bottom: 10px;
}
.plans-panel .plan-tabs a .tab-logo svg {
height: 20px;
max-width: 100%;
}
}
.plans-panel .tab-pane .plan-col {
padding-left: 12px;
padding-right: 12px;
margin-bottom: 30px;
}
.plans-panel .plan-col:before {
display: block;
text-align: center;
content: "-";
text-transform: uppercase;
color: white;
height: 25px;
}
.plans-panel .plan-col.popular:before {
content: "Most Popular";
color: #2276ad;
height: 25px;
line-height: 25px;
background-color: #c1dff3;
}
.plans-panel .plan-box {
background-color: #1d3447;
color: #fff;
padding: 20px;
}
.plans-panel .plan-box .plan-header {
margin-bottom: 20px;
padding-bottom: 20px;
border-bottom: 1px dashed #495b6a;
}
.plans-panel .plan-box.gray .plan-header {
border-bottom: 1px dashed #ccc;
}
.plans-panel .plan-box .qe-plan-description {
margin-bottom: 20px;
}
.plans-panel .plan-box .plan-description {
margin-top: 20px;
padding-top: 20px;
border-top: 1px dashed #495b6a;
text-align: center;
font-size: 16px;
margin-bottom: 20px;
}
.plans-panel .plan-box .plan-header b {
font-size: 18px;
color: white;
}
.plans-panel .plan-box.gray .plan-header b {
color: black;
}
.plans-panel .plan-box .plan-header .plan-box-price {
float: right;
width: 75px;
text-align: right;
font-size: 18px;
position: relative;
}
.plans-panel .plan-box .plan-header .plan-box-price-hidden {
font-style: italic;
font-size: 13px;
line-height: 27px;
}
.plans-panel .plan-box .trial-button {
display: block;
text-align: center;
width: 100%;
}
.plans-panel .plan-box .trial-button .fa {
padding-left: 5px;
}
.plans-panel .plan-box select {
margin-bottom: 10px;
}
.plans-panel .plan-box ul {
padding: 0px;
padding-left: 16px;
list-style: square;
}
.plans-panel .plan-box.gray {
background-color: #f5f5f5;
text-align: center;
color: black;
}
.plans-panel .plan-box.gray .qe-plan-description {
margin-bottom: 10px;
}
.plans-panel .plan-box.gray .qe-plan-description i {
margin-right: 5px;
}
.plans-panel .plan-features {
margin: 0px;
margin-top: 10px;
list-style: none;
text-align: center;
padding: 0px;
}
.plans-panel .plan-features li {
text-align: left;
margin-bottom: 10px;
position: relative;
padding-left: 25px;
}
.plans-panel .plan-features i.fa {
position: absolute;
left: 0px;
top: 4px;
color: #6aab56;
}
.plans-panel .plan-features li b {
color: #6aab56;
}
.plans-panel .plan-features.single-feature li {
text-align: center;
color: #6aab56;
}
.plans-panel .plan-features.single-feature i.fa {
position: static;
margin-right: 4px;
}
.plans-panel .plan-features li.disabled {
color: #aaa;
}
.plans-panel .faq-features {
margin-top: 15px;
border-top: 1px solid #ddd;
}
.plans-panel .features-col {
background-color: #f9f9f9;
padding: 30px;
border-bottom: 1px solid #ddd;
border-top: none;
}
.plans-panel .row-container {
margin-left: 30px;
margin-right: 30px;
}
.plans-panel .row-container-no-margin {
margin-right: 15px;
margin-left: 15px;
}
.plans-panel .tab-content {
margin-bottom: 0px;
}
.plans-panel .features-col .features-title {
text-align: center;
font-size: 18px;
margin-bottom: 20px;
color: #1d3447;
font-weight: bold;
}
.plans-panel .features-col .features-list {
padding: 0px;
list-style: none;
}
.plans-panel .features-col .features-list li {
position: relative;
padding-left: 40px;
margin-bottom: 20px;
}
.plans-panel .features-col .features-list li i {
position: absolute;
font-size: 25px;
color: #2e79b9;
left: 0px;
display: block;
width: 30px;
text-align: center;
}
.plans-panel .features-col .features-list li b {
display: block;
color: #2e79b9;
font-size: 16px;
margin-bottom: 12px;
}
.plans-panel .features-col .features-list li p {
display: none;
}
@media (min-width: 992px) {
.plans-panel .features-col .features-list li p {
display: block;
}
.plans-panel .features-col {
border-right: none;
border-left: 1px solid #ddd;
}
}
.plans-panel .faq-col {
padding-bottom: 0px;
padding-top: 30px;
padding-left: 30px;
padding-right: 30px;
margin-bottom: 20px
}
.plans-panel .faq-col h4 {
color: #2e79b9;
margin-top: 50px;
font-weight: 500;
margin-bottom: 23px;
}
.plans-panel .faq-col p+h4 {
margin-top: 40px;
}
.plans-panel .faq-col h4:first-child {
margin-top: 0px;
}
.plans-panel .faq-col > b {
margin-bottom: 10px;
display: block;
}
.plans-panel .faq-col p+b {
margin-top: 20px;
}
.plans-panel .faq-sub-col {
padding: 40px;
padding-bottom: 0px;
}
.plans-panel .faq-sub-col ul {
padding: 0px;
list-style: square;
}
.plans-panel .faq-sub-col img {
width: 100%;
max-height: 45px;
margin-bottom: 20px;
}
.plans-panel .faq-sub-col b {
margin-bottom: 20px;
text-align: center;
display: block;
}
#tectonicManagerDialog .modal-body {
text-align: center;
padding: 40px;
}
#tectonicManagerDialog .qe-logo {
max-height: 50px;
margin-bottom: 30px;
max-width: 100%;
}
#tectonicManagerDialog b {
display: block;
margin-bottom: 15px;
}
#tectonicManagerDialog .buttons {
margin-top: 10px;
margin-bottom: 30px;
}
#tectonicManagerDialog .buttons .btn {
margin-left: 30px;
font-size: 14px;
padding-left: 20px;
padding-right: 20px
}
@media (max-width: 767px) {
#tectonicManagerDialog .buttons .btn {
margin: 0px;
margin-top: 20px;
}
}
#tectonicManagerDialog .buttons .btn:first-child {
margin-left: 0px;
}
#tectonicManagerDialog .shoutout {
padding: 20px;
border: 1px solid #ddd;
border-radius: 4px;
max-width: 300px;
display: inline-block;
color: #1d304f;
font-size: 13px;
margin-top: 30px;
margin-bottom: 10px;
}
#tectonicManagerDialog .shoutout img {
max-width: 260px;
max-height: 20px;
margin-top: 20px;
}
#tectonicManagerDialog .modal-dialog {
max-width: 650px;
}

View file

@ -41,3 +41,26 @@
margin-right: 10px; margin-right: 10px;
text-align: center; text-align: center;
} }
.repo-list .qe-shoutout {
padding: 10px;
}
.repo-list .qe-shoutout b {
margin-left: 6px;
}
.repo-list .qe-shoutout div {
margin-right: 94px;
}
.repo-list .qe-shoutout img {
max-height: 24px;
max-width: 100%;
}
.repo-list .qe-shoutout span {
float: right;
line-height: 24px;
margin-right: 4px;
}

View file

@ -83,6 +83,8 @@
} }
#quay-logo { #quay-logo {
margin-left: 10px;
margin-top: 1px;
height: 36px; height: 36px;
width: 100px; width: 100px;
background-repeat: no-repeat; background-repeat: no-repeat;
@ -91,6 +93,10 @@
display: inline-block; display: inline-block;
} }
#quay-logo.hosted-logo {
width: 150px;
}
#padding-container { #padding-container {
padding: 20px; padding: 20px;
padding-top: 20px; padding-top: 20px;
@ -565,6 +571,7 @@ i.toggle-icon:hover {
.notification-error { .notification-error {
background: red; background: red;
color: white;
} }
.user-notification.notification-animated { .user-notification.notification-animated {
@ -1142,221 +1149,6 @@ i.toggle-icon:hover {
padding: 20px; padding: 20px;
} }
.jumbotron {
background: transparent;
}
.jumbotron p {
font-size: 100%;
}
.jumbotron .disclaimer-link {
font-size: .3em;
vertical-align: 23px;
}
.jumbotron .disclaimer-link:hover {
text-decoration: none;
}
.landing-page .help-block {
color: #C9C9C9;
}
.landing-page {
color: #555;
}
.landing-page #padding-container {
padding: 0px;
}
.landing-page .main-panel {
padding: 0px;
border: 0px;
padding-bottom: 10px;
}
.landing-page.signedin .main-panel {
background: transparent;
box-shadow: none;
}
.landing {
position: relative;
}
.landing .popover {
font-size: 14px;
}
.landing-background {
z-index: 0;
position: absolute;
top: 0px;
bottom: 0px;
left: 0px;
right: 0px;
background: url('/static/img/network-tile.png') left top repeat, linear-gradient(30deg, #2277ad, #144768) no-repeat left top fixed;
background-color: #2277ad;
background-size: auto, 100% 100%;
}
.landing-page.signedin .landing-background {
background: transparent;
}
.landing-filter {
z-index: 0;
position: absolute;
top: 0px;
bottom: 0px;
left: 0px;
right: 0px;
}
.landing-content {
z-index: 2;
padding-left: 20px;
padding-right: 20px;
}
.landing .call-to-action i.fa {
margin-left: 10px;
}
.landing .call-to-action {
font-size: 18px;
padding-left: 14px;
padding-right: 14px;
padding-top: 2px;
padding-bottom: 2px;
background: rgba(15, 131, 203, 0.6);
display: inline-block;
margin-top: 20px;
}
.landing {
color: white;
margin-bottom: 0px;
padding-top: 40px;
min-height: 440px;
}
.landing a:not(.btn) {
color: #BEE1FF;
}
.landing .logo {
position: absolute;
top: 20px;
left: 16px;
}
.landing .product-name {
position: absolute;
top: 22px;
left: 131px;
color: white;
font-size: 40px;
}
.landing .messages h1 {
font-size: 48px;
}
.landing-section {
padding: 20px;
padding-bottom: 40px;
border-top: 1px solid #ddd;
display: block;
}
.landing-section:nth-child(even) {
background: #f7f7f7;
}
.landing-section h2 {
text-align: center;
display: block;
margin-bottom: 30px;
font-weight: 200;
}
.landing-page .tour-overview {
}
.landing-page .tour-overview img {
width: 500px;
padding: 10px;
display: inline-block;
}
.form-signup input {
margin: 12px;
margin-left: 0px;
}
.signin-buttons {
text-align: center;
}
.landing-signup-button {
margin-bottom: 10px;
}
.landing-page .twitter-tweet {
width: 100%;
margin: 0px;
border: 0px;
}
.landing-page .twitter-tweet p {
display: block;
text-align: center;
font-size: 20px;
}
.landing-page .twitter-tweet .attribute {
display: block;
text-align: center;
margin-bottom: 20px;
position: relative;
margin-top: 20px;
}
.landing-page .twitter-tweet .info-wrap {
display: inline-block;
padding-left: 70px;
}
.landing-page .twitter-tweet .twitter-avatar img {
border-radius: 4px;
border: 2px solid rgb(70, 70, 70);
width: 50px;
float: left;
}
.landing-page .twitter-tweet .info {
display: inline-block;
margin-left: 10px;
text-align: left;
margin-top: 4px;
}
.landing-page .twitter-tweet .reference {
display: block;
}
.landing-page .twitter-tweet .author {
display: block;
}
.follow-button { .follow-button {
text-align: center; text-align: center;
} }
@ -1409,68 +1201,13 @@ form input.ng-valid.ng-dirty,
border-top: 4px solid #ccc; border-top: 4px solid #ccc;
} }
.landing .popover-content { .form-signup input {
color: black; margin: 12px;
margin-left: 0px;
} }
.landing-page .shoutout > i { .signin-buttons {
font-size: 50px;
display: inline-block;
width: 120px;
height: 120px;
background: #eee;
text-align: center; text-align: center;
border-radius: 50%;
line-height: 120px;
}
.landing-page .shoutout > b {
font-size: 22px;
display: block;
line-height: 31px;
margin-top: 10px;
margin-bottom: 10px;
}
.landing-page .shoutout {
font-size: 14px;
text-align: center;
line-height: normal;
padding-top: 30px;
margin-bottom: 15px;
}
.landing-page .shoutout .shoutout-link {
display: block;
margin-top: 16px;
font-size: 18px;
text-align: center;
}
.landing-page .feature-shoutout {
margin-bottom: 20px;
}
.landing-page .feature-shoutout > a {
font-size: 22px;
display: block;
line-height: 31px;
padding-top: 10px;
padding-bottom: 10px;
text-align: center;
}
.landing-page .feature-shoutout .img-responsive {
border: 2px solid #eee;
padding: 6px;
}
.landing-page .landing-action {
text-align: center;
}
.landing-page .landing-action a {
font-size: 26px;
} }
.footer-container, .push { .footer-container, .push {
@ -3116,34 +2853,6 @@ p.editable:hover i {
margin-bottom: 20px; margin-bottom: 20px;
} }
.plan-manager-element .plans-list-table thead td {
color: #aaa;
font-weight: bold;
}
.plan-manager-element .plans-list-table td {
padding: 10px;
font-size: 16px;
vertical-align: middle;
}
.plan-manager-element .plans-list-table td.controls {
text-align: right;
}
.plan-manager-element .plans-list-table .plan-price {
font-size: 16px;
margin-bottom: 0px;
}
.plan-manager-element .plans-list-table .deprecated-plan {
color: #aaa;
}
.plan-manager-element .plans-list-table .deprecated-plan-label {
font-size: 0.7em;
}
.plans-table-element table { .plans-table-element table {
margin: 20px; margin: 20px;
border: 1px solid #eee; border: 1px solid #eee;
@ -3162,7 +2871,6 @@ p.editable:hover i {
padding: 0px; padding: 0px;
} }
.plans-table ul li { .plans-table ul li {
padding: 4px; padding: 4px;
margin: 0px; margin: 0px;

View file

@ -1,12 +1,12 @@
<!-- Modal message dialog --> <!-- Modal message dialog -->
<div class="modal fade" id="createNotificationModal"> <div class="co-dialog modal fade" id="createNotificationModal">
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content"> <div class="modal-content">
<form id="createForm" name="createForm" ng-submit="createNotification()"> <form id="createForm" name="createForm" ng-submit="createNotification()">
<div class="modal-header"> <div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true" ng-disabled="creating">&times;</button> <button type="button" class="close" data-dismiss="modal" aria-hidden="true" ng-disabled="creating">&times;</button>
<h4 class="modal-title"> <h4 class="modal-title">
Create Repository Notification <i class="fa fa-bell"></i> Create Repository Notification
</h4> </h4>
</div> </div>
<div class="modal-body"> <div class="modal-body">
@ -29,17 +29,19 @@
</div> </div>
<!-- Create View --> <!-- Create View -->
<table style="width: 100%" ng-show="status == ''"> <div class="options-table-wrapper">
<table class="options-table" ng-show="status == ''">
<tr> <tr>
<td style="width: 120px">Notification title:</td> <td class="name">Notification title:</td>
<td style="padding-right: 21px;"> <td>
<input class="form-control" type="text" placeholder="(Optional Title)" ng-model="currentTitle" <input class="form-control" type="text" placeholder="(Optional Title)" ng-model="currentTitle">
style="margin: 10px;">
</td> </td>
</tr> </tr>
</table>
<table class="options-table" ng-show="status == ''">
<tr> <tr>
<td style="width: 120px">When this occurs:</td> <td class="name">When this occurs:</td>
<td> <td>
<div class="dropdown-select" placeholder="'(Notification Event)'" selected-item="currentEvent.title" <div class="dropdown-select" placeholder="'(Notification Event)'" selected-item="currentEvent.title"
handle-item-selected="handleEventSelected(datum)" clear-value="clearCounter"> handle-item-selected="handleEventSelected(datum)" clear-value="clearCounter">
@ -58,8 +60,28 @@
</td> </td>
</tr> </tr>
<tr ng-repeat="field in currentEvent.fields">
<td class="name" valign="top">With {{ field.title }} of:</td>
<td>
<div ng-switch on="field.type">
<select class="form-control" ng-if="field.type == 'enum'"
ng-model="currentEventConfig[field.name]" required>
<option ng-repeat="(key, info) in field.values | orderObjectBy: 'index'" value="{{key}}">{{ info.title }}</option>
</select>
<div class="co-alert co-alert-info"
style="margin-top: 6px; margin-bottom: 10px;"
ng-if="field.values[currentEventConfig[field.name]].description">
{{ field.values[currentEventConfig[field.name]].description }}
</div>
</div>
</td>
</tr>
</table>
<table class="options-table" ng-show="status == ''">
<tr> <tr>
<td>Then issue a:</td> <td class="name">Then issue a:</td>
<td> <td>
<div class="dropdown-select" placeholder="'(Notification Action)'" selected-item="currentMethod.title" <div class="dropdown-select" placeholder="'(Notification Action)'" selected-item="currentMethod.title"
handle-item-selected="handleMethodSelected(datum)" clear-value="clearCounter"> handle-item-selected="handleMethodSelected(datum)" clear-value="clearCounter">
@ -78,10 +100,8 @@
</td> </td>
</tr> </tr>
<tr ng-if="currentMethod.fields.length"><td colspan="2"><hr></td></tr>
<tr ng-repeat="field in currentMethod.fields"> <tr ng-repeat="field in currentMethod.fields">
<td valign="top" style="padding-top: 10px">{{ field.title }}:</td> <td valign="top" class="name">{{ field.title }}:</td>
<td> <td>
<div ng-switch on="field.type"> <div ng-switch on="field.type">
<span ng-switch-when="email"> <span ng-switch-when="email">
@ -114,13 +134,9 @@
style="margin-top: 10px; margin-bottom: 10px"> style="margin-top: 10px; margin-bottom: 10px">
See: <a href="{{ getHelpUrl(field, currentConfig) }}" target="_blank">{{ getHelpUrl(field, currentConfig) }}</a> See: <a href="{{ getHelpUrl(field, currentConfig) }}" target="_blank">{{ getHelpUrl(field, currentConfig) }}</a>
</div> </div>
</div>
</td>
</tr>
<tr ng-if="currentMethod.id == 'webhook'"> <div class="co-alert co-alert-info" ng-if="currentMethod.id == 'webhook'"
<td colspan="2"> style="margin-top: 6px; margin-bottom: 0px">
<div class="alert alert-info" style="margin-top: 20px; margin-bottom: 0px">
JSON metadata representing the event will be <b>POST</b>ed to the URL. JSON metadata representing the event will be <b>POST</b>ed to the URL.
<br><br> <br><br>
The contents for each event can be found in the user guide: The contents for each event can be found in the user guide:
@ -129,10 +145,12 @@
http://docs.quay.io/guides/notifications.html http://docs.quay.io/guides/notifications.html
</a> </a>
</div> </div>
</div>
</td> </td>
</tr> </tr>
</table> </table>
</div> </div>
</div>
<!-- Auth e-mail button bar --> <!-- Auth e-mail button bar -->
<div class="modal-footer" ng-if="status == 'unauthorized-email'"> <div class="modal-footer" ng-if="status == 'unauthorized-email'">

View file

@ -39,7 +39,10 @@
<table class="trigger-list"> <table class="trigger-list">
<tr ng-repeat="trigger in triggers"> <tr ng-repeat="trigger in triggers">
<td><span class="trigger-description" trigger="trigger"></span></td> <td><span class="trigger-description" trigger="trigger"></span></td>
<td><button class="btn btn-primary" ng-click="runTriggerNow(trigger)">Run Trigger</button></td> <td>
<button class="btn btn-primary" ng-click="runTriggerNow(trigger)" ng-if="trigger.is_connected_user">Run Trigger</button>
<span class="empty" ng-if="!trigger.is_connected_user">You cannot start triggers created by another user</span>
</td>
</tr> </tr>
</table> </table>
</div> </div>

View file

@ -7,7 +7,8 @@
&equiv; &equiv;
</button> </button>
<a class="navbar-brand" ng-href="{{ user.anonymous ? '/' : '/repository/' }}" target="{{ appLinkTarget() }}"> <a class="navbar-brand" ng-href="{{ user.anonymous ? '/' : '/repository/' }}" target="{{ appLinkTarget() }}">
<span id="quay-logo" ng-style="{'background-image': 'url(' + getEnterpriseLogo() + ')'}"></span> <span id="quay-logo" ng-style="{'background-image': 'url(' + getEnterpriseLogo() + ')'}"
ng-class="Config.ENTERPRISE_LOGO_URL ? 'enterprise-logo' : 'hosted-logo'"></span>
</a> </a>
<span class="user-tools visible-xs" style="float: right;"> <span class="user-tools visible-xs" style="float: right;">
<i class="fa fa-search fa-lg user-tool" ng-click="toggleSearch()" <i class="fa fa-search fa-lg user-tool" ng-click="toggleSearch()"

View file

@ -20,14 +20,14 @@
<div class="image-section"> <div class="image-section">
<i class="fa fa-tag section-icon" data-title="Current Tags" bs-tooltip></i> <i class="fa fa-tag section-icon" data-title="Current Tags" bs-tooltip></i>
<span class="section-info section-info-with-dropdown"> <span class="section-info section-info-with-dropdown">
<a class="label tag label-default" ng-repeat="tag in imageData.tags" <a class="label tag label-default" ng-repeat="tag in getTags(imageData)"
href="javascript:void(0)" ng-click="tagSelected({'tag': tag})"> href="javascript:void(0)" ng-click="tagSelected({'tag': tag})">
{{ tag }} {{ tag }}
</a> </a>
<span style="color: #ccc;" ng-if="!imageData.tags.length">(No Tags)</span> <span style="color: #ccc;" ng-if="!getTags(imageData).length">(No Tags)</span>
<div class="dropdown" data-placement="top" <div class="dropdown" data-placement="top"
ng-if="tracker.repository.can_write || imageData.tags"> ng-if="tracker.repository.can_write || getTags(imageData)">
<a href="javascript:void(0)" class="dropdown-button" data-toggle="dropdown" <a href="javascript:void(0)" class="dropdown-button" data-toggle="dropdown"
bs-tooltip="tooltip.title" data-title="Manage Tags" bs-tooltip="tooltip.title" data-title="Manage Tags"
data-container="body"> data-container="body">
@ -35,7 +35,7 @@
</a> </a>
<ul class="dropdown-menu pull-right"> <ul class="dropdown-menu pull-right">
<li ng-repeat="tag in imageData.tags"> <li ng-repeat="tag in getTags(imageData)">
<a href="javascript:void(0)" ng-click="tagSelected({'tag': tag})"> <a href="javascript:void(0)" ng-click="tagSelected({'tag': tag})">
<i class="fa fa-tag"></i>{{ tag }} <i class="fa fa-tag"></i>{{ tag }}
</a> </a>

View file

@ -23,6 +23,23 @@
Free trial until <strong>{{ parseDate(subscription.trialEnd) | date }}</strong> Free trial until <strong>{{ parseDate(subscription.trialEnd) | date }}</strong>
</div> </div>
<!-- QE Shoutout -->
<div class="qe-shoutout hidden-xs hidden-sm">
<table>
<tr>
<td>
<img src="/static/img/QuayEnterprise_horizontal_color.svg">
<div class="shoutout-text">
Run a private instance of Quay, with the same build features and geo-replication. Fixed price for unlimited users and repositories.
</div>
</td>
<td>
<a href="/plans?tab=enterprise" class="btn btn-default">Learn More</a>
</td>
</tr>
</table>
</div>
<!-- Chart --> <!-- Chart -->
<div class="usage-chart" total="subscribedPlan.privateRepos || 0" <div class="usage-chart" total="subscribedPlan.privateRepos || 0"
current="subscription.usedPrivateRepos || 0" current="subscription.usedPrivateRepos || 0"

View file

@ -1,6 +1,6 @@
<div class="repo-panel-changes-element"> <div class="repo-panel-changes-element">
<div class="resource-view" resource="imagesResource" <div class="cor-loader" ng-show="loading"></div>
error-message="'Could not load repository images'"> <div ng-show="!loading">
<h3 class="tab-header"> <h3 class="tab-header">
Visualize Tags: Visualize Tags:
<span class="multiselect-dropdown" items="tagNames" selected-items="selectedTagsSlice" <span class="multiselect-dropdown" items="tagNames" selected-items="selectedTagsSlice"
@ -19,9 +19,6 @@
<!-- Tags Selected --> <!-- Tags Selected -->
<div ng-show="selectedTagsSlice.length > 0"> <div ng-show="selectedTagsSlice.length > 0">
<div id="image-history row" class="resource-view" resource="imagesResource"
error-message="'Cannot load repository images'">
<!-- Tree View container --> <!-- Tree View container -->
<div class="col-md-8"> <div class="col-md-8">
<div class="panel panel-default"> <div class="panel panel-default">
@ -53,6 +50,7 @@
<div class="image-info-sidebar" <div class="image-info-sidebar"
tracker="tracker" tracker="tracker"
image="currentImage" image="currentImage"
image-loader="imageLoader"
tag-selected="setTag(tag)" tag-selected="setTag(tag)"
add-tag-requested="tagActionHandler.askAddTag(image)" add-tag-requested="tagActionHandler.askAddTag(image)"
ng-if="currentImage"> ng-if="currentImage">
@ -61,7 +59,6 @@
</div> </div>
</div> </div>
</div> </div>
</div>
<div class="tag-operations-dialog" repository="repository" images="images" <div class="tag-operations-dialog" repository="repository" image-loader="imageLoader"
action-handler="tagActionHandler" tag-changed="handleTagChanged(data)"></div> action-handler="tagActionHandler" tag-changed="handleTagChanged(data)"></div>

View file

@ -87,7 +87,7 @@
<!-- Status Image --> <!-- Status Image -->
<a ng-href="/repository/{{ repository.namespace }}/{{ repository.name }}"> <a ng-href="/repository/{{ repository.namespace }}/{{ repository.name }}">
<img ng-src="/repository/{{ repository.namespace }}/{{ repository.name }}/status?token={{ repository.status_token }}" <img ng-src="/repository/{{ repository.namespace }}/{{ repository.name }}/status?token={{ repository.status_token }}"
data-title="Docker Repository on Quay.io"> data-title="Container Repository on Quay">
</a> </a>
<!-- Embed formats --> <!-- Embed formats -->

View file

@ -172,7 +172,7 @@
</div> </div>
<div class="tag-operations-dialog" repository="repository" <div class="tag-operations-dialog" repository="repository"
get-images="getImages({'callback': callback})" image-loader="imageLoader"
action-handler="tagActionHandler"></div> action-handler="tagActionHandler"></div>
<div class="fetch-tag-dialog" repository="repository" action-handler="fetchTagActionHandler"></div> <div class="fetch-tag-dialog" repository="repository" action-handler="fetchTagActionHandler"></div>

View file

@ -4,6 +4,6 @@
</span> </span>
<span class="anchor" href="{{ getUrl(commitSha, urlTemplate) }}" target="_blank" <span class="anchor" href="{{ getUrl(commitSha, urlTemplate) }}" target="_blank"
is-only-text="!urlTemplate || !getUrl(commitSha, urlTemplate)"> is-only-text="!urlTemplate || !getUrl(commitSha, urlTemplate)">
{{ commitSha.substring(0, 8) }} {{ commitSha.substring(0, 7) }}
</span> </span>
</span> </span>

View file

@ -49,11 +49,11 @@
<div class="tag-specific-images-view" <div class="tag-specific-images-view"
tag="tagToCreate" tag="tagToCreate"
repository="repo" repository="repository"
images="imagesInternal"
image-cutoff="toTagImage" image-cutoff="toTagImage"
style="margin: 10px; margin-top: 20px; margin-bottom: -10px;" style="margin: 10px; margin-top: 20px; margin-bottom: -10px;"
ng-show="isAnotherImageTag(toTagImage, tagToCreate)"> ng-show="isAnotherImageTag(toTagImage, tagToCreate)"
image-loader="imageLoader">
This will also delete any unattached images and delete the following images: This will also delete any unattached images and delete the following images:
</div> </div>
</div> </div>
@ -100,7 +100,7 @@
<span class="label label-default tag">{{ deleteTagInfo.tag }}</span>? <span class="label label-default tag">{{ deleteTagInfo.tag }}</span>?
<div class="tag-specific-images-view" tag="deleteTagInfo.tag" repository="repository" <div class="tag-specific-images-view" tag="deleteTagInfo.tag" repository="repository"
images="imagesInternal" style="margin-top: 20px"> image-loader="imageLoader" style="margin-top: 20px">
The following images and any other images not referenced by a tag will be deleted: The following images and any other images not referenced by a tag will be deleted:
</div> </div>
</div> </div>

View file

@ -1,4 +1,8 @@
<div class="tag-specific-images-view-element" ng-show="tagSpecificImages.length"> <div class="tag-specific-images-view-element" ng-show="loading">
<div class="cor-loader-inline"></div>
</div>
<div class="tag-specific-images-view-element" ng-show="tagSpecificImages.length && !loading">
<div ng-transclude></div> <div ng-transclude></div>
<div class="image-listings"> <div class="image-listings">
<div class="image-listing" ng-repeat="image in tagSpecificImages | limitTo:5" <div class="image-listing" ng-repeat="image in tagSpecificImages | limitTo:5"

View file

@ -198,205 +198,17 @@
<div class="product-tour enterprise-tour" ng-if="kind == 'enterprise'"> <div class="product-tour enterprise-tour" ng-if="kind == 'enterprise'">
<div class="tour-section row tour-header"> <div class="tour-section row tour-header">
<div class="col-md-12"> <div class="col-md-12">
<div class="tour-section-title">Run Quay.io Behind Your Firewall</div> <div class="tour-section-title"><img src="/static/img/QuayEnterprise_horizontal_color.svg"></div>
<div class="tour-section-description"> <div class="tour-section-description">
<div class="col-lg-6 enterprise-plan col-lg-offset-3"> <div class="col-lg-6 enterprise-plan col-lg-offset-3">
<div class="plan-combine"> Run a private instance of Quay behind your firewall, with all the same build features and geo-replication. Fixed price for unlimited users and repositories.
<img src="/static/img/quay-logo.png">
<span class="plus">+</span>
<img src="/static/img/coreos.svg" style="height: 50px">
</div>
Quay.io has partnered with CoreOS to offer <a href="https://coreos.com/products/enterprise-registry">Enterprise Registry</a>, a version
of Quay.io that can be hosted behind your firewall.
</div>
</div> </div>
</div> </div>
</div> </div>
<div class="tour-section row features">
<div class="col-lg-4 col-md-4 col-sm-4 feature-desc">
<div class="feature-illustration">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
preserveAspectRatio="xMinYMin" width="100px" viewBox="0 0 144 144" enable-background="new 0 0 144 144" xml:space="preserve">
<path fill="none" stroke="#010101" stroke-width="2" stroke-miterlimit="10" d="M124.979,62.559
c0.695,3.413,1.058,6.944,1.058,10.56c0,29.104-23.591,52.695-52.695,52.695c-17.612,0-33.207-8.641-42.776-21.913"/>
<path fill="none" stroke="#010101" stroke-width="2" stroke-miterlimit="10" d="M21.319,81.551c-0.44-2.744-0.671-5.561-0.671-8.431
c0-29.104,23.591-52.695,52.695-52.695c17.442,0,32.906,8.474,42.497,21.532"/>
<polygon fill="#010101" points="122.993,57.123 119.268,67.523 131.435,63.599 "/>
<polygon fill="#010101" points="23.006,87.718 26.73,77.318 14.565,81.242 "/>
<g enable-background="new ">
<path d="M41.5,75.337v-5.679c0-0.204,0.071-0.403,0.215-0.598c0.143-0.194,0.307-0.302,0.491-0.322l4.757-0.737
c0.225-0.716,0.552-1.493,0.982-2.332c-0.696-0.982-1.617-2.159-2.762-3.53c-0.144-0.225-0.215-0.43-0.215-0.613
c0-0.246,0.071-0.45,0.215-0.614c0.45-0.613,1.289-1.523,2.517-2.731c1.228-1.207,2.036-1.811,2.425-1.811
c0.225,0,0.439,0.072,0.645,0.215l3.529,2.762c0.696-0.368,1.483-0.695,2.363-0.981c0.225-2.21,0.46-3.785,0.706-4.727
c0.143-0.491,0.45-0.737,0.921-0.737h5.708c0.225,0,0.43,0.077,0.614,0.23c0.185,0.153,0.287,0.333,0.307,0.537l0.706,4.696
c0.695,0.204,1.462,0.521,2.302,0.951l3.622-2.731c0.164-0.143,0.368-0.215,0.614-0.215c0.225,0,0.44,0.082,0.645,0.245
c2.946,2.722,4.42,4.358,4.42,4.911c0,0.184-0.072,0.379-0.215,0.583c-0.245,0.327-0.675,0.88-1.289,1.657
c-0.614,0.778-1.074,1.392-1.382,1.842c0.471,0.982,0.818,1.821,1.044,2.517l4.665,0.706c0.205,0.041,0.379,0.148,0.521,0.322
c0.144,0.174,0.215,0.373,0.215,0.599v5.678c0,0.205-0.071,0.404-0.215,0.599c-0.143,0.194-0.307,0.302-0.49,0.322l-4.758,0.736
c-0.225,0.717-0.553,1.494-0.982,2.333c0.695,0.981,1.617,2.159,2.763,3.529c0.144,0.205,0.215,0.409,0.215,0.614
c0,0.245-0.071,0.439-0.215,0.583c-0.471,0.613-1.315,1.529-2.532,2.747c-1.217,1.217-2.021,1.826-2.409,1.826
c-0.225,0-0.44-0.072-0.645-0.215l-3.529-2.763c-0.757,0.389-1.545,0.706-2.363,0.951c-0.226,2.21-0.46,3.796-0.706,4.758
c-0.144,0.491-0.45,0.736-0.921,0.736h-5.708c-0.226,0-0.43-0.077-0.614-0.23s-0.287-0.332-0.307-0.537l-0.706-4.695
c-0.696-0.204-1.463-0.522-2.302-0.952l-3.622,2.732c-0.144,0.143-0.348,0.215-0.614,0.215c-0.226,0-0.44-0.082-0.645-0.246
c-2.946-2.721-4.42-4.358-4.42-4.91c0-0.185,0.072-0.379,0.215-0.584c0.205-0.286,0.624-0.828,1.259-1.626
c0.634-0.798,1.115-1.422,1.442-1.872c-0.471-0.9-0.829-1.739-1.074-2.518l-4.665-0.736c-0.205-0.02-0.379-0.117-0.522-0.291
C41.571,75.762,41.5,75.562,41.5,75.337z M55.587,66.988c-1.534,1.535-2.302,3.387-2.302,5.556s0.768,4.021,2.302,5.555
c1.535,1.535,3.386,2.302,5.556,2.302c2.168,0,4.021-0.767,5.555-2.302C68.233,76.564,69,74.713,69,72.544
s-0.767-4.021-2.302-5.556c-1.535-1.534-3.387-2.302-5.555-2.302C58.974,64.687,57.122,65.454,55.587,66.988z M76.857,58.978
v-4.297c0-0.327,1.523-0.645,4.572-0.951c0.266-0.593,0.573-1.125,0.922-1.596c-1.044-2.312-1.566-3.724-1.566-4.235
c0-0.082,0.041-0.154,0.123-0.215c0.082-0.041,0.439-0.246,1.074-0.614s1.238-0.716,1.811-1.044c0.573-0.326,0.88-0.49,0.922-0.49
c0.163,0,0.633,0.476,1.411,1.427c0.777,0.951,1.31,1.642,1.597,2.071c0.408-0.04,0.715-0.061,0.92-0.061s0.512,0.021,0.921,0.061
c1.044-1.452,1.984-2.598,2.823-3.438l0.185-0.061c0.081,0,1.351,0.716,3.806,2.148c0.082,0.061,0.123,0.133,0.123,0.215
c0,0.512-0.521,1.923-1.565,4.235c0.348,0.471,0.655,1.003,0.921,1.596c3.049,0.307,4.573,0.624,4.573,0.951v4.297
c0,0.328-1.524,0.645-4.573,0.952c-0.246,0.552-0.553,1.084-0.921,1.596c1.044,2.312,1.565,3.725,1.565,4.235
c0,0.082-0.041,0.153-0.123,0.215c-2.496,1.453-3.765,2.179-3.806,2.179c-0.164,0-0.634-0.48-1.411-1.442
c-0.778-0.961-1.311-1.657-1.597-2.087c-0.409,0.041-0.716,0.062-0.921,0.062s-0.512-0.021-0.92-0.062
c-0.287,0.43-0.819,1.126-1.597,2.087c-0.778,0.962-1.248,1.442-1.411,1.442c-0.042,0-1.311-0.726-3.807-2.179
c-0.082-0.062-0.123-0.133-0.123-0.215c0-0.511,0.522-1.923,1.566-4.235c-0.369-0.512-0.676-1.044-0.922-1.596
C78.381,59.622,76.857,59.306,76.857,58.978z M76.857,90.406v-4.297c0-0.327,1.523-0.645,4.572-0.951
c0.266-0.594,0.573-1.125,0.922-1.596c-1.044-2.312-1.566-3.725-1.566-4.236c0-0.081,0.041-0.153,0.123-0.215
c0.082-0.04,0.439-0.245,1.074-0.613s1.238-0.716,1.811-1.044c0.573-0.327,0.88-0.491,0.922-0.491c0.163,0,0.633,0.477,1.411,1.428
c0.777,0.951,1.31,1.642,1.597,2.071c0.408-0.04,0.715-0.062,0.92-0.062s0.512,0.021,0.921,0.062
c1.044-1.452,1.984-2.598,2.823-3.438l0.185-0.062c0.081,0,1.351,0.717,3.806,2.148c0.082,0.062,0.123,0.134,0.123,0.215
c0,0.512-0.521,1.924-1.565,4.236c0.348,0.471,0.655,1.002,0.921,1.596c3.049,0.307,4.573,0.624,4.573,0.951v4.297
c0,0.328-1.524,0.645-4.573,0.951c-0.246,0.553-0.553,1.085-0.921,1.597c1.044,2.312,1.565,3.724,1.565,4.235
c0,0.081-0.041,0.153-0.123,0.215c-2.496,1.452-3.765,2.179-3.806,2.179c-0.164,0-0.634-0.48-1.411-1.442
c-0.778-0.962-1.311-1.657-1.597-2.087c-0.409,0.041-0.716,0.062-0.921,0.062s-0.512-0.021-0.92-0.062
c-0.287,0.43-0.819,1.125-1.597,2.087c-0.778,0.962-1.248,1.442-1.411,1.442c-0.042,0-1.311-0.727-3.807-2.179
c-0.082-0.062-0.123-0.134-0.123-0.215c0-0.512,0.522-1.924,1.566-4.235c-0.369-0.512-0.676-1.044-0.922-1.597
C78.381,91.051,76.857,90.734,76.857,90.406z M84.715,56.829c0,1.085,0.383,2.011,1.15,2.778c0.768,0.767,1.693,1.15,2.777,1.15
s2.011-0.384,2.777-1.15c0.768-0.768,1.151-1.693,1.151-2.778c0-1.063-0.389-1.984-1.166-2.762
c-0.778-0.777-1.698-1.167-2.763-1.167s-1.984,0.39-2.762,1.167C85.103,54.845,84.715,55.766,84.715,56.829z M84.715,88.258
c0,1.085,0.383,2.011,1.15,2.777c0.768,0.768,1.693,1.151,2.777,1.151s2.011-0.384,2.777-1.151
c0.768-0.767,1.151-1.692,1.151-2.777c0-1.063-0.389-1.984-1.166-2.762c-0.778-0.777-1.698-1.167-2.763-1.167
s-1.984,0.39-2.762,1.167C85.103,86.273,84.715,87.194,84.715,88.258z"/>
</g>
</svg>
</div>
<h4>Deployment Made Easy</h4>
<p>Trigger container builds when your code is checked into GitHub and passes tests. Automatically pushed into your repository for immediate access by your servers.</p>
</div>
<div class="col-lg-4 col-md-4 col-sm-4 feature-desc">
<div class="feature-illustration">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
preserveAspectRatio="xMinYMin" width="140px" viewBox="0 0 202.5 144" enable-background="new 0 0 202.5 144" xml:space="preserve">
<circle fill="none" stroke="#010101" stroke-width="1.9753" stroke-miterlimit="10" cx="72" cy="72" r="52.567"/>
<circle fill="none" stroke="#010101" stroke-width="1.9753" stroke-miterlimit="10" cx="132.006" cy="72" r="52.567"/>
<g>
<path fill="#00A3A0" d="M90.699,77.453c0-0.552,0.019-1.091,0.055-1.617s0.109-1.094,0.219-1.703s0.247-1.175,0.414-1.696
c0.167-0.521,0.391-1.028,0.672-1.523c0.281-0.495,0.604-0.917,0.969-1.266c0.364-0.349,0.81-0.627,1.336-0.836
c0.526-0.208,1.106-0.312,1.742-0.312c0.094,0,0.312,0.112,0.656,0.336c0.344,0.224,0.731,0.474,1.164,0.75
c0.433,0.276,0.995,0.526,1.688,0.75c0.692,0.224,1.388,0.336,2.086,0.336s1.394-0.112,2.086-0.336
c0.692-0.224,1.255-0.474,1.688-0.75c0.433-0.276,0.82-0.526,1.164-0.75c0.344-0.224,0.562-0.336,0.656-0.336
c0.636,0,1.216,0.104,1.742,0.312c0.526,0.208,0.972,0.487,1.336,0.836c0.364,0.349,0.688,0.771,0.969,1.266
c0.281,0.495,0.505,1.003,0.672,1.523c0.167,0.521,0.305,1.086,0.414,1.696s0.183,1.177,0.219,1.703s0.055,1.065,0.055,1.617
c0,1.25-0.38,2.236-1.141,2.961c-0.761,0.724-1.771,1.086-3.031,1.086H94.871c-1.261,0-2.271-0.362-3.031-1.086
C91.079,79.689,90.699,78.703,90.699,77.453z M97.457,67.742c-1.172-1.172-1.758-2.586-1.758-4.242s0.586-3.07,1.758-4.242
s2.586-1.758,4.242-1.758s3.07,0.586,4.242,1.758s1.758,2.586,1.758,4.242s-0.586,3.07-1.758,4.242s-2.586,1.758-4.242,1.758
S98.629,68.914,97.457,67.742z"/>
</g>
<g>
<path fill="#4BA0D8" d="M42.782,57.118c0-0.552,0.018-1.091,0.055-1.617c0.036-0.526,0.109-1.094,0.219-1.703
s0.247-1.174,0.414-1.695c0.167-0.521,0.391-1.028,0.672-1.523c0.281-0.495,0.604-0.917,0.969-1.266
c0.364-0.349,0.81-0.627,1.336-0.836c0.526-0.208,1.106-0.312,1.742-0.312c0.094,0,0.312,0.112,0.656,0.336
c0.344,0.224,0.731,0.474,1.164,0.75c0.432,0.276,0.995,0.526,1.688,0.75c0.692,0.224,1.388,0.336,2.086,0.336
c0.698,0,1.393-0.112,2.086-0.336c0.692-0.224,1.255-0.474,1.688-0.75c0.432-0.276,0.82-0.526,1.164-0.75
c0.344-0.224,0.562-0.336,0.656-0.336c0.635,0,1.216,0.104,1.742,0.312c0.526,0.208,0.971,0.487,1.336,0.836
c0.364,0.349,0.688,0.771,0.969,1.266c0.281,0.495,0.505,1.003,0.672,1.523c0.167,0.521,0.305,1.086,0.414,1.695
s0.182,1.177,0.219,1.703c0.036,0.526,0.055,1.065,0.055,1.617c0,1.25-0.38,2.237-1.141,2.961
c-0.761,0.724-1.771,1.086-3.031,1.086H46.954c-1.261,0-2.271-0.362-3.031-1.086C43.162,59.355,42.782,58.368,42.782,57.118z
M49.54,47.407c-1.172-1.172-1.758-2.586-1.758-4.242s0.586-3.07,1.758-4.242s2.586-1.758,4.242-1.758s3.07,0.586,4.242,1.758
s1.758,2.586,1.758,4.242s-0.586,3.07-1.758,4.242s-2.586,1.758-4.242,1.758S50.712,48.579,49.54,47.407z"/>
</g>
<g>
<path fill="#4BA0D8" d="M27.422,90.324c0-0.552,0.018-1.091,0.055-1.617c0.036-0.526,0.109-1.094,0.219-1.703
s0.247-1.175,0.414-1.695c0.167-0.521,0.391-1.028,0.672-1.523s0.604-0.917,0.969-1.266c0.364-0.349,0.81-0.628,1.336-0.836
c0.526-0.208,1.106-0.312,1.742-0.312c0.094,0,0.312,0.112,0.656,0.336s0.731,0.474,1.164,0.75c0.432,0.276,0.995,0.526,1.688,0.75
c0.692,0.224,1.388,0.336,2.086,0.336c0.698,0,1.393-0.112,2.086-0.336c0.692-0.224,1.255-0.474,1.688-0.75
c0.432-0.276,0.82-0.526,1.164-0.75s0.562-0.336,0.656-0.336c0.635,0,1.216,0.104,1.742,0.312c0.526,0.208,0.971,0.487,1.336,0.836
c0.364,0.349,0.688,0.771,0.969,1.266s0.505,1.003,0.672,1.523c0.167,0.521,0.305,1.086,0.414,1.695s0.182,1.177,0.219,1.703
c0.036,0.526,0.055,1.065,0.055,1.617c0,1.25-0.38,2.236-1.141,2.961c-0.761,0.724-1.771,1.086-3.031,1.086H31.594
c-1.261,0-2.271-0.362-3.031-1.086C27.802,92.561,27.422,91.574,27.422,90.324z M34.18,80.613
c-1.172-1.172-1.758-2.586-1.758-4.242s0.586-3.07,1.758-4.242s2.586-1.758,4.242-1.758s3.07,0.586,4.242,1.758
s1.758,2.586,1.758,4.242s-0.586,3.07-1.758,4.242s-2.586,1.758-4.242,1.758S35.352,81.785,34.18,80.613z"/>
</g>
<g>
<path fill="#4BA0D8" d="M54.999,106.043c0-0.552,0.018-1.091,0.055-1.617c0.036-0.525,0.109-1.094,0.219-1.703
s0.247-1.174,0.414-1.695c0.167-0.521,0.391-1.028,0.672-1.523c0.281-0.494,0.604-0.916,0.969-1.266
c0.364-0.349,0.81-0.627,1.336-0.836c0.526-0.208,1.106-0.312,1.742-0.312c0.094,0,0.312,0.112,0.656,0.336
c0.344,0.225,0.731,0.475,1.164,0.75c0.432,0.276,0.995,0.526,1.688,0.75c0.692,0.225,1.388,0.336,2.086,0.336
c0.698,0,1.393-0.111,2.086-0.336c0.692-0.224,1.255-0.474,1.688-0.75c0.432-0.275,0.82-0.525,1.164-0.75
c0.344-0.224,0.562-0.336,0.656-0.336c0.635,0,1.216,0.104,1.742,0.312c0.525,0.209,0.971,0.487,1.336,0.836
c0.364,0.35,0.688,0.771,0.969,1.266c0.281,0.495,0.505,1.003,0.672,1.523c0.166,0.521,0.305,1.086,0.414,1.695
s0.182,1.178,0.219,1.703c0.036,0.526,0.055,1.065,0.055,1.617c0,1.25-0.381,2.237-1.141,2.961
c-0.761,0.724-1.771,1.086-3.031,1.086H59.171c-1.261,0-2.271-0.362-3.031-1.086C55.379,108.28,54.999,107.293,54.999,106.043z
M61.757,96.332c-1.172-1.172-1.758-2.586-1.758-4.242s0.586-3.07,1.758-4.242s2.586-1.758,4.242-1.758s3.07,0.586,4.242,1.758
s1.758,2.586,1.758,4.242s-0.586,3.07-1.758,4.242s-2.586,1.758-4.242,1.758S62.929,97.504,61.757,96.332z"/>
</g>
<g>
<path fill="#00A651" d="M141.867,57.118c0-0.552,0.018-1.091,0.055-1.617c0.035-0.526,0.109-1.094,0.219-1.703
s0.246-1.174,0.414-1.695c0.166-0.521,0.391-1.028,0.672-1.523c0.281-0.495,0.604-0.917,0.969-1.266
c0.363-0.349,0.809-0.627,1.336-0.836c0.525-0.208,1.105-0.312,1.742-0.312c0.094,0,0.312,0.112,0.656,0.336
c0.344,0.224,0.73,0.474,1.164,0.75c0.432,0.276,0.994,0.526,1.688,0.75c0.691,0.224,1.387,0.336,2.086,0.336
c0.697,0,1.393-0.112,2.086-0.336c0.691-0.224,1.254-0.474,1.688-0.75c0.432-0.276,0.82-0.526,1.164-0.75
c0.344-0.224,0.562-0.336,0.656-0.336c0.635,0,1.215,0.104,1.742,0.312c0.525,0.208,0.971,0.487,1.336,0.836
c0.363,0.349,0.688,0.771,0.969,1.266c0.281,0.495,0.504,1.003,0.672,1.523c0.166,0.521,0.305,1.086,0.414,1.695
s0.182,1.177,0.219,1.703c0.035,0.526,0.055,1.065,0.055,1.617c0,1.25-0.381,2.237-1.141,2.961
c-0.762,0.724-1.771,1.086-3.031,1.086h-13.656c-1.262,0-2.271-0.362-3.031-1.086C142.246,59.354,141.867,58.368,141.867,57.118z
M148.625,47.407c-1.172-1.172-1.758-2.586-1.758-4.242s0.586-3.07,1.758-4.242s2.586-1.758,4.242-1.758s3.07,0.586,4.242,1.758
s1.758,2.586,1.758,4.242s-0.586,3.07-1.758,4.242s-2.586,1.758-4.242,1.758S149.797,48.579,148.625,47.407z"/>
</g>
<g>
<path fill="#00A651" d="M132.005,102.458c0-0.552,0.019-1.091,0.055-1.617s0.109-1.094,0.219-1.703s0.247-1.175,0.414-1.695
s0.391-1.028,0.672-1.523s0.604-0.917,0.969-1.266s0.81-0.628,1.336-0.836s1.106-0.312,1.742-0.312
c0.094,0,0.312,0.112,0.656,0.336s0.731,0.474,1.164,0.75s0.995,0.526,1.688,0.75s1.388,0.336,2.086,0.336s1.394-0.112,2.086-0.336
s1.255-0.474,1.688-0.75s0.82-0.526,1.164-0.75s0.562-0.336,0.656-0.336c0.636,0,1.216,0.104,1.742,0.312s0.972,0.487,1.336,0.836
s0.688,0.771,0.969,1.266s0.505,1.003,0.672,1.523s0.305,1.086,0.414,1.695s0.183,1.177,0.219,1.703s0.055,1.065,0.055,1.617
c0,1.25-0.38,2.236-1.141,2.961c-0.761,0.724-1.771,1.086-3.031,1.086h-13.656c-1.261,0-2.271-0.362-3.031-1.086
C132.385,104.694,132.005,103.708,132.005,102.458z M138.763,92.747c-1.172-1.172-1.758-2.586-1.758-4.242s0.586-3.07,1.758-4.242
s2.586-1.758,4.242-1.758s3.07,0.586,4.242,1.758s1.758,2.586,1.758,4.242s-0.586,3.07-1.758,4.242s-2.586,1.758-4.242,1.758
S139.935,93.919,138.763,92.747z"/>
</g>
</svg>
</div>
<h4>Teamwork Optimized</h4>
<p>Control how the different teams and projects within your enterprise collaborate on repositories.</p>
</div>
<div class="col-lg-4 col-md-4 col-sm-4 feature-desc">
<div class="feature-illustration">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
preserveAspectRatio="xMinYMin" width="100px" viewBox="0 0 144 144" enable-background="new 0 0 144 144" xml:space="preserve">
<path d="M103.5,123.357V110.5c0-0.595,0.208-1.101,0.625-1.518c0.416-0.416,0.922-0.625,1.518-0.625h0.715v-4.286
c0-2.737,0.982-5.089,2.946-7.053c1.964-1.965,4.315-2.947,7.054-2.947c2.737,0,5.089,0.982,7.054,2.947
c1.964,1.964,2.946,4.315,2.946,7.053v4.286h0.714c0.595,0,1.102,0.209,1.518,0.625c0.417,0.417,0.625,0.923,0.625,1.518v12.857
c0,0.596-0.208,1.102-0.625,1.518c-0.416,0.417-0.923,0.625-1.518,0.625h-21.429c-0.596,0-1.102-0.208-1.518-0.625
C103.708,124.459,103.5,123.953,103.5,123.357z M110.643,108.357h11.429v-4.286c0-1.577-0.558-2.924-1.674-4.04
s-2.463-1.674-4.04-1.674c-1.578,0-2.924,0.558-4.041,1.674c-1.115,1.116-1.674,2.463-1.674,4.04V108.357z"/>
<polyline fill="none" stroke="#000000" stroke-width="2" stroke-miterlimit="10" points="99.889,122.5 21.5,122.5 21.5,20.5
123.5,20.5 123.5,92.469 "/>
<rect x="32.214" y="61.422" fill="#4CA0D8" width="20.156" height="20.156"/>
<rect x="92.63" y="61.422" fill="#4CA0D8" width="20.156" height="20.156"/>
<rect x="62.422" y="61.422" fill="#4CA0D8" width="20.156" height="20.156"/>
<rect x="32.214" y="31.874" fill="#4CA0D8" width="20.156" height="20.156"/>
<rect x="92.63" y="31.874" fill="#4CA0D8" width="20.156" height="20.156"/>
<rect x="62.422" y="31.874" fill="#4CA0D8" width="20.156" height="20.156"/>
<rect x="32.214" y="91.909" fill="#4CA0D8" width="20.156" height="20.156"/>
<g>
<path fill="#4CA0D8" d="M101.579,106.437c0.361-0.361,0.756-0.666,1.179-0.913v-1.452c0-3.714,1.346-6.943,4-9.599
c1.073-1.073,2.244-1.921,3.499-2.564H92.63v20.156H99.9V110.5C99.9,108.94,100.48,107.535,101.579,106.437z"/>
</g>
<rect x="62.422" y="91.909" fill="#4CA0D8" width="20.156" height="20.156"/>
</svg>
</div>
<h4>Secure, Private Storage</h4>
<p>Containers often contain keys and passwords &mdash; take control of your registry by running it behind your firewall on <a href="https://coreos.com/products/managed-linux" data-category="Products: Managed Linux" data-event="Product: Managed Linux">CoreOS Managed Linux</a>.</p>
</div>
</div> </div>
<div class="row" style="text-align: center; margin-bottom: 20px;"> <div class="row" style="text-align: center; margin-bottom: 20px;">
<a href="https://coreos.com/products/enterprise-registry" class="btn btn-primary">Learn more about Enterprise Registry</a> <a href="/plans?tab=enterprise" class="btn btn-primary">Learn more about Quay Enterprise</a>
</div> </div>
</div> </div>

View file

@ -0,0 +1,111 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 17.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="506.031px" height="76.147px" viewBox="0 0 506.031 76.147" enable-background="new 0 0 506.031 76.147"
xml:space="preserve">
<path fill="#003764" d="M139.937,74.386c-1.004,0.502-2.33,0.922-3.975,1.257c-1.646,0.335-3.529,0.503-5.649,0.504
c-5.803,0.002-10.7-1.337-14.688-4.014c-3.991-2.676-6.963-6.164-8.917-10.458c-2.902-0.613-5.539-1.686-7.91-3.221
c-2.373-1.533-4.395-3.499-6.07-5.898c-1.674-2.397-2.959-5.174-3.852-8.326c-0.894-3.15-1.341-6.652-1.342-10.502
c-0.001-4.464,0.597-8.439,1.796-11.928c1.198-3.486,2.885-6.416,5.061-8.788s4.782-4.187,7.823-5.443
c3.041-1.256,6.402-1.885,10.084-1.886c3.683-0.001,7.044,0.626,10.085,1.88c3.041,1.255,5.65,3.083,7.827,5.479
c2.177,2.399,3.864,5.343,5.066,8.829c1.2,3.486,1.801,7.435,1.803,11.842c0.002,7.364-1.559,13.419-4.681,18.162
c-3.124,4.743-7.336,7.869-12.635,9.377c1.283,1.896,3.014,3.221,5.19,3.975c2.176,0.752,4.436,1.128,6.779,1.128
c1.172-0.001,2.273-0.1,3.306-0.294c1.032-0.197,1.938-0.434,2.72-0.713L139.937,74.386z M100.171,33.724
c0.001,5.858,1.091,10.406,3.268,13.641c2.177,3.235,5.134,4.852,8.873,4.851c3.737-0.001,6.695-1.619,8.87-4.855
c2.175-3.236,3.261-7.784,3.26-13.643c-0.001-5.468-1.091-9.736-3.268-12.805c-2.177-3.067-5.135-4.602-8.873-4.601
c-3.739,0.001-6.695,1.537-8.87,4.605C101.256,23.987,100.17,28.256,100.171,33.724z"/>
<path fill="#003764" d="M142.595,6.678l12.387-0.003l0.009,30.548c0.001,5.356,0.811,9.094,2.43,11.214
c1.618,2.12,3.962,3.179,7.031,3.178c3.068-0.001,5.439-1.061,7.113-3.182c1.673-2.121,2.509-5.859,2.507-11.216l-0.009-30.548
l11.884-0.004l0.008,29.292c0.003,9.151-1.823,15.819-5.476,20.005c-3.653,4.186-8.995,6.279-16.025,6.281
c-7.086,0.002-12.5-2.088-16.238-6.271c-3.74-4.184-5.61-10.851-5.613-20.002L142.595,6.678z"/>
<path fill="#003764" d="M218.767,48.251l-16.655,0.005l-3.344,12.974l-12.554,0.003L203.271,6.66l14.813-0.004l17.089,54.562
l-13.056,0.004L218.767,48.251z M216.253,38.627l-1.257-5.021c-0.783-2.733-1.537-5.635-2.262-8.703
c-0.727-3.068-1.454-6.026-2.179-8.871h-0.334c-0.668,2.901-1.352,5.873-2.048,8.914c-0.697,3.041-1.436,5.929-2.215,8.663
l-1.337,5.021L216.253,38.627z"/>
<path fill="#003764" d="M242.868,41.8L226.37,6.653l13.223-0.003l4.857,12.552c0.782,2.065,1.521,4.06,2.22,5.983
c0.697,1.925,1.437,3.948,2.219,6.067h0.334c0.78-2.12,1.547-4.144,2.3-6.068c0.752-1.925,1.519-3.919,2.299-5.985l4.935-12.555
l12.889-0.004L255.17,41.796l0.005,19.417l-12.303,0.004L242.868,41.8z"/>
<path fill="#003764" d="M281.615,21.928l2.403-0.001l0.004,11.362l-0.107,5.135c1.346-1.128,2.785-2.094,4.314-2.896
c1.529-0.801,3.113-1.203,4.752-1.203c3.496-0.001,6.119,1.209,7.867,3.63c1.749,2.422,2.624,5.673,2.625,9.751
c0.001,2.222-0.309,4.206-0.927,5.954c-0.619,1.748-1.465,3.232-2.539,4.453s-2.312,2.149-3.714,2.787
c-1.402,0.637-2.867,0.957-4.396,0.957c-1.275,0-2.596-0.281-3.961-0.846c-1.366-0.563-2.669-1.318-3.907-2.266h-0.108l-0.273,2.458
l-2.021,0.001L281.615,21.928z M284.028,56.342c1.457,1.239,2.86,2.112,4.208,2.622c1.347,0.51,2.53,0.764,3.551,0.764
c1.311,0,2.53-0.292,3.66-0.875c1.128-0.582,2.093-1.403,2.894-2.46c0.801-1.056,1.429-2.321,1.884-3.796
c0.455-1.477,0.683-3.105,0.682-4.891c0-1.603-0.156-3.095-0.465-4.479c-0.311-1.383-0.793-2.575-1.449-3.577
c-0.656-1.001-1.513-1.784-2.568-2.348c-1.057-0.564-2.331-0.847-3.824-0.846c-1.311,0-2.677,0.374-4.097,1.12
c-1.42,0.747-2.914,1.813-4.479,3.197L284.028,56.342z"/>
<path fill="#003764" d="M309.161,70.784c0.363,0.091,0.746,0.137,1.147,0.137c1.639-0.001,3.004-0.656,4.096-1.969
c1.093-1.311,1.967-2.969,2.62-4.971l0.711-2.296l-10.77-26.709l2.622-0.001l6.342,16.496c0.4,1.093,0.846,2.294,1.339,3.604
c0.492,1.311,0.993,2.55,1.503,3.715h0.219c0.4-1.165,0.81-2.404,1.228-3.715c0.419-1.312,0.81-2.514,1.175-3.606l5.622-16.499
l2.457-0.001l-10.37,29.502c-0.363,1.093-0.811,2.158-1.337,3.196c-0.529,1.038-1.146,1.968-1.857,2.787
c-0.71,0.819-1.529,1.475-2.458,1.967c-0.928,0.492-1.976,0.738-3.141,0.739c-0.983,0-1.876-0.183-2.677-0.547l0.546-2.13
C308.469,70.594,308.797,70.693,309.161,70.784z"/>
<path fill="#003764" d="M344.005,43.106c-0.001-2.841,0.372-5.409,1.117-7.703c0.745-2.295,1.793-4.252,3.14-5.874
c1.347-1.62,2.967-2.859,4.86-3.716c1.894-0.855,3.988-1.284,6.282-1.285c2.076-0.001,3.915,0.436,5.518,1.309
c1.603,0.874,2.878,1.876,3.825,3.004l-1.528,1.64c-0.948-1.093-2.077-1.975-3.388-2.648c-1.312-0.673-2.787-1.011-4.427-1.01
c-1.966,0.001-3.74,0.384-5.325,1.149c-1.584,0.765-2.932,1.849-4.041,3.251c-1.111,1.403-1.957,3.105-2.539,5.108
c-0.582,2.004-0.872,4.262-0.872,6.774c0.001,2.513,0.293,4.78,0.876,6.801s1.421,3.741,2.515,5.162
c1.093,1.419,2.413,2.521,3.962,3.304c1.547,0.783,3.286,1.174,5.217,1.173c1.857-0.001,3.505-0.365,4.943-1.094
c1.438-0.729,2.812-1.84,4.124-3.334l1.529,1.584c-1.384,1.603-2.931,2.86-4.643,3.771c-1.711,0.911-3.732,1.366-6.062,1.367
c-2.222,0.001-4.261-0.436-6.119-1.309c-1.857-0.874-3.451-2.12-4.781-3.741c-1.33-1.619-2.359-3.586-3.088-5.899
C344.37,48.577,344.006,45.983,344.005,43.106z"/>
<path fill="#003764" d="M374.652,48.122c-0.001-2.186,0.317-4.143,0.954-5.873c0.637-1.729,1.491-3.177,2.566-4.344
c1.072-1.165,2.32-2.057,3.741-2.678c1.42-0.619,2.913-0.93,4.479-0.93c1.565-0.001,3.067,0.309,4.507,0.928
c1.438,0.619,2.694,1.511,3.77,2.675c1.074,1.166,1.932,2.613,2.569,4.343c0.638,1.73,0.957,3.687,0.958,5.872
c0,2.185-0.318,4.125-0.955,5.818s-1.493,3.123-2.566,4.288c-1.074,1.167-2.331,2.06-3.768,2.679
c-1.439,0.619-2.941,0.929-4.507,0.93c-1.566,0-3.06-0.309-4.479-0.928c-1.422-0.617-2.67-1.51-3.743-2.676
c-1.076-1.164-1.932-2.594-2.569-4.287S374.652,50.307,374.652,48.122z M377.165,48.122c0,1.712,0.237,3.277,0.711,4.696
c0.474,1.421,1.121,2.642,1.94,3.66c0.82,1.021,1.794,1.812,2.924,2.376c1.128,0.564,2.349,0.846,3.66,0.846
c1.311,0,2.54-0.283,3.688-0.848c1.146-0.565,2.13-1.357,2.948-2.378c0.819-1.019,1.466-2.239,1.938-3.661
c0.473-1.42,0.709-2.985,0.709-4.697c-0.001-1.748-0.238-3.333-0.712-4.753s-1.12-2.648-1.94-3.687
c-0.819-1.038-1.803-1.839-2.949-2.402c-1.148-0.564-2.378-0.846-3.688-0.846c-1.312,0-2.532,0.283-3.659,0.848
c-1.13,0.564-2.104,1.366-2.923,2.404c-0.818,1.038-1.466,2.267-1.938,3.688S377.164,46.374,377.165,48.122z"/>
<path fill="#003764" d="M406.059,34.947l2.076-0.001l0.22,4.862h0.164c0.874-1.639,1.938-2.968,3.194-3.989
c1.257-1.019,2.667-1.529,4.233-1.53c0.51,0,0.965,0.037,1.366,0.109c0.4,0.073,0.819,0.219,1.256,0.436l-0.546,2.187
c-0.437-0.182-0.819-0.301-1.147-0.355c-0.327-0.055-0.746-0.082-1.256-0.082c-1.166,0.001-2.396,0.503-3.687,1.504
c-1.293,1.003-2.449,2.741-3.468,5.218l0.006,17.863l-2.404,0.001L406.059,34.947z"/>
<path fill="#003764" d="M420.648,48.109c-0.001-2.148,0.317-4.079,0.954-5.792c0.637-1.711,1.491-3.159,2.566-4.343
c1.072-1.185,2.293-2.095,3.659-2.732c1.365-0.638,2.776-0.957,4.233-0.958c3.132-0.001,5.59,1.056,7.376,3.166
c1.784,2.113,2.678,5.099,2.679,8.958c0,0.328,0,0.656,0.001,0.983c0,0.328-0.037,0.656-0.109,0.983l-18.901,0.006
c0.036,1.639,0.292,3.16,0.766,4.562c0.474,1.402,1.139,2.604,1.995,3.604c0.856,1.002,1.886,1.785,3.088,2.349
c1.201,0.564,2.548,0.847,4.042,0.846c1.42,0,2.704-0.211,3.852-0.63c1.146-0.418,2.212-0.992,3.195-1.722l0.983,1.857
c-1.057,0.62-2.212,1.202-3.468,1.749c-1.257,0.547-2.85,0.82-4.78,0.82c-1.675,0.001-3.25-0.309-4.725-0.927
c-1.477-0.618-2.76-1.52-3.853-2.703c-1.094-1.183-1.959-2.622-2.597-4.315C420.968,52.179,420.648,50.258,420.648,48.109z
M439.821,46.519c-0.001-3.387-0.692-5.927-2.077-7.619c-1.385-1.693-3.261-2.539-5.628-2.539c-1.129,0.001-2.204,0.238-3.223,0.711
c-1.02,0.475-1.938,1.148-2.758,2.022s-1.493,1.939-2.021,3.196c-0.528,1.257-0.864,2.668-1.01,4.233L439.821,46.519z"/>
<path fill="#003764" d="M462.004,61.808c-2.149,0.001-4.134-0.444-5.955-1.336s-3.387-2.157-4.699-3.795
c-1.312-1.639-2.332-3.614-3.061-5.926c-0.729-2.312-1.094-4.89-1.095-7.73s0.362-5.398,1.09-7.675s1.747-4.226,3.058-5.847
c1.312-1.621,2.876-2.859,4.697-3.717c1.82-0.854,3.805-1.284,5.954-1.285c2.186,0,4.188,0.428,6.009,1.282
c1.821,0.855,3.389,2.094,4.699,3.713c1.312,1.621,2.331,3.57,3.062,5.845c0.729,2.276,1.094,4.834,1.095,7.675
s-0.363,5.418-1.09,7.73c-0.729,2.313-1.747,4.289-3.059,5.928c-1.31,1.64-2.876,2.905-4.697,3.799
C466.191,61.36,464.189,61.808,462.004,61.808z M462.003,59.514c1.82,0,3.478-0.392,4.972-1.176c1.492-0.782,2.775-1.894,3.85-3.333
c1.073-1.438,1.902-3.17,2.483-5.19c0.583-2.021,0.874-4.289,0.873-6.802c0-2.476-0.293-4.716-0.877-6.719
c-0.582-2.003-1.412-3.704-2.486-5.107c-1.075-1.401-2.359-2.484-3.852-3.249c-1.494-0.765-3.152-1.146-4.973-1.146
c-1.821,0.001-3.486,0.385-4.997,1.149c-1.512,0.765-2.796,1.85-3.851,3.251c-1.057,1.403-1.875,3.106-2.457,5.109
s-0.872,4.243-0.872,6.719c0.001,2.513,0.293,4.78,0.876,6.802c0.583,2.021,1.402,3.75,2.46,5.188
c1.056,1.439,2.341,2.549,3.853,3.331C458.516,59.125,460.182,59.515,462.003,59.514z"/>
<path fill="#003764" d="M483.962,54.646c1.312,1.494,2.878,2.676,4.699,3.55c1.82,0.873,3.77,1.31,5.846,1.31
c2.767-0.001,4.952-0.666,6.555-1.996c1.602-1.33,2.403-3.068,2.402-5.218c0-1.129-0.175-2.076-0.521-2.841
c-0.347-0.765-0.811-1.428-1.393-1.993c-0.584-0.563-1.267-1.056-2.05-1.475c-0.783-0.417-1.611-0.827-2.485-1.229l-5.464-2.456
c-0.802-0.328-1.631-0.729-2.485-1.202c-0.856-0.472-1.641-1.063-2.351-1.774c-0.71-0.71-1.303-1.546-1.776-2.512
c-0.474-0.965-0.71-2.121-0.711-3.469c0-1.311,0.264-2.504,0.791-3.578c0.527-1.075,1.257-2.003,2.185-2.787
c0.929-0.782,2.039-1.394,3.332-1.831c1.292-0.437,2.686-0.656,4.179-0.656c2.186-0.001,4.125,0.428,5.818,1.281
c1.693,0.856,3.087,1.866,4.18,3.031l-1.419,1.693c-1.058-1.127-2.296-2.028-3.716-2.703c-1.42-0.672-3.042-1.01-4.863-1.009
c-2.403,0-4.333,0.584-5.789,1.749c-1.457,1.167-2.185,2.732-2.185,4.699c0.001,1.057,0.201,1.949,0.603,2.677
c0.4,0.729,0.91,1.357,1.53,1.884c0.619,0.528,1.292,0.983,2.021,1.366c0.728,0.382,1.42,0.718,2.075,1.009l5.464,2.403
c1.02,0.436,1.984,0.928,2.896,1.474c0.91,0.546,1.711,1.183,2.403,1.911c0.691,0.729,1.248,1.594,1.667,2.595
s0.63,2.193,0.63,3.577c0,1.385-0.272,2.668-0.818,3.853c-0.547,1.184-1.32,2.204-2.321,3.059c-1.002,0.857-2.212,1.531-3.632,2.022
c-1.42,0.493-3.004,0.738-4.752,0.739c-2.622,0.001-4.954-0.5-6.994-1.5s-3.769-2.302-5.19-3.905L483.962,54.646z"/>
<g>
<polygon fill="#40B4E5" points="58.633,0 74.909,34.428 58.633,68.857 44.777,68.857 61.053,34.428 44.777,0 "/>
<polygon fill="#003764" points="44.778,68.857 28.502,34.428 44.778,0 58.634,0 42.358,34.428 58.634,68.857 "/>
</g>
<g>
<g>
<polygon fill="#40B4E5" points="37.455,15.491 30.131,0 16.275,0 30.527,30.146 "/>
<polygon fill="#40B4E5" points="30.527,38.711 16.275,68.857 30.131,68.857 37.455,53.365 "/>
</g>
<polygon fill="#003764" points="16.276,68.857 0,34.428 16.276,0 30.132,0 13.856,34.428 30.132,68.857 "/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 11 KiB

View file

@ -0,0 +1,108 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 17.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="636.213px" height="79.308px" viewBox="0 0 636.213 79.308" enable-background="new 0 0 636.213 79.308"
xml:space="preserve">
<g>
<path fill="#003764" d="M139.937,74.385c-1.004,0.502-2.33,0.922-3.975,1.257c-1.646,0.335-3.529,0.503-5.649,0.504
c-5.803,0.002-10.7-1.337-14.688-4.014c-3.991-2.676-6.963-6.164-8.917-10.458c-2.902-0.613-5.539-1.686-7.91-3.221
c-2.373-1.533-4.395-3.499-6.07-5.898c-1.674-2.397-2.959-5.174-3.852-8.326c-0.894-3.15-1.341-6.652-1.342-10.502
c-0.001-4.464,0.597-8.439,1.796-11.928c1.198-3.486,2.885-6.416,5.061-8.788s4.782-4.187,7.824-5.443
c3.04-1.256,6.402-1.885,10.084-1.886c3.683-0.001,7.044,0.626,10.086,1.88c3.041,1.255,5.65,3.083,7.827,5.479
c2.177,2.399,3.864,5.343,5.066,8.829c1.2,3.486,1.801,7.435,1.803,11.842c0.002,7.364-1.559,13.419-4.681,18.162
c-3.124,4.743-7.335,7.869-12.635,9.377c1.284,1.896,3.014,3.221,5.19,3.975c2.176,0.752,4.436,1.128,6.779,1.128
c1.172-0.001,2.273-0.1,3.306-0.294c1.032-0.197,1.938-0.434,2.72-0.713L139.937,74.385z M100.171,33.723
c0.002,5.858,1.091,10.406,3.268,13.641c2.177,3.235,5.134,4.852,8.873,4.851c3.737-0.001,6.695-1.619,8.87-4.855
c2.175-3.236,3.262-7.784,3.26-13.643c-0.001-5.468-1.091-9.736-3.268-12.805c-2.177-3.067-5.135-4.602-8.873-4.601
c-3.739,0.001-6.695,1.537-8.87,4.605C101.256,23.986,100.169,28.255,100.171,33.723z"/>
<path fill="#003764" d="M142.594,6.677l12.387-0.003l0.009,30.548c0.001,5.356,0.811,9.094,2.43,11.214
c1.618,2.12,3.962,3.179,7.031,3.178c3.068-0.001,5.439-1.061,7.113-3.182s2.509-5.859,2.507-11.216l-0.009-30.548l11.884-0.004
l0.008,29.292c0.003,9.151-1.823,15.819-5.476,20.005c-3.653,4.186-8.995,6.279-16.025,6.281c-7.086,0.002-12.5-2.088-16.238-6.271
c-3.74-4.184-5.61-10.851-5.613-20.002L142.594,6.677z"/>
<path fill="#003764" d="M218.766,48.25l-16.655,0.005l-3.344,12.974l-12.554,0.003L203.27,6.659l14.813-0.004l17.089,54.562
l-13.056,0.004L218.766,48.25z M216.252,38.626l-1.257-5.021c-0.783-2.733-1.537-5.635-2.262-8.703
c-0.727-3.068-1.454-6.026-2.179-8.871h-0.334c-0.669,2.901-1.352,5.873-2.048,8.914c-0.697,3.041-1.436,5.929-2.215,8.663
l-1.337,5.021L216.252,38.626z"/>
<path fill="#003764" d="M242.867,41.799L226.37,6.652l13.223-0.003l4.857,12.552c0.782,2.065,1.521,4.06,2.22,5.983
c0.697,1.925,1.437,3.948,2.22,6.067h0.334c0.78-2.12,1.547-4.144,2.3-6.068c0.752-1.925,1.519-3.919,2.299-5.985l4.935-12.555
l12.889-0.004L255.17,41.795l0.005,19.417l-12.303,0.004L242.867,41.799z"/>
<path fill="#003764" d="M302.11,6.045l30.213-0.009l0.001,3.348L305.96,9.392l0.006,20.84l22.094-0.007l0.001,3.348l-22.094,0.007
l0.007,24.271l27.2-0.008l0.001,3.348l-31.049,0.009L302.11,6.045z"/>
<path fill="#003764" d="M344.796,21.014l3.181-0.001l0.337,6.193h0.251c2.063-2.064,4.184-3.78,6.359-5.149
c2.175-1.366,4.657-2.052,7.448-2.053c4.185-0.001,7.252,1.254,9.207,3.764c1.953,2.511,2.931,6.389,2.933,11.632l0.007,25.777
l-3.683,0.002l-0.007-25.275c-0.002-4.295-0.714-7.461-2.137-9.499c-1.424-2.035-3.781-3.054-7.073-3.053
c-2.399,0.001-4.575,0.63-6.526,1.886c-1.954,1.256-4.157,3.139-6.611,5.65l0.009,30.297l-3.683,0.001L344.796,21.014z"/>
<path fill="#003764" d="M390.242,24.181l-6.276,0.002l-0.001-2.845l6.36-0.337l0.499-11.634l3.181-0.001L394.008,21l11.633-0.004
l0.001,3.18l-11.633,0.004l0.008,26.196c0,1.284,0.098,2.441,0.294,3.473c0.194,1.033,0.545,1.924,1.047,2.678
c0.502,0.753,1.185,1.339,2.051,1.757c0.864,0.418,1.995,0.627,3.39,0.627c0.781-0.001,1.633-0.126,2.552-0.378
c0.921-0.251,1.743-0.545,2.469-0.879l1.006,3.013c-1.172,0.447-2.344,0.809-3.516,1.089c-1.171,0.278-2.175,0.419-3.012,0.419
c-1.953,0-3.572-0.292-4.854-0.877c-1.284-0.585-2.316-1.408-3.097-2.469c-0.783-1.059-1.327-2.342-1.634-3.849
c-0.308-1.506-0.462-3.151-0.462-4.938L390.242,24.181z"/>
<path fill="#003764" d="M411.253,41.165c-0.001-3.292,0.486-6.25,1.463-8.873c0.975-2.622,2.285-4.841,3.932-6.654
c1.645-1.813,3.515-3.209,5.605-4.187c2.093-0.976,4.254-1.466,6.486-1.467c4.798-0.001,8.563,1.617,11.299,4.852
c2.734,3.235,4.104,7.812,4.106,13.725c0,0.502,0,1.004,0,1.506c0,0.503-0.057,1.005-0.168,1.507l-28.957,0.008
c0.056,2.511,0.448,4.841,1.174,6.988c0.726,2.148,1.745,3.99,3.057,5.522c1.312,1.535,2.889,2.733,4.729,3.598
c1.841,0.865,3.905,1.297,6.194,1.296c2.176-0.001,4.142-0.321,5.899-0.965c1.757-0.641,3.39-1.521,4.895-2.637l1.508,2.844
c-1.618,0.951-3.389,1.843-5.313,2.681c-1.925,0.837-4.365,1.256-7.322,1.257c-2.567,0.001-4.98-0.473-7.24-1.421
c-2.26-0.946-4.228-2.327-5.901-4.141c-1.675-1.812-3.001-4.016-3.978-6.61C411.743,47.399,411.254,44.457,411.253,41.165z
M440.629,38.729c-0.002-5.188-1.063-9.08-3.184-11.674c-2.122-2.595-4.995-3.891-8.621-3.89c-1.73,0.001-3.377,0.364-4.938,1.09
c-1.562,0.726-2.97,1.759-4.226,3.098c-1.254,1.339-2.287,2.972-3.095,4.896c-0.809,1.926-1.324,4.088-1.546,6.486L440.629,38.729z
"/>
<path fill="#003764" d="M455.018,20.983l3.181-0.001l0.337,7.448l0.251-0.001c1.339-2.511,2.97-4.547,4.895-6.11
c1.925-1.562,4.086-2.345,6.485-2.346c0.781,0,1.478,0.057,2.093,0.167c0.613,0.112,1.255,0.335,1.925,0.669l-0.836,3.348
c-0.67-0.278-1.256-0.46-1.758-0.543s-1.145-0.125-1.925-0.125c-1.786,0.001-3.669,0.769-5.648,2.303
c-1.98,1.536-3.752,4.2-5.312,7.994l0.008,27.367l-3.683,0.002L455.018,20.983z"/>
<path fill="#003764" d="M485.242,64.66l0.005,14.646l-3.683,0.001l-0.018-58.333l3.181-0.001l0.337,5.021h0.251
c1.951-1.562,4.086-2.959,6.401-4.187c2.313-1.228,4.728-1.844,7.238-1.844c5.356-0.002,9.375,1.853,12.054,5.562
c2.679,3.711,4.02,8.69,4.021,14.938c0.001,3.404-0.473,6.444-1.42,9.122c-0.948,2.679-2.246,4.953-3.89,6.822
c-1.646,1.871-3.543,3.294-5.69,4.271c-2.148,0.977-4.393,1.465-6.736,1.466c-1.897,0.001-3.851-0.432-5.859-1.295
c-2.009-0.864-4.073-2.077-6.194-3.639L485.242,64.66z M485.239,53.697c2.345,1.896,4.521,3.234,6.529,4.015
c2.009,0.781,3.795,1.171,5.357,1.17c2.009-0.001,3.876-0.447,5.606-1.341c1.729-0.892,3.207-2.148,4.435-3.768
c1.227-1.617,2.188-3.558,2.886-5.817c0.696-2.26,1.045-4.756,1.044-7.49c-0.001-2.454-0.239-4.742-0.714-6.862
c-0.476-2.12-1.214-3.947-2.22-5.481c-1.004-1.534-2.315-2.733-3.935-3.598c-1.618-0.864-3.571-1.296-5.858-1.295
c-2.009,0-4.088,0.573-6.234,1.717c-2.148,1.145-4.449,2.777-6.903,4.898L485.239,53.697z"/>
<path fill="#003764" d="M527.074,20.961l3.181-0.001l0.337,7.448h0.251c1.339-2.511,2.97-4.548,4.895-6.11s4.086-2.346,6.485-2.346
c0.781-0.001,1.478,0.056,2.093,0.167c0.613,0.112,1.255,0.335,1.925,0.668l-0.836,3.349c-0.67-0.278-1.256-0.46-1.758-0.544
c-0.502-0.083-1.145-0.124-1.925-0.124c-1.786,0-3.669,0.769-5.648,2.302c-1.98,1.537-3.752,4.201-5.312,7.995l0.008,27.367
l-3.683,0.001L527.074,20.961z"/>
<path fill="#003764" d="M555.526,11.244c-0.95,0-1.745-0.306-2.386-0.92c-0.642-0.612-0.962-1.394-0.963-2.343
c0-1.06,0.32-1.883,0.962-2.47c0.641-0.586,1.435-0.879,2.385-0.879c0.947-0.001,1.744,0.292,2.386,0.878
c0.641,0.586,0.963,1.409,0.963,2.469c0.001,0.949-0.321,1.73-0.962,2.344C557.27,10.938,556.474,11.243,555.526,11.244z
M553.604,20.953l3.683-0.001l0.012,40.172l-3.683,0.001L553.604,20.953z"/>
<path fill="#003764" d="M569.933,53.839c1.729,1.451,3.57,2.664,5.524,3.64c1.952,0.976,4.38,1.464,7.282,1.463
c3.18-0.001,5.564-0.797,7.154-2.388s2.385-3.445,2.384-5.566c0-1.227-0.293-2.314-0.88-3.264
c-0.586-0.948-1.354-1.758-2.302-2.426c-0.95-0.67-1.996-1.256-3.14-1.757c-1.144-0.502-2.301-0.976-3.473-1.422
c-1.508-0.557-3.028-1.142-4.562-1.757c-1.535-0.612-2.916-1.351-4.144-2.217c-1.229-0.863-2.218-1.895-2.973-3.096
c-0.753-1.199-1.129-2.662-1.13-4.393c0-1.45,0.277-2.832,0.836-4.144c0.557-1.311,1.393-2.455,2.51-3.432
c1.115-0.977,2.468-1.744,4.058-2.304c1.59-0.557,3.444-0.838,5.565-0.838c2.12-0.001,4.198,0.39,6.235,1.17
c2.037,0.781,3.78,1.785,5.231,3.011l-2.008,2.596c-1.339-1.004-2.763-1.854-4.269-2.552c-1.507-0.696-3.293-1.045-5.356-1.044
c-1.562,0-2.902,0.21-4.018,0.629c-1.116,0.418-2.051,0.963-2.803,1.633c-0.753,0.669-1.312,1.451-1.674,2.343
c-0.362,0.894-0.543,1.787-0.543,2.679c0,1.172,0.266,2.163,0.796,2.971c0.53,0.81,1.241,1.521,2.136,2.135
c0.892,0.613,1.91,1.157,3.055,1.631c1.143,0.474,2.301,0.934,3.474,1.38c1.562,0.613,3.124,1.227,4.687,1.839
c1.562,0.615,2.972,1.367,4.228,2.259c1.256,0.894,2.273,1.995,3.056,3.305c0.781,1.312,1.173,2.944,1.173,4.896
c0.001,1.506-0.292,2.943-0.878,4.31c-0.585,1.368-1.463,2.567-2.634,3.601c-1.172,1.032-2.595,1.856-4.269,2.47
s-3.598,0.921-5.773,0.922c-3.013,0.001-5.776-0.543-8.287-1.63s-4.66-2.383-6.445-3.89L569.933,53.839z"/>
<path fill="#003764" d="M603.321,41.109c-0.001-3.292,0.486-6.25,1.463-8.873c0.975-2.622,2.285-4.841,3.932-6.654
c1.645-1.813,3.515-3.209,5.605-4.187c2.093-0.976,4.254-1.466,6.486-1.467c4.798-0.001,8.563,1.617,11.299,4.852
c2.734,3.235,4.104,7.812,4.106,13.725c0,0.502,0,1.004,0,1.506c0,0.503-0.057,1.005-0.168,1.507l-28.957,0.008
c0.056,2.511,0.448,4.841,1.174,6.988c0.726,2.148,1.745,3.99,3.057,5.522c1.312,1.535,2.889,2.733,4.729,3.598
c1.841,0.865,3.905,1.297,6.194,1.296c2.176-0.001,4.142-0.321,5.899-0.965c1.757-0.641,3.39-1.521,4.895-2.637l1.508,2.844
c-1.618,0.951-3.389,1.843-5.313,2.681c-1.925,0.837-4.365,1.256-7.322,1.257c-2.567,0.001-4.98-0.474-7.24-1.421
c-2.26-0.946-4.228-2.327-5.901-4.141c-1.675-1.812-3.001-4.016-3.978-6.61C603.812,47.343,603.322,44.401,603.321,41.109z
M632.697,38.673c-0.002-5.188-1.063-9.08-3.184-11.674c-2.122-2.595-4.995-3.891-8.621-3.89c-1.73,0.001-3.377,0.364-4.938,1.09
c-1.562,0.726-2.97,1.759-4.226,3.098c-1.254,1.339-2.287,2.972-3.095,4.896c-0.809,1.926-1.324,4.088-1.546,6.486L632.697,38.673z
"/>
</g>
<g>
<polygon fill="#68C28D" points="58.633,0 74.909,34.428 58.633,68.857 44.777,68.857 61.053,34.428 44.777,0 "/>
<polygon fill="#003764" points="44.778,68.857 28.502,34.428 44.778,0 58.634,0 42.358,34.428 58.634,68.857 "/>
</g>
<g>
<g>
<polygon fill="#68C28D" points="37.455,15.491 30.131,0 16.275,0 30.527,30.146 "/>
<polygon fill="#68C28D" points="30.527,38.711 16.275,68.857 30.131,68.857 37.455,53.365 "/>
</g>
<polygon fill="#003764" points="16.276,68.857 0,34.428 16.276,0 30.132,0 13.856,34.428 30.132,68.857 "/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 10 KiB

View file

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 17.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="271.647px" height="76.147px" viewBox="0 0 271.647 76.147" enable-background="new 0 0 271.647 76.147"
xml:space="preserve">
<path fill="#003764" d="M139.937,74.386c-1.004,0.502-2.33,0.922-3.975,1.257c-1.646,0.335-3.529,0.503-5.649,0.504
c-5.803,0.002-10.7-1.337-14.688-4.014c-3.991-2.676-6.963-6.164-8.917-10.458c-2.902-0.613-5.539-1.686-7.91-3.221
c-2.373-1.533-4.395-3.499-6.07-5.898c-1.674-2.397-2.959-5.174-3.852-8.326c-0.894-3.15-1.341-6.652-1.342-10.502
c-0.001-4.464,0.597-8.439,1.796-11.928c1.198-3.486,2.885-6.416,5.061-8.788s4.782-4.187,7.823-5.443
c3.041-1.256,6.402-1.885,10.084-1.886c3.683-0.001,7.044,0.626,10.085,1.88c3.041,1.255,5.65,3.083,7.827,5.479
c2.177,2.399,3.864,5.343,5.066,8.829c1.2,3.486,1.801,7.435,1.803,11.842c0.002,7.364-1.559,13.419-4.681,18.162
c-3.124,4.743-7.336,7.869-12.635,9.377c1.283,1.896,3.014,3.221,5.19,3.975c2.176,0.752,4.436,1.128,6.779,1.128
c1.172-0.001,2.273-0.1,3.306-0.294c1.032-0.197,1.938-0.434,2.72-0.713L139.937,74.386z M100.171,33.724
c0.001,5.858,1.091,10.406,3.268,13.641c2.177,3.235,5.134,4.852,8.873,4.851c3.737-0.001,6.695-1.619,8.87-4.855
c2.175-3.236,3.261-7.784,3.26-13.643c-0.001-5.468-1.091-9.736-3.268-12.805c-2.177-3.067-5.135-4.602-8.873-4.601
c-3.739,0.001-6.695,1.537-8.87,4.605C101.256,23.987,100.17,28.256,100.171,33.724z"/>
<path fill="#003764" d="M142.595,6.678l12.387-0.003l0.009,30.548c0.001,5.356,0.811,9.094,2.43,11.214
c1.618,2.12,3.962,3.179,7.031,3.178c3.068-0.001,5.439-1.061,7.113-3.182c1.673-2.121,2.509-5.859,2.507-11.216l-0.009-30.548
l11.884-0.004l0.008,29.292c0.003,9.151-1.823,15.819-5.476,20.005c-3.653,4.186-8.995,6.279-16.025,6.281
c-7.086,0.002-12.5-2.088-16.238-6.271c-3.74-4.184-5.61-10.851-5.613-20.002L142.595,6.678z"/>
<path fill="#003764" d="M218.767,48.251l-16.655,0.005l-3.344,12.974l-12.554,0.003L203.271,6.66l14.813-0.004l17.089,54.562
l-13.056,0.004L218.767,48.251z M216.253,38.627l-1.257-5.021c-0.783-2.733-1.537-5.635-2.262-8.703
c-0.727-3.068-1.454-6.026-2.179-8.871h-0.334c-0.668,2.901-1.352,5.873-2.048,8.914c-0.697,3.041-1.436,5.929-2.215,8.663
l-1.337,5.021L216.253,38.627z"/>
<path fill="#003764" d="M242.868,41.8L226.37,6.653l13.223-0.003l4.857,12.552c0.782,2.065,1.521,4.06,2.22,5.983
c0.697,1.925,1.437,3.948,2.219,6.067h0.334c0.78-2.12,1.547-4.144,2.3-6.068c0.752-1.925,1.519-3.919,2.299-5.985l4.935-12.555
l12.889-0.004L255.17,41.796l0.005,19.417l-12.303,0.004L242.868,41.8z"/>
<g>
<polygon fill="#40B4E5" points="58.633,0 74.909,34.428 58.633,68.857 44.777,68.857 61.053,34.428 44.777,0 "/>
<polygon fill="#003764" points="44.778,68.857 28.502,34.428 44.778,0 58.634,0 42.358,34.428 58.634,68.857 "/>
</g>
<g>
<g>
<polygon fill="#40B4E5" points="37.455,15.491 30.131,0 16.275,0 30.527,30.146 "/>
<polygon fill="#40B4E5" points="30.527,38.711 16.275,68.857 30.131,68.857 37.455,53.365 "/>
</g>
<polygon fill="#003764" points="16.276,68.857 0,34.428 16.276,0 30.132,0 13.856,34.428 30.132,68.857 "/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

View file

@ -1,48 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 17.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="325.68px" height="112.574px" viewBox="0 0 325.68 112.574" enable-background="new 0 0 325.68 112.574"
xml:space="preserve">
<path d="M95.701,60.164c0,27.658,9.385,30.316,24.336,30.316c14.95-0.083,24.502-3.488,24.502-30.316
c0-28.489-10.133-31.229-24.502-31.229C105.916,28.934,95.701,31.675,95.701,60.164 M107.329,60.164
c0-19.352,3.738-20.515,12.708-20.515c9.22,0,12.874,1.578,12.874,20.515c0,16.363-3.073,19.602-12.874,19.602
C109.654,79.766,107.329,76.775,107.329,60.164z M116.465,92.889c0.665,10.632,11.711,13.538,20.1,11.711v-9.385
c-4.9,1.329-10.881,2.076-13.538-2.326H116.465z"/>
<path d="M150.438,75.364c0,8.472,2.907,15.532,13.123,15.532c6.727,0,9.635-2.824,14.037-5.399l2.243,4.651h9.386V46.044h-11.628
v31.645c-3.405,2.658-5.98,3.821-10.631,3.821c-2.907,0-4.901-1.495-4.901-5.897V46.044h-11.628V75.364z"/>
<path d="M195.457,78.935c0,5.731,3.987,11.462,12.459,11.711c6.728,0.166,12.957-4.402,13.455-5.316l2.409,4.818h9.22V59.417
c0-10.964-6.479-14.203-15.947-14.203c-8.223,0-14.286,0.665-20.1,2.907v7.226c3.571-0.249,14.618-0.914,18.522-0.914
c3.654,0,5.897,0.665,5.897,5.399c0.083,0.083,0,2.824,0,2.824c0,0.083-12.957,0-12.957,0c-9.469,0-12.957,5.648-12.957,12.127
V78.935z M207.085,77.274v-2.16c0-1.91,0.499-4.236,3.572-4.485l10.714-0.831v8.057c0,0-5.316,3.488-10.133,3.488
C209.078,81.344,207.085,80.929,207.085,77.274z"/>
<path d="M236.573,46.044l8.638,32.891c2.159,8.14,6.977,11.213,13.123,11.213l-6.229,22.426h8.306l6.063-8.472l15.283-58.058H269.63
l-8.804,34.137c-0.664-0.083-3.489,0.249-4.402-3.239l-7.641-30.898H236.573z"/>
<path d="M283.668,89.321c0,0.621,0.443,1.021,1.065,1.021h3.815c0.621,0,1.109-0.222,1.109-1.021v-3.992
c0-0.71-0.311-1.109-1.109-1.109h-3.815c-0.71,0-1.065,0.444-1.065,1.109V89.321z"/>
<path d="M294.138,63.281c0,0.621,0.444,1.021,1.065,1.021h4.658c0.621,0,0.976-0.355,0.976-1.021v-3.904
c0-0.621-0.311-1.153-0.976-1.153h-4.658c-0.71,0-1.065,0.444-1.065,1.153V63.281z M294.36,90.342h6.211V66.785h-6.211V90.342z"/>
<path d="M304.208,78.896c0,8.429,2.617,11.667,10.736,11.667c8.251,0,10.735-2.928,10.735-11.667
c0-9.361-2.617-12.333-10.735-12.333C307.003,66.563,304.208,69.27,304.208,78.896 M310.419,78.896c0-6.61,0.843-7.364,4.525-7.364
c3.726,0,4.525,0.754,4.525,7.364c0,6.21-1.02,6.654-4.525,6.654C311.661,85.55,310.419,84.974,310.419,78.896z"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M38.505,0c13.241,0,24.037,13.64,24.588,30.737h-8.876
c0-11.805-7.035-21.374-15.712-21.374c-8.678,0-15.713,9.569-15.713,21.374h-8.876C14.467,13.64,25.264,0,38.505,0"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M38.504,57.156c2.483,0,4.495,2.013,4.495,4.495c0,0.946-0.292,1.823-0.791,2.547
l2.294,12.511H32.507l2.294-12.511c-0.499-0.724-0.791-1.601-0.791-2.547C34.009,59.168,36.022,57.156,38.504,57.156"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.394,34.801h4.724c1.317,0,2.394,1.077,2.394,2.394v59.474
c0,1.317-1.077,2.394-2.394,2.394H2.394C1.077,99.063,0,97.986,0,96.669V37.195C0,35.878,1.077,34.801,2.394,34.801"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M16.314,34.801h4.724c1.316,0,2.394,1.077,2.394,2.394v59.474
c0,1.317-1.077,2.394-2.394,2.394h-4.724c-1.316,0-2.394-1.077-2.394-2.394V37.195C13.92,35.878,14.997,34.801,16.314,34.801"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M29.183,34.801h4.724c1.317,0,2.394,1.077,2.394,2.394V51.96
c0,1.317-1.077,2.394-2.394,2.394h-4.724c-1.317,0-2.394-1.077-2.394-2.394V37.195C26.789,35.878,27.866,34.801,29.183,34.801"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M43.103,34.801h4.724c1.316,0,2.393,1.077,2.393,2.394V51.96
c0,1.317-1.077,2.394-2.393,2.394h-4.724c-1.316,0-2.394-1.077-2.394-2.394V37.195C40.709,35.878,41.786,34.801,43.103,34.801"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M55.971,34.801h4.724c1.317,0,2.394,1.077,2.394,2.394v59.474
c0,1.317-1.077,2.394-2.394,2.394h-4.724c-1.316,0-2.393-1.077-2.393-2.394V37.195C53.578,35.878,54.655,34.801,55.971,34.801"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M69.891,34.801h4.724c1.317,0,2.394,1.077,2.394,2.394v59.474
c0,1.317-1.077,2.394-2.394,2.394h-4.724c-1.317,0-2.394-1.077-2.394-2.394V37.195C67.498,35.878,68.575,34.801,69.891,34.801"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M29.183,79.51h4.724c1.317,0,2.394,1.077,2.394,2.394V96.67
c0,1.317-1.077,2.394-2.394,2.394h-4.724c-1.317,0-2.394-1.077-2.394-2.394V81.904C26.789,80.588,27.866,79.51,29.183,79.51"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M43.103,79.51h4.724c1.316,0,2.393,1.077,2.393,2.394V96.67
c0,1.317-1.077,2.394-2.393,2.394h-4.724c-1.316,0-2.394-1.077-2.394-2.394V81.904C40.709,80.588,41.786,79.51,43.103,79.51"/>
</svg>

Before

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 531 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 884 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 151 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 920 KiB

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="-344 260 102.2 94" style="enable-background:new -344 260 102.2 94;" xml:space="preserve">
<g>
<polygon style="fill:#ECEEEF;" points="-321.7,260 -263.9,260 -241.6,307 -263.9,354 -321.7,354 -343.9,307 "/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 496 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 67 KiB

59
static/img/qe-legos.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 36 KiB

BIN
static/img/quay_favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Some files were not shown because too many files have changed in this diff Show more