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
- Added experimental Dex login support (#447, #468)

View file

@ -6,7 +6,7 @@ ENV DEBIAN_FRONTEND noninteractive
ENV HOME /root
# 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
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.
## 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
- Framework for microservice decomposition
- Improve documentation
- 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
### Sprint 11/18 - 12/1 Builds (Planned 8 Days)
- Move build traffic to Packet
- Preliminary tests reduce build start latency from 2 minutes to 20 seconds
- Multi-step builds
- build artifact
- bundle artifact
- test bundle
- Docker Notary
- Support signed images with a known key
- Give thanks
### 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
### 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
### Long Term
- Become the Tectonic app store
- Pods/apps as top level concept
### Unallocated
- Builds as top level concept
- Multiple Quay.io repos from a single git push
- Multi-step builds
- build artifact
- bundle artifact
- test bundle
- 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
- Cockroach?
- 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.configutil import generate_secret_key
from util.config.superusermanager import SuperUserManager
from util.secscan.secscanendpoint import SecurityScanEndpoint
OVERRIDE_CONFIG_DIRECTORY = 'conf/stack/'
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,
reporter=MetricQueueReporter(metric_queue))
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.
_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)
REPO=quay.io/quay/quay:$TAG
#!/usr/bin/env bash
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 .
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'])
if match:
email_address = match.group(1)
author_info = JSONPathDict(lookup_author(email_address))
author = lookup_author(match.group(1))
author_info = JSONPathDict(author) if author is not None else None
if author_info:
config['commit_info.author.username'] = 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
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):
""" Returns an authorized API client. """

View file

@ -206,7 +206,7 @@ class GithubBuildTrigger(BuildTriggerHandler):
hook = gh_repo.create_hook('web', webhook_config)
config['hook_id'] = hook.id
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
msg = GithubBuildTrigger._get_error_message(ghe, default_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;
}
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/ {
proxy_buffering off;
@ -77,14 +98,8 @@ location /c1/ {
location /static/ {
# checks for static file, if not found proxy to app
alias /static/;
}
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';
alias /static/;
error_page 404 /404;
}
location ~ ^/b1/controller(/?)(.*) {

View file

@ -45,8 +45,8 @@ class DefaultConfig(object):
PREFERRED_URL_SCHEME = 'http'
SERVER_HOSTNAME = 'localhost:5000'
REGISTRY_TITLE = 'CoreOS Enterprise Registry'
REGISTRY_TITLE_SHORT = 'Enterprise Registry'
REGISTRY_TITLE = 'Quay Enterprise'
REGISTRY_TITLE_SHORT = 'Quay Enterprise'
CONTACT_INFO = [
'mailto:support@quay.io',
@ -253,3 +253,12 @@ class DefaultConfig(object):
# Experiment: 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',
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):
code = CharField(default=random_string_generator(), unique=True, index=True)
@ -487,11 +484,11 @@ class EmailConfirmation(BaseModel):
class ImageStorage(BaseModel):
uuid = CharField(default=uuid_generator, index=True, unique=True)
checksum = CharField(null=True)
image_size = BigIntegerField(null=True)
uncompressed_size = BigIntegerField(null=True)
uploading = BooleanField(default=True, null=True)
cas_path = BooleanField(default=True)
content_checksum = CharField(null=True, index=True)
class ImageStorageTransformation(BaseModel):
@ -573,6 +570,11 @@ class Image(BaseModel):
command = TextField(null=True)
aggregate_size = BigIntegerField(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:
database = db
@ -580,6 +582,8 @@ class Image(BaseModel):
indexes = (
# we don't really want duplicates
(('repository', 'docker_image_id'), True),
(('security_indexed_engine', 'security_indexed'), False),
)
@ -746,6 +750,7 @@ class RepositoryNotification(BaseModel):
method = ForeignKeyField(ExternalNotificationMethod)
title = CharField(null=True)
config_json = TextField()
event_config_json = TextField(default='{}')
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.dialects.mysql import LONGTEXT
import uuid
class EngineLongText(TypeDecorator):
"""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())
from util.migrate import UTF8LongText
def upgrade(tables):
### commands auto generated by Alembic - please adjust! ###
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 ###

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
import sqlalchemy as sa
from util.migrate import UTF8LongText
def upgrade(tables):
### 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('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('v1_json_metadata', sa.Text(), nullable=True))
op.add_column('image', sa.Column('v1_json_metadata', UTF8LongText(), nullable=True))
### 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(Namespace, on=(Namespace.id == Repository.namespace_user))
.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:
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():
repo = _basequery.get_existing_repository(namespace, repo_name)
try:
storage = ImageStorage.get(checksum=blob_digest)
storage = ImageStorage.get(content_checksum=blob_digest)
storage.image_size = byte_count
storage.save()
ImageStoragePlacement.get(storage=storage, location=location_obj)
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)
except ImageStoragePlacement.DoesNotExist:
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:
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()
if created_date_str is not None:
try:
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.
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.command = command
fetched.v1_json_metadata = v1_json_metadata
if parent:
fetched.ancestors = '%s%s/' % (parent.ancestors, parent.id)
fetched.parent = parent
fetched.save()
return fetched
@ -363,7 +365,8 @@ def get_repo_image_by_storage_checksum(namespace, repository_name, storage_check
.join(Repository)
.join(Namespace, on=(Namespace.id == Repository.namespace_user))
.where(Repository.name == repository_name, Namespace.username == namespace,
ImageStorage.checksum == storage_checksum, ImageStorage.uploading == False)
ImageStorage.content_checksum == storage_checksum,
ImageStorage.uploading == False)
.get())
except Image.DoesNotExist:
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()
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)
method = ExternalNotificationMethod.get(ExternalNotificationMethod.name == method_name)
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):

View file

@ -209,33 +209,18 @@ def get_storage_by_uuid(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):
""" Returns the path in the storage engine to the layer data referenced by the storage row. """
store = config.store
if not storage_record.cas_path:
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
and checksum. """
return (ImageStorage
.select()
.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))
def _available_jobs(self, now, name_match_query):
return (QueueItem
.select()
.where(QueueItem.queue_name ** name_match_query, QueueItem.available_after <= now,
return self._available_jobs_where(QueueItem.select(), now, name_match_query)
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.retries_remaining > 0))
QueueItem.retries_remaining > 0)
def _available_jobs_not_running(self, now, name_match_query, running_query):
return (self
@ -145,25 +146,30 @@ class WorkQueue(object):
item = None
try:
db_item_candidate = avail.order_by(QueueItem.id).get()
with self._transaction_factory(db):
still_available_query = (db_for_update(self
._available_jobs(now, name_match_query)
.where(QueueItem.id == db_item_candidate.id)))
db_item = still_available_query.get()
db_item.available = False
db_item.processing_expires = now + timedelta(seconds=processing_time)
db_item.retries_remaining -= 1
db_item.save()
# The previous solution to this used a select for update in a
# transaction to prevent multiple instances from processing the
# same queue item. This suffered performance problems. This solution
# instead has instances attempt to update the potential queue item to be
# unavailable. However, since their update clause is restricted to items
# 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
# rows know that another instance is already handling that item.
db_item = avail.order_by(QueueItem.id).get()
changed_query = (QueueItem.update(
available=False,
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({
'id': db_item.id,
'body': db_item.body,
'retries_remaining': db_item.retries_remaining
'retries_remaining': db_item.retries_remaining - 1,
})
self._currently_processing = True
except QueueItem.DoesNotExist:
self._currently_processing = False

View file

@ -75,6 +75,14 @@ def simple_checksum_handler(json_data):
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):
data = json_data + '\n'
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)
class DownstreamIssue(ApiException):
def __init__(self, payload=None):
ApiException.__init__(self, None, 520, 'Downstream Issue', payload)
@api_bp.app_errorhandler(ApiException)
@crossdomain(origin='*', headers=['Authorization', 'Content-Type'])
def handle_api_error(error):
@ -105,14 +110,6 @@ def handle_api_error(error):
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 wrapper(api_resource):
if not api_resource:
@ -426,4 +423,5 @@ import endpoints.api.tag
import endpoints.api.team
import endpoints.api.trigger
import endpoints.api.user
import endpoints.api.secscan

View file

@ -241,10 +241,10 @@ def swagger_route_data(include_internal=False, compact=False):
],
'info': {
'version': 'v1',
'title': 'Quay.io Frontend',
'title': 'Quay Frontend',
'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 '
'at <a href="https://quay.io">Quay.io</a>.'),
'with Quay repositories, users, and organizations. You can find out more '
'at <a href="https://quay.io">Quay</a>.'),
'termsOfService': 'https://quay.io/tos',
'contact': {
'email': 'support@quay.io'

View file

@ -57,6 +57,10 @@ class RepositoryNotificationList(RepositoryParamResource):
'type': 'object',
'description': 'JSON config information for the specific method of notification'
},
'eventConfig': {
'type': 'object',
'description': 'JSON config information for the specific event of notification',
},
'title': {
'type': 'string',
'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'],
parsed['method'], parsed['config'],
parsed['eventConfig'],
parsed.get('title', None))
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
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,
internal_only, require_scope, show_if, parse_args,
query_param, abort, require_fresh_login, path_param, verify_not_prod)
@ -131,6 +131,7 @@ class SuperUserLogs(ApiResource):
def org_view(org):
return {
'name': org.username,
'email': org.email,
'avatar': avatar.get_data_for_org(org),
}
@ -236,6 +237,10 @@ class SuperUserList(ApiResource):
@require_scope(scopes.SUPERUSER)
def post(self):
""" Creates a new user. """
# Ensure that we are using database auth.
if app.config['AUTHENTICATION_TYPE'] != 'Database':
abort(400)
user_information = request.get_json()
if SuperUserPermission().can():
username = user_information['username']
@ -274,6 +279,10 @@ class SuperUserSendRecoveryEmail(ApiResource):
@nickname('sendInstallUserRecoveryEmail')
@require_scope(scopes.SUPERUSER)
def post(self, username):
# Ensure that we are using database auth.
if app.config['AUTHENTICATION_TYPE'] != 'Database':
abort(400)
if SuperUserPermission().can():
user = model.user.get_nonrobot_user(username)
if not user:
@ -370,9 +379,17 @@ class SuperUserManagement(ApiResource):
user_data = request.get_json()
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'])
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)
if 'enabled' in user_data:
@ -380,6 +397,18 @@ class SuperUserManagement(ApiResource):
user.enabled = bool(user_data['enabled'])
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'))
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,
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 data import model
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. """
@require_repo_read
@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. """
try:
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)
image_map = {}
image_map[str(tag_image.id)] = tag_image
for image in parent_images:
image_map[str(image.id)] = image
image_map_all = dict(image_map)
parents = list(parent_images)
parents.reverse()
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 {
'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 util.useremails import CannotSendEmailException
from util.config.provider.baseprovider import CannotWriteConfigException
from flask.ext.restful.utils.cors import crossdomain
from data import model
logger = logging.getLogger(__name__)
@ -25,4 +26,13 @@ def handle_configexception(ex):
'Please make sure the mounted volume is not read-only and restart ' +
'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
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):
@classmethod
def event_name(cls):

View file

@ -152,7 +152,7 @@ def get_image_layer(namespace, repository, image_id, headers):
image_id=image_id)
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:
path = store.v1_image_layer_path(repo_image.storage.uuid)
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)
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.
with database.CloseForLongOperation(app.config):
try:
@ -258,13 +262,14 @@ def put_image_layer(namespace, repository, image_id):
except (IOError, checksums.TarError) as 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.
# Not removing the mark though, image is not downloadable yet.
session['checksum'] = csums
session['content_checksum'] = 'sha256:{0}'.format(ch.hexdigest())
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
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',
issue='image-write-error', image_id=image_id)
logger.debug('Storing image checksum')
err = store_checksum(repo_image.storage, checksum)
logger.debug('Storing image and content checksums')
content_checksum = session.get('content_checksum', None)
err = store_checksum(repo_image, checksum, content_checksum)
if err:
abort(400, err)
@ -398,14 +404,17 @@ def get_image_ancestry(namespace, repository, image_id, headers):
return response
def store_checksum(image_storage, checksum):
def store_checksum(image_with_storage, checksum, content_checksum):
checksum_parts = checksum.split(':')
if len(checksum_parts) != 2:
return 'Invalid checksum format'
# We store the checksum
image_storage.checksum = checksum
image_storage.save()
image_with_storage.storage.content_checksum = content_checksum
image_with_storage.storage.save()
image_with_storage.v1_checksum = checksum
image_with_storage.save()
@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)
# 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:
logger.info('Processing diffs for newly stored v1 image at %s', layer_path)
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.
checksums = [str(mdata.digest) for mdata in manifest.layers]
storage_query = model.storage.lookup_repo_storages_by_checksum(repo, checksums)
storage_map = {storage.checksum: storage for storage in storage_query}
storage_query = model.storage.lookup_repo_storages_by_content_checksum(repo, checksums)
storage_map = {storage.content_checksum: storage for storage in storage_query}
# Synthesize the V1 metadata for each layer.
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)
# 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:
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.
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.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():
for current_image in image_list:
yield current_image
@ -42,7 +46,8 @@ def _open_stream(formatter, namespace, repository, tag, synthetic_image_id, imag
current_image_path)
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
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():
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'])
@no_cache
@ -393,7 +399,6 @@ def confirm_recovery():
@web.route('/repository/<path:repository>/status', methods=['GET'])
@parse_repository_name
@no_cache
@anon_protect
def build_status_badge(namespace, repository):
token = request.args.get('token', None)
@ -417,8 +422,13 @@ def build_status_badge(namespace, repository):
else:
status_name = 'none'
if request.headers.get('If-None-Match') == status_name:
return Response(status=304)
response = make_response(STATUS_TAGS[status_name])
response.content_type = 'image/svg+xml'
response.headers['Cache-Control'] = 'no-cache'
response.headers['ETag'] = status_name
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. """
info = tarfile.TarInfo(name=name)
info.type = tarfile.DIRTYPE
# allow the directory to be readable by non-root users
info.mode = 0755
return info.tobuf()

View file

@ -6,6 +6,7 @@ import calendar
import os
import argparse
from sys import maxsize
from datetime import datetime, timedelta
from peewee import (SqliteDatabase, create_model_tables, drop_model_tables, savepoint_sqlite,
savepoint)
@ -84,7 +85,7 @@ def __create_subtree(repo, structure, creator_username, parent, tag_map):
new_image_locations = new_image.storage.locations
new_image.storage.uuid = __gen_image_uuid(repo, image_num)
new_image.storage.uploading = False
new_image.storage.checksum = checksum
new_image.storage.content_checksum = checksum
new_image.storage.save()
# 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)
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)
command_list = SAMPLE_CMDS[image_num % len(SAMPLE_CMDS)]
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_success')
ExternalNotificationEvent.create(name='build_failure')
ExternalNotificationEvent.create(name='vulnerability_found')
ExternalNotificationMethod.create(name='quay_notification')
ExternalNotificationMethod.create(name='email')
@ -325,6 +331,7 @@ def initialize_database():
NotificationKind.create(name='build_start')
NotificationKind.create(name='build_success')
NotificationKind.create(name='build_failure')
NotificationKind.create(name='vulnerability_found')
NotificationKind.create(name='password_required')
NotificationKind.create(name='over_private_usage')

View file

@ -5,3 +5,4 @@ export TROLLIUSDEBUG=1
python -m unittest discover -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');
});
casper.thenOpen(rootUrl + 'repository/devtable/' + repo + '?tab=tags', function() {
this.wait(1000);
});
casper.then(function() {
this.log('Generating repository image diff screenshot.');
});
@ -179,7 +183,7 @@ casper.then(function() {
});
casper.thenOpen(rootUrl + 'repository/' + buildrepo + '?tab=builds', function() {
this.wait(1000);
this.wait(10000);
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%;
}
.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

@ -40,4 +40,27 @@
width: 30px;
margin-right: 10px;
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 {
margin-left: 10px;
margin-top: 1px;
height: 36px;
width: 100px;
background-repeat: no-repeat;
@ -91,6 +93,10 @@
display: inline-block;
}
#quay-logo.hosted-logo {
width: 150px;
}
#padding-container {
padding: 20px;
padding-top: 20px;
@ -565,6 +571,7 @@ i.toggle-icon:hover {
.notification-error {
background: red;
color: white;
}
.user-notification.notification-animated {
@ -1142,221 +1149,6 @@ i.toggle-icon:hover {
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 {
text-align: center;
}
@ -1409,68 +1201,13 @@ form input.ng-valid.ng-dirty,
border-top: 4px solid #ccc;
}
.landing .popover-content {
color: black;
.form-signup input {
margin: 12px;
margin-left: 0px;
}
.landing-page .shoutout > i {
font-size: 50px;
display: inline-block;
width: 120px;
height: 120px;
background: #eee;
.signin-buttons {
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 {
@ -3116,34 +2853,6 @@ p.editable:hover i {
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 {
margin: 20px;
border: 1px solid #eee;
@ -3162,7 +2871,6 @@ p.editable:hover i {
padding: 0px;
}
.plans-table ul li {
padding: 4px;
margin: 0px;

View file

@ -1,12 +1,12 @@
<!-- Modal message dialog -->
<div class="modal fade" id="createNotificationModal">
<div class="co-dialog modal fade" id="createNotificationModal">
<div class="modal-dialog">
<div class="modal-content">
<form id="createForm" name="createForm" ng-submit="createNotification()">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true" ng-disabled="creating">&times;</button>
<h4 class="modal-title">
Create Repository Notification
<i class="fa fa-bell"></i> Create Repository Notification
</h4>
</div>
<div class="modal-body">
@ -29,109 +29,127 @@
</div>
<!-- Create View -->
<table style="width: 100%" ng-show="status == ''">
<tr>
<td style="width: 120px">Notification title:</td>
<td style="padding-right: 21px;">
<input class="form-control" type="text" placeholder="(Optional Title)" ng-model="currentTitle"
style="margin: 10px;">
</td>
</tr>
<div class="options-table-wrapper">
<table class="options-table" ng-show="status == ''">
<tr>
<td class="name">Notification title:</td>
<td>
<input class="form-control" type="text" placeholder="(Optional Title)" ng-model="currentTitle">
</td>
</tr>
</table>
<tr>
<td style="width: 120px">When this occurs:</td>
<td>
<div class="dropdown-select" placeholder="'(Notification Event)'" selected-item="currentEvent.title"
handle-item-selected="handleEventSelected(datum)" clear-value="clearCounter">
<!-- Icons -->
<i class="dropdown-select-icon fa fa-lg" ng-class="currentEvent.icon"></i>
<table class="options-table" ng-show="status == ''">
<tr>
<td class="name">When this occurs:</td>
<td>
<div class="dropdown-select" placeholder="'(Notification Event)'" selected-item="currentEvent.title"
handle-item-selected="handleEventSelected(datum)" clear-value="clearCounter">
<!-- Icons -->
<i class="dropdown-select-icon fa fa-lg" ng-class="currentEvent.icon"></i>
<!-- Dropdown menu -->
<ul class="dropdown-select-menu pull-right" role="menu">
<li ng-repeat="event in events">
<a href="javascript:void(0)" ng-click="setEvent(event)">
<i class="fa fa-lg" ng-class="event.icon"></i> {{ event.title }}
</a>
</li>
</ul>
</div>
</td>
</tr>
<!-- Dropdown menu -->
<ul class="dropdown-select-menu pull-right" role="menu">
<li ng-repeat="event in events">
<a href="javascript:void(0)" ng-click="setEvent(event)">
<i class="fa fa-lg" ng-class="event.icon"></i> {{ event.title }}
</a>
</li>
</ul>
</div>
</td>
</tr>
<tr>
<td>Then issue a:</td>
<td>
<div class="dropdown-select" placeholder="'(Notification Action)'" selected-item="currentMethod.title"
handle-item-selected="handleMethodSelected(datum)" clear-value="clearCounter">
<!-- Icons -->
<i class="dropdown-select-icon fa fa-lg" ng-class="currentMethod.icon"></i>
<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>
<!-- Dropdown menu -->
<ul class="dropdown-select-menu pull-right" role="menu">
<li ng-repeat="method in methods">
<a href="javascript:void(0)" ng-click="setMethod(method)">
<i class="fa fa-lg" ng-class="method.icon"></i> {{ method.title }}
</a>
</li>
</ul>
</div>
</td>
</tr>
<tr ng-if="currentMethod.fields.length"><td colspan="2"><hr></td></tr>
<tr ng-repeat="field in currentMethod.fields">
<td valign="top" style="padding-top: 10px">{{ field.title }}:</td>
<td>
<div ng-switch on="field.type">
<span ng-switch-when="email">
<input type="email" class="form-control" ng-model="currentConfig[field.name]" required>
</span>
<input type="url" class="form-control" ng-model="currentConfig[field.name]" ng-switch-when="url" required>
<input type="text" class="form-control" ng-model="currentConfig[field.name]" ng-switch-when="string" required>
<!-- TODO(jschorr): unify the ability to create an input box with all the usual features -->
<div ng-switch-when="regex">
<input type="text" class="form-control" ng-model="currentConfig[field.name]"
ng-pattern="getPattern(field)"
placeholder="{{ field.placeholder }}"
ng-name="field.name"
id="{{ field.name }}"
required>
<div class="alert alert-warning" style="margin-top: 10px; margin-bottom: 10px"
ng-if="field.regex_fail_message && hasRegexMismatch(createForm.$error, field.name)">
<span ng-bind-html="field.regex_fail_message"></span>
<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>
<div class="entity-search" namespace="repository.namespace"
placeholder="''"
current-entity="currentConfig[field.name]"
ng-model="currentConfig[field.name]"
allowed-entities="['user', 'team', 'org']"
ng-switch-when="entity"></div>
</td>
</tr>
</table>
<div ng-if="getHelpUrl(field, currentConfig)"
style="margin-top: 10px; margin-bottom: 10px">
See: <a href="{{ getHelpUrl(field, currentConfig) }}" target="_blank">{{ getHelpUrl(field, currentConfig) }}</a>
<table class="options-table" ng-show="status == ''">
<tr>
<td class="name">Then issue a:</td>
<td>
<div class="dropdown-select" placeholder="'(Notification Action)'" selected-item="currentMethod.title"
handle-item-selected="handleMethodSelected(datum)" clear-value="clearCounter">
<!-- Icons -->
<i class="dropdown-select-icon fa fa-lg" ng-class="currentMethod.icon"></i>
<!-- Dropdown menu -->
<ul class="dropdown-select-menu pull-right" role="menu">
<li ng-repeat="method in methods">
<a href="javascript:void(0)" ng-click="setMethod(method)">
<i class="fa fa-lg" ng-class="method.icon"></i> {{ method.title }}
</a>
</li>
</ul>
</div>
</div>
</td>
</tr>
</td>
</tr>
<tr ng-if="currentMethod.id == 'webhook'">
<td colspan="2">
<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.
<br><br>
The contents for each event can be found in the user guide:
<a href="http://docs.quay.io/guides/notifications.html#webhook{{ currentEvent.id ? '_' + currentEvent.id : '' }}"
target="_blank">
http://docs.quay.io/guides/notifications.html
</a>
</div>
</td>
</tr>
</table>
<tr ng-repeat="field in currentMethod.fields">
<td valign="top" class="name">{{ field.title }}:</td>
<td>
<div ng-switch on="field.type">
<span ng-switch-when="email">
<input type="email" class="form-control" ng-model="currentConfig[field.name]" required>
</span>
<input type="url" class="form-control" ng-model="currentConfig[field.name]" ng-switch-when="url" required>
<input type="text" class="form-control" ng-model="currentConfig[field.name]" ng-switch-when="string" required>
<!-- TODO(jschorr): unify the ability to create an input box with all the usual features -->
<div ng-switch-when="regex">
<input type="text" class="form-control" ng-model="currentConfig[field.name]"
ng-pattern="getPattern(field)"
placeholder="{{ field.placeholder }}"
ng-name="field.name"
id="{{ field.name }}"
required>
<div class="alert alert-warning" style="margin-top: 10px; margin-bottom: 10px"
ng-if="field.regex_fail_message && hasRegexMismatch(createForm.$error, field.name)">
<span ng-bind-html="field.regex_fail_message"></span>
</div>
</div>
<div class="entity-search" namespace="repository.namespace"
placeholder="''"
current-entity="currentConfig[field.name]"
ng-model="currentConfig[field.name]"
allowed-entities="['user', 'team', 'org']"
ng-switch-when="entity"></div>
<div ng-if="getHelpUrl(field, currentConfig)"
style="margin-top: 10px; margin-bottom: 10px">
See: <a href="{{ getHelpUrl(field, currentConfig) }}" target="_blank">{{ getHelpUrl(field, currentConfig) }}</a>
</div>
<div class="co-alert co-alert-info" ng-if="currentMethod.id == 'webhook'"
style="margin-top: 6px; margin-bottom: 0px">
JSON metadata representing the event will be <b>POST</b>ed to the URL.
<br><br>
The contents for each event can be found in the user guide:
<a href="http://docs.quay.io/guides/notifications.html#webhook{{ currentEvent.id ? '_' + currentEvent.id : '' }}"
target="_blank">
http://docs.quay.io/guides/notifications.html
</a>
</div>
</div>
</td>
</tr>
</table>
</div>
</div>
<!-- Auth e-mail button bar -->

View file

@ -39,7 +39,10 @@
<table class="trigger-list">
<tr ng-repeat="trigger in triggers">
<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>
</table>
</div>

View file

@ -7,7 +7,8 @@
&equiv;
</button>
<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>
<span class="user-tools visible-xs" style="float: right;">
<i class="fa fa-search fa-lg user-tool" ng-click="toggleSearch()"

View file

@ -20,14 +20,14 @@
<div class="image-section">
<i class="fa fa-tag section-icon" data-title="Current Tags" bs-tooltip></i>
<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})">
{{ tag }}
</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"
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"
bs-tooltip="tooltip.title" data-title="Manage Tags"
data-container="body">
@ -35,7 +35,7 @@
</a>
<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})">
<i class="fa fa-tag"></i>{{ tag }}
</a>

View file

@ -23,6 +23,23 @@
Free trial until <strong>{{ parseDate(subscription.trialEnd) | date }}</strong>
</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 -->
<div class="usage-chart" total="subscribedPlan.privateRepos || 0"
current="subscription.usedPrivateRepos || 0"

View file

@ -1,6 +1,6 @@
<div class="repo-panel-changes-element">
<div class="resource-view" resource="imagesResource"
error-message="'Could not load repository images'">
<div class="cor-loader" ng-show="loading"></div>
<div ng-show="!loading">
<h3 class="tab-header">
Visualize Tags:
<span class="multiselect-dropdown" items="tagNames" selected-items="selectedTagsSlice"
@ -19,49 +19,46 @@
<!-- Tags Selected -->
<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 -->
<div class="col-md-8">
<div class="panel panel-default">
<!-- Image history tree -->
<div id="image-history-container" onresize="tree.notifyResized()"></div>
</div>
</div>
<!-- Tree View container -->
<div class="col-md-8">
<div class="panel panel-default">
<!-- Image history tree -->
<div id="image-history-container" onresize="tree.notifyResized()"></div>
</div>
<!-- Side Panel -->
<div class="col-md-4">
<div class="side-panel-title" ng-if="currentTag">
<i class="fa fa-tag"></i>{{ currentTag }}
</div>
<div class="side-panel-title" ng-if="currentImage">
<i class="fa fa-archive"></i>{{ currentImage.substr(0, 12) }}
</div>
<!-- Side Panel -->
<div class="col-md-4">
<div class="side-panel-title" ng-if="currentTag">
<i class="fa fa-tag"></i>{{ currentTag }}
</div>
<div class="side-panel-title" ng-if="currentImage">
<i class="fa fa-archive"></i>{{ currentImage.substr(0, 12) }}
<div class="side-panel">
<!-- Tag Info -->
<div class="tag-info-sidebar"
tracker="tracker"
tag="currentTag"
image-selected="setImage(image)"
delete-tag-requested="tagActionHandler.askDeleteTag(tag)"
ng-if="currentTag">
</div>
<div class="side-panel">
<!-- Tag Info -->
<div class="tag-info-sidebar"
tracker="tracker"
tag="currentTag"
image-selected="setImage(image)"
delete-tag-requested="tagActionHandler.askDeleteTag(tag)"
ng-if="currentTag">
</div>
<!-- Image Info -->
<div class="image-info-sidebar"
tracker="tracker"
image="currentImage"
tag-selected="setTag(tag)"
add-tag-requested="tagActionHandler.askAddTag(image)"
ng-if="currentImage">
</div>
<!-- Image Info -->
<div class="image-info-sidebar"
tracker="tracker"
image="currentImage"
image-loader="imageLoader"
tag-selected="setTag(tag)"
add-tag-requested="tagActionHandler.askAddTag(image)"
ng-if="currentImage">
</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>

View file

@ -87,7 +87,7 @@
<!-- Status Image -->
<a ng-href="/repository/{{ repository.namespace }}/{{ repository.name }}">
<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>
<!-- Embed formats -->

View file

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

View file

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

View file

@ -49,11 +49,11 @@
<div class="tag-specific-images-view"
tag="tagToCreate"
repository="repo"
images="imagesInternal"
repository="repository"
image-cutoff="toTagImage"
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:
</div>
</div>
@ -100,7 +100,7 @@
<span class="label label-default tag">{{ deleteTagInfo.tag }}</span>?
<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:
</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 class="image-listings">
<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="tour-section row tour-header">
<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="col-lg-6 enterprise-plan col-lg-offset-3">
<div class="plan-combine">
<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.
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.
</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 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>

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