mirror of https://github.com/portainer/portainer
feat(registry): enforce name uniqueness for registries (#6709)
* feat(app/registries): add name uniqueness validation on registry creation * feat(api/registry): enforce name uniqueness on registry creation * feat(api/registry): enforce name uniqueness on registry update * feat(app/registry): enforce name uniqueness on registry updatepull/6739/head
parent
9ffaf47741
commit
298e3d263e
|
@ -126,6 +126,9 @@ func (handler *Handler) registryCreate(w http.ResponseWriter, r *http.Request) *
|
||||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve registries from the database", err}
|
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve registries from the database", err}
|
||||||
}
|
}
|
||||||
for _, r := range registries {
|
for _, r := range registries {
|
||||||
|
if r.Name == registry.Name {
|
||||||
|
return &httperror.HandlerError{http.StatusConflict, "Another registry with the same name already exists", errors.New("A registry is already defined with this name")}
|
||||||
|
}
|
||||||
if handler.registriesHaveSameURLAndCredentials(&r, registry) {
|
if handler.registriesHaveSameURLAndCredentials(&r, registry) {
|
||||||
return &httperror.HandlerError{http.StatusConflict, "Another registry with the same URL and credentials already exists", errors.New("A registry is already defined for this URL and credentials")}
|
return &httperror.HandlerError{http.StatusConflict, "Another registry with the same URL and credentials already exists", errors.New("A registry is already defined for this URL and credentials")}
|
||||||
}
|
}
|
||||||
|
|
|
@ -77,6 +77,11 @@ func (handler *Handler) registryUpdate(w http.ResponseWriter, r *http.Request) *
|
||||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find a registry with the specified identifier inside the database", err}
|
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find a registry with the specified identifier inside the database", err}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
registries, err := handler.DataStore.Registry().Registries()
|
||||||
|
if err != nil {
|
||||||
|
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve registries from the database", err}
|
||||||
|
}
|
||||||
|
|
||||||
var payload registryUpdatePayload
|
var payload registryUpdatePayload
|
||||||
err = request.DecodeAndValidateJSONPayload(r, &payload)
|
err = request.DecodeAndValidateJSONPayload(r, &payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -86,6 +91,15 @@ func (handler *Handler) registryUpdate(w http.ResponseWriter, r *http.Request) *
|
||||||
if payload.Name != nil {
|
if payload.Name != nil {
|
||||||
registry.Name = *payload.Name
|
registry.Name = *payload.Name
|
||||||
}
|
}
|
||||||
|
// enforce name uniqueness across registries
|
||||||
|
// check is performed even if Name didn't change (Name not in payload) as we need
|
||||||
|
// to enforce this rule on updates not performed with frontend (e.g. on direct API requests)
|
||||||
|
// see https://portainer.atlassian.net/browse/EE-2706 for more details
|
||||||
|
for _, r := range registries {
|
||||||
|
if r.ID != registry.ID && r.Name == registry.Name {
|
||||||
|
return &httperror.HandlerError{http.StatusConflict, "Another registry with the same name already exists", errors.New("A registry is already defined with this name")}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if registry.Type == portainer.ProGetRegistry && payload.BaseURL != nil {
|
if registry.Type == portainer.ProGetRegistry && payload.BaseURL != nil {
|
||||||
registry.BaseURL = *payload.BaseURL
|
registry.BaseURL = *payload.BaseURL
|
||||||
|
@ -129,10 +143,6 @@ func (handler *Handler) registryUpdate(w http.ResponseWriter, r *http.Request) *
|
||||||
shouldUpdateSecrets = shouldUpdateSecrets || (*payload.URL != registry.URL)
|
shouldUpdateSecrets = shouldUpdateSecrets || (*payload.URL != registry.URL)
|
||||||
|
|
||||||
registry.URL = *payload.URL
|
registry.URL = *payload.URL
|
||||||
registries, err := handler.DataStore.Registry().Registries()
|
|
||||||
if err != nil {
|
|
||||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve registries from the database", err}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, r := range registries {
|
for _, r := range registries {
|
||||||
if r.ID != registry.ID && handler.registriesHaveSameURLAndCredentials(&r, registry) {
|
if r.ID != registry.ID && handler.registriesHaveSameURLAndCredentials(&r, registry) {
|
||||||
|
|
|
@ -401,8 +401,7 @@ angular
|
||||||
url: '/:id',
|
url: '/:id',
|
||||||
views: {
|
views: {
|
||||||
'content@': {
|
'content@': {
|
||||||
templateUrl: './views/registries/edit/registry.html',
|
component: 'editRegistry',
|
||||||
controller: 'RegistryController',
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<form class="form-horizontal" name="registryFormEcr" ng-submit="$ctrl.formAction()">
|
<form class="form-horizontal" name="$ctrl.registryFormEcr" ng-submit="$ctrl.formAction()">
|
||||||
<div class="col-sm-12 form-section-title"> Important notice </div>
|
<div class="col-sm-12 form-section-title"> Important notice </div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<span class="col-sm-12 text-muted small">
|
<span class="col-sm-12 text-muted small">
|
||||||
|
@ -16,10 +16,11 @@
|
||||||
<input type="text" class="form-control" id="registry_name" name="registry_name" ng-model="$ctrl.model.Name" placeholder="my-ecr-registry" required auto-focus />
|
<input type="text" class="form-control" id="registry_name" name="registry_name" ng-model="$ctrl.model.Name" placeholder="my-ecr-registry" required auto-focus />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group" ng-show="registryFormEcr.registry_name.$invalid">
|
<div class="form-group" ng-show="$ctrl.registryFormEcr.registry_name.$invalid">
|
||||||
<div class="col-sm-12 small text-warning">
|
<div class="col-sm-12 small text-warning">
|
||||||
<div ng-messages="registryFormEcr.registry_name.$error">
|
<div ng-messages="$ctrl.registryFormEcr.registry_name.$error">
|
||||||
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
|
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
|
||||||
|
<p ng-message="used"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> A registry with the same name already exists.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -43,9 +44,9 @@
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group" ng-show="registryFormEcr.registry_url.$invalid">
|
<div class="form-group" ng-show="$ctrl.registryFormEcr.registry_url.$invalid">
|
||||||
<div class="col-sm-12 small text-warning">
|
<div class="col-sm-12 small text-warning">
|
||||||
<div ng-messages="registryFormEcr.registry_url.$error">
|
<div ng-messages="$ctrl.registryFormEcr.registry_url.$error">
|
||||||
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
|
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -72,9 +73,9 @@
|
||||||
<input type="text" class="form-control" id="registry_access_key" name="registry_access_key" ng-model="$ctrl.model.Username" required />
|
<input type="text" class="form-control" id="registry_access_key" name="registry_access_key" ng-model="$ctrl.model.Username" required />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group" ng-show="registryFormEcr.registry_access_key.$invalid">
|
<div class="form-group" ng-show="$ctrl.registryFormEcr.registry_access_key.$invalid">
|
||||||
<div class="col-sm-12 small text-warning">
|
<div class="col-sm-12 small text-warning">
|
||||||
<div ng-messages="registryFormEcr.registry_access_key.$error">
|
<div ng-messages="$ctrl.registryFormEcr.registry_access_key.$error">
|
||||||
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
|
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -88,9 +89,9 @@
|
||||||
<input type="password" class="form-control" id="registry_secret_access_key" name="registry_secret_access_key" ng-model="$ctrl.model.Password" required />
|
<input type="password" class="form-control" id="registry_secret_access_key" name="registry_secret_access_key" ng-model="$ctrl.model.Password" required />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group" ng-show="registryFormEcr.registry_secret_access_key.$invalid">
|
<div class="form-group" ng-show="$ctrl.registryFormEcr.registry_secret_access_key.$invalid">
|
||||||
<div class="col-sm-12 small text-warning">
|
<div class="col-sm-12 small text-warning">
|
||||||
<div ng-messages="registryFormEcr.registry_secret_access_key.$error">
|
<div ng-messages="$ctrl.registryFormEcr.registry_secret_access_key.$error">
|
||||||
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
|
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -104,10 +105,11 @@
|
||||||
<input type="text" class="form-control" id="registry_region" name="registry_region" placeholder="us-west-1" ng-model="$ctrl.model.Ecr.Region" required />
|
<input type="text" class="form-control" id="registry_region" name="registry_region" placeholder="us-west-1" ng-model="$ctrl.model.Ecr.Region" required />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group" ng-show="registryFormEcr.registry_region.$invalid">
|
<div class="form-group" ng-show="$ctrl.registryFormEcr.registry_region.$invalid">
|
||||||
<div class="col-sm-12 small text-warning">
|
<div class="col-sm-12 small text-warning">
|
||||||
<div ng-messages="registryFormEcr.registry_region.$error">
|
<div ng-messages="$ctrl.registryFormEcr.registry_region.$error">
|
||||||
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
|
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
|
||||||
|
<p ng-message="used"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> A registry with the same name already exists.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -121,7 +123,7 @@
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
class="btn btn-primary btn-sm"
|
class="btn btn-primary btn-sm"
|
||||||
ng-disabled="$ctrl.actionInProgress || !registryFormEcr.$valid"
|
ng-disabled="$ctrl.actionInProgress || !$ctrl.registryFormEcr.$valid"
|
||||||
button-spinner="$ctrl.actionInProgress"
|
button-spinner="$ctrl.actionInProgress"
|
||||||
analytics-on
|
analytics-on
|
||||||
analytics-category="portainer"
|
analytics-category="portainer"
|
||||||
|
|
|
@ -1,3 +1,9 @@
|
||||||
|
class controller {
|
||||||
|
$postLink() {
|
||||||
|
this.registryFormEcr.registry_name.$validators.used = (modelValue) => !this.nameIsUsed(modelValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
angular.module('portainer.app').component('registryFormEcr', {
|
angular.module('portainer.app').component('registryFormEcr', {
|
||||||
templateUrl: './registry-form-ecr.html',
|
templateUrl: './registry-form-ecr.html',
|
||||||
bindings: {
|
bindings: {
|
||||||
|
@ -5,5 +11,7 @@ angular.module('portainer.app').component('registryFormEcr', {
|
||||||
formAction: '<',
|
formAction: '<',
|
||||||
formActionLabel: '@',
|
formActionLabel: '@',
|
||||||
actionInProgress: '<',
|
actionInProgress: '<',
|
||||||
|
nameIsUsed: '<',
|
||||||
},
|
},
|
||||||
|
controller,
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<form class="form-horizontal" name="registryFormAzure" ng-submit="$ctrl.formAction()">
|
<form class="form-horizontal" name="$ctrl.registryFormAzure" ng-submit="$ctrl.formAction()">
|
||||||
<div class="col-sm-12 form-section-title"> Azure registry details </div>
|
<div class="col-sm-12 form-section-title"> Azure registry details </div>
|
||||||
<!-- name-input -->
|
<!-- name-input -->
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
@ -7,10 +7,11 @@
|
||||||
<input type="text" class="form-control" id="registry_name" name="registry_name" ng-model="$ctrl.model.Name" placeholder="my-azure-registry" required auto-focus />
|
<input type="text" class="form-control" id="registry_name" name="registry_name" ng-model="$ctrl.model.Name" placeholder="my-azure-registry" required auto-focus />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group" ng-show="registryFormAzure.registry_name.$invalid">
|
<div class="form-group" ng-show="$ctrl.registryFormAzure.registry_name.$invalid">
|
||||||
<div class="col-sm-12 small text-warning">
|
<div class="col-sm-12 small text-warning">
|
||||||
<div ng-messages="registryFormAzure.registry_name.$error">
|
<div ng-messages="$ctrl.registryFormAzure.registry_name.$error">
|
||||||
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
|
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
|
||||||
|
<p ng-message="used"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> A registry with the same name already exists.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -25,9 +26,9 @@
|
||||||
<input type="text" class="form-control" id="registry_url" name="registry_url" ng-model="$ctrl.model.URL" placeholder="myproject.azurecr.io" required />
|
<input type="text" class="form-control" id="registry_url" name="registry_url" ng-model="$ctrl.model.URL" placeholder="myproject.azurecr.io" required />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group" ng-show="registryFormAzure.registry_url.$invalid">
|
<div class="form-group" ng-show="$ctrl.registryFormAzure.registry_url.$invalid">
|
||||||
<div class="col-sm-12 small text-warning">
|
<div class="col-sm-12 small text-warning">
|
||||||
<div ng-messages="registryFormAzure.registry_url.$error">
|
<div ng-messages="$ctrl.registryFormAzure.registry_url.$error">
|
||||||
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
|
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -40,9 +41,9 @@
|
||||||
<input type="text" class="form-control" id="registry_username" name="registry_username" ng-model="$ctrl.model.Username" required />
|
<input type="text" class="form-control" id="registry_username" name="registry_username" ng-model="$ctrl.model.Username" required />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group" ng-show="registryFormAzure.registry_username.$invalid">
|
<div class="form-group" ng-show="$ctrl.registryFormAzure.registry_username.$invalid">
|
||||||
<div class="col-sm-12 small text-warning">
|
<div class="col-sm-12 small text-warning">
|
||||||
<div ng-messages="registryFormAzure.registry_username.$error">
|
<div ng-messages="$ctrl.registryFormAzure.registry_username.$error">
|
||||||
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
|
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -55,9 +56,9 @@
|
||||||
<input type="password" class="form-control" id="registry_password" name="registry_password" ng-model="$ctrl.model.Password" required />
|
<input type="password" class="form-control" id="registry_password" name="registry_password" ng-model="$ctrl.model.Password" required />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group" ng-show="registryFormAzure.registry_password.$invalid">
|
<div class="form-group" ng-show="$ctrl.registryFormAzure.registry_password.$invalid">
|
||||||
<div class="col-sm-12 small text-warning">
|
<div class="col-sm-12 small text-warning">
|
||||||
<div ng-messages="registryFormAzure.registry_password.$error">
|
<div ng-messages="$ctrl.registryFormAzure.registry_password.$error">
|
||||||
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
|
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -70,7 +71,7 @@
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
class="btn btn-primary btn-sm"
|
class="btn btn-primary btn-sm"
|
||||||
ng-disabled="$ctrl.actionInProgress || !registryFormAzure.$valid"
|
ng-disabled="$ctrl.actionInProgress || !$ctrl.registryFormAzure.$valid"
|
||||||
button-spinner="$ctrl.actionInProgress"
|
button-spinner="$ctrl.actionInProgress"
|
||||||
analytics-on
|
analytics-on
|
||||||
analytics-category="portainer"
|
analytics-category="portainer"
|
||||||
|
|
|
@ -1,3 +1,9 @@
|
||||||
|
class controller {
|
||||||
|
$postLink() {
|
||||||
|
this.registryFormAzure.registry_name.$validators.used = (modelValue) => !this.nameIsUsed(modelValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
angular.module('portainer.app').component('registryFormAzure', {
|
angular.module('portainer.app').component('registryFormAzure', {
|
||||||
templateUrl: './registry-form-azure.html',
|
templateUrl: './registry-form-azure.html',
|
||||||
bindings: {
|
bindings: {
|
||||||
|
@ -5,5 +11,7 @@ angular.module('portainer.app').component('registryFormAzure', {
|
||||||
formAction: '<',
|
formAction: '<',
|
||||||
formActionLabel: '@',
|
formActionLabel: '@',
|
||||||
actionInProgress: '<',
|
actionInProgress: '<',
|
||||||
|
nameIsUsed: '<',
|
||||||
},
|
},
|
||||||
|
controller,
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<form class="form-horizontal" name="registryFormCustom" ng-submit="$ctrl.formAction()">
|
<form class="form-horizontal" name="$ctrl.registryFormCustom" ng-submit="$ctrl.formAction()">
|
||||||
<div class="col-sm-12 form-section-title"> Important notice </div>
|
<div class="col-sm-12 form-section-title"> Important notice </div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<span class="col-sm-12 text-muted small">
|
<span class="col-sm-12 text-muted small">
|
||||||
|
@ -14,10 +14,11 @@
|
||||||
<input type="text" class="form-control" id="registry_name" name="registry_name" ng-model="$ctrl.model.Name" placeholder="my-custom-registry" required auto-focus />
|
<input type="text" class="form-control" id="registry_name" name="registry_name" ng-model="$ctrl.model.Name" placeholder="my-custom-registry" required auto-focus />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group" ng-show="registryFormCustom.registry_name.$invalid">
|
<div class="form-group" ng-show="$ctrl.registryFormCustom.registry_name.$invalid">
|
||||||
<div class="col-sm-12 small text-warning">
|
<div class="col-sm-12 small text-warning">
|
||||||
<div ng-messages="registryFormCustom.registry_name.$error">
|
<div ng-messages="$ctrl.registryFormCustom.registry_name.$error">
|
||||||
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
|
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
|
||||||
|
<p ng-message="used"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> A registry with the same name already exists.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -32,9 +33,9 @@
|
||||||
<input type="text" class="form-control" id="registry_url" name="registry_url" ng-model="$ctrl.model.URL" placeholder="10.0.0.10:5000 or myregistry.domain.tld" required />
|
<input type="text" class="form-control" id="registry_url" name="registry_url" ng-model="$ctrl.model.URL" placeholder="10.0.0.10:5000 or myregistry.domain.tld" required />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group" ng-show="registryFormCustom.registry_url.$invalid">
|
<div class="form-group" ng-show="$ctrl.registryFormCustom.registry_url.$invalid">
|
||||||
<div class="col-sm-12 small text-warning">
|
<div class="col-sm-12 small text-warning">
|
||||||
<div ng-messages="registryFormCustom.registry_url.$error">
|
<div ng-messages="$ctrl.registryFormCustom.registry_url.$error">
|
||||||
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
|
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -59,9 +60,9 @@
|
||||||
<input type="text" class="form-control" id="registry_username" name="registry_username" ng-model="$ctrl.model.Username" required />
|
<input type="text" class="form-control" id="registry_username" name="registry_username" ng-model="$ctrl.model.Username" required />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group" ng-show="registryFormCustom.registry_username.$invalid">
|
<div class="form-group" ng-show="$ctrl.registryFormCustom.registry_username.$invalid">
|
||||||
<div class="col-sm-12 small text-warning">
|
<div class="col-sm-12 small text-warning">
|
||||||
<div ng-messages="registryFormCustom.registry_username.$error">
|
<div ng-messages="$ctrl.registryFormCustom.registry_username.$error">
|
||||||
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
|
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -74,9 +75,9 @@
|
||||||
<input type="password" class="form-control" id="registry_password" name="registry_password" ng-model="$ctrl.model.Password" required />
|
<input type="password" class="form-control" id="registry_password" name="registry_password" ng-model="$ctrl.model.Password" required />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group" ng-show="registryFormCustom.registry_password.$invalid">
|
<div class="form-group" ng-show="$ctrl.registryFormCustom.registry_password.$invalid">
|
||||||
<div class="col-sm-12 small text-warning">
|
<div class="col-sm-12 small text-warning">
|
||||||
<div ng-messages="registryFormCustom.registry_password.$error">
|
<div ng-messages="$ctrl.registryFormCustom.registry_password.$error">
|
||||||
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
|
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -90,7 +91,7 @@
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
class="btn btn-primary btn-sm"
|
class="btn btn-primary btn-sm"
|
||||||
ng-disabled="$ctrl.actionInProgress || !registryFormCustom.$valid"
|
ng-disabled="$ctrl.actionInProgress || !$ctrl.registryFormCustom.$valid"
|
||||||
button-spinner="$ctrl.actionInProgress"
|
button-spinner="$ctrl.actionInProgress"
|
||||||
analytics-on
|
analytics-on
|
||||||
analytics-category="portainer"
|
analytics-category="portainer"
|
||||||
|
|
|
@ -1,3 +1,9 @@
|
||||||
|
class controller {
|
||||||
|
$postLink() {
|
||||||
|
this.registryFormCustom.registry_name.$validators.used = (modelValue) => !this.nameIsUsed(modelValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
angular.module('portainer.app').component('registryFormCustom', {
|
angular.module('portainer.app').component('registryFormCustom', {
|
||||||
templateUrl: './registry-form-custom.html',
|
templateUrl: './registry-form-custom.html',
|
||||||
bindings: {
|
bindings: {
|
||||||
|
@ -5,5 +11,7 @@ angular.module('portainer.app').component('registryFormCustom', {
|
||||||
formAction: '<',
|
formAction: '<',
|
||||||
formActionLabel: '@',
|
formActionLabel: '@',
|
||||||
actionInProgress: '<',
|
actionInProgress: '<',
|
||||||
|
nameIsUsed: '<',
|
||||||
},
|
},
|
||||||
|
controller,
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<form class="form-horizontal" name="registryFormDockerhub" ng-submit="$ctrl.formAction()">
|
<form class="form-horizontal" name="$ctrl.registryFormDockerhub" ng-submit="$ctrl.formAction()">
|
||||||
<div class="col-sm-12 form-section-title"> Important notice </div>
|
<div class="col-sm-12 form-section-title"> Important notice </div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<span class="col-sm-12 text-muted small">
|
<span class="col-sm-12 text-muted small">
|
||||||
|
@ -16,10 +16,11 @@
|
||||||
<input type="text" class="form-control" id="registry_name" name="registry_name" ng-model="$ctrl.model.Name" placeholder="dockerhub-prod-us" required />
|
<input type="text" class="form-control" id="registry_name" name="registry_name" ng-model="$ctrl.model.Name" placeholder="dockerhub-prod-us" required />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group" ng-show="registryFormDockerhub.registry_name.$invalid">
|
<div class="form-group" ng-show="$ctrl.registryFormDockerhub.registry_name.$invalid">
|
||||||
<div class="col-sm-12 small text-warning">
|
<div class="col-sm-12 small text-warning">
|
||||||
<div ng-messages="registryFormDockerhub.registry_name.$error">
|
<div ng-messages="$ctrl.registryFormDockerhub.registry_name.$error">
|
||||||
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
|
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
|
||||||
|
<p ng-message="used"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> A registry with the same name already exists.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -31,9 +32,9 @@
|
||||||
<input type="text" class="form-control" id="registry_username" name="registry_username" ng-model="$ctrl.model.Username" required />
|
<input type="text" class="form-control" id="registry_username" name="registry_username" ng-model="$ctrl.model.Username" required />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group" ng-show="registryFormDockerhub.registry_username.$invalid">
|
<div class="form-group" ng-show="$ctrl.registryFormDockerhub.registry_username.$invalid">
|
||||||
<div class="col-sm-12 small text-warning">
|
<div class="col-sm-12 small text-warning">
|
||||||
<div ng-messages="registryFormDockerhub.registry_username.$error">
|
<div ng-messages="$ctrl.registryFormDockerhub.registry_username.$error">
|
||||||
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
|
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -46,9 +47,9 @@
|
||||||
<input type="password" class="form-control" id="registry_password" name="registry_password" ng-model="$ctrl.model.Password" required />
|
<input type="password" class="form-control" id="registry_password" name="registry_password" ng-model="$ctrl.model.Password" required />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group" ng-show="registryFormDockerhub.registry_password.$invalid">
|
<div class="form-group" ng-show="$ctrl.registryFormDockerhub.registry_password.$invalid">
|
||||||
<div class="col-sm-12 small text-warning">
|
<div class="col-sm-12 small text-warning">
|
||||||
<div ng-messages="registryFormDockerhub.registry_password.$error">
|
<div ng-messages="$ctrl.registryFormDockerhub.registry_password.$error">
|
||||||
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
|
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -62,7 +63,7 @@
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
class="btn btn-primary btn-sm"
|
class="btn btn-primary btn-sm"
|
||||||
ng-disabled="$ctrl.actionInProgress || !registryFormDockerhub.$valid"
|
ng-disabled="$ctrl.actionInProgress || !$ctrl.registryFormDockerhub.$valid"
|
||||||
button-spinner="$ctrl.actionInProgress"
|
button-spinner="$ctrl.actionInProgress"
|
||||||
analytics-on
|
analytics-on
|
||||||
analytics-category="portainer"
|
analytics-category="portainer"
|
||||||
|
|
|
@ -1,3 +1,9 @@
|
||||||
|
class controller {
|
||||||
|
$postLink() {
|
||||||
|
this.registryFormDockerhub.registry_name.$validators.used = (modelValue) => !this.nameIsUsed(modelValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
angular.module('portainer.app').component('registryFormDockerhub', {
|
angular.module('portainer.app').component('registryFormDockerhub', {
|
||||||
templateUrl: './registry-form-dockerhub.html',
|
templateUrl: './registry-form-dockerhub.html',
|
||||||
bindings: {
|
bindings: {
|
||||||
|
@ -5,5 +11,7 @@ angular.module('portainer.app').component('registryFormDockerhub', {
|
||||||
formAction: '<',
|
formAction: '<',
|
||||||
formActionLabel: '@',
|
formActionLabel: '@',
|
||||||
actionInProgress: '<',
|
actionInProgress: '<',
|
||||||
|
nameIsUsed: '<',
|
||||||
},
|
},
|
||||||
|
controller,
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<form class="form-horizontal" name="registryFormProGet" ng-submit="$ctrl.formAction()">
|
<form class="form-horizontal" name="$ctrl.registryFormProGet" ng-submit="$ctrl.formAction()">
|
||||||
<div class="col-sm-12 form-section-title"> Important notice </div>
|
<div class="col-sm-12 form-section-title"> Important notice </div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="col-sm-12 text-muted small">
|
<div class="col-sm-12 text-muted small">
|
||||||
|
@ -14,10 +14,11 @@
|
||||||
<input type="text" class="form-control" id="registry_name" name="registry_name" ng-model="$ctrl.model.Name" placeholder="proget-registry" required auto-focus />
|
<input type="text" class="form-control" id="registry_name" name="registry_name" ng-model="$ctrl.model.Name" placeholder="proget-registry" required auto-focus />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group" ng-show="registryFormProGet.registry_name.$invalid">
|
<div class="form-group" ng-show="$ctrl.registryFormProGet.registry_name.$invalid">
|
||||||
<div class="col-sm-12 small text-warning">
|
<div class="col-sm-12 small text-warning">
|
||||||
<div ng-messages="registryFormProGet.registry_name.$error">
|
<div ng-messages="$ctrl.registryFormProGet.registry_name.$error">
|
||||||
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i>This field is required.</p>
|
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i>This field is required.</p>
|
||||||
|
<p ng-message="used"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> A registry with the same name already exists.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -32,9 +33,9 @@
|
||||||
<input type="text" class="form-control" id="registry_url" name="registry_url" ng-model="$ctrl.model.URL" placeholder="proget.example.com/example-registry" required />
|
<input type="text" class="form-control" id="registry_url" name="registry_url" ng-model="$ctrl.model.URL" placeholder="proget.example.com/example-registry" required />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group" ng-show="registryFormProGet.registry_url.$invalid">
|
<div class="form-group" ng-show="$ctrl.registryFormProGet.registry_url.$invalid">
|
||||||
<div class="col-sm-12 small text-warning">
|
<div class="col-sm-12 small text-warning">
|
||||||
<div ng-messages="registryFormProGet.registry_url.$error">
|
<div ng-messages="$ctrl.registryFormProGet.registry_url.$error">
|
||||||
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
|
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -50,9 +51,9 @@
|
||||||
<input type="text" class="form-control" id="registry_base_url" name="registry_base_url" ng-model="$ctrl.model.BaseURL" placeholder="proget.example.com" required />
|
<input type="text" class="form-control" id="registry_base_url" name="registry_base_url" ng-model="$ctrl.model.BaseURL" placeholder="proget.example.com" required />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group" ng-show="registryFormProGet.registry_base_url.$invalid">
|
<div class="form-group" ng-show="$ctrl.registryFormProGet.registry_base_url.$invalid">
|
||||||
<div class="col-sm-12 small text-warning">
|
<div class="col-sm-12 small text-warning">
|
||||||
<div ng-messages="registryFormProGet.registry_base_url.$error">
|
<div ng-messages="$ctrl.registryFormProGet.registry_base_url.$error">
|
||||||
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
|
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -66,9 +67,9 @@
|
||||||
<input type="text" class="form-control" id="registry_username" name="registry_username" ng-model="$ctrl.model.Username" required />
|
<input type="text" class="form-control" id="registry_username" name="registry_username" ng-model="$ctrl.model.Username" required />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group" ng-show="registryFormProGet.registry_username.$invalid">
|
<div class="form-group" ng-show="$ctrl.registryFormProGet.registry_username.$invalid">
|
||||||
<div class="col-sm-12 small text-warning">
|
<div class="col-sm-12 small text-warning">
|
||||||
<div ng-messages="registryFormProGet.registry_username.$error">
|
<div ng-messages="$ctrl.registryFormProGet.registry_username.$error">
|
||||||
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
|
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -81,9 +82,9 @@
|
||||||
<input type="password" class="form-control" id="registry_password" name="registry_password" ng-model="$ctrl.model.Password" required />
|
<input type="password" class="form-control" id="registry_password" name="registry_password" ng-model="$ctrl.model.Password" required />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group" ng-show="registryFormProGet.registry_password.$invalid">
|
<div class="form-group" ng-show="$ctrl.registryFormProGet.registry_password.$invalid">
|
||||||
<div class="col-sm-12 small text-warning">
|
<div class="col-sm-12 small text-warning">
|
||||||
<div ng-messages="registryFormProGet.registry_password.$error">
|
<div ng-messages="$ctrl.registryFormProGet.registry_password.$error">
|
||||||
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
|
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -97,7 +98,7 @@
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
class="btn btn-primary btn-sm"
|
class="btn btn-primary btn-sm"
|
||||||
ng-disabled="$ctrl.actionInProgress || !registryFormProGet.$valid"
|
ng-disabled="$ctrl.actionInProgress || !$ctrl.registryFormProGet.$valid"
|
||||||
button-spinner="$ctrl.actionInProgress"
|
button-spinner="$ctrl.actionInProgress"
|
||||||
analytics-on
|
analytics-on
|
||||||
analytics-category="portainer"
|
analytics-category="portainer"
|
||||||
|
|
|
@ -1,3 +1,9 @@
|
||||||
|
class controller {
|
||||||
|
$postLink() {
|
||||||
|
this.registryFormProGet.registry_name.$validators.used = (modelValue) => !this.nameIsUsed(modelValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
angular.module('portainer.app').component('registryFormProget', {
|
angular.module('portainer.app').component('registryFormProget', {
|
||||||
templateUrl: './registry-form-proget.html',
|
templateUrl: './registry-form-proget.html',
|
||||||
bindings: {
|
bindings: {
|
||||||
|
@ -5,5 +11,7 @@ angular.module('portainer.app').component('registryFormProget', {
|
||||||
formAction: '<',
|
formAction: '<',
|
||||||
formActionLabel: '@',
|
formActionLabel: '@',
|
||||||
actionInProgress: '<',
|
actionInProgress: '<',
|
||||||
|
nameIsUsed: '<',
|
||||||
},
|
},
|
||||||
|
controller,
|
||||||
});
|
});
|
||||||
|
|
|
@ -101,6 +101,7 @@
|
||||||
form-action="$ctrl.createRegistry"
|
form-action="$ctrl.createRegistry"
|
||||||
form-action-label="Add registry"
|
form-action-label="Add registry"
|
||||||
action-in-progress="$ctrl.state.actionInProgress"
|
action-in-progress="$ctrl.state.actionInProgress"
|
||||||
|
name-is-used="($ctrl.nameIsUsed)"
|
||||||
></registry-form-azure>
|
></registry-form-azure>
|
||||||
|
|
||||||
<registry-form-custom
|
<registry-form-custom
|
||||||
|
@ -109,6 +110,7 @@
|
||||||
form-action="$ctrl.createRegistry"
|
form-action="$ctrl.createRegistry"
|
||||||
form-action-label="Add registry"
|
form-action-label="Add registry"
|
||||||
action-in-progress="$ctrl.state.actionInProgress"
|
action-in-progress="$ctrl.state.actionInProgress"
|
||||||
|
name-is-used="($ctrl.nameIsUsed)"
|
||||||
></registry-form-custom>
|
></registry-form-custom>
|
||||||
|
|
||||||
<registry-form-ecr
|
<registry-form-ecr
|
||||||
|
@ -117,6 +119,7 @@
|
||||||
form-action="$ctrl.createRegistry"
|
form-action="$ctrl.createRegistry"
|
||||||
form-action-label="Add registry"
|
form-action-label="Add registry"
|
||||||
action-in-progress="$ctrl.state.actionInProgress"
|
action-in-progress="$ctrl.state.actionInProgress"
|
||||||
|
name-is-used="($ctrl.nameIsUsed)"
|
||||||
></registry-form-ecr>
|
></registry-form-ecr>
|
||||||
|
|
||||||
<registry-form-proget
|
<registry-form-proget
|
||||||
|
@ -125,6 +128,7 @@
|
||||||
form-action="$ctrl.createRegistry"
|
form-action="$ctrl.createRegistry"
|
||||||
form-action-label="Add registry"
|
form-action-label="Add registry"
|
||||||
action-in-progress="$ctrl.state.actionInProgress"
|
action-in-progress="$ctrl.state.actionInProgress"
|
||||||
|
name-is-used="($ctrl.nameIsUsed)"
|
||||||
></registry-form-proget>
|
></registry-form-proget>
|
||||||
|
|
||||||
<registry-form-gitlab
|
<registry-form-gitlab
|
||||||
|
@ -144,6 +148,7 @@
|
||||||
form-action="$ctrl.createRegistry"
|
form-action="$ctrl.createRegistry"
|
||||||
form-action-label="Add registry"
|
form-action-label="Add registry"
|
||||||
action-in-progress="$ctrl.state.actionInProgress"
|
action-in-progress="$ctrl.state.actionInProgress"
|
||||||
|
name-is-used="($ctrl.nameIsUsed)"
|
||||||
></registry-form-dockerhub>
|
></registry-form-dockerhub>
|
||||||
</form>
|
</form>
|
||||||
</rd-widget-body>
|
</rd-widget-body>
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import _ from 'lodash';
|
||||||
import { RegistryTypes } from 'Portainer/models/registryTypes';
|
import { RegistryTypes } from 'Portainer/models/registryTypes';
|
||||||
import { RegistryCreateFormValues } from 'Portainer/models/registry';
|
import { RegistryCreateFormValues } from 'Portainer/models/registry';
|
||||||
|
|
||||||
|
@ -21,6 +22,8 @@ class CreateRegistryController {
|
||||||
};
|
};
|
||||||
|
|
||||||
this.createRegistry = this.createRegistry.bind(this);
|
this.createRegistry = this.createRegistry.bind(this);
|
||||||
|
this.getRegistries = this.getRegistries.bind(this);
|
||||||
|
this.nameIsUsed = this.nameIsUsed.bind(this);
|
||||||
this.retrieveGitlabRegistries = this.retrieveGitlabRegistries.bind(this);
|
this.retrieveGitlabRegistries = this.retrieveGitlabRegistries.bind(this);
|
||||||
this.createGitlabRegistries = this.createGitlabRegistries.bind(this);
|
this.createGitlabRegistries = this.createGitlabRegistries.bind(this);
|
||||||
}
|
}
|
||||||
|
@ -128,7 +131,23 @@ class CreateRegistryController {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nameIsUsed(name) {
|
||||||
|
return _.includes(this.registriesNames, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
getRegistries() {
|
||||||
|
return this.$async(async () => {
|
||||||
|
try {
|
||||||
|
const registries = await this.RegistryService.registries();
|
||||||
|
this.registriesNames = _.map(registries, 'Name');
|
||||||
|
} catch (err) {
|
||||||
|
this.Notifications.error('Failure', err, 'Unable to fetch existing registries');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
$onInit() {
|
$onInit() {
|
||||||
|
return this.$async(async () => {
|
||||||
this.model = new RegistryCreateFormValues();
|
this.model = new RegistryCreateFormValues();
|
||||||
|
|
||||||
const from = this.$transition$.from();
|
const from = this.$transition$.from();
|
||||||
|
@ -138,6 +157,8 @@ class CreateRegistryController {
|
||||||
this.state.originViewReference = from;
|
this.state.originViewReference = from;
|
||||||
this.state.originalEndpointId = params.endpointId || null;
|
this.state.originalEndpointId = params.endpointId || null;
|
||||||
}
|
}
|
||||||
|
await this.getRegistries();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
<rd-header>
|
<rd-header>
|
||||||
<rd-header-title title-text="Registry details"></rd-header-title>
|
<rd-header-title title-text="Registry details"></rd-header-title>
|
||||||
<rd-header-content>
|
<rd-header-content>
|
||||||
<a ui-sref="portainer.registries">Registries</a> > <a ui-sref="portainer.registries.registry({id: registry.Id})">{{ registry.Name }}</a>
|
<a ui-sref="portainer.registries">Registries</a> > <a ui-sref="portainer.registries.registry({id: $ctrl.registry.Id})">{{ $ctrl.registry.Name }}</a>
|
||||||
</rd-header-content>
|
</rd-header-content>
|
||||||
</rd-header>
|
</rd-header>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row" ng-if="!$ctrl.state.loading">
|
||||||
<div class="col-sm-12">
|
<div class="col-sm-12">
|
||||||
<rd-widget>
|
<rd-widget>
|
||||||
<rd-widget-body>
|
<rd-widget-body>
|
||||||
|
@ -14,7 +14,14 @@
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="registry_name" class="col-sm-3 col-lg-2 control-label text-left">Name</label>
|
<label for="registry_name" class="col-sm-3 col-lg-2 control-label text-left">Name</label>
|
||||||
<div class="col-sm-9 col-lg-10">
|
<div class="col-sm-9 col-lg-10">
|
||||||
<input type="text" class="form-control" id="registry_name" ng-model="registry.Name" placeholder="e.g. my-registry" />
|
<input type="text" class="form-control" id="registry_name" ng-model="$ctrl.formValues.Name" placeholder="e.g. my-registry" ng-change="$ctrl.onChangeName()" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group" ng-show="$ctrl.state.nameAlreadyExists">
|
||||||
|
<div class="col-sm-12 small text-warning">
|
||||||
|
<div>
|
||||||
|
<p><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> A registry with the same name already exists.</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- !name-input -->
|
<!-- !name-input -->
|
||||||
|
@ -22,81 +29,79 @@
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="registry_url" class="col-sm-3 col-lg-2 control-label text-left">
|
<label for="registry_url" class="col-sm-3 col-lg-2 control-label text-left">
|
||||||
Registry URL
|
Registry URL
|
||||||
<portainer-tooltip
|
<portainer-tooltip position="bottom" message="URL or IP address of a Docker registry. Any protocol or trailing slash will be stripped if present.">
|
||||||
position="bottom"
|
</portainer-tooltip>
|
||||||
message="URL or IP address of a Docker registry. Any protocol or trailing slash will be stripped if present."
|
|
||||||
></portainer-tooltip>
|
|
||||||
</label>
|
</label>
|
||||||
<div class="col-sm-9 col-lg-10">
|
<div class="col-sm-9 col-lg-10">
|
||||||
<input type="text" class="form-control" id="registry_url" ng-model="registry.URL" placeholder="e.g. 10.0.0.10:5000 or myregistry.domain.tld" />
|
<input type="text" class="form-control" id="registry_url" ng-model="$ctrl.registry.URL" placeholder="e.g. 10.0.0.10:5000 or myregistry.domain.tld" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- !registry-url-input -->
|
<!-- !registry-url-input -->
|
||||||
|
|
||||||
<!-- authentication-checkbox -->
|
<!-- authentication-checkbox -->
|
||||||
<div class="form-group" ng-if="registry.Type !== RegistryTypes.PROGET">
|
<div class="form-group" ng-if="$ctrl.registry.Type !== $ctrl.RegistryTypes.PROGET">
|
||||||
<div class="col-sm-12">
|
<div class="col-sm-12">
|
||||||
<label for="registry_auth" class="control-label text-left">
|
<label for="registry_auth" class="control-label text-left">
|
||||||
Authentication
|
Authentication
|
||||||
<portainer-tooltip position="bottom" message="Enable this option if you need to specify credentials to connect to this registry."></portainer-tooltip>
|
<portainer-tooltip position="bottom" message="Enable this option if you need to specify credentials to connect to this registry."> </portainer-tooltip>
|
||||||
</label>
|
</label>
|
||||||
<label class="switch" style="margin-left: 20px"> <input type="checkbox" ng-model="registry.Authentication" /><i></i> </label>
|
<label class="switch" style="margin-left: 20px"> <input type="checkbox" ng-model="$ctrl.registry.Authentication" /><i></i> </label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- !authentication-checkbox -->
|
<!-- !authentication-checkbox -->
|
||||||
|
|
||||||
<!-- authentication-credentials -->
|
<!-- authentication-credentials -->
|
||||||
<div ng-if="registry.Authentication">
|
<div ng-if="$ctrl.registry.Authentication">
|
||||||
<!-- credentials-user -->
|
<!-- credentials-user -->
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="credentials_username" class="col-sm-3 col-lg-2 control-label text-left">
|
<label for="credentials_username" class="col-sm-3 col-lg-2 control-label text-left">
|
||||||
{{ registry.Type === RegistryTypes.ECR ? 'AWS Access Key' : 'Username' }}
|
{{ $ctrl.registry.Type === $ctrl.RegistryTypes.ECR ? 'AWS Access Key' : 'Username' }}
|
||||||
</label>
|
</label>
|
||||||
<div class="col-sm-9 col-lg-10">
|
<div class="col-sm-9 col-lg-10">
|
||||||
<input type="text" class="form-control" id="credentials_username" ng-model="registry.Username" />
|
<input type="text" class="form-control" id="credentials_username" ng-model="$ctrl.registry.Username" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- !credentials-user -->
|
<!-- !credentials-user -->
|
||||||
<!-- credentials-password -->
|
<!-- credentials-password -->
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="credentials_password" class="col-sm-3 col-lg-2 control-label text-left">
|
<label for="credentials_password" class="col-sm-3 col-lg-2 control-label text-left">
|
||||||
{{ passwordLabel() }}
|
{{ $ctrl.passwordLabel() }}
|
||||||
</label>
|
</label>
|
||||||
<div class="col-sm-9 col-lg-10">
|
<div class="col-sm-9 col-lg-10">
|
||||||
<input type="password" class="form-control" id="credentials_password" ng-model="formValues.Password" placeholder="*******" />
|
<input type="password" class="form-control" id="credentials_password" ng-model="$ctrl.formValues.Password" placeholder="*******" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- !credentials-password -->
|
<!-- !credentials-password -->
|
||||||
</div>
|
</div>
|
||||||
<!-- !authentication-credentials -->
|
<!-- !authentication-credentials -->
|
||||||
|
|
||||||
<div ng-if="registry.Type == RegistryTypes.QUAY">
|
<div ng-if="$ctrl.registry.Type == $ctrl.RegistryTypes.QUAY">
|
||||||
<!-- organisation-checkbox -->
|
<!-- organisation-checkbox -->
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="col-sm-12">
|
<div class="col-sm-12">
|
||||||
<label class="control-label text-left"> Use organisation registry </label>
|
<label class="control-label text-left"> Use organisation registry </label>
|
||||||
<label class="switch" style="margin-left: 20px"> <input type="checkbox" ng-model="registry.Quay.UseOrganisation" /><i></i> </label>
|
<label class="switch" style="margin-left: 20px"> <input type="checkbox" ng-model="$ctrl.registry.Quay.UseOrganisation" /><i></i> </label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- !organisation-checkbox -->
|
<!-- !organisation-checkbox -->
|
||||||
<div ng-if="registry.Quay.UseOrganisation">
|
<div ng-if="$ctrl.registry.Quay.UseOrganisation">
|
||||||
<!-- organisation_name -->
|
<!-- organisation_name -->
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="organisation_name" class="col-sm-3 col-lg-2 control-label text-left">Organisation name</label>
|
<label for="organisation_name" class="col-sm-3 col-lg-2 control-label text-left">Organisation name</label>
|
||||||
<div class="col-sm-9 col-lg-10">
|
<div class="col-sm-9 col-lg-10">
|
||||||
<input type="text" class="form-control" id="organisation_name" name="organisation_name" ng-model="registry.Quay.OrganisationName" required />
|
<input type="text" class="form-control" id="organisation_name" name="organisation_name" ng-model="$ctrl.registry.Quay.OrganisationName" required />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- !organisation_name -->
|
<!-- !organisation_name -->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div ng-if="registry.Type == RegistryTypes.ECR">
|
<div ng-if="$ctrl.registry.Type == $ctrl.RegistryTypes.ECR">
|
||||||
<!-- region -->
|
<!-- region -->
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="registry_region" class="col-sm-3 col-lg-2 control-label text-left">Region</label>
|
<label for="registry_region" class="col-sm-3 col-lg-2 control-label text-left">Region</label>
|
||||||
<div class="col-sm-9 col-lg-10">
|
<div class="col-sm-9 col-lg-10">
|
||||||
<input type="text" class="form-control" id="registry_region" name="registry_region" placeholder="us-west-1" ng-model="registry.Ecr.Region" required />
|
<input type="text" class="form-control" id="registry_region" name="registry_region" placeholder="us-west-1" ng-model="$ctrl.registry.Ecr.Region" required />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group" ng-show="registryFormEcr.registry_region.$invalid">
|
<div class="form-group" ng-show="registryFormEcr.registry_region.$invalid">
|
||||||
|
@ -114,12 +119,12 @@
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="btn btn-primary btn-sm"
|
class="btn btn-primary btn-sm"
|
||||||
ng-disabled="state.actionInProgress || !registry.Name || !registry.URL || (registry.Type == RegistryTypes.QUAY && registry.Quay.UseOrganisation && !registry.Quay.OrganisationName)"
|
ng-disabled="$ctrl.isUpdateButtonDisabled()"
|
||||||
ng-click="updateRegistry()"
|
ng-click="$ctrl.updateRegistry()"
|
||||||
button-spinner="state.actionInProgress"
|
button-spinner="$ctrl.state.actionInProgress"
|
||||||
>
|
>
|
||||||
<span ng-hide="state.actionInProgress">Update registry</span>
|
<span ng-hide="$ctrl.state.actionInProgress">Update registry</span>
|
||||||
<span ng-show="state.actionInProgress">Updating registry...</span>
|
<span ng-show="$ctrl.state.actionInProgress">Updating registry...</span>
|
||||||
</button>
|
</button>
|
||||||
<a type="button" class="btn btn-default btn-sm" ui-sref="portainer.registries">Cancel</a>
|
<a type="button" class="btn btn-default btn-sm" ui-sref="portainer.registries">Cancel</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
import angular from 'angular';
|
||||||
|
import controller from './registryController';
|
||||||
|
|
||||||
|
angular.module('portainer.app').component('editRegistry', {
|
||||||
|
templateUrl: './registry.html',
|
||||||
|
controller,
|
||||||
|
bindings: {
|
||||||
|
$transition$: '<',
|
||||||
|
},
|
||||||
|
});
|
|
@ -1,23 +1,25 @@
|
||||||
|
import _ from 'lodash';
|
||||||
import { RegistryTypes } from '@/portainer/models/registryTypes';
|
import { RegistryTypes } from '@/portainer/models/registryTypes';
|
||||||
|
|
||||||
angular.module('portainer.app').controller('RegistryController', [
|
export default class RegistryController {
|
||||||
'$scope',
|
/* @ngInject */
|
||||||
'$state',
|
constructor($async, $state, RegistryService, Notifications) {
|
||||||
'RegistryService',
|
Object.assign(this, { $async, $state, RegistryService, Notifications });
|
||||||
'Notifications',
|
|
||||||
function ($scope, $state, RegistryService, Notifications) {
|
this.RegistryTypes = RegistryTypes;
|
||||||
$scope.state = {
|
|
||||||
|
this.state = {
|
||||||
actionInProgress: false,
|
actionInProgress: false,
|
||||||
|
loading: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.formValues = {
|
this.formValues = {
|
||||||
Password: '',
|
Password: '',
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
$scope.RegistryTypes = RegistryTypes;
|
passwordLabel() {
|
||||||
|
const type = this.registry.Type;
|
||||||
$scope.passwordLabel = () => {
|
|
||||||
const type = $scope.registry.Type;
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case RegistryTypes.ECR:
|
case RegistryTypes.ECR:
|
||||||
return 'AWS Secret Access Key';
|
return 'AWS Secret Access Key';
|
||||||
|
@ -28,34 +30,55 @@ angular.module('portainer.app').controller('RegistryController', [
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.updateRegistry = function () {
|
updateRegistry() {
|
||||||
var registry = $scope.registry;
|
return this.$async(async () => {
|
||||||
registry.Password = $scope.formValues.Password;
|
try {
|
||||||
$scope.state.actionInProgress = true;
|
this.state.actionInProgress = true;
|
||||||
RegistryService.updateRegistry(registry)
|
const registry = this.registry;
|
||||||
.then(function success() {
|
registry.Password = this.formValues.Password;
|
||||||
Notifications.success('Registry successfully updated');
|
registry.Name = this.formValues.Name;
|
||||||
$state.go('portainer.registries');
|
|
||||||
})
|
|
||||||
.catch(function error(err) {
|
|
||||||
Notifications.error('Failure', err, 'Unable to update registry');
|
|
||||||
})
|
|
||||||
.finally(function final() {
|
|
||||||
$scope.state.actionInProgress = false;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
function initView() {
|
await this.RegistryService.updateRegistry(registry);
|
||||||
var registryID = $state.params.id;
|
this.Notifications.success('Registry successfully updated');
|
||||||
RegistryService.registry(registryID)
|
this.$state.go('portainer.registries');
|
||||||
.then(function success(data) {
|
} catch (err) {
|
||||||
$scope.registry = data;
|
this.Notifications.error('Failure', err, 'Unable to update registry');
|
||||||
})
|
} finally {
|
||||||
.catch(function error(err) {
|
this.state.actionInProgress = false;
|
||||||
Notifications.error('Failure', err, 'Unable to retrieve registry details');
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
initView();
|
onChangeName() {
|
||||||
},
|
this.state.nameAlreadyExists = _.includes(this.registriesNames, this.formValues.Name);
|
||||||
]);
|
}
|
||||||
|
|
||||||
|
isUpdateButtonDisabled() {
|
||||||
|
return (
|
||||||
|
this.state.actionInProgress ||
|
||||||
|
this.state.nameAlreadyExists ||
|
||||||
|
!this.registry.Name ||
|
||||||
|
!this.registry.URL ||
|
||||||
|
(this.registry.Type == this.RegistryTypes.QUAY && this.registry.Quay.UseOrganisation && !this.registry.Quay.OrganisationName)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async $onInit() {
|
||||||
|
try {
|
||||||
|
this.state.loading = true;
|
||||||
|
|
||||||
|
const registryId = this.$state.params.id;
|
||||||
|
const registry = await this.RegistryService.registry(registryId);
|
||||||
|
this.registry = registry;
|
||||||
|
this.formValues.Name = registry.Name;
|
||||||
|
|
||||||
|
const registries = await this.RegistryService.registries();
|
||||||
|
_.pullAllBy(registries, [registry], 'Id');
|
||||||
|
this.registriesNames = _.map(registries, 'Name');
|
||||||
|
} catch (err) {
|
||||||
|
this.Notifications.error('Failure', err, 'Unable to retrieve registry details');
|
||||||
|
} finally {
|
||||||
|
this.state.loading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue