Merge remote-tracking branch 'origin/redalert'
Conflicts: app.py
This commit is contained in:
commit
0372013f70
46 changed files with 2432 additions and 432 deletions
|
@ -89,6 +89,11 @@ nav.navbar-default .navbar-nav>li>a {
|
|||
background: rgba(66, 139, 202, 0.1);
|
||||
}
|
||||
|
||||
.notification-view-element .right-controls {
|
||||
text-align: right;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.dockerfile-path {
|
||||
margin-top: 10px;
|
||||
padding: 20px;
|
||||
|
@ -219,11 +224,12 @@ nav.navbar-default .navbar-nav>li>a {
|
|||
|
||||
.entity-search-element {
|
||||
position: relative;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.entity-search-element .entity-reference {
|
||||
position: absolute !important;
|
||||
top: 0px;
|
||||
top: 7px;
|
||||
left: 8px;
|
||||
right: 36px;
|
||||
z-index: 0;
|
||||
|
@ -244,6 +250,7 @@ nav.navbar-default .navbar-nav>li>a {
|
|||
|
||||
.entity-search-element input {
|
||||
vertical-align: middle;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.entity-search-element.persistent input {
|
||||
|
@ -253,12 +260,15 @@ nav.navbar-default .navbar-nav>li>a {
|
|||
|
||||
.entity-search-element .twitter-typeahead {
|
||||
vertical-align: middle;
|
||||
display: block !important;
|
||||
margin-right: 36px;
|
||||
}
|
||||
|
||||
.entity-search-element .dropdown {
|
||||
vertical-align: middle;
|
||||
display: inline-block;
|
||||
margin-top: 0px;
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
right: 0px;
|
||||
}
|
||||
|
||||
.dropdown-menu i.fa {
|
||||
|
@ -2508,10 +2518,6 @@ p.editable:hover i {
|
|||
text-align: right;
|
||||
}
|
||||
|
||||
.repo-admin .entity-search input {
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.repo-admin .panel {
|
||||
display: inline-block;
|
||||
width: 720px;
|
||||
|
@ -3023,7 +3029,6 @@ p.editable:hover i {
|
|||
|
||||
.team-view .entity-search {
|
||||
margin-top: 10px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.team-view .delete-ui {
|
||||
|
@ -3889,6 +3894,11 @@ pre.command:before {
|
|||
display: none;
|
||||
}
|
||||
|
||||
.dropdown-select input.form-control[readonly] {
|
||||
cursor: pointer;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.dropdown-select .lookahead-input {
|
||||
padding-left: 32px;
|
||||
}
|
||||
|
@ -4429,3 +4439,46 @@ have a fixed width and height (but it's not required).
|
|||
float: right;
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
i.quay-icon {
|
||||
background-image: url(/static/img/favicon.ico);
|
||||
background-size: 16px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.external-notification-view-element {
|
||||
margin: 10px;
|
||||
padding: 6px;
|
||||
border: 1px solid #eee;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.external-notification-view-element i.fa {
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
.external-notification-view-element .view-row {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.external-notification-view-element .view-row:last-child {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
.external-notification-view-element .flow-text {
|
||||
display: inline-block;
|
||||
color: #aaa;
|
||||
text-transform: lowercase;
|
||||
font-variant: small-caps;
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
.external-notification-view-element .side-controls button {
|
||||
border: 1px solid transparent;
|
||||
transition: all 300ms ease-in-out;
|
||||
}
|
||||
|
||||
.external-notification-view-element:hover .side-controls button {
|
||||
border: 1px solid #eee;
|
||||
}
|
129
static/directives/create-external-notification-dialog.html
Normal file
129
static/directives/create-external-notification-dialog.html
Normal file
|
@ -0,0 +1,129 @@
|
|||
<!-- Modal message dialog -->
|
||||
<div class="modal fade" id="createNotificationModal">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<form id="createForm" name="createForm" ng-submit="createNotification()">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true" ng-disabled="creating">×</button>
|
||||
<h4 class="modal-title">
|
||||
Create Repository Notification
|
||||
</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<!-- Creating spinner -->
|
||||
<div class="quay-spinner" ng-show="status == 'creating' || status == 'authorizing-email'"></div>
|
||||
|
||||
<!-- Authorize e-mail view -->
|
||||
<div ng-show="status == 'authorizing-email-sent'">
|
||||
An e-mail has been sent to <code>{{ currentConfig.email }}</code>. Please click the link contained
|
||||
in the e-mail.
|
||||
<br><br>
|
||||
Waiting... <span class="quay-spinner"></span>
|
||||
</div>
|
||||
|
||||
<!-- Authorize e-mail view -->
|
||||
<div ng-show="status == 'unauthorized-email'">
|
||||
The e-mail address <code>{{ currentConfig.email }}</code> has not been authorized to receive
|
||||
notifications from this repository. Please click "Send Authorization E-mail" below to start
|
||||
the authorization process.
|
||||
</div>
|
||||
|
||||
<!-- Create View -->
|
||||
<table style="width: 100%" ng-show="status == ''">
|
||||
<tr>
|
||||
<td style="width: 120px">When this occurs:</td>
|
||||
<td>
|
||||
<div class="dropdown-select" placeholder="'(Notification Event)'" selected-item="currentEvent.title"
|
||||
handle-item-selected="handleEventSelected(datum)" clear-value="clearCounter">
|
||||
<!-- Icons -->
|
||||
<i class="dropdown-select-icon fa fa-lg" ng-class="currentEvent.icon"></i>
|
||||
|
||||
<!-- Dropdown menu -->
|
||||
<ul class="dropdown-select-menu pull-right" role="menu">
|
||||
<li ng-repeat="event in events">
|
||||
<a href="javascript:void(0)" ng-click="setEvent(event)">
|
||||
<i class="fa fa-lg" ng-class="event.icon"></i> {{ event.title }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>Then issue a:</td>
|
||||
<td>
|
||||
<div class="dropdown-select" placeholder="'(Notification Action)'" selected-item="currentMethod.title"
|
||||
handle-item-selected="handleMethodSelected(datum)" clear-value="clearCounter">
|
||||
<!-- Icons -->
|
||||
<i class="dropdown-select-icon fa fa-lg" ng-class="currentMethod.icon"></i>
|
||||
|
||||
<!-- Dropdown menu -->
|
||||
<ul class="dropdown-select-menu pull-right" role="menu">
|
||||
<li ng-repeat="method in methods">
|
||||
<a href="javascript:void(0)" ng-click="setMethod(method)">
|
||||
<i class="fa fa-lg" ng-class="method.icon"></i> {{ method.title }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr ng-if="currentMethod.fields.length"><td colspan="2"><hr></td></tr>
|
||||
|
||||
<tr ng-repeat="field in currentMethod.fields">
|
||||
<td>{{ field.title }}:</td>
|
||||
<td>
|
||||
<div ng-switch on="field.type">
|
||||
<span ng-switch-when="email">
|
||||
<input type="email" class="form-control" ng-model="currentConfig[field.name]" required>
|
||||
</span>
|
||||
<input type="url" class="form-control" ng-model="currentConfig[field.name]" ng-switch-when="url" required>
|
||||
<input type="text" class="form-control" ng-model="currentConfig[field.name]" ng-switch-when="string" required>
|
||||
<div class="entity-search" namespace="repository.namespace"
|
||||
placeholder="''"
|
||||
current-entity="currentConfig[field.name]"
|
||||
ng-model="currentConfig[field.name]"
|
||||
allowed-entities="['user', 'team', 'org']"
|
||||
ng-switch-when="entity">
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr ng-if="currentMethod.id == 'webhook'">
|
||||
<td colspan="2">
|
||||
<div class="alert alert-info" style="margin-top: 20px; margin-bottom: 0px">
|
||||
JSON metadata representing the event will be <b>POST</b>ed to the URL.
|
||||
<br><br>
|
||||
The contents for each event can be found in the user guide:
|
||||
<a href="http://docs.quay.io/guides/notifications.html#webhook{{ currentEvent.id ? '_' + currentEvent.id : '' }}"
|
||||
target="_blank">
|
||||
http://docs.quay.io/guides/notifications.html
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Auth e-mail button bar -->
|
||||
<div class="modal-footer" ng-if="status == 'unauthorized-email'">
|
||||
<button type="button" class="btn btn-success" ng-click="sendAuthEmail()">
|
||||
Send Authorization E-mail
|
||||
</button>
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal" ng-disabled="creating">Cancel</button>
|
||||
</div>
|
||||
|
||||
<!-- Normal button bar -->
|
||||
<div class="modal-footer" ng-if="status == '' || status == 'creating'">
|
||||
<button type="submit" class="btn btn-primary"
|
||||
ng-disabled="createForm.$invalid || !currentMethod.id || !currentEvent.id || creating">
|
||||
Create Notification
|
||||
</button>
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal" ng-disabled="creating">Cancel</button>
|
||||
</div>
|
||||
</form>
|
||||
</div><!-- /.modal-content -->
|
||||
</div><!-- /.modal-dialog -->
|
||||
</div><!-- /.modal -->
|
|
@ -1,7 +1,8 @@
|
|||
<div class="dropdown-select-element" ng-class="selectedItem ? 'has-item' : ''">
|
||||
<div class="current-item">
|
||||
<div class="dropdown-select-icon-transclude"></div>
|
||||
<input type="text" class="lookahead-input form-control" placeholder="{{ placeholder }}"></input>
|
||||
<input type="text" class="lookahead-input form-control" placeholder="{{ placeholder }}"
|
||||
ng-readonly="!lookaheadItems || !lookaheadItems.length"></input>
|
||||
</div>
|
||||
<div class="dropdown">
|
||||
<button class="btn btn-default dropdown-toggle" type="button" data-toggle="dropdown">
|
||||
|
|
|
@ -6,7 +6,14 @@
|
|||
<span ng-if="getIsAdmin(namespace)"><a href="/organization/{{ namespace }}/teams/{{ entity.name }}">{{entity.name}}</a></span>
|
||||
</span>
|
||||
</span>
|
||||
<span ng-if="entity.kind != 'team'">
|
||||
<span ng-if="entity.kind == 'org'">
|
||||
<img src="//www.gravatar.com/avatar/{{ entity.gravatar }}?s=16&d=identicon">
|
||||
<span class="entity-name">
|
||||
<span ng-if="!getIsAdmin(entity.name)">{{entity.name}}</span>
|
||||
<span ng-if="getIsAdmin(entity.name)"><a href="/organization/{{ entity.name }}">{{entity.name}}</a></span>
|
||||
</span>
|
||||
</span>
|
||||
<span ng-if="entity.kind != 'team' && entity.kind != 'org'">
|
||||
<i class="fa fa-user" ng-show="!entity.is_robot" data-title="User" bs-tooltip="tooltip.title" data-container="body"></i>
|
||||
<i class="fa fa-wrench" ng-show="entity.is_robot" data-title="Robot Account" bs-tooltip="tooltip.title" data-container="body"></i>
|
||||
<span class="entity-name" ng-if="entity.is_robot">
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<span class="entity-search-element" ng-class="isPersistent ? 'persistent' : ''"><input class="entity-search-control form-control">
|
||||
<span class="entity-reference block-reference" ng-show="isPersistent && currentEntityInternal" entity="currentEntityInternal"></span>
|
||||
<span class="entity-search-element" ng-class="autoClear ? '' : 'persistent'"><input class="entity-search-control form-control">
|
||||
<span class="entity-reference block-reference" ng-show="!autoClear && currentEntityInternal" entity="currentEntityInternal"></span>
|
||||
<div class="dropdown">
|
||||
<button class="btn btn-default dropdown-toggle" type="button" id="entityDropdownMenu" data-toggle="dropdown"
|
||||
ng-click="lazyLoad()">
|
||||
|
@ -11,6 +11,29 @@
|
|||
<li role="presentation" class="dropdown-header" ng-show="!lazyLoading && !robots && !isAdmin && !teams">
|
||||
You do not have permission to manage teams and robots for this organization
|
||||
</li>
|
||||
|
||||
<li role="presentation" class="dropdown-header"
|
||||
ng-show="!lazyLoading && !teams.length && !robots.length && !((includeTeams && isOrganization && isAdmin) || (includeRobots && isAdmin))">
|
||||
<span ng-if="includeRobots && includeTeams && isOrganization">
|
||||
No robot accounts or teams found
|
||||
</span>
|
||||
<span ng-if="!includeRobots && includeTeams && isOrganization">
|
||||
No teams found
|
||||
</span>
|
||||
<span ng-if="includeRobots && !includeTeams && isOrganization">
|
||||
No robot accounts found
|
||||
</span>
|
||||
<span ng-if="!includeRobots && !includeTeams && isOrganization">
|
||||
Robot accounts and teams are not permitted
|
||||
</span>
|
||||
|
||||
<span ng-if="includeRobots && !isOrganization">
|
||||
No robot accounts found
|
||||
</span>
|
||||
<span ng-if="!includeRobots && !isOrganization">
|
||||
Robot accounts are not permitted
|
||||
</span>
|
||||
</li>
|
||||
|
||||
<li role="presentation" ng-repeat="team in teams" ng-show="!lazyLoading"
|
||||
ng-click="setEntity(team.name, 'team', false)">
|
||||
|
@ -34,7 +57,7 @@
|
|||
<i class="fa fa-group"></i> Create team
|
||||
</a>
|
||||
</li>
|
||||
<li role="presentation" ng-show="!lazyLoading && isAdmin">
|
||||
<li role="presentation" ng-show="includeRobots && !lazyLoading && isAdmin">
|
||||
<a role="menuitem" class="new-action" tabindex="-1" href="javascript:void(0)" ng-click="createRobot()">
|
||||
<i class="fa fa-wrench"></i>
|
||||
Create robot account
|
||||
|
|
62
static/directives/external-notification-view.html
Normal file
62
static/directives/external-notification-view.html
Normal file
|
@ -0,0 +1,62 @@
|
|||
<div class="external-notification-view-element">
|
||||
<div class="side-controls">
|
||||
<div class="dropdown" style="display: inline-block">
|
||||
<button class="btn btn-default dropdown-toggle" data-toggle="dropdown">
|
||||
<i class="fa fa-cog"></i>
|
||||
<b class="caret"></b>
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-right pull-right">
|
||||
<li ng-if="methodInfo.id == 'webhook'">
|
||||
<a href="http://docs.quay.io/guides/notifications.html#webhook_{{ eventInfo.id }}"
|
||||
target="_blank">
|
||||
<i class="fa fa-book"></i>
|
||||
Webhook Documentation</a>
|
||||
</li>
|
||||
<li class="divider" ng-if="methodInfo.id == 'webhook'"></li>
|
||||
<li><a href="javascript:void(0)" ng-click="testNotification()">
|
||||
<i class="fa fa-send"></i>
|
||||
Issue Test Notification</a>
|
||||
</li>
|
||||
<li><a href="javascript:void(0)" ng-click="deleteNotification()">
|
||||
<i class="fa fa-times"></i>
|
||||
Delete</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="view-row">
|
||||
<span class="flow-text">On</span>
|
||||
<span class="notification-event">
|
||||
<i class="fa fa-lg" ng-class="eventInfo.icon"></i>
|
||||
{{ eventInfo.title }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="view-row">
|
||||
<span class="flow-text">Issue A</span>
|
||||
<span class="notification-method">
|
||||
<i class="fa fa-lg" ng-class="methodInfo.icon"></i>
|
||||
{{ methodInfo.title }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="view-row">
|
||||
<span ng-switch on="methodInfo.id">
|
||||
<span ng-switch-when="email">
|
||||
<span class="flow-text">To</span>
|
||||
<code>{{ config.email }}</code>
|
||||
</span>
|
||||
|
||||
<span ng-switch-when="webhook">
|
||||
<span class="flow-text">To</span>
|
||||
<code>{{ config.url }}</code>
|
||||
</span>
|
||||
|
||||
<span ng-switch-when="quay_notification">
|
||||
<span class="flow-text">To</span>
|
||||
<span class="entity-reference" entity="config.target" namespace="repository.namespace"></span>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
|
@ -6,6 +6,11 @@
|
|||
<img src="//www.gravatar.com/avatar/{{ getGravatar(notification.organization) }}?s=24&d=identicon" />
|
||||
<span class="orgname">{{ notification.organization }}</span>
|
||||
</div>
|
||||
<div class="datetime">{{ parseDate(notification.created) | date:'medium'}}</div>
|
||||
</div>
|
||||
<div class="datetime">{{ parseDate(notification.created) | date:'medium'}}</div>
|
||||
<div class="right-controls">
|
||||
<a href="javascript:void(0)" ng-if="canDismiss(notification)" ng-click="dismissNotification(notification)">
|
||||
Dismiss Notification
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -83,9 +83,11 @@
|
|||
<tr ng-show="!newForWholeOrg">
|
||||
<td>Repository Creator:</td>
|
||||
<td>
|
||||
<span class="entity-search" namespace="organization.name" input-title="'User/Robot'"
|
||||
is-organization="true" include-teams="false" current-entity="activatingForNew" is-persistent="true"
|
||||
clear-now="clearCounter">
|
||||
<span class="entity-search" namespace="organization.name"
|
||||
placeholder="'User/Robot'"
|
||||
allowed-entities="['user', 'robot']"
|
||||
current-entity="activatingForNew"
|
||||
clear-value="clearCounter">
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -98,9 +100,9 @@
|
|||
<tr>
|
||||
<td>Applied To:</td>
|
||||
<td>
|
||||
<span class="entity-search" namespace="organization.name" input-title="'User/Robot/Team'"
|
||||
is-organization="true" include-teams="true" current-entity="delegateForNew" is-persistent="true"
|
||||
clear-now="clearCounter">
|
||||
<span class="entity-search" namespace="organization.name" placeholder="'User/Robot/Team'"
|
||||
current-entity="delegateForNew"
|
||||
clear-value="clearCounter">
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
|
@ -71,12 +71,10 @@
|
|||
<td>
|
||||
</td>
|
||||
<td>
|
||||
<div class="entity-search" namespace="repository.namespace" include-teams="false"
|
||||
input-title="'Select robot account for pulling...'"
|
||||
is-organization="repository.is_organization"
|
||||
is-persistent="true"
|
||||
<div class="entity-search" namespace="repository.namespace"
|
||||
placeholder="'Select robot account for pulling...'"
|
||||
current-entity="pullEntity"
|
||||
filter="['robot']"></div>
|
||||
allowed-entities="['robot']"></div>
|
||||
|
||||
<div class="alert alert-info" ng-if="pullRequirements.robots.length" style="margin-top: 20px; margin-bottom: 0px;">
|
||||
Note: We've automatically selected robot account <span class="entity-reference" entity="pullRequirements.robots[0]"></span>, since it has access to the Quay.io repository.
|
||||
|
|
710
static/js/app.js
710
static/js/app.js
|
@ -969,6 +969,106 @@ quayApp = angular.module('quay', quayDependencies, function($provide, cfpLoading
|
|||
return userService;
|
||||
}]);
|
||||
|
||||
$provide.factory('ExternalNotificationData', [function() {
|
||||
var externalNotificationData = {};
|
||||
|
||||
var events = [
|
||||
{
|
||||
'id': 'repo_push',
|
||||
'title': 'Push to Repository',
|
||||
'icon': 'fa-upload'
|
||||
},
|
||||
{
|
||||
'id': 'build_queued',
|
||||
'title': 'Dockerfile Build Queued',
|
||||
'icon': 'fa-tasks'
|
||||
},
|
||||
{
|
||||
'id': 'build_start',
|
||||
'title': 'Dockerfile Build Started',
|
||||
'icon': 'fa-circle-o-notch'
|
||||
},
|
||||
{
|
||||
'id': 'build_success',
|
||||
'title': 'Dockerfile Build Successfully Completed',
|
||||
'icon': 'fa-check-circle-o'
|
||||
},
|
||||
{
|
||||
'id': 'build_failure',
|
||||
'title': 'Dockerfile Build Failed',
|
||||
'icon': 'fa-times-circle-o'
|
||||
}
|
||||
];
|
||||
|
||||
var methods = [
|
||||
{
|
||||
'id': 'quay_notification',
|
||||
'title': 'Quay.io Notification',
|
||||
'icon': 'quay-icon',
|
||||
'fields': [
|
||||
{
|
||||
'name': 'target',
|
||||
'type': 'entity',
|
||||
'title': 'Recipient'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'id': 'email',
|
||||
'title': 'E-mail',
|
||||
'icon': 'fa-envelope',
|
||||
'fields': [
|
||||
{
|
||||
'name': 'email',
|
||||
'type': 'email',
|
||||
'title': 'E-mail address'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'id': 'webhook',
|
||||
'title': 'Webhook POST',
|
||||
'icon': 'fa-link',
|
||||
'fields': [
|
||||
{
|
||||
'name': 'url',
|
||||
'type': 'url',
|
||||
'title': 'Webhook URL'
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
var methodMap = {};
|
||||
var eventMap = {};
|
||||
|
||||
for (var i = 0; i < methods.length; ++i) {
|
||||
methodMap[methods[i].id] = methods[i];
|
||||
}
|
||||
|
||||
for (var i = 0; i < events.length; ++i) {
|
||||
eventMap[events[i].id] = events[i];
|
||||
}
|
||||
|
||||
externalNotificationData.getSupportedEvents = function() {
|
||||
return events;
|
||||
};
|
||||
|
||||
externalNotificationData.getSupportedMethods = function() {
|
||||
return methods;
|
||||
};
|
||||
|
||||
externalNotificationData.getEventInfo = function(event) {
|
||||
return eventMap[event];
|
||||
};
|
||||
|
||||
externalNotificationData.getMethodInfo = function(method) {
|
||||
return methodMap[method];
|
||||
};
|
||||
|
||||
return externalNotificationData;
|
||||
}]);
|
||||
|
||||
$provide.factory('NotificationService', ['$rootScope', '$interval', 'UserService', 'ApiService', 'StringBuilderService', 'PlanService', 'UserService', 'Config',
|
||||
function($rootScope, $interval, UserService, ApiService, StringBuilderService, PlanService, UserService, Config) {
|
||||
var notificationService = {
|
||||
|
@ -984,7 +1084,8 @@ quayApp = angular.module('quay', quayDependencies, function($provide, cfpLoading
|
|||
'test_notification': {
|
||||
'level': 'primary',
|
||||
'message': 'This notification is a long message for testing',
|
||||
'page': '/about/'
|
||||
'page': '/about/',
|
||||
'dismissable': true
|
||||
},
|
||||
'password_required': {
|
||||
'level': 'error',
|
||||
|
@ -1015,9 +1116,59 @@ quayApp = angular.module('quay', quayDependencies, function($provide, cfpLoading
|
|||
'message': 'We will be down for schedule maintenance from {from_date} to {to_date} ' +
|
||||
'for {reason}. We are sorry about any inconvenience.',
|
||||
'page': 'http://status.quay.io/'
|
||||
},
|
||||
'repo_push': {
|
||||
'level': 'info',
|
||||
'message': 'Repository {repository} has been pushed with the following tags updated: {updated_tags}',
|
||||
'page': function(metadata) {
|
||||
return '/repository/' + metadata.repository;
|
||||
},
|
||||
'dismissable': true
|
||||
},
|
||||
'build_queued': {
|
||||
'level': 'info',
|
||||
'message': 'A build has been queued for repository {repository}',
|
||||
'page': function(metadata) {
|
||||
return '/repository/' + metadata.repository + '/build?current=' + metadata.build_id;
|
||||
},
|
||||
'dismissable': true
|
||||
},
|
||||
'build_start': {
|
||||
'level': 'info',
|
||||
'message': 'A build has been started for repository {repository}',
|
||||
'page': function(metadata) {
|
||||
return '/repository/' + metadata.repository + '/build?current=' + metadata.build_id;
|
||||
},
|
||||
'dismissable': true
|
||||
},
|
||||
'build_failure': {
|
||||
'level': 'error',
|
||||
'message': 'A build has failed for repository {repository}',
|
||||
'page': function(metadata) {
|
||||
return '/repository/' + metadata.repository + '/build?current=' + metadata.build_id;
|
||||
},
|
||||
'dismissable': true
|
||||
}
|
||||
};
|
||||
|
||||
notificationService.dismissNotification = function(notification) {
|
||||
notification.dismissed = true;
|
||||
var params = {
|
||||
'uuid': notification.id
|
||||
};
|
||||
|
||||
ApiService.updateUserNotification(notification, params);
|
||||
|
||||
var index = $.inArray(notification, notificationService.notifications);
|
||||
if (index >= 0) {
|
||||
notificationService.notifications.splice(index, 1);
|
||||
}
|
||||
};
|
||||
|
||||
notificationService.canDismiss = function(notification) {
|
||||
return !!notificationKinds[notification['kind']]['dismissable'];
|
||||
};
|
||||
|
||||
notificationService.getPage = function(notification) {
|
||||
var page = notificationKinds[notification['kind']]['page'];
|
||||
if (typeof page != 'string') {
|
||||
|
@ -2274,7 +2425,8 @@ quayApp.directive('logsView', function () {
|
|||
'repository': '=repository',
|
||||
'performer': '=performer'
|
||||
},
|
||||
controller: function($scope, $element, $sce, Restangular, ApiService, TriggerDescriptionBuilder, StringBuilderService) {
|
||||
controller: function($scope, $element, $sce, Restangular, ApiService, TriggerDescriptionBuilder,
|
||||
StringBuilderService, ExternalNotificationData) {
|
||||
$scope.loading = true;
|
||||
$scope.logs = null;
|
||||
$scope.kindsAllowed = null;
|
||||
|
@ -2335,8 +2487,6 @@ quayApp.directive('logsView', function () {
|
|||
'change_repo_visibility': 'Change visibility for repository {repo} to {visibility}',
|
||||
'add_repo_accesstoken': 'Create access token {token} in repository {repo}',
|
||||
'delete_repo_accesstoken': 'Delete access token {token} in repository {repo}',
|
||||
'add_repo_webhook': 'Add webhook in repository {repo}',
|
||||
'delete_repo_webhook': 'Delete webhook in repository {repo}',
|
||||
'set_repo_description': 'Change description for repository {repo}: {description}',
|
||||
'build_dockerfile': function(metadata) {
|
||||
if (metadata.trigger_id) {
|
||||
|
@ -2387,7 +2537,21 @@ quayApp.directive('logsView', function () {
|
|||
'update_application': 'Update application to {application_name} for client ID {client_id}',
|
||||
'delete_application': 'Delete application {application_name} with client ID {client_id}',
|
||||
'reset_application_client_secret': 'Reset the Client Secret of application {application_name} ' +
|
||||
'with client ID {client_id}'
|
||||
'with client ID {client_id}',
|
||||
|
||||
'add_repo_notification': function(metadata) {
|
||||
var eventData = ExternalNotificationData.getEventInfo(metadata.event);
|
||||
return 'Add notification of event "' + eventData['title'] + '" for repository {repo}';
|
||||
},
|
||||
|
||||
'delete_repo_notification': function(metadata) {
|
||||
var eventData = ExternalNotificationData.getEventInfo(metadata.event);
|
||||
return 'Delete notification of event "' + eventData['title'] + '" for repository {repo}';
|
||||
},
|
||||
|
||||
// Note: These are deprecated.
|
||||
'add_repo_webhook': 'Add webhook in repository {repo}',
|
||||
'delete_repo_webhook': 'Delete webhook in repository {repo}'
|
||||
};
|
||||
|
||||
var logKinds = {
|
||||
|
@ -2406,8 +2570,6 @@ quayApp.directive('logsView', function () {
|
|||
'change_repo_visibility': 'Change repository visibility',
|
||||
'add_repo_accesstoken': 'Create access token',
|
||||
'delete_repo_accesstoken': 'Delete access token',
|
||||
'add_repo_webhook': 'Add webhook',
|
||||
'delete_repo_webhook': 'Delete webhook',
|
||||
'set_repo_description': 'Change repository description',
|
||||
'build_dockerfile': 'Build image from Dockerfile',
|
||||
'delete_tag': 'Delete Tag',
|
||||
|
@ -2427,7 +2589,13 @@ quayApp.directive('logsView', function () {
|
|||
'create_application': 'Create Application',
|
||||
'update_application': 'Update Application',
|
||||
'delete_application': 'Delete Application',
|
||||
'reset_application_client_secret': 'Reset Client Secret'
|
||||
'reset_application_client_secret': 'Reset Client Secret',
|
||||
'add_repo_notification': 'Add repository notification',
|
||||
'delete_repo_notification': 'Delete repository notification',
|
||||
|
||||
// Note: these are deprecated.
|
||||
'add_repo_webhook': 'Add webhook',
|
||||
'delete_repo_webhook': 'Delete webhook'
|
||||
};
|
||||
|
||||
var getDateString = function(date) {
|
||||
|
@ -3127,48 +3295,73 @@ quayApp.directive('entitySearch', function () {
|
|||
replace: false,
|
||||
transclude: false,
|
||||
restrict: 'C',
|
||||
require: '?ngModel',
|
||||
link: function(scope, element, attr, ctrl) {
|
||||
scope.ngModel = ctrl;
|
||||
},
|
||||
scope: {
|
||||
'namespace': '=namespace',
|
||||
'inputTitle': '=inputTitle',
|
||||
'entitySelected': '=entitySelected',
|
||||
'includeTeams': '=includeTeams',
|
||||
'isOrganization': '=isOrganization',
|
||||
'isPersistent': '=isPersistent',
|
||||
'placeholder': '=placeholder',
|
||||
|
||||
// Default: ['user', 'team', 'robot']
|
||||
'allowedEntities': '=allowedEntities',
|
||||
|
||||
'currentEntity': '=currentEntity',
|
||||
'clearNow': '=clearNow',
|
||||
'filter': '=filter',
|
||||
'entitySelected': '&entitySelected',
|
||||
|
||||
// When set to true, the contents of the control will be cleared as soon
|
||||
// as an entity is selected.
|
||||
'autoClear': '=autoClear',
|
||||
|
||||
// Set this property to immediately clear the contents of the control.
|
||||
'clearValue': '=clearValue',
|
||||
},
|
||||
controller: function($scope, $element, Restangular, UserService, ApiService) {
|
||||
controller: function($rootScope, $scope, $element, Restangular, UserService, ApiService) {
|
||||
$scope.lazyLoading = true;
|
||||
|
||||
$scope.teams = null;
|
||||
$scope.robots = null;
|
||||
|
||||
$scope.isAdmin = false;
|
||||
$scope.isOrganization = false;
|
||||
|
||||
$scope.includeTeams = true;
|
||||
$scope.includeRobots = true;
|
||||
$scope.includeOrgs = false;
|
||||
|
||||
$scope.currentEntityInternal = $scope.currentEntity;
|
||||
|
||||
var isSupported = function(kind, opt_array) {
|
||||
return $.inArray(kind, opt_array || $scope.allowedEntities || ['user', 'team', 'robot']) >= 0;
|
||||
};
|
||||
|
||||
$scope.lazyLoad = function() {
|
||||
if (!$scope.namespace || !$scope.lazyLoading) { return; }
|
||||
|
||||
// Determine whether we can admin this namespace.
|
||||
$scope.isAdmin = UserService.isNamespaceAdmin($scope.namespace);
|
||||
|
||||
// If the scope is an organization and we are not part of it, then nothing more we can do.
|
||||
if (!$scope.isAdmin && $scope.isOrganization && !UserService.getOrganization($scope.namespace)) {
|
||||
$scope.teams = null;
|
||||
$scope.robots = null;
|
||||
$scope.lazyLoading = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if ($scope.isOrganization && $scope.includeTeams) {
|
||||
// Reset the cached teams and robots.
|
||||
$scope.teams = null;
|
||||
$scope.robots = null;
|
||||
|
||||
// Load the organization's teams (if applicable).
|
||||
if ($scope.isOrganization && isSupported('team')) {
|
||||
// Note: We load the org here again so that we always have the fully up-to-date
|
||||
// teams list.
|
||||
ApiService.getOrganization(null, {'orgname': $scope.namespace}).then(function(resp) {
|
||||
$scope.teams = resp.teams;
|
||||
});
|
||||
}
|
||||
|
||||
ApiService.getRobots($scope.isOrganization ? $scope.namespace : null).then(function(resp) {
|
||||
$scope.robots = resp.robots;
|
||||
// Load the user/organization's robots (if applicable).
|
||||
if ($scope.isAdmin && isSupported('robot')) {
|
||||
ApiService.getRobots($scope.isOrganization ? $scope.namespace : null).then(function(resp) {
|
||||
$scope.robots = resp.robots;
|
||||
$scope.lazyLoading = false;
|
||||
}, function() {
|
||||
$scope.lazyLoading = false;
|
||||
});
|
||||
} else {
|
||||
$scope.lazyLoading = false;
|
||||
}, function() {
|
||||
$scope.lazyLoading = false;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
$scope.createTeam = function() {
|
||||
|
@ -3216,7 +3409,7 @@ quayApp.directive('entitySearch', function () {
|
|||
'is_robot': is_robot
|
||||
};
|
||||
|
||||
if ($scope.is_organization) {
|
||||
if ($scope.isOrganization) {
|
||||
entity['is_org_member'] = true;
|
||||
}
|
||||
|
||||
|
@ -3226,146 +3419,194 @@ quayApp.directive('entitySearch', function () {
|
|||
$scope.clearEntityInternal = function() {
|
||||
$scope.currentEntityInternal = null;
|
||||
$scope.currentEntity = null;
|
||||
|
||||
if ($scope.entitySelected) {
|
||||
$scope.entitySelected(null);
|
||||
$scope.entitySelected({'entity': null});
|
||||
if ($scope.ngModel) {
|
||||
$scope.ngModel.$setValidity('entity', false);
|
||||
}
|
||||
};
|
||||
|
||||
$scope.setEntityInternal = function(entity, updateTypeahead) {
|
||||
if (updateTypeahead) {
|
||||
$(input).typeahead('val', $scope.isPersistent ? entity.name : '');
|
||||
$(input).typeahead('val', $scope.autoClear ? '' : entity.name);
|
||||
} else {
|
||||
$(input).val($scope.isPersistent ? entity.name : '');
|
||||
$(input).val($scope.autoClear ? '' : entity.name);
|
||||
}
|
||||
|
||||
if ($scope.isPersistent) {
|
||||
if (!$scope.autoClear) {
|
||||
$scope.currentEntityInternal = entity;
|
||||
$scope.currentEntity = entity;
|
||||
}
|
||||
|
||||
if ($scope.entitySelected) {
|
||||
$scope.entitySelected(entity);
|
||||
$scope.entitySelected({'entity': entity});
|
||||
if ($scope.ngModel) {
|
||||
$scope.ngModel.$setValidity('entity', !!entity);
|
||||
}
|
||||
};
|
||||
|
||||
number++;
|
||||
// Setup the typeahead.
|
||||
var input = $element[0].firstChild.firstChild;
|
||||
|
||||
(function() {
|
||||
// Create the bloodhound search query system.
|
||||
$rootScope.__entity_search_counter = (($rootScope.__entity_search_counter || 0) + 1);
|
||||
var entitySearchB = new Bloodhound({
|
||||
name: 'entities' + $rootScope.__entity_search_counter,
|
||||
remote: {
|
||||
url: '/api/v1/entities/%QUERY',
|
||||
replace: function (url, uriEncodedQuery) {
|
||||
var namespace = $scope.namespace || '';
|
||||
url = url.replace('%QUERY', uriEncodedQuery);
|
||||
url += '?namespace=' + encodeURIComponent(namespace);
|
||||
if ($scope.isOrganization && isSupported('team')) {
|
||||
url += '&includeTeams=true'
|
||||
}
|
||||
if (isSupported('org')) {
|
||||
url += '&includeOrgs=true'
|
||||
}
|
||||
return url;
|
||||
},
|
||||
filter: function(data) {
|
||||
var datums = [];
|
||||
for (var i = 0; i < data.results.length; ++i) {
|
||||
var entity = data.results[i];
|
||||
|
||||
var entitySearchB = new Bloodhound({
|
||||
name: 'entities' + number,
|
||||
remote: {
|
||||
url: '/api/v1/entities/%QUERY',
|
||||
replace: function (url, uriEncodedQuery) {
|
||||
var namespace = $scope.namespace || '';
|
||||
url = url.replace('%QUERY', uriEncodedQuery);
|
||||
url += '?namespace=' + encodeURIComponent(namespace);
|
||||
if ($scope.includeTeams) {
|
||||
url += '&includeTeams=true'
|
||||
}
|
||||
return url;
|
||||
},
|
||||
filter: function(data) {
|
||||
var datums = [];
|
||||
for (var i = 0; i < data.results.length; ++i) {
|
||||
var entity = data.results[i];
|
||||
if ($scope.filter) {
|
||||
var allowed = $scope.filter;
|
||||
var found = 'user';
|
||||
if (entity.kind == 'user') {
|
||||
found = entity.is_robot ? 'robot' : 'user';
|
||||
} else if (entity.kind == 'team') {
|
||||
found = 'team';
|
||||
} else if (entity.kind == 'org') {
|
||||
found = 'org';
|
||||
}
|
||||
if (allowed.indexOf(found)) {
|
||||
|
||||
if (!isSupported(found)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
datums.push({
|
||||
'value': entity.name,
|
||||
'tokens': [entity.name],
|
||||
'entity': entity
|
||||
});
|
||||
}
|
||||
return datums;
|
||||
}
|
||||
},
|
||||
datumTokenizer: function(d) {
|
||||
return Bloodhound.tokenizers.whitespace(d.val);
|
||||
},
|
||||
queryTokenizer: Bloodhound.tokenizers.whitespace
|
||||
});
|
||||
entitySearchB.initialize();
|
||||
|
||||
// Setup the typeahead.
|
||||
$(input).typeahead({
|
||||
'highlight': true
|
||||
}, {
|
||||
source: entitySearchB.ttAdapter(),
|
||||
templates: {
|
||||
'empty': function(info) {
|
||||
// Only display the empty dialog if the server load has finished.
|
||||
if (info.resultKind == 'remote') {
|
||||
var val = $(input).val();
|
||||
if (!val) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (val.indexOf('@') > 0) {
|
||||
return '<div class="tt-empty">A Quay.io username (not an e-mail address) must be specified</div>';
|
||||
}
|
||||
|
||||
var classes = [];
|
||||
|
||||
if (isSupported('user')) { classes.push('users'); }
|
||||
if (isSupported('org')) { classes.push('organizations'); }
|
||||
if ($scope.isAdmin && isSupported('robot')) { classes.push('robot accounts'); }
|
||||
if ($scope.isOrganization && isSupported('team')) { classes.push('teams'); }
|
||||
|
||||
if (classes.length > 1) {
|
||||
classes[classes.length - 1] = 'or ' + classes[classes.length - 1];
|
||||
}
|
||||
|
||||
var class_string = '';
|
||||
for (var i = 0; i < classes.length; ++i) {
|
||||
if (i > 0) {
|
||||
if (i == classes.length - 1) {
|
||||
class_string += ' or ';
|
||||
} else {
|
||||
class_string += ', ';
|
||||
}
|
||||
}
|
||||
|
||||
class_string += classes[i];
|
||||
}
|
||||
|
||||
return '<div class="tt-empty">No matching Quay.io ' + class_string + ' found</div>';
|
||||
}
|
||||
|
||||
datums.push({
|
||||
'value': entity.name,
|
||||
'tokens': [entity.name],
|
||||
'entity': entity
|
||||
});
|
||||
}
|
||||
return datums;
|
||||
}
|
||||
},
|
||||
datumTokenizer: function(d) {
|
||||
return Bloodhound.tokenizers.whitespace(d.val);
|
||||
},
|
||||
queryTokenizer: Bloodhound.tokenizers.whitespace
|
||||
});
|
||||
entitySearchB.initialize();
|
||||
|
||||
var counter = 0;
|
||||
var input = $element[0].firstChild.firstChild;
|
||||
$(input).typeahead({
|
||||
'highlight': true
|
||||
}, {
|
||||
source: entitySearchB.ttAdapter(),
|
||||
templates: {
|
||||
'empty': function(info) {
|
||||
// Only display the empty dialog if the server load has finished.
|
||||
if (info.resultKind == 'remote') {
|
||||
var val = $(input).val();
|
||||
if (!val) {
|
||||
return null;
|
||||
return null;
|
||||
},
|
||||
'suggestion': function (datum) {
|
||||
template = '<div class="entity-mini-listing">';
|
||||
if (datum.entity.kind == 'user' && !datum.entity.is_robot) {
|
||||
template += '<i class="fa fa-user fa-lg"></i>';
|
||||
} else if (datum.entity.kind == 'user' && datum.entity.is_robot) {
|
||||
template += '<i class="fa fa-wrench fa-lg"></i>';
|
||||
} else if (datum.entity.kind == 'team') {
|
||||
template += '<i class="fa fa-group fa-lg"></i>';
|
||||
} else if (datum.entity.kind == 'org') {
|
||||
template += '<i class="fa"><img src="//www.gravatar.com/avatar/' +
|
||||
datum.entity.gravatar + '?s=16&d=identicon"></i>';
|
||||
}
|
||||
|
||||
if (val.indexOf('@') > 0) {
|
||||
return '<div class="tt-empty">A Quay.io username (not an e-mail address) must be specified</div>';
|
||||
template += '<span class="name">' + datum.value + '</span>';
|
||||
|
||||
if (datum.entity.is_org_member === false && datum.entity.kind == 'user') {
|
||||
template += '<i class="fa fa-exclamation-triangle" title="User is outside the organization"></i>';
|
||||
}
|
||||
|
||||
var robots = $scope.isOrganization ? ', robot accounts' : '';
|
||||
var teams = ($scope.includeTeams && $scope.isOrganization) ? ' or teams' : '';
|
||||
return '<div class="tt-empty">No matching Quay.io users' + robots + teams + ' found</div>';
|
||||
}
|
||||
template += '</div>';
|
||||
return template;
|
||||
}}
|
||||
});
|
||||
|
||||
return null;
|
||||
},
|
||||
'suggestion': function (datum) {
|
||||
template = '<div class="entity-mini-listing">';
|
||||
if (datum.entity.kind == 'user' && !datum.entity.is_robot) {
|
||||
template += '<i class="fa fa-user fa-lg"></i>';
|
||||
} else if (datum.entity.kind == 'user' && datum.entity.is_robot) {
|
||||
template += '<i class="fa fa-wrench fa-lg"></i>';
|
||||
} else if (datum.entity.kind == 'team') {
|
||||
template += '<i class="fa fa-group fa-lg"></i>';
|
||||
}
|
||||
template += '<span class="name">' + datum.value + '</span>';
|
||||
|
||||
if (datum.entity.is_org_member === false && datum.entity.kind == 'user') {
|
||||
template += '<i class="fa fa-exclamation-triangle" title="User is outside the organization"></i>';
|
||||
}
|
||||
|
||||
template += '</div>';
|
||||
return template;
|
||||
}}
|
||||
});
|
||||
|
||||
$(input).on('input', function(e) {
|
||||
$scope.$apply(function() {
|
||||
if ($scope.isPersistent) {
|
||||
$(input).on('input', function(e) {
|
||||
$scope.$apply(function() {
|
||||
$scope.clearEntityInternal();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
$(input).on('typeahead:selected', function(e, datum) {
|
||||
$scope.$apply(function() {
|
||||
$scope.setEntityInternal(datum.entity, true);
|
||||
$(input).on('typeahead:selected', function(e, datum) {
|
||||
$scope.$apply(function() {
|
||||
$scope.setEntityInternal(datum.entity, true);
|
||||
});
|
||||
});
|
||||
});
|
||||
})();
|
||||
|
||||
$scope.$watch('clearValue', function() {
|
||||
if (!input) { return; }
|
||||
|
||||
$scope.$watch('clearNow', function() {
|
||||
$(input).typeahead('val', '');
|
||||
$scope.clearEntityInternal();
|
||||
});
|
||||
|
||||
$scope.$watch('inputTitle', function(title) {
|
||||
$scope.$watch('placeholder', function(title) {
|
||||
input.setAttribute('placeholder', title);
|
||||
});
|
||||
|
||||
$scope.$watch('allowedEntities', function(allowed) {
|
||||
if (!allowed) { return; }
|
||||
$scope.includeTeams = isSupported('team', allowed);
|
||||
$scope.includeRobots = isSupported('robot', allowed);
|
||||
});
|
||||
|
||||
$scope.$watch('namespace', function(namespace) {
|
||||
if (!namespace) { return; }
|
||||
|
||||
$scope.isAdmin = UserService.isNamespaceAdmin(namespace);
|
||||
$scope.isOrganization = !!UserService.getOrganization(namespace);
|
||||
});
|
||||
|
||||
$scope.$watch('currentEntity', function(entity) {
|
||||
if ($scope.currentEntityInternal != entity) {
|
||||
if (entity) {
|
||||
|
@ -3778,7 +4019,9 @@ quayApp.directive('dropdownSelect', function ($compile) {
|
|||
'placeholder': '=placeholder',
|
||||
'lookaheadItems': '=lookaheadItems',
|
||||
'handleItemSelected': '&handleItemSelected',
|
||||
'handleInput': '&handleInput'
|
||||
'handleInput': '&handleInput',
|
||||
|
||||
'clearValue': '=clearValue'
|
||||
},
|
||||
controller: function($scope, $element, $rootScope) {
|
||||
if (!$rootScope.__dropdownSelectCounter) {
|
||||
|
@ -3791,6 +4034,13 @@ quayApp.directive('dropdownSelect', function ($compile) {
|
|||
// Setup lookahead.
|
||||
var input = $($element).find('.lookahead-input');
|
||||
|
||||
$scope.$watch('clearValue', function(cv) {
|
||||
if (cv) {
|
||||
$scope.selectedItem = null;
|
||||
$(input).val('');
|
||||
}
|
||||
});
|
||||
|
||||
$scope.$watch('selectedItem', function(item) {
|
||||
if ($scope.selectedItem == $scope.internalItem) {
|
||||
// The item has already been set due to an internal action.
|
||||
|
@ -4462,6 +4712,192 @@ quayApp.directive('buildProgress', function () {
|
|||
});
|
||||
|
||||
|
||||
quayApp.directive('externalNotificationView', function () {
|
||||
var directiveDefinitionObject = {
|
||||
priority: 0,
|
||||
templateUrl: '/static/directives/external-notification-view.html',
|
||||
replace: false,
|
||||
transclude: false,
|
||||
restrict: 'C',
|
||||
scope: {
|
||||
'repository': '=repository',
|
||||
'notification': '=notification',
|
||||
'notificationDeleted': '¬ificationDeleted'
|
||||
},
|
||||
controller: function($scope, $element, ExternalNotificationData, ApiService) {
|
||||
$scope.deleteNotification = function() {
|
||||
var params = {
|
||||
'repository': $scope.repository.namespace + '/' + $scope.repository.name,
|
||||
'uuid': $scope.notification.uuid
|
||||
};
|
||||
|
||||
ApiService.deleteRepoNotification(null, params).then(function() {
|
||||
$scope.notificationDeleted({'notification': $scope.notification});
|
||||
});
|
||||
};
|
||||
|
||||
$scope.testNotification = function() {
|
||||
var params = {
|
||||
'repository': $scope.repository.namespace + '/' + $scope.repository.name,
|
||||
'uuid': $scope.notification.uuid
|
||||
};
|
||||
|
||||
ApiService.testRepoNotification(null, params).then(function() {
|
||||
bootbox.dialog({
|
||||
"title": "Test Notification Queued",
|
||||
"message": "A test version of this notification has been queued and should appear shortly",
|
||||
"buttons": {
|
||||
"close": {
|
||||
"label": "Close",
|
||||
"className": "btn-primary"
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
$scope.$watch('notification', function(notification) {
|
||||
if (notification) {
|
||||
$scope.eventInfo = ExternalNotificationData.getEventInfo(notification.event);
|
||||
$scope.methodInfo = ExternalNotificationData.getMethodInfo(notification.method);
|
||||
$scope.config = notification.config;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
return directiveDefinitionObject;
|
||||
});
|
||||
|
||||
|
||||
quayApp.directive('createExternalNotificationDialog', function () {
|
||||
var directiveDefinitionObject = {
|
||||
priority: 0,
|
||||
templateUrl: '/static/directives/create-external-notification-dialog.html',
|
||||
replace: false,
|
||||
transclude: false,
|
||||
restrict: 'C',
|
||||
scope: {
|
||||
'repository': '=repository',
|
||||
'counter': '=counter',
|
||||
'notificationCreated': '¬ificationCreated'
|
||||
},
|
||||
controller: function($scope, $element, ExternalNotificationData, ApiService, $timeout) {
|
||||
$scope.currentEvent = null;
|
||||
$scope.currentMethod = null;
|
||||
$scope.status = '';
|
||||
$scope.currentConfig = {};
|
||||
$scope.clearCounter = 0;
|
||||
$scope.unauthorizedEmail = false;
|
||||
|
||||
$scope.events = ExternalNotificationData.getSupportedEvents();
|
||||
$scope.methods = ExternalNotificationData.getSupportedMethods();
|
||||
|
||||
$scope.setEvent = function(event) {
|
||||
$scope.currentEvent = event;
|
||||
};
|
||||
|
||||
$scope.setMethod = function(method) {
|
||||
$scope.currentConfig = {};
|
||||
$scope.currentMethod = method;
|
||||
$scope.unauthorizedEmail = false;
|
||||
};
|
||||
|
||||
$scope.createNotification = function() {
|
||||
if (!$scope.currentConfig.email) {
|
||||
$scope.performCreateNotification();
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.status = 'checking-email';
|
||||
$scope.checkEmailAuthorization();
|
||||
};
|
||||
|
||||
$scope.checkEmailAuthorization = function() {
|
||||
var params = {
|
||||
'repository': $scope.repository.namespace + '/' + $scope.repository.name,
|
||||
'email': $scope.currentConfig.email
|
||||
};
|
||||
|
||||
ApiService.checkRepoEmailAuthorized(null, params).then(function(resp) {
|
||||
$scope.handleEmailCheck(resp.confirmed);
|
||||
}, function(resp) {
|
||||
$scope.handleEmailCheck(false);
|
||||
});
|
||||
};
|
||||
|
||||
$scope.performCreateNotification = function() {
|
||||
$scope.status = 'creating';
|
||||
|
||||
var params = {
|
||||
'repository': $scope.repository.namespace + '/' + $scope.repository.name
|
||||
};
|
||||
|
||||
var data = {
|
||||
'event': $scope.currentEvent.id,
|
||||
'method': $scope.currentMethod.id,
|
||||
'config': $scope.currentConfig
|
||||
};
|
||||
|
||||
ApiService.createRepoNotification(data, params).then(function(resp) {
|
||||
$scope.status = '';
|
||||
$scope.notificationCreated({'notification': resp});
|
||||
$('#createNotificationModal').modal('hide');
|
||||
});
|
||||
};
|
||||
|
||||
$scope.handleEmailCheck = function(isAuthorized) {
|
||||
if (isAuthorized) {
|
||||
$scope.performCreateNotification();
|
||||
return;
|
||||
}
|
||||
|
||||
if ($scope.status == 'authorizing-email-sent') {
|
||||
$scope.watchEmail();
|
||||
} else {
|
||||
$scope.status = 'unauthorized-email';
|
||||
}
|
||||
|
||||
$scope.unauthorizedEmail = true;
|
||||
};
|
||||
|
||||
$scope.sendAuthEmail = function() {
|
||||
$scope.status = 'authorizing-email';
|
||||
|
||||
var params = {
|
||||
'repository': $scope.repository.namespace + '/' + $scope.repository.name,
|
||||
'email': $scope.currentConfig.email
|
||||
};
|
||||
|
||||
ApiService.sendAuthorizeRepoEmail(null, params).then(function(resp) {
|
||||
$scope.status = 'authorizing-email-sent';
|
||||
$scope.watchEmail();
|
||||
});
|
||||
};
|
||||
|
||||
$scope.watchEmail = function() {
|
||||
// TODO: change this to SSE?
|
||||
$timeout(function() {
|
||||
$scope.checkEmailAuthorization();
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
$scope.$watch('counter', function(counter) {
|
||||
if (counter) {
|
||||
$scope.clearCounter++;
|
||||
$scope.status = '';
|
||||
$scope.currentEvent = null;
|
||||
$scope.currentMethod = null;
|
||||
$scope.unauthorizedEmail = false;
|
||||
$('#createNotificationModal').modal({});
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
return directiveDefinitionObject;
|
||||
});
|
||||
|
||||
|
||||
|
||||
quayApp.directive('twitterView', function () {
|
||||
var directiveDefinitionObject = {
|
||||
priority: 0,
|
||||
|
@ -4494,7 +4930,7 @@ quayApp.directive('notificationView', function () {
|
|||
'notification': '=notification',
|
||||
'parent': '=parent'
|
||||
},
|
||||
controller: function($scope, $element, $window, $location, UserService, NotificationService) {
|
||||
controller: function($scope, $element, $window, $location, UserService, NotificationService, ApiService) {
|
||||
var stringStartsWith = function (str, prefix) {
|
||||
return str.slice(0, prefix.length) == prefix;
|
||||
};
|
||||
|
@ -4530,6 +4966,14 @@ quayApp.directive('notificationView', function () {
|
|||
}
|
||||
};
|
||||
|
||||
$scope.dismissNotification = function(notification) {
|
||||
NotificationService.dismissNotification(notification);
|
||||
};
|
||||
|
||||
$scope.canDismiss = function(notification) {
|
||||
return NotificationService.canDismiss(notification);
|
||||
};
|
||||
|
||||
$scope.getClass = function(notification) {
|
||||
return NotificationService.getClass(notification);
|
||||
};
|
||||
|
|
|
@ -181,7 +181,7 @@ function TutorialCtrl($scope, AngularTour, AngularTourSignals, UserService, Conf
|
|||
},
|
||||
{
|
||||
'title': 'Repository Admin',
|
||||
'content': "The repository admin panel allows for modification of a repository's permissions, webhooks, visibility and other settings",
|
||||
'content': "The repository admin panel allows for modification of a repository's permissions, notifications, visibility and other settings",
|
||||
'overlayable': true,
|
||||
'mixpanelEvent': 'tutorial_view_admin'
|
||||
},
|
||||
|
@ -1258,7 +1258,7 @@ function RepoBuildCtrl($scope, Restangular, ApiService, $routeParams, $rootScope
|
|||
fetchRepository();
|
||||
}
|
||||
|
||||
function RepoAdminCtrl($scope, Restangular, ApiService, KeyService, $routeParams, $rootScope, $location, UserService, Config, Features) {
|
||||
function RepoAdminCtrl($scope, Restangular, ApiService, KeyService, $routeParams, $rootScope, $location, UserService, Config, Features, ExternalNotificationData) {
|
||||
var namespace = $routeParams.namespace;
|
||||
var name = $routeParams.name;
|
||||
|
||||
|
@ -1481,43 +1481,32 @@ function RepoAdminCtrl($scope, Restangular, ApiService, KeyService, $routeParams
|
|||
});
|
||||
};
|
||||
|
||||
$scope.loadWebhooks = function() {
|
||||
$scope.showNewNotificationCounter = 0;
|
||||
|
||||
$scope.showNewNotificationDialog = function() {
|
||||
$scope.showNewNotificationCounter++;
|
||||
};
|
||||
|
||||
$scope.handleNotificationCreated = function(notification) {
|
||||
$scope.notifications.push(notification);
|
||||
};
|
||||
|
||||
$scope.handleNotificationDeleted = function(notification) {
|
||||
var index = $.inArray(notification, $scope.notifications);
|
||||
if (index < 0) { return; }
|
||||
$scope.notifications.splice(index, 1);
|
||||
};
|
||||
|
||||
$scope.loadNotifications = function() {
|
||||
var params = {
|
||||
'repository': namespace + '/' + name
|
||||
};
|
||||
|
||||
$scope.newWebhook = {};
|
||||
$scope.webhooksResource = ApiService.listWebhooksAsResource(params).get(function(resp) {
|
||||
$scope.webhooks = resp.webhooks;
|
||||
return $scope.webhooks;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.createWebhook = function() {
|
||||
if (!$scope.newWebhook.url) {
|
||||
return;
|
||||
}
|
||||
|
||||
var params = {
|
||||
'repository': namespace + '/' + name
|
||||
};
|
||||
|
||||
ApiService.createWebhook($scope.newWebhook, params).then(function(resp) {
|
||||
$scope.webhooks.push(resp);
|
||||
$scope.newWebhook.url = '';
|
||||
$scope.createWebhookForm.$setPristine();
|
||||
});
|
||||
};
|
||||
|
||||
$scope.deleteWebhook = function(webhook) {
|
||||
var params = {
|
||||
'repository': namespace + '/' + name,
|
||||
'public_id': webhook.public_id
|
||||
};
|
||||
|
||||
ApiService.deleteWebhook(null, params).then(function(resp) {
|
||||
$scope.webhooks.splice($scope.webhooks.indexOf(webhook), 1);
|
||||
});
|
||||
$scope.notificationsResource = ApiService.listRepoNotificationsAsResource(params).get(
|
||||
function(resp) {
|
||||
$scope.notifications = resp.notifications;
|
||||
return $scope.notifications;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.showBuild = function(buildInfo) {
|
||||
|
@ -1649,7 +1638,7 @@ function RepoAdminCtrl($scope, Restangular, ApiService, KeyService, $routeParams
|
|||
$scope.repo = repo;
|
||||
$rootScope.title = 'Settings - ' + namespace + '/' + name;
|
||||
$rootScope.description = 'Administrator settings for ' + namespace + '/' + name +
|
||||
': Permissions, webhooks and other settings';
|
||||
': Permissions, notifications and other settings';
|
||||
|
||||
// Fetch all the permissions and token info for the repository.
|
||||
fetchPermissions('user');
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<div class="container">
|
||||
<h3>Redirecting...</h3>
|
||||
<META http-equiv="refresh" content="0;URL=http://docs.quay.io/getting-started.html">
|
||||
If this page does not redirect, please <a href="http://docs.quay.io/getting-started.html"> click here</a>.
|
||||
<META http-equiv="refresh" content="0;URL=http://docs.quay.io/solution/getting-started.html">
|
||||
If this page does not redirect, please <a href="http://docs.quay.io/solution/getting-started.html"> click here</a>.
|
||||
</div>
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
<li class="active"><a href="javascript:void(0)" data-toggle="tab" data-target="#permissions">Permissions</a></li>
|
||||
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#trigger" ng-click="loadTriggers()">Build Triggers</a></li>
|
||||
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#badge">Status Badge</a></li>
|
||||
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#webhook" ng-click="loadWebhooks()">Webhooks</a></li>
|
||||
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#notification" ng-click="loadNotifications()">Notifications</a></li>
|
||||
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#publicprivate">Public/Private</a></li>
|
||||
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#delete">Delete</a></li>
|
||||
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#logs" ng-click="loadLogs()">Usage Logs</a></li>
|
||||
|
@ -135,10 +135,11 @@
|
|||
|
||||
<tr>
|
||||
<td id="add-entity-permission" colspan="2" class="admin-search">
|
||||
<span class="entity-search" namespace="repo.namespace" include-teams="true"
|
||||
input-title="'Add a ' + (repo.is_organization ? 'team or ' : '') + 'user...'"
|
||||
entity-selected="addNewPermission" is-organization="repo.is_organization"
|
||||
current-entity="selectedEntity"></span>
|
||||
<span class="entity-search" namespace="repo.namespace"
|
||||
placeholder="'Add a ' + (repo.is_organization ? 'team or ' : '') + 'user...'"
|
||||
entity-selected="addNewPermission(entity)"
|
||||
current-entity="selectedEntity"
|
||||
auto-clear="true"></span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
@ -192,51 +193,32 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Webhook tab -->
|
||||
<div id="webhook" class="tab-pane">
|
||||
<!-- Notification tab -->
|
||||
<div id="notification" class="tab-pane">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">Push Webhooks
|
||||
<i class="info-icon fa fa-info-circle" data-placement="left" data-content="URLs which will be invoked with an HTTP POST and JSON payload when a successful push to the repository occurs."></i>
|
||||
<div class="panel-heading">Repository Notifications
|
||||
<i class="info-icon fa fa-info-circle" data-placement="left" data-content="Notifications to call to external services (web, email, etc) on repository events"></i>
|
||||
</div>
|
||||
|
||||
<div class="panel-body">
|
||||
<div class="resource-view" resource="webhooksResource" error-message="'Could not load webhooks'">
|
||||
<table class="permissions">
|
||||
<thead>
|
||||
<tr>
|
||||
<td style="width: 500px;">Webhook URL</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr ng-repeat="webhook in webhooks">
|
||||
<td>{{ webhook.parameters.url }}</td>
|
||||
<td>
|
||||
<span class="delete-ui" delete-title="'Delete Webhook'" perform-delete="deleteWebhook(webhook)"></span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<!-- Notifications list -->
|
||||
<div class="resource-view" resource="notificationsResource" error-message="'Could not load notifications'">
|
||||
<div class="empty" ng-if="!notifications.length">
|
||||
There are no notifications defined for this repository
|
||||
</div>
|
||||
<div class="nonempty" ng-show="notifications.length">
|
||||
<div class="external-notification-view" notification="notification" repository="repo"
|
||||
notification-deleted="handleNotificationDeleted(notification)"
|
||||
ng-repeat="notification in notifications"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form name="createWebhookForm" ng-submit="createWebhook()">
|
||||
<table class="permissions">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="width: 500px;">
|
||||
<input type="url" class="form-control" placeholder="New webhook url..." ng-model="newWebhook.url" required>
|
||||
</td>
|
||||
<td>
|
||||
<button class="btn btn-primary" type="submit" ng-disabled="createWebhookForm.$invalid">Create</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</form>
|
||||
|
||||
<div class="right-info">
|
||||
Quay will <b>POST</b> to these webhooks whenever a push occurs. See the <a href="/guide">User Guide</a> for more information.
|
||||
<!-- Right controls -->
|
||||
<div class="right-controls">
|
||||
<button class="btn btn-success" ng-click="showNewNotificationDialog()">
|
||||
<i class="fa fa-paper-plane"></i>
|
||||
New Notification
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -390,6 +372,11 @@
|
|||
canceled="cancelSetupTrigger(trigger)"
|
||||
counter="showTriggerSetupCounter"></div>
|
||||
|
||||
<!-- New notification dialog-->
|
||||
<div class="create-external-notification-dialog" repository="repo"
|
||||
counter="showNewNotificationCounter"
|
||||
notification-created="handleNotificationCreated(notification)"></div>
|
||||
|
||||
<!-- Modal message dialog -->
|
||||
<div class="modal fade" id="cannotchangeModal">
|
||||
<div class="modal-dialog">
|
||||
|
|
|
@ -41,7 +41,7 @@
|
|||
<div class="alert alert-info">
|
||||
<h4 ng-show="namespace == user.username">You don't have any repositories yet!</h4>
|
||||
<h4 ng-show="namespace != user.username">This organization doesn't have any repositories, or you have not been provided access.</h4>
|
||||
<a href="http://docs.quay.io/getting-started.html"><b>Click here</b> to learn how to create a repository</a>
|
||||
<a href="http://docs.quay.io/solution/getting-started.html"><b>Click here</b> to learn how to create a repository</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -23,10 +23,13 @@
|
|||
</tr>
|
||||
|
||||
<tr ng-show="canEditMembers">
|
||||
<td colspan="2">
|
||||
<span class="entity-search" namespace="orgname" include-teams="false" input-title="'Add a Quay.io user...'"
|
||||
entity-selected="addNewMember" is-organization="true"
|
||||
current-entity="selectedMember"></span>
|
||||
<td colspan="3">
|
||||
<div class="entity-search" style="width: 100%"
|
||||
namespace="orgname" placeholder="'Add a Quay.io user or robot...'"
|
||||
entity-selected="addNewMember(entity)"
|
||||
current-entity="selectedMember"
|
||||
auto-clear="true"
|
||||
allowed-entities="['user', 'robot']"></div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
|
Reference in a new issue