Merge branch 'master' into nolurk

This commit is contained in:
Joseph Schorr 2015-06-02 13:55:16 -04:00
commit c0e995c1d4
43 changed files with 1091 additions and 127 deletions

View file

@ -388,6 +388,29 @@ a:focus {
width: 400px;
}
.config-map-field-element table {
margin-bottom: 10px;
}
.config-map-field-element .form-control-container {
border-top: 1px solid #eee;
padding-top: 10px;
}
.config-map-field-element .form-control-container select, .config-map-field-element .form-control-container input {
margin-bottom: 10px;
}
.config-map-field-element .empty {
color: #ccc;
margin-bottom: 10px;
display: block;
}
.config-map-field-element .item-title {
font-weight: bold;
}
.config-contact-field {
margin-bottom: 4px;
}

View file

@ -157,6 +157,24 @@
transition: all 0.15s ease-in-out;
}
.build-logs-view .download-button i.fa {
margin-right: 10px;
}
.build-logs-view .download-button {
position: absolute;
top: 6px;
right: 124px;
z-index: 2;
transition: all 0.15s ease-in-out;
}
.build-logs-view .download-button:not(:hover) {
background: transparent;
border: 1px solid transparent;
color: #ddd;
}
.build-logs-view .copy-button:not(.zeroclipboard-is-hover) {
background: transparent;
border: 1px solid transparent;

View file

@ -3,6 +3,12 @@
<i class="fa fa-clipboard"></i>Copy Logs
</button>
<a id="downloadButton" class="btn btn-primary download-button"
ng-href="/buildlogs/{{ currentBuild.id }}"
target="_blank">
<i class="fa fa-download"></i>Download Logs
</a>
<span class="cor-loader" ng-if="!logEntries"></span>
<span class="no-logs" ng-if="!logEntries.length && currentBuild.phase == 'waiting'">

View file

@ -0,0 +1,20 @@
<div class="config-map-field-element">
<table class="table" ng-show="hasValues(binding)">
<tr class="item" ng-repeat="(key, value) in binding">
<td class="item-title">{{ key }}</td>
<td class="item-value">{{ value }}</td>
<td class="item-delete">
<a href="javascript:void(0)" ng-click="removeKey(key)">Remove</a>
</td>
</tr>
</table>
<span class="empty" ng-if="!hasValues(binding)">No entries defined</span>
<form class="form-control-container" ng-submit="addEntry()">
Add Key-Value:
<select ng-model="newKey">
<option ng-repeat="key in keys" value="{{ key }}">{{ key }}</option>
</select>
<input type="text" class="form-control" placeholder="Value" ng-model="newValue">
<button class="btn btn-default" style="display: inline-block">Add Entry</button>
</form>
</div>

View file

@ -112,6 +112,11 @@
A valid SSL certificate and private key files are required to use this option.
</div>
<div class="co-alert co-alert-info" ng-if="config.PREFERRED_URL_SCHEME == 'https'">
Enabling SSL also enables <a href="https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security">HTTP Strict Transport Security</a>.<br/>
This prevents downgrade attacks and cookie theft, but browsers will reject all future insecure connections on this hostname.
</div>
<table class="config-table" ng-if="config.PREFERRED_URL_SCHEME == 'https'">
<tr>
<td class="non-input">Certificate:</td>
@ -198,6 +203,7 @@
<option value="S3Storage">Amazon S3</option>
<option value="GoogleCloudStorage">Google Cloud Storage</option>
<option value="RadosGWStorage">Ceph Object Gateway (RADOS)</option>
<option value="SwiftStorage">OpenStack Storage (Swift)</option>
</select>
</td>
</tr>
@ -206,10 +212,15 @@
<tr ng-repeat="field in STORAGE_CONFIG_FIELDS[config.DISTRIBUTED_STORAGE_CONFIG.local[0]]">
<td>{{ field.title }}:</td>
<td>
<span class="config-map-field"
binding="config.DISTRIBUTED_STORAGE_CONFIG.local[1][field.name]"
ng-if="field.kind == 'map'"
keys="field.keys"></span>
<span class="config-string-field"
binding="config.DISTRIBUTED_STORAGE_CONFIG.local[1][field.name]"
placeholder="{{ field.placeholder }}"
ng-if="field.kind == 'text'"></span>
ng-if="field.kind == 'text'"
is-optional="field.optional"></span>
<div class="co-checkbox" ng-if="field.kind == 'bool'">
<input id="dsc-{{ field.name }}" type="checkbox"
ng-model="config.DISTRIBUTED_STORAGE_CONFIG.local[1][field.name]">
@ -849,4 +860,4 @@
</div><!-- /.modal -->
</div>
</div>
</div>

View file

@ -2,7 +2,7 @@
<form name="fieldform" novalidate>
<input type="text" class="form-control" placeholder="{{ placeholder || '' }}"
ng-model="binding" ng-trim="false" ng-minlength="1"
ng-pattern="getRegexp(pattern)" required>
ng-pattern="getRegexp(pattern)" ng-required="!isOptional">
<div class="alert alert-danger" ng-show="errorMessage">
{{ errorMessage }}
</div>

View file

@ -63,6 +63,12 @@
<!-- Build Status Badge -->
<div class="panel-body panel-section hidden-xs">
<!-- Token Info Banner -->
<div class="co-alert co-alert-info" ng-if="!repository.is_public">
Note: This badge contains a token so the badge can be seen by external users. The token does not grant any other access and is safe to share!
</div>
<!-- Status Image -->
<a ng-href="/repository/{{ repository.namespace }}/{{ repository.name }}">
<img ng-src="/repository/{{ repository.namespace }}/{{ repository.name }}/status?token={{ repository.status_token }}"

View file

@ -15,10 +15,10 @@
<div class="empty" ng-if="!notifications.length">
<div class="empty-primary-msg">No notifications have been setup for this repository.</div>
<div class="empty-secondary-msg hidden-xs" ng-if="repository.can_write">
<div class="empty-secondary-msg hidden-sm hidden-xs" ng-if="repository.can_write">
Click the "Create Notification" button above to add a new notification for a repository event.
</div>
<div class="empty-secondary-msg visible-xs" ng-if="repository.can_write">
<div class="empty-secondary-msg visible-sm visible-xs" ng-if="repository.can_write">
<a href="javascript:void(0)" ng-click="askCreateNotification()">Click here</a> to add a new notification for a repository event.
</div>
</div>

View file

@ -78,6 +78,19 @@ angular.module("core-config-setup", ['angularFileUpload'])
{'name': 'secret_key', 'title': 'Secret Key', 'placeholder': 'secretkeyhere', 'kind': 'text'},
{'name': 'bucket_name', 'title': 'Bucket Name', 'placeholder': 'my-cool-bucket', 'kind': 'text'},
{'name': 'storage_path', 'title': 'Storage Directory', 'placeholder': '/path/inside/bucket', 'kind': 'text'}
],
'SwiftStorage': [
{'name': 'auth_url', 'title': 'Swift Auth URL', 'placeholder': '', 'kind': 'text'},
{'name': 'swift_container', 'title': 'Swift Container Name', 'placeholder': 'mycontainer', 'kind': 'text'},
{'name': 'storage_path', 'title': 'Storage Path', 'placeholder': '/path/inside/container', 'kind': 'text'},
{'name': 'swift_user', 'title': 'Username', 'placeholder': 'accesskeyhere', 'kind': 'text'},
{'name': 'swift_password', 'title': 'Password/Key', 'placeholder': 'secretkeyhere', 'kind': 'text'},
{'name': 'ca_cert_path', 'title': 'CA Cert Filename', 'placeholder': 'conf/stack/swift.cert', 'kind': 'text', 'optional': true},
{'name': 'os_options', 'title': 'OS Options', 'kind': 'map',
'keys': ['tenant_id', 'auth_token', 'service_type', 'endpoint_type', 'tenant_name', 'object_storage_url', 'region_name']}
]
};
@ -760,6 +773,42 @@ angular.module("core-config-setup", ['angularFileUpload'])
return directiveDefinitionObject;
})
.directive('configMapField', function () {
var directiveDefinitionObject = {
priority: 0,
templateUrl: '/static/directives/config/config-map-field.html',
replace: false,
transclude: false,
restrict: 'C',
scope: {
'binding': '=binding',
'keys': '=keys'
},
controller: function($scope, $element) {
$scope.newKey = null;
$scope.newValue = null;
$scope.hasValues = function(binding) {
return binding && Object.keys(binding).length;
};
$scope.removeKey = function(key) {
delete $scope.binding[key];
};
$scope.addEntry = function() {
if (!$scope.newKey || !$scope.newValue) { return; }
$scope.binding = $scope.binding || {};
$scope.binding[$scope.newKey] = $scope.newValue;
$scope.newKey = null;
$scope.newValue = null;
}
}
};
return directiveDefinitionObject;
})
.directive('configStringField', function () {
var directiveDefinitionObject = {
priority: 0,
@ -772,7 +821,8 @@ angular.module("core-config-setup", ['angularFileUpload'])
'placeholder': '@placeholder',
'pattern': '@pattern',
'defaultValue': '@defaultValue',
'validator': '&validator'
'validator': '&validator',
'isOptional': '=isOptional'
},
controller: function($scope, $element) {
$scope.getRegexp = function(pattern) {

View file

@ -260,7 +260,7 @@ angular.module('quay').directive('repoPanelBuilds', function () {
};
$scope.handleBuildStarted = function(build) {
if (!$scope.allBuilds) {
if ($scope.allBuilds) {
$scope.allBuilds.push(build);
}
updateBuilds();

View file

@ -123,6 +123,7 @@ angular.module('quay').directive('triggerSetupGithost', function () {
ApiService.listBuildTriggerSubdirs($scope.trigger['config'], params).then(function(resp) {
if (resp['status'] == 'error') {
$scope.locations = [];
callback(resp['message'] || 'Could not load Dockerfile locations');
return;
}

View file

@ -45,6 +45,7 @@
$scope.signinStarted = function() {
if (Features.BILLING) {
PlanService.getMinimumPlan(1, true, function(plan) {
if (!plan) { return; }
PlanService.notePlan(plan.stripeId);
});
}

View file

@ -94,7 +94,7 @@
}, ApiService.errorDisplay('Could not generate token'));
};
UIService.showPasswordDialog('Enter your password to generated an encrypted version:', generateToken);
UIService.showPasswordDialog('Enter your password to generate an encrypted version:', generateToken);
};
$scope.changeEmail = function() {

View file

@ -1,7 +1,8 @@
/**
* Specialized class for conducting an HTTP poll, while properly preventing multiple calls.
*/
angular.module('quay').factory('AngularPollChannel', ['ApiService', '$timeout', function(ApiService, $timeout) {
angular.module('quay').factory('AngularPollChannel', ['ApiService', '$timeout', 'DocumentVisibilityService',
function(ApiService, $timeout, DocumentVisibilityService) {
var _PollChannel = function(scope, requester, opt_sleeptime) {
this.scope_ = scope;
this.requester_ = requester;
@ -50,6 +51,12 @@ angular.module('quay').factory('AngularPollChannel', ['ApiService', '$timeout',
_PollChannel.prototype.call_ = function() {
if (this.working) { return; }
// If the document is currently hidden, skip the call.
if (DocumentVisibilityService.isHidden()) {
this.setupTimer_();
return;
}
var that = this;
this.working = true;
this.scope_.$apply(function() {

View file

@ -0,0 +1,60 @@
/**
* Helper service which fires off events when the document's visibility changes, as well as allowing
* other Angular code to query the state of the document's visibility directly.
*/
angular.module('quay').constant('CORE_EVENT', {
DOC_VISIBILITY_CHANGE: 'core.event.doc_visibility_change'
});
angular.module('quay').factory('DocumentVisibilityService', ['$rootScope', '$document', 'CORE_EVENT',
function($rootScope, $document, CORE_EVENT) {
var document = $document[0],
features,
detectedFeature;
function broadcastChangeEvent() {
$rootScope.$broadcast(CORE_EVENT.DOC_VISIBILITY_CHANGE,
document[detectedFeature.propertyName]);
}
features = {
standard: {
eventName: 'visibilitychange',
propertyName: 'hidden'
},
moz: {
eventName: 'mozvisibilitychange',
propertyName: 'mozHidden'
},
ms: {
eventName: 'msvisibilitychange',
propertyName: 'msHidden'
},
webkit: {
eventName: 'webkitvisibilitychange',
propertyName: 'webkitHidden'
}
};
Object.keys(features).some(function(feature) {
if (document[features[feature].propertyName] !== undefined) {
detectedFeature = features[feature];
return true;
}
});
if (detectedFeature) {
$document.on(detectedFeature.eventName, broadcastChangeEvent);
}
return {
/**
* Is the window currently hidden or not.
*/
isHidden: function() {
if (detectedFeature) {
return document[detectedFeature.propertyName];
}
}
};
}]);

View file

@ -196,6 +196,10 @@ function($rootScope, $interval, UserService, ApiService, StringBuilderService, P
};
notificationService.getClasses = function(notifications) {
if (!notifications.length) {
return '';
}
var classes = [];
for (var i = 0; i < notifications.length; ++i) {
var notification = notifications[i];

View file

@ -122,7 +122,7 @@ angular.module('quay').factory('TriggerService', ['UtilService', '$sanitize', 'K
'title': 'Commit',
'type': 'regex',
'name': 'commit_sha',
'regex': '^([A-Fa-f0-9]{7})$',
'regex': '^([A-Fa-f0-9]{7,})$',
'placeholder': '1c002dd'
}
],