mirror of
https://github.com/portainer/portainer.git
synced 2025-08-10 00:05:24 +02:00
feat(app): rework private registries and support private registries in kubernetes EE-30 (#5131)
* feat(app): rework private registries and support private registries in kubernetes [EE-30] feat(api): backport private registries backend changes (#5072) * feat(api/bolt): backport bolt changes * feat(api/exec): backport exec changes * feat(api/http): backport http/handler/dockerhub changes * feat(api/http): backport http/handler/endpoints changes * feat(api/http): backport http/handler/registries changes * feat(api/http): backport http/handler/stacks changes * feat(api/http): backport http/handler changes * feat(api/http): backport http/proxy/factory/azure changes * feat(api/http): backport http/proxy/factory/docker changes * feat(api/http): backport http/proxy/factory/utils changes * feat(api/http): backport http/proxy/factory/kubernetes changes * feat(api/http): backport http/proxy/factory changes * feat(api/http): backport http/security changes * feat(api/http): backport http changes * feat(api/internal): backport internal changes * feat(api): backport api changes * feat(api/kubernetes): backport kubernetes changes * fix(api/http): changes on backend following backport feat(app): backport private registries frontend changes (#5056) * feat(app/docker): backport docker/components changes * feat(app/docker): backport docker/helpers changes * feat(app/docker): backport docker/views/container changes * feat(app/docker): backport docker/views/images changes * feat(app/docker): backport docker/views/registries changes * feat(app/docker): backport docker/views/services changes * feat(app/docker): backport docker changes * feat(app/kubernetes): backport kubernetes/components changes * feat(app/kubernetes): backport kubernetes/converters changes * feat(app/kubernetes): backport kubernetes/models changes * feat(app/kubernetes): backport kubernetes/registries changes * feat(app/kubernetes): backport kubernetes/services changes * feat(app/kubernetes): backport kubernetes/views/applications changes * feat(app/kubernetes): backport kubernetes/views/configurations changes * feat(app/kubernetes): backport kubernetes/views/configure changes * feat(app/kubernetes): backport kubernetes/views/resource-pools changes * feat(app/kubernetes): backport kubernetes/views changes * feat(app/portainer): backport portainer/components/accessManagement changes * feat(app/portainer): backport portainer/components/datatables changes * feat(app/portainer): backport portainer/components/forms changes * feat(app/portainer): backport portainer/components/registry-details changes * feat(app/portainer): backport portainer/models changes * feat(app/portainer): backport portainer/rest changes * feat(app/portainer): backport portainer/services changes * feat(app/portainer): backport portainer/views changes * feat(app/portainer): backport portainer changes * feat(app): backport app changes * config(project): gitignore + jsconfig changes gitignore all files under api/cmd/portainer but main.go and enable Code Editor autocomplete on import ... from '@/...' fix(app): fix pull rate limit checker fix(app/registries): sidebar menus and registry accesses users filtering fix(api): add missing kube client factory fix(kube): fetch dockerhub pull limits (#5133) fix(app): pre review fixes (#5142) * fix(app/registries): remove checkbox for endpointRegistries view * fix(endpoints): allow access to default namespace * fix(docker): fetch pull limits * fix(kube/ns): show selected registries for non admin Co-authored-by: Chaim Lev-Ari <chiptus@gmail.com> chore(webpack): ignore missing sourcemaps fix(registries): fetch registry config from url feat(kube/registries): ignore not found when deleting secret feat(db): move migration to db 31 fix(registries): fix bugs in PR EE-869 (#5169) * fix(registries): hide role * fix(endpoints): set empty access policy to edge endpoint * fix(registry): remove double arguments * fix(admin): ignore warning * feat(kube/configurations): tag registry secrets (#5157) * feat(kube/configurations): tag registry secrets * feat(kube/secrets): show registry secrets for admins * fix(registries): move dockerhub to beginning * refactor(registries): use endpoint scoped registries feat(registries): filter by namespace if supplied feat(access-managment): filter users for registry (#5191) * refactor(access-manage): move users selector to component * feat(access-managment): filter users for registry refactor(registries): sync code with CE (#5200) * refactor(registry): add inspect handler under endpoints * refactor(endpoint): sync endpoint_registries_list * refactor(endpoints): sync registry_access * fix(db): rename migration functions * fix(registries): show accesses for admin * fix(kube): set token on transport * refactor(kube): move secret help to bottom * fix(kuberentes): remove shouldLog parameter * style(auth): add description of security.IsAdmin * feat(security): allow admin access to registry * feat(edge): connect to edge endpoint when creating client * style(portainer): change deprecation version * refactor(sidebar): hide manage * refactor(containers): revert changes * style(container): remove whitespace * fix(endpoint): add handler to registy on endpointService * refactor(image): use endpointService.registries * fix(kueb/namespaces): rename resource pool to namespace * fix(kube/namespace): move selected registries * fix(api/registries): hide accesses on registry creation Co-authored-by: LP B <xAt0mZ@users.noreply.github.com> refactor(api): remove code duplication after rebase fix(app/registries): replace last registry api usage by endpoint registry api fix(api/endpoints): update registry access policies on endpoint deletion (#5226) [EE-1027] fix(db): update db version * fix(dockerhub): fetch rate limits * fix(registry/tests): supply restricred context * fix(registries): show proget registry only when selected * fix(registry): create dockerhub registry * feat(db): move migrations to db 32 Co-authored-by: Chaim Lev-Ari <chiptus@gmail.com>
This commit is contained in:
parent
0f5407da40
commit
179df06267
175 changed files with 3757 additions and 2544 deletions
|
@ -295,24 +295,12 @@ angular.module('portainer.app', ['portainer.oauth', componentsModule]).config([
|
|||
},
|
||||
};
|
||||
|
||||
var registryCreation = {
|
||||
const registryCreation = {
|
||||
name: 'portainer.registries.new',
|
||||
url: '/new',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: './views/registries/create/createregistry.html',
|
||||
controller: 'CreateRegistryController',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
var registryAccess = {
|
||||
name: 'portainer.registries.registry.access',
|
||||
url: '/access',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: './views/registries/access/registryAccess.html',
|
||||
controller: 'RegistryAccessController',
|
||||
component: 'createRegistry',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -425,7 +413,6 @@ angular.module('portainer.app', ['portainer.oauth', componentsModule]).config([
|
|||
$stateRegistryProvider.register(initAdmin);
|
||||
$stateRegistryProvider.register(registries);
|
||||
$stateRegistryProvider.register(registry);
|
||||
$stateRegistryProvider.register(registryAccess);
|
||||
$stateRegistryProvider.register(registryCreation);
|
||||
$stateRegistryProvider.register(settings);
|
||||
$stateRegistryProvider.register(settingsAuthentication);
|
||||
|
|
9
app/portainer/components/accessManagement/index.js
Normal file
9
app/portainer/components/accessManagement/index.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
import angular from 'angular';
|
||||
|
||||
import { porAccessManagement } from './por-access-management';
|
||||
import { porAccessManagementUsersSelector } from './por-access-management-users-selector';
|
||||
|
||||
export default angular
|
||||
.module('portainer.app.component.access-management', [])
|
||||
.component('porAccessManagement', porAccessManagement)
|
||||
.component('porAccessManagementUsersSelector', porAccessManagementUsersSelector).name;
|
|
@ -0,0 +1,7 @@
|
|||
export const porAccessManagementUsersSelector = {
|
||||
templateUrl: './por-access-management-users-selector.html',
|
||||
bindings: {
|
||||
options: '<',
|
||||
value: '=',
|
||||
},
|
||||
};
|
|
@ -0,0 +1,23 @@
|
|||
<div class="form-group">
|
||||
<label class="col-sm-3 col-lg-2 control-label text-left">
|
||||
Select user(s) and/or team(s)
|
||||
</label>
|
||||
<div class="col-sm-9 col-lg-4">
|
||||
<span class="small text-muted" ng-if="$ctrl.options.length === 0">
|
||||
No users or teams available.
|
||||
</span>
|
||||
<span
|
||||
isteven-multi-select
|
||||
ng-if="$ctrl.options.length > 0"
|
||||
input-model="$ctrl.options"
|
||||
output-model="$ctrl.value"
|
||||
button-label="icon '-' Name"
|
||||
item-label="icon '-' Name"
|
||||
tick-property="ticked"
|
||||
helper-elements="filter"
|
||||
search-property="Name"
|
||||
translation="{nothingSelected: 'Select one or more users and/or teams', search: 'Search...'}"
|
||||
>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
|
@ -1,4 +1,4 @@
|
|||
angular.module('portainer.app').component('porAccessManagement', {
|
||||
export const porAccessManagement = {
|
||||
templateUrl: './porAccessManagement.html',
|
||||
controller: 'porAccessManagementController',
|
||||
controllerAs: 'ctrl',
|
||||
|
@ -8,5 +8,6 @@ angular.module('portainer.app').component('porAccessManagement', {
|
|||
entityType: '@',
|
||||
updateAccess: '<',
|
||||
actionInProgress: '<',
|
||||
filterUsers: '<',
|
||||
},
|
||||
});
|
||||
};
|
||||
|
|
|
@ -4,31 +4,9 @@
|
|||
<rd-widget-header icon="fa-user-lock" title-text="Create access"></rd-widget-header>
|
||||
<rd-widget-body>
|
||||
<form class="form-horizontal">
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 col-lg-2 control-label text-left">
|
||||
Select user(s) and/or team(s)
|
||||
</label>
|
||||
<div class="col-sm-9 col-lg-4">
|
||||
<span class="small text-muted" ng-if="ctrl.availableUsersAndTeams.length === 0">
|
||||
No users or teams available.
|
||||
</span>
|
||||
<span
|
||||
isteven-multi-select
|
||||
ng-if="ctrl.availableUsersAndTeams.length > 0"
|
||||
input-model="ctrl.availableUsersAndTeams"
|
||||
output-model="ctrl.formValues.multiselectOutput"
|
||||
button-label="icon '-' Name"
|
||||
item-label="icon '-' Name"
|
||||
tick-property="ticked"
|
||||
helper-elements="filter"
|
||||
search-property="Name"
|
||||
translation="{nothingSelected: 'Select one or more users and/or teams', search: 'Search...'}"
|
||||
>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<por-access-management-users-selector options="ctrl.availableUsersAndTeams" value="ctrl.formValues.multiselectOutput"></por-access-management-users-selector>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="form-group" ng-if="ctrl.entityType != 'registry'">
|
||||
<label class="col-sm-3 col-lg-2 control-label text-left">
|
||||
Role
|
||||
</label>
|
||||
|
|
|
@ -44,6 +44,7 @@ class PorAccessManagementController {
|
|||
const teamAccessPolicies = entity.TeamAccessPolicies;
|
||||
const selectedUserAccesses = _.filter(selectedAccesses, (access) => access.Type === 'user');
|
||||
const selectedTeamAccesses = _.filter(selectedAccesses, (access) => access.Type === 'team');
|
||||
|
||||
_.forEach(selectedUserAccesses, (access) => delete userAccessPolicies[access.Id]);
|
||||
_.forEach(selectedTeamAccesses, (access) => delete teamAccessPolicies[access.Id]);
|
||||
this.updateAccess();
|
||||
|
@ -55,6 +56,11 @@ class PorAccessManagementController {
|
|||
const parent = this.inheritFrom;
|
||||
|
||||
const data = await this.AccessService.accesses(entity, parent, this.roles);
|
||||
|
||||
if (this.filterUsers) {
|
||||
data.availableUsersAndTeams = this.filterUsers(data.availableUsersAndTeams);
|
||||
}
|
||||
|
||||
this.availableUsersAndTeams = _.orderBy(data.availableUsersAndTeams, 'Name', 'asc');
|
||||
this.authorizedUsersAndTeams = data.authorizedUsersAndTeams;
|
||||
} catch (err) {
|
||||
|
|
|
@ -4,8 +4,14 @@
|
|||
<div class="toolBar">
|
||||
<div class="toolBarTitle"> <i class="fa" ng-class="$ctrl.titleIcon" aria-hidden="true" style="margin-right: 2px;"></i> {{ $ctrl.titleText }} </div>
|
||||
</div>
|
||||
<div class="actionBar" ng-if="$ctrl.accessManagement">
|
||||
<button type="button" class="btn btn-sm btn-danger" ng-disabled="$ctrl.state.selectedItemCount === 0" ng-click="$ctrl.removeAction($ctrl.state.selectedItems)">
|
||||
<div class="actionBar" ng-if="$ctrl.isAdmin">
|
||||
<button
|
||||
ng-if="!$ctrl.endpointType"
|
||||
type="button"
|
||||
class="btn btn-sm btn-danger"
|
||||
ng-disabled="$ctrl.state.selectedItemCount === 0"
|
||||
ng-click="$ctrl.removeAction($ctrl.state.selectedItems)"
|
||||
>
|
||||
<i class="fa fa-trash-alt space-right" aria-hidden="true"></i>Remove
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-primary" ui-sref="portainer.registries.new"> <i class="fa fa-plus space-right" aria-hidden="true"></i>Add registry </button>
|
||||
|
@ -27,7 +33,7 @@
|
|||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<span class="md-checkbox" ng-if="$ctrl.accessManagement">
|
||||
<span class="md-checkbox" ng-if="$ctrl.isAdmin && !$ctrl.endpointType">
|
||||
<input id="select_all" type="checkbox" ng-model="$ctrl.state.selectAll" ng-change="$ctrl.selectAll()" />
|
||||
<label for="select_all"></label>
|
||||
</span>
|
||||
|
@ -53,22 +59,29 @@
|
|||
ng-class="{ active: item.Checked }"
|
||||
>
|
||||
<td>
|
||||
<span class="md-checkbox" ng-if="$ctrl.accessManagement">
|
||||
<input id="select_{{ $index }}" type="checkbox" ng-model="item.Checked" ng-click="$ctrl.selectItem(item, $event)" />
|
||||
<span class="md-checkbox" ng-if="$ctrl.isAdmin && !$ctrl.endpointType">
|
||||
<input id="select_{{ $index }}" type="checkbox" ng-model="item.Checked" ng-click="$ctrl.selectItem(item, $event)" ng-disabled="!$ctrl.allowSelection(item)" />
|
||||
<label for="select_{{ $index }}"></label>
|
||||
</span>
|
||||
<a ui-sref="portainer.registries.registry({id: item.Id})" ng-if="$ctrl.accessManagement">{{ item.Name }}</a>
|
||||
<span ng-if="!$ctrl.accessManagement">{{ item.Name }}</span>
|
||||
<a ui-sref="portainer.registries.registry({id: item.Id})" ng-if="$ctrl.enableGoToLink(item)">{{ item.Name }}</a>
|
||||
<span ng-if="!$ctrl.enableGoToLink(item)">{{ item.Name }}</span>
|
||||
<span ng-if="item.Authentication" style="margin-left: 5px;" class="label label-info image-tag">authentication-enabled</span>
|
||||
</td>
|
||||
<td>
|
||||
{{ item.URL }}
|
||||
</td>
|
||||
<td>
|
||||
<a ui-sref="portainer.registries.registry.access({id: item.Id})" ng-if="$ctrl.accessManagement"> <i class="fa fa-users" aria-hidden="true"></i> Manage access </a>
|
||||
<span class="text-muted space-left" style="cursor: pointer;" data-toggle="tooltip" title="This feature is available in Portainer Business Edition">
|
||||
<a ng-if="$ctrl.canManageAccess(item)" ng-click="$ctrl.redirectToManageAccess(item)"> <i class="fa fa-users" aria-hidden="true"></i> Manage access </a>
|
||||
<span
|
||||
ng-if="$ctrl.canBrowse(item)"
|
||||
class="text-muted space-left"
|
||||
style="cursor: pointer;"
|
||||
data-toggle="tooltip"
|
||||
title="This feature is available in Portainer Business Edition"
|
||||
>
|
||||
<i class="fa fa-search" aria-hidden="true"></i> Browse</span
|
||||
>
|
||||
<span ng-if="!$ctrl.canBrowse(item) && !$ctrl.canManageAccess(item)"> - </span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="!$ctrl.dataset">
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
angular.module('portainer.app').component('registriesDatatable', {
|
||||
templateUrl: './registriesDatatable.html',
|
||||
controller: 'GenericDatatableController',
|
||||
controller: 'RegistriesDatatableController',
|
||||
bindings: {
|
||||
titleText: '@',
|
||||
titleIcon: '@',
|
||||
|
@ -8,8 +8,9 @@ angular.module('portainer.app').component('registriesDatatable', {
|
|||
tableKey: '@',
|
||||
orderBy: '@',
|
||||
reverseOrder: '<',
|
||||
accessManagement: '<',
|
||||
removeAction: '<',
|
||||
canBrowse: '<',
|
||||
endpointType: '<',
|
||||
canManageAccess: '<',
|
||||
},
|
||||
});
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
import { PortainerEndpointTypes } from 'Portainer/models/endpoint/models';
|
||||
|
||||
angular.module('portainer.docker').controller('RegistriesDatatableController', RegistriesDatatableController);
|
||||
|
||||
/* @ngInject */
|
||||
function RegistriesDatatableController($scope, $controller, $state, Authentication, DatatableService) {
|
||||
angular.extend(this, $controller('GenericDatatableController', { $scope: $scope }));
|
||||
|
||||
this.allowSelection = function (item) {
|
||||
return item.Id;
|
||||
};
|
||||
|
||||
this.enableGoToLink = (item) => {
|
||||
return this.isAdmin && item.Id && !this.endpointType;
|
||||
};
|
||||
|
||||
this.goToRegistry = function (item) {
|
||||
if (
|
||||
this.endpointType === PortainerEndpointTypes.KubernetesLocalEnvironment ||
|
||||
this.endpointType === PortainerEndpointTypes.AgentOnKubernetesEnvironment ||
|
||||
this.endpointType === PortainerEndpointTypes.EdgeAgentOnKubernetesEnvironment
|
||||
) {
|
||||
$state.go('kubernetes.registries.registry', { id: item.Id });
|
||||
} else if (
|
||||
this.endpointType === PortainerEndpointTypes.DockerEnvironment ||
|
||||
this.endpointType === PortainerEndpointTypes.AgentOnDockerEnvironment ||
|
||||
this.endpointType === PortainerEndpointTypes.EdgeAgentOnDockerEnvironment
|
||||
) {
|
||||
$state.go('docker.registries.registry', { id: item.Id });
|
||||
} else {
|
||||
$state.go('portainer.registries.registry', { id: item.Id });
|
||||
}
|
||||
};
|
||||
|
||||
this.redirectToManageAccess = function (item) {
|
||||
if (
|
||||
this.endpointType === PortainerEndpointTypes.KubernetesLocalEnvironment ||
|
||||
this.endpointType === PortainerEndpointTypes.AgentOnKubernetesEnvironment ||
|
||||
this.endpointType === PortainerEndpointTypes.EdgeAgentOnKubernetesEnvironment
|
||||
) {
|
||||
$state.go('kubernetes.registries.access', { id: item.Id });
|
||||
} else {
|
||||
$state.go('docker.registries.access', { id: item.Id });
|
||||
}
|
||||
};
|
||||
|
||||
this.$onInit = function () {
|
||||
this.isAdmin = Authentication.isAdmin();
|
||||
this.setDefaults();
|
||||
this.prepareTableFromDataset();
|
||||
|
||||
this.state.orderBy = this.orderBy;
|
||||
var storedOrder = DatatableService.getDataTableOrder(this.tableKey);
|
||||
if (storedOrder !== null) {
|
||||
this.state.reverseOrder = storedOrder.reverse;
|
||||
this.state.orderBy = storedOrder.orderBy;
|
||||
}
|
||||
|
||||
var textFilter = DatatableService.getDataTableTextFilters(this.tableKey);
|
||||
if (textFilter !== null) {
|
||||
this.state.textFilter = textFilter;
|
||||
this.onTextFilterChange();
|
||||
}
|
||||
|
||||
var storedFilters = DatatableService.getDataTableFilters(this.tableKey);
|
||||
if (storedFilters !== null) {
|
||||
this.filters = storedFilters;
|
||||
}
|
||||
if (this.filters && this.filters.state) {
|
||||
this.filters.state.open = false;
|
||||
}
|
||||
|
||||
var storedSettings = DatatableService.getDataTableSettings(this.tableKey);
|
||||
if (storedSettings !== null) {
|
||||
this.settings = storedSettings;
|
||||
this.settings.open = false;
|
||||
}
|
||||
this.onSettingsRepeaterChange();
|
||||
|
||||
var storedColumnVisibility = DatatableService.getColumnVisibilitySettings(this.tableKey);
|
||||
if (storedColumnVisibility !== null) {
|
||||
this.columnVisibility = storedColumnVisibility;
|
||||
}
|
||||
};
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
import angular from 'angular';
|
||||
// import controller from './strings-datatable.controller.js';
|
||||
|
||||
export const stringsDatatable = {
|
||||
templateUrl: './strings-datatable.html',
|
||||
controller: 'GenericDatatableController',
|
||||
bindings: {
|
||||
titleText: '@',
|
||||
titleIcon: '@',
|
||||
dataset: '<',
|
||||
emptyDatasetMessage: '@',
|
||||
|
||||
columnHeader: '@',
|
||||
tableKey: '@',
|
||||
|
||||
onRemove: '<',
|
||||
},
|
||||
};
|
||||
|
||||
angular.module('portainer.app').component('stringsDatatable', stringsDatatable);
|
|
@ -0,0 +1,65 @@
|
|||
<div class="datatable">
|
||||
<rd-widget>
|
||||
<rd-widget-header icon="{{ $ctrl.titleIcon }}" title-text="{{ $ctrl.titleText }}"> </rd-widget-header>
|
||||
<rd-widget-body classes="no-padding">
|
||||
<div class="actionBar">
|
||||
<button type="button" class="btn btn-sm btn-danger" ng-disabled="$ctrl.state.selectedItemCount === 0" ng-click="$ctrl.onRemove($ctrl.state.selectedItems)">
|
||||
<i class="fa fa-trash-alt space-right" aria-hidden="true"></i>Remove
|
||||
</button>
|
||||
</div>
|
||||
<div class="searchBar">
|
||||
<i class="fa fa-search searchIcon" aria-hidden="true"></i>
|
||||
<input
|
||||
type="text"
|
||||
class="searchInput"
|
||||
ng-model="$ctrl.state.textFilter"
|
||||
ng-change="$ctrl.onTextFilterChange()"
|
||||
placeholder="Search..."
|
||||
ng-model-options="{ debounce: 300 }"
|
||||
/>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover nowrap-cells">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<span class="md-checkbox">
|
||||
<input id="select_all" type="checkbox" ng-model="$ctrl.state.selectAll" ng-change="$ctrl.selectAll()" />
|
||||
<label for="select_all"></label>
|
||||
</span>
|
||||
<a ng-click="$ctrl.changeOrderBy('Name')">
|
||||
{{ $ctrl.columnHeader }}
|
||||
<i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Name' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Name' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
dir-paginate="item in ($ctrl.state.filteredDataSet = ($ctrl.dataset | filter:$ctrl.state.textFilter | orderBy:$ctrl.state.orderBy:$ctrl.state.reverseOrder | itemsPerPage: $ctrl.state.paginatedItemLimit)) track by $index"
|
||||
ng-class="{ active: $ctrl.state.selectedItems.includes(item) }"
|
||||
>
|
||||
<td>
|
||||
<span class="md-checkbox">
|
||||
<input
|
||||
id="select_{{ $index }}"
|
||||
type="checkbox"
|
||||
ng-checked="$ctrl.state.selectedItems.includes(item)"
|
||||
ng-disabled="$ctrl.disableRemove(item)"
|
||||
ng-click="$ctrl.selectItem(item, $event)"
|
||||
/>
|
||||
<label for="select_{{ $index }}"></label>
|
||||
</span>
|
||||
{{ item.value }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="$ctrl.state.filteredDataSet.length === 0">
|
||||
<td class="text-center text-muted">{{ $ctrl.emptyDatasetMessage }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
|
@ -0,0 +1,64 @@
|
|||
<form class="form-horizontal" name="registryFormDockerhub" ng-submit="$ctrl.formAction()">
|
||||
<div class="col-sm-12 form-section-title">
|
||||
DockerHub account details
|
||||
</div>
|
||||
<!-- name-input -->
|
||||
<div class="form-group">
|
||||
<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">
|
||||
<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 class="form-group" ng-show="registryFormDockerhub.registry_name.$invalid">
|
||||
<div class="col-sm-12 small text-warning">
|
||||
<div ng-messages="registryFormDockerhub.registry_name.$error">
|
||||
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !name-input -->
|
||||
<!-- credentials-user -->
|
||||
<div class="form-group">
|
||||
<label for="registry_username" class="col-sm-3 col-lg-2 control-label text-left">DockerHub username</label>
|
||||
<div class="col-sm-9 col-lg-10">
|
||||
<input type="text" class="form-control" id="registry_username" name="registry_username" ng-model="$ctrl.model.Username" required />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" ng-show="registryFormDockerhub.registry_username.$invalid">
|
||||
<div class="col-sm-12 small text-warning">
|
||||
<div ng-messages="registryFormDockerhub.registry_username.$error">
|
||||
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !credentials-user -->
|
||||
<!-- credentials-password -->
|
||||
<div class="form-group">
|
||||
<label for="registry_password" class="col-sm-3 col-lg-2 control-label text-left">DockerHub password</label>
|
||||
<div class="col-sm-9 col-lg-10">
|
||||
<input type="password" class="form-control" id="registry_password" name="registry_password" ng-model="$ctrl.model.Password" required />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" ng-show="registryFormDockerhub.registry_password.$invalid">
|
||||
<div class="col-sm-12 small text-warning">
|
||||
<div ng-messages="registryFormDockerhub.registry_password.$error">
|
||||
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !credentials-password -->
|
||||
|
||||
<!-- actions -->
|
||||
<div class="col-sm-12 form-section-title">
|
||||
Actions
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<button type="submit" class="btn btn-primary btn-sm" ng-disabled="$ctrl.actionInProgress || !registryFormDockerhub.$valid" button-spinner="$ctrl.actionInProgress">
|
||||
<span ng-hide="$ctrl.actionInProgress">{{ $ctrl.formActionLabel }}</span>
|
||||
<span ng-show="$ctrl.actionInProgress">In progress...</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !actions -->
|
||||
</form>
|
|
@ -0,0 +1,9 @@
|
|||
angular.module('portainer.app').component('registryFormDockerhub', {
|
||||
templateUrl: './registry-form-dockerhub.html',
|
||||
bindings: {
|
||||
model: '=',
|
||||
formAction: '<',
|
||||
formActionLabel: '@',
|
||||
actionInProgress: '<',
|
||||
},
|
||||
});
|
|
@ -1,13 +0,0 @@
|
|||
angular.module('portainer.app').component('templateForm', {
|
||||
templateUrl: './templateForm.html',
|
||||
controller: 'TemplateFormController',
|
||||
bindings: {
|
||||
model: '=',
|
||||
categories: '<',
|
||||
networks: '<',
|
||||
formAction: '<',
|
||||
formActionLabel: '@',
|
||||
actionInProgress: '<',
|
||||
showTypeSelector: '<',
|
||||
},
|
||||
});
|
|
@ -1,580 +0,0 @@
|
|||
<form class="form-horizontal" name="templateForm">
|
||||
<!-- title-input -->
|
||||
<div class="form-group" ng-class="{ 'has-error': templateForm.template_title.$invalid }">
|
||||
<label for="template_title" class="col-sm-3 col-lg-2 control-label text-left">Title</label>
|
||||
<div class="col-sm-9 col-lg-10">
|
||||
<input type="text" class="form-control" name="template_title" ng-model="$ctrl.model.Title" placeholder="e.g. my-template" required auto-focus />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" ng-show="templateForm.template_title.$invalid">
|
||||
<div class="col-sm-12 small text-warning">
|
||||
<div ng-messages="templateForm.template_title.$error">
|
||||
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !title-input -->
|
||||
<!-- description-input -->
|
||||
<div class="form-group" ng-class="{ 'has-error': templateForm.template_description.$invalid }">
|
||||
<label for="template_description" class="col-sm-3 col-lg-2 control-label text-left">Description</label>
|
||||
<div class="col-sm-9 col-lg-10">
|
||||
<input type="text" class="form-control" name="template_description" ng-model="$ctrl.model.Description" placeholder="e.g. template description..." required />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" ng-show="templateForm.template_description.$invalid">
|
||||
<div class="col-sm-12 small text-warning">
|
||||
<div ng-messages="templateForm.template_description.$error">
|
||||
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !description-input -->
|
||||
<div class="col-sm-12 form-section-title interactive" ng-click="$ctrl.state.collapseTemplate = !$ctrl.state.collapseTemplate">
|
||||
Template
|
||||
<span class="small space-left">
|
||||
<a ng-if="$ctrl.state.collapseTemplate"><i class="fa fa-plus" aria-hidden="true"></i> expand</a>
|
||||
<a ng-if="!$ctrl.state.collapseTemplate"><i class="fa fa-minus" aria-hidden="true"></i> collapse</a>
|
||||
</span>
|
||||
</div>
|
||||
<!-- template-details -->
|
||||
<div uib-collapse="$ctrl.state.collapseTemplate">
|
||||
<div ng-if="$ctrl.showTypeSelector">
|
||||
<div class="form-group"></div>
|
||||
<div class="form-group" style="margin-bottom: 0;">
|
||||
<div class="boxselector_wrapper">
|
||||
<div>
|
||||
<input type="radio" id="template_container" ng-model="$ctrl.model.Type" ng-value="1" />
|
||||
<label for="template_container">
|
||||
<div class="boxselector_header">
|
||||
<i class="fa fa-cubes" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||
Container
|
||||
</div>
|
||||
<p>Container template</p>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<input type="radio" id="template_swarm_stack" ng-model="$ctrl.model.Type" ng-value="2" />
|
||||
<label for="template_swarm_stack">
|
||||
<div class="boxselector_header">
|
||||
<i class="fa fa-th-list" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||
Swarm stack
|
||||
</div>
|
||||
<p>Stack template (Swarm)</p>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<input type="radio" id="template_compose_stack" ng-model="$ctrl.model.Type" ng-value="3" />
|
||||
<label for="template_compose_stack">
|
||||
<div class="boxselector_header">
|
||||
<i class="fa fa-th-list" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||
Compose stack
|
||||
</div>
|
||||
<p>Stack template (Compose)</p>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- name -->
|
||||
<div class="form-group">
|
||||
<label for="template_name" class="col-sm-3 col-lg-2 control-label text-left">
|
||||
Name
|
||||
<portainer-tooltip position="bottom" message="Default name that will be associated to the template"></portainer-tooltip>
|
||||
</label>
|
||||
<div class="col-sm-9 col-lg-10">
|
||||
<input type="text" class="form-control" name="template_name" ng-model="$ctrl.model.Name" placeholder="e.g. myApp" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- !name -->
|
||||
<!-- logo -->
|
||||
<div class="form-group">
|
||||
<label for="template_logo" class="col-sm-3 col-lg-2 control-label text-left">
|
||||
Logo URL
|
||||
<portainer-tooltip position="bottom" message="Recommended size: 60x60"></portainer-tooltip>
|
||||
</label>
|
||||
<div class="col-sm-9 col-lg-10">
|
||||
<input type="text" class="form-control" name="template_logo" ng-model="$ctrl.model.Logo" placeholder="e.g. https://portainer.io/images/logos/nginx.png" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- !logo -->
|
||||
<!-- note -->
|
||||
<div class="form-group">
|
||||
<label for="template_note" class="col-sm-3 col-lg-2 control-label text-left">
|
||||
Note
|
||||
<portainer-tooltip position="bottom" message="Usage/extra information about the template. Supports HTML."></portainer-tooltip>
|
||||
</label>
|
||||
<div class="col-sm-9 col-lg-10">
|
||||
<textarea
|
||||
class="form-control"
|
||||
name="template_note"
|
||||
ng-model="$ctrl.model.Note"
|
||||
placeholder="You can use this field to specify extra information. <br/> It supports <b>HTML</b>."
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !note -->
|
||||
<!-- platform -->
|
||||
<div class="form-group">
|
||||
<label for="template_platform" class="col-sm-3 col-lg-2 control-label text-left">
|
||||
Platform
|
||||
</label>
|
||||
<div class="col-sm-9 col-lg-10">
|
||||
<select class="form-control" name="template_platform" ng-model="$ctrl.model.Platform">
|
||||
<option value="">Multi-platform</option>
|
||||
<option value="linux">Linux</option>
|
||||
<option value="windows">Windows</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !platform -->
|
||||
<!-- categories -->
|
||||
<div class="form-group">
|
||||
<label for="template_categories" class="col-sm-3 col-lg-2 control-label text-left">
|
||||
Categories
|
||||
</label>
|
||||
<div class="col-sm-9 col-lg-10">
|
||||
<ui-select multiple tagging tagging-label="(new category)" ng-model="$ctrl.model.Categories" sortable="true" style="width: 300px;" title="Choose a category">
|
||||
<ui-select-match placeholder="Select categories...">{{ $item }}</ui-select-match>
|
||||
<ui-select-choices repeat="category in $ctrl.categories | filter:$select.search">
|
||||
{{ category }}
|
||||
</ui-select-choices>
|
||||
</ui-select>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !categories -->
|
||||
<!-- administrator-only -->
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<label for="tls" class="control-label text-left">
|
||||
Administrator template
|
||||
<portainer-tooltip position="bottom" message="This template will only be available to administrator users."></portainer-tooltip>
|
||||
</label>
|
||||
<label class="switch" style="margin-left: 20px;"> <input type="checkbox" ng-model="$ctrl.model.AdministratorOnly" /><i></i> </label>
|
||||
</div>
|
||||
</div>
|
||||
<!-- administrator-only -->
|
||||
</div>
|
||||
<!-- !template-details -->
|
||||
<div ng-if="$ctrl.model.Type === 2 || $ctrl.model.Type === 3">
|
||||
<div class="col-sm-12 form-section-title interactive" ng-click="$ctrl.state.collapseStack = !$ctrl.state.collapseStack">
|
||||
Stack
|
||||
<span class="small space-left">
|
||||
<a ng-if="$ctrl.state.collapseStack"><i class="fa fa-plus" aria-hidden="true"></i> expand</a>
|
||||
<a ng-if="!$ctrl.state.collapseStack"><i class="fa fa-minus" aria-hidden="true"></i> collapse</a>
|
||||
</span>
|
||||
</div>
|
||||
<!-- stack-details -->
|
||||
<div uib-collapse="$ctrl.state.collapseStack">
|
||||
<!-- repository-url -->
|
||||
<div class="form-group" ng-class="{ 'has-error': templateForm.template_repository_url.$invalid }">
|
||||
<label for="template_repository_url" class="col-sm-3 col-lg-2 control-label text-left">Repository URL</label>
|
||||
<div class="col-sm-9 col-lg-10">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
name="template_repository_url"
|
||||
ng-model="$ctrl.model.Repository.url"
|
||||
placeholder="https://github.com/portainer/portainer-compose"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" ng-show="templateForm.template_repository_url.$invalid">
|
||||
<div class="col-sm-12 small text-warning">
|
||||
<div ng-messages="templateForm.template_repository_url.$error">
|
||||
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !repository-url -->
|
||||
<!-- composefile-path -->
|
||||
<div class="form-group">
|
||||
<label for="template_repository_path" class="col-sm-3 col-lg-2 control-label text-left">
|
||||
Compose file path
|
||||
</label>
|
||||
<div class="col-sm-9 col-lg-10">
|
||||
<input type="text" class="form-control" name="template_repository_path" ng-model="$ctrl.model.Repository.stackfile" placeholder="docker-compose.yml" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- !composefile-path -->
|
||||
</div>
|
||||
<!-- !stack-details -->
|
||||
</div>
|
||||
<div ng-if="$ctrl.model.Type === 1">
|
||||
<div class="col-sm-12 form-section-title interactive" ng-click="$ctrl.state.collapseContainer = !$ctrl.state.collapseContainer">
|
||||
Container
|
||||
<span class="small space-left">
|
||||
<a ng-if="$ctrl.state.collapseContainer"><i class="fa fa-plus" aria-hidden="true"></i> expand</a>
|
||||
<a ng-if="!$ctrl.state.collapseContainer"><i class="fa fa-minus" aria-hidden="true"></i> collapse</a>
|
||||
</span>
|
||||
</div>
|
||||
<!-- container-details -->
|
||||
<div uib-collapse="$ctrl.state.collapseContainer">
|
||||
<por-image-registry model="$ctrl.model.RegistryModel" auto-complete="true" label-class="col-sm-1" input-class="col-sm-11"></por-image-registry>
|
||||
<!-- command -->
|
||||
<div class="form-group">
|
||||
<label for="template_command" class="col-sm-3 col-lg-2 control-label text-left">
|
||||
Command
|
||||
<portainer-tooltip
|
||||
position="bottom"
|
||||
message="The command to run in the container. If not specified, the container will use the default command specified in its Dockerfile."
|
||||
></portainer-tooltip>
|
||||
</label>
|
||||
<div class="col-sm-9 col-lg-10">
|
||||
<input type="text" class="form-control" name="template_command" ng-model="$ctrl.model.Command" placeholder='/bin/bash -c \"echo hello\" && exit 777' />
|
||||
</div>
|
||||
</div>
|
||||
<!-- !command -->
|
||||
<!-- hostname -->
|
||||
<div class="form-group">
|
||||
<label for="template_hostname" class="col-sm-3 col-lg-2 control-label text-left">
|
||||
Hostname
|
||||
<portainer-tooltip position="bottom" message="Set the hostname of the container. Will use Docker default if not specified."></portainer-tooltip>
|
||||
</label>
|
||||
<div class="col-sm-9 col-lg-10">
|
||||
<input type="text" class="form-control" name="template_hostname" ng-model="$ctrl.model.Hostname" placeholder="mycontainername" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- !hostname -->
|
||||
<!-- network -->
|
||||
<div class="form-group">
|
||||
<label for="template_network" class="col-sm-3 col-lg-2 control-label text-left">
|
||||
Network
|
||||
</label>
|
||||
<div class="col-sm-10">
|
||||
<select class="form-control" ng-options="net.Name for net in $ctrl.networks" ng-model="$ctrl.model.Network">
|
||||
<option disabled hidden value="">Select a network</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !network -->
|
||||
<!-- port-mapping -->
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12" style="margin-top: 5px;">
|
||||
<label class="control-label text-left">Port mapping</label>
|
||||
<span class="label label-default interactive" style="margin-left: 10px;" ng-click="$ctrl.addPortBinding()">
|
||||
<i class="fa fa-plus-circle" aria-hidden="true"></i> map additional port
|
||||
</span>
|
||||
</div>
|
||||
<div class="col-sm-12" style="margin-top: 10px;" ng-if="$ctrl.model.Ports.length > 0">
|
||||
<span class="small text-muted">Portainer will automatically assign a port if you leave the host port empty.</span>
|
||||
</div>
|
||||
<!-- port-mapping-input-list -->
|
||||
<div class="col-sm-12">
|
||||
<div class="col-sm-12 form-inline" style="margin-top: 10px;">
|
||||
<div ng-repeat="portBinding in $ctrl.model.Ports" style="margin-top: 2px;">
|
||||
<!-- host-port -->
|
||||
<div class="input-group col-sm-4 input-group-sm">
|
||||
<span class="input-group-addon">host</span>
|
||||
<input type="text" class="form-control" ng-model="portBinding.hostPort" placeholder="e.g. 80 or 1.2.3.4:80 (optional)" />
|
||||
</div>
|
||||
<!-- !host-port -->
|
||||
<span style="margin: 0 10px 0 10px;">
|
||||
<i class="fa fa-long-arrow-alt-right" aria-hidden="true"></i>
|
||||
</span>
|
||||
<!-- container-port -->
|
||||
<div class="input-group col-sm-4 input-group-sm">
|
||||
<span class="input-group-addon">container</span>
|
||||
<input type="text" class="form-control" ng-model="portBinding.containerPort" placeholder="e.g. 80" />
|
||||
</div>
|
||||
<!-- !container-port -->
|
||||
<!-- protocol-actions -->
|
||||
<div class="input-group col-sm-3 input-group-sm">
|
||||
<div class="btn-group btn-group-sm">
|
||||
<label class="btn btn-primary" ng-model="portBinding.protocol" uib-btn-radio="'tcp'">TCP</label>
|
||||
<label class="btn btn-primary" ng-model="portBinding.protocol" uib-btn-radio="'udp'">UDP</label>
|
||||
</div>
|
||||
<button class="btn btn-sm btn-danger" type="button" ng-click="$ctrl.removePortBinding($index)">
|
||||
<i class="fa fa-trash" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
<!-- !protocol-actions -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !port-mapping-input-list -->
|
||||
</div>
|
||||
<!-- !port-mapping -->
|
||||
<!-- volumes -->
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12" style="margin-top: 5px;">
|
||||
<label class="control-label text-left">Volume mapping</label>
|
||||
<span class="label label-default interactive" style="margin-left: 10px;" ng-click="$ctrl.addVolume()">
|
||||
<i class="fa fa-plus-circle" aria-hidden="true"></i> map additional volume
|
||||
</span>
|
||||
</div>
|
||||
<div class="col-sm-12" style="margin-top: 10px;" ng-if="$ctrl.model.Volumes.length > 0">
|
||||
<span class="small text-muted">Portainer will automatically create and map a local volume when using the <b>auto</b> option.</span>
|
||||
</div>
|
||||
<div ng-repeat="volume in $ctrl.model.Volumes">
|
||||
<div class="col-sm-12" style="margin-top: 10px;">
|
||||
<!-- volume-line1 -->
|
||||
<div class="col-sm-12 form-inline">
|
||||
<!-- container-path -->
|
||||
<div class="input-group input-group-sm col-sm-6">
|
||||
<span class="input-group-addon">container</span>
|
||||
<input type="text" class="form-control" ng-model="volume.container" placeholder="e.g. /path/in/container" />
|
||||
</div>
|
||||
<!-- !container-path -->
|
||||
<!-- volume-type -->
|
||||
<div class="input-group col-sm-5" style="margin-left: 5px;">
|
||||
<div class="btn-group btn-group-sm">
|
||||
<label class="btn btn-primary" ng-model="volume.type" uib-btn-radio="'auto'" ng-click="volume.bind = ''">Auto</label>
|
||||
<label class="btn btn-primary" ng-model="volume.type" uib-btn-radio="'bind'" ng-click="volume.bind = ''">Bind</label>
|
||||
</div>
|
||||
<button class="btn btn-sm btn-danger" type="button" ng-click="$ctrl.removeVolume($index)">
|
||||
<i class="fa fa-trash" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
<!-- !volume-type -->
|
||||
</div>
|
||||
<!-- !volume-line1 -->
|
||||
<!-- volume-line2 -->
|
||||
<div class="col-sm-12 form-inline" style="margin-top: 5px;" ng-if="volume.type !== 'auto'">
|
||||
<i class="fa fa-long-arrow-alt-right" aria-hidden="true"></i>
|
||||
<!-- bind -->
|
||||
<div class="input-group input-group-sm col-sm-6" ng-if="volume.type === 'bind'">
|
||||
<span class="input-group-addon">host</span>
|
||||
<input type="text" class="form-control" ng-model="volume.bind" placeholder="e.g. /path/on/host" />
|
||||
</div>
|
||||
<!-- !bind -->
|
||||
<!-- read-only -->
|
||||
<div class="input-group input-group-sm col-sm-5" style="margin-left: 5px;">
|
||||
<div class="btn-group btn-group-sm">
|
||||
<label class="btn btn-primary" ng-model="volume.readonly" uib-btn-radio="false">Writable</label>
|
||||
<label class="btn btn-primary" ng-model="volume.readonly" uib-btn-radio="true">Read-only</label>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !read-only -->
|
||||
</div>
|
||||
<!-- !volume-line2 -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !volumes -->
|
||||
<!-- labels -->
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12" style="margin-top: 5px;">
|
||||
<label class="control-label text-left">Labels</label>
|
||||
<span class="label label-default interactive" style="margin-left: 10px;" ng-click="$ctrl.addLabel()">
|
||||
<i class="fa fa-plus-circle" aria-hidden="true"></i> add label
|
||||
</span>
|
||||
</div>
|
||||
<!-- labels-input-list -->
|
||||
<div class="col-sm-12">
|
||||
<div class="col-sm-12 form-inline" style="margin-top: 10px;">
|
||||
<div ng-repeat="label in $ctrl.model.Labels" style="margin-top: 2px;">
|
||||
<div class="input-group col-sm-5 input-group-sm">
|
||||
<span class="input-group-addon">name</span>
|
||||
<input type="text" class="form-control" ng-model="label.name" placeholder="e.g. com.example.foo" />
|
||||
</div>
|
||||
<div class="input-group col-sm-5 input-group-sm">
|
||||
<span class="input-group-addon">value</span>
|
||||
<input type="text" class="form-control" ng-model="label.value" placeholder="e.g. bar" />
|
||||
</div>
|
||||
<button class="btn btn-sm btn-danger" type="button" ng-click="$ctrl.removeLabel($index)">
|
||||
<i class="fa fa-trash" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !labels-input-list -->
|
||||
</div>
|
||||
<!-- !labels -->
|
||||
<!-- restart_policy -->
|
||||
<div class="form-group">
|
||||
<label for="template_restart_policy" class="col-sm-3 col-lg-2 control-label text-left">
|
||||
Restart policy
|
||||
</label>
|
||||
<div class="col-sm-9 col-lg-10">
|
||||
<select class="form-control" name="template_platform" ng-model="$ctrl.model.RestartPolicy">
|
||||
<option value="always">Always</option>
|
||||
<option value="unless-stopped">Unless stopped</option>
|
||||
<option value="on-failure">On failure</option>
|
||||
<option value="no">None</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !restart_policy -->
|
||||
<!-- privileged-mode -->
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<label for="tls" class="control-label text-left">
|
||||
Privileged mode
|
||||
<portainer-tooltip position="bottom" message="Start the container in privileged mode."></portainer-tooltip>
|
||||
</label>
|
||||
<label class="switch" style="margin-left: 20px;"> <input type="checkbox" ng-model="$ctrl.model.Privileged" /><i></i> </label>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !privileged-mode -->
|
||||
<!-- interactive-mode -->
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<label for="tls" class="control-label text-left">
|
||||
Interactive mode
|
||||
<portainer-tooltip position="bottom" message="Start the container in foreground (equivalent of -i -t flags)."></portainer-tooltip>
|
||||
</label>
|
||||
<label class="switch" style="margin-left: 20px;"> <input type="checkbox" ng-model="$ctrl.model.Interactive" /><i></i> </label>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !interactive-mode -->
|
||||
</div>
|
||||
<!-- !container-details -->
|
||||
</div>
|
||||
<div class="col-sm-12 form-section-title interactive" ng-click="$ctrl.state.collapseEnv = !$ctrl.state.collapseEnv">
|
||||
Environment
|
||||
<span class="small space-left">
|
||||
<a ng-if="$ctrl.state.collapseEnv"><i class="fa fa-plus" aria-hidden="true"></i> expand</a>
|
||||
<a ng-if="!$ctrl.state.collapseEnv"><i class="fa fa-minus" aria-hidden="true"></i> collapse</a>
|
||||
</span>
|
||||
</div>
|
||||
<!-- environment-details -->
|
||||
<div uib-collapse="$ctrl.state.collapseEnv">
|
||||
<!-- env -->
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12" style="margin-top: 5px;">
|
||||
<label class="control-label text-left">Environment variables</label>
|
||||
<span class="label label-default interactive" style="margin-left: 10px;" ng-click="$ctrl.addEnvVar()">
|
||||
<i class="fa fa-plus-circle" aria-hidden="true"></i> add variable
|
||||
</span>
|
||||
</div>
|
||||
<!-- env-var-list -->
|
||||
<div style="margin-top: 10px;">
|
||||
<div class="col-sm-12 template-envvar" ng-repeat="var in $ctrl.model.Env" style="margin-top: 10px;">
|
||||
<div class="form-group"></div>
|
||||
<div class="form-group" style="margin-bottom: 0;">
|
||||
<div class="boxselector_wrapper">
|
||||
<div>
|
||||
<input type="radio" id="preset_var_{{ $index }}" ng-model="var.type" ng-value="1" ng-change="$ctrl.changeEnvVarType(var)" />
|
||||
<label for="preset_var_{{ $index }}">
|
||||
<div class="boxselector_header">
|
||||
<i class="fa fa-user-slash" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||
Preset
|
||||
</div>
|
||||
<p>Preset variable</p>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<input type="radio" id="text_var_{{ $index }}" ng-model="var.type" ng-value="2" ng-change="$ctrl.changeEnvVarType(var)" />
|
||||
<label for="text_var_{{ $index }}">
|
||||
<div class="boxselector_header">
|
||||
<i class="fa fa-edit" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||
Text
|
||||
</div>
|
||||
<p>Free text value</p>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<input type="radio" id="select_var_{{ $index }}" ng-model="var.type" ng-value="3" />
|
||||
<label for="select_var_{{ $index }}">
|
||||
<div class="boxselector_header">
|
||||
<i class="fa fa-list-ol" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||
Select
|
||||
</div>
|
||||
<p>Choose value from list</p>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label text-left">
|
||||
Name
|
||||
</label>
|
||||
<div class="col-sm-8">
|
||||
<input type="text" class="form-control" ng-model="var.name" placeholder="env_var" />
|
||||
</div>
|
||||
<div class="col-sm-2">
|
||||
<button class="btn btn-sm btn-danger space-left" type="button" ng-click="$ctrl.removeEnvVar($index)">
|
||||
<i class="fa fa-trash" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-if="var.type == 2 || var.type == 3">
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label text-left">
|
||||
Label
|
||||
</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" class="form-control" ng-model="var.label" placeholder="Choose a label" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label text-left" style="margin-top: 2px;">
|
||||
Description
|
||||
</label>
|
||||
<div class="col-sm-10" style="margin-top: 2px;">
|
||||
<input type="text" class="form-control" ng-model="var.description" placeholder="Tooltip" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" ng-if="var.type === 1 || var.type === 2">
|
||||
<label class="col-sm-2 control-label text-left">
|
||||
Default value
|
||||
</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" class="form-control" ng-model="var.default" placeholder="default_value" />
|
||||
</div>
|
||||
</div>
|
||||
<div ng-if="var.type === 3" style="margin-bottom: 5px;" class="form-group">
|
||||
<div class="col-sm-12" style="margin-top: 5px;">
|
||||
<label class="control-label text-left">Values</label>
|
||||
<span class="label label-default interactive" style="margin-left: 10px;" ng-click="$ctrl.addEnvVarValue(var)">
|
||||
<i class="fa fa-plus-circle" aria-hidden="true"></i> add allowed value
|
||||
</span>
|
||||
</div>
|
||||
<!-- envvar-values-list -->
|
||||
<div class="col-sm-12">
|
||||
<div class="col-sm-12 form-inline" style="margin-top: 10px;">
|
||||
<div ng-repeat="val in var.select" style="margin-top: 2px;">
|
||||
<div class="input-group col-sm-5 input-group-sm">
|
||||
<span class="input-group-addon">name</span>
|
||||
<input type="text" class="form-control" ng-model="val.text" placeholder="Yes, I agree" />
|
||||
</div>
|
||||
<div class="input-group col-sm-5 input-group-sm">
|
||||
<span class="input-group-addon">value</span>
|
||||
<input type="text" class="form-control" ng-model="val.value" placeholder="Y" />
|
||||
</div>
|
||||
<div class="input-group col-sm-1 input-group-sm">
|
||||
<button class="btn btn-sm btn-danger" type="button" ng-click="$ctrl.removeEnvVarValue(var, $index)">
|
||||
<i class="fa fa-trash" aria-hidden="true"></i>
|
||||
</button>
|
||||
<input style="margin-left: 5px;" type="checkbox" ng-model="val.default" id="val_default_{{ $index }}" /><label for="val_default_{{ $index }}" class="space-left"
|
||||
>Default</label
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- envvar-values-list -->
|
||||
</div>
|
||||
<div class="col-sm-12" ng-show="$ctrl.model.Env.length > 1">
|
||||
<div class="line-separator"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !env-var-list -->
|
||||
</div>
|
||||
<!-- !env -->
|
||||
</div>
|
||||
<!-- !environment-details -->
|
||||
<!-- actions -->
|
||||
<div class="col-sm-12 form-section-title">
|
||||
Actions
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary btn-sm"
|
||||
ng-click="$ctrl.formAction()"
|
||||
ng-disabled="$ctrl.actionInProgress || !templateForm.$valid"
|
||||
button-spinner="$ctrl.actionInProgress"
|
||||
>
|
||||
<span ng-hide="$ctrl.actionInProgress">{{ $ctrl.formActionLabel }}</span>
|
||||
<span ng-show="$ctrl.actionInProgress">In progress...</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !actions -->
|
||||
</form>
|
|
@ -1,55 +0,0 @@
|
|||
angular.module('portainer.app').controller('TemplateFormController', [
|
||||
function () {
|
||||
this.state = {
|
||||
collapseTemplate: false,
|
||||
collapseContainer: false,
|
||||
collapseStack: false,
|
||||
collapseEnv: false,
|
||||
};
|
||||
|
||||
this.addPortBinding = function () {
|
||||
this.model.Ports.push({ containerPort: '', protocol: 'tcp' });
|
||||
};
|
||||
|
||||
this.removePortBinding = function (index) {
|
||||
this.model.Ports.splice(index, 1);
|
||||
};
|
||||
|
||||
this.addVolume = function () {
|
||||
this.model.Volumes.push({ container: '', bind: '', readonly: false, type: 'auto' });
|
||||
};
|
||||
|
||||
this.removeVolume = function (index) {
|
||||
this.model.Volumes.splice(index, 1);
|
||||
};
|
||||
|
||||
this.addLabel = function () {
|
||||
this.model.Labels.push({ name: '', value: '' });
|
||||
};
|
||||
|
||||
this.removeLabel = function (index) {
|
||||
this.model.Labels.splice(index, 1);
|
||||
};
|
||||
|
||||
this.addEnvVar = function () {
|
||||
this.model.Env.push({ type: 1, name: '', label: '', description: '', default: '', preset: true, select: [] });
|
||||
};
|
||||
|
||||
this.removeEnvVar = function (index) {
|
||||
this.model.Env.splice(index, 1);
|
||||
};
|
||||
|
||||
this.addEnvVarValue = function (env) {
|
||||
env.select = env.select || [];
|
||||
env.select.push({ name: '', value: '' });
|
||||
};
|
||||
|
||||
this.removeEnvVarValue = function (env, index) {
|
||||
env.select.splice(index, 1);
|
||||
};
|
||||
|
||||
this.changeEnvVarType = function (env) {
|
||||
env.preset = env.type === 1;
|
||||
};
|
||||
},
|
||||
]);
|
|
@ -1,5 +1,6 @@
|
|||
import angular from 'angular';
|
||||
|
||||
import gitFormModule from './forms/git-form';
|
||||
import porAccessManagementModule from './accessManagement';
|
||||
|
||||
export default angular.module('portainer.app.components', [gitFormModule]).name;
|
||||
export default angular.module('portainer.app.components', [gitFormModule, porAccessManagementModule]).name;
|
||||
|
|
10
app/portainer/components/registry-details/index.js
Normal file
10
app/portainer/components/registry-details/index.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
import angular from 'angular';
|
||||
|
||||
export const registryDetails = {
|
||||
templateUrl: './registry-details.html',
|
||||
bindings: {
|
||||
registry: '<',
|
||||
},
|
||||
};
|
||||
|
||||
angular.module('portainer.app').component('registryDetails', registryDetails);
|
|
@ -0,0 +1,25 @@
|
|||
<div class="row" ng-if="$ctrl.registry">
|
||||
<div class="col-lg-12 col-md-12 col-xs-12">
|
||||
<rd-widget>
|
||||
<rd-widget-header icon="fa-plug" title-text="Registry"></rd-widget-header>
|
||||
<rd-widget-body classes="no-padding">
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Name</td>
|
||||
<td>
|
||||
{{ $ctrl.registry.Name }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>URL</td>
|
||||
<td>
|
||||
{{ $ctrl.registry.URL }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
</div>
|
|
@ -1,36 +1,33 @@
|
|||
import _ from 'lodash-es';
|
||||
import { PortainerEndpointTypes } from 'Portainer/models/endpoint/models';
|
||||
|
||||
angular.module('portainer.app').factory('EndpointHelper', [
|
||||
function EndpointHelperFactory() {
|
||||
'use strict';
|
||||
var helper = {};
|
||||
function findAssociatedGroup(endpoint, groups) {
|
||||
return _.find(groups, function (group) {
|
||||
return group.Id === endpoint.GroupId;
|
||||
});
|
||||
}
|
||||
|
||||
function findAssociatedGroup(endpoint, groups) {
|
||||
return _.find(groups, function (group) {
|
||||
return group.Id === endpoint.GroupId;
|
||||
});
|
||||
}
|
||||
export default class EndpointHelper {
|
||||
static isLocalEndpoint(endpoint) {
|
||||
return endpoint.URL.includes('unix://') || endpoint.URL.includes('npipe://') || endpoint.Type === PortainerEndpointTypes.KubernetesLocalEnvironment;
|
||||
}
|
||||
|
||||
helper.isLocalEndpoint = isLocalEndpoint;
|
||||
function isLocalEndpoint(endpoint) {
|
||||
return endpoint.URL.includes('unix://') || endpoint.URL.includes('npipe://') || endpoint.Type === 5;
|
||||
}
|
||||
static isAgentEndpoint(endpoint) {
|
||||
return [
|
||||
PortainerEndpointTypes.AgentOnDockerEnvironment,
|
||||
PortainerEndpointTypes.EdgeAgentOnDockerEnvironment,
|
||||
PortainerEndpointTypes.AgentOnKubernetesEnvironment,
|
||||
PortainerEndpointTypes.EdgeAgentOnKubernetesEnvironment,
|
||||
].includes(endpoint.Type);
|
||||
}
|
||||
|
||||
helper.isAgentEndpoint = isAgentEndpoint;
|
||||
function isAgentEndpoint(endpoint) {
|
||||
return [2, 4, 6, 7].includes(endpoint.Type);
|
||||
}
|
||||
|
||||
helper.mapGroupNameToEndpoint = function (endpoints, groups) {
|
||||
for (var i = 0; i < endpoints.length; i++) {
|
||||
var endpoint = endpoints[i];
|
||||
var group = findAssociatedGroup(endpoint, groups);
|
||||
if (group) {
|
||||
endpoint.GroupName = group.Name;
|
||||
}
|
||||
static mapGroupNameToEndpoint(endpoints, groups) {
|
||||
for (var i = 0; i < endpoints.length; i++) {
|
||||
var endpoint = endpoints[i];
|
||||
var group = findAssociatedGroup(endpoint, groups);
|
||||
if (group) {
|
||||
endpoint.GroupName = group.Name;
|
||||
}
|
||||
};
|
||||
|
||||
return helper;
|
||||
},
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
export function DockerHubViewModel(data) {
|
||||
this.Name = 'DockerHub';
|
||||
this.URL = '';
|
||||
this.Authentication = data.Authentication;
|
||||
this.Username = data.Username;
|
||||
this.Password = data.Password;
|
||||
import { RegistryTypes } from './registryTypes';
|
||||
|
||||
export function DockerHubViewModel() {
|
||||
this.Id = 0;
|
||||
this.Type = RegistryTypes.ANONYMOUS;
|
||||
this.Name = 'DockerHub (anonymous)';
|
||||
this.URL = 'docker.io';
|
||||
}
|
||||
|
|
|
@ -10,10 +10,7 @@ export function RegistryViewModel(data) {
|
|||
this.Authentication = data.Authentication;
|
||||
this.Username = data.Username;
|
||||
this.Password = data.Password;
|
||||
this.AuthorizedUsers = data.AuthorizedUsers;
|
||||
this.AuthorizedTeams = data.AuthorizedTeams;
|
||||
this.UserAccessPolicies = data.UserAccessPolicies;
|
||||
this.TeamAccessPolicies = data.TeamAccessPolicies;
|
||||
this.RegistryAccesses = data.RegistryAccesses; // map[EndpointID]{UserAccessPolicies, TeamAccessPolicies, NamespaceAccessPolicies}
|
||||
this.Checked = false;
|
||||
this.Gitlab = data.Gitlab;
|
||||
this.Quay = data.Quay;
|
||||
|
@ -40,7 +37,7 @@ export function RegistryManagementConfigurationDefaultModel(registry) {
|
|||
}
|
||||
}
|
||||
|
||||
export function RegistryDefaultModel() {
|
||||
export function RegistryCreateFormValues() {
|
||||
this.Type = RegistryTypes.CUSTOM;
|
||||
this.URL = '';
|
||||
this.Name = '';
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
export const RegistryTypes = Object.freeze({
|
||||
ANONYMOUS: 0, // not backend replicated, only for frontend
|
||||
QUAY: 1,
|
||||
AZURE: 2,
|
||||
CUSTOM: 3,
|
||||
GITLAB: 4,
|
||||
PROGET: 5,
|
||||
DOCKERHUB: 6,
|
||||
});
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
angular.module('portainer.app').factory('DockerHub', [
|
||||
'$resource',
|
||||
'API_ENDPOINT_DOCKERHUB',
|
||||
function DockerHubFactory($resource, API_ENDPOINT_DOCKERHUB) {
|
||||
'use strict';
|
||||
return $resource(
|
||||
API_ENDPOINT_DOCKERHUB,
|
||||
{},
|
||||
{
|
||||
get: { method: 'GET' },
|
||||
update: { method: 'PUT' },
|
||||
}
|
||||
);
|
||||
},
|
||||
]);
|
|
@ -22,7 +22,26 @@ angular.module('portainer.app').factory('Endpoints', [
|
|||
snapshot: { method: 'POST', params: { id: '@id', action: 'snapshot' } },
|
||||
status: { method: 'GET', params: { id: '@id', action: 'status' } },
|
||||
updateSecuritySettings: { method: 'PUT', params: { id: '@id', action: 'settings' } },
|
||||
dockerhubLimits: { method: 'GET', params: { id: '@id', action: 'dockerhub' } },
|
||||
dockerhubLimits: {
|
||||
method: 'GET',
|
||||
url: `${API_ENDPOINT_ENDPOINTS}/:id/dockerhub/:registryId`,
|
||||
},
|
||||
registries: {
|
||||
method: 'GET',
|
||||
url: `${API_ENDPOINT_ENDPOINTS}/:id/registries`,
|
||||
params: { id: '@id', namespace: '@namespace' },
|
||||
isArray: true,
|
||||
},
|
||||
registry: {
|
||||
url: `${API_ENDPOINT_ENDPOINTS}/:id/registries/:registryId`,
|
||||
method: 'GET',
|
||||
params: { id: '@id', namespace: '@namespace', registryId: '@registryId' },
|
||||
},
|
||||
updateRegistryAccess: {
|
||||
method: 'PUT',
|
||||
url: `${API_ENDPOINT_ENDPOINTS}/:id/registries/:registryId`,
|
||||
params: { id: '@id', registryId: '@registryId' },
|
||||
},
|
||||
}
|
||||
);
|
||||
},
|
||||
|
|
|
@ -9,9 +9,8 @@ angular.module('portainer.app').factory('Registries', [
|
|||
{
|
||||
create: { method: 'POST', ignoreLoadingBar: true },
|
||||
query: { method: 'GET', isArray: true },
|
||||
get: { method: 'GET', params: { id: '@id' } },
|
||||
get: { method: 'GET', params: { id: '@id', action: '', endpointId: '@endpointId' } },
|
||||
update: { method: 'PUT', params: { id: '@id' } },
|
||||
updateAccess: { method: 'PUT', params: { id: '@id', action: 'access' } },
|
||||
remove: { method: 'DELETE', params: { id: '@id' } },
|
||||
configure: { method: 'POST', params: { id: '@id', action: 'configure' } },
|
||||
}
|
||||
|
|
|
@ -9,7 +9,10 @@ angular.module('portainer.app').factory('AccessService', [
|
|||
'TeamService',
|
||||
function AccessServiceFactory($q, $async, UserService, TeamService) {
|
||||
'use strict';
|
||||
var service = {};
|
||||
return {
|
||||
accesses,
|
||||
generateAccessPolicies,
|
||||
};
|
||||
|
||||
function _mapAccessData(accesses, authorizedPolicies, inheritedPolicies) {
|
||||
var availableAccesses = [];
|
||||
|
@ -76,7 +79,7 @@ angular.module('portainer.app').factory('AccessService', [
|
|||
async function accessesAsync(entity, parent) {
|
||||
try {
|
||||
if (!entity) {
|
||||
throw { msg: 'Unable to retrieve accesses' };
|
||||
throw new Error('Unable to retrieve accesses');
|
||||
}
|
||||
if (!entity.UserAccessPolicies) {
|
||||
entity.UserAccessPolicies = {};
|
||||
|
@ -100,9 +103,7 @@ angular.module('portainer.app').factory('AccessService', [
|
|||
return $async(accessesAsync, entity, parent);
|
||||
}
|
||||
|
||||
service.accesses = accesses;
|
||||
|
||||
service.generateAccessPolicies = function (userAccessPolicies, teamAccessPolicies, selectedUserAccesses, selectedTeamAccesses, selectedRoleId) {
|
||||
function generateAccessPolicies(userAccessPolicies, teamAccessPolicies, selectedUserAccesses, selectedTeamAccesses, selectedRoleId) {
|
||||
const newUserPolicies = _.clone(userAccessPolicies);
|
||||
const newTeamPolicies = _.clone(teamAccessPolicies);
|
||||
|
||||
|
@ -115,8 +116,6 @@ angular.module('portainer.app').factory('AccessService', [
|
|||
};
|
||||
|
||||
return accessPolicies;
|
||||
};
|
||||
|
||||
return service;
|
||||
}
|
||||
},
|
||||
]);
|
||||
|
|
|
@ -1,51 +1,27 @@
|
|||
import { DockerHubViewModel } from '../../models/dockerhub';
|
||||
import EndpointHelper from 'Portainer/helpers/endpointHelper';
|
||||
import { PortainerEndpointTypes } from 'Portainer/models/endpoint/models';
|
||||
|
||||
angular.module('portainer.app').factory('DockerHubService', [
|
||||
'$q',
|
||||
'DockerHub',
|
||||
'Endpoints',
|
||||
'AgentDockerhub',
|
||||
'EndpointHelper',
|
||||
function DockerHubServiceFactory($q, DockerHub, Endpoints, AgentDockerhub, EndpointHelper) {
|
||||
'use strict';
|
||||
var service = {};
|
||||
angular.module('portainer.app').factory('DockerHubService', DockerHubService);
|
||||
|
||||
service.dockerhub = function () {
|
||||
var deferred = $q.defer();
|
||||
/* @ngInject */
|
||||
function DockerHubService(Endpoints, AgentDockerhub) {
|
||||
return {
|
||||
checkRateLimits,
|
||||
};
|
||||
|
||||
DockerHub.get()
|
||||
.$promise.then(function success(data) {
|
||||
var dockerhub = new DockerHubViewModel(data);
|
||||
deferred.resolve(dockerhub);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to retrieve DockerHub details', err: err });
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
service.update = function (dockerhub) {
|
||||
return DockerHub.update({}, dockerhub).$promise;
|
||||
};
|
||||
|
||||
service.checkRateLimits = checkRateLimits;
|
||||
function checkRateLimits(endpoint) {
|
||||
if (EndpointHelper.isLocalEndpoint(endpoint)) {
|
||||
return Endpoints.dockerhubLimits({ id: endpoint.Id }).$promise;
|
||||
}
|
||||
|
||||
switch (endpoint.Type) {
|
||||
case 2: //AgentOnDockerEnvironment
|
||||
case 4: //EdgeAgentOnDockerEnvironment
|
||||
return AgentDockerhub.limits({ endpointId: endpoint.Id, endpointType: 'docker' }).$promise;
|
||||
|
||||
case 6: //AgentOnKubernetesEnvironment
|
||||
case 7: //EdgeAgentOnKubernetesEnvironment
|
||||
return AgentDockerhub.limits({ endpointId: endpoint.Id, endpointType: 'kubernetes' }).$promise;
|
||||
}
|
||||
function checkRateLimits(endpoint, registryId) {
|
||||
if (EndpointHelper.isLocalEndpoint(endpoint)) {
|
||||
return Endpoints.dockerhubLimits({ id: endpoint.Id, registryId }).$promise;
|
||||
}
|
||||
|
||||
return service;
|
||||
},
|
||||
]);
|
||||
switch (endpoint.Type) {
|
||||
case PortainerEndpointTypes.AgentOnDockerEnvironment:
|
||||
case PortainerEndpointTypes.EdgeAgentOnDockerEnvironment:
|
||||
return AgentDockerhub.limits({ endpointId: endpoint.Id, endpointType: 'docker', registryId }).$promise;
|
||||
|
||||
case PortainerEndpointTypes.AgentOnKubernetesEnvironment:
|
||||
case PortainerEndpointTypes.EdgeAgentOnKubernetesEnvironment:
|
||||
return AgentDockerhub.limits({ endpointId: endpoint.Id, endpointType: 'kubernetes', registryId }).$promise;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,9 @@ angular.module('portainer.app').factory('EndpointService', [
|
|||
'use strict';
|
||||
var service = {
|
||||
updateSecuritySettings,
|
||||
registries,
|
||||
registry,
|
||||
updateRegistryAccess,
|
||||
};
|
||||
|
||||
service.endpoint = function (endpointID) {
|
||||
|
@ -157,10 +160,22 @@ angular.module('portainer.app').factory('EndpointService', [
|
|||
return deferred.promise;
|
||||
};
|
||||
|
||||
function updateRegistryAccess(id, registryId, endpointAccesses) {
|
||||
return Endpoints.updateRegistryAccess({ registryId, id }, endpointAccesses).$promise;
|
||||
}
|
||||
|
||||
function registries(id, namespace) {
|
||||
return Endpoints.registries({ namespace, id }).$promise;
|
||||
}
|
||||
|
||||
return service;
|
||||
|
||||
function updateSecuritySettings(id, securitySettings) {
|
||||
return Endpoints.updateSecuritySettings({ id }, securitySettings).$promise;
|
||||
}
|
||||
|
||||
function registry(endpointId, registryId) {
|
||||
return Endpoints.registry({ registryId, id: endpointId }).$promise;
|
||||
}
|
||||
},
|
||||
]);
|
||||
|
|
|
@ -1,20 +1,30 @@
|
|||
import _ from 'lodash-es';
|
||||
import { PorImageRegistryModel } from 'Docker/models/porImageRegistry';
|
||||
import { RegistryTypes } from '@/portainer/models/registryTypes';
|
||||
import { RegistryCreateRequest, RegistryViewModel } from '../../models/registry';
|
||||
import { RegistryTypes } from 'Portainer/models/registryTypes';
|
||||
import { RegistryCreateRequest, RegistryViewModel } from 'Portainer/models/registry';
|
||||
import { DockerHubViewModel } from 'Portainer/models/dockerhub';
|
||||
|
||||
angular.module('portainer.app').factory('RegistryService', [
|
||||
'$q',
|
||||
'$async',
|
||||
'EndpointService',
|
||||
'Registries',
|
||||
'DockerHubService',
|
||||
'ImageHelper',
|
||||
'FileUploadService',
|
||||
function RegistryServiceFactory($q, $async, Registries, DockerHubService, ImageHelper, FileUploadService) {
|
||||
'use strict';
|
||||
var service = {};
|
||||
function RegistryServiceFactory($q, $async, EndpointService, Registries, ImageHelper, FileUploadService) {
|
||||
return {
|
||||
registries,
|
||||
registry,
|
||||
encodedCredentials,
|
||||
deleteRegistry,
|
||||
updateRegistry,
|
||||
configureRegistry,
|
||||
createRegistry,
|
||||
createGitlabRegistries,
|
||||
retrievePorRegistryModelFromRepository,
|
||||
};
|
||||
|
||||
service.registries = function () {
|
||||
function registries() {
|
||||
var deferred = $q.defer();
|
||||
|
||||
Registries.query()
|
||||
|
@ -29,12 +39,12 @@ angular.module('portainer.app').factory('RegistryService', [
|
|||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
}
|
||||
|
||||
service.registry = function (id) {
|
||||
function registry(id, endpointId) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
Registries.get({ id: id })
|
||||
Registries.get({ id, endpointId })
|
||||
.$promise.then(function success(data) {
|
||||
var registry = new RegistryViewModel(data);
|
||||
deferred.resolve(registry);
|
||||
|
@ -44,39 +54,35 @@ angular.module('portainer.app').factory('RegistryService', [
|
|||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
}
|
||||
|
||||
service.encodedCredentials = function (registry) {
|
||||
function encodedCredentials(registry) {
|
||||
var credentials = {
|
||||
serveraddress: registry.URL,
|
||||
registryId: registry.Id,
|
||||
};
|
||||
return btoa(JSON.stringify(credentials));
|
||||
};
|
||||
}
|
||||
|
||||
service.updateAccess = function (id, userAccessPolicies, teamAccessPolicies) {
|
||||
return Registries.updateAccess({ id: id }, { UserAccessPolicies: userAccessPolicies, TeamAccessPolicies: teamAccessPolicies }).$promise;
|
||||
};
|
||||
|
||||
service.deleteRegistry = function (id) {
|
||||
function deleteRegistry(id) {
|
||||
return Registries.remove({ id: id }).$promise;
|
||||
};
|
||||
}
|
||||
|
||||
service.updateRegistry = function (registry) {
|
||||
function updateRegistry(registry) {
|
||||
registry.URL = _.replace(registry.URL, /^https?\:\/\//i, '');
|
||||
registry.URL = _.replace(registry.URL, /\/$/, '');
|
||||
return Registries.update({ id: registry.Id }, registry).$promise;
|
||||
};
|
||||
}
|
||||
|
||||
service.configureRegistry = function (id, registryManagementConfigurationModel) {
|
||||
function configureRegistry(id, registryManagementConfigurationModel) {
|
||||
return FileUploadService.configureRegistry(id, registryManagementConfigurationModel);
|
||||
};
|
||||
}
|
||||
|
||||
service.createRegistry = function (model) {
|
||||
function createRegistry(model) {
|
||||
var payload = new RegistryCreateRequest(model);
|
||||
return Registries.create(payload).$promise;
|
||||
};
|
||||
}
|
||||
|
||||
service.createGitlabRegistries = function (model, projects) {
|
||||
function createGitlabRegistries(model, projects) {
|
||||
const promises = [];
|
||||
_.forEach(projects, (p) => {
|
||||
const m = model;
|
||||
|
@ -88,9 +94,7 @@ angular.module('portainer.app').factory('RegistryService', [
|
|||
promises.push(Registries.create(payload).$promise);
|
||||
});
|
||||
return $q.all(promises);
|
||||
};
|
||||
|
||||
service.retrievePorRegistryModelFromRepositoryWithRegistries = retrievePorRegistryModelFromRepositoryWithRegistries;
|
||||
}
|
||||
|
||||
function getURL(reg) {
|
||||
let url = reg.URL;
|
||||
|
@ -103,14 +107,23 @@ angular.module('portainer.app').factory('RegistryService', [
|
|||
return url;
|
||||
}
|
||||
|
||||
function retrievePorRegistryModelFromRepositoryWithRegistries(repository, registries, dockerhub) {
|
||||
function retrievePorRegistryModelFromRepositoryWithRegistries(repository, registries, registryId) {
|
||||
const model = new PorImageRegistryModel();
|
||||
const registry = _.find(registries, (reg) => _.includes(repository, getURL(reg)));
|
||||
const registry = registries.find((reg) => {
|
||||
if (registryId) {
|
||||
return reg.Id === registryId;
|
||||
}
|
||||
if (reg.Type === RegistryTypes.DOCKERHUB) {
|
||||
return _.includes(repository, reg.Username);
|
||||
}
|
||||
return _.includes(repository, getURL(reg));
|
||||
});
|
||||
if (registry) {
|
||||
const url = getURL(registry);
|
||||
const lastIndex = repository.lastIndexOf(url) + url.length;
|
||||
let lastIndex = repository.lastIndexOf(url);
|
||||
lastIndex = lastIndex === -1 ? 0 : lastIndex + url.length;
|
||||
let image = repository.substring(lastIndex);
|
||||
if (!_.startsWith(image, ':')) {
|
||||
if (_.startsWith(image, '/')) {
|
||||
image = image.substring(1);
|
||||
}
|
||||
model.Registry = registry;
|
||||
|
@ -119,25 +132,21 @@ angular.module('portainer.app').factory('RegistryService', [
|
|||
if (ImageHelper.imageContainsURL(repository)) {
|
||||
model.UseRegistry = false;
|
||||
}
|
||||
model.Registry = dockerhub;
|
||||
model.Registry = new DockerHubViewModel();
|
||||
model.Image = repository;
|
||||
}
|
||||
return model;
|
||||
}
|
||||
|
||||
async function retrievePorRegistryModelFromRepositoryAsync(repository) {
|
||||
try {
|
||||
let [registries, dockerhub] = await Promise.all([service.registries(), DockerHubService.dockerhub()]);
|
||||
return retrievePorRegistryModelFromRepositoryWithRegistries(repository, registries, dockerhub);
|
||||
} catch (err) {
|
||||
throw { msg: 'Unable to retrieve the registry associated to the repository', err: err };
|
||||
}
|
||||
function retrievePorRegistryModelFromRepository(repository, endpointId, registryId, namespace) {
|
||||
return $async(async () => {
|
||||
try {
|
||||
const regs = await EndpointService.registries(endpointId, namespace);
|
||||
return retrievePorRegistryModelFromRepositoryWithRegistries(repository, regs, registryId);
|
||||
} catch (err) {
|
||||
throw { msg: 'Unable to retrieve the registry associated to the repository', err: err };
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
service.retrievePorRegistryModelFromRepository = function (repository) {
|
||||
return $async(retrievePorRegistryModelFromRepositoryAsync, repository);
|
||||
};
|
||||
|
||||
return service;
|
||||
},
|
||||
]);
|
||||
|
|
|
@ -1,91 +1,85 @@
|
|||
import _ from 'lodash-es';
|
||||
import { DockerHubViewModel } from 'Portainer/models/dockerhub';
|
||||
import { TemplateViewModel } from '../../models/template';
|
||||
|
||||
angular.module('portainer.app').factory('TemplateService', [
|
||||
'$q',
|
||||
'Templates',
|
||||
'TemplateHelper',
|
||||
'RegistryService',
|
||||
'DockerHubService',
|
||||
'ImageHelper',
|
||||
'ContainerHelper',
|
||||
function TemplateServiceFactory($q, Templates, TemplateHelper, RegistryService, DockerHubService, ImageHelper, ContainerHelper) {
|
||||
'use strict';
|
||||
var service = {};
|
||||
angular.module('portainer.app').factory('TemplateService', TemplateServiceFactory);
|
||||
|
||||
service.templates = function () {
|
||||
const deferred = $q.defer();
|
||||
/* @ngInject */
|
||||
function TemplateServiceFactory($q, Templates, TemplateHelper, EndpointProvider, ImageHelper, ContainerHelper, EndpointService) {
|
||||
var service = {};
|
||||
|
||||
$q.all({
|
||||
templates: Templates.query().$promise,
|
||||
registries: RegistryService.registries(),
|
||||
dockerhub: DockerHubService.dockerhub(),
|
||||
})
|
||||
.then(function success(data) {
|
||||
const version = data.templates.version;
|
||||
const templates = _.map(data.templates.templates, (item) => {
|
||||
service.templates = function () {
|
||||
const deferred = $q.defer();
|
||||
const endpointId = EndpointProvider.currentEndpoint().Id;
|
||||
|
||||
$q.all({
|
||||
templates: Templates.query().$promise,
|
||||
registries: EndpointService.registries(endpointId),
|
||||
})
|
||||
.then(function success({ templates, registries }) {
|
||||
const version = templates.version;
|
||||
deferred.resolve(
|
||||
templates.templates.map((item) => {
|
||||
try {
|
||||
const template = new TemplateViewModel(item, version);
|
||||
const registry = RegistryService.retrievePorRegistryModelFromRepositoryWithRegistries(template.RegistryModel.Registry.URL, data.registries, data.dockerhub);
|
||||
registry.Image = template.RegistryModel.Image;
|
||||
template.RegistryModel = registry;
|
||||
const registryURL = template.RegistryModel.Registry.URL;
|
||||
const registry = registryURL ? registries.find((reg) => reg.URL === registryURL) : new DockerHubViewModel();
|
||||
template.RegistryModel.Registry = registry;
|
||||
return template;
|
||||
} catch (err) {
|
||||
deferred.reject({ msg: 'Unable to retrieve templates', err: err });
|
||||
}
|
||||
});
|
||||
deferred.resolve(templates);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to retrieve templates', err: err });
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
service.templateFile = templateFile;
|
||||
function templateFile(repositoryUrl, composeFilePathInRepository) {
|
||||
return Templates.file({ repositoryUrl, composeFilePathInRepository }).$promise;
|
||||
}
|
||||
|
||||
service.createTemplateConfiguration = function (template, containerName, network) {
|
||||
var imageConfiguration = ImageHelper.createImageConfigForContainer(template.RegistryModel);
|
||||
var containerConfiguration = createContainerConfiguration(template, containerName, network);
|
||||
containerConfiguration.Image = imageConfiguration.fromImage;
|
||||
return containerConfiguration;
|
||||
};
|
||||
|
||||
function createContainerConfiguration(template, containerName, network) {
|
||||
var configuration = TemplateHelper.getDefaultContainerConfiguration();
|
||||
configuration.HostConfig.NetworkMode = network.Name;
|
||||
configuration.HostConfig.Privileged = template.Privileged;
|
||||
configuration.HostConfig.RestartPolicy = { Name: template.RestartPolicy };
|
||||
configuration.HostConfig.ExtraHosts = template.Hosts ? template.Hosts : [];
|
||||
configuration.name = containerName;
|
||||
configuration.Hostname = template.Hostname;
|
||||
configuration.Env = TemplateHelper.EnvToStringArray(template.Env);
|
||||
configuration.Cmd = ContainerHelper.commandStringToArray(template.Command);
|
||||
var portConfiguration = TemplateHelper.portArrayToPortConfiguration(template.Ports);
|
||||
configuration.HostConfig.PortBindings = portConfiguration.bindings;
|
||||
configuration.ExposedPorts = portConfiguration.exposedPorts;
|
||||
var consoleConfiguration = TemplateHelper.getConsoleConfiguration(template.Interactive);
|
||||
configuration.OpenStdin = consoleConfiguration.openStdin;
|
||||
configuration.Tty = consoleConfiguration.tty;
|
||||
configuration.Labels = TemplateHelper.updateContainerConfigurationWithLabels(template.Labels);
|
||||
return configuration;
|
||||
}
|
||||
|
||||
service.updateContainerConfigurationWithVolumes = function (configuration, template, generatedVolumesPile) {
|
||||
var volumes = template.Volumes;
|
||||
TemplateHelper.createVolumeBindings(volumes, generatedVolumesPile);
|
||||
volumes.forEach(function (volume) {
|
||||
if (volume.binding) {
|
||||
configuration.Volumes[volume.container] = {};
|
||||
configuration.HostConfig.Binds.push(volume.binding);
|
||||
}
|
||||
})
|
||||
);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to retrieve templates', err: err });
|
||||
});
|
||||
};
|
||||
|
||||
return service;
|
||||
},
|
||||
]);
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
service.templateFile = templateFile;
|
||||
function templateFile(repositoryUrl, composeFilePathInRepository) {
|
||||
return Templates.file({ repositoryUrl, composeFilePathInRepository }).$promise;
|
||||
}
|
||||
|
||||
service.createTemplateConfiguration = function (template, containerName, network) {
|
||||
var imageConfiguration = ImageHelper.createImageConfigForContainer(template.RegistryModel);
|
||||
var containerConfiguration = createContainerConfiguration(template, containerName, network);
|
||||
containerConfiguration.Image = imageConfiguration.fromImage;
|
||||
return containerConfiguration;
|
||||
};
|
||||
|
||||
function createContainerConfiguration(template, containerName, network) {
|
||||
var configuration = TemplateHelper.getDefaultContainerConfiguration();
|
||||
configuration.HostConfig.NetworkMode = network.Name;
|
||||
configuration.HostConfig.Privileged = template.Privileged;
|
||||
configuration.HostConfig.RestartPolicy = { Name: template.RestartPolicy };
|
||||
configuration.HostConfig.ExtraHosts = template.Hosts ? template.Hosts : [];
|
||||
configuration.name = containerName;
|
||||
configuration.Hostname = template.Hostname;
|
||||
configuration.Env = TemplateHelper.EnvToStringArray(template.Env);
|
||||
configuration.Cmd = ContainerHelper.commandStringToArray(template.Command);
|
||||
var portConfiguration = TemplateHelper.portArrayToPortConfiguration(template.Ports);
|
||||
configuration.HostConfig.PortBindings = portConfiguration.bindings;
|
||||
configuration.ExposedPorts = portConfiguration.exposedPorts;
|
||||
var consoleConfiguration = TemplateHelper.getConsoleConfiguration(template.Interactive);
|
||||
configuration.OpenStdin = consoleConfiguration.openStdin;
|
||||
configuration.Tty = consoleConfiguration.tty;
|
||||
configuration.Labels = TemplateHelper.updateContainerConfigurationWithLabels(template.Labels);
|
||||
return configuration;
|
||||
}
|
||||
|
||||
service.updateContainerConfigurationWithVolumes = function (configuration, template, generatedVolumesPile) {
|
||||
var volumes = template.Volumes;
|
||||
TemplateHelper.createVolumeBindings(volumes, generatedVolumesPile);
|
||||
volumes.forEach(function (volume) {
|
||||
if (volume.binding) {
|
||||
configuration.Volumes[volume.container] = {};
|
||||
configuration.HostConfig.Binds.push(volume.binding);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return service;
|
||||
}
|
||||
|
|
21
app/portainer/views/endpoint-registries/registries.html
Normal file
21
app/portainer/views/endpoint-registries/registries.html
Normal file
|
@ -0,0 +1,21 @@
|
|||
<rd-header>
|
||||
<rd-header-title title-text="Environment registries">
|
||||
<a data-toggle="tooltip" title="Refresh" ui-sref="docker.registries" ui-sref-opts="{reload: true}">
|
||||
<i class="fa fa-sync" aria-hidden="true"></i>
|
||||
</a>
|
||||
</rd-header-title>
|
||||
<rd-header-content>Manage registry access inside this environment</rd-header-content>
|
||||
</rd-header>
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<registries-datatable
|
||||
title-text="Registries"
|
||||
title-icon="fa-database"
|
||||
dataset="$ctrl.registries"
|
||||
table-key="endpointRegistries"
|
||||
order-by="Name"
|
||||
endpoint-type="$ctrl.endpointType"
|
||||
can-manage-access="$ctrl.canManageAccess"
|
||||
></registries-datatable>
|
||||
</div>
|
||||
</div>
|
7
app/portainer/views/endpoint-registries/registries.js
Normal file
7
app/portainer/views/endpoint-registries/registries.js
Normal file
|
@ -0,0 +1,7 @@
|
|||
angular.module('portainer.app').component('endpointRegistriesView', {
|
||||
templateUrl: './registries.html',
|
||||
controller: 'EndpointRegistriesController',
|
||||
bindings: {
|
||||
endpoint: '<',
|
||||
},
|
||||
});
|
|
@ -0,0 +1,51 @@
|
|||
import _ from 'lodash-es';
|
||||
import { DockerHubViewModel } from 'Portainer/models/dockerhub';
|
||||
import { RegistryTypes } from 'Portainer/models/registryTypes';
|
||||
|
||||
class EndpointRegistriesController {
|
||||
/* @ngInject */
|
||||
constructor($async, Notifications, EndpointService) {
|
||||
this.$async = $async;
|
||||
this.Notifications = Notifications;
|
||||
this.EndpointService = EndpointService;
|
||||
|
||||
this.canManageAccess = this.canManageAccess.bind(this);
|
||||
}
|
||||
|
||||
canManageAccess(item) {
|
||||
return item.Type !== RegistryTypes.ANONYMOUS;
|
||||
}
|
||||
|
||||
getRegistries() {
|
||||
return this.$async(async () => {
|
||||
try {
|
||||
const dockerhub = new DockerHubViewModel();
|
||||
const registries = await this.EndpointService.registries(this.endpointId);
|
||||
this.registries = _.concat(dockerhub, registries);
|
||||
} catch (err) {
|
||||
this.Notifications.error('Failure', err, 'Unable to retrieve registries');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$onInit() {
|
||||
return this.$async(async () => {
|
||||
this.state = {
|
||||
viewReady: false,
|
||||
};
|
||||
|
||||
try {
|
||||
this.endpointType = this.endpoint.Type;
|
||||
this.endpointId = this.endpoint.Id;
|
||||
await this.getRegistries();
|
||||
} catch (err) {
|
||||
this.Notifications.error('Failure', err, 'Unable to retrieve registries');
|
||||
} finally {
|
||||
this.state.viewReady = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default EndpointRegistriesController;
|
||||
angular.module('portainer.app').controller('EndpointRegistriesController', EndpointRegistriesController);
|
|
@ -1,8 +1,9 @@
|
|||
import angular from 'angular';
|
||||
import EndpointHelper from 'Portainer/helpers/endpointHelper';
|
||||
|
||||
angular.module('portainer.app').controller('EndpointsController', EndpointsController);
|
||||
|
||||
function EndpointsController($q, $scope, $state, $async, EndpointService, GroupService, EndpointHelper, Notifications) {
|
||||
function EndpointsController($q, $scope, $state, $async, EndpointService, GroupService, Notifications) {
|
||||
$scope.removeAction = removeAction;
|
||||
|
||||
function removeAction(endpoints) {
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import EndpointHelper from 'Portainer/helpers/endpointHelper';
|
||||
|
||||
angular
|
||||
.module('portainer.app')
|
||||
.controller('HomeController', function (
|
||||
|
@ -7,7 +9,6 @@ angular
|
|||
TagService,
|
||||
Authentication,
|
||||
EndpointService,
|
||||
EndpointHelper,
|
||||
GroupService,
|
||||
Notifications,
|
||||
EndpointProvider,
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
angular.module('portainer.app').controller('InitAdminController', [
|
||||
'$async',
|
||||
'$scope',
|
||||
'$state',
|
||||
'Notifications',
|
||||
|
@ -10,7 +9,7 @@ angular.module('portainer.app').controller('InitAdminController', [
|
|||
'EndpointService',
|
||||
'BackupService',
|
||||
'StatusService',
|
||||
function ($async, $scope, $state, Notifications, Authentication, StateManager, SettingsService, UserService, EndpointService, BackupService, StatusService) {
|
||||
function ($scope, $state, Notifications, Authentication, StateManager, SettingsService, UserService, EndpointService, BackupService, StatusService) {
|
||||
$scope.logo = StateManager.getState().application.logo;
|
||||
|
||||
$scope.formValues = {
|
||||
|
@ -85,7 +84,9 @@ angular.module('portainer.app').controller('InitAdminController', [
|
|||
if (status && status.Version) {
|
||||
return;
|
||||
}
|
||||
} catch (e) {}
|
||||
} catch (e) {
|
||||
// pass
|
||||
}
|
||||
}
|
||||
throw 'Timeout to wait for Portainer restarting';
|
||||
}
|
||||
|
|
|
@ -1,35 +0,0 @@
|
|||
<rd-header>
|
||||
<rd-header-title title-text="Registry access"></rd-header-title>
|
||||
<rd-header-content>
|
||||
<a ui-sref="portainer.registries">Registries</a> > <a ui-sref="portainer.registries.registry({id: registry.Id})">{{ registry.Name }}</a> > Access management
|
||||
</rd-header-content>
|
||||
</rd-header>
|
||||
|
||||
<div class="row" ng-if="registry">
|
||||
<div class="col-lg-12 col-md-12 col-xs-12">
|
||||
<rd-widget>
|
||||
<rd-widget-header icon="fa-plug" title-text="Registry"></rd-widget-header>
|
||||
<rd-widget-body classes="no-padding">
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Name</td>
|
||||
<td>
|
||||
{{ registry.Name }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>URL</td>
|
||||
<td>
|
||||
{{ registry.URL }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<por-access-management ng-if="registry" access-controlled-entity="registry" entity-type="registry" action-in-progress="state.actionInProgress" update-access="updateAccess">
|
||||
</por-access-management>
|
|
@ -1,34 +0,0 @@
|
|||
angular.module('portainer.app').controller('RegistryAccessController', [
|
||||
'$scope',
|
||||
'$state',
|
||||
'$transition$',
|
||||
'RegistryService',
|
||||
'Notifications',
|
||||
function ($scope, $state, $transition$, RegistryService, Notifications) {
|
||||
$scope.updateAccess = function () {
|
||||
$scope.state.actionInProgress = true;
|
||||
RegistryService.updateRegistry($scope.registry)
|
||||
.then(() => {
|
||||
Notifications.success('Access successfully updated');
|
||||
$state.reload();
|
||||
})
|
||||
.catch((err) => {
|
||||
$scope.state.actionInProgress = false;
|
||||
Notifications.error('Failure', err, 'Unable to update accesses');
|
||||
});
|
||||
};
|
||||
|
||||
function initView() {
|
||||
$scope.state = { actionInProgress: false };
|
||||
RegistryService.registry($transition$.params().id)
|
||||
.then(function success(data) {
|
||||
$scope.registry = data;
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to retrieve registry details');
|
||||
});
|
||||
}
|
||||
|
||||
initView();
|
||||
},
|
||||
]);
|
|
@ -17,8 +17,18 @@
|
|||
<div class="form-group" style="margin-bottom: 0;">
|
||||
<div class="boxselector_wrapper">
|
||||
<div>
|
||||
<input type="radio" id="registry_quay" ng-model="model.Type" ng-value="RegistryTypes.QUAY" />
|
||||
<label for="registry_quay" ng-click="selectQuayRegistry()">
|
||||
<input type="radio" id="registry_dockerhub" ng-model="$ctrl.model.Type" ng-value="$ctrl.RegistryTypes.DOCKERHUB" />
|
||||
<label for="registry_dockerhub" ng-click="$ctrl.selectDockerHub()">
|
||||
<div class="boxselector_header">
|
||||
<i class="fa fa-database" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||
DockerHub
|
||||
</div>
|
||||
<p>DockerHub authenticated account</p>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<input type="radio" id="registry_quay" ng-model="$ctrl.model.Type" ng-value="$ctrl.RegistryTypes.QUAY" />
|
||||
<label for="registry_quay" ng-click="$ctrl.selectQuayRegistry()">
|
||||
<div class="boxselector_header">
|
||||
<i class="fa fa-database" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||
Quay.io
|
||||
|
@ -27,8 +37,8 @@
|
|||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<input type="radio" id="registry_proget" ng-model="model.Type" ng-value="RegistryTypes.PROGET" />
|
||||
<label for="registry_proget" ng-click="selectProGetRegistry()">
|
||||
<input type="radio" id="registry_proget" ng-model="$ctrl.model.Type" ng-value="$ctrl.RegistryTypes.PROGET" />
|
||||
<label for="registry_proget" ng-click="$ctrl.selectProGetRegistry()">
|
||||
<div class="boxselector_header">
|
||||
<i class="fa fa-database" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||
ProGet
|
||||
|
@ -37,8 +47,8 @@
|
|||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<input type="radio" id="registry_azure" ng-model="model.Type" ng-value="RegistryTypes.AZURE" />
|
||||
<label for="registry_azure" ng-click="selectAzureRegistry()">
|
||||
<input type="radio" id="registry_azure" ng-model="$ctrl.model.Type" ng-value="$ctrl.RegistryTypes.AZURE" />
|
||||
<label for="registry_azure" ng-click="$ctrl.selectAzureRegistry()">
|
||||
<div class="boxselector_header">
|
||||
<i class="fab fa-microsoft" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||
Azure
|
||||
|
@ -47,8 +57,8 @@
|
|||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<input type="radio" id="registry_gitlab" ng-model="model.Type" ng-value="RegistryTypes.GITLAB" />
|
||||
<label for="registry_gitlab" ng-click="selectGitlabRegistry()">
|
||||
<input type="radio" id="registry_gitlab" ng-model="$ctrl.model.Type" ng-value="$ctrl.RegistryTypes.GITLAB" />
|
||||
<label for="registry_gitlab" ng-click="$ctrl.selectGitlabRegistry()">
|
||||
<div class="boxselector_header">
|
||||
<i class="fab fa-gitlab" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||
Gitlab
|
||||
|
@ -57,8 +67,8 @@
|
|||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<input type="radio" id="registry_custom" ng-model="model.Type" ng-value="RegistryTypes.CUSTOM" />
|
||||
<label for="registry_custom" ng-click="selectCustomRegistry()">
|
||||
<input type="radio" id="registry_custom" ng-model="$ctrl.model.Type" ng-value="$ctrl.RegistryTypes.CUSTOM" />
|
||||
<label for="registry_custom" ng-click="$ctrl.selectCustomRegistry()">
|
||||
<div class="boxselector_header">
|
||||
<i class="fa fa-database" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||
Custom registry
|
||||
|
@ -70,47 +80,55 @@
|
|||
</div>
|
||||
|
||||
<registry-form-quay
|
||||
ng-if="model.Type === RegistryTypes.QUAY"
|
||||
model="model"
|
||||
form-action="create"
|
||||
ng-if="$ctrl.model.Type === $ctrl.RegistryTypes.QUAY"
|
||||
model="$ctrl.model"
|
||||
form-action="$ctrl.createRegistry"
|
||||
form-action-label="Add registry"
|
||||
action-in-progress="state.actionInProgress"
|
||||
action-in-progress="$ctrl.state.actionInProgress"
|
||||
></registry-form-quay>
|
||||
|
||||
<registry-form-azure
|
||||
ng-if="model.Type === RegistryTypes.AZURE"
|
||||
model="model"
|
||||
form-action="create"
|
||||
ng-if="$ctrl.model.Type === $ctrl.RegistryTypes.AZURE"
|
||||
model="$ctrl.model"
|
||||
form-action="$ctrl.createRegistry"
|
||||
form-action-label="Add registry"
|
||||
action-in-progress="state.actionInProgress"
|
||||
action-in-progress="$ctrl.state.actionInProgress"
|
||||
></registry-form-azure>
|
||||
|
||||
<registry-form-custom
|
||||
ng-if="model.Type === RegistryTypes.CUSTOM"
|
||||
model="model"
|
||||
form-action="create"
|
||||
ng-if="$ctrl.model.Type === $ctrl.RegistryTypes.CUSTOM"
|
||||
model="$ctrl.model"
|
||||
form-action="$ctrl.createRegistry"
|
||||
form-action-label="Add registry"
|
||||
action-in-progress="state.actionInProgress"
|
||||
action-in-progress="$ctrl.state.actionInProgress"
|
||||
></registry-form-custom>
|
||||
|
||||
<registry-form-proget
|
||||
ng-if="model.Type === RegistryTypes.PROGET"
|
||||
model="model"
|
||||
form-action="create"
|
||||
ng-if="$ctrl.model.Type === $ctrl.RegistryTypes.PROGET"
|
||||
model="$ctrl.model"
|
||||
form-action="$ctrl.createRegistry"
|
||||
form-action-label="Add registry"
|
||||
action-in-progress="state.actionInProgress"
|
||||
action-in-progress="$ctrl.state.actionInProgress"
|
||||
></registry-form-proget>
|
||||
|
||||
<registry-form-gitlab
|
||||
ng-if="model.Type === RegistryTypes.GITLAB"
|
||||
model="model"
|
||||
retrieve-registries="retrieveGitlabRegistries"
|
||||
create-registries="createGitlabRegistries"
|
||||
projects="gitlabProjects"
|
||||
state="state"
|
||||
action-in-progress="state.actionInProgress"
|
||||
reset-defaults="useDefaultGitlabConfiguration"
|
||||
ng-if="$ctrl.model.Type === $ctrl.RegistryTypes.GITLAB"
|
||||
model="$ctrl.model"
|
||||
retrieve-registries="$ctrl.retrieveGitlabRegistries"
|
||||
create-registries="$ctrl.createGitlabRegistries"
|
||||
projects="$ctrl.gitlabProjects"
|
||||
state="$ctrl.state"
|
||||
action-in-progress="$ctrl.state.actionInProgress"
|
||||
reset-defaults="$ctrl.useDefaultGitlabConfiguration"
|
||||
></registry-form-gitlab>
|
||||
|
||||
<registry-form-dockerhub
|
||||
ng-if="$ctrl.model.Type === $ctrl.RegistryTypes.DOCKERHUB"
|
||||
model="$ctrl.model"
|
||||
form-action="$ctrl.createRegistry"
|
||||
form-action-label="Add registry"
|
||||
action-in-progress="$ctrl.state.actionInProgress"
|
||||
></registry-form-dockerhub>
|
||||
</form>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
10
app/portainer/views/registries/create/createRegistry.js
Normal file
10
app/portainer/views/registries/create/createRegistry.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
import angular from 'angular';
|
||||
import CreateRegistryController from './createRegistryController';
|
||||
|
||||
angular.module('portainer.app').component('createRegistry', {
|
||||
templateUrl: './createRegistry.html',
|
||||
controller: CreateRegistryController,
|
||||
bindings: {
|
||||
$transition$: '<',
|
||||
},
|
||||
});
|
|
@ -1,24 +1,13 @@
|
|||
import { RegistryTypes } from '@/portainer/models/registryTypes';
|
||||
import { RegistryDefaultModel } from '@/portainer/models/registry';
|
||||
import { RegistryTypes } from 'Portainer/models/registryTypes';
|
||||
import { RegistryCreateFormValues } from 'Portainer/models/registry';
|
||||
|
||||
angular.module('portainer.app').controller('CreateRegistryController', [
|
||||
'$scope',
|
||||
'$state',
|
||||
'RegistryService',
|
||||
'Notifications',
|
||||
'RegistryGitlabService',
|
||||
function ($scope, $state, RegistryService, Notifications, RegistryGitlabService) {
|
||||
$scope.selectQuayRegistry = selectQuayRegistry;
|
||||
$scope.selectAzureRegistry = selectAzureRegistry;
|
||||
$scope.selectCustomRegistry = selectCustomRegistry;
|
||||
$scope.selectProGetRegistry = selectProGetRegistry;
|
||||
$scope.selectGitlabRegistry = selectGitlabRegistry;
|
||||
$scope.create = createRegistry;
|
||||
$scope.useDefaultGitlabConfiguration = useDefaultGitlabConfiguration;
|
||||
$scope.retrieveGitlabRegistries = retrieveGitlabRegistries;
|
||||
$scope.createGitlabRegistries = createGitlabRegistries;
|
||||
class CreateRegistryController {
|
||||
/* @ngInject */
|
||||
constructor($async, $state, EndpointProvider, Notifications, RegistryService, RegistryGitlabService) {
|
||||
Object.assign(this, { $async, $state, EndpointProvider, Notifications, RegistryService, RegistryGitlabService });
|
||||
|
||||
$scope.state = {
|
||||
this.RegistryTypes = RegistryTypes;
|
||||
this.state = {
|
||||
actionInProgress: false,
|
||||
overrideConfiguration: false,
|
||||
gitlab: {
|
||||
|
@ -27,101 +16,113 @@ angular.module('portainer.app').controller('CreateRegistryController', [
|
|||
},
|
||||
selectedItems: [],
|
||||
},
|
||||
originViewReference: 'portainer.registries',
|
||||
};
|
||||
|
||||
function useDefaultQuayConfiguration() {
|
||||
$scope.model.Quay.useOrganisation = false;
|
||||
$scope.model.Quay.organisationName = '';
|
||||
}
|
||||
this.createRegistry = this.createRegistry.bind(this);
|
||||
this.retrieveGitlabRegistries = this.retrieveGitlabRegistries.bind(this);
|
||||
this.createGitlabRegistries = this.createGitlabRegistries.bind(this);
|
||||
}
|
||||
|
||||
function selectQuayRegistry() {
|
||||
$scope.model.Name = 'Quay';
|
||||
$scope.model.URL = 'quay.io';
|
||||
$scope.model.Authentication = true;
|
||||
$scope.model.Quay = {};
|
||||
useDefaultQuayConfiguration();
|
||||
}
|
||||
useDefaultQuayConfiguration() {
|
||||
this.model.Quay.useOrganisation = false;
|
||||
this.model.Quay.organisationName = '';
|
||||
}
|
||||
|
||||
function useDefaultGitlabConfiguration() {
|
||||
$scope.model.URL = 'https://registry.gitlab.com';
|
||||
$scope.model.Gitlab.InstanceURL = 'https://gitlab.com';
|
||||
}
|
||||
selectQuayRegistry() {
|
||||
this.model.Name = 'Quay';
|
||||
this.model.URL = 'quay.io';
|
||||
this.model.Authentication = true;
|
||||
this.model.Quay = {};
|
||||
this.useDefaultQuayConfiguration();
|
||||
}
|
||||
|
||||
function selectGitlabRegistry() {
|
||||
$scope.model.Name = '';
|
||||
$scope.model.Authentication = true;
|
||||
$scope.model.Gitlab = {};
|
||||
useDefaultGitlabConfiguration();
|
||||
}
|
||||
useDefaultGitlabConfiguration() {
|
||||
this.model.URL = 'https://registry.gitlab.com';
|
||||
this.model.Gitlab.InstanceURL = 'https://gitlab.com';
|
||||
}
|
||||
|
||||
function selectAzureRegistry() {
|
||||
$scope.model.Name = '';
|
||||
$scope.model.URL = '';
|
||||
$scope.model.Authentication = true;
|
||||
}
|
||||
selectGitlabRegistry() {
|
||||
this.model.Name = '';
|
||||
this.model.Authentication = true;
|
||||
this.model.Gitlab = {};
|
||||
this.useDefaultGitlabConfiguration();
|
||||
}
|
||||
|
||||
function selectCustomRegistry() {
|
||||
$scope.model.Name = '';
|
||||
$scope.model.URL = '';
|
||||
$scope.model.Authentication = false;
|
||||
}
|
||||
selectAzureRegistry() {
|
||||
this.model.Name = '';
|
||||
this.model.URL = '';
|
||||
this.model.Authentication = true;
|
||||
}
|
||||
|
||||
function selectProGetRegistry() {
|
||||
$scope.model.Name = '';
|
||||
$scope.model.URL = '';
|
||||
$scope.model.BaseURL = '';
|
||||
$scope.model.Authentication = true;
|
||||
}
|
||||
selectProGetRegistry() {
|
||||
this.model.Name = '';
|
||||
this.model.URL = '';
|
||||
this.model.BaseURL = '';
|
||||
this.model.Authentication = true;
|
||||
}
|
||||
|
||||
function retrieveGitlabRegistries() {
|
||||
$scope.state.actionInProgress = true;
|
||||
RegistryGitlabService.projects($scope.model.Gitlab.InstanceURL, $scope.model.Token)
|
||||
.then((data) => {
|
||||
$scope.gitlabProjects = data;
|
||||
})
|
||||
.catch((err) => {
|
||||
Notifications.error('Failure', err, 'Unable to retrieve projects');
|
||||
})
|
||||
.finally(() => {
|
||||
$scope.state.actionInProgress = false;
|
||||
});
|
||||
}
|
||||
selectCustomRegistry() {
|
||||
this.model.Name = '';
|
||||
this.model.URL = '';
|
||||
this.model.Authentication = false;
|
||||
}
|
||||
|
||||
function createGitlabRegistries() {
|
||||
$scope.state.actionInProgress = true;
|
||||
RegistryService.createGitlabRegistries($scope.model, $scope.state.gitlab.selectedItems)
|
||||
.then(() => {
|
||||
Notifications.success('Registries successfully created');
|
||||
$state.go('portainer.registries');
|
||||
})
|
||||
.catch((err) => {
|
||||
Notifications.error('Failure', err, 'Unable to create registries');
|
||||
})
|
||||
.finally(() => {
|
||||
$scope.state.actionInProgress = false;
|
||||
});
|
||||
}
|
||||
selectDockerHub() {
|
||||
this.model.Name = '';
|
||||
this.model.URL = 'docker.io';
|
||||
this.model.Authentication = true;
|
||||
}
|
||||
|
||||
function createRegistry() {
|
||||
$scope.state.actionInProgress = true;
|
||||
RegistryService.createRegistry($scope.model)
|
||||
.then(function success() {
|
||||
Notifications.success('Registry successfully created');
|
||||
$state.go('portainer.registries');
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to create registry');
|
||||
})
|
||||
.finally(function final() {
|
||||
$scope.state.actionInProgress = false;
|
||||
});
|
||||
}
|
||||
retrieveGitlabRegistries() {
|
||||
return this.$async(async () => {
|
||||
this.state.actionInProgress = true;
|
||||
try {
|
||||
this.gitlabProjects = await this.RegistryGitlabService.projects(this.model.Gitlab.InstanceURL, this.model.Token);
|
||||
} catch (err) {
|
||||
this.Notifications.error('Failure', err, 'Unable to retrieve projects');
|
||||
} finally {
|
||||
this.state.actionInProgress = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function initView() {
|
||||
$scope.RegistryTypes = RegistryTypes;
|
||||
$scope.model = new RegistryDefaultModel();
|
||||
}
|
||||
createGitlabRegistries() {
|
||||
return this.$async(async () => {
|
||||
try {
|
||||
this.state.actionInProgress = true;
|
||||
await this.RegistryService.createGitlabRegistries(this.model, this.state.gitlab.selectedItems);
|
||||
this.Notifications.success('Registries successfully created');
|
||||
this.$state.go(this.state.originViewReference, { endpointId: this.EndpointProvider.endpointID() });
|
||||
} catch (err) {
|
||||
this.Notifications.error('Failure', err, 'Unable to create registries');
|
||||
this.state.actionInProgress = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
initView();
|
||||
},
|
||||
]);
|
||||
createRegistry() {
|
||||
return this.$async(async () => {
|
||||
try {
|
||||
this.state.actionInProgress = true;
|
||||
await this.RegistryService.createRegistry(this.model);
|
||||
this.Notifications.success('Registry successfully created');
|
||||
this.$state.go(this.state.originViewReference, { endpointId: this.EndpointProvider.endpointID() });
|
||||
} catch (err) {
|
||||
this.Notifications.error('Failure', err, 'Unable to create registry');
|
||||
this.state.actionInProgress = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$onInit() {
|
||||
this.model = new RegistryCreateFormValues();
|
||||
|
||||
const origin = this.$transition$.originalTransition().from();
|
||||
if (origin.name && /^[a-z]+\.registries$/.test(origin.name)) {
|
||||
this.state.originViewReference = origin;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default CreateRegistryController;
|
||||
|
|
|
@ -3,11 +3,9 @@ import { RegistryTypes } from '@/portainer/models/registryTypes';
|
|||
angular.module('portainer.app').controller('RegistryController', [
|
||||
'$scope',
|
||||
'$state',
|
||||
'$transition$',
|
||||
'$filter',
|
||||
'RegistryService',
|
||||
'Notifications',
|
||||
function ($scope, $state, $transition$, $filter, RegistryService, Notifications) {
|
||||
function ($scope, $state, RegistryService, Notifications) {
|
||||
$scope.state = {
|
||||
actionInProgress: false,
|
||||
};
|
||||
|
@ -36,7 +34,7 @@ angular.module('portainer.app').controller('RegistryController', [
|
|||
};
|
||||
|
||||
function initView() {
|
||||
var registryID = $transition$.params().id;
|
||||
var registryID = $state.params.id;
|
||||
RegistryService.registry(registryID)
|
||||
.then(function success(data) {
|
||||
$scope.registry = data;
|
||||
|
|
|
@ -7,79 +7,6 @@
|
|||
<rd-header-content>Registry management</rd-header-content>
|
||||
</rd-header>
|
||||
|
||||
<information-panel title-text="Registry usage">
|
||||
<span class="small">
|
||||
<p class="text-muted">
|
||||
<i class="fa fa-info-circle blue-icon" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||
DockerHub credentials and registries can only be used with Docker endpoints at the time.
|
||||
</p>
|
||||
</span>
|
||||
</information-panel>
|
||||
|
||||
<div class="row" ng-if="dockerhub && isAdmin">
|
||||
<div class="col-sm-12">
|
||||
<rd-widget>
|
||||
<rd-widget-header icon="fa-database" title-text="DockerHub"> </rd-widget-header>
|
||||
<rd-widget-body>
|
||||
<form class="form-horizontal">
|
||||
<!-- note -->
|
||||
<div class="form-group">
|
||||
<span class="col-sm-12 text-muted small">
|
||||
The DockerHub registry can be used by any user. You can specify the credentials that will be used to push & pull images here.
|
||||
</span>
|
||||
</div>
|
||||
<!-- !note -->
|
||||
<!-- authentication-checkbox -->
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<label for="registry_auth" class="control-label text-left">
|
||||
Authentication
|
||||
<portainer-tooltip position="bottom" message="Enable this option if you need to specify credentials to connect to push/pull private images."></portainer-tooltip>
|
||||
</label>
|
||||
<label class="switch" style="margin-left: 20px;"> <input type="checkbox" ng-model="dockerhub.Authentication" /><i></i> </label>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !authentication-checkbox -->
|
||||
<!-- authentication-credentials -->
|
||||
<div ng-if="dockerhub.Authentication">
|
||||
<!-- credentials-user -->
|
||||
<div class="form-group">
|
||||
<label for="hub_username" class="col-sm-3 col-lg-2 control-label text-left">Username</label>
|
||||
<div class="col-sm-9 col-lg-10">
|
||||
<input type="text" class="form-control" id="hub_username" ng-model="dockerhub.Username" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- !credentials-user -->
|
||||
<!-- credentials-password -->
|
||||
<div class="form-group">
|
||||
<label for="hub_password" class="col-sm-3 col-lg-2 control-label text-left">Password</label>
|
||||
<div class="col-sm-9 col-lg-10">
|
||||
<input type="password" class="form-control" id="hub_password" ng-model="formValues.dockerHubPassword" placeholder="*******" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- !credentials-password -->
|
||||
</div>
|
||||
<!-- !authentication-credentials -->
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary btn-sm"
|
||||
ng-disabled="state.actionInProgress || dockerhub.Authentication && (!dockerhub.Username || !formValues.dockerHubPassword)"
|
||||
ng-click="updateDockerHub()"
|
||||
button-spinner="state.actionInProgress"
|
||||
>
|
||||
<span ng-hide="state.actionInProgress">Update</span>
|
||||
<span ng-show="state.actionInProgress">Updating DockerHub settings...</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<registries-datatable
|
||||
|
@ -88,7 +15,6 @@
|
|||
dataset="registries"
|
||||
table-key="registries"
|
||||
order-by="Name"
|
||||
access-management="isAdmin"
|
||||
remove-action="removeAction"
|
||||
can-browse="canBrowse"
|
||||
></registries-datatable>
|
||||
|
|
|
@ -1,43 +1,23 @@
|
|||
import _ from 'lodash-es';
|
||||
import { RegistryTypes } from 'Portainer/models/registryTypes';
|
||||
import { DockerHubViewModel } from 'Portainer/models/dockerhub';
|
||||
|
||||
angular.module('portainer.app').controller('RegistriesController', [
|
||||
'$q',
|
||||
'$scope',
|
||||
'$state',
|
||||
'RegistryService',
|
||||
'DockerHubService',
|
||||
'ModalService',
|
||||
'Notifications',
|
||||
'Authentication',
|
||||
function ($q, $scope, $state, RegistryService, DockerHubService, ModalService, Notifications, Authentication) {
|
||||
function ($q, $scope, $state, RegistryService, ModalService, Notifications) {
|
||||
$scope.state = {
|
||||
actionInProgress: false,
|
||||
};
|
||||
|
||||
$scope.formValues = {
|
||||
dockerHubPassword: '',
|
||||
};
|
||||
|
||||
const nonBrowsableUrls = ['quay.io'];
|
||||
const nonBrowsableTypes = [RegistryTypes.ANONYMOUS, RegistryTypes.DOCKERHUB, RegistryTypes.QUAY];
|
||||
|
||||
$scope.canBrowse = function (item) {
|
||||
return !_.includes(nonBrowsableUrls, item.URL);
|
||||
};
|
||||
|
||||
$scope.updateDockerHub = function () {
|
||||
var dockerhub = $scope.dockerhub;
|
||||
dockerhub.Password = $scope.formValues.dockerHubPassword;
|
||||
$scope.state.actionInProgress = true;
|
||||
DockerHubService.update(dockerhub)
|
||||
.then(function success() {
|
||||
Notifications.success('DockerHub registry updated');
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to update DockerHub details');
|
||||
})
|
||||
.finally(function final() {
|
||||
$scope.state.actionInProgress = false;
|
||||
});
|
||||
return !_.includes(nonBrowsableTypes, item.Type);
|
||||
};
|
||||
|
||||
$scope.removeAction = function (selectedItems) {
|
||||
|
@ -73,12 +53,9 @@ angular.module('portainer.app').controller('RegistriesController', [
|
|||
function initView() {
|
||||
$q.all({
|
||||
registries: RegistryService.registries(),
|
||||
dockerhub: DockerHubService.dockerhub(),
|
||||
})
|
||||
.then(function success(data) {
|
||||
$scope.registries = data.registries;
|
||||
$scope.dockerhub = data.dockerhub;
|
||||
$scope.isAdmin = Authentication.isAdmin();
|
||||
$scope.registries = _.concat(new DockerHubViewModel(), data.registries);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
$scope.registries = [];
|
||||
|
|
|
@ -108,7 +108,7 @@
|
|||
<li class="sidebar-list" ng-if="isAdmin && applicationState.application.enableEdgeComputeFeatures">
|
||||
<a ui-sref="edge.jobs" ui-sref-active="active">Edge Jobs <span class="menu-icon fa fa-clock fa-fw"></span></a>
|
||||
</li>
|
||||
<li class="sidebar-title">
|
||||
<li class="sidebar-title" ng-if="isAdmin || isTeamLeader">
|
||||
<span>Settings</span>
|
||||
</li>
|
||||
<li class="sidebar-list" ng-if="isAdmin || isTeamLeader">
|
||||
|
@ -181,7 +181,7 @@
|
|||
<a ui-sref="portainer.tags" ui-sref-active="active">Tags</a>
|
||||
</div>
|
||||
</li>
|
||||
<li class="sidebar-list">
|
||||
<li class="sidebar-list" ng-if="isAdmin">
|
||||
<a ui-sref="portainer.registries" ui-sref-active="active">Registries <span class="menu-icon fa fa-database fa-fw"></span></a>
|
||||
</li>
|
||||
<li class="sidebar-list" ng-if="isAdmin">
|
||||
|
|
|
@ -19,15 +19,20 @@ angular.module('portainer.app').controller('SidebarController', [
|
|||
$scope.isTeamLeader = isLeader;
|
||||
}
|
||||
|
||||
function isClusterAdmin() {
|
||||
return Authentication.isAdmin();
|
||||
}
|
||||
|
||||
async function initView() {
|
||||
$scope.uiVersion = StateManager.getState().application.version;
|
||||
$scope.logo = StateManager.getState().application.logo;
|
||||
$scope.showStacks = await shouldShowStacks();
|
||||
|
||||
$scope.endpointId = EndpointProvider.endpointID();
|
||||
$scope.showStacks = shouldShowStacks();
|
||||
|
||||
let userDetails = Authentication.getUserDetails();
|
||||
let isAdmin = Authentication.isAdmin();
|
||||
const isAdmin = isClusterAdmin();
|
||||
$scope.isAdmin = isAdmin;
|
||||
$scope.endpointId = EndpointProvider.endpointID();
|
||||
|
||||
$q.when(!isAdmin ? UserService.userMemberships(userDetails.ID) : [])
|
||||
.then(function success(data) {
|
||||
|
@ -36,18 +41,12 @@ angular.module('portainer.app').controller('SidebarController', [
|
|||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to retrieve user memberships');
|
||||
});
|
||||
|
||||
$transitions.onEnter({}, () => {
|
||||
$scope.endpointId = EndpointProvider.endpointID();
|
||||
});
|
||||
}
|
||||
|
||||
initView();
|
||||
|
||||
async function shouldShowStacks() {
|
||||
const isAdmin = Authentication.isAdmin();
|
||||
|
||||
if (isAdmin) {
|
||||
function shouldShowStacks() {
|
||||
if (isClusterAdmin()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -60,7 +59,9 @@ angular.module('portainer.app').controller('SidebarController', [
|
|||
}
|
||||
|
||||
$transitions.onEnter({}, async () => {
|
||||
$scope.showStacks = await shouldShowStacks();
|
||||
$scope.endpointId = EndpointProvider.endpointID();
|
||||
$scope.showStacks = shouldShowStacks();
|
||||
$scope.isAdmin = isClusterAdmin();
|
||||
|
||||
if ($scope.applicationState.endpoint.name) {
|
||||
document.title = `${$rootScope.defaultTitle} | ${$scope.applicationState.endpoint.name}`;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue