Merge branch 'master' into gitlab
This commit is contained in:
commit
e3aededcbc
70 changed files with 1000 additions and 265 deletions
|
@ -104,7 +104,9 @@ class BuildJob(object):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Build an in-memory tree of the full heirarchy of images in the repository.
|
# Build an in-memory tree of the full heirarchy of images in the repository.
|
||||||
all_images = model.get_repository_images(repo_namespace, repo_name)
|
all_images = model.get_repository_images_without_placements(repo_build.repository,
|
||||||
|
with_ancestor=base_image)
|
||||||
|
|
||||||
all_tags = model.list_repository_tags(repo_namespace, repo_name)
|
all_tags = model.list_repository_tags(repo_namespace, repo_name)
|
||||||
tree = ImageTree(all_images, all_tags, base_filter=base_image.id)
|
tree = ImageTree(all_images, all_tags, base_filter=base_image.id)
|
||||||
|
|
||||||
|
|
|
@ -561,6 +561,14 @@ class LogEntry(BaseModel):
|
||||||
ip = CharField(null=True)
|
ip = CharField(null=True)
|
||||||
metadata_json = TextField(default='{}')
|
metadata_json = TextField(default='{}')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
database = db
|
||||||
|
read_slaves = (read_slave,)
|
||||||
|
indexes = (
|
||||||
|
# create an index on repository and date
|
||||||
|
(('repository', 'datetime'), False),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class RepositoryActionCount(BaseModel):
|
class RepositoryActionCount(BaseModel):
|
||||||
repository = ForeignKeyField(Repository, index=True)
|
repository = ForeignKeyField(Repository, index=True)
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
"""Add bitbucket build trigger type
|
||||||
|
|
||||||
|
Revision ID: 313179799c8b
|
||||||
|
Revises: 37c47a7af956
|
||||||
|
Create Date: 2015-04-30 15:52:33.388825
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '313179799c8b'
|
||||||
|
down_revision = '37c47a7af956'
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade(tables):
|
||||||
|
op.bulk_insert(tables.buildtriggerservice, [{'id': 3, 'name': 'bitbucket'}])
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade(tables):
|
||||||
|
op.execute(
|
||||||
|
tables.buildtriggerservice.delete()
|
||||||
|
.where(tables.buildtriggerservice.c.name == op.inline_literal('bitbucket'))
|
||||||
|
)
|
|
@ -0,0 +1,25 @@
|
||||||
|
"""add custom-git trigger type to database
|
||||||
|
|
||||||
|
Revision ID: 37c47a7af956
|
||||||
|
Revises: 3fee6f979c2a
|
||||||
|
Create Date: 2015-04-24 14:50:26.275516
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '37c47a7af956'
|
||||||
|
down_revision = '3fee6f979c2a'
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade(tables):
|
||||||
|
op.bulk_insert(tables.buildtriggerservice, [{'id': 2, 'name': 'custom-git'}])
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade(tables):
|
||||||
|
op.execute(
|
||||||
|
tables.buildtriggerservice.delete()
|
||||||
|
.where(tables.buildtriggerservice.c.name == op.inline_literal('custom-git'))
|
||||||
|
)
|
|
@ -1751,6 +1751,21 @@ def get_matching_repository_images(namespace_name, repository_name, docker_image
|
||||||
|
|
||||||
return _get_repository_images_base(namespace_name, repository_name, modify_query)
|
return _get_repository_images_base(namespace_name, repository_name, modify_query)
|
||||||
|
|
||||||
|
|
||||||
|
def get_repository_images_without_placements(repository, with_ancestor=None):
|
||||||
|
query = (Image
|
||||||
|
.select(Image, ImageStorage)
|
||||||
|
.join(ImageStorage)
|
||||||
|
.where(Image.repository == repository))
|
||||||
|
|
||||||
|
if with_ancestor:
|
||||||
|
ancestors_string = '%s%s/' % (with_ancestor.ancestors, with_ancestor.id)
|
||||||
|
query = query.where((Image.ancestors ** (ancestors_string + '%')) |
|
||||||
|
(Image.id == with_ancestor.id))
|
||||||
|
|
||||||
|
return query
|
||||||
|
|
||||||
|
|
||||||
def get_repository_images(namespace_name, repository_name):
|
def get_repository_images(namespace_name, repository_name):
|
||||||
return _get_repository_images_base(namespace_name, repository_name, lambda q: q)
|
return _get_repository_images_base(namespace_name, repository_name, lambda q: q)
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,9 @@ from endpoints.building import start_build, PreparedBuild
|
||||||
from endpoints.trigger import BuildTriggerHandler
|
from endpoints.trigger import BuildTriggerHandler
|
||||||
from data import model, database
|
from data import model, database
|
||||||
from auth.auth_context import get_authenticated_user
|
from auth.auth_context import get_authenticated_user
|
||||||
from auth.permissions import ModifyRepositoryPermission, AdministerOrganizationPermission
|
from auth.permissions import (ReadRepositoryPermission, ModifyRepositoryPermission,
|
||||||
|
AdministerRepositoryPermission, AdministerOrganizationPermission)
|
||||||
|
|
||||||
from data.buildlogs import BuildStatusRetrievalError
|
from data.buildlogs import BuildStatusRetrievalError
|
||||||
from util.names import parse_robot_username
|
from util.names import parse_robot_username
|
||||||
|
|
||||||
|
@ -33,7 +35,7 @@ def get_job_config(build_obj):
|
||||||
try:
|
try:
|
||||||
return json.loads(build_obj.job_config)
|
return json.loads(build_obj.job_config)
|
||||||
except:
|
except:
|
||||||
return None
|
return {}
|
||||||
|
|
||||||
|
|
||||||
def user_view(user):
|
def user_view(user):
|
||||||
|
@ -43,11 +45,12 @@ def user_view(user):
|
||||||
'is_robot': user.robot,
|
'is_robot': user.robot,
|
||||||
}
|
}
|
||||||
|
|
||||||
def trigger_view(trigger, can_admin=False):
|
def trigger_view(trigger, can_read=False, can_admin=False):
|
||||||
if trigger and trigger.uuid:
|
if trigger and trigger.uuid:
|
||||||
build_trigger = BuildTriggerHandler.get_handler(trigger)
|
build_trigger = BuildTriggerHandler.get_handler(trigger)
|
||||||
return {
|
return {
|
||||||
'service': trigger.service.name,
|
'service': trigger.service.name,
|
||||||
|
'build_source': build_trigger.config.get('build_source') if can_read else None,
|
||||||
'config': build_trigger.config if can_admin else {},
|
'config': build_trigger.config if can_admin else {},
|
||||||
'id': trigger.uuid,
|
'id': trigger.uuid,
|
||||||
'connected_user': trigger.connected_user.username,
|
'connected_user': trigger.connected_user.username,
|
||||||
|
@ -58,7 +61,7 @@ def trigger_view(trigger, can_admin=False):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def build_status_view(build_obj, can_write=False, can_admin=False):
|
def build_status_view(build_obj):
|
||||||
phase = build_obj.phase
|
phase = build_obj.phase
|
||||||
try:
|
try:
|
||||||
status = build_logs.get_status(build_obj.uuid)
|
status = build_logs.get_status(build_obj.uuid)
|
||||||
|
@ -74,28 +77,39 @@ def build_status_view(build_obj, can_write=False, can_admin=False):
|
||||||
if datetime.datetime.utcnow() - heartbeat > datetime.timedelta(minutes=1):
|
if datetime.datetime.utcnow() - heartbeat > datetime.timedelta(minutes=1):
|
||||||
phase = database.BUILD_PHASE.INTERNAL_ERROR
|
phase = database.BUILD_PHASE.INTERNAL_ERROR
|
||||||
|
|
||||||
# If the phase is internal error, return 'error' instead of the number if retries
|
# If the phase is internal error, return 'error' instead if the number of retries
|
||||||
# on the queue item is 0.
|
# on the queue item is 0.
|
||||||
if phase == database.BUILD_PHASE.INTERNAL_ERROR:
|
if phase == database.BUILD_PHASE.INTERNAL_ERROR:
|
||||||
retry = build_obj.queue_id and dockerfile_build_queue.has_retries_remaining(build_obj.queue_id)
|
retry = build_obj.queue_id and dockerfile_build_queue.has_retries_remaining(build_obj.queue_id)
|
||||||
if not retry:
|
if not retry:
|
||||||
phase = database.BUILD_PHASE.ERROR
|
phase = database.BUILD_PHASE.ERROR
|
||||||
|
|
||||||
logger.debug('Can write: %s job_config: %s', can_write, build_obj.job_config)
|
repo_namespace = build_obj.repository.namespace_user.username
|
||||||
|
repo_name = build_obj.repository.name
|
||||||
|
|
||||||
|
can_read = ReadRepositoryPermission(repo_namespace, repo_name).can()
|
||||||
|
can_write = ModifyRepositoryPermission(repo_namespace, repo_name).can()
|
||||||
|
can_admin = AdministerRepositoryPermission(repo_namespace, repo_name).can()
|
||||||
|
|
||||||
|
job_config = get_job_config(build_obj)
|
||||||
|
|
||||||
resp = {
|
resp = {
|
||||||
'id': build_obj.uuid,
|
'id': build_obj.uuid,
|
||||||
'phase': phase,
|
'phase': phase,
|
||||||
'started': format_date(build_obj.started),
|
'started': format_date(build_obj.started),
|
||||||
'display_name': build_obj.display_name,
|
'display_name': build_obj.display_name,
|
||||||
'status': status or {},
|
'status': status or {},
|
||||||
'job_config': get_job_config(build_obj) if can_write else None,
|
'subdirectory': job_config.get('build_subdir', ''),
|
||||||
|
'tags': job_config.get('docker_tags', []),
|
||||||
|
'manual_user': job_config.get('manual_user', None),
|
||||||
'is_writer': can_write,
|
'is_writer': can_write,
|
||||||
'trigger': trigger_view(build_obj.trigger, can_admin),
|
'trigger': trigger_view(build_obj.trigger, can_read, can_admin),
|
||||||
|
'trigger_metadata': job_config.get('trigger_metadata', None) if can_read else None,
|
||||||
'resource_key': build_obj.resource_key,
|
'resource_key': build_obj.resource_key,
|
||||||
'pull_robot': user_view(build_obj.pull_robot) if build_obj.pull_robot else None,
|
'pull_robot': user_view(build_obj.pull_robot) if build_obj.pull_robot else None,
|
||||||
'repository': {
|
'repository': {
|
||||||
'namespace': build_obj.repository.namespace_user.username,
|
'namespace': repo_namespace,
|
||||||
'name': build_obj.repository.name
|
'name': repo_name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -157,9 +171,8 @@ class RepositoryBuildList(RepositoryParamResource):
|
||||||
since = datetime.datetime.utcfromtimestamp(since)
|
since = datetime.datetime.utcfromtimestamp(since)
|
||||||
|
|
||||||
builds = model.list_repository_builds(namespace, repository, limit, since=since)
|
builds = model.list_repository_builds(namespace, repository, limit, since=since)
|
||||||
can_write = ModifyRepositoryPermission(namespace, repository).can()
|
|
||||||
return {
|
return {
|
||||||
'builds': [build_status_view(build, can_write) for build in builds]
|
'builds': [build_status_view(build) for build in builds]
|
||||||
}
|
}
|
||||||
|
|
||||||
@require_repo_write
|
@require_repo_write
|
||||||
|
@ -211,7 +224,7 @@ class RepositoryBuildList(RepositoryParamResource):
|
||||||
prepared.metadata = {}
|
prepared.metadata = {}
|
||||||
|
|
||||||
build_request = start_build(repo, prepared, pull_robot_name=pull_robot_name)
|
build_request = start_build(repo, prepared, pull_robot_name=pull_robot_name)
|
||||||
resp = build_status_view(build_request, can_write=True)
|
resp = build_status_view(build_request)
|
||||||
repo_string = '%s/%s' % (namespace, repository)
|
repo_string = '%s/%s' % (namespace, repository)
|
||||||
headers = {
|
headers = {
|
||||||
'Location': api.url_for(RepositoryBuildStatus, repository=repo_string,
|
'Location': api.url_for(RepositoryBuildStatus, repository=repo_string,
|
||||||
|
@ -236,8 +249,7 @@ class RepositoryBuildResource(RepositoryParamResource):
|
||||||
except model.InvalidRepositoryBuildException:
|
except model.InvalidRepositoryBuildException:
|
||||||
raise NotFound()
|
raise NotFound()
|
||||||
|
|
||||||
can_write = ModifyRepositoryPermission(namespace, repository).can()
|
return build_status_view(build)
|
||||||
return build_status_view(build, can_write)
|
|
||||||
|
|
||||||
@require_repo_admin
|
@require_repo_admin
|
||||||
@nickname('cancelRepoBuild')
|
@nickname('cancelRepoBuild')
|
||||||
|
@ -271,8 +283,7 @@ class RepositoryBuildStatus(RepositoryParamResource):
|
||||||
build.repository.namespace_user.username != namespace):
|
build.repository.namespace_user.username != namespace):
|
||||||
raise NotFound()
|
raise NotFound()
|
||||||
|
|
||||||
can_write = ModifyRepositoryPermission(namespace, repository).can()
|
return build_status_view(build)
|
||||||
return build_status_view(build, can_write)
|
|
||||||
|
|
||||||
|
|
||||||
@resource('/v1/repository/<repopath:repository>/build/<build_uuid>/logs')
|
@resource('/v1/repository/<repopath:repository>/build/<build_uuid>/logs')
|
||||||
|
|
|
@ -432,7 +432,7 @@ class ActivateBuildTrigger(RepositoryParamResource):
|
||||||
except TriggerStartException as tse:
|
except TriggerStartException as tse:
|
||||||
raise InvalidRequest(tse.message)
|
raise InvalidRequest(tse.message)
|
||||||
|
|
||||||
resp = build_status_view(build_request, can_write=True)
|
resp = build_status_view(build_request)
|
||||||
repo_string = '%s/%s' % (namespace, repository)
|
repo_string = '%s/%s' % (namespace, repository)
|
||||||
headers = {
|
headers = {
|
||||||
'Location': api.url_for(RepositoryBuildStatus, repository=repo_string,
|
'Location': api.url_for(RepositoryBuildStatus, repository=repo_string,
|
||||||
|
@ -456,7 +456,7 @@ class TriggerBuildList(RepositoryParamResource):
|
||||||
builds = list(model.list_trigger_builds(namespace, repository,
|
builds = list(model.list_trigger_builds(namespace, repository,
|
||||||
trigger_uuid, limit))
|
trigger_uuid, limit))
|
||||||
return {
|
return {
|
||||||
'builds': [build_status_view(build, can_write=True) for build in builds]
|
'builds': [build_status_view(build) for build in builds]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -444,19 +444,19 @@ class ConvertToOrganization(ApiResource):
|
||||||
user = get_authenticated_user()
|
user = get_authenticated_user()
|
||||||
convert_data = request.get_json()
|
convert_data = request.get_json()
|
||||||
|
|
||||||
# Ensure that the new admin user is the not user being converted.
|
|
||||||
admin_username = convert_data['adminUser']
|
|
||||||
if admin_username == user.username:
|
|
||||||
raise request_error(reason='invaliduser',
|
|
||||||
message='The admin user is not valid')
|
|
||||||
|
|
||||||
# Ensure that the sign in credentials work.
|
# Ensure that the sign in credentials work.
|
||||||
|
admin_username = convert_data['adminUser']
|
||||||
admin_password = convert_data['adminPassword']
|
admin_password = convert_data['adminPassword']
|
||||||
(admin_user, error_message) = authentication.verify_user(admin_username, admin_password)
|
(admin_user, error_message) = authentication.verify_user(admin_username, admin_password)
|
||||||
if not admin_user:
|
if not admin_user:
|
||||||
raise request_error(reason='invaliduser',
|
raise request_error(reason='invaliduser',
|
||||||
message='The admin user credentials are not valid')
|
message='The admin user credentials are not valid')
|
||||||
|
|
||||||
|
# Ensure that the new admin user is the not user being converted.
|
||||||
|
if admin_user.id == user.id:
|
||||||
|
raise request_error(reason='invaliduser',
|
||||||
|
message='The admin user is not valid')
|
||||||
|
|
||||||
# Subscribe the organization to the new plan.
|
# Subscribe the organization to the new plan.
|
||||||
if features.BILLING:
|
if features.BILLING:
|
||||||
plan = convert_data.get('plan', 'free')
|
plan = convert_data.get('plan', 'free')
|
||||||
|
|
|
@ -126,7 +126,7 @@ def github_oauth_callback():
|
||||||
|
|
||||||
# Exchange the OAuth code.
|
# Exchange the OAuth code.
|
||||||
code = request.args.get('code')
|
code = request.args.get('code')
|
||||||
token = google_login.exchange_code_for_token(app.config, client, code)
|
token = github_login.exchange_code_for_token(app.config, client, code)
|
||||||
|
|
||||||
# Retrieve the user's information.
|
# Retrieve the user's information.
|
||||||
user_data = get_user(github_login, token)
|
user_data = get_user(github_login, token)
|
||||||
|
@ -214,7 +214,7 @@ def google_oauth_attach():
|
||||||
@require_session_login
|
@require_session_login
|
||||||
def github_oauth_attach():
|
def github_oauth_attach():
|
||||||
code = request.args.get('code')
|
code = request.args.get('code')
|
||||||
token = google_login.exchange_code_for_token(app.config, client, code)
|
token = github_login.exchange_code_for_token(app.config, client, code)
|
||||||
user_data = get_user(github_login, token)
|
user_data = get_user(github_login, token)
|
||||||
if not user_data:
|
if not user_data:
|
||||||
return render_ologin_error('GitHub')
|
return render_ologin_error('GitHub')
|
||||||
|
|
|
@ -140,7 +140,7 @@ def _repo_verb_signature(namespace, repository, tag, verb, checker=None, **kwarg
|
||||||
# Lookup the derived image storage for the verb.
|
# Lookup the derived image storage for the verb.
|
||||||
derived = model.find_derived_storage(repo_image.storage, verb)
|
derived = model.find_derived_storage(repo_image.storage, verb)
|
||||||
if derived is None or derived.uploading:
|
if derived is None or derived.uploading:
|
||||||
abort(404)
|
return make_response('', 202)
|
||||||
|
|
||||||
# Check if we have a valid signer configured.
|
# Check if we have a valid signer configured.
|
||||||
if not signer.name:
|
if not signer.name:
|
||||||
|
|
|
@ -28,6 +28,7 @@ from util.systemlogs import build_logs_archive
|
||||||
from auth import scopes
|
from auth import scopes
|
||||||
|
|
||||||
import features
|
import features
|
||||||
|
import json
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -432,16 +433,16 @@ def request_authorization_code():
|
||||||
|
|
||||||
# Load the application information.
|
# Load the application information.
|
||||||
oauth_app = provider.get_application_for_client_id(client_id)
|
oauth_app = provider.get_application_for_client_id(client_id)
|
||||||
app_email = oauth_app.email or organization.email
|
app_email = oauth_app.avatar_email or oauth_app.organization.email
|
||||||
|
|
||||||
oauth_app_view = {
|
oauth_app_view = {
|
||||||
'name': oauth_app.name,
|
'name': oauth_app.name,
|
||||||
'description': oauth_app.description,
|
'description': oauth_app.description,
|
||||||
'url': oauth_app.application_uri,
|
'url': oauth_app.application_uri,
|
||||||
'avatar': avatar.get_data(oauth_app.name, app_email, 'app'),
|
'avatar': json.dumps(avatar.get_data(oauth_app.name, app_email, 'app')),
|
||||||
'organization': {
|
'organization': {
|
||||||
'name': oauth_app.organization.username,
|
'name': oauth_app.organization.username,
|
||||||
'avatar': avatar.get_data_for_org(oauth_app.organization)
|
'avatar': json.dumps(avatar.get_data_for_org(oauth_app.organization))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -562,6 +563,9 @@ def redirect_to_repository(namespace, reponame, tag):
|
||||||
permission = ReadRepositoryPermission(namespace, reponame)
|
permission = ReadRepositoryPermission(namespace, reponame)
|
||||||
is_public = model.repository_is_public(namespace, reponame)
|
is_public = model.repository_is_public(namespace, reponame)
|
||||||
|
|
||||||
|
if request.args.get('ac-discovery', 0) == 1:
|
||||||
|
return index('')
|
||||||
|
|
||||||
if permission.can() or is_public:
|
if permission.can() or is_public:
|
||||||
repository_name = '/'.join([namespace, reponame])
|
repository_name = '/'.join([namespace, reponame])
|
||||||
return redirect(url_for('web.repository', path=repository_name, tag=tag))
|
return redirect(url_for('web.repository', path=repository_name, tag=tag))
|
||||||
|
|
|
@ -18,9 +18,9 @@ EXTERNAL_JS = [
|
||||||
]
|
]
|
||||||
|
|
||||||
EXTERNAL_CSS = [
|
EXTERNAL_CSS = [
|
||||||
'netdna.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.css',
|
'netdna.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.css',
|
||||||
'netdna.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css',
|
'netdna.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css',
|
||||||
'fonts.googleapis.com/css?family=Source+Sans+Pro:400,700',
|
'fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,700',
|
||||||
's3.amazonaws.com/cdn.core-os.net/icons/core-icons.css'
|
's3.amazonaws.com/cdn.core-os.net/icons/core-icons.css'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -53,11 +53,13 @@ raven==5.1.1
|
||||||
redis==2.10.3
|
redis==2.10.3
|
||||||
reportlab==2.7
|
reportlab==2.7
|
||||||
requests==2.5.1
|
requests==2.5.1
|
||||||
|
requests-oauthlib==0.4.2
|
||||||
six==1.9.0
|
six==1.9.0
|
||||||
stringscore==0.1.0
|
stringscore==0.1.0
|
||||||
stripe==1.20.1
|
stripe==1.20.1
|
||||||
trollius==1.0.4
|
trollius==1.0.4
|
||||||
tzlocal==1.1.2
|
tzlocal==1.1.2
|
||||||
|
urllib3==1.10.2
|
||||||
waitress==0.8.9
|
waitress==0.8.9
|
||||||
websocket-client==0.23.0
|
websocket-client==0.23.0
|
||||||
wsgiref==0.1.2
|
wsgiref==0.1.2
|
||||||
|
|
|
@ -1,3 +1,10 @@
|
||||||
|
a:active {
|
||||||
|
outline: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:focus {
|
||||||
|
outline: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
.co-options-menu .fa-gear {
|
.co-options-menu .fa-gear {
|
||||||
color: #999;
|
color: #999;
|
||||||
|
@ -879,6 +886,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.cor-title-link {
|
.cor-title-link {
|
||||||
|
font-weight: 300;
|
||||||
line-height: 30px;
|
line-height: 30px;
|
||||||
margin-top: 22px;
|
margin-top: 22px;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
|
|
|
@ -26,3 +26,9 @@
|
||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.repo-panel-changes .multiselect-dropdown {
|
||||||
|
display: inline-block;
|
||||||
|
margin-left: 10px;
|
||||||
|
min-width: 200px;
|
||||||
|
}
|
|
@ -68,3 +68,7 @@
|
||||||
.repo-panel-info-element .builds-list {
|
.repo-panel-info-element .builds-list {
|
||||||
min-height: 200px;
|
min-height: 200px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.repo-panel-info-element .copy-box {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
|
@ -48,7 +48,7 @@ nav.navbar-default .navbar-nav>li>a.active {
|
||||||
right: 0px;
|
right: 0px;
|
||||||
top: -50px;
|
top: -50px;
|
||||||
z-index: 4;
|
z-index: 4;
|
||||||
height: 83px;
|
height: 56px;
|
||||||
transition: top 0.3s cubic-bezier(.23,.88,.72,.98);
|
transition: top 0.3s cubic-bezier(.23,.88,.72,.98);
|
||||||
background: white;
|
background: white;
|
||||||
box-shadow: 0px 1px 16px #444;
|
box-shadow: 0px 1px 16px #444;
|
||||||
|
@ -71,7 +71,7 @@ nav.navbar-default .navbar-nav>li>a.active {
|
||||||
color: #ccc;
|
color: #ccc;
|
||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 34px;
|
top: 20px;
|
||||||
left: 14px;
|
left: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,9 +84,9 @@ nav.navbar-default .navbar-nav>li>a.active {
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-bar-element .search-box .search-box-wrapper input {
|
.header-bar-element .search-box .search-box-wrapper input {
|
||||||
font-size: 28px;
|
font-size: 18px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 10px;
|
padding: 6px;
|
||||||
border: 0px;
|
border: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,7 +94,7 @@ nav.navbar-default .navbar-nav>li>a.active {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0px;
|
left: 0px;
|
||||||
right: 0px;
|
right: 0px;
|
||||||
top: -130px;
|
top: -106px;
|
||||||
z-index: 3;
|
z-index: 3;
|
||||||
transition: top 0.4s cubic-bezier(.23,.88,.72,.98), height 0.25s ease-in-out;
|
transition: top 0.4s cubic-bezier(.23,.88,.72,.98), height 0.25s ease-in-out;
|
||||||
|
|
||||||
|
@ -104,7 +104,7 @@ nav.navbar-default .navbar-nav>li>a.active {
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-bar-element .search-results.loading, .header-bar-element .search-results.results {
|
.header-bar-element .search-results.loading, .header-bar-element .search-results.results {
|
||||||
top: 130px;
|
top: 106px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-bar-element .search-results.loading {
|
.header-bar-element .search-results.loading {
|
||||||
|
@ -153,7 +153,7 @@ nav.navbar-default .navbar-nav>li>a.active {
|
||||||
margin-right: 4px;
|
margin-right: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-bar-element .search-results li .description {
|
.header-bar-element .search-results li .result-description {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
max-height: 24px;
|
max-height: 24px;
|
||||||
|
@ -161,6 +161,11 @@ nav.navbar-default .navbar-nav>li>a.active {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
color: #aaa;
|
color: #aaa;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-bar-element .search-results li .description img {
|
||||||
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-bar-element .search-results li .score:before {
|
.header-bar-element .search-results li .score:before {
|
||||||
|
|
|
@ -94,7 +94,12 @@
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.logs-view-element .side-controls .filter-input {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
.logs-view-element .side-controls {
|
.logs-view-element .side-controls {
|
||||||
|
float: none !important;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
42
static/css/directives/ui/multiselect-dropdown.css
Normal file
42
static/css/directives/ui/multiselect-dropdown.css
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
.multiselect-dropdown .dropdown,
|
||||||
|
.multiselect-dropdown .dropdown .btn-dropdown,
|
||||||
|
.multiselect-dropdown .dropdown .dropdown-menu {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.multiselect-dropdown .dropdown .btn-dropdown {
|
||||||
|
text-align: left;
|
||||||
|
position: relative;
|
||||||
|
padding-right: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.multiselect-dropdown .dropdown .btn-dropdown .caret {
|
||||||
|
position: absolute;
|
||||||
|
top: 14px;
|
||||||
|
right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.multiselect-dropdown .none {
|
||||||
|
color: #ccc;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.multiselect-dropdown .dropdown-menu {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.multiselect-dropdown .dropdown-menu .menu-item {
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.multiselect-dropdown .dropdown-menu .menu-item .co-checkable-item {
|
||||||
|
margin-right: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.multiselect-dropdown .dropdown-menu .menu-item .menu-item-template {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.multiselect-dropdown .selected-item-template {
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
35
static/css/directives/ui/namespace-selector.css
Normal file
35
static/css/directives/ui/namespace-selector.css
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
.namespace-selector-dropdown .namespace {
|
||||||
|
padding: 6px;
|
||||||
|
padding-left: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.namespace-selector-dropdown .namespace-item {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.namespace-selector-dropdown .namespace-item .fa {
|
||||||
|
position: absolute;
|
||||||
|
right: 12px;
|
||||||
|
top: 12px;
|
||||||
|
color: #aaa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.namespace-selector-dropdown .avatar {
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.namespace-selector-dropdown a.namespace {
|
||||||
|
color: black !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.namespace-selector-dropdown .namespace-item.disabled .avatar {
|
||||||
|
-webkit-filter: grayscale(1);
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.namespace-selector-dropdown .namespace-item .tooltip-inner {
|
||||||
|
min-width: 200px;
|
||||||
|
}
|
|
@ -90,9 +90,7 @@
|
||||||
.new-repo-listing .description {
|
.new-repo-listing .description {
|
||||||
font-size: 0.91em;
|
font-size: 0.91em;
|
||||||
padding-top: 13px;
|
padding-top: 13px;
|
||||||
}
|
padding-left: 11px;
|
||||||
|
|
||||||
.new-repo-listing .description {
|
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
28
static/css/directives/ui/setup-trigger-dialog.css
Normal file
28
static/css/directives/ui/setup-trigger-dialog.css
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
.setup-trigger-directive-element .dockerfile-found-content {
|
||||||
|
margin-left: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setup-trigger-directive-element .dockerfile-found-content:before {
|
||||||
|
content: "\f071";
|
||||||
|
font-family: FontAwesome;
|
||||||
|
color: rgb(255, 194, 0);
|
||||||
|
position: absolute;
|
||||||
|
top: 0px;
|
||||||
|
left: 0px;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setup-trigger-directive-element .loading {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setup-trigger-directive-element .loading .cor-loader-inline {
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setup-trigger-directive-element .dockerfile-found {
|
||||||
|
position: relative;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
padding-bottom: 16px;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
}
|
|
@ -1,8 +1,42 @@
|
||||||
.source-commit-link-element .fa {
|
.source-commit-link-element .commit-circle {
|
||||||
margin-right: 4px;
|
margin-right: 6px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
padding-top: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.source-commit-link-element .commit-circle {
|
||||||
|
display: inline-block;
|
||||||
|
border: 2px solid #999;
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
border-radius: 50%;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.source-commit-link-element .commit-circle:before,
|
||||||
|
.source-commit-link-element .commit-circle:after {
|
||||||
|
content: "";
|
||||||
|
display: inline-block;
|
||||||
|
border-top: 2px solid #999;
|
||||||
|
height: 10px;
|
||||||
|
width: 4px;
|
||||||
|
position: absolute;
|
||||||
|
top: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.source-commit-link-element .commit-circle:before {
|
||||||
|
left: -5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.source-commit-link-element .commit-circle:after {
|
||||||
|
left: 7px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
.source-commit-link-element {
|
.source-commit-link-element {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.source-commit-link-element .anchor {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
9
static/css/directives/ui/step-view-step.css
Normal file
9
static/css/directives/ui/step-view-step.css
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
.step-view-step-content .loading-message {
|
||||||
|
position: relative;
|
||||||
|
text-align: center;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-view-step-content .loading-message .cor-loader-inline {
|
||||||
|
margin-right: 6px;
|
||||||
|
}
|
|
@ -42,7 +42,7 @@
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
.trigger-setup-githost-element li.repo-listing i {
|
.trigger-setup-githost-element li.host-repo-listing i {
|
||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
margin-left: 6px;
|
margin-left: 6px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,9 +10,8 @@
|
||||||
.triggered-build-description-element .commit-who img {
|
.triggered-build-description-element .commit-who img {
|
||||||
width: 16px;
|
width: 16px;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
margin-left: 2px;
|
margin-left: 4px;
|
||||||
margin-right: 2px;
|
margin-right: 2px;
|
||||||
border-radius: 50%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.triggered-build-description-element .fa-github {
|
.triggered-build-description-element .fa-github {
|
||||||
|
|
|
@ -50,9 +50,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 767px) {
|
@media (max-width: 767px) {
|
||||||
.repository-view .repo-star {
|
.repository-view .cor-title-content {
|
||||||
position: absolute;
|
padding-top: 8px;
|
||||||
top: 16px;
|
|
||||||
left: -16px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -548,38 +548,6 @@ i.toggle-icon:hover {
|
||||||
float: right;
|
float: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
.namespace-selector-dropdown .namespace {
|
|
||||||
padding: 6px;
|
|
||||||
padding-left: 10px;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 14px;
|
|
||||||
color: black;
|
|
||||||
}
|
|
||||||
|
|
||||||
.namespace-selector-dropdown .namespace-item {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.namespace-selector-dropdown .namespace-item .fa {
|
|
||||||
position: absolute;
|
|
||||||
right: 12px;
|
|
||||||
top: 12px;
|
|
||||||
color: #aaa;
|
|
||||||
}
|
|
||||||
|
|
||||||
.namespace-selector-dropdown a.namespace {
|
|
||||||
color: black !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.namespace-selector-dropdown .namespace-item.disabled img {
|
|
||||||
-webkit-filter: grayscale(1);
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.namespace-selector-dropdown .namespace-item .tooltip-inner {
|
|
||||||
min-width: 200px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.notification-primary {
|
.notification-primary {
|
||||||
background: #428bca;
|
background: #428bca;
|
||||||
color: white;
|
color: white;
|
||||||
|
@ -3768,27 +3736,6 @@ pre.command:before {
|
||||||
border-bottom-left-radius: 0px;
|
border-bottom-left-radius: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.setup-trigger-directive-element .dockerfile-found-content {
|
|
||||||
margin-left: 32px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.setup-trigger-directive-element .dockerfile-found-content:before {
|
|
||||||
content: "\f071";
|
|
||||||
font-family: FontAwesome;
|
|
||||||
color: rgb(255, 194, 0);
|
|
||||||
position: absolute;
|
|
||||||
top: 0px;
|
|
||||||
left: 0px;
|
|
||||||
font-size: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.setup-trigger-directive-element .dockerfile-found {
|
|
||||||
position: relative;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
padding-bottom: 16px;
|
|
||||||
border-bottom: 1px solid #eee;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slideinout {
|
.slideinout {
|
||||||
-webkit-transition:0.5s all;
|
-webkit-transition:0.5s all;
|
||||||
transition:0.5s linear all;
|
transition:0.5s linear all;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<span class="anchor-element">
|
<span class="anchor-element">
|
||||||
<a ng-href="{{ href }}" ng-show="href && !isOnlyText"><span ng-transclude></span></a>
|
<a ng-href="{{ href }}" ng-show="href && !isOnlyText" target="{{ target }}"><span ng-transclude></span></a>
|
||||||
<span ng-show="!href || isOnlyText"><span ng-transclude></span></span>
|
<span ng-show="!href || isOnlyText"><span ng-transclude></span></span>
|
||||||
</span>
|
</span>
|
||||||
|
|
|
@ -30,7 +30,7 @@
|
||||||
<div class="container-logs" ng-show="container.logs.isVisible">
|
<div class="container-logs" ng-show="container.logs.isVisible">
|
||||||
<div class="log-entry" bindonce ng-repeat="entry in container.logs.visibleEntries">
|
<div class="log-entry" bindonce ng-repeat="entry in container.logs.visibleEntries">
|
||||||
<span class="id" bo-text="$index + container.index + 1" ng-if="!useTimestamps"></span>
|
<span class="id" bo-text="$index + container.index + 1" ng-if="!useTimestamps"></span>
|
||||||
<span class="id" bo-text="formatDatetime(entry.datetime)" ng-if="useTimestamps"></span>
|
<span class="id" bo-text="formatDatetime(entry.data.datetime)" ng-if="useTimestamps"></span>
|
||||||
<span class="message" bo-html="processANSI(entry.message, container)"></span>
|
<span class="message" bo-html="processANSI(entry.message, container)"></span>
|
||||||
<span class="timestamp" bo-text="formatDatetime(entry.data.datetime)" ng-if="!useTimestamps"></span>
|
<span class="timestamp" bo-text="formatDatetime(entry.data.datetime)" ng-if="!useTimestamps"></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
<span class="build-mini-status-element">
|
<span class="build-mini-status-element">
|
||||||
<span class="anchor" href="/repository/{{ build.repository.namespace }}/{{ build.repository.name }}/build/{{ build.id }}"
|
<span class="anchor"
|
||||||
is-text-only="!isAdmin">
|
href="/repository/{{ build.repository.namespace }}/{{ build.repository.name }}/build/{{ build.id }}"
|
||||||
|
is-only-text="!isAdmin">
|
||||||
<div>
|
<div>
|
||||||
<span class="build-state-icon" build="build"></span>
|
<span class="build-state-icon" build="build"></span>
|
||||||
<span class="timing">
|
<span class="timing">
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
<div class="col-lg-6 col-md-6 col-sm-6 col-xs-12">
|
<div class="col-lg-6 col-md-6 col-sm-5 col-xs-12">
|
||||||
<h2 class="co-nav-title-content co-fx-text-shadow" ng-transclude></h2>
|
<h2 class="co-nav-title-content co-fx-text-shadow" ng-transclude></h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
<div class="col-lg-3 col-md-3 hidden-sm hidden-xs" ng-transclude></div>
|
<div class="col-lg-3 col-md-3 col-sm-3 hidden-xs" ng-transclude></div>
|
||||||
|
|
31
static/directives/multiselect-dropdown.html
Normal file
31
static/directives/multiselect-dropdown.html
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
<div class="multiselect-dropdown-element">
|
||||||
|
<div class="dropdown" style="text-align: left;">
|
||||||
|
<button class="btn-dropdown btn btn-default" data-toggle="dropdown">
|
||||||
|
<span class="selected-item-template" ng-repeat="item in selectedItems" ng-transcope></span>
|
||||||
|
<span class="none" ng-if="!selectedItems.length">(No {{ itemName }}s selected)</span>
|
||||||
|
<span class="caret" ng-if="!readOnly"></span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<ul class="dropdown-menu noclose">
|
||||||
|
<li>
|
||||||
|
<input type="search" class="form-control" ng-model="filter" placeholder="{{ itemName }} filter...">
|
||||||
|
</li>
|
||||||
|
<li role="presentation" class="divider"></li>
|
||||||
|
<li ng-repeat="item in items | filter:filter">
|
||||||
|
<a class="menu-item" href="javascript:void(0)" ng-click="toggleItem(item)">
|
||||||
|
<span class="co-checkable-item" ng-class="isChecked(selectedItems, item) ? 'checked': 'not-checked'">
|
||||||
|
</span>
|
||||||
|
<span class="menu-item-template" ng-transcope></span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li role="presentation" ng-if="(items | filter:filter).length == 0">
|
||||||
|
<div class="empty">
|
||||||
|
<div class="empty-primary-msg">No matching {{ itemName }}s found</div>
|
||||||
|
<div class="empty-secondary-msg">
|
||||||
|
Please reduce your filter above
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -108,8 +108,8 @@
|
||||||
data-container="body" data-animation="am-slide-right" bs-aside>
|
data-container="body" data-animation="am-slide-right" bs-aside>
|
||||||
<i class="fa fa-bell user-tool"
|
<i class="fa fa-bell user-tool"
|
||||||
data-placement="bottom" data-title="Notifications" bs-tooltip></i>
|
data-placement="bottom" data-title="Notifications" bs-tooltip></i>
|
||||||
</a>
|
|
||||||
<span class="notifications-bubble"></span>
|
<span class="notifications-bubble"></span>
|
||||||
|
</a>
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
@ -185,7 +185,7 @@
|
||||||
<span ng-switch-when="repository">
|
<span ng-switch-when="repository">
|
||||||
<span class="avatar" data="result.namespace.avatar" size="16"></span>
|
<span class="avatar" data="result.namespace.avatar" size="16"></span>
|
||||||
<span class="result-name">{{ result.namespace.name }}/{{ result.name }}</span>
|
<span class="result-name">{{ result.namespace.name }}/{{ result.name }}</span>
|
||||||
<div class="description" ng-if="result.description">
|
<div class="result-description" ng-if="result.description">
|
||||||
<div class="description markdown-view" content="result.description"
|
<div class="description markdown-view" content="result.description"
|
||||||
first-line-only="true" placeholder-needed="false"></div>
|
first-line-only="true" placeholder-needed="false"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<div class="plan-manager-element">
|
<div class="plan-manager-element">
|
||||||
<!-- Loading/Changing -->
|
<!-- Loading/Changing -->
|
||||||
<div class="quay-spinner 3x" ng-show="planLoading"></div>
|
<div class="cor-loader" ng-show="planLoading"></div>
|
||||||
|
|
||||||
<!-- Alerts -->
|
<!-- Alerts -->
|
||||||
<div class="co-alert co-alert-danger" ng-show="limit == 'over' && !planLoading">
|
<div class="co-alert co-alert-danger" ng-show="limit == 'over' && !planLoading">
|
||||||
|
@ -58,7 +58,7 @@
|
||||||
<div ng-switch='plan.deprecated'>
|
<div ng-switch='plan.deprecated'>
|
||||||
<div ng-switch-when='true'>
|
<div ng-switch-when='true'>
|
||||||
<button class="btn btn-danger" ng-click="cancelSubscription()">
|
<button class="btn btn-danger" ng-click="cancelSubscription()">
|
||||||
<span class="quay-spinner" ng-show="planChanging"></span>
|
<span class="cor-loader-inline" ng-show="planChanging"></span>
|
||||||
<span ng-show="!planChanging">Cancel</span>
|
<span ng-show="!planChanging">Cancel</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -66,14 +66,14 @@
|
||||||
<button class="btn" ng-show="subscribedPlan.stripeId !== plan.stripeId"
|
<button class="btn" ng-show="subscribedPlan.stripeId !== plan.stripeId"
|
||||||
ng-class="subscribedPlan.price == 0 ? 'btn-primary' : 'btn-default'"
|
ng-class="subscribedPlan.price == 0 ? 'btn-primary' : 'btn-default'"
|
||||||
ng-click="changeSubscription(plan.stripeId)">
|
ng-click="changeSubscription(plan.stripeId)">
|
||||||
<span class="quay-spinner" ng-show="planChanging"></span>
|
<span class="cor-loader-inline" ng-show="planChanging"></span>
|
||||||
<span ng-show="!planChanging && subscribedPlan.price != 0">Change</span>
|
<span ng-show="!planChanging && subscribedPlan.price != 0">Change</span>
|
||||||
<span ng-show="!planChanging && subscribedPlan.price == 0 && !isExistingCustomer">Start Free Trial</span>
|
<span ng-show="!planChanging && subscribedPlan.price == 0 && !isExistingCustomer">Start Free Trial</span>
|
||||||
<span ng-show="!planChanging && subscribedPlan.price == 0 && isExistingCustomer">Subscribe</span>
|
<span ng-show="!planChanging && subscribedPlan.price == 0 && isExistingCustomer">Subscribe</span>
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-danger" ng-show="subscription.plan === plan.stripeId && plan.price > 0"
|
<button class="btn btn-danger" ng-show="subscription.plan === plan.stripeId && plan.price > 0"
|
||||||
ng-click="cancelSubscription()">
|
ng-click="cancelSubscription()">
|
||||||
<span class="quay-spinner" ng-show="planChanging"></span>
|
<span class="cor-loader-inline" ng-show="planChanging"></span>
|
||||||
<span ng-show="!planChanging">Cancel</span>
|
<span ng-show="!planChanging">Cancel</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -54,7 +54,6 @@
|
||||||
style="min-width: 66px;">
|
style="min-width: 66px;">
|
||||||
<a href="javascript:void(0)" ng-click="orderBy('tags')">Tags</a>
|
<a href="javascript:void(0)" ng-click="orderBy('tags')">Tags</a>
|
||||||
</td>
|
</td>
|
||||||
<td class="options-col hidden-xs"></td>
|
|
||||||
</thead>
|
</thead>
|
||||||
|
|
||||||
<tr ng-repeat="build in fullBuilds">
|
<tr ng-repeat="build in fullBuilds">
|
||||||
|
@ -139,6 +138,7 @@
|
||||||
<td>{{ trigger.config.branchtag_regex || 'All' }}</td>
|
<td>{{ trigger.config.branchtag_regex || 'All' }}</td>
|
||||||
<td>
|
<td>
|
||||||
<span class="entity-reference" entity="trigger.pull_robot" ng-if="trigger.pull_robot"></span>
|
<span class="entity-reference" entity="trigger.pull_robot" ng-if="trigger.pull_robot"></span>
|
||||||
|
<span class="empty" ng-if="!trigger.pull_robot">(None)</span>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<span class="cor-options-menu">
|
<span class="cor-options-menu">
|
||||||
|
|
|
@ -1,23 +1,24 @@
|
||||||
<div class="repo-panel-changes-element">
|
<div class="repo-panel-changes-element">
|
||||||
<div class="resource-view" resource="imagesResource"
|
<div class="resource-view" resource="imagesResource"
|
||||||
error-message="'Could not load repository images'">
|
error-message="'Could not load repository images'">
|
||||||
|
<h3 class="tab-header">
|
||||||
|
Visualize Tags:
|
||||||
|
<span class="multiselect-dropdown" items="tagNames" selected-items="selectedTags"
|
||||||
|
item-name="tag" item-checked="updateState()">
|
||||||
|
<span class="tag-span">{{ item }}</span>
|
||||||
|
</span>
|
||||||
|
</h3>
|
||||||
|
|
||||||
<!-- No Tags Selected -->
|
<!-- No Tags Selected -->
|
||||||
<div class="empty" ng-if="!selectedTags.length">
|
<div class="empty" ng-if="!selectedTags.length">
|
||||||
<div class="empty-primary-msg">No tags selected to view</div>
|
<div class="empty-primary-msg">No tags selected to view</div>
|
||||||
<div class="empty-secondary-msg">
|
<div class="empty-secondary-msg">
|
||||||
Please select one or more tags in the <i class="fa fa-tags" style="margin-left: 4px; margin-right: 4px;"></i> Tags tab to visualize.
|
Please select one or more tags above.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Tags Selected -->
|
<!-- Tags Selected -->
|
||||||
<div ng-if="selectedTags.length > 0">
|
<div ng-show="selectedTags.length > 0">
|
||||||
<h3 class="tab-header">
|
|
||||||
Visualize Tags:
|
|
||||||
<span class="visualized-tag" ng-repeat="tag in selectedTags">
|
|
||||||
<i class="fa fa-tag"></i>{{ tag }}
|
|
||||||
</span>
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<div id="image-history row" class="resource-view" resource="imagesResource"
|
<div id="image-history row" class="resource-view" resource="imagesResource"
|
||||||
error-message="'Cannot load repository images'">
|
error-message="'Cannot load repository images'">
|
||||||
|
|
||||||
|
|
|
@ -6,12 +6,12 @@
|
||||||
<div class="stat-title">Repo Pulls</div>
|
<div class="stat-title">Repo Pulls</div>
|
||||||
|
|
||||||
<div class="stat">
|
<div class="stat">
|
||||||
<div class="stat-value">{{ repository.stats.pulls.today }}</div>
|
<div class="stat-value">{{ repository.stats.pulls.today | abbreviated }}</div>
|
||||||
<div class="stat-subtitle">Last 24 hours</div>
|
<div class="stat-subtitle">Last 24 hours</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="stat">
|
<div class="stat">
|
||||||
<div class="stat-value">{{ repository.stats.pulls.thirty_day }}</div>
|
<div class="stat-value">{{ repository.stats.pulls.thirty_day | abbreviated }}</div>
|
||||||
<div class="stat-subtitle">Last 30 days</div>
|
<div class="stat-subtitle">Last 30 days</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -21,12 +21,12 @@
|
||||||
<div class="stat-title">Repo Pushes</div>
|
<div class="stat-title">Repo Pushes</div>
|
||||||
|
|
||||||
<div class="stat">
|
<div class="stat">
|
||||||
<div class="stat-value">{{ repository.stats.pushes.today }}</div>
|
<div class="stat-value">{{ repository.stats.pushes.today | abbreviated }}</div>
|
||||||
<div class="stat-subtitle">Last 24 hours</div>
|
<div class="stat-subtitle">Last 24 hours</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="stat">
|
<div class="stat">
|
||||||
<div class="stat-value">{{ repository.stats.pushes.thirty_day }}</div>
|
<div class="stat-value">{{ repository.stats.pushes.thirty_day | abbreviated }}</div>
|
||||||
<div class="stat-subtitle">Last 30 days</div>
|
<div class="stat-subtitle">Last 30 days</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -133,12 +133,12 @@
|
||||||
</td>
|
</td>
|
||||||
<td class="options-col">
|
<td class="options-col">
|
||||||
<span class="cor-options-menu" ng-if="repository.can_write">
|
<span class="cor-options-menu" ng-if="repository.can_write">
|
||||||
<span class="cor-option" option-click="askDeleteTag(tag.name)">
|
|
||||||
<i class="fa fa-times"></i> Delete Tag
|
|
||||||
</span>
|
|
||||||
<span class="cor-option" option-click="askAddTag(tag)">
|
<span class="cor-option" option-click="askAddTag(tag)">
|
||||||
<i class="fa fa-plus"></i> Add New Tag
|
<i class="fa fa-plus"></i> Add New Tag
|
||||||
</span>
|
</span>
|
||||||
|
<span class="cor-option" option-click="askDeleteTag(tag.name)">
|
||||||
|
<i class="fa fa-times"></i> Delete Tag
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
@ -65,7 +65,7 @@
|
||||||
<span class="empty" bo-if="robotInfo.teams.length > 0">
|
<span class="empty" bo-if="robotInfo.teams.length > 0">
|
||||||
<span ng-repeat="team in robotInfo.teams"
|
<span ng-repeat="team in robotInfo.teams"
|
||||||
data-title="Team {{ team.name }}" bs-tooltip>
|
data-title="Team {{ team.name }}" bs-tooltip>
|
||||||
<span class="anchor" is-text-only="!organization.admin" href="/organization/{{ organization.name }}/teams/{{ team.name }}">
|
<span class="anchor" is-only-text="!organization.admin" href="/organization/{{ organization.name }}/teams/{{ team.name }}">
|
||||||
<span class="avatar" size="24" data="team.avatar"></span>
|
<span class="avatar" size="24" data="team.avatar"></span>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
|
@ -78,7 +78,7 @@
|
||||||
|
|
||||||
<span class="member-perm-summary" bo-if="robotInfo.repositories.length > 0">
|
<span class="member-perm-summary" bo-if="robotInfo.repositories.length > 0">
|
||||||
Direct Permissions on
|
Direct Permissions on
|
||||||
<span class="anchor hidden-xs" href="javascript:void(0)" is-text-only="!organization.is_admin"
|
<span class="anchor hidden-xs" href="javascript:void(0)" is-only-text="!organization.is_admin"
|
||||||
ng-click="showPermissions(robotInfo)">
|
ng-click="showPermissions(robotInfo)">
|
||||||
<span bo-text="robotInfo.repositories.length"></span>
|
<span bo-text="robotInfo.repositories.length"></span>
|
||||||
<span bo-if="robotInfo.repositories.length == 1">repository</span>
|
<span bo-if="robotInfo.repositories.length == 1">repository</span>
|
||||||
|
@ -96,7 +96,7 @@
|
||||||
<span class="cor-option" option-click="showRobot(robotInfo)">
|
<span class="cor-option" option-click="showRobot(robotInfo)">
|
||||||
<i class="fa fa-key"></i> View Credentials
|
<i class="fa fa-key"></i> View Credentials
|
||||||
</span>
|
</span>
|
||||||
<span class="cor-option" option-click="deleteRobot(robotInfo)">
|
<span class="cor-option" option-click="askDeleteRobot(robotInfo)">
|
||||||
<i class="fa fa-times"></i> Delete Robot {{ robotInfo.name }}
|
<i class="fa fa-times"></i> Delete Robot {{ robotInfo.name }}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
<div class="setup-trigger-directive-element">
|
<div class="setup-trigger-directive-element">
|
||||||
|
|
||||||
<!-- Modal message dialog -->
|
<!-- Modal message dialog -->
|
||||||
<div class="modal fade" id="setupTriggerModal">
|
<div class="modal fade" id="setupTriggerModal">
|
||||||
<div class="modal-dialog">
|
<div class="modal-dialog">
|
||||||
|
@ -8,8 +7,8 @@
|
||||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||||
<h4 class="modal-title">Setup new build trigger</h4>
|
<h4 class="modal-title">Setup new build trigger</h4>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body" ng-show="currentView == 'activating'">
|
<div class="modal-body loading" ng-show="currentView == 'activating'">
|
||||||
<span class="quay-spinner"></span> Setting up trigger...
|
<span class="cor-loader-inline"></span> Setting up trigger...
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body" ng-show="currentView != 'activating'">
|
<div class="modal-body" ng-show="currentView != 'activating'">
|
||||||
<!-- Trigger-specific setup -->
|
<!-- Trigger-specific setup -->
|
||||||
|
@ -34,8 +33,8 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Loading pull information -->
|
<!-- Loading pull information -->
|
||||||
<div ng-show="currentView == 'analyzing'">
|
<div ng-show="currentView == 'analyzing'" class="loading">
|
||||||
<span class="quay-spinner"></span> Checking pull credential requirements...
|
<span class="cor-loader-inline"></span> Checking pull credential requirements...
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Pull information -->
|
<!-- Pull information -->
|
||||||
|
@ -125,7 +124,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer" ng-show="currentView != 'activating'">
|
||||||
<button type="button" class="btn btn-primary" ng-disabled="!state.stepValid"
|
<button type="button" class="btn btn-primary" ng-disabled="!state.stepValid"
|
||||||
ng-click="nextStepCounter = nextStepCounter + 1"
|
ng-click="nextStepCounter = nextStepCounter + 1"
|
||||||
ng-show="currentView == 'config'">Next</button>
|
ng-show="currentView == 'config'">Next</button>
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
<span class="source-commit-link-elememt">
|
<span class="source-commit-link-element">
|
||||||
<i class="fa fa-dot-circle-o" data-title="Commit" data-container="body" bs-tooltip></i>
|
<soan class="commit-circle-container" data-title="Commit" data-container="body" bs-tooltip>
|
||||||
<span class="anchor" href="getUrl(commitSha, urlTemplate)" target="_blank" is-only-text="!urlTemplate">
|
<span class="commit-circle"></span>
|
||||||
|
</span>
|
||||||
|
<span class="anchor" href="{{ getUrl(commitSha, urlTemplate) }}" target="_blank"
|
||||||
|
is-only-text="!urlTemplate">
|
||||||
{{ commitSha.substring(0, 8) }}
|
{{ commitSha.substring(0, 8) }}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
|
|
|
@ -3,13 +3,13 @@
|
||||||
<!-- Branch -->
|
<!-- Branch -->
|
||||||
<span ng-switch-when="heads">
|
<span ng-switch-when="heads">
|
||||||
<i class="fa fa-code-fork" data-container="body" data-title="Branch" bs-tooltip></i>
|
<i class="fa fa-code-fork" data-container="body" data-title="Branch" bs-tooltip></i>
|
||||||
<span href="getUrl(ref, branchTemplate, 'branch')" is-only-text="!branchTemplate" target="_blank">{{ getTitle(ref) }}</span>
|
<span class="anchor" href="{{ getUrl(ref, branchTemplate, 'branch') }}" is-only-text="!branchTemplate" target="_blank">{{ getTitle(ref) }}</span>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<!-- Tag -->
|
<!-- Tag -->
|
||||||
<span ng-switch-when="tags">
|
<span ng-switch-when="tags">
|
||||||
<i class="fa fa-tag" data-container="body" data-title="Tag" bs-tooltip></i>
|
<i class="fa fa-tag" data-container="body" data-title="Tag" bs-tooltip></i>
|
||||||
<span href="getUrl(ref, tagTemplate, 'tag')" is-only-text="!tagTemplate" target="_blank">{{ getTitle(ref) }}</span>
|
<span class="anchor" href="{{ getUrl(ref, tagTemplate, 'tag') }}" is-only-text="!tagTemplate" target="_blank">{{ getTitle(ref) }}</span>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
<span class="step-view-step-content">
|
<div class="step-view-step-content">
|
||||||
<span ng-show="!loading">
|
<div ng-show="!loading">
|
||||||
<span ng-transclude></span>
|
<div ng-transclude></div>
|
||||||
</span>
|
</div>
|
||||||
<span ng-show="loading">
|
<div ng-show="loading" class="loading-message">
|
||||||
<span class="quay-spinner"></span>
|
<span class="cor-loader-inline"></span>
|
||||||
{{ loadMessage }}
|
{{ loadMessage }}
|
||||||
</span>
|
</div>
|
||||||
</span>
|
</div>
|
||||||
|
|
|
@ -8,17 +8,12 @@
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div class="current-repo">
|
<div class="current-repo">
|
||||||
|
<i class="dropdown-select-icon org-icon fa" ng-class="'fa-' + kind"
|
||||||
|
ng-show="!state.currentRepo.avatar_url"></i>
|
||||||
<img class="dropdown-select-icon org-icon"
|
<img class="dropdown-select-icon org-icon"
|
||||||
ng-src="{{ state.currentRepo.avatar_url ? state.currentRepo.avatar_url : '/static/img/empty.png' }}">
|
ng-src="{{ state.currentRepo.avatar_url ? state.currentRepo.avatar_url : '/static/img/empty.png' }}"
|
||||||
|
ng-show="state.currentRepo.avatar_url">
|
||||||
<!-- Kind Switch -->
|
|
||||||
<a ng-href="https://github.com/{{ state.currentRepo.repo }}" target="_blank" ng-if="kind == 'github'">
|
|
||||||
{{ state.currentRepo.repo }}
|
{{ state.currentRepo.repo }}
|
||||||
</a>
|
|
||||||
<a ng-href="https://bitbucket.org/{{ state.currentRepo.repo }}" target="_blank" ng-if="kind == 'bitbucket'">
|
|
||||||
{{ state.currentRepo.repo }}
|
|
||||||
</a>
|
|
||||||
<!-- /Kind Switch -->
|
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -61,14 +56,17 @@
|
||||||
<!-- Icons -->
|
<!-- Icons -->
|
||||||
<i class="dropdown-select-icon none-icon fa fa-lg" ng-class="'fa-' + kind"></i>
|
<i class="dropdown-select-icon none-icon fa fa-lg" ng-class="'fa-' + kind"></i>
|
||||||
<img class="dropdown-select-icon org-icon"
|
<img class="dropdown-select-icon org-icon"
|
||||||
ng-src="{{ state.currentRepo.avatar_url ? state.currentRepo.avatar_url : '/static/image/empty.png' }}">
|
ng-show="state.currentRepo.avatar_url"
|
||||||
|
ng-src="{{ state.currentRepo.avatar_url ? state.currentRepo.avatar_url : '/static/img/empty.png' }}">
|
||||||
|
<i class="dropdown-select-icon org-icon fa fa-lg" ng-class="'fa-' + kind"
|
||||||
|
ng-show="!state.currentRepo.avatar_url"></i>
|
||||||
|
|
||||||
<!-- Dropdown menu -->
|
<!-- Dropdown menu -->
|
||||||
<ul class="dropdown-select-menu scrollable-menu" role="menu">
|
<ul class="dropdown-select-menu scrollable-menu" role="menu">
|
||||||
<li ng-repeat-start="org in orgs" role="presentation" class="dropdown-header org-header">
|
<li ng-repeat-start="org in orgs" role="presentation" class="dropdown-header org-header">
|
||||||
<img ng-src="{{ org.info.avatar_url }}" class="org-icon">{{ org.info.name }}
|
<img ng-src="{{ org.info.avatar_url }}" class="org-icon">{{ org.info.name }}
|
||||||
</li>
|
</li>
|
||||||
<li ng-repeat="repo in org.repos" class="repo-listing">
|
<li ng-repeat="repo in org.repos" class="trigger-repo-listing">
|
||||||
<a href="javascript:void(0)" ng-click="selectRepo(repo, org)">
|
<a href="javascript:void(0)" ng-click="selectRepo(repo, org)">
|
||||||
<i class="fa fa-lg" ng-class="'fa-' + kind"></i> {{ repo }}
|
<i class="fa fa-lg" ng-class="'fa-' + kind"></i> {{ repo }}
|
||||||
</a>
|
</a>
|
||||||
|
@ -187,7 +185,7 @@
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="quay-spinner" ng-show="!locations && !locationError"></div>
|
<div class="cor-loader" ng-show="!locations && !locationError"></div>
|
||||||
<div class="alert alert-warning" ng-show="locationError">
|
<div class="alert alert-warning" ng-show="locationError">
|
||||||
{{ locationError }}
|
{{ locationError }}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -7,39 +7,51 @@
|
||||||
|
|
||||||
<!-- Manually built with a user -->
|
<!-- Manually built with a user -->
|
||||||
<div class="tbd-content" ng-switch-when="manual+user">
|
<div class="tbd-content" ng-switch-when="manual+user">
|
||||||
<i class="fa fa-user"></i> {{ build.job_config.manual_user }}
|
<i class="fa fa-user"></i> {{ build.manual_user }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Source information only (i.e. no info) -->
|
<!-- Source information only (i.e. no info) -->
|
||||||
<div class="tbd-content" ng-switch-when="source">
|
<div class="tbd-content" ng-switch-when="source">
|
||||||
Triggered by commit to
|
Triggered by
|
||||||
<div class="trigger-description" short="true" trigger="build.trigger"></div>
|
<div class="trigger-description" short="true" trigger="build.trigger"
|
||||||
|
style="display: inline-block; margin-left: 4px;"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Just commit SHA -->
|
<!-- Just commit SHA -->
|
||||||
<div class="tbd-content" ng-switch-when="commitsha">
|
<div class="tbd-content" ng-switch-when="commitsha">
|
||||||
Triggered by commit
|
Triggered by commit
|
||||||
<span class="source-commit-link"
|
<span class="source-commit-link"
|
||||||
commit-sha="build.job_config.trigger_metadata.commit_sha"></span>
|
commit-sha="build.trigger_metadata.commit_sha"></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Full Commit Information -->
|
<!-- Full Commit Information -->
|
||||||
<div class="tbd-content" ng-switch-when="fullcommit">
|
<div class="tbd-content" ng-switch-when="fullcommit">
|
||||||
<div class="commit-message">
|
<div class="commit-message">
|
||||||
{{ build.job_config.trigger_metadata.commit_info.message }}
|
<span class="anchor" is-only-text="!TriggerService.getFullLinkTemplate(build, 'commit')"
|
||||||
|
target="_blank" href="{{ TriggerService.getFullLinkTemplate(build, 'commit').replace('{sha}', build.trigger_metadata.commit_sha) }}">
|
||||||
|
{{ build.trigger_metadata.commit_info.message }}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="commit-information">
|
<div class="commit-information">
|
||||||
<span class="commit-who-when">
|
<span class="commit-who-when">
|
||||||
Authored
|
Authored
|
||||||
<span am-time-ago="build.job_config.trigger_metadata.commit_info.date"></span>
|
<span am-time-ago="build.trigger_metadata.commit_info.date"></span>
|
||||||
<span class="commit-who">
|
<span class="anchor commit-who"
|
||||||
{{ build.job_config.trigger_metadata.commit_info.author.username }}
|
href="{{ build.trigger_metadata.commit_info.author.url }}"
|
||||||
|
is-only-text="!build.trigger_metadata.commit_info.author.url"
|
||||||
|
target="_blank">
|
||||||
|
<img ng-src="{{ build.trigger_metadata.commit_info.author.avatar_url }}"
|
||||||
|
ng-if="build.trigger_metadata.commit_info.author.avatar_url">
|
||||||
|
{{ build.trigger_metadata.commit_info.author.username }}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<span class="source-commit-link"
|
<span class="source-commit-link"
|
||||||
commit-sha="build.job_config.trigger_metadata.commit_sha"></span>
|
commit-sha="build.trigger_metadata.commit_sha"
|
||||||
|
url-template="TriggerService.getFullLinkTemplate(build, 'commit')"></span>
|
||||||
<span class="source-ref-link"
|
<span class="source-ref-link"
|
||||||
ref="build.job_config.trigger_metadata.ref"></span>
|
ref="build.trigger_metadata.ref"
|
||||||
|
branch-template="TriggerService.getFullLinkTemplate(build, 'branch')"
|
||||||
|
tag-template="TriggerService.getFullLinkTemplate(build, 'tag')"></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
26
static/js/directives/filters/abbreviated.js
Normal file
26
static/js/directives/filters/abbreviated.js
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
/**
|
||||||
|
* Filter which displays numbers with suffixes.
|
||||||
|
*
|
||||||
|
* Based on: https://gist.github.com/pedrorocha-net/9aa21d5f34d9cc15d18f
|
||||||
|
*/
|
||||||
|
angular.module('quay').filter('abbreviated', function() {
|
||||||
|
return function(number) {
|
||||||
|
if (number >= 10000000) {
|
||||||
|
return (number / 1000000).toFixed(0) + 'M'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (number >= 1000000) {
|
||||||
|
return (number / 1000000).toFixed(1) + 'M'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (number >= 10000) {
|
||||||
|
return (number / 1000).toFixed(0) + 'K'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (number >= 1000) {
|
||||||
|
return (number / 1000).toFixed(1) + 'K'
|
||||||
|
}
|
||||||
|
|
||||||
|
return number
|
||||||
|
}
|
||||||
|
});
|
25
static/js/directives/ng-transcope.js
Normal file
25
static/js/directives/ng-transcope.js
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
/**
|
||||||
|
* Directive to transclude a template under an ng-repeat. From: http://stackoverflow.com/a/24512435
|
||||||
|
*/
|
||||||
|
angular.module('quay').directive('ngTranscope', function() {
|
||||||
|
return {
|
||||||
|
link: function( $scope, $element, $attrs, controller, $transclude ) {
|
||||||
|
if ( !$transclude ) {
|
||||||
|
throw minErr( 'ngTranscope' )( 'orphan',
|
||||||
|
'Illegal use of ngTransclude directive in the template! ' +
|
||||||
|
'No parent directive that requires a transclusion found. ' +
|
||||||
|
'Element: {0}',
|
||||||
|
startingTag( $element ));
|
||||||
|
}
|
||||||
|
var innerScope = $scope.$new();
|
||||||
|
|
||||||
|
$transclude( innerScope, function( clone ) {
|
||||||
|
$element.empty();
|
||||||
|
$element.append( clone );
|
||||||
|
$element.on( '$destroy', function() {
|
||||||
|
innerScope.$destroy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
|
@ -42,15 +42,14 @@ angular.module('quay').directive('repoPanelBuilds', function () {
|
||||||
|
|
||||||
var unordered = $scope.allBuilds.map(function(build_info) {
|
var unordered = $scope.allBuilds.map(function(build_info) {
|
||||||
var commit_sha = null;
|
var commit_sha = null;
|
||||||
var job_config = build_info.job_config || {};
|
|
||||||
|
|
||||||
if (job_config.trigger_metadata) {
|
if (build_info.trigger_metadata) {
|
||||||
commit_sha = job_config.trigger_metadata.commit_sha;
|
commit_sha = build_info.trigger_metadata.commit_sha;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $.extend(build_info, {
|
return $.extend(build_info, {
|
||||||
'started_datetime': (new Date(build_info.started)).valueOf() * (-1),
|
'started_datetime': (new Date(build_info.started)).valueOf() * (-1),
|
||||||
'building_tags': job_config.docker_tags || [],
|
'building_tags': build_info.tags || [],
|
||||||
'commit_sha': commit_sha
|
'commit_sha': commit_sha
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -58,14 +57,16 @@ angular.module('quay').directive('repoPanelBuilds', function () {
|
||||||
$scope.fullBuilds = orderBy(unordered, $scope.options.predicate, $scope.options.reverse);
|
$scope.fullBuilds = orderBy(unordered, $scope.options.predicate, $scope.options.reverse);
|
||||||
};
|
};
|
||||||
|
|
||||||
var loadBuilds = function() {
|
var loadBuilds = function(opt_forcerefresh) {
|
||||||
if (!$scope.builds || !$scope.repository || !$scope.options.filter) {
|
if (!$scope.builds || !$scope.repository || !$scope.options.filter) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: We only refresh if the filter has changed.
|
// Note: We only refresh if the filter has changed.
|
||||||
var filter = $scope.options.filter;
|
var filter = $scope.options.filter;
|
||||||
if ($scope.buildsResource && filter == $scope.currentFilter) { return; }
|
if ($scope.buildsResource && filter == $scope.currentFilter && !opt_forcerefresh) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var since = null;
|
var since = null;
|
||||||
var limit = 10;
|
var limit = 10;
|
||||||
|
@ -105,17 +106,30 @@ angular.module('quay').directive('repoPanelBuilds', function () {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replace any build records with updated records from the server.
|
// Replace any build records with updated records from the server.
|
||||||
|
var requireReload = false;
|
||||||
$scope.builds.map(function(build) {
|
$scope.builds.map(function(build) {
|
||||||
|
var found = false;
|
||||||
for (var i = 0; i < $scope.allBuilds.length; ++i) {
|
for (var i = 0; i < $scope.allBuilds.length; ++i) {
|
||||||
var current = $scope.allBuilds[i];
|
var current = $scope.allBuilds[i];
|
||||||
if (current.id == build.id && current.phase != build.phase) {
|
if (current.id == build.id && current.phase != build.phase) {
|
||||||
$scope.allBuilds[i] = build;
|
$scope.allBuilds[i] = build;
|
||||||
break
|
found = true;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the build was not found, then a new build has started. Reload
|
||||||
|
// the builds list.
|
||||||
|
if (!found) {
|
||||||
|
requireReload = true;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (requireReload) {
|
||||||
|
loadBuilds(/* force refresh */true);
|
||||||
|
} else {
|
||||||
updateBuilds();
|
updateBuilds();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var loadBuildTriggers = function() {
|
var loadBuildTriggers = function() {
|
||||||
|
|
|
@ -96,20 +96,24 @@ angular.module('quay').directive('repoPanelChanges', function () {
|
||||||
'isEnabled': '=isEnabled'
|
'isEnabled': '=isEnabled'
|
||||||
},
|
},
|
||||||
controller: function($scope, $element, $timeout, ApiService, UtilService, ImageMetadataService) {
|
controller: function($scope, $element, $timeout, ApiService, UtilService, ImageMetadataService) {
|
||||||
|
$scope.tagNames = [];
|
||||||
|
|
||||||
var update = function() {
|
var update = function() {
|
||||||
if (!$scope.repository || !$scope.selectedTags) { return; }
|
if (!$scope.repository || !$scope.isEnabled) { return; }
|
||||||
|
|
||||||
|
$scope.tagNames = Object.keys($scope.repository.tags);
|
||||||
$scope.currentImage = null;
|
$scope.currentImage = null;
|
||||||
$scope.currentTag = null;
|
$scope.currentTag = null;
|
||||||
|
|
||||||
if (!$scope.tracker) {
|
if ($scope.tracker) {
|
||||||
|
refreshTree();
|
||||||
|
} else {
|
||||||
updateImages();
|
updateImages();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var updateImages = function() {
|
var updateImages = function() {
|
||||||
if (!$scope.repository || !$scope.images) { return; }
|
if (!$scope.repository || !$scope.images || !$scope.isEnabled) { return; }
|
||||||
|
|
||||||
$scope.tracker = new RepositoryImageTracker($scope.repository, $scope.images);
|
$scope.tracker = new RepositoryImageTracker($scope.repository, $scope.images);
|
||||||
|
|
||||||
|
@ -120,16 +124,17 @@ angular.module('quay').directive('repoPanelChanges', function () {
|
||||||
|
|
||||||
$scope.$watch('selectedTags', update)
|
$scope.$watch('selectedTags', update)
|
||||||
$scope.$watch('repository', update);
|
$scope.$watch('repository', update);
|
||||||
|
$scope.$watch('isEnabled', update);
|
||||||
|
|
||||||
$scope.$watch('images', updateImages);
|
$scope.$watch('images', updateImages);
|
||||||
|
|
||||||
$scope.$watch('isEnabled', function(isEnabled) {
|
$scope.updateState = function() {
|
||||||
if (isEnabled) {
|
update();
|
||||||
refreshTree();
|
};
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
var refreshTree = function() {
|
var refreshTree = function() {
|
||||||
if (!$scope.repository || !$scope.images) { return; }
|
if (!$scope.repository || !$scope.images || !$scope.isEnabled) { return; }
|
||||||
|
if ($scope.selectedTags.length < 1) { return; }
|
||||||
|
|
||||||
$('#image-history-container').empty();
|
$('#image-history-container').empty();
|
||||||
|
|
||||||
|
@ -149,6 +154,7 @@ angular.module('quay').directive('repoPanelChanges', function () {
|
||||||
// Give enough time for the UI to be drawn before we resize the tree.
|
// Give enough time for the UI to be drawn before we resize the tree.
|
||||||
$timeout(function() {
|
$timeout(function() {
|
||||||
$scope.tree.notifyResized();
|
$scope.tree.notifyResized();
|
||||||
|
$scope.setTag($scope.selectedTags[0]);
|
||||||
}, 100);
|
}, 100);
|
||||||
|
|
||||||
// Listen for changes to the selected tag and image in the tree.
|
// Listen for changes to the selected tag and image in the tree.
|
||||||
|
|
|
@ -10,6 +10,7 @@ angular.module('quay').directive('anchor', function () {
|
||||||
restrict: 'C',
|
restrict: 'C',
|
||||||
scope: {
|
scope: {
|
||||||
'href': '@href',
|
'href': '@href',
|
||||||
|
'target': '@target',
|
||||||
'isOnlyText': '=isOnlyText'
|
'isOnlyText': '=isOnlyText'
|
||||||
},
|
},
|
||||||
controller: function($scope, $element) {
|
controller: function($scope, $element) {
|
||||||
|
|
35
static/js/directives/ui/multiselect-dropdown.js
Normal file
35
static/js/directives/ui/multiselect-dropdown.js
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
/**
|
||||||
|
* An element which displays a dropdown for selecting multiple elements.
|
||||||
|
*/
|
||||||
|
angular.module('quay').directive('multiselectDropdown', function ($compile) {
|
||||||
|
var directiveDefinitionObject = {
|
||||||
|
priority: 0,
|
||||||
|
templateUrl: '/static/directives/multiselect-dropdown.html',
|
||||||
|
transclude: true,
|
||||||
|
replace: false,
|
||||||
|
restrict: 'C',
|
||||||
|
scope: {
|
||||||
|
'items': '=items',
|
||||||
|
'selectedItems': '=selectedItems',
|
||||||
|
'itemName': '@itemName',
|
||||||
|
'itemChecked': '&itemChecked'
|
||||||
|
},
|
||||||
|
controller: function($scope, $element) {
|
||||||
|
$scope.isChecked = function(checked, item) {
|
||||||
|
return checked.indexOf(item) >= 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.toggleItem = function(item) {
|
||||||
|
var isChecked = $scope.isChecked($scope.selectedItems, item);
|
||||||
|
if (!isChecked) {
|
||||||
|
$scope.selectedItems.push(item);
|
||||||
|
} else {
|
||||||
|
var index = $scope.selectedItems.indexOf(item);
|
||||||
|
$scope.selectedItems.splice(index, 1);
|
||||||
|
}
|
||||||
|
$scope.itemChecked({'item': item, 'checked': !isChecked});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return directiveDefinitionObject;
|
||||||
|
});
|
|
@ -128,6 +128,15 @@ angular.module('quay').directive('robotsManager', function () {
|
||||||
}, ApiService.errorDisplay('Cannot delete robot account'));
|
}, ApiService.errorDisplay('Cannot delete robot account'));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
$scope.askDeleteRobot = function(info) {
|
||||||
|
bootbox.confirm('Are you sure you want to delete robot ' + info.name + '?', function(resp) {
|
||||||
|
if (resp) {
|
||||||
|
$scope.deleteRobot(info);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
var update = function() {
|
var update = function() {
|
||||||
if (!$scope.user && !$scope.organization) { return; }
|
if (!$scope.user && !$scope.organization) { return; }
|
||||||
if ($scope.loading || !$scope.isEnabled) { return; }
|
if ($scope.loading || !$scope.isEnabled) { return; }
|
||||||
|
|
|
@ -6,11 +6,12 @@ angular.module('quay').directive('sourceCommitLink', function () {
|
||||||
priority: 0,
|
priority: 0,
|
||||||
templateUrl: '/static/directives/source-commit-link.html',
|
templateUrl: '/static/directives/source-commit-link.html',
|
||||||
replace: false,
|
replace: false,
|
||||||
transclude: false,
|
transclude: true,
|
||||||
restrict: 'C',
|
restrict: 'C',
|
||||||
scope: {
|
scope: {
|
||||||
'commitSha': '=commitSha',
|
'commitSha': '=commitSha',
|
||||||
'urlTemplate': '=urlTemplate'
|
'urlTemplate': '=urlTemplate',
|
||||||
|
'showTransclude': '=showTransclude'
|
||||||
},
|
},
|
||||||
controller: function($scope, $element) {
|
controller: function($scope, $element) {
|
||||||
$scope.getUrl = function(sha, template) {
|
$scope.getUrl = function(sha, template) {
|
||||||
|
|
|
@ -16,26 +16,38 @@ angular.module('quay').directive('stepView', function ($compile) {
|
||||||
'stepsCompleted': '&stepsCompleted'
|
'stepsCompleted': '&stepsCompleted'
|
||||||
},
|
},
|
||||||
controller: function($scope, $element, $rootScope) {
|
controller: function($scope, $element, $rootScope) {
|
||||||
this.currentStepIndex = -1;
|
var currentStepIndex = -1;
|
||||||
this.steps = [];
|
var steps = [];
|
||||||
this.watcher = null;
|
var watcher = null;
|
||||||
|
|
||||||
this.getCurrentStep = function() {
|
// Members on 'this' are accessed by the individual steps.
|
||||||
return this.steps[this.currentStepIndex];
|
this.register = function(scope, element) {
|
||||||
|
element.hide();
|
||||||
|
|
||||||
|
steps.push({
|
||||||
|
'scope': scope,
|
||||||
|
'element': element
|
||||||
|
});
|
||||||
|
|
||||||
|
nextStep();
|
||||||
};
|
};
|
||||||
|
|
||||||
this.reset = function() {
|
var getCurrentStep = function() {
|
||||||
this.currentStepIndex = -1;
|
return steps[currentStepIndex];
|
||||||
for (var i = 0; i < this.steps.length; ++i) {
|
};
|
||||||
this.steps[i].element.hide();
|
|
||||||
|
var reset = function() {
|
||||||
|
currentStepIndex = -1;
|
||||||
|
for (var i = 0; i < steps.length; ++i) {
|
||||||
|
steps[i].element.hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
$scope.currentStepValid = false;
|
$scope.currentStepValid = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
this.next = function() {
|
var next = function() {
|
||||||
if (this.currentStepIndex >= 0) {
|
if (currentStepIndex >= 0) {
|
||||||
var currentStep = this.getCurrentStep();
|
var currentStep = getCurrentStep();
|
||||||
if (!currentStep || !currentStep.scope) { return; }
|
if (!currentStep || !currentStep.scope) { return; }
|
||||||
|
|
||||||
if (!currentStep.scope.completeCondition) {
|
if (!currentStep.scope.completeCondition) {
|
||||||
|
@ -44,20 +56,20 @@ angular.module('quay').directive('stepView', function ($compile) {
|
||||||
|
|
||||||
currentStep.element.hide();
|
currentStep.element.hide();
|
||||||
|
|
||||||
if (this.unwatch) {
|
if (unwatch) {
|
||||||
this.unwatch();
|
unwatch();
|
||||||
this.unwatch = null;
|
unwatch = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.currentStepIndex++;
|
currentStepIndex++;
|
||||||
|
|
||||||
if (this.currentStepIndex < this.steps.length) {
|
if (currentStepIndex < steps.length) {
|
||||||
var currentStep = this.getCurrentStep();
|
var currentStep = getCurrentStep();
|
||||||
currentStep.element.show();
|
currentStep.element.show();
|
||||||
currentStep.scope.load()
|
currentStep.scope.load()
|
||||||
|
|
||||||
this.unwatch = currentStep.scope.$watch('completeCondition', function(cc) {
|
unwatch = currentStep.scope.$watch('completeCondition', function(cc) {
|
||||||
$scope.currentStepValid = !!cc;
|
$scope.currentStepValid = !!cc;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
@ -65,23 +77,17 @@ angular.module('quay').directive('stepView', function ($compile) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.register = function(scope, element) {
|
var nextStep = function() {
|
||||||
element.hide();
|
if (!steps || !steps.length) { return; }
|
||||||
|
|
||||||
this.steps.push({
|
if ($scope.nextStepCounter >= 0) {
|
||||||
'scope': scope,
|
next();
|
||||||
'element': element
|
} else {
|
||||||
});
|
reset();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var that = this;
|
$scope.$watch('nextStepCounter', nextStep);
|
||||||
$scope.$watch('nextStepCounter', function(nsc) {
|
|
||||||
if (nsc >= 0) {
|
|
||||||
that.next();
|
|
||||||
} else {
|
|
||||||
that.reset();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
return directiveDefinitionObject;
|
return directiveDefinitionObject;
|
||||||
|
|
|
@ -12,29 +12,30 @@ angular.module('quay').directive('triggeredBuildDescription', function () {
|
||||||
'build': '=build'
|
'build': '=build'
|
||||||
},
|
},
|
||||||
controller: function($scope, $element, KeyService, TriggerService) {
|
controller: function($scope, $element, KeyService, TriggerService) {
|
||||||
|
$scope.TriggerService = TriggerService;
|
||||||
$scope.$watch('build', function(build) {
|
$scope.$watch('build', function(build) {
|
||||||
if (!build) { return; }
|
if (!build) { return; }
|
||||||
|
|
||||||
var jobConfig = build.job_config || {};
|
var triggerMetadata = build.trigger_metadata || {};
|
||||||
var triggerMetadata = jobConfig.trigger_metadata || {};
|
|
||||||
|
|
||||||
if (!build.trigger && !jobConfig.manual_user) {
|
if (!build.trigger && !build.manual_user) {
|
||||||
$scope.infoDisplay = 'manual';
|
$scope.infoDisplay = 'manual';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!build.trigger && jobConfig.manual_user) {
|
if (!build.trigger && build.manual_user) {
|
||||||
$scope.infoDisplay = 'manual+user';
|
$scope.infoDisplay = 'manual+user';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (triggerMetadata.commit_info) {
|
if (build.trigger && triggerMetadata.commit_info) {
|
||||||
$scope.infoDisplay = 'fullcommit';
|
$scope.infoDisplay = 'fullcommit';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (triggerMetadata.commit_sha) {
|
if (build.trigger && build.trigger.build_source && triggerMetadata.commit_sha) {
|
||||||
$scope.infoDisplay = 'commitsha';
|
$scope.infoDisplay = 'commitsha';
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$scope.infoDisplay = 'source';
|
$scope.infoDisplay = 'source';
|
||||||
|
|
|
@ -30,8 +30,7 @@
|
||||||
|
|
||||||
var determineDockerfilePath = function() {
|
var determineDockerfilePath = function() {
|
||||||
var dockerfilePath = 'Dockerfile';
|
var dockerfilePath = 'Dockerfile';
|
||||||
if ($scope.repobuild['job_config']) {
|
var dockerfileFolder = ($scope.repobuild['subdirectory'] || '');
|
||||||
var dockerfileFolder = ($scope.repobuild['job_config']['build_subdir'] || '');
|
|
||||||
if (dockerfileFolder[0] == '/') {
|
if (dockerfileFolder[0] == '/') {
|
||||||
dockerfileFolder = dockerfileFolder.substr(1);
|
dockerfileFolder = dockerfileFolder.substr(1);
|
||||||
}
|
}
|
||||||
|
@ -39,7 +38,6 @@
|
||||||
dockerfileFolder += '/';
|
dockerfileFolder += '/';
|
||||||
}
|
}
|
||||||
dockerfilePath = dockerfileFolder + 'Dockerfile';
|
dockerfilePath = dockerfileFolder + 'Dockerfile';
|
||||||
}
|
|
||||||
return dockerfilePath;
|
return dockerfilePath;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -70,15 +70,12 @@
|
||||||
$scope.restartBuild = function(build) {
|
$scope.restartBuild = function(build) {
|
||||||
$('#confirmRestartBuildModal').modal('hide');
|
$('#confirmRestartBuildModal').modal('hide');
|
||||||
|
|
||||||
var subdirectory = '';
|
var subdirectory = build['subdirectory'] || '';
|
||||||
if (build['job_config']) {
|
|
||||||
subdirectory = build['job_config']['build_subdir'] || '';
|
|
||||||
}
|
|
||||||
|
|
||||||
var data = {
|
var data = {
|
||||||
'file_id': build['resource_key'],
|
'file_id': build['resource_key'],
|
||||||
'subdirectory': subdirectory,
|
'subdirectory': subdirectory,
|
||||||
'docker_tags': build['job_config']['docker_tags']
|
'docker_tags': build['tags']
|
||||||
};
|
};
|
||||||
|
|
||||||
if (build['pull_robot']) {
|
if (build['pull_robot']) {
|
||||||
|
|
|
@ -56,6 +56,16 @@ function($rootScope, $interval, UserService, ApiService, StringBuilderService, P
|
||||||
'<br><br>Please upgrade your plan to avoid disruptions in service.',
|
'<br><br>Please upgrade your plan to avoid disruptions in service.',
|
||||||
'page': function(metadata) {
|
'page': function(metadata) {
|
||||||
var organization = UserService.getOrganization(metadata['namespace']);
|
var organization = UserService.getOrganization(metadata['namespace']);
|
||||||
|
|
||||||
|
// TODO(jschorr): Remove once the new layout is in prod.
|
||||||
|
if (Config.isNewLayout()) {
|
||||||
|
if (organization) {
|
||||||
|
return '/organization/' + metadata['namespace'] + '?tab=billing';
|
||||||
|
} else {
|
||||||
|
return '/user/' + metadata['namespace'] + '?tab=billing';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (organization) {
|
if (organization) {
|
||||||
return '/organization/' + metadata['namespace'] + '/admin';
|
return '/organization/' + metadata['namespace'] + '/admin';
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -54,6 +54,14 @@ angular.module('quay').factory('TriggerService', ['UtilService', '$sanitize', 'K
|
||||||
'templates': {
|
'templates': {
|
||||||
'credentials': '/static/directives/trigger/githost/credentials.html',
|
'credentials': '/static/directives/trigger/githost/credentials.html',
|
||||||
'trigger-description': '/static/directives/trigger/github/trigger-description.html'
|
'trigger-description': '/static/directives/trigger/github/trigger-description.html'
|
||||||
|
},
|
||||||
|
'repository_url': function(build) {
|
||||||
|
return KeyService['githubTriggerEndpoint'] + build.trigger.build_source;
|
||||||
|
},
|
||||||
|
'link_templates': {
|
||||||
|
'commit': '/commit/{sha}',
|
||||||
|
'branch': '/tree/{branch}',
|
||||||
|
'tag': '/releases/tag/{tag}',
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -85,6 +93,14 @@ angular.module('quay').factory('TriggerService', ['UtilService', '$sanitize', 'K
|
||||||
'templates': {
|
'templates': {
|
||||||
'credentials': '/static/directives/trigger/githost/credentials.html',
|
'credentials': '/static/directives/trigger/githost/credentials.html',
|
||||||
'trigger-description': '/static/directives/trigger/bitbucket/trigger-description.html'
|
'trigger-description': '/static/directives/trigger/bitbucket/trigger-description.html'
|
||||||
|
},
|
||||||
|
'repository_url': function(build) {
|
||||||
|
return 'https://bitbucket.org/' + build.trigger.build_source;
|
||||||
|
},
|
||||||
|
'link_templates': {
|
||||||
|
'commit': '/commits/{sha}',
|
||||||
|
'branch': '/branch/{branch}',
|
||||||
|
'tag': '/commits/tag/{tag}',
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -160,6 +176,26 @@ angular.module('quay').factory('TriggerService', ['UtilService', '$sanitize', 'K
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
triggerService.getFullLinkTemplate = function(build, templateName) {
|
||||||
|
var name = build.trigger.service;
|
||||||
|
var type = triggerTypes[name];
|
||||||
|
if (!type) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var repositoryUrl = type.repository_url;
|
||||||
|
if (!repositoryUrl) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var linkTemplate = type.link_templates;
|
||||||
|
if (!linkTemplate || !linkTemplate[templateName]) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return repositoryUrl(build) + linkTemplate[templateName];
|
||||||
|
};
|
||||||
|
|
||||||
triggerService.supportsFullListing = function(name) {
|
triggerService.supportsFullListing = function(name) {
|
||||||
var type = triggerTypes[name];
|
var type = triggerTypes[name];
|
||||||
if (!type) {
|
if (!type) {
|
||||||
|
|
267
static/lib/dropdowns-enhancement.js
Normal file
267
static/lib/dropdowns-enhancement.js
Normal file
|
@ -0,0 +1,267 @@
|
||||||
|
/* ========================================================================
|
||||||
|
* Bootstrap Dropdowns Enhancement: dropdowns-enhancement.js v3.1.1 (Beta 1)
|
||||||
|
* http://behigh.github.io/bootstrap_dropdowns_enhancement/
|
||||||
|
* ========================================================================
|
||||||
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
||||||
|
* ======================================================================== */
|
||||||
|
|
||||||
|
(function($) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var toggle = '[data-toggle="dropdown"]',
|
||||||
|
disabled = '.disabled, :disabled',
|
||||||
|
backdrop = '.dropdown-backdrop',
|
||||||
|
menuClass = 'dropdown-menu',
|
||||||
|
subMenuClass = 'dropdown-submenu',
|
||||||
|
namespace = '.bs.dropdown.data-api',
|
||||||
|
eventNamespace = '.bs.dropdown',
|
||||||
|
openClass = 'open',
|
||||||
|
touchSupport = 'ontouchstart' in document.documentElement,
|
||||||
|
opened;
|
||||||
|
|
||||||
|
|
||||||
|
function Dropdown(element) {
|
||||||
|
$(element).on('click' + eventNamespace, this.toggle)
|
||||||
|
}
|
||||||
|
|
||||||
|
var proto = Dropdown.prototype;
|
||||||
|
|
||||||
|
proto.toggle = function(event) {
|
||||||
|
var $element = $(this);
|
||||||
|
|
||||||
|
if ($element.is(disabled)) return;
|
||||||
|
|
||||||
|
var $parent = getParent($element);
|
||||||
|
var isActive = $parent.hasClass(openClass);
|
||||||
|
var isSubMenu = $parent.hasClass(subMenuClass);
|
||||||
|
var menuTree = isSubMenu ? getSubMenuParents($parent) : null;
|
||||||
|
|
||||||
|
closeOpened(event, menuTree);
|
||||||
|
|
||||||
|
if (!isActive) {
|
||||||
|
if (!menuTree)
|
||||||
|
menuTree = [$parent];
|
||||||
|
|
||||||
|
if (touchSupport && !$parent.closest('.navbar-nav').length && !menuTree[0].find(backdrop).length) {
|
||||||
|
// if mobile we use a backdrop because click events don't delegate
|
||||||
|
$('<div class="' + backdrop.substr(1) + '"/>').appendTo(menuTree[0]).on('click', closeOpened)
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0, s = menuTree.length; i < s; i++) {
|
||||||
|
if (!menuTree[i].hasClass(openClass)) {
|
||||||
|
menuTree[i].addClass(openClass);
|
||||||
|
positioning(menuTree[i].children('.' + menuClass), menuTree[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
opened = menuTree[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
proto.keydown = function (e) {
|
||||||
|
if (!/(38|40|27)/.test(e.keyCode)) return;
|
||||||
|
|
||||||
|
var $this = $(this);
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
if ($this.is('.disabled, :disabled')) return;
|
||||||
|
|
||||||
|
var $parent = getParent($this);
|
||||||
|
var isActive = $parent.hasClass('open');
|
||||||
|
|
||||||
|
if (!isActive || (isActive && e.keyCode == 27)) {
|
||||||
|
if (e.which == 27) $parent.find(toggle).trigger('focus');
|
||||||
|
return $this.trigger('click')
|
||||||
|
}
|
||||||
|
|
||||||
|
var desc = ' li:not(.divider):visible a';
|
||||||
|
var desc1 = 'li:not(.divider):visible > input:not(disabled) ~ label';
|
||||||
|
var $items = $parent.find(desc1 + ', ' + '[role="menu"]' + desc + ', [role="listbox"]' + desc);
|
||||||
|
|
||||||
|
if (!$items.length) return;
|
||||||
|
|
||||||
|
var index = $items.index($items.filter(':focus'));
|
||||||
|
|
||||||
|
if (e.keyCode == 38 && index > 0) index--; // up
|
||||||
|
if (e.keyCode == 40 && index < $items.length - 1) index++; // down
|
||||||
|
if (!~index) index = 0;
|
||||||
|
|
||||||
|
$items.eq(index).trigger('focus')
|
||||||
|
};
|
||||||
|
|
||||||
|
proto.change = function (e) {
|
||||||
|
|
||||||
|
var
|
||||||
|
$parent,
|
||||||
|
$menu,
|
||||||
|
$toggle,
|
||||||
|
selector,
|
||||||
|
text = '',
|
||||||
|
$items;
|
||||||
|
|
||||||
|
$menu = $(this).closest('.' + menuClass);
|
||||||
|
|
||||||
|
$toggle = $menu.parent().find('[data-label-placement]');
|
||||||
|
|
||||||
|
if (!$toggle || !$toggle.length) {
|
||||||
|
$toggle = $menu.parent().find(toggle);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$toggle || !$toggle.length || $toggle.data('placeholder') === false)
|
||||||
|
return; // do nothing, no control
|
||||||
|
|
||||||
|
($toggle.data('placeholder') == undefined && $toggle.data('placeholder', $.trim($toggle.text())));
|
||||||
|
text = $.data($toggle[0], 'placeholder');
|
||||||
|
|
||||||
|
$items = $menu.find('li > input:checked');
|
||||||
|
|
||||||
|
if ($items.length) {
|
||||||
|
text = [];
|
||||||
|
$items.each(function () {
|
||||||
|
var str = $(this).parent().find('label').eq(0),
|
||||||
|
label = str.find('.data-label');
|
||||||
|
|
||||||
|
if (label.length) {
|
||||||
|
var p = $('<p></p>');
|
||||||
|
p.append(label.clone());
|
||||||
|
str = p.html();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
str = str.html();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
str && text.push($.trim(str));
|
||||||
|
});
|
||||||
|
|
||||||
|
text = text.length < 4 ? text.join(', ') : text.length + ' selected';
|
||||||
|
}
|
||||||
|
|
||||||
|
var caret = $toggle.find('.caret');
|
||||||
|
|
||||||
|
$toggle.html(text || ' ');
|
||||||
|
if (caret.length)
|
||||||
|
$toggle.append(' ') && caret.appendTo($toggle);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
function positioning($menu, $control) {
|
||||||
|
if ($menu.hasClass('pull-center')) {
|
||||||
|
$menu.css('margin-right', $menu.outerWidth() / -2);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($menu.hasClass('pull-middle')) {
|
||||||
|
$menu.css('margin-top', ($menu.outerHeight() / -2) - ($control.outerHeight() / 2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeOpened(event, menuTree) {
|
||||||
|
if (opened) {
|
||||||
|
|
||||||
|
if (!menuTree) {
|
||||||
|
menuTree = [opened];
|
||||||
|
}
|
||||||
|
|
||||||
|
var parent;
|
||||||
|
|
||||||
|
if (opened[0] !== menuTree[0][0]) {
|
||||||
|
parent = opened;
|
||||||
|
} else {
|
||||||
|
parent = menuTree[menuTree.length - 1];
|
||||||
|
if (parent.parent().hasClass(menuClass)) {
|
||||||
|
parent = parent.parent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
parent.find('.' + openClass).removeClass(openClass);
|
||||||
|
|
||||||
|
if (parent.hasClass(openClass))
|
||||||
|
parent.removeClass(openClass);
|
||||||
|
|
||||||
|
if (parent === opened) {
|
||||||
|
opened = null;
|
||||||
|
$(backdrop).remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSubMenuParents($submenu) {
|
||||||
|
var result = [$submenu];
|
||||||
|
var $parent;
|
||||||
|
while (!$parent || $parent.hasClass(subMenuClass)) {
|
||||||
|
$parent = ($parent || $submenu).parent();
|
||||||
|
if ($parent.hasClass(menuClass)) {
|
||||||
|
$parent = $parent.parent();
|
||||||
|
}
|
||||||
|
if ($parent.children(toggle)) {
|
||||||
|
result.unshift($parent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getParent($this) {
|
||||||
|
var selector = $this.attr('data-target');
|
||||||
|
|
||||||
|
if (!selector) {
|
||||||
|
selector = $this.attr('href');
|
||||||
|
selector = selector && /#[A-Za-z]/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, ''); //strip for ie7
|
||||||
|
}
|
||||||
|
|
||||||
|
var $parent = selector && $(selector);
|
||||||
|
|
||||||
|
return $parent && $parent.length ? $parent : $this.parent()
|
||||||
|
}
|
||||||
|
|
||||||
|
// DROPDOWN PLUGIN DEFINITION
|
||||||
|
// ==========================
|
||||||
|
|
||||||
|
var old = $.fn.dropdown;
|
||||||
|
|
||||||
|
$.fn.dropdown = function (option) {
|
||||||
|
return this.each(function () {
|
||||||
|
var $this = $(this);
|
||||||
|
var data = $this.data('bs.dropdown');
|
||||||
|
|
||||||
|
if (!data) $this.data('bs.dropdown', (data = new Dropdown(this)));
|
||||||
|
if (typeof option == 'string') data[option].call($this);
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
$.fn.dropdown.Constructor = Dropdown;
|
||||||
|
|
||||||
|
$.fn.dropdown.clearMenus = function(e) {
|
||||||
|
$(backdrop).remove();
|
||||||
|
$('.' + openClass + ' ' + toggle).each(function () {
|
||||||
|
var $parent = getParent($(this));
|
||||||
|
var relatedTarget = { relatedTarget: this };
|
||||||
|
if (!$parent.hasClass('open')) return;
|
||||||
|
$parent.trigger(e = $.Event('hide' + eventNamespace, relatedTarget));
|
||||||
|
if (e.isDefaultPrevented()) return;
|
||||||
|
$parent.removeClass('open').trigger('hidden' + eventNamespace, relatedTarget);
|
||||||
|
});
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// DROPDOWN NO CONFLICT
|
||||||
|
// ====================
|
||||||
|
|
||||||
|
$.fn.dropdown.noConflict = function () {
|
||||||
|
$.fn.dropdown = old;
|
||||||
|
return this
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
$(document).off(namespace)
|
||||||
|
.on('click' + namespace, closeOpened)
|
||||||
|
.on('click' + namespace, toggle, proto.toggle)
|
||||||
|
.on('click' + namespace, '.dropdown-menu > li > input[type="checkbox"] ~ label, .dropdown-menu > li > input[type="checkbox"], .dropdown-menu.noclose > li', function (e) {
|
||||||
|
e.stopPropagation()
|
||||||
|
})
|
||||||
|
.on('change' + namespace, '.dropdown-menu > li > input[type="checkbox"], .dropdown-menu > li > input[type="radio"]', proto.change)
|
||||||
|
.on('keydown' + namespace, toggle + ', [role="menu"], [role="listbox"]', proto.keydown)
|
||||||
|
}(jQuery));
|
|
@ -14,7 +14,7 @@
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row" style="margin-top: 10px" ng-if="!application.redirect_uri">
|
<div class="row" style="padding: 14px; padding-top: 0px; padding-bottom: 0px;" ng-if="!application.redirect_uri">
|
||||||
<div class="co-alert co-alert-warning">
|
<div class="co-alert co-alert-warning">
|
||||||
Warning: There is no OAuth Redirect setup for this application. Please enter it in the <strong>Settings</strong> tab.
|
Warning: There is no OAuth Redirect setup for this application. Please enter it in the <strong>Settings</strong> tab.
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -90,9 +90,9 @@
|
||||||
<!-- Payment -->
|
<!-- Payment -->
|
||||||
<div class="required-plan" ng-show="repo.is_public == '0' && planRequired && planRequired.title">
|
<div class="required-plan" ng-show="repo.is_public == '0' && planRequired && planRequired.title">
|
||||||
<div class="co-alert co-alert-warning">
|
<div class="co-alert co-alert-warning">
|
||||||
In order to make this repository private
|
In order to make this repository private under
|
||||||
<span ng-if="isUserNamespace">under your personal namespace</span>
|
<strong ng-if="isUserNamespace">your personal namespace</strong>
|
||||||
<span ng-if="!isUserNamespace">under the organization <b>{{ repo.namespace }}</b></span>, you will need to upgrade your plan to
|
<strong ng-if="!isUserNamespace">organization <b>{{ repo.namespace }}</b></strong>, you will need to upgrade your plan to
|
||||||
<b style="border-bottom: 1px dotted black;" data-html="true"
|
<b style="border-bottom: 1px dotted black;" data-html="true"
|
||||||
data-title="{{ '<b>' + planRequired.title + '</b><br>' + planRequired.privateRepos + ' private repositories' }}" bs-tooltip>
|
data-title="{{ '<b>' + planRequired.title + '</b><br>' + planRequired.privateRepos + ' private repositories' }}" bs-tooltip>
|
||||||
{{ planRequired.title }}
|
{{ planRequired.title }}
|
||||||
|
@ -102,10 +102,10 @@
|
||||||
<a class="btn btn-primary" ng-click="upgradePlan()" ng-show="!planChanging">Upgrade now</a>
|
<a class="btn btn-primary" ng-click="upgradePlan()" ng-show="!planChanging">Upgrade now</a>
|
||||||
<span ng-if="isUserNamespace && user.organizations.length == 1" style="margin-left: 6px; display: inline-block;">or did you mean to create this repository
|
<span ng-if="isUserNamespace && user.organizations.length == 1" style="margin-left: 6px; display: inline-block;">or did you mean to create this repository
|
||||||
under <a href="javascript:void(0)" ng-click="changeNamespace(user.organizations[0].name)"><b>{{ user.organizations[0].name }}</b></a>?</span>
|
under <a href="javascript:void(0)" ng-click="changeNamespace(user.organizations[0].name)"><b>{{ user.organizations[0].name }}</b></a>?</span>
|
||||||
<div class="quay-spinner" ng-show="planChanging"></div>
|
<div class="cor-loader-inline" ng-show="planChanging"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="quay-spinner" ng-show="repo.is_public == '0' && checkingPlan"></div>
|
<div class="cor-loader-inline" ng-show="repo.is_public == '0' && checkingPlan"></div>
|
||||||
|
|
||||||
<div class="required-plan" ng-show="repo.is_public == '0' && planRequired && !isUserNamespace && !planRequired.title">
|
<div class="required-plan" ng-show="repo.is_public == '0' && planRequired && !isUserNamespace && !planRequired.title">
|
||||||
<div class="co-alert co-alert-warning">
|
<div class="co-alert co-alert-warning">
|
||||||
|
|
|
@ -8,6 +8,12 @@
|
||||||
<span class="avatar" size="32" data="organization.avatar"></span>
|
<span class="avatar" size="32" data="organization.avatar"></span>
|
||||||
<span class="organization-name">{{ organization.name }}</span>
|
<span class="organization-name">{{ organization.name }}</span>
|
||||||
</span>
|
</span>
|
||||||
|
<span class="cor-title-action" ng-if="isMember">
|
||||||
|
<a href="/new/?namespace={{ organization.name }}">
|
||||||
|
<i class="fa fa-plus" data-title="Create new repository"></i>
|
||||||
|
Create New Repository
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="co-main-content-panel" ng-if="user.anonymous || !isMember">
|
<div class="co-main-content-panel" ng-if="user.anonymous || !isMember">
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
<span class="cor-title-content">
|
<span class="cor-title-content">
|
||||||
<span class="repo-circle no-background hidden-xs" repo="viewScope.repository"></span>
|
<span class="repo-circle no-background hidden-xs" repo="viewScope.repository"></span>
|
||||||
{{ namespace }} / {{ name }}
|
{{ namespace }} / {{ name }}
|
||||||
<span class="repo-star" repository="viewScope.repository" ng-if="!user.anonymous"></span>
|
<span class="repo-star hidden-xs" repository="viewScope.repository" ng-if="!user.anonymous"></span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,12 @@
|
||||||
<span class="avatar" size="32" data="viewuser.avatar"></span>
|
<span class="avatar" size="32" data="viewuser.avatar"></span>
|
||||||
<span class="user-name">{{ viewuser.username }}</span>
|
<span class="user-name">{{ viewuser.username }}</span>
|
||||||
</span>
|
</span>
|
||||||
|
<span class="cor-title-action" ng-if="viewuser.is_me">
|
||||||
|
<a href="/new/?namespace={{ viewuser.username }}">
|
||||||
|
<i class="fa fa-plus" data-title="Create new repository"></i>
|
||||||
|
Create New Repository
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="co-main-content-panel user-repo-list" ng-if="!viewuser.is_me">
|
<div class="co-main-content-panel user-repo-list" ng-if="!viewuser.is_me">
|
||||||
|
|
|
@ -13,10 +13,10 @@
|
||||||
|
|
||||||
<div class="container auth-container" ng-if="!user.anonymous">
|
<div class="container auth-container" ng-if="!user.anonymous">
|
||||||
<div class="auth-header">
|
<div class="auth-header">
|
||||||
<span class="avatar" size="48" hash="'{{ application.avatar }}'"></span>
|
<span class="avatar" size="48" data="{{ application.avatar }}"></span>
|
||||||
<h2><a href="{{ application.url }}" target="_blank">{{ application.name }}</a></h2>
|
<h2><a href="{{ application.url }}" target="_blank">{{ application.name }}</a></h2>
|
||||||
<h4>
|
<h4>
|
||||||
<span class="avatar" size="24" hash="'{{ application.organization.avatar }}'"
|
<span class="avatar" size="24" data="{{ application.organization.avatar }}"
|
||||||
style="vertical-align: middle; margin-right: 4px;"></span>
|
style="vertical-align: middle; margin-right: 4px;"></span>
|
||||||
<span style="vertical-align: middle">{{ application.organization.name }}</span>
|
<span style="vertical-align: middle">{{ application.organization.name }}</span>
|
||||||
</h4>
|
</h4>
|
||||||
|
|
|
@ -308,6 +308,16 @@ class TestConvertToOrganization(ApiTestCase):
|
||||||
|
|
||||||
self.assertEqual('The admin user is not valid', json['message'])
|
self.assertEqual('The admin user is not valid', json['message'])
|
||||||
|
|
||||||
|
def test_sameadminuser_by_email(self):
|
||||||
|
self.login(READ_ACCESS_USER)
|
||||||
|
json = self.postJsonResponse(ConvertToOrganization,
|
||||||
|
data={'adminUser': 'no1@thanks.com',
|
||||||
|
'adminPassword': 'password',
|
||||||
|
'plan': 'free'},
|
||||||
|
expected_code=400)
|
||||||
|
|
||||||
|
self.assertEqual('The admin user is not valid', json['message'])
|
||||||
|
|
||||||
def test_invalidadminuser(self):
|
def test_invalidadminuser(self):
|
||||||
self.login(READ_ACCESS_USER)
|
self.login(READ_ACCESS_USER)
|
||||||
json = self.postJsonResponse(ConvertToOrganization,
|
json = self.postJsonResponse(ConvertToOrganization,
|
||||||
|
@ -2637,7 +2647,9 @@ class TestBuildTriggers(ApiTestCase):
|
||||||
self.assertEquals({'status': 'success', 'subdir': ['sometoken', 'foo', 'bar', 'meh']}, subdir_json)
|
self.assertEquals({'status': 'success', 'subdir': ['sometoken', 'foo', 'bar', 'meh']}, subdir_json)
|
||||||
|
|
||||||
# Activate the trigger.
|
# Activate the trigger.
|
||||||
trigger_config = {}
|
trigger_config = {
|
||||||
|
'build_source': 'somesource'
|
||||||
|
}
|
||||||
activate_json = self.postJsonResponse(BuildTriggerActivate,
|
activate_json = self.postJsonResponse(BuildTriggerActivate,
|
||||||
params=dict(repository=ADMIN_ACCESS_USER + '/simple', trigger_uuid=trigger.uuid),
|
params=dict(repository=ADMIN_ACCESS_USER + '/simple', trigger_uuid=trigger.uuid),
|
||||||
data={'config': trigger_config})
|
data={'config': trigger_config})
|
||||||
|
@ -2675,7 +2687,9 @@ class TestBuildTriggers(ApiTestCase):
|
||||||
|
|
||||||
assert 'id' in start_json
|
assert 'id' in start_json
|
||||||
self.assertEquals("build-name", start_json['display_name'])
|
self.assertEquals("build-name", start_json['display_name'])
|
||||||
self.assertEquals(['bar'], start_json['job_config']['docker_tags'])
|
self.assertEquals(['bar'], start_json['tags'])
|
||||||
|
self.assertEquals('subdir', start_json['subdirectory'])
|
||||||
|
self.assertEquals('somesource', start_json['trigger']['build_source'])
|
||||||
|
|
||||||
# Verify the metadata was added.
|
# Verify the metadata was added.
|
||||||
build_obj = database.RepositoryBuild.get(database.RepositoryBuild.uuid == start_json['id'])
|
build_obj = database.RepositoryBuild.get(database.RepositoryBuild.uuid == start_json['id'])
|
||||||
|
@ -2743,7 +2757,7 @@ class TestBuildTriggers(ApiTestCase):
|
||||||
|
|
||||||
assert 'id' in start_json
|
assert 'id' in start_json
|
||||||
self.assertEquals("build-name", start_json['display_name'])
|
self.assertEquals("build-name", start_json['display_name'])
|
||||||
self.assertEquals(['bar'], start_json['job_config']['docker_tags'])
|
self.assertEquals(['bar'], start_json['tags'])
|
||||||
|
|
||||||
|
|
||||||
class TestUserAuthorizations(ApiTestCase):
|
class TestUserAuthorizations(ApiTestCase):
|
||||||
|
|
|
@ -91,6 +91,28 @@ class TestImageTree(unittest.TestCase):
|
||||||
self.assertEquals('staging', tree.tag_containing_image(result[0]))
|
self.assertEquals('staging', tree.tag_containing_image(result[0]))
|
||||||
|
|
||||||
|
|
||||||
|
def test_longest_path_simple_repo_direct_lookup(self):
|
||||||
|
repository = model.get_repository(NAMESPACE, SIMPLE_REPO)
|
||||||
|
all_images = list(model.get_repository_images(NAMESPACE, SIMPLE_REPO))
|
||||||
|
all_tags = list(model.list_repository_tags(NAMESPACE, SIMPLE_REPO))
|
||||||
|
|
||||||
|
base_image = self._get_base_image(all_images)
|
||||||
|
tag_image = all_tags[0].image
|
||||||
|
|
||||||
|
def checker(index, image):
|
||||||
|
return True
|
||||||
|
|
||||||
|
filtered_images = model.get_repository_images_without_placements(repository,
|
||||||
|
with_ancestor=base_image)
|
||||||
|
self.assertEquals(set([f.id for f in filtered_images]), set([a.id for a in all_images]))
|
||||||
|
|
||||||
|
tree = ImageTree(filtered_images, all_tags)
|
||||||
|
|
||||||
|
ancestors = tag_image.ancestors.split('/')[2:-1] # Skip the first image.
|
||||||
|
result = tree.find_longest_path(base_image.id, checker)
|
||||||
|
self.assertEquals(3, len(result))
|
||||||
|
self.assertEquals('latest', tree.tag_containing_image(result[-1]))
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
||||||
|
|
Reference in a new issue