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
|
@ -1397,16 +1397,38 @@ a:focus {
|
|||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.co-check-bar .co-filter-box input, .co-top-bar .co-filter-box input {
|
||||
.co-check-bar .co-filter-box input, .co-top-bar .co-filter-box input[type="text"] {
|
||||
width: 300px;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.co-check-bar .co-filter-box input, .co-top-bar .co-filter-box label {
|
||||
margin-left: 6px;
|
||||
}
|
||||
|
||||
.co-top-bar .co-filter-box input {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 767px) {
|
||||
.co-top-bar .page-controls {
|
||||
display: block;
|
||||
margin-bottom: 10px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.co-top-bar .co-filter-box {
|
||||
display: block;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.co-top-bar .filter-options {
|
||||
display: block;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.empty {
|
||||
border-bottom: none !important;
|
||||
}
|
||||
|
|
64
static/css/directives/ui/linear-workflow.css
Normal file
64
static/css/directives/ui/linear-workflow.css
Normal file
|
@ -0,0 +1,64 @@
|
|||
.linear-workflow-section {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.linear-workflow-section.row {
|
||||
margin-left: 0px;
|
||||
margin-right: 0px;
|
||||
}
|
||||
|
||||
.linear-workflow .upcoming-table {
|
||||
vertical-align: middle;
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.linear-workflow .upcoming-table .fa {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.linear-workflow .upcoming {
|
||||
color: #888;
|
||||
vertical-align: middle;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.linear-workflow .upcoming ul {
|
||||
padding: 0px;
|
||||
display: inline-block;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
.linear-workflow .upcoming li {
|
||||
display: inline-block;
|
||||
margin-right: 6px;
|
||||
margin-left: 2px;
|
||||
}
|
||||
|
||||
.linear-workflow .upcoming li:after {
|
||||
content: "•";
|
||||
display: inline-block;
|
||||
margin-left: 6px;
|
||||
margin-right: 2px;
|
||||
}
|
||||
|
||||
.linear-workflow .upcoming li:last-child:after {
|
||||
content: "";
|
||||
}
|
||||
|
||||
.linear-workflow .bottom-controls {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.linear-workflow-section-element {
|
||||
padding: 20px;
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.linear-workflow-section-element h3, .linear-workflow-section-element strong {
|
||||
color: #444;
|
||||
}
|
||||
|
||||
.linear-workflow-section-element.current-section h3,
|
||||
.linear-workflow-section-element.current-section strong {
|
||||
color: black;
|
||||
}
|
106
static/css/directives/ui/manage-trigger-control.css
Normal file
106
static/css/directives/ui/manage-trigger-control.css
Normal file
|
@ -0,0 +1,106 @@
|
|||
.manage-trigger-control .help-col {
|
||||
padding: 30px;
|
||||
padding-top: 100px;
|
||||
}
|
||||
|
||||
.manage-trigger-control .main-col {
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.manage-trigger-control strong {
|
||||
margin-bottom: 10px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.manage-trigger-control .namespace-avatar {
|
||||
margin-right: 4px;
|
||||
width: 24px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.manage-trigger-control .importance-col {
|
||||
text-align: center;
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
.manage-trigger-control .co-top-bar {
|
||||
margin-top: 20px;
|
||||
height: 28px;
|
||||
}
|
||||
|
||||
.manage-trigger-control .namespace-avatar {
|
||||
margin-left: 2px;
|
||||
margin-right: 2px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.manage-trigger-control .service-icon {
|
||||
font-size: 24px;
|
||||
margin-right: 4px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.manage-trigger-control .fa-exclamation-triangle {
|
||||
color: #FCA657;
|
||||
}
|
||||
|
||||
.manage-trigger-control .empty-description {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.manage-trigger-control .radio {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.manage-trigger-control .radio input[type="radio"] {
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.manage-trigger-control .radio label .title {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.manage-trigger-control .radio label .weak {
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.manage-trigger-control .radio label .description {
|
||||
margin-top: 6px;
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
.manage-trigger-control .radio label .extended {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.manage-trigger-control .radio label td:first-child {
|
||||
vertical-align: top;
|
||||
padding: 4px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.manage-trigger-control .regex-match-view {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.manage-trigger-control h3 .fa {
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.manage-trigger-control h3.warning {
|
||||
color: #FCA657;
|
||||
}
|
||||
|
||||
.manage-trigger-control h3.error {
|
||||
color: #D64456;
|
||||
}
|
||||
|
||||
.manage-trigger-control .success {
|
||||
color: #2FC98E !important;
|
||||
}
|
||||
|
||||
.manage-trigger-control .nowrap-col {
|
||||
white-space: nowrap;
|
||||
}
|
36
static/css/directives/ui/regex-match-view.css
Normal file
36
static/css/directives/ui/regex-match-view.css
Normal file
|
@ -0,0 +1,36 @@
|
|||
.regex-match-view-element .match-list {
|
||||
list-style: none;
|
||||
overflow: auto;
|
||||
max-height: 150px;
|
||||
}
|
||||
|
||||
.regex-match-view-element .match-list li {
|
||||
display: inline-block;
|
||||
margin-right: 4px;
|
||||
width: 120px;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.regex-match-view-element .match-list li .fa {
|
||||
margin-right: 4px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.regex-match-view-element .match-list.not-matching li {
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
.regex-match-view-element .match-list.matching li {
|
||||
color: #2fc98e;
|
||||
}
|
||||
|
||||
.regex-match-view-element .match-table td:first-child {
|
||||
vertical-align: top;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
|
||||
.regex-match-view-element .fa-exclamation-triangle {
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
.setup-trigger-directive-element .dockerfile-found-content {
|
||||
margin-left: 32px;
|
||||
}
|
||||
|
||||
.setup-trigger-directive-element .dockerfile-found-content:before {
|
||||
content: "\f071";
|
||||
font-family: FontAwesome;
|
||||
color: rgb(255, 194, 0);
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.setup-trigger-directive-element .loading {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.setup-trigger-directive-element .loading .cor-loader-inline {
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.setup-trigger-directive-element .dockerfile-found {
|
||||
position: relative;
|
||||
margin-bottom: 16px;
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
.step-view-step-content .loading-message {
|
||||
position: relative;
|
||||
text-align: center;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.step-view-step-content .loading-message .cor-loader-inline {
|
||||
margin-right: 6px;
|
||||
}
|
35
static/css/pages/trigger-setup.css
Normal file
35
static/css/pages/trigger-setup.css
Normal file
|
@ -0,0 +1,35 @@
|
|||
|
||||
.trigger-setup-element .activated .content {
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.trigger-setup-element .activated h3 {
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.trigger-setup-element .button-bar {
|
||||
text-align: right;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.trigger-setup-element .activating .cor-loader-inline {
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
.trigger-setup-element .activating .btn-success {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.trigger-setup-element .activating-message {
|
||||
padding: 10px;
|
||||
padding-left: 30px;
|
||||
}
|
||||
|
||||
.trigger-setup-element .activating-message b {
|
||||
vertical-align: middle;
|
||||
font-size: 18px;
|
||||
font-weight: normal;
|
||||
}
|
|
@ -4,9 +4,7 @@
|
|||
|
||||
<!-- Credentials -->
|
||||
<div ng-repeat="credential in trigger.config.credentials">
|
||||
<p>
|
||||
{{ credential.name }}:
|
||||
<div class="copy-box" value="credential.value"></div>
|
||||
</p>
|
||||
{{ credential.name }}:
|
||||
<div class="copy-box" value="credential.value"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
32
static/directives/dockerfile-path-select.html
Normal file
32
static/directives/dockerfile-path-select.html
Normal file
|
@ -0,0 +1,32 @@
|
|||
<div class="dockerfile-path-select-element">
|
||||
<div class="dropdown-select" placeholder="'Enter path containing a Dockerfile'"
|
||||
selected-item="selectedPath"
|
||||
lookahead-items="paths"
|
||||
handle-input="setPath(input)"
|
||||
handle-item-selected="setSelectedPath(datum.value)"
|
||||
allow-custom-input="true"
|
||||
hide-dropdown="!supportsFullListing">
|
||||
<!-- Icons -->
|
||||
<i class="dropdown-select-icon none-icon fa fa-folder-o fa-lg" ng-show="isUnknownPath"></i>
|
||||
<i class="dropdown-select-icon none-icon fa fa-folder fa-lg" style="color: black;" ng-show="!isUnknownPath"></i>
|
||||
<i class="dropdown-select-icon fa fa-folder fa-lg"></i>
|
||||
|
||||
<!-- Dropdown menu -->
|
||||
<ul class="dropdown-select-menu pull-right" role="menu">
|
||||
<li ng-repeat="path in paths">
|
||||
<a ng-click="setSelectedPath(path)" ng-if="path">
|
||||
<i class="fa fa-folder fa-lg"></i> {{ path }}
|
||||
</a>
|
||||
</li>
|
||||
<li class="dropdown-header" role="presentation" ng-show="!paths.length">
|
||||
No Dockerfiles found in repository
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div style="padding: 10px">
|
||||
<div class="co-alert co-alert-danger" ng-show="!isValidPath && currentPath">
|
||||
Path entered for folder containing Dockerfile is invalid: Must start with a '/'.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
6
static/directives/linear-workflow-section.html
Normal file
6
static/directives/linear-workflow-section.html
Normal file
|
@ -0,0 +1,6 @@
|
|||
<div class="linear-workflow-section-element" ng-show="sectionVisible"
|
||||
ng-class="isCurrentSection ? 'current-section' : ''">
|
||||
<form ng-submit="submitSection()">
|
||||
<div ng-transclude />
|
||||
</form>
|
||||
</div>
|
31
static/directives/linear-workflow.html
Normal file
31
static/directives/linear-workflow.html
Normal file
|
@ -0,0 +1,31 @@
|
|||
<div class="linear-workflow-element">
|
||||
<!-- Contents -->
|
||||
<div ng-transclude/>
|
||||
|
||||
<div class="bottom-controls">
|
||||
<table class="upcoming-table">
|
||||
<tr>
|
||||
<td>
|
||||
<!-- Next button -->
|
||||
<button class="btn btn-primary" ng-disabled="!currentSection.valid"
|
||||
ng-click="nextSection()"
|
||||
ng-class="{'btn-success': currentSection.index == sections.length - 1, 'btn-lg': currentSection.index == sections.length - 1}">
|
||||
<span ng-if="currentSection.index != sections.length - 1">Continue</span>
|
||||
<span ng-if="currentSection.index == sections.length - 1"><i class="fa fa-check-circle"></i>{{ doneTitle }}</span>
|
||||
</button>
|
||||
</td>
|
||||
<td>
|
||||
<!-- Next sections -->
|
||||
<div class="upcoming" ng-if="currentSection.index != sections.length - 1">
|
||||
<b>Next:</b>
|
||||
<ul>
|
||||
<li ng-repeat="section in sections" ng-if="section.index > currentSection.index">
|
||||
{{ section.title }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
43
static/directives/manage-trigger-custom-git.html
Normal file
43
static/directives/manage-trigger-custom-git.html
Normal file
|
@ -0,0 +1,43 @@
|
|||
<div class="manage-trigger-custom-git-element manage-trigger-control">
|
||||
<div class="linear-workflow" workflow-state="currentState" done-title="Create Trigger"
|
||||
workflow-complete="activateTrigger({'config': config})">
|
||||
<!-- Section: Repository -->
|
||||
<div class="linear-workflow-section row"
|
||||
section-id="repo"
|
||||
section-title="Git Repository"
|
||||
section-valid="config.build_source">
|
||||
|
||||
<div class="col-lg-7 col-md-7 col-sm-12 main-col">
|
||||
<h3>Enter repository</h3>
|
||||
<strong>
|
||||
Please enter the HTTP or SSH style URL used to clone your git repository:
|
||||
</strong>
|
||||
<input class="form-control" type="text" placeholder="git@example.com:namespace/repository.git"
|
||||
ng-model="config.build_source" ng-pattern="/(((http|https):\/\/)(.+)|\w+@(.+):(.+))/">
|
||||
</div>
|
||||
<div class="col-lg-5 col-md-5 hidden-sm hidden-xs help-col">
|
||||
<p>Custom git triggers support any externally accessible git repository, via either the normal git protocol or HTTP.</p>
|
||||
|
||||
<p><b>It is the responsibility of the git repository to invoke a webhook to tell <span class="registry-name" short="true"></span> that a commit has been added.</b></p>
|
||||
</div>
|
||||
</div><!-- /Section: Repository -->
|
||||
|
||||
<!-- Section: Build context -->
|
||||
<div class="linear-workflow-section row"
|
||||
section-id="dockerfile"
|
||||
section-title="Build context"
|
||||
section-valid="config.subdir">
|
||||
|
||||
<div class="col-lg-7 col-md-7 col-sm-12 main-col">
|
||||
<h3>Select build context directory</h3>
|
||||
<strong>Please select the build context directory under the git repository:</strong>
|
||||
<input class="form-control" type="text" placeholder="/"
|
||||
ng-model="config.subdir" ng-pattern="/^($|\/|\/.+)/">
|
||||
</div>
|
||||
<div class="col-lg-5 col-md-5 hidden-sm hidden-xs help-col">
|
||||
<p>The build context directory is the path of the directory containing the Dockerfile and any other files to be made available when the build is triggered.</p>
|
||||
<p>If the Dockerfile is located at the root of the git repository, enter <code>/</code> as the build context directory.</p>
|
||||
</div>
|
||||
|
||||
</div><!-- /Section: Build context -->
|
||||
</div>
|
330
static/directives/manage-trigger-githost.html
Normal file
330
static/directives/manage-trigger-githost.html
Normal file
|
@ -0,0 +1,330 @@
|
|||
<div class="manage-trigger-githost-element manage-trigger-control">
|
||||
<div class="linear-workflow" workflow-state="currentState" done-title="Create Trigger"
|
||||
workflow-complete="createTrigger()">
|
||||
|
||||
<!-- Section: Namespace -->
|
||||
<div class="linear-workflow-section row"
|
||||
section-id="namespace"
|
||||
section-title="{{ 'Select ' + namespaceTitle }}"
|
||||
section-valid="local.selectedNamespace">
|
||||
<div class="col-lg-7 col-md-7 col-sm-12 main-col" ng-show="local.namespaces">
|
||||
<h3>Select {{ namespaceTitle }}</h3>
|
||||
<strong>
|
||||
Please select the {{ namespaceTitle }} under which the repository lives
|
||||
</strong>
|
||||
|
||||
<div class="co-top-bar">
|
||||
<div class="co-filter-box">
|
||||
<span class="page-controls" total-count="local.orderedNamespaces.entries.length" current-page="local.namespaceOptions.page" page-size="namespacesPerPage"></span>
|
||||
<input class="form-control" type="text" ng-model="local.namespaceOptions.filter" placeholder="Filter {{ namespaceTitle }}s...">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table class="co-table" style="margin-top: 20px;">
|
||||
<thead>
|
||||
<td class="checkbox-col"></td>
|
||||
<td ng-class="TableService.tablePredicateClass('id', local.namespaceOptions.predicate, local.namespaceOptions.reverse)">
|
||||
<a ng-click="TableService.orderBy('id', local.namespaceOptions)">{{ namespaceTitle }}</a>
|
||||
</td>
|
||||
<td ng-class="TableService.tablePredicateClass('score', local.namespaceOptions.predicate, local.namespaceOptions.reverse)"
|
||||
class="importance-col hidden-xs">
|
||||
<a ng-click="TableService.orderBy('score', local.namespaceOptions)">Importance</a>
|
||||
</td>
|
||||
</thead>
|
||||
|
||||
<tr class="co-checkable-row"
|
||||
ng-repeat="namespace in local.orderedNamespaces.visibleEntries | slice:(namespacesPerPage * local.namespaceOptions.page):(namespacesPerPage * (local.namespaceOptions.page + 1))"
|
||||
ng-class="local.selectedNamespace == namespace ? 'checked' : ''"
|
||||
bindonce>
|
||||
<td>
|
||||
<input type="radio" ng-model="local.selectedNamespace" ng-value="namespace">
|
||||
</td>
|
||||
<td>
|
||||
<img class="namespace-avatar" ng-src="{{ namespace.avatar_url }}">
|
||||
<span class="anchor" href="{{ namespace.url }}" is-text-only="!namespace.url">{{ namespace.id }}</span>
|
||||
</td>
|
||||
<td class="importance-col hidden-xs">
|
||||
<span class="strength-indicator" value="::namespace.score" maximum="::local.maxScore"
|
||||
log-base="10"></span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<div class="empty" ng-if="local.namespaces.length && !local.orderedNamespaces.entries.length"
|
||||
style="margin-top: 20px;">
|
||||
<div class="empty-primary-msg">No matching {{ namespaceTitle }} found.</div>
|
||||
<div class="empty-secondary-msg">Try expanding your filtering terms.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-8 col-md-8 col-sm-12 main-col" ng-show="!local.namespaces">
|
||||
<span class="cor-loader-inline"></span> Retrieving {{ namespaceTitle }}s
|
||||
</div>
|
||||
<div class="col-lg-4 col-md-4 hidden-sm hidden-xs help-col" ng-show="local.namespaces">
|
||||
<p>
|
||||
<span class="registry-name"></span> has been granted access to read and view these {{ namespaceTitle }}s.
|
||||
</p>
|
||||
<p>
|
||||
Don't see an expected {{ namespaceTitle }} here? Please make sure third-party access is enabled for <span class="registry-name"></span> under that {{ namespaceTitle }}.
|
||||
</p>
|
||||
</div>
|
||||
</div><!-- /Section: Namespace -->
|
||||
|
||||
<!-- Section: Repository -->
|
||||
<div class="linear-workflow-section row"
|
||||
section-id="repo"
|
||||
section-title="Select Repository"
|
||||
section-valid="local.selectedRepository">
|
||||
|
||||
<div class="col-lg-7 col-md-7 col-sm-12 main-col" ng-show="local.repositories">
|
||||
<h3>Select Repository</h3>
|
||||
<strong>
|
||||
Select a repository in
|
||||
<img class="namespace-avatar" ng-src="{{ local.selectedNamespace.avatar_url }}">
|
||||
{{ local.selectedNamespace.id }}
|
||||
</strong>
|
||||
|
||||
<div class="co-top-bar">
|
||||
<div class="co-filter-box">
|
||||
<span class="page-controls" total-count="local.orderedRepositories.entries.length" current-page="local.repositoryOptions.page" page-size="repositoriesPerPage"></span>
|
||||
<input class="form-control" type="text" ng-model="local.repositoryOptions.filter" placeholder="Filter repositories...">
|
||||
<div class="filter-options">
|
||||
<label><input type="checkbox" ng-model="local.repositoryOptions.hideStale">Hide stale repositories</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table class="co-table" style="margin-top: 20px;">
|
||||
<thead>
|
||||
<td class="checkbox-col"></td>
|
||||
<td ng-class="TableService.tablePredicateClass('name', local.repositoryOptions.predicate, local.repositoryOptions.reverse)" class="nowrap-col">
|
||||
<a ng-click="TableService.orderBy('name', local.repositoryOptions)">Repository Name</a>
|
||||
</td>
|
||||
<td ng-class="TableService.tablePredicateClass('last_updated_datetime', local.repositoryOptions.predicate, local.repositoryOptions.reverse)"
|
||||
class="last-updated-col nowrap-col">
|
||||
<a ng-click="TableService.orderBy('last_updated_datetime', local.namespaceOptions)">Last Updated</a>
|
||||
</td>
|
||||
<td class="hidden-xs">Description</td>
|
||||
</thead>
|
||||
|
||||
<tr class="co-checkable-row"
|
||||
ng-repeat="repository in local.orderedRepositories.visibleEntries | slice:(repositoriesPerPage * local.repositoryOptions.page):(repositoriesPerPage * (local.repositoryOptions.page + 1))"
|
||||
ng-class="local.selectedRepository == repository ? 'checked' : ''"
|
||||
bindonce>
|
||||
<td>
|
||||
<span ng-if="!repository.has_admin_permissions">
|
||||
<i class="fa fa-exclamation-triangle"
|
||||
data-title="Admin access is required to add the webhook trigger to this repository" bs-tooltip></i>
|
||||
</span>
|
||||
<input type="radio" ng-model="local.selectedRepository" ng-value="repository"
|
||||
ng-if="repository.has_admin_permissions">
|
||||
</td>
|
||||
<td class="nowrap-col">
|
||||
<i class="service-icon fa {{ getTriggerIcon() }}"></i>
|
||||
<span class="anchor" href="{{ repository.url }}" is-text-only="!repository.url">{{ repository.name }}</span>
|
||||
</td>
|
||||
<td class="last-updated-col nowrap-col">
|
||||
<span am-time-ago="repository.last_updated_datetime"></span>
|
||||
</td>
|
||||
<td class="hidden-xs">
|
||||
<span ng-if="repository.description">{{ repository.description }}</span>
|
||||
<span class="empty-description" ng-if="!repository.description">(None)</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<div class="empty" ng-if="local.repositories.length && !local.orderedRepositories.entries.length"
|
||||
style="margin-top: 20px;">
|
||||
<div class="empty-primary-msg">No matching repositories found.</div>
|
||||
<div class="empty-secondary-msg">Try expanding your filtering terms.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-8 col-md-8 col-sm-12 main-col" ng-show="!local.repositories">
|
||||
<span class="cor-loader-inline"></span> Retrieving repositories
|
||||
</div>
|
||||
<div class="col-lg-4 col-md-4 hidden-sm hidden-xs help-col" ng-show="local.repositories">
|
||||
<p>
|
||||
A webhook will be added to the selected repository in order to detect when new commits are made.
|
||||
</p>
|
||||
<p>
|
||||
Don't see an expected repository here? Please make sure you have admin access on that repository.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</div><!-- /Section: Repository -->
|
||||
|
||||
<!-- Section: Trigger Options -->
|
||||
<div class="linear-workflow-section row"
|
||||
section-id="triggeroptions"
|
||||
section-title="Configure Trigger"
|
||||
section-valid="local.triggerOptions">
|
||||
<div class="col-lg-7 col-md-7 col-sm-12 main-col" ng-show="local.repositoryRefs">
|
||||
<h3>Configure Trigger</h3>
|
||||
<strong>
|
||||
Configure trigger options for
|
||||
<img class="namespace-avatar" ng-src="{{ local.selectedNamespace.avatar_url }}">
|
||||
{{ local.selectedNamespace.id }}/{{ local.selectedRepository.name }}
|
||||
</strong>
|
||||
|
||||
<div class="radio" style="margin-top: 20px;">
|
||||
<label>
|
||||
<input type="radio" name="optionRadio" ng-model="local.triggerOptions.hasBranchTagFilter" ng-value="false">
|
||||
<div class="title">Trigger for all branches and tags <span class="weak">(default)</span></div>
|
||||
<div class="description">Build a container image for each commit across all branches and tags</div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="radio">
|
||||
<label>
|
||||
<input type="radio" name="optionRadio" ng-model="local.triggerOptions.hasBranchTagFilter" ng-value="true">
|
||||
<div class="title">Trigger only on branches and tags matching a regular expression</div>
|
||||
<div class="description">Only build container images for a subset of branches and/or tags.</div>
|
||||
<div class="extended" ng-if="local.triggerOptions.hasBranchTagFilter">
|
||||
<table>
|
||||
<tr>
|
||||
<td style="white-space: nowrap;">Regular Expression:</td>
|
||||
<td>
|
||||
<input type="text" class="form-control" ng-model="local.triggerOptions.branchTagFilter" required>
|
||||
<div class="description">Examples: heads/master, tags/tagname, heads/.+</div>
|
||||
<div class="regex-match-view"
|
||||
items="local.repositoryFullRefs"
|
||||
regex="local.triggerOptions.branchTagFilter"
|
||||
ng-if="local.triggerOptions.branchTagFilter"></div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-8 col-md-8 col-sm-12 main-col" ng-show="!local.repositoryRefs">
|
||||
<span class="cor-loader-inline"></span> Retrieving repository refs
|
||||
</div>
|
||||
<div class="col-lg-4 col-md-4 hidden-sm hidden-xs help-col">
|
||||
<p>Do you want to build a new container image for commits across all branches and tags, or limit to a subset?</p>
|
||||
<p>For example, if you use release branches instead of <code>master</code> for building versions of your software, you can configure the trigger to only build images for these branches.</p>
|
||||
<p>All images built will be tagged with the name of the branch or tag whose change invoked the trigger</p>
|
||||
</div>
|
||||
</div><!-- /Section: Trigger Options -->
|
||||
|
||||
<!-- Section: Dockerfile Location -->
|
||||
<div class="linear-workflow-section row"
|
||||
section-id="dockerfilelocation"
|
||||
section-title="Select Dockerfile"
|
||||
section-valid="local.hasValidDockerfilePath">
|
||||
<div class="col-lg-7 col-md-7 col-sm-12 main-col" ng-show="local.dockerfileLocations.status == 'error'">
|
||||
<div class="co-alert co-alert-warning">
|
||||
{{ local.dockerfileLocations.message }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-7 col-md-7 col-sm-12 main-col" ng-show="local.dockerfileLocations.status == 'success'">
|
||||
<h3>Select Dockerfile</h3>
|
||||
<strong>
|
||||
Please select the location of the Dockerfile to build when this trigger is invoked
|
||||
</strong>
|
||||
|
||||
<div class="dockerfile-path-select" current-path="local.dockerfilePath" paths="local.dockerfileLocations.subdir"
|
||||
supports-full-listing="true" is-valid-path="local.hasValidDockerfilePath"></div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-8 col-md-8 col-sm-12 main-col" ng-show="!local.dockerfileLocations">
|
||||
<span class="cor-loader-inline"></span> Retrieving Dockerfile locations
|
||||
</div>
|
||||
<div class="col-lg-4 col-md-4 hidden-sm hidden-xs help-col">
|
||||
<p>Please select the location containing the Dockerfile to be built.</p>
|
||||
<p>The build context will start at the location selected.</p>
|
||||
</div>
|
||||
</div><!-- /Section: Dockerfile Location -->
|
||||
|
||||
<!-- Section: Verification and Robot Account -->
|
||||
<div class="linear-workflow-section row"
|
||||
section-id="verification"
|
||||
section-title="Confirm"
|
||||
section-valid="local.triggerAnalysis.status != 'error' && (local.triggerAnalysis.status != 'requiresrobot' || local.robotAccount != null)">
|
||||
<!-- Error -->
|
||||
<div class="col-lg-7 col-md-7 col-sm-12 main-col" ng-show="local.triggerAnalysis.status == 'error'">
|
||||
<h3 class="error"><i class="fa fa-exclamation-circle"></i> Verification Error</h3>
|
||||
<strong>
|
||||
There was an error when verifying the state of <img class="namespace-avatar" ng-src="{{ local.selectedNamespace.avatar_url }}">
|
||||
{{ local.selectedNamespace.id }}/{{ local.selectedRepository.name }}
|
||||
</strong>
|
||||
|
||||
{{ local.triggerAnalysis.message }}
|
||||
</div>
|
||||
|
||||
<!-- Warning -->
|
||||
<div class="col-lg-7 col-md-7 col-sm-12 main-col" ng-show="local.triggerAnalysis.status == 'warning'">
|
||||
<h3 class="warning"><i class="fa fa-exclamation-triangle"></i> Verification Warning</h3>
|
||||
{{ local.triggerAnalysis.message }}
|
||||
</div>
|
||||
|
||||
<!-- Public base -->
|
||||
<div class="col-lg-7 col-md-7 col-sm-12 main-col" ng-show="local.triggerAnalysis.status == 'publicbase'">
|
||||
<h3 class="success"><i class="fa fa-check-circle"></i> Ready to go!</h3>
|
||||
<strong>Click "Create Trigger" to complete setup of this build trigger</strong>
|
||||
</div>
|
||||
|
||||
<!-- Requires robot and is not admin -->
|
||||
<div class="col-lg-7 col-md-7 col-sm-12 main-col" ng-show="local.triggerAnalysis.status == 'requiresrobot' && !local.triggerAnalysis.is_admin">
|
||||
<h3>Robot Account Required</h3>
|
||||
<p>The selected Dockerfile in the selected repository depends upon a private base image</p>
|
||||
<p>A robot account with access to the base image is required to setup this trigger, but you are not the administrator of this namespace.</p>
|
||||
<p>Administrative access is required to continue to ensure security of the robot credentials.</p>
|
||||
</div>
|
||||
|
||||
<!-- Requires robot and is admin -->
|
||||
<div class="col-lg-7 col-md-7 col-sm-12 main-col" ng-show="local.triggerAnalysis.status == 'requiresrobot' && local.triggerAnalysis.is_admin">
|
||||
<h3>Select Robot Account</h3>
|
||||
<strong>
|
||||
The selected Dockerfile in the selected repository depends upon a private base image. Select a robot account with access:
|
||||
</strong>
|
||||
|
||||
<div class="co-top-bar">
|
||||
<div class="co-filter-box">
|
||||
<span class="page-controls" total-count="local.orderedRobotAccounts.entries.length" current-page="local.robotOptions.page" page-size="robotsPerPage"></span>
|
||||
<input class="form-control" type="text" ng-model="local.robotOptions.filter" placeholder="Filter robot accounts...">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table class="co-table" style="margin-top: 20px;">
|
||||
<thead>
|
||||
<td class="checkbox-col"></td>
|
||||
<td ng-class="TableService.tablePredicateClass('name', local.robotOptions.predicate, local.robotOptions.reverse)">
|
||||
<a ng-click="TableService.orderBy('name', local.robotOptions)">Robot Account</a>
|
||||
</td>
|
||||
<td ng-class="TableService.tablePredicateClass('can_read', local.robotOptions.predicate, local.robotOptions.reverse)">
|
||||
<a ng-click="TableService.orderBy('can_read', local.robotOptions)">Has Read Access</a>
|
||||
</td>
|
||||
</thead>
|
||||
|
||||
<tr class="co-checkable-row"
|
||||
ng-repeat="robot in local.orderedRobotAccounts.visibleEntries | slice:(robotsPerPage * local.namespaceOptions.page):(robotsPerPage * (local.robotOptions.page + 1))"
|
||||
ng-class="local.robotAccount == robot ? 'checked' : ''"
|
||||
bindonce>
|
||||
<td>
|
||||
<input type="radio" ng-model="local.robotAccount" ng-value="robot">
|
||||
</td>
|
||||
<td>
|
||||
<span class="entity-reference" entity="robot"></span>
|
||||
</td>
|
||||
<td>
|
||||
<span ng-if="robot.can_read" class="success">Can Read</span>
|
||||
<span ng-if="!robot.can_read">Read access will be added if selected</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<div class="empty" ng-if="local.triggerAnalysis.robots.length && !local.orderedRobotAccounts.entries.length"
|
||||
style="margin-top: 20px;">
|
||||
<div class="empty-primary-msg">No matching robot accounts found.</div>
|
||||
<div class="empty-secondary-msg">Try expanding your filtering terms.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-4 col-md-4 hidden-sm hidden-xs help-col" ng-if="local.triggerAnalysis.status == 'requiresrobot' && local.triggerAnalysis.is_admin">
|
||||
<p>The Dockerfile you selected utilizes a private base image.</p>
|
||||
<p>In order for the <span class="registry-name"></span> to pull the base image during the build process, a robot account with access must be selected.</p>
|
||||
<p>Robot accounts that already have access to this base image are listed first. If you select a robot account that does not currently have access, read permission will be granted to that robot account on trigger creation.</p>
|
||||
</div>
|
||||
</div><!-- /Section: Robot Account -->
|
||||
|
||||
</div>
|
29
static/directives/regex-match-view.html
Normal file
29
static/directives/regex-match-view.html
Normal file
|
@ -0,0 +1,29 @@
|
|||
<div class="regex-match-view-element">
|
||||
<div ng-if="filterMatches(regex, items, false) == null">
|
||||
<i class="fa fa-exclamation-triangle"></i>Invalid Regular Expression!
|
||||
</div>
|
||||
<div ng-if="filterMatches(regex, items, false) != null">
|
||||
<table class="match-table">
|
||||
<tr>
|
||||
<td>Matching:</td>
|
||||
<td>
|
||||
<ul class="matching match-list">
|
||||
<li ng-repeat="item in filterMatches(regex, items, true)">
|
||||
<i class="fa {{ item.icon }}"></i>{{ item.title }}
|
||||
</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Not Matching:</td>
|
||||
<td>
|
||||
<ul class="not-matching match-list">
|
||||
<li ng-repeat="item in filterMatches(regex, items, false)">
|
||||
<i class="fa {{ item.icon }}"></i>{{ item.title }}
|
||||
</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
|
@ -126,10 +126,8 @@
|
|||
|
||||
<tr ng-repeat="trigger in triggers | filter:{'is_active':false}">
|
||||
<td colspan="5" style="text-align: center">
|
||||
<span class="cor-loader-inline"></span>
|
||||
Trigger Setup in progress:
|
||||
<a ng-click="setupTrigger(trigger)">Resume</a> |
|
||||
<a ng-click="deleteTrigger(trigger)">Cancel</a>
|
||||
This build trigger has not had its setup completed:
|
||||
<a ng-click="deleteTrigger(trigger)">Delete Trigger</a>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
|
@ -185,14 +183,6 @@
|
|||
build-started="handleBuildStarted(build)">
|
||||
</div>
|
||||
|
||||
<!-- Setup trigger dialog-->
|
||||
<div class="setup-trigger-dialog"
|
||||
repository="repository"
|
||||
trigger="currentSetupTrigger"
|
||||
canceled="cancelSetupTrigger(trigger)"
|
||||
counter="showTriggerSetupCounter"
|
||||
trigger-runner="askRunTrigger(trigger)"></div>
|
||||
|
||||
<!-- Manual trigger dialog -->
|
||||
<div class="manual-trigger-build-dialog"
|
||||
repository="repository"
|
||||
|
@ -201,5 +191,4 @@
|
|||
build-started="handleBuildStarted(build)"></div>
|
||||
|
||||
<!-- /Dialogs -->
|
||||
|
||||
</div>
|
||||
|
|
|
@ -1,133 +0,0 @@
|
|||
<div class="setup-trigger-directive-element">
|
||||
<!-- Modal message dialog -->
|
||||
<div class="modal fade" id="setupTriggerModal">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
<h4 class="modal-title">Setup new build trigger</h4>
|
||||
</div>
|
||||
<div class="modal-body loading" ng-show="currentView == 'activating'">
|
||||
<span class="cor-loader-inline"></span> Setting up trigger...
|
||||
</div>
|
||||
<div class="modal-body" ng-show="currentView != 'activating'">
|
||||
<!-- Trigger-specific setup -->
|
||||
<div class="trigger-description-element trigger-option-section" ng-switch on="trigger.service">
|
||||
<div ng-switch-when="custom-git">
|
||||
<div class="trigger-setup-custom" repository="repository" trigger="trigger"
|
||||
next-step-counter="nextStepCounter" current-step-valid="state.stepValid"
|
||||
analyze="checkAnalyze(isValid)"></div>
|
||||
</div>
|
||||
<div ng-switch-default>
|
||||
<div class="trigger-setup-githost" repository="repository" trigger="trigger"
|
||||
kind="{{ trigger.service }}"
|
||||
next-step-counter="nextStepCounter" current-step-valid="state.stepValid"
|
||||
analyze="checkAnalyze(isValid)"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Loading pull information -->
|
||||
<div ng-show="currentView == 'analyzing'" class="loading">
|
||||
<span class="cor-loader-inline"></span> Checking pull credential requirements...
|
||||
</div>
|
||||
|
||||
<!-- Pull information -->
|
||||
<div class="trigger-option-section" ng-show="currentView == 'analyzed'">
|
||||
|
||||
<!-- Messaging -->
|
||||
<div ng-switch on="pullInfo.analysis.status">
|
||||
<div ng-switch-when="error" class="alert alert-danger">{{ pullInfo.analysis.message }}</div>
|
||||
<div ng-switch-when="warning" class="alert alert-warning">{{ pullInfo.analysis.message }}</div>
|
||||
<div ng-switch-when="notimplemented" class="alert alert-warning">
|
||||
<p>For {{ TriggerService.getTitle(trigger.service) }} triggers, we are unable to determine dependencies automatically.</p>
|
||||
<p>If the git repository being built depends on a private base image, you must manually select a robot account with the proper permissions.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dockerfile-found" ng-if="pullInfo.analysis.is_public === false">
|
||||
<div class="dockerfile-found-content">
|
||||
A robot account is <strong>required</strong> for this build trigger because the
|
||||
Dockerfile found
|
||||
pulls from the private <span class="registry-name"></span> repository
|
||||
|
||||
<a href="/repository/{{ pullInfo.analysis.namespace }}/{{ pullInfo.analysis.name }}" ng-safenewtab>
|
||||
{{ pullInfo.analysis.namespace }}/{{ pullInfo.analysis.name }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 12px">
|
||||
Please select the credentials to use when pulling the base image:
|
||||
</div>
|
||||
<div ng-if="!isNamespaceAdmin(repository.namespace)" style="color: #aaa;">
|
||||
<strong>Note:</strong> In order to set pull credentials for a build trigger, you must be an
|
||||
Administrator of the namespace <strong>{{ repository.namespace }}</strong>
|
||||
</div>
|
||||
|
||||
<!-- Namespace admin -->
|
||||
<div ng-show="isNamespaceAdmin(repository.namespace)">
|
||||
<!-- Select credentials -->
|
||||
<div class="btn-group btn-group-sm">
|
||||
<button type="button" class="btn btn-default"
|
||||
ng-class="pullInfo.is_public ? 'active btn-info' : ''"
|
||||
ng-click="pullInfo.is_public = true">
|
||||
None
|
||||
</button>
|
||||
<button type="button" class="btn btn-default"
|
||||
ng-class="pullInfo.is_public ? '' : 'active btn-info'"
|
||||
ng-click="pullInfo.is_public = false">
|
||||
<i class="fa ci-robot"></i>
|
||||
Robot account
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Robot Select -->
|
||||
<div ng-show="!pullInfo.is_public" style="margin-top: 10px">
|
||||
<div class="entity-search" namespace="repository.namespace"
|
||||
placeholder="'Select robot account for pulling...'"
|
||||
current-entity="pullInfo.pull_entity"
|
||||
allowed-entities="['robot']"></div>
|
||||
|
||||
<div ng-if="pullInfo.analysis.robots.length" style="margin-top: 20px; margin-bottom: 0px;">
|
||||
<strong>Note</strong>: We've automatically selected robot account
|
||||
<span class="entity-reference" entity="pullInfo.analysis.robots[0]"></span>,
|
||||
since it has access to the private repository.
|
||||
</div>
|
||||
<div ng-if="!pullInfo.analysis.robots.length && pullInfo.analysis.name"
|
||||
style="margin-top: 20px; margin-bottom: 0px;">
|
||||
<strong>Note</strong>: No robot account currently has access to the private repository. Please create one and/or assign access in the
|
||||
<a href="/repository/{{ pullInfo.analysis.namespace }}/{{ pullInfo.analysis.name }}/admin" ng-safenewtab>
|
||||
repository's admin panel.
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="trigger-option-section" ng-show="currentView == 'postActivation'">
|
||||
<div ng-if="trigger.config.credentials" class="credentials" trigger="trigger"></div>
|
||||
<div ng-if="!trigger.config.credentials">
|
||||
<div class="alert alert-success">The trigger has been successfully created.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="modal-footer" ng-show="currentView != 'activating'">
|
||||
<button type="button" class="btn btn-primary" ng-disabled="!state.stepValid"
|
||||
ng-click="nextStepCounter = nextStepCounter + 1"
|
||||
ng-show="currentView == 'config'">Next</button>
|
||||
|
||||
<button type="button" class="btn btn-primary"
|
||||
ng-disabled="!trigger.$ready || (!pullInfo['is_public'] && !pullInfo['pull_entity'])"
|
||||
ng-click="activate()"
|
||||
ng-show="currentView == 'analyzed'">Create Trigger</button>
|
||||
|
||||
<button type="button" class="btn btn-success" ng-click="runTriggerNow()"
|
||||
ng-if="currentView == 'postActivation'">Run Trigger Now</button>
|
||||
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">{{ currentView == 'postActivation' ? 'Done' : 'Cancel' }}</button>
|
||||
</div>
|
||||
</div><!-- /.modal-content -->
|
||||
</div><!-- /.modal-dialog -->
|
||||
</div><!-- /.modal -->
|
||||
</div>
|
|
@ -1,9 +0,0 @@
|
|||
<div class="step-view-step-content">
|
||||
<div ng-show="!loading">
|
||||
<div ng-transclude></div>
|
||||
</div>
|
||||
<div ng-show="loading" class="loading-message">
|
||||
<span class="cor-loader-inline"></span>
|
||||
{{ loadMessage }}
|
||||
</div>
|
||||
</div>
|
|
@ -1,3 +0,0 @@
|
|||
<div class="step-view-element">
|
||||
<div class="transcluded" ng-transclude>
|
||||
</div>
|
|
@ -1,40 +0,0 @@
|
|||
<div class="trigger-setup-custom-element">
|
||||
<div class="selected-info" ng-show="nextStepCounter > 0">
|
||||
<table style="width: 100%;">
|
||||
<tr ng-show="nextStepCounter > 0">
|
||||
<td width="200px">Repository</td>
|
||||
<td>{{ state.build_source }}</td>
|
||||
</tr>
|
||||
|
||||
<tr ng-show="nextStepCounter > 1">
|
||||
<td>Dockerfile Location:</td>
|
||||
<td>
|
||||
<div class="dockerfile-location">
|
||||
<i class="fa fa-folder fa-lg"></i> {{ state.subdir || '/' }}
|
||||
</div>
|
||||
</td>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Step view -->
|
||||
<div class="step-view" next-step-counter="nextStepCounter" current-step-valid="currentStepValid"
|
||||
steps-completed="stepsCompleted()">
|
||||
|
||||
<!-- Git URL Input -->
|
||||
<!-- TODO(jschorr): make nopLoad(callback) no longer required -->
|
||||
<div class="step-view-step" complete-condition="trigger['config']['build_source']" load-callback="nopLoad(callback)"
|
||||
load-message="Loading Git URL Input">
|
||||
<div style="margin-bottom: 12px;">Please enter an HTTP or SSH style URL used to clone your git repository:</div>
|
||||
<input class="form-control" type="text" placeholder="git@example.com:namespace/repository.git" style="width: 100%;"
|
||||
ng-model="state.build_source" ng-pattern="/(((http|https):\/\/)(.+)|\w+@(.+):(.+))/">
|
||||
</div>
|
||||
|
||||
<!-- Dockerfile folder select -->
|
||||
<div class="step-view-step" complete-condition="trigger.$ready" load-callback="nopLoad(callback)"
|
||||
load-message="Loading Folder Input">
|
||||
<div style="margin-bottom: 12px">Dockerfile Location:</div>
|
||||
<input class="form-control" type="text" placeholder="/" style="width: 100%;"
|
||||
ng-model="state.subdir" ng-pattern="/^($|\/|\/.+)/">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -1,201 +0,0 @@
|
|||
<div class="trigger-setup-githost-element">
|
||||
<!-- Current selected info -->
|
||||
<div class="selected-info" ng-show="nextStepCounter > 0">
|
||||
<table style="width: 100%;">
|
||||
<tr ng-show="state.currentRepo && nextStepCounter > 0">
|
||||
<td width="200px">
|
||||
Repository:
|
||||
</td>
|
||||
<td>
|
||||
<div class="current-repo">
|
||||
<i class="dropdown-select-icon org-icon fa" ng-class="scmIcon(kind)"
|
||||
ng-show="!state.currentRepo.avatar_url"></i>
|
||||
<img class="dropdown-select-icon org-icon"
|
||||
ng-src="{{ state.currentRepo.avatar_url ? state.currentRepo.avatar_url : '/static/img/empty.png' }}"
|
||||
ng-show="state.currentRepo.avatar_url">
|
||||
{{ state.currentRepo.repo }}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr ng-show="nextStepCounter > 1">
|
||||
<td>
|
||||
Branches and Tags:
|
||||
</td>
|
||||
<td>
|
||||
<div class="ref-filter">
|
||||
<span ng-if="!state.hasBranchTagFilter">(Build All)</span>
|
||||
<span ng-if="state.hasBranchTagFilter">Regular Expression: <code>{{ state.branchTagFilter }}</code></span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr ng-show="nextStepCounter > 2">
|
||||
<td>
|
||||
Dockerfile Location:
|
||||
</td>
|
||||
<td>
|
||||
<div class="dockerfile-location">
|
||||
<i class="fa fa-folder fa-lg"></i> {{ state.currentLocation || '(Repository Root)' }}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Step view -->
|
||||
<div class="step-view" next-step-counter="nextStepCounter" current-step-valid="currentStepValid"
|
||||
steps-completed="stepsCompleted()">
|
||||
<!-- Repository select -->
|
||||
<div class="step-view-step" complete-condition="state.currentRepo" load-callback="loadRepositories(callback)"
|
||||
load-message="Loading Repositories">
|
||||
<div style="margin-bottom: 12px">Please choose the repository that will trigger the build:</div>
|
||||
<div class="dropdown-select" placeholder="'Enter or select a repository'" selected-item="state.currentRepo"
|
||||
lookahead-items="repoLookahead" allow-custom-input="true">
|
||||
<!-- Icons -->
|
||||
<i class="dropdown-select-icon none-icon fa fa-lg" ng-class="scmIcon(kind)"></i>
|
||||
<img class="dropdown-select-icon org-icon"
|
||||
ng-show="state.currentRepo.avatar_url"
|
||||
ng-src="{{ state.currentRepo.avatar_url ? state.currentRepo.avatar_url : '/static/img/empty.png' }}">
|
||||
<i class="dropdown-select-icon org-icon fa fa-lg" ng-class="scmIcon(kind)"
|
||||
ng-show="!state.currentRepo.avatar_url"></i>
|
||||
|
||||
<!-- Dropdown menu -->
|
||||
<ul class="dropdown-select-menu scrollable-menu" role="menu">
|
||||
<li ng-repeat-start="org in orgs" role="presentation" class="dropdown-header org-header">
|
||||
<img ng-src="{{ org.info.avatar_url }}" class="org-icon">{{ org.info.name }}
|
||||
</li>
|
||||
<li ng-repeat="repo in org.repos" class="trigger-repo-listing">
|
||||
<a ng-click="selectRepo(repo, org)">
|
||||
<i class="fa fa-lg" ng-class="scmIcon(kind)"></i> {{ repo }}
|
||||
</a>
|
||||
</li>
|
||||
<li role="presentation" class="divider" ng-repeat-end ng-show="$index < orgs.length - 1"></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Branch/Tag filter/select -->
|
||||
<div class="step-view-step" complete-condition="!state.hasBranchTagFilter || state.branchTagFilter"
|
||||
load-callback="loadBranchesAndTags(callback)"
|
||||
load-message="Loading Branches and Tags">
|
||||
|
||||
<div style="margin-bottom: 12px">Please choose the branches and tags to which this trigger will apply:</div>
|
||||
<div style="margin-left: 20px;">
|
||||
<div class="btn-group btn-group-sm" style="margin-bottom: 12px">
|
||||
<button type="button" class="btn btn-default"
|
||||
ng-class="state.hasBranchTagFilter ? '' : 'active btn-info'" ng-click="state.hasBranchTagFilter = false">
|
||||
All Branches and Tags
|
||||
</button>
|
||||
<button type="button" class="btn btn-default"
|
||||
ng-class="state.hasBranchTagFilter ? 'active btn-info' : ''" ng-click="state.hasBranchTagFilter = true">
|
||||
Matching Regular Expression
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div ng-show="state.hasBranchTagFilter" style="margin-top: 10px;">
|
||||
<form>
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<input class="form-control" type="text" ng-model="state.branchTagFilter"
|
||||
placeholder="(Regular expression. Examples: heads/branchname, tags/tagname)" required>
|
||||
<div class="dropdown input-group-addon">
|
||||
<button class="btn btn-default dropdown-toggle" type="button" data-toggle="dropdown">
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu pull-right">
|
||||
<li><a ng-click="state.branchTagFilter = 'heads/.+'">
|
||||
<i class="fa fa-code-fork"></i>All Branches</a>
|
||||
</li>
|
||||
<li><a ng-click="state.branchTagFilter = 'tags/.+'">
|
||||
<i class="fa fa-tag"></i>All Tags</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
|
||||
<div style="margin-top: 10px">
|
||||
<div class="ref-matches" ng-if="branchNames.length">
|
||||
<span class="kind">Branches:</span>
|
||||
<ul class="matching-refs branches">
|
||||
<li ng-repeat="branchName in branchNames | limitTo:20"
|
||||
class="ref-reference"
|
||||
ng-class="isMatching('heads', branchName, state.branchTagFilter) ? 'match' : 'not-match'">
|
||||
<span ng-click="addRef('heads', branchName)" ng-safenewtab>
|
||||
{{ branchName }}
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
<span ng-if="branchNames.length > 20">...</span>
|
||||
</div>
|
||||
<div class="ref-matches" ng-if="tagNames.length">
|
||||
<span class="kind">Tags:</span>
|
||||
<ul class="matching-refs tags">
|
||||
<li ng-repeat="tagName in tagNames | limitTo:20"
|
||||
class="ref-reference"
|
||||
ng-class="isMatching('tags', tagName, state.branchTagFilter) ? 'match' : 'not-match'">
|
||||
<span ng-click="addRef('tags', tagName)" ng-safenewtab>
|
||||
{{ tagName }}
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
<span ng-if="tagNames.length > 20">...</span>
|
||||
</div>
|
||||
<div ng-if="state.branchTagFilter && !branchNames.length"
|
||||
style="margin-top: 10px">
|
||||
<strong>Warning:</strong> No branches found
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Dockerfile folder select -->
|
||||
<div class="step-view-step" complete-condition="trigger.$ready" load-callback="loadLocations(callback)"
|
||||
load-message="Loading Folders">
|
||||
|
||||
<div style="margin-bottom: 12px">Dockerfile Location:</div>
|
||||
<div class="dropdown-select" placeholder="'(Repository Root)'" selected-item="state.currentLocation"
|
||||
lookahead-items="locations" handle-input="handleLocationInput(input)"
|
||||
handle-item-selected="handleLocationSelected(datum)"
|
||||
allow-custom-input="true"
|
||||
hide-dropdown="!supportsFullListing">
|
||||
<!-- Icons -->
|
||||
<i class="dropdown-select-icon none-icon fa fa-folder-o fa-lg" ng-show="state.isInvalidLocation"></i>
|
||||
<i class="dropdown-select-icon none-icon fa fa-folder fa-lg" style="color: black;" ng-show="!state.isInvalidLocation"></i>
|
||||
<i class="dropdown-select-icon fa fa-folder fa-lg"></i>
|
||||
|
||||
<!-- Dropdown menu -->
|
||||
<ul class="dropdown-select-menu" role="menu">
|
||||
<li ng-repeat="location in locations">
|
||||
<a ng-click="setLocation(location)" ng-if="!location">
|
||||
<i class="fa fa-github fa-lg"></i> Repository Root
|
||||
</a>
|
||||
<a ng-click="setLocation(location)" ng-if="location">
|
||||
<i class="fa fa-folder fa-lg"></i> {{ location }}
|
||||
</a>
|
||||
</li>
|
||||
<li class="dropdown-header" role="presentation" ng-show="!locations.length">
|
||||
No Dockerfiles found in repository
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="cor-loader" ng-show="!locations && !locationError"></div>
|
||||
<div class="alert alert-warning" ng-show="locationError">
|
||||
{{ locationError }}
|
||||
</div>
|
||||
<div class="alert alert-warning" ng-show="locations && !locations.length && supportsFullListing">
|
||||
Warning: No Dockerfiles were found in {{ state.currentRepo.repo }}
|
||||
</div>
|
||||
<div class="alert alert-info" ng-show="locations.length && state.isInvalidLocation && supportsFullListing">
|
||||
Note: The folder does not currently exist or contain a Dockerfile
|
||||
</div>
|
||||
</div>
|
||||
<!-- /step-view -->
|
||||
</div>
|
||||
</div>
|
|
@ -1,4 +1,4 @@
|
|||
<span>
|
||||
<i class="fa fa-git-square fa-lg" style="margin-right: 6px;" data-title="git" bs-tooltip="tooltip.title"></i>
|
||||
Push to {{ trigger.config.build_source }}
|
||||
Push to repository {{ trigger.config.build_source }}
|
||||
</span>
|
||||
|
|
|
@ -29,11 +29,9 @@ angular.module('quay').directive('repoPanelBuilds', function () {
|
|||
$scope.currentFilter = null;
|
||||
|
||||
$scope.currentStartTrigger = null;
|
||||
$scope.currentSetupTrigger = null;
|
||||
|
||||
$scope.showBuildDialogCounter = 0;
|
||||
$scope.showTriggerStartDialogCounter = 0;
|
||||
$scope.showTriggerSetupCounter = 0;
|
||||
|
||||
$scope.triggerCredentialsModalTrigger = null;
|
||||
$scope.triggerCredentialsModalCounter = 0;
|
||||
|
@ -144,16 +142,6 @@ angular.module('quay').directive('repoPanelBuilds', function () {
|
|||
|
||||
$scope.triggersResource = ApiService.listBuildTriggersAsResource(params).get(function(resp) {
|
||||
$scope.triggers = resp.triggers;
|
||||
|
||||
// Check to see if we need to setup any trigger.
|
||||
var newTriggerId = $routeParams.newtrigger;
|
||||
if (newTriggerId) {
|
||||
$scope.triggers.map(function(trigger) {
|
||||
if (trigger['id'] == newTriggerId && !trigger['is_active']) {
|
||||
$scope.setupTrigger(trigger);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -208,18 +196,6 @@ angular.module('quay').directive('repoPanelBuilds', function () {
|
|||
$scope.showTriggerStartDialogCounter++;
|
||||
};
|
||||
|
||||
$scope.cancelSetupTrigger = function(trigger) {
|
||||
if ($scope.currentSetupTrigger != trigger) { return; }
|
||||
|
||||
$scope.currentSetupTrigger = null;
|
||||
$scope.deleteTrigger(trigger);
|
||||
};
|
||||
|
||||
$scope.setupTrigger = function(trigger) {
|
||||
$scope.currentSetupTrigger = trigger;
|
||||
$scope.showTriggerSetupCounter++;
|
||||
};
|
||||
|
||||
$scope.deleteTrigger = function(trigger, opt_callback) {
|
||||
if (!trigger) { return; }
|
||||
|
||||
|
|
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;
|
||||
});
|
89
static/js/pages/trigger-setup.js
Normal file
89
static/js/pages/trigger-setup.js
Normal file
|
@ -0,0 +1,89 @@
|
|||
(function() {
|
||||
/**
|
||||
* Trigger setup page.
|
||||
*/
|
||||
angular.module('quayPages').config(['pages', function(pages) {
|
||||
pages.create('trigger-setup', 'trigger-setup.html', TriggerSetupCtrl, {
|
||||
'title': 'Setup build trigger',
|
||||
'description': 'Setup build trigger',
|
||||
'newLayout': true
|
||||
});
|
||||
}]);
|
||||
|
||||
function TriggerSetupCtrl($scope, ApiService, $routeParams, $location, UserService, TriggerService) {
|
||||
var namespace = $routeParams.namespace;
|
||||
var name = $routeParams.name;
|
||||
var trigger_uuid = $routeParams.triggerid;
|
||||
|
||||
var loadRepository = function() {
|
||||
var params = {
|
||||
'repository': namespace + '/' + name
|
||||
};
|
||||
|
||||
$scope.repositoryResource = ApiService.getRepoAsResource(params).get(function(repo) {
|
||||
$scope.repository = repo;
|
||||
});
|
||||
};
|
||||
|
||||
var loadTrigger = function() {
|
||||
var params = {
|
||||
'repository': namespace + '/' + name,
|
||||
'trigger_uuid': trigger_uuid
|
||||
};
|
||||
|
||||
$scope.triggerResource = ApiService.getBuildTriggerAsResource(params).get(function(trigger) {
|
||||
$scope.trigger = trigger;
|
||||
});
|
||||
};
|
||||
|
||||
loadTrigger();
|
||||
loadRepository();
|
||||
|
||||
$scope.state = 'managing';
|
||||
|
||||
$scope.activateTrigger = function(config, pull_robot) {
|
||||
$scope.state = 'activating';
|
||||
var params = {
|
||||
'repository': namespace + '/' + name,
|
||||
'trigger_uuid': trigger_uuid
|
||||
};
|
||||
|
||||
var data = {
|
||||
'config': config
|
||||
};
|
||||
|
||||
if (pull_robot) {
|
||||
data['pull_robot'] = pull_robot['name'];
|
||||
}
|
||||
|
||||
var errorHandler = ApiService.errorDisplay('Cannot activate build trigger', function(resp) {
|
||||
$scope.state = 'managing';
|
||||
return ApiService.getErrorMessage(resp) +
|
||||
'\n\nNote: Errors can occur if you do not have admin access on the repository';
|
||||
});
|
||||
|
||||
ApiService.activateBuildTrigger(data, params).then(function(resp) {
|
||||
$scope.trigger['is_active'] = true;
|
||||
$scope.trigger['config'] = resp['config'];
|
||||
$scope.trigger['pull_robot'] = resp['pull_robot'];
|
||||
$scope.trigger['repository_url'] = resp['repository_url'];
|
||||
$scope.state = 'activated';
|
||||
|
||||
// If there are no credentials to display, redirect to the builds tab.
|
||||
if (!$scope.trigger['config'].credentials) {
|
||||
$location.url('/repository/' + namespace + '/' + name + '?tab=builds');
|
||||
}
|
||||
}, errorHandler);
|
||||
};
|
||||
|
||||
$scope.getTriggerIcon = function() {
|
||||
if (!$scope.trigger) { return ''; }
|
||||
return TriggerService.getIcon($scope.trigger.service);
|
||||
};
|
||||
|
||||
$scope.getTriggerId = function() {
|
||||
if (!trigger_uuid) { return ''; }
|
||||
return trigger_uuid.split('-')[0];
|
||||
};
|
||||
}
|
||||
}());
|
|
@ -43,6 +43,9 @@ export function routeConfig(
|
|||
// Repo Build View
|
||||
.route('/repository/:namespace/:name/build/:buildid', 'build-view')
|
||||
|
||||
// Repo Trigger View
|
||||
.route('/repository/:namespace/:name/trigger/:triggerid', 'trigger-setup')
|
||||
|
||||
// Create repository notification
|
||||
.route('/repository/:namespace/:name/create-notification', 'create-repository-notification')
|
||||
|
||||
|
|
65
static/partials/trigger-setup.html
Normal file
65
static/partials/trigger-setup.html
Normal file
|
@ -0,0 +1,65 @@
|
|||
<div class="resource-view trigger-setup-element"
|
||||
resources="[repositoryResource, triggerResource]"
|
||||
error-message="'Build trigger not found'">
|
||||
<div class="page-content">
|
||||
<div class="cor-title">
|
||||
<span class="cor-title-link">
|
||||
<a class="back-link" href="/repository/{{ repository.namespace }}/{{ repository.name }}?tab=builds">
|
||||
<i class="fa fa-hdd-o" style="margin-right: 4px"></i>
|
||||
{{ repository.namespace }}/{{ repository.name }}
|
||||
</a>
|
||||
</span>
|
||||
<span class="cor-title-content">
|
||||
<i class="fa" ng-class="getTriggerIcon()"></i>
|
||||
Setup Build Trigger: {{ getTriggerId() }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="co-main-content-panel" ng-show="state != 'activated' && trigger.is_active">
|
||||
<div class="co-alert co-alert-info">
|
||||
Trigger has already been activated.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="co-main-content-panel" ng-show="state == 'activated' || !trigger.is_active">
|
||||
<!-- state = activated -->
|
||||
<div class="activated" ng-if="state == 'activated'">
|
||||
<div class="row">
|
||||
<div class="col-md-offset-3 col-md-6 col-sm-12 col-lg-6 content">
|
||||
<h3>Trigger has been successfully activated</h3>
|
||||
<div class="credentials" trigger="trigger"></div>
|
||||
<div class="button-bar">
|
||||
<a href="/repository/{{ repository.namespace }}/{{ repository.name }}?tab=builds">
|
||||
Return to {{ repository.namespace }}/{{ repository.name }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div> <!-- /state = activated -->
|
||||
|
||||
<!-- state = managing or activating -->
|
||||
<div ng-if="state == 'managing' || state == 'activating'"
|
||||
ng-class="{'activating': state == 'activating'}">
|
||||
<!-- Select the correct flow -->
|
||||
<div ng-switch on="trigger.service">
|
||||
<!-- Custom Git -->
|
||||
<div ng-switch-when="custom-git">
|
||||
<div class="manage-trigger-custom-git" trigger="trigger"
|
||||
activate-trigger="activateTrigger(config, pull_robot)"></div>
|
||||
</div> <!-- /custom-git -->
|
||||
|
||||
<!-- Hosted Git (GitHub, Gitlab, BitBucket) -->
|
||||
<div ng-switch-default>
|
||||
<div class="manage-trigger-githost" trigger="trigger" repository="repository"
|
||||
activate-trigger="activateTrigger(config, pull_robot)"></div>
|
||||
</div> <!-- /hosted -->
|
||||
</div> <!-- /ngSwitch -->
|
||||
|
||||
<div class="activating-message" ng-show="state == 'activating'">
|
||||
<div class="cor-loader-inline"></div><b>Completing setup of the build trigger</b>
|
||||
</div>
|
||||
</div> <!-- /state = managing -->
|
||||
|
||||
</div> <!-- /co-main-content-panel -->
|
||||
</div>
|
||||
</div>
|
Reference in a new issue