Implement new create and manager trigger UI
Implements the new trigger setup user interface, which is now a linear workflow found on its own page, rather than a tiny modal dialog Fixes #1187
This commit is contained in:
parent
21b09a7451
commit
8e863b8cf5
47 changed files with 1835 additions and 1068 deletions
54
static/js/directives/ui/dockerfile-path-select.js
Normal file
54
static/js/directives/ui/dockerfile-path-select.js
Normal file
|
@ -0,0 +1,54 @@
|
|||
/**
|
||||
* An element which displays a list of selectable paths containing Dockerfiles.
|
||||
*/
|
||||
angular.module('quay').directive('dockerfilePathSelect', function () {
|
||||
var directiveDefinitionObject = {
|
||||
priority: 0,
|
||||
templateUrl: '/static/directives/dockerfile-path-select.html',
|
||||
replace: false,
|
||||
transclude: true,
|
||||
restrict: 'C',
|
||||
scope: {
|
||||
'currentPath': '=currentPath',
|
||||
'isValidPath': '=?isValidPath',
|
||||
'paths': '=paths',
|
||||
'supportsFullListing': '=supportsFullListing'
|
||||
},
|
||||
controller: function($scope, $element) {
|
||||
$scope.isUnknownPath = true;
|
||||
$scope.selectedPath = null;
|
||||
|
||||
var checkPath = function() {
|
||||
$scope.isUnknownPath = false;
|
||||
$scope.isValidPath = false;
|
||||
|
||||
var path = $scope.currentPath || '';
|
||||
if (path.length == 0 || path[0] != '/') {
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.isValidPath = true;
|
||||
|
||||
if (!$scope.paths) {
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.isUnknownPath = $scope.supportsFullListing && $scope.paths.indexOf(path) < 0;
|
||||
};
|
||||
|
||||
$scope.setPath = function(path) {
|
||||
$scope.currentPath = path;
|
||||
$scope.selectedPath = null;
|
||||
};
|
||||
|
||||
$scope.setSelectedPath = function(path) {
|
||||
$scope.currentPath = path;
|
||||
$scope.selectedPath = path;
|
||||
};
|
||||
|
||||
$scope.$watch('currentPath', checkPath);
|
||||
$scope.$watch('paths', checkPath);
|
||||
}
|
||||
};
|
||||
return directiveDefinitionObject;
|
||||
});
|
|
@ -28,36 +28,29 @@ angular.module('quay').directive('dropdownSelect', function ($compile) {
|
|||
}
|
||||
|
||||
$scope.placeholder = $scope.placeholder || '';
|
||||
$scope.internalItem = null;
|
||||
$scope.lookaheadSetup = false;
|
||||
|
||||
// Setup lookahead.
|
||||
var input = $($element).find('.lookahead-input');
|
||||
|
||||
$scope.$watch('clearValue', function(cv) {
|
||||
if (cv) {
|
||||
if (cv && $scope.lookaheadSetup) {
|
||||
$scope.selectedItem = null;
|
||||
$(input).val('');
|
||||
$(input).typeahead('val', '');
|
||||
$(input).typeahead('close');
|
||||
}
|
||||
});
|
||||
|
||||
$scope.$watch('selectedItem', function(item) {
|
||||
if ($scope.selectedItem == $scope.internalItem) {
|
||||
// The item has already been set due to an internal action.
|
||||
return;
|
||||
}
|
||||
|
||||
if ($scope.selectedItem != null) {
|
||||
$(input).val(item.toString());
|
||||
} else {
|
||||
$(input).val('');
|
||||
if (item != null && $scope.lookaheadSetup) {
|
||||
$(input).typeahead('val', item.toString());
|
||||
$(input).typeahead('close');
|
||||
}
|
||||
});
|
||||
|
||||
$scope.$watch('lookaheadItems', function(items) {
|
||||
$(input).off();
|
||||
if (!items) {
|
||||
return;
|
||||
}
|
||||
items = items || [];
|
||||
|
||||
var formattedItems = [];
|
||||
for (var i = 0; i < items.length; ++i) {
|
||||
|
@ -80,7 +73,10 @@ angular.module('quay').directive('dropdownSelect', function ($compile) {
|
|||
});
|
||||
dropdownHound.initialize();
|
||||
|
||||
$(input).typeahead({}, {
|
||||
$(input).typeahead({
|
||||
'hint': false,
|
||||
'highlight': false
|
||||
}, {
|
||||
source: dropdownHound.ttAdapter(),
|
||||
templates: {
|
||||
'suggestion': function (datum) {
|
||||
|
@ -92,7 +88,6 @@ angular.module('quay').directive('dropdownSelect', function ($compile) {
|
|||
|
||||
$(input).on('input', function(e) {
|
||||
$scope.$apply(function() {
|
||||
$scope.internalItem = null;
|
||||
$scope.selectedItem = null;
|
||||
if ($scope.handleInput) {
|
||||
$scope.handleInput({'input': $(input).val()});
|
||||
|
@ -102,7 +97,6 @@ angular.module('quay').directive('dropdownSelect', function ($compile) {
|
|||
|
||||
$(input).on('typeahead:selected', function(e, datum) {
|
||||
$scope.$apply(function() {
|
||||
$scope.internalItem = datum['item'] || datum['value'];
|
||||
$scope.selectedItem = datum['item'] || datum['value'];
|
||||
if ($scope.handleItemSelected) {
|
||||
$scope.handleItemSelected({'datum': datum});
|
||||
|
@ -111,6 +105,7 @@ angular.module('quay').directive('dropdownSelect', function ($compile) {
|
|||
});
|
||||
|
||||
$rootScope.__dropdownSelectCounter++;
|
||||
$scope.lookaheadSetup = true;
|
||||
});
|
||||
},
|
||||
link: function(scope, element, attrs) {
|
||||
|
|
141
static/js/directives/ui/linear-workflow.js
Normal file
141
static/js/directives/ui/linear-workflow.js
Normal file
|
@ -0,0 +1,141 @@
|
|||
/**
|
||||
* An element which displays a linear workflow of sections, each completed in order before the next
|
||||
* step is made visible.
|
||||
*/
|
||||
angular.module('quay').directive('linearWorkflow', function () {
|
||||
var directiveDefinitionObject = {
|
||||
priority: 0,
|
||||
templateUrl: '/static/directives/linear-workflow.html',
|
||||
replace: false,
|
||||
transclude: true,
|
||||
restrict: 'C',
|
||||
scope: {
|
||||
'workflowState': '=?workflowState',
|
||||
'workflowComplete': '&workflowComplete',
|
||||
'doneTitle': '@doneTitle'
|
||||
},
|
||||
controller: function($scope, $element, $timeout) {
|
||||
$scope.sections = [];
|
||||
|
||||
$scope.nextSection = function() {
|
||||
if (!$scope.currentSection.valid) { return; }
|
||||
|
||||
var currentIndex = $scope.currentSection.index;
|
||||
if (currentIndex + 1 >= $scope.sections.length) {
|
||||
$scope.workflowComplete();
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.workflowState = $scope.sections[currentIndex + 1].id;
|
||||
};
|
||||
|
||||
this.registerSection = function(sectionScope, sectionElement) {
|
||||
// Add the section to the list.
|
||||
var sectionInfo = {
|
||||
'index': $scope.sections.length,
|
||||
'id': sectionScope.sectionId,
|
||||
'title': sectionScope.sectionTitle,
|
||||
'scope': sectionScope,
|
||||
'element': sectionElement
|
||||
};
|
||||
|
||||
$scope.sections.push(sectionInfo);
|
||||
|
||||
// Add a watch on the `sectionValid` value on the section itself. If/when this value
|
||||
// changes, we copy it over to the sectionInfo, so that the overall workflow can watch
|
||||
// the change.
|
||||
sectionScope.$watch('sectionValid', function(isValid) {
|
||||
sectionInfo['valid'] = isValid;
|
||||
if (!isValid) {
|
||||
// Reset the sections back to this section.
|
||||
updateState();
|
||||
}
|
||||
});
|
||||
|
||||
// Bind the `submitSection` callback to move to the next section when the user hits
|
||||
// enter (which calls this method on the scope via an ng-submit set on a wrapping
|
||||
// <form> tag).
|
||||
sectionScope.submitSection = function() {
|
||||
$scope.nextSection();
|
||||
};
|
||||
|
||||
// Update the state of the workflow to account for the new section.
|
||||
updateState();
|
||||
};
|
||||
|
||||
var updateState = function() {
|
||||
// Find the furthest state we can show.
|
||||
var foundIndex = 0;
|
||||
var maxValidIndex = -1;
|
||||
|
||||
$scope.sections.forEach(function(section, index) {
|
||||
if (section.id == $scope.workflowState) {
|
||||
foundIndex = index;
|
||||
}
|
||||
|
||||
if (maxValidIndex == index - 1 && section.valid) {
|
||||
maxValidIndex = index;
|
||||
}
|
||||
});
|
||||
|
||||
var minSectionIndex = Math.min(maxValidIndex + 1, foundIndex);
|
||||
$scope.sections.forEach(function(section, index) {
|
||||
section.scope.sectionVisible = index <= minSectionIndex;
|
||||
section.scope.isCurrentSection = false;
|
||||
});
|
||||
|
||||
$scope.workflowState = null;
|
||||
if (minSectionIndex >= 0 && minSectionIndex < $scope.sections.length) {
|
||||
$scope.currentSection = $scope.sections[minSectionIndex];
|
||||
$scope.workflowState = $scope.currentSection.id;
|
||||
$scope.currentSection.scope.isCurrentSection = true;
|
||||
|
||||
// Focus to the first input (if any) in the section.
|
||||
$timeout(function() {
|
||||
var inputs = $scope.currentSection.element.find('input');
|
||||
if (inputs.length == 1) {
|
||||
inputs.focus();
|
||||
}
|
||||
}, 10);
|
||||
}
|
||||
};
|
||||
|
||||
$scope.$watch('workflowState', updateState);
|
||||
}
|
||||
};
|
||||
return directiveDefinitionObject;
|
||||
});
|
||||
|
||||
/**
|
||||
* An element which displays a single section in a linear workflow.
|
||||
*/
|
||||
angular.module('quay').directive('linearWorkflowSection', function () {
|
||||
var directiveDefinitionObject = {
|
||||
priority: 0,
|
||||
templateUrl: '/static/directives/linear-workflow-section.html',
|
||||
replace: false,
|
||||
transclude: true,
|
||||
restrict: 'C',
|
||||
require: '^linearWorkflow',
|
||||
scope: {
|
||||
'sectionId': '@sectionId',
|
||||
'sectionTitle': '@sectionTitle',
|
||||
'sectionValid': '=?sectionValid',
|
||||
},
|
||||
|
||||
link: function($scope, $element, $attrs, $ctrl) {
|
||||
$ctrl.registerSection($scope, $element);
|
||||
},
|
||||
|
||||
controller: function($scope, $element) {
|
||||
$scope.$watch('sectionVisible', function(visible) {
|
||||
if (visible) {
|
||||
$element.show();
|
||||
} else {
|
||||
$element.hide();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
return directiveDefinitionObject;
|
||||
});
|
27
static/js/directives/ui/manage-trigger-custom-git.js
Normal file
27
static/js/directives/ui/manage-trigger-custom-git.js
Normal file
|
@ -0,0 +1,27 @@
|
|||
/**
|
||||
* An element which displays the setup and management workflow for a custom git trigger.
|
||||
*/
|
||||
angular.module('quay').directive('manageTriggerCustomGit', function () {
|
||||
var directiveDefinitionObject = {
|
||||
priority: 0,
|
||||
templateUrl: '/static/directives/manage-trigger-custom-git.html',
|
||||
replace: false,
|
||||
transclude: true,
|
||||
restrict: 'C',
|
||||
scope: {
|
||||
'trigger': '=trigger',
|
||||
'activateTrigger': '&activateTrigger'
|
||||
},
|
||||
controller: function($scope, $element) {
|
||||
$scope.config = {};
|
||||
$scope.currentState = null;
|
||||
|
||||
$scope.$watch('trigger', function(trigger) {
|
||||
if (trigger) {
|
||||
$scope.config = trigger['config'] || {};
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
return directiveDefinitionObject;
|
||||
});
|
306
static/js/directives/ui/manage-trigger-githost.js
Normal file
306
static/js/directives/ui/manage-trigger-githost.js
Normal file
|
@ -0,0 +1,306 @@
|
|||
/**
|
||||
* An element which displays the setup and management workflow for a normal SCM git trigger.
|
||||
*/
|
||||
angular.module('quay').directive('manageTriggerGithost', function () {
|
||||
var directiveDefinitionObject = {
|
||||
priority: 0,
|
||||
templateUrl: '/static/directives/manage-trigger-githost.html',
|
||||
replace: false,
|
||||
transclude: true,
|
||||
restrict: 'C',
|
||||
scope: {
|
||||
'repository': '=repository',
|
||||
'trigger': '=trigger',
|
||||
|
||||
'activateTrigger': '&activateTrigger'
|
||||
},
|
||||
controller: function($scope, $element, ApiService, TableService, TriggerService, RolesService) {
|
||||
$scope.TableService = TableService;
|
||||
|
||||
$scope.config = {};
|
||||
$scope.local = {};
|
||||
$scope.currentState = null;
|
||||
|
||||
$scope.namespacesPerPage = 10;
|
||||
$scope.repositoriesPerPage = 10;
|
||||
$scope.robotsPerPage = 10;
|
||||
|
||||
$scope.local.namespaceOptions = {
|
||||
'filter': '',
|
||||
'predicate': 'score',
|
||||
'reverse': false,
|
||||
'page': 0
|
||||
};
|
||||
|
||||
$scope.local.repositoryOptions = {
|
||||
'filter': '',
|
||||
'predicate': 'last_updated',
|
||||
'reverse': false,
|
||||
'page': 0,
|
||||
'hideStale': true
|
||||
};
|
||||
|
||||
$scope.local.robotOptions = {
|
||||
'filter': '',
|
||||
'predicate': 'can_read',
|
||||
'reverse': false,
|
||||
'page': 0
|
||||
};
|
||||
|
||||
$scope.getTriggerIcon = function() {
|
||||
return TriggerService.getIcon($scope.trigger.service);
|
||||
};
|
||||
|
||||
$scope.createTrigger = function() {
|
||||
var config = {
|
||||
'build_source': $scope.local.selectedRepository.full_name,
|
||||
'subdir': $scope.local.dockerfilePath.substr(1) // Remove starting /
|
||||
};
|
||||
|
||||
if ($scope.local.triggerOptions.hasBranchTagFilter &&
|
||||
$scope.local.triggerOptions.branchTagFilter) {
|
||||
config['branchtag_regex'] = $scope.local.triggerOptions.branchTagFilter;
|
||||
}
|
||||
|
||||
var activate = function() {
|
||||
$scope.activateTrigger({'config': config, 'pull_robot': $scope.local.robotAccount});
|
||||
};
|
||||
|
||||
if ($scope.local.robotAccount) {
|
||||
if ($scope.local.robotAccount.can_read) {
|
||||
activate();
|
||||
} else {
|
||||
// Add read permission onto the base repository for the robot and then activate the
|
||||
// trigger.
|
||||
var robot_name = $scope.local.robotAccount.name;
|
||||
RolesService.setRepositoryRole($scope.repository, 'read', 'robot', robot_name, activate);
|
||||
}
|
||||
} else {
|
||||
activate();
|
||||
}
|
||||
};
|
||||
|
||||
var buildOrderedNamespaces = function() {
|
||||
if (!$scope.local.namespaces) {
|
||||
return;
|
||||
}
|
||||
|
||||
var namespaces = $scope.local.namespaces || [];
|
||||
$scope.local.orderedNamespaces = TableService.buildOrderedItems(namespaces,
|
||||
$scope.local.namespaceOptions,
|
||||
['id'],
|
||||
['score'])
|
||||
|
||||
$scope.local.maxScore = 0;
|
||||
namespaces.forEach(function(namespace) {
|
||||
$scope.local.maxScore = Math.max(namespace.score, $scope.local.maxScore);
|
||||
});
|
||||
};
|
||||
|
||||
var loadNamespaces = function() {
|
||||
$scope.local.namespaces = null;
|
||||
$scope.local.selectedNamespace = null;
|
||||
$scope.local.orderedNamespaces = null;
|
||||
|
||||
$scope.local.selectedRepository = null;
|
||||
$scope.local.orderedRepositories = null;
|
||||
|
||||
var params = {
|
||||
'repository': $scope.repository.namespace + '/' + $scope.repository.name,
|
||||
'trigger_uuid': $scope.trigger.id
|
||||
};
|
||||
|
||||
ApiService.listTriggerBuildSourceNamespaces(null, params).then(function(resp) {
|
||||
$scope.local.namespaces = resp['namespaces'];
|
||||
$scope.local.repositories = null;
|
||||
buildOrderedNamespaces();
|
||||
}, ApiService.errorDisplay('Could not retrieve the list of ' + $scope.namespaceTitle))
|
||||
};
|
||||
|
||||
var buildOrderedRepositories = function() {
|
||||
if (!$scope.local.repositories) {
|
||||
return;
|
||||
}
|
||||
|
||||
var repositories = $scope.local.repositories || [];
|
||||
repositories.forEach(function(repository) {
|
||||
repository['last_updated_datetime'] = new Date(repository['last_updated'] * 1000);
|
||||
});
|
||||
|
||||
if ($scope.local.repositoryOptions.hideStale) {
|
||||
var existingRepositories = repositories;
|
||||
|
||||
repositories = repositories.filter(function(repository) {
|
||||
var older_date = moment(repository['last_updated_datetime']).add(1, 'months');
|
||||
return !moment().isAfter(older_date);
|
||||
});
|
||||
|
||||
if (existingRepositories.length > 0 && repositories.length == 0) {
|
||||
repositories = existingRepositories;
|
||||
}
|
||||
}
|
||||
|
||||
$scope.local.orderedRepositories = TableService.buildOrderedItems(repositories,
|
||||
$scope.local.repositoryOptions,
|
||||
['name', 'description'],
|
||||
[]);
|
||||
};
|
||||
|
||||
var loadRepositories = function(namespace) {
|
||||
$scope.local.repositories = null;
|
||||
$scope.local.selectedRepository = null;
|
||||
$scope.local.repositoryRefs = null;
|
||||
$scope.local.triggerOptions = {
|
||||
'hasBranchTagFilter': false
|
||||
};
|
||||
|
||||
$scope.local.orderedRepositories = null;
|
||||
|
||||
var params = {
|
||||
'repository': $scope.repository.namespace + '/' + $scope.repository.name,
|
||||
'trigger_uuid': $scope.trigger.id
|
||||
};
|
||||
|
||||
var data = {
|
||||
'namespace': namespace.id
|
||||
};
|
||||
|
||||
ApiService.listTriggerBuildSources(data, params).then(function(resp) {
|
||||
if (namespace == $scope.local.selectedNamespace) {
|
||||
$scope.local.repositories = resp['sources'];
|
||||
buildOrderedRepositories();
|
||||
}
|
||||
}, ApiService.errorDisplay('Could not retrieve repositories'));
|
||||
};
|
||||
|
||||
var loadRepositoryRefs = function(repository) {
|
||||
$scope.local.repositoryRefs = null;
|
||||
$scope.local.triggerOptions = {
|
||||
'hasBranchTagFilter': false
|
||||
};
|
||||
|
||||
var params = {
|
||||
'repository': $scope.repository.namespace + '/' + $scope.repository.name,
|
||||
'trigger_uuid': $scope.trigger.id,
|
||||
'field_name': 'refs'
|
||||
};
|
||||
|
||||
var config = {
|
||||
'build_source': repository.full_name
|
||||
};
|
||||
|
||||
ApiService.listTriggerFieldValues(config, params).then(function(resp) {
|
||||
if (repository == $scope.local.selectedRepository) {
|
||||
$scope.local.repositoryRefs = resp['values'];
|
||||
$scope.local.repositoryFullRefs = resp['values'].map(function(ref) {
|
||||
var kind = ref.kind == 'branch' ? 'heads' : 'tags';
|
||||
var icon = ref.kind == 'branch' ? 'fa-code-fork' : 'fa-tag';
|
||||
return {
|
||||
'value': kind + '/' + ref.name,
|
||||
'icon': icon,
|
||||
'title': ref.name
|
||||
};
|
||||
});
|
||||
}
|
||||
}, ApiService.errorDisplay('Could not retrieve repository refs'));
|
||||
};
|
||||
|
||||
var loadDockerfileLocations = function(repository) {
|
||||
$scope.local.dockerfilePath = null;
|
||||
|
||||
var params = {
|
||||
'repository': $scope.repository.namespace + '/' + $scope.repository.name,
|
||||
'trigger_uuid': $scope.trigger.id
|
||||
};
|
||||
|
||||
var config = {
|
||||
'build_source': repository.full_name
|
||||
};
|
||||
|
||||
ApiService.listBuildTriggerSubdirs(config, params).then(function(resp) {
|
||||
if (repository == $scope.local.selectedRepository) {
|
||||
$scope.local.dockerfileLocations = resp;
|
||||
}
|
||||
}, ApiService.errorDisplay('Could not retrieve Dockerfile locations'));
|
||||
};
|
||||
|
||||
var buildOrderedRobotAccounts = function() {
|
||||
if (!$scope.local.triggerAnalysis || !$scope.local.triggerAnalysis.robots) {
|
||||
return;
|
||||
}
|
||||
|
||||
var robots = $scope.local.triggerAnalysis.robots;
|
||||
$scope.local.orderedRobotAccounts = TableService.buildOrderedItems(robots,
|
||||
$scope.local.robotOptions,
|
||||
['name'],
|
||||
[]);
|
||||
};
|
||||
|
||||
var checkDockerfilePath = function(repository, path) {
|
||||
$scope.local.triggerAnalysis = null;
|
||||
$scope.local.robotAccount = null;
|
||||
|
||||
var params = {
|
||||
'repository': $scope.repository.namespace + '/' + $scope.repository.name,
|
||||
'trigger_uuid': $scope.trigger.id
|
||||
};
|
||||
|
||||
var config = {
|
||||
'build_source': repository.full_name,
|
||||
'subdir': path.substr(1)
|
||||
};
|
||||
|
||||
var data = {
|
||||
'config': config
|
||||
};
|
||||
|
||||
ApiService.analyzeBuildTrigger(data, params).then(function(resp) {
|
||||
$scope.local.triggerAnalysis = resp;
|
||||
buildOrderedRobotAccounts();
|
||||
}, ApiService.errorDisplay('Could not analyze trigger'));
|
||||
};
|
||||
|
||||
$scope.$watch('trigger', function(trigger) {
|
||||
if (trigger && $scope.repository) {
|
||||
$scope.config = trigger['config'] || {};
|
||||
$scope.namespaceTitle = 'organization';
|
||||
$scope.local.selectedNamespace = null;
|
||||
loadNamespaces();
|
||||
}
|
||||
});
|
||||
|
||||
$scope.$watch('local.selectedNamespace', function(namespace) {
|
||||
if (namespace) {
|
||||
loadRepositories(namespace);
|
||||
}
|
||||
});
|
||||
|
||||
$scope.$watch('local.selectedRepository', function(repository) {
|
||||
if (repository) {
|
||||
loadRepositoryRefs(repository);
|
||||
loadDockerfileLocations(repository);
|
||||
}
|
||||
});
|
||||
|
||||
$scope.$watch('local.dockerfilePath', function(path) {
|
||||
if (path && $scope.local.selectedRepository) {
|
||||
checkDockerfilePath($scope.local.selectedRepository, path);
|
||||
}
|
||||
});
|
||||
|
||||
$scope.$watch('local.namespaceOptions.predicate', buildOrderedNamespaces);
|
||||
$scope.$watch('local.namespaceOptions.reverse', buildOrderedNamespaces);
|
||||
$scope.$watch('local.namespaceOptions.filter', buildOrderedNamespaces);
|
||||
|
||||
$scope.$watch('local.repositoryOptions.predicate', buildOrderedRepositories);
|
||||
$scope.$watch('local.repositoryOptions.reverse', buildOrderedRepositories);
|
||||
$scope.$watch('local.repositoryOptions.filter', buildOrderedRepositories);
|
||||
$scope.$watch('local.repositoryOptions.hideStale', buildOrderedRepositories);
|
||||
|
||||
$scope.$watch('local.robotOptions.predicate', buildOrderedRobotAccounts);
|
||||
$scope.$watch('local.robotOptions.reverse', buildOrderedRobotAccounts);
|
||||
$scope.$watch('local.robotOptions.filter', buildOrderedRobotAccounts);
|
||||
}
|
||||
};
|
||||
return directiveDefinitionObject;
|
||||
});
|
36
static/js/directives/ui/regex-match-view.js
Normal file
36
static/js/directives/ui/regex-match-view.js
Normal file
|
@ -0,0 +1,36 @@
|
|||
/**
|
||||
* An element which displays the matches and non-matches for a regular expression against a set of
|
||||
* items.
|
||||
*/
|
||||
angular.module('quay').directive('regexMatchView', function () {
|
||||
var directiveDefinitionObject = {
|
||||
priority: 0,
|
||||
templateUrl: '/static/directives/regex-match-view.html',
|
||||
replace: false,
|
||||
transclude: true,
|
||||
restrict: 'C',
|
||||
scope: {
|
||||
'regex': '=regex',
|
||||
'items': '=items'
|
||||
},
|
||||
controller: function($scope, $element) {
|
||||
$scope.filterMatches = function(regexstr, items, shouldMatch) {
|
||||
regexstr = regexstr || '.+';
|
||||
|
||||
try {
|
||||
var regex = new RegExp(regexstr);
|
||||
} catch (ex) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return items.filter(function(item) {
|
||||
var value = item['value'];
|
||||
var m = value.match(regex);
|
||||
var matches = !!(m && m[0].length == value.length);
|
||||
return matches == shouldMatch;
|
||||
});
|
||||
};
|
||||
}
|
||||
};
|
||||
return directiveDefinitionObject;
|
||||
});
|
|
@ -1,126 +0,0 @@
|
|||
/**
|
||||
* An element which displays the steps of the wizard-like dialog, changing them as each step
|
||||
* is completed.
|
||||
*/
|
||||
angular.module('quay').directive('stepView', function ($compile) {
|
||||
var directiveDefinitionObject = {
|
||||
priority: 0,
|
||||
templateUrl: '/static/directives/step-view.html',
|
||||
replace: true,
|
||||
transclude: true,
|
||||
restrict: 'C',
|
||||
scope: {
|
||||
'nextStepCounter': '=nextStepCounter',
|
||||
'currentStepValid': '=currentStepValid',
|
||||
|
||||
'stepsCompleted': '&stepsCompleted'
|
||||
},
|
||||
controller: function($scope, $element, $rootScope) {
|
||||
var currentStepIndex = -1;
|
||||
var steps = [];
|
||||
var watcher = null;
|
||||
|
||||
// Members on 'this' are accessed by the individual steps.
|
||||
this.register = function(scope, element) {
|
||||
element.hide();
|
||||
|
||||
steps.push({
|
||||
'scope': scope,
|
||||
'element': element
|
||||
});
|
||||
|
||||
nextStep();
|
||||
};
|
||||
|
||||
var getCurrentStep = function() {
|
||||
return steps[currentStepIndex];
|
||||
};
|
||||
|
||||
var reset = function() {
|
||||
currentStepIndex = -1;
|
||||
for (var i = 0; i < steps.length; ++i) {
|
||||
steps[i].element.hide();
|
||||
}
|
||||
|
||||
$scope.currentStepValid = false;
|
||||
};
|
||||
|
||||
var next = function() {
|
||||
if (currentStepIndex >= 0) {
|
||||
var currentStep = getCurrentStep();
|
||||
if (!currentStep || !currentStep.scope) { return; }
|
||||
|
||||
if (!currentStep.scope.completeCondition) {
|
||||
return;
|
||||
}
|
||||
|
||||
currentStep.element.hide();
|
||||
|
||||
if (unwatch) {
|
||||
unwatch();
|
||||
unwatch = null;
|
||||
}
|
||||
}
|
||||
|
||||
currentStepIndex++;
|
||||
|
||||
if (currentStepIndex < steps.length) {
|
||||
var currentStep = getCurrentStep();
|
||||
currentStep.element.show();
|
||||
currentStep.scope.load()
|
||||
|
||||
unwatch = currentStep.scope.$watch('completeCondition', function(cc) {
|
||||
$scope.currentStepValid = !!cc;
|
||||
});
|
||||
} else {
|
||||
$scope.stepsCompleted();
|
||||
}
|
||||
};
|
||||
|
||||
var nextStep = function() {
|
||||
if (!steps || !steps.length) { return; }
|
||||
|
||||
if ($scope.nextStepCounter >= 0) {
|
||||
next();
|
||||
} else {
|
||||
reset();
|
||||
}
|
||||
};
|
||||
|
||||
$scope.$watch('nextStepCounter', nextStep);
|
||||
}
|
||||
};
|
||||
return directiveDefinitionObject;
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* A step in the step view.
|
||||
*/
|
||||
angular.module('quay').directive('stepViewStep', function () {
|
||||
var directiveDefinitionObject = {
|
||||
priority: 1,
|
||||
require: '^stepView',
|
||||
templateUrl: '/static/directives/step-view-step.html',
|
||||
replace: false,
|
||||
transclude: true,
|
||||
restrict: 'C',
|
||||
scope: {
|
||||
'completeCondition': '=completeCondition',
|
||||
'loadCallback': '&loadCallback',
|
||||
'loadMessage': '@loadMessage'
|
||||
},
|
||||
link: function(scope, element, attrs, controller) {
|
||||
controller.register(scope, element);
|
||||
},
|
||||
controller: function($scope, $element) {
|
||||
$scope.load = function() {
|
||||
$scope.loading = true;
|
||||
$scope.loadCallback({'callback': function() {
|
||||
$scope.loading = false;
|
||||
}});
|
||||
};
|
||||
}
|
||||
};
|
||||
return directiveDefinitionObject;
|
||||
});
|
|
@ -1,49 +0,0 @@
|
|||
/**
|
||||
* An element which displays custom git-specific setup information for its build triggers.
|
||||
*/
|
||||
angular.module('quay').directive('triggerSetupCustom', function() {
|
||||
var directiveDefinitionObject = {
|
||||
priority: 0,
|
||||
templateUrl: '/static/directives/trigger-setup-custom.html',
|
||||
replace: false,
|
||||
transclude: false,
|
||||
restrict: 'C',
|
||||
scope: {
|
||||
'repository': '=repository',
|
||||
'trigger': '=trigger',
|
||||
|
||||
'nextStepCounter': '=nextStepCounter',
|
||||
'currentStepValid': '=currentStepValid',
|
||||
|
||||
'analyze': '&analyze'
|
||||
},
|
||||
controller: function($scope, $element, ApiService) {
|
||||
$scope.analyzeCounter = 0;
|
||||
$scope.setupReady = false;
|
||||
|
||||
$scope.state = {
|
||||
'build_source': null,
|
||||
'subdir': null
|
||||
};
|
||||
|
||||
$scope.stepsCompleted = function() {
|
||||
$scope.analyze({'isValid': $scope.state.build_source != null && $scope.state.subdir != null});
|
||||
};
|
||||
|
||||
$scope.$watch('state.build_source', function(build_source) {
|
||||
$scope.trigger['config']['build_source'] = build_source;
|
||||
});
|
||||
|
||||
$scope.$watch('state.subdir', function(subdir) {
|
||||
$scope.trigger['config']['subdir'] = subdir;
|
||||
$scope.trigger.$ready = subdir != null;
|
||||
});
|
||||
|
||||
$scope.nopLoad = function(callback) {
|
||||
callback();
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
return directiveDefinitionObject;
|
||||
});
|
|
@ -1,242 +0,0 @@
|
|||
/**
|
||||
* An element which displays hosted Git (GitHub, Bitbucket)-specific setup information for its build triggers.
|
||||
*/
|
||||
angular.module('quay').directive('triggerSetupGithost', function () {
|
||||
var directiveDefinitionObject = {
|
||||
priority: 0,
|
||||
templateUrl: '/static/directives/trigger-setup-githost.html',
|
||||
replace: false,
|
||||
transclude: false,
|
||||
restrict: 'C',
|
||||
scope: {
|
||||
'repository': '=repository',
|
||||
'trigger': '=trigger',
|
||||
'kind': '@kind',
|
||||
|
||||
'nextStepCounter': '=nextStepCounter',
|
||||
'currentStepValid': '=currentStepValid',
|
||||
|
||||
'analyze': '&analyze'
|
||||
},
|
||||
controller: function($scope, $element, ApiService, TriggerService) {
|
||||
$scope.analyzeCounter = 0;
|
||||
$scope.setupReady = false;
|
||||
$scope.refs = null;
|
||||
$scope.branchNames = null;
|
||||
$scope.tagNames = null;
|
||||
|
||||
$scope.state = {
|
||||
'currentRepo': null,
|
||||
'branchTagFilter': '',
|
||||
'hasBranchTagFilter': false,
|
||||
'isInvalidLocation': true,
|
||||
'currentLocation': null
|
||||
};
|
||||
|
||||
var checkLocation = function() {
|
||||
var location = $scope.state.currentLocation || '';
|
||||
$scope.state.isInvalidLocation = $scope.supportsFullListing &&
|
||||
$scope.locations.indexOf(location) < 0;
|
||||
};
|
||||
|
||||
$scope.isMatching = function(kind, name, filter) {
|
||||
try {
|
||||
var patt = new RegExp(filter);
|
||||
} catch (ex) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var fullname = (kind + '/' + name);
|
||||
var m = fullname.match(patt);
|
||||
return m && m[0].length == fullname.length;
|
||||
}
|
||||
|
||||
$scope.addRef = function(kind, name) {
|
||||
if ($scope.isMatching(kind, name, $scope.state.branchTagFilter)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var newFilter = kind + '/' + name;
|
||||
var existing = $scope.state.branchTagFilter;
|
||||
if (existing) {
|
||||
$scope.state.branchTagFilter = '(' + existing + ')|(' + newFilter + ')';
|
||||
} else {
|
||||
$scope.state.branchTagFilter = newFilter;
|
||||
}
|
||||
}
|
||||
|
||||
$scope.stepsCompleted = function() {
|
||||
$scope.analyze({'isValid': !$scope.state.isInvalidLocation});
|
||||
};
|
||||
|
||||
$scope.loadRepositories = function(callback) {
|
||||
if (!$scope.trigger || !$scope.repository) { return; }
|
||||
|
||||
var params = {
|
||||
'repository': $scope.repository.namespace + '/' + $scope.repository.name,
|
||||
'trigger_uuid': $scope.trigger.id
|
||||
};
|
||||
|
||||
ApiService.listTriggerBuildSources(null, params).then(function(resp) {
|
||||
$scope.orgs = resp['sources'];
|
||||
setupTypeahead();
|
||||
callback();
|
||||
}, ApiService.errorDisplay('Cannot load repositories'));
|
||||
};
|
||||
|
||||
$scope.loadBranchesAndTags = function(callback) {
|
||||
if (!$scope.trigger || !$scope.repository) { return; }
|
||||
|
||||
var params = {
|
||||
'repository': $scope.repository.namespace + '/' + $scope.repository.name,
|
||||
'trigger_uuid': $scope.trigger['id'],
|
||||
'field_name': 'refs'
|
||||
};
|
||||
|
||||
ApiService.listTriggerFieldValues($scope.trigger['config'], params).then(function(resp) {
|
||||
$scope.refs = resp['values'];
|
||||
$scope.branchNames = [];
|
||||
$scope.tagNames = [];
|
||||
|
||||
for (var i = 0; i < $scope.refs.length; ++i) {
|
||||
var ref = $scope.refs[i];
|
||||
if (ref.kind == 'branch') {
|
||||
$scope.branchNames.push(ref.name);
|
||||
} else {
|
||||
$scope.tagNames.push(ref.name);
|
||||
}
|
||||
}
|
||||
|
||||
callback();
|
||||
}, ApiService.errorDisplay('Cannot load branch and tag names'));
|
||||
};
|
||||
|
||||
$scope.loadLocations = function(callback) {
|
||||
if (!$scope.trigger || !$scope.repository) { return; }
|
||||
|
||||
$scope.locations = null;
|
||||
|
||||
var params = {
|
||||
'repository': $scope.repository.namespace + '/' + $scope.repository.name,
|
||||
'trigger_uuid': $scope.trigger.id
|
||||
};
|
||||
|
||||
ApiService.listBuildTriggerSubdirs($scope.trigger['config'], params).then(function(resp) {
|
||||
if (resp['status'] == 'error') {
|
||||
$scope.locations = [];
|
||||
callback(resp['message'] || 'Could not load Dockerfile locations');
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.locations = resp['subdir'] || [];
|
||||
|
||||
// Select a default location (if any).
|
||||
if ($scope.locations.length > 0) {
|
||||
$scope.setLocation($scope.locations[0]);
|
||||
} else {
|
||||
$scope.state.currentLocation = null;
|
||||
$scope.trigger.$ready = true;
|
||||
checkLocation();
|
||||
}
|
||||
|
||||
callback();
|
||||
}, ApiService.errorDisplay('Cannot load locations'));
|
||||
}
|
||||
|
||||
$scope.handleLocationInput = function(location) {
|
||||
$scope.trigger['config']['subdir'] = location || '';
|
||||
$scope.trigger.$ready = true;
|
||||
checkLocation();
|
||||
};
|
||||
|
||||
$scope.handleLocationSelected = function(datum) {
|
||||
$scope.setLocation(datum['value']);
|
||||
};
|
||||
|
||||
$scope.setLocation = function(location) {
|
||||
$scope.state.currentLocation = location;
|
||||
$scope.trigger['config']['subdir'] = location || '';
|
||||
$scope.trigger.$ready = true;
|
||||
checkLocation();
|
||||
};
|
||||
|
||||
$scope.selectRepo = function(repo, org) {
|
||||
$scope.state.currentRepo = {
|
||||
'repo': repo,
|
||||
'avatar_url': org['info']['avatar_url'],
|
||||
'toString': function() {
|
||||
return this.repo;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
$scope.selectRepoInternal = function(currentRepo) {
|
||||
$scope.trigger.$ready = false;
|
||||
|
||||
var params = {
|
||||
'repository': $scope.repository.namespace + '/' + $scope.repository.name,
|
||||
'trigger_uuid': $scope.trigger['id']
|
||||
};
|
||||
|
||||
var repo = currentRepo['repo'];
|
||||
$scope.trigger['config'] = {
|
||||
'build_source': repo,
|
||||
'subdir': ''
|
||||
};
|
||||
};
|
||||
|
||||
$scope.scmIcon = function(kind) {
|
||||
return TriggerService.getIcon(kind);
|
||||
};
|
||||
|
||||
var setupTypeahead = function() {
|
||||
var repos = [];
|
||||
for (var i = 0; i < $scope.orgs.length; ++i) {
|
||||
var org = $scope.orgs[i];
|
||||
var orepos = org['repos'];
|
||||
for (var j = 0; j < orepos.length; ++j) {
|
||||
var repoValue = {
|
||||
'repo': orepos[j],
|
||||
'avatar_url': org['info']['avatar_url'],
|
||||
'toString': function() {
|
||||
return this.repo;
|
||||
}
|
||||
};
|
||||
var datum = {
|
||||
'name': orepos[j],
|
||||
'org': org,
|
||||
'value': orepos[j],
|
||||
'title': orepos[j],
|
||||
'item': repoValue
|
||||
};
|
||||
repos.push(datum);
|
||||
}
|
||||
}
|
||||
|
||||
$scope.repoLookahead = repos;
|
||||
};
|
||||
|
||||
$scope.$watch('trigger', function(trigger) {
|
||||
if (!trigger) { return; }
|
||||
$scope.supportsFullListing = TriggerService.supportsFullListing(trigger.service)
|
||||
});
|
||||
|
||||
$scope.$watch('state.currentRepo', function(repo) {
|
||||
if (repo) {
|
||||
$scope.selectRepoInternal(repo);
|
||||
}
|
||||
});
|
||||
|
||||
$scope.$watch('state.branchTagFilter', function(bf) {
|
||||
if (!$scope.trigger) { return; }
|
||||
|
||||
if ($scope.state.hasBranchTagFilter) {
|
||||
$scope.trigger['config']['branchtag_regex'] = bf;
|
||||
} else {
|
||||
delete $scope.trigger['config']['branchtag_regex'];
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
return directiveDefinitionObject;
|
||||
});
|
Reference in a new issue