1
0
Fork 0
mirror of https://github.com/portainer/portainer.git synced 2025-08-05 13:55:21 +02:00

feat(registries): add registry management (#930)

This commit is contained in:
Anthony Lapenna 2017-06-20 13:00:32 +02:00 committed by GitHub
parent 9360f24d89
commit 08c5a5a4f6
75 changed files with 2317 additions and 621 deletions

View file

@ -17,11 +17,11 @@
<!-- !access-control-switch -->
<!-- restricted-access -->
<div class="form-group" ng-if="formValues.enableAccessControl" style="margin-bottom: 0">
<div class="ownership_wrapper">
<div class="boxselector_wrapper">
<div ng-if="isAdmin">
<input type="radio" id="access_administrators" ng-model="formValues.Ownership" ng-click="synchronizeFormData()" value="administrators">
<label for="access_administrators">
<div class="ownership_header">
<div class="boxselector_header">
<i ng-class="'administrators' | ownershipicon" aria-hidden="true" style="margin-right: 2px;"></i>
Administrators
</div>
@ -31,7 +31,7 @@
<div ng-if="isAdmin">
<input type="radio" id="access_restricted" ng-model="formValues.Ownership" ng-click="synchronizeFormData()" value="restricted">
<label for="access_restricted">
<div class="ownership_header">
<div class="boxselector_header">
<i ng-class="'restricted' | ownershipicon" aria-hidden="true" style="margin-right: 2px;"></i>
Restricted
</div>
@ -43,7 +43,7 @@
<div ng-if="!isAdmin">
<input type="radio" id="access_private" ng-model="formValues.Ownership" ng-click="synchronizeFormData()" value="private">
<label for="access_private">
<div class="ownership_header">
<div class="boxselector_header">
<i ng-class="'private' | ownershipicon" aria-hidden="true" style="margin-right: 2px;"></i>
Private
</div>
@ -55,7 +55,7 @@
<div ng-if="!isAdmin && availableTeams.length > 0">
<input type="radio" id="access_restricted" ng-model="formValues.Ownership" ng-click="synchronizeFormData()" value="restricted">
<label for="access_restricted">
<div class="ownership_header">
<div class="boxselector_header">
<i ng-class="'restricted' | ownershipicon" aria-hidden="true" style="margin-right: 2px;"></i>
Restricted
</div>

View file

@ -63,11 +63,11 @@
<!-- edit-ownership-choices -->
<tr ng-if="state.editOwnership">
<td colspan="2">
<div class="ownership_wrapper">
<div class="boxselector_wrapper">
<div ng-if="isAdmin">
<input type="radio" id="access_administrators" ng-model="formValues.Ownership" value="administrators">
<label for="access_administrators">
<div class="ownership_header">
<div class="boxselector_header">
<i ng-class="'administrators' | ownershipicon" aria-hidden="true" style="margin-right: 2px;"></i>
Administrators
</div>
@ -77,7 +77,7 @@
<div ng-if="isAdmin">
<input type="radio" id="access_restricted" ng-model="formValues.Ownership" value="restricted">
<label for="access_restricted">
<div class="ownership_header">
<div class="boxselector_header">
<i ng-class="'restricted' | ownershipicon" aria-hidden="true" style="margin-right: 2px;"></i>
Restricted
</div>
@ -89,7 +89,7 @@
<div ng-if="!isAdmin && state.canChangeOwnershipToTeam && availableTeams.length > 0">
<input type="radio" id="access_restricted" ng-model="formValues.Ownership" value="restricted">
<label for="access_restricted">
<div class="ownership_header">
<div class="boxselector_header">
<i ng-class="'restricted' | ownershipicon" aria-hidden="true" style="margin-right: 2px;"></i>
Restricted
</div>
@ -104,7 +104,7 @@
<div>
<input type="radio" id="access_public" ng-model="formValues.Ownership" value="public">
<label for="access_public">
<div class="ownership_header">
<div class="boxselector_header">
<i ng-class="'public' | ownershipicon" aria-hidden="true" style="margin-right: 2px;"></i>
Public
</div>

View file

@ -134,21 +134,11 @@
</div>
</div>
<!-- !tag-description -->
<!-- name-and-registry-inputs -->
<!-- image-and-registry -->
<div class="form-group">
<label for="image_name" class="col-sm-1 control-label text-left">Name</label>
<div class="col-sm-11 col-md-6">
<input type="text" class="form-control" ng-model="config.Image" id="image_name" placeholder="e.g. myImage:myTag">
</div>
<label for="image_registry" class="col-sm-2 margin-sm-top control-label text-left">
Registry
<portainer-tooltip position="bottom" message="A registry to pull the image from. Leave empty to use the official Docker registry."></portainer-tooltip>
</label>
<div class="col-sm-10 col-md-3 margin-sm-top">
<input type="text" class="form-control" ng-model="config.Registry" id="image_registry" placeholder="optional">
</div>
<por-image-registry image="config.Image" registry="config.Registry"></por-image-registry>
</div>
<!-- !name-and-registry-inputs -->
<!-- !image-and-registry -->
<!-- tag-note -->
<div class="form-group">
<div class="col-sm-12">

View file

@ -1,6 +1,6 @@
angular.module('containers', [])
.controller('ContainersController', ['$q', '$scope', '$filter', 'Container', 'ContainerService', 'ContainerHelper', 'Info', 'Notifications', 'Pagination', 'EntityListService', 'ModalService', 'ResourceControlService', 'EndpointProvider',
function ($q, $scope, $filter, Container, ContainerService, ContainerHelper, Info, Notifications, Pagination, EntityListService, ModalService, ResourceControlService, EndpointProvider) {
.controller('ContainersController', ['$q', '$scope', '$filter', 'Container', 'ContainerService', 'ContainerHelper', 'SystemService', 'Notifications', 'Pagination', 'EntityListService', 'ModalService', 'ResourceControlService', 'EndpointProvider',
function ($q, $scope, $filter, Container, ContainerService, ContainerHelper, SystemService, Notifications, Pagination, EntityListService, ModalService, ResourceControlService, EndpointProvider) {
$scope.state = {};
$scope.state.pagination_count = Pagination.getPaginationCount('containers');
$scope.state.displayAll = true;
@ -202,15 +202,18 @@ angular.module('containers', [])
return swarm_hosts;
}
function initView(){
if ($scope.applicationState.endpoint.mode.provider === 'DOCKER_SWARM') {
Info.get({}, function (d) {
function initView() {
var provider = $scope.applicationState.endpoint.mode.provider;
$q.when(provider !== 'DOCKER_SWARM' || SystemService.info())
.then(function success(data) {
if (provider === 'DOCKER_SWARM') {
$scope.swarm_hosts = retrieveSwarmHostsInfo(d);
update({all: $scope.state.displayAll ? 1 : 0});
});
} else {
}
update({all: $scope.state.displayAll ? 1 : 0});
}
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to retrieve cluster information');
});
}
initView();

View file

@ -1,8 +1,8 @@
// @@OLD_SERVICE_CONTROLLER: this service should be rewritten to use services.
// See app/components/templates/templatesController.js as a reference.
angular.module('createContainer', [])
.controller('CreateContainerController', ['$q', '$scope', '$state', '$stateParams', '$filter', 'Info', 'Container', 'ContainerHelper', 'Image', 'ImageHelper', 'Volume', 'Network', 'ResourceControlService', 'Authentication', 'Notifications', 'ContainerService', 'ImageService', 'ControllerDataPipeline', 'FormValidator',
function ($q, $scope, $state, $stateParams, $filter, Info, Container, ContainerHelper, Image, ImageHelper, Volume, Network, ResourceControlService, Authentication, Notifications, ContainerService, ImageService, ControllerDataPipeline, FormValidator) {
.controller('CreateContainerController', ['$q', '$scope', '$state', '$stateParams', '$filter', 'Container', 'ContainerHelper', 'Image', 'ImageHelper', 'Volume', 'Network', 'ResourceControlService', 'Authentication', 'Notifications', 'ContainerService', 'ImageService', 'ControllerDataPipeline', 'FormValidator',
function ($q, $scope, $state, $stateParams, $filter, Container, ContainerHelper, Image, ImageHelper, Volume, Network, ResourceControlService, Authentication, Notifications, ContainerService, ImageService, ControllerDataPipeline, FormValidator) {
$scope.formValues = {
alwaysPull: true,
@ -94,7 +94,7 @@ function ($q, $scope, $state, $stateParams, $filter, Info, Container, ContainerH
function prepareImageConfig(config) {
var image = config.Image;
var registry = $scope.formValues.Registry;
var imageConfig = ImageHelper.createImageConfigForContainer(image, registry);
var imageConfig = ImageHelper.createImageConfigForContainer(image, registry.URL);
config.Image = imageConfig.fromImage + ':' + imageConfig.tag;
$scope.imageConfig = imageConfig;
}

View file

@ -21,21 +21,11 @@
<div class="col-sm-12 form-section-title">
Image configuration
</div>
<!-- image-and-registry-inputs -->
<!-- image-and-registry -->
<div class="form-group">
<label for="container_image" class="col-sm-1 control-label text-left">Image</label>
<div class="col-sm-11 col-md-6">
<input type="text" class="form-control" ng-model="config.Image" id="container_image" placeholder="e.g. ubuntu:trusty">
</div>
<label for="image_registry" class="col-sm-2 margin-sm-top control-label text-left">
Registry
<portainer-tooltip position="bottom" message="A registry to pull the image from. Leave empty to use the official Docker registry."></portainer-tooltip>
</label>
<div class="col-sm-10 col-md-3 margin-sm-top">
<input type="text" class="form-control" ng-model="formValues.Registry" id="image_registry" placeholder="e.g. myregistry.mydomain">
</div>
<por-image-registry image="config.Image" registry="formValues.Registry"></por-image-registry>
</div>
<!-- !image-and-registry-inputs -->
<!-- !image-and-registry -->
<!-- always-pull -->
<div class="form-group">
<div class="col-sm-12">

View file

@ -0,0 +1,49 @@
angular.module('createRegistry', [])
.controller('CreateRegistryController', ['$scope', '$state', 'RegistryService', 'Notifications',
function ($scope, $state, RegistryService, Notifications) {
$scope.state = {
RegistryType: 'quay'
};
$scope.formValues = {
Name: 'Quay',
URL: 'quay.io',
Authentication: true,
Username: '',
Password: ''
};
$scope.selectQuayRegistry = function() {
$scope.formValues.Name = 'Quay';
$scope.formValues.URL = 'quay.io';
$scope.formValues.Authentication = true;
};
$scope.selectCustomRegistry = function() {
$scope.formValues.Name = '';
$scope.formValues.URL = '';
$scope.formValues.Authentication = false;
};
$scope.addRegistry = function() {
$('#createRegistrySpinner').show();
var registryName = $scope.formValues.Name;
var registryURL = $scope.formValues.URL.replace(/^https?\:\/\//i, '');
var authentication = $scope.formValues.Authentication;
var username = $scope.formValues.Username;
var password = $scope.formValues.Password;
RegistryService.createRegistry(registryName, registryURL, authentication, username, password)
.then(function success(data) {
Notifications.success('Registry successfully created');
$state.go('registries');
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to create registry');
})
.finally(function final() {
$('#createRegistrySpinner').hide();
});
};
}]);

View file

@ -0,0 +1,117 @@
<rd-header>
<rd-header-title title="Create registry">
<i id="loadingViewSpinner" class="fa fa-cog fa-spin" style="display:none"></i>
</rd-header-title>
<rd-header-content>
<a ui-sref="registries">Registries</a> > Add registry
</rd-header-content>
</rd-header>
<div class="row">
<div class="col-sm-12">
<rd-widget>
<rd-widget-body>
<form class="form-horizontal">
<div class="col-sm-12 form-section-title">
Registry provider
</div>
<div class="form-group"></div>
<div class="form-group" style="margin-bottom: 0">
<div class="boxselector_wrapper">
<div ng-click="selectQuayRegistry()">
<input type="radio" id="registry_quay" ng-model="state.RegistryType" value="quay">
<label for="registry_quay">
<div class="boxselector_header">
<i class="fa fa-database" aria-hidden="true" style="margin-right: 2px;"></i>
Quay.io
</div>
<p>Quay container registry</p>
</label>
</div>
<div ng-click="selectCustomRegistry()">
<input type="radio" id="registry_custom" ng-model="state.RegistryType" value="custom">
<label for="registry_custom">
<div class="boxselector_header">
<i class="fa fa-database" aria-hidden="true" style="margin-right: 2px;"></i>
Custom registry
</div>
<p>Define your own registry</p>
</label>
</div>
</div>
</div>
<div class="col-sm-12 form-section-title" ng-if="state.RegistryType === 'custom'">
Important notice
</div>
<div class="form-group" ng-if="state.RegistryType === 'custom'">
<span class="col-sm-12 text-muted small">
Docker requires you to connect to a <a href="https://docs.docker.com/registry/deploying/#running-a-domain-registry" target="_blank">secure registry</a>.
You can find more information about how to connect to an insecure registry <a href="https://docs.docker.com/registry/insecure/" target="_blank">in the Docker documentation</a>.
</span>
</div>
<div class="col-sm-12 form-section-title">
Registry details
</div>
<!-- name-input -->
<div class="form-group" ng-if="state.RegistryType === 'custom'">
<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" ng-model="formValues.Name" placeholder="e.g. my-registry">
</div>
</div>
<!-- !name-input -->
<!-- registry-url-input -->
<div class="form-group" ng-if="state.RegistryType === 'custom'">
<label for="registry_url" class="col-sm-3 col-lg-2 control-label text-left">
Registry URL
<portainer-tooltip position="bottom" message="URL or IP address of a Docker registry. Any protocol will be stripped."></portainer-tooltip>
</label>
<div class="col-sm-9 col-lg-10">
<input type="text" class="form-control" id="registry_url" ng-model="formValues.URL" placeholder="e.g. 10.0.0.10:5000 or myregistry.domain.tld">
</div>
</div>
<!-- !registry-url-input -->
<!-- authentication-checkbox -->
<div class="form-group" ng-if="state.RegistryType === 'custom'">
<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 this registry."></portainer-tooltip>
</label>
<label class="switch" style="margin-left: 20px;">
<input type="checkbox" ng-model="formValues.Authentication"><i></i>
</label>
</div>
</div>
<!-- !authentication-checkbox -->
<!-- authentication-credentials -->
<div ng-if="formValues.Authentication || state.RegistryType === 'quay'">
<!-- credentials-user -->
<div class="form-group">
<label for="credentials_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="credentials_username" ng-model="formValues.Username">
</div>
</div>
<!-- !credentials-user -->
<!-- credentials-password -->
<div class="form-group">
<label for="credentials_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="credentials_password" ng-model="formValues.Password">
</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="!formValues.Name || !formValues.URL || (formValues.Authentication && (!formValues.Username || !formValues.Password))" ng-click="addRegistry()">Add registry</button>
<i id="createRegistrySpinner" class="fa fa-cog fa-spin" style="margin-left: 5px; display: none;"></i>
</div>
</div>
</form>
</rd-widget-body>
</rd-widget>
</div>
</div>

View file

@ -1,13 +1,13 @@
// @@OLD_SERVICE_CONTROLLER: this service should be rewritten to use services.
// See app/components/templates/templatesController.js as a reference.
angular.module('createService', [])
.controller('CreateServiceController', ['$q', '$scope', '$state', 'Service', 'ServiceHelper', 'SecretHelper', 'SecretService', 'VolumeService', 'NetworkService', 'ImageHelper', 'Authentication', 'ResourceControlService', 'Notifications', 'ControllerDataPipeline', 'FormValidator',
function ($q, $scope, $state, Service, ServiceHelper, SecretHelper, SecretService, VolumeService, NetworkService, ImageHelper, Authentication, ResourceControlService, Notifications, ControllerDataPipeline, FormValidator) {
.controller('CreateServiceController', ['$q', '$scope', '$state', 'Service', 'ServiceHelper', 'SecretHelper', 'SecretService', 'VolumeService', 'NetworkService', 'ImageHelper', 'Authentication', 'ResourceControlService', 'Notifications', 'ControllerDataPipeline', 'FormValidator', 'RegistryService', 'HttpRequestHelper',
function ($q, $scope, $state, Service, ServiceHelper, SecretHelper, SecretService, VolumeService, NetworkService, ImageHelper, Authentication, ResourceControlService, Notifications, ControllerDataPipeline, FormValidator, RegistryService, HttpRequestHelper) {
$scope.formValues = {
Name: '',
Image: '',
Registry: '',
Registry: {},
Mode: 'replicated',
Replicas: 1,
Command: '',
@ -105,7 +105,7 @@ function ($q, $scope, $state, Service, ServiceHelper, SecretHelper, SecretServic
};
function prepareImageConfig(config, input) {
var imageConfig = ImageHelper.createImageConfigForContainer(input.Image, input.Registry);
var imageConfig = ImageHelper.createImageConfigForContainer(input.Image, input.Registry.URL);
config.TaskTemplate.ContainerSpec.Image = imageConfig.fromImage + ':' + imageConfig.tag;
}
@ -257,6 +257,10 @@ function ($q, $scope, $state, Service, ServiceHelper, SecretHelper, SecretServic
}
function createNewService(config, accessControlData) {
var registry = $scope.formValues.Registry;
var authenticationDetails = registry.Authentication ? RegistryService.encodedCredentials(registry) : '';
HttpRequestHelper.setRegistryAuthenticationHeader(authenticationDetails);
Service.create(config).$promise
.then(function success(data) {
var serviceIdentifier = data.ID;

View file

@ -23,21 +23,11 @@
<div class="col-sm-12 form-section-title">
Image configuration
</div>
<!-- image-and-registry-inputs -->
<!-- image-and-registry -->
<div class="form-group">
<label for="service_image" class="col-sm-1 control-label text-left">Image</label>
<div class="col-sm-11 col-md-6">
<input type="text" class="form-control" ng-model="formValues.Image" id="service_image" placeholder="e.g. nginx:latest">
</div>
<label for="image_registry" class="col-sm-2 margin-sm-top control-label text-left">
Registry
<portainer-tooltip position="bottom" message="A registry to pull the image from. Leave empty to use the official Docker registry."></portainer-tooltip>
</label>
<div class="col-sm-10 col-md-3 margin-sm-top">
<input type="text" class="form-control" ng-model="formValues.Registry" id="image_registry" placeholder="e.g. myregistry.mydomain">
</div>
<por-image-registry image="formValues.Image" registry="formValues.Registry"></por-image-registry>
</div>
<!-- !image-and-registry-inputs -->
<!-- !image-and-registry -->
<div class="col-sm-12 form-section-title">
Scheduling
</div>

View file

@ -1,6 +1,6 @@
angular.module('createVolume', [])
.controller('CreateVolumeController', ['$scope', '$state', 'VolumeService', 'InfoService', 'ResourceControlService', 'Authentication', 'Notifications', 'ControllerDataPipeline', 'FormValidator',
function ($scope, $state, VolumeService, InfoService, ResourceControlService, Authentication, Notifications, ControllerDataPipeline, FormValidator) {
.controller('CreateVolumeController', ['$scope', '$state', 'VolumeService', 'SystemService', 'ResourceControlService', 'Authentication', 'Notifications', 'ControllerDataPipeline', 'FormValidator',
function ($scope, $state, VolumeService, SystemService, ResourceControlService, Authentication, Notifications, ControllerDataPipeline, FormValidator) {
$scope.formValues = {
Driver: 'local',
@ -69,7 +69,7 @@ function ($scope, $state, VolumeService, InfoService, ResourceControlService, Au
function initView() {
$('#loadingViewSpinner').show();
InfoService.getVolumePlugins()
SystemService.getVolumePlugins()
.then(function success(data) {
$scope.availableVolumeDrivers = data;
})

View file

@ -1,6 +1,6 @@
angular.module('dashboard', [])
.controller('DashboardController', ['$scope', '$q', 'Container', 'ContainerHelper', 'Image', 'Network', 'Volume', 'Info', 'Notifications',
function ($scope, $q, Container, ContainerHelper, Image, Network, Volume, Info, Notifications) {
.controller('DashboardController', ['$scope', '$q', 'Container', 'ContainerHelper', 'Image', 'Network', 'Volume', 'SystemService', 'Notifications',
function ($scope, $q, Container, ContainerHelper, Image, Network, Volume, SystemService, Notifications) {
$scope.containerData = {
total: 0
@ -68,7 +68,7 @@ function ($scope, $q, Container, ContainerHelper, Image, Network, Volume, Info,
Image.query({}).$promise,
Volume.query({}).$promise,
Network.query({}).$promise,
Info.get({}).$promise
SystemService.info()
]).then(function (d) {
prepareContainerData(d[0]);
prepareImageData(d[1]);

View file

@ -8,7 +8,7 @@
<rd-header-content>Docker</rd-header-content>
</rd-header>
<div class="row" ng-if="state.loaded">
<div class="row" ng-if="info && version">
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
<rd-widget>
<rd-widget-header icon="fa-code" title="Engine version"></rd-widget-header>
@ -50,7 +50,7 @@
</div>
</div>
<div class="row" ng-if="state.loaded">
<div class="row" ng-if="info && version">
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
<rd-widget>
<rd-widget-header icon="fa-th" title="Engine status"></rd-widget-header>
@ -92,7 +92,7 @@
</div>
</div>
<div class="row" ng-if="state.loaded && info.Plugins">
<div class="row" ng-if="info && info.Plugins">
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
<rd-widget>
<rd-widget-header icon="fa-plug" title="Engine plugins"></rd-widget-header>

View file

@ -1,24 +1,26 @@
angular.module('docker', [])
.controller('DockerController', ['$scope', 'Info', 'Version', 'Notifications',
function ($scope, Info, Version, Notifications) {
$scope.state = {
loaded: false
};
.controller('DockerController', ['$q', '$scope', 'SystemService', 'Notifications',
function ($q, $scope, SystemService, Notifications) {
$scope.info = {};
$scope.version = {};
Info.get({}, function (infoData) {
$scope.info = infoData;
Version.get({}, function (versionData) {
$scope.version = versionData;
$scope.state.loaded = true;
$('#loadingViewSpinner').hide();
}, function (e) {
Notifications.error('Failure', e, 'Unable to retrieve engine details');
function initView() {
$('#loadingViewSpinner').show();
$q.all({
version: SystemService.version(),
info: SystemService.info()
})
.then(function success(data) {
$scope.version = data.version;
$scope.info = data.info;
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to retrieve engine details');
})
.finally(function final() {
$('#loadingViewSpinner').hide();
});
}, function (e) {
Notifications.error('Failure', e, 'Unable to retrieve engine information');
$('#loadingViewSpinner').hide();
});
}
initView();
}]);

View file

@ -41,137 +41,5 @@
</div>
</div>
<div class="row" ng-if="endpoint">
<div class="col-sm-6">
<rd-widget>
<rd-widget-header classes="col-sm-12 col-md-6 nopadding" icon="fa-users" title="Users and groups">
<div class="pull-md-right pull-lg-right">
Items per page:
<select ng-model="state.pagination_count_accesses" ng-change="changePaginationCountAccesses()">
<option value="0">All</option>
<option value="10">10</option>
<option value="25">25</option>
<option value="50">50</option>
<option value="100">100</option>
</select>
</div>
</rd-widget-header>
<rd-widget-taskbar classes="col-sm-12 nopadding">
<div class="col-sm-12 col-md-6 nopadding">
<button class="btn btn-primary btn-sm" ng-click="authorizeAllAccesses()" ng-disabled="accesses.length === 0 || filteredUsers.length === 0"><i class="fa fa-user-plus space-right" aria-hidden="true"></i>Authorize all</button>
</div>
<div class="col-sm-12 col-md-6 nopadding">
<input type="text" id="filter" ng-model="state.filterUsers" placeholder="Filter..." class="form-control input-sm" />
</div>
</rd-widget-taskbar>
<rd-widget-body classes="no-padding">
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>
<a ui-sref="endpoint.access({id: endpoint.Id})" ng-click="orderAccesses('Name')">
Name
<span ng-show="sortTypeAccesses == 'Name' && !sortReverseAccesses" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortTypeAccesses == 'Name' && sortReverseAccesses" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th>
<a ui-sref="endpoint.access({id: endpoint.Id})" ng-click="orderAccesses('Type')">
Type
<span ng-show="sortTypeAccesses == 'Type' && !sortReverseAccesses" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortTypeAccesses == 'Type' && sortReverseAccesses" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
</tr>
</thead>
<tbody>
<tr ng-click="authorizeAccess(user)" class="interactive" dir-paginate="user in accesses | filter:state.filterUsers | orderBy:sortTypeAccesses:sortReverseAccesses | itemsPerPage: state.pagination_count_accesses">
<td>{{ user.Name }}</td>
<td>
<i class="fa" ng-class="user.Type === 'user' ? 'fa-user' : 'fa-users'" aria-hidden="true" style="margin-right: 2px;"></i>
{{ user.Type }}
</td>
</tr>
<tr ng-if="!accesses">
<td colspan="2" class="text-center text-muted">Loading...</td>
</tr>
<tr ng-if="accesses.length === 0 || (accesses | filter:state.filterUsers | orderBy:sortTypeAccesses:sortReverseAccesses | itemsPerPage: state.pagination_count_accesses).length === 0">
<td colspan="2" class="text-center text-muted">No user or team available.</td>
</tr>
</tbody>
</table>
<div ng-if="accesses" class="pull-left pagination-controls">
<dir-pagination-controls></dir-pagination-controls>
</div>
</div>
</rd-widget-body>
</rd-widget>
</div>
<div class="col-sm-6">
<rd-widget>
<rd-widget-header classes="col-sm-12 col-md-6 nopadding" icon="fa-users" title="Authorized users and groups">
<div class="pull-md-right pull-lg-right">
Items per page:
<select ng-model="state.pagination_count_authorizedAccesses" ng-change="changePaginationCountAuthorizedAccesses()">
<option value="0">All</option>
<option value="10">10</option>
<option value="25">25</option>
<option value="50">50</option>
<option value="100">100</option>
</select>
</div>
</rd-widget-header>
<rd-widget-taskbar classes="col-sm-12 nopadding">
<div class="col-sm-12 col-md-6 nopadding">
<button class="btn btn-primary btn-sm" ng-click="unauthorizeAllAccesses()" ng-disabled="authorizedAccesses.length === 0 || filteredAuthorizedUsers.length === 0"><i class="fa fa-user-times space-right" aria-hidden="true"></i>Deny all</button>
</div>
<div class="col-sm-12 col-md-6 nopadding">
<input type="text" id="filter" ng-model="state.filterAuthorizedUsers" placeholder="Filter..." class="form-control input-sm" />
</div>
</rd-widget-taskbar>
<rd-widget-body classes="no-padding">
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>
<a ui-sref="endpoint.access({id: endpoint.Id})" ng-click="orderAuthorizedAccesses('Name')">
Name
<span ng-show="sortTypeAuthorizedAccesses == 'Name' && !sortReverseAuthorizedAccesses" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortTypeAuthorizedAccesses == 'Name' && sortReverseAuthorizedAccesses" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th>
<a ui-sref="endpoint.access({id: endpoint.Id})" ng-click="orderAuthorizedAccesses('Type')">
Type
<span ng-show="sortTypeAuthorizedAccesses == 'Type' && !sortReverseAuthorizedAccesses" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortTypeAuthorizedAccesses == 'Type' && sortReverseAuthorizedAccesses" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
</tr>
</thead>
<tbody>
<tr ng-click="unauthorizeAccess(user)" class="interactive" pagination-id="table_authaccess" dir-paginate="user in authorizedAccesses | filter:state.filterAuthorizedUsers | orderBy:sortTypeAuthorizedAccesses:sortReverseAuthorizedAccesses | itemsPerPage: state.pagination_count_authorizedAccesses">
<td>{{ user.Name }}</td>
<td>
<i class="fa" ng-class="user.Type === 'user' ? 'fa-user' : 'fa-users'" aria-hidden="true" style="margin-right: 2px;"></i>
{{ user.Type }}
</td>
</tr>
<tr ng-if="!authorizedAccesses">
<td colspan="2" class="text-center text-muted">Loading...</td>
</tr>
<tr ng-if="authorizedAccesses.length === 0 || (authorizedAccesses | filter:state.filterAuthorizedUsers | orderBy:sortTypeAuthorizedAccesses:sortReverseAuthorizedAccesses | itemsPerPage: state.pagination_count_authorizedAccesses).length === 0">
<td colspan="2" class="text-center text-muted">No authorized user or team.</td>
</tr>
</tbody>
</table>
<div ng-if="authorizedAccesses" class="pull-left pagination-controls">
<dir-pagination-controls pagination-id="table_authaccess"></dir-pagination-controls>
</div>
</div>
</rd-widget-body>
</rd-widget>
</div>
</div>
<por-access-management ng-if="endpoint" access-controlled-entity="endpoint" update-access="updateAccess(userAccesses, teamAccesses)">
</por-access-management>

View file

@ -1,177 +1,18 @@
angular.module('endpointAccess', [])
.controller('EndpointAccessController', ['$q', '$scope', '$state', '$stateParams', '$filter', 'EndpointService', 'UserService', 'TeamService', 'Pagination', 'Notifications',
function ($q, $scope, $state, $stateParams, $filter, EndpointService, UserService, TeamService, Pagination, Notifications) {
.controller('EndpointAccessController', ['$scope', '$stateParams', 'EndpointService', 'Notifications',
function ($scope, $stateParams, EndpointService, Notifications) {
$scope.state = {
pagination_count_accesses: Pagination.getPaginationCount('endpoint_access_accesses'),
pagination_count_authorizedAccesses: Pagination.getPaginationCount('endpoint_access_authorizedAccesses')
};
$scope.sortTypeAccesses = 'Type';
$scope.sortReverseAccesses = false;
$scope.orderAccesses = function(sortType) {
$scope.sortReverseAccesses = ($scope.sortTypeAccesses === sortType) ? !$scope.sortReverseAccesses : false;
$scope.sortTypeAccesses = sortType;
};
$scope.changePaginationCountAccesses = function() {
Pagination.setPaginationCount('endpoint_access_accesses', $scope.state.pagination_count_accesses);
};
$scope.sortTypeAuthorizedAccesses = 'Type';
$scope.sortReverseAuthorizedAccesses = false;
$scope.orderAuthorizedAccesses = function(sortType) {
$scope.sortReverseAuthorizedAccesses = ($scope.sortTypeAuthorizedAccesses === sortType) ? !$scope.sortReverseAuthorizedAccesses : false;
$scope.sortTypeAuthorizedAccesses = sortType;
};
$scope.changePaginationCountAuthorizedAccesses = function() {
Pagination.setPaginationCount('endpoint_access_authorizedAccesses', $scope.state.pagination_count_authorizedAccesses);
};
$scope.authorizeAllAccesses = function() {
var authorizedUsers = [];
var authorizedTeams = [];
angular.forEach($scope.authorizedAccesses, function (a) {
if (a.Type === 'user') {
authorizedUsers.push(a.Id);
} else if (a.Type === 'team') {
authorizedTeams.push(a.Id);
}
});
angular.forEach($scope.accesses, function (a) {
if (a.Type === 'user') {
authorizedUsers.push(a.Id);
} else if (a.Type === 'team') {
authorizedTeams.push(a.Id);
}
});
EndpointService.updateAccess($stateParams.id, authorizedUsers, authorizedTeams)
.then(function success(data) {
$scope.authorizedAccesses = $scope.authorizedAccesses.concat($scope.accesses);
$scope.accesses = [];
Notifications.success('Endpoint accesses successfully updated');
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to update endpoint accesses');
});
};
$scope.unauthorizeAllAccesses = function() {
EndpointService.updateAccess($stateParams.id, [], [])
.then(function success(data) {
$scope.accesses = $scope.accesses.concat($scope.authorizedAccesses);
$scope.authorizedAccesses = [];
Notifications.success('Endpoint accesses successfully updated');
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to update endpoint accesses');
});
};
$scope.authorizeAccess = function(access) {
var authorizedUsers = [];
var authorizedTeams = [];
angular.forEach($scope.authorizedAccesses, function (a) {
if (a.Type === 'user') {
authorizedUsers.push(a.Id);
} else if (a.Type === 'team') {
authorizedTeams.push(a.Id);
}
});
if (access.Type === 'user') {
authorizedUsers.push(access.Id);
} else if (access.Type === 'team') {
authorizedTeams.push(access.Id);
}
EndpointService.updateAccess($stateParams.id, authorizedUsers, authorizedTeams)
.then(function success(data) {
removeAccessFromArray(access, $scope.accesses);
$scope.authorizedAccesses.push(access);
Notifications.success('Endpoint accesses successfully updated', access.Name);
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to update endpoint accesses');
});
};
$scope.unauthorizeAccess = function(access) {
var authorizedUsers = [];
var authorizedTeams = [];
angular.forEach($scope.authorizedAccesses, function (a) {
if (a.Type === 'user') {
authorizedUsers.push(a.Id);
} else if (a.Type === 'team') {
authorizedTeams.push(a.Id);
}
});
if (access.Type === 'user') {
_.remove(authorizedUsers, function(n) {
return n === access.Id;
});
} else if (access.Type === 'team') {
_.remove(authorizedTeams, function(n) {
return n === access.Id;
});
}
EndpointService.updateAccess($stateParams.id, authorizedUsers, authorizedTeams)
.then(function success(data) {
removeAccessFromArray(access, $scope.authorizedAccesses);
$scope.accesses.push(access);
Notifications.success('Endpoint accesses successfully updated', access.Name);
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to update endpoint accesses');
});
$scope.updateAccess = function(authorizedUsers, authorizedTeams) {
return EndpointService.updateAccess($stateParams.id, authorizedUsers, authorizedTeams);
};
function initView() {
$('#loadingViewSpinner').show();
$q.all({
endpoint: EndpointService.endpoint($stateParams.id),
users: UserService.users(false),
teams: TeamService.teams()
})
EndpointService.endpoint($stateParams.id)
.then(function success(data) {
$scope.endpoint = data.endpoint;
$scope.accesses = [];
var users = data.users.map(function (user) {
return new EndpointAccessUserViewModel(user);
});
var teams = data.teams.map(function (team) {
return new EndpointAccessTeamViewModel(team);
});
$scope.accesses = $scope.accesses.concat(users, teams);
$scope.authorizedAccesses = [];
angular.forEach($scope.endpoint.AuthorizedUsers, function(userID) {
for (var i = 0, l = $scope.accesses.length; i < l; i++) {
if ($scope.accesses[i].Type === 'user' && $scope.accesses[i].Id === userID) {
$scope.authorizedAccesses.push($scope.accesses[i]);
$scope.accesses.splice(i, 1);
return;
}
}
});
angular.forEach($scope.endpoint.AuthorizedTeams, function(teamID) {
for (var i = 0, l = $scope.accesses.length; i < l; i++) {
if ($scope.accesses[i].Type === 'team' && $scope.accesses[i].Id === teamID) {
$scope.authorizedAccesses.push($scope.accesses[i]);
$scope.accesses.splice(i, 1);
return;
}
}
});
$scope.endpoint = data;
})
.catch(function error(err) {
$scope.accesses = [];
$scope.authorizedAccesses = [];
Notifications.error('Failure', err, 'Unable to retrieve endpoint details');
})
.finally(function final(){
@ -179,14 +20,5 @@ function ($q, $scope, $state, $stateParams, $filter, EndpointService, UserServic
});
}
function removeAccessFromArray(access, accesses) {
for (var i = 0, l = accesses.length; i < l; i++) {
if (access.Type === accesses[i].Type && access.Id === accesses[i].Id) {
accesses.splice(i, 1);
return;
}
}
}
initView();
}]);

View file

@ -1,6 +1,6 @@
angular.module('events', [])
.controller('EventsController', ['$scope', 'Notifications', 'Events', 'Pagination',
function ($scope, Notifications, Events, Pagination) {
.controller('EventsController', ['$scope', 'Notifications', 'SystemService', 'Pagination',
function ($scope, Notifications, SystemService, Pagination) {
$scope.state = {};
$scope.state.pagination_count = Pagination.getPaginationCount('events');
$scope.sortType = 'Time';
@ -15,18 +15,22 @@ function ($scope, Notifications, Events, Pagination) {
Pagination.setPaginationCount('events', $scope.state.pagination_count);
};
var from = moment().subtract(24, 'hour').unix();
var to = moment().unix();
function initView() {
var from = moment().subtract(24, 'hour').unix();
var to = moment().unix();
Events.query({since: from, until: to},
function(d) {
$scope.events = d.map(function (item) {
return new EventViewModel(item);
$('#loadEventsSpinner').show();
SystemService.events(from, to)
.then(function success(data) {
$scope.events = data;
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to load events');
})
.finally(function final() {
$('#loadEventsSpinner').hide();
});
$('#loadEventsSpinner').hide();
},
function (e) {
$('#loadEventsSpinner').hide();
Notifications.error('Failure', e, 'Unable to load events');
});
}
initView();
}]);

View file

@ -54,21 +54,11 @@
<rd-widget-header icon="fa-tag" title="Tag the image"></rd-widget-header>
<rd-widget-body>
<form class="form-horizontal">
<!-- name-and-registry-inputs -->
<!-- image-and-registry -->
<div class="form-group">
<label for="image_name" class="col-sm-1 control-label text-left">Name</label>
<div class="col-sm-11 col-md-6">
<input type="text" class="form-control" ng-model="config.Image" id="image_name" placeholder="e.g. myImage:myTag">
</div>
<label for="image_registry" class="col-sm-2 margin-sm-top control-label text-left">
Registry
<portainer-tooltip position="bottom" message="A registry to pull the image from. Leave empty to use the official Docker registry."></portainer-tooltip>
</label>
<div class="col-sm-10 col-md-3 margin-sm-top">
<input type="text" class="form-control" ng-model="config.Registry" id="image_registry" placeholder="e.g. myregistry.mydomain">
</div>
<por-image-registry image="formValues.Image" registry="formValues.Registry"></por-image-registry>
</div>
<!-- !name-and-registry-inputs -->
<!-- !image-and-registry -->
<!-- tag-note -->
<div class="form-group">
<div class="col-sm-12">
@ -78,7 +68,7 @@
<!-- !tag-note -->
<div class="form-group">
<div class="col-sm-12">
<button type="button" class="btn btn-primary btn-sm" ng-disabled="!config.Image" ng-click="tagImage()">Tag</button>
<button type="button" class="btn btn-primary btn-sm" ng-disabled="!formValues.Image" ng-click="tagImage()">Tag</button>
<i id="pullImageSpinner" class="fa fa-cog fa-spin" style="margin-left: 5px; display: none;"></i>
</div>
</div>

View file

@ -1,17 +1,17 @@
angular.module('image', [])
.controller('ImageController', ['$scope', '$stateParams', '$state', 'ImageService', 'Notifications',
function ($scope, $stateParams, $state, ImageService, Notifications) {
$scope.config = {
.controller('ImageController', ['$scope', '$stateParams', '$state', '$timeout', 'ImageService', 'RegistryService', 'Notifications',
function ($scope, $stateParams, $state, $timeout, ImageService, RegistryService, Notifications) {
$scope.formValues = {
Image: '',
Registry: ''
};
$scope.tagImage = function() {
$('#loadingViewSpinner').show();
var image = $scope.config.Image;
var registry = $scope.config.Registry;
var image = $scope.formValues.Image;
var registry = $scope.formValues.Registry;
ImageService.tagImage($stateParams.id, image, registry)
ImageService.tagImage($stateParams.id, image, registry.URL)
.then(function success(data) {
Notifications.success('Image successfully tagged');
$state.go('image', {id: $stateParams.id}, {reload: true});
@ -24,28 +24,35 @@ function ($scope, $stateParams, $state, ImageService, Notifications) {
});
};
$scope.pushTag = function(tag) {
$scope.pushTag = function(repository) {
$('#loadingViewSpinner').show();
ImageService.pushImage(tag)
.then(function success() {
Notifications.success('Image successfully pushed');
RegistryService.retrieveRegistryFromRepository(repository)
.then(function success(data) {
var registry = data;
return ImageService.pushImage(repository, registry);
})
.then(function success(data) {
Notifications.success('Image successfully pushed', repository);
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to push image tag');
Notifications.error('Failure', err, 'Unable to push image to repository');
})
.finally(function final() {
$('#loadingViewSpinner').hide();
});
};
$scope.pullTag = function(tag) {
$scope.pullTag = function(repository) {
$('#loadingViewSpinner').show();
ImageService.pullTag(tag)
RegistryService.retrieveRegistryFromRepository(repository)
.then(function success(data) {
Notifications.success('Image successfully pulled', tag);
var registry = data;
return ImageService.pullImage(repository, registry);
})
.catch(function error(err){
.then(function success(data) {
Notifications.success('Image successfully pulled', repository);
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to pull image');
})
.finally(function final() {
@ -53,15 +60,15 @@ function ($scope, $stateParams, $state, ImageService, Notifications) {
});
};
$scope.removeTag = function(id) {
$scope.removeTag = function(repository) {
$('#loadingViewSpinner').show();
ImageService.deleteImage(id, false)
ImageService.deleteImage(repository, false)
.then(function success() {
if ($scope.image.RepoTags.length === 1) {
Notifications.success('Image successfully deleted', id);
Notifications.success('Image successfully deleted', repository);
$state.go('images', {}, {reload: true});
} else {
Notifications.success('Tag successfully deleted', id);
Notifications.success('Tag successfully deleted', repository);
$state.go('image', {id: $stateParams.id}, {reload: true});
}
})

View file

@ -15,21 +15,11 @@
</rd-widget-header>
<rd-widget-body>
<form class="form-horizontal">
<!-- name-and-registry-inputs -->
<!-- image-and-registry -->
<div class="form-group">
<label for="image_name" class="col-sm-1 control-label text-left">Name</label>
<div class="col-sm-11 col-md-6">
<input type="text" class="form-control" ng-model="config.Image" id="image_name" placeholder="e.g. ubuntu:trusty">
</div>
<label for="image_registry" class="col-sm-2 margin-sm-top control-label text-left">
Registry
<portainer-tooltip position="bottom" message="A registry to pull the image from. Leave empty to use the official Docker registry."></portainer-tooltip>
</label>
<div class="col-sm-10 col-md-3 margin-sm-top">
<input type="text" class="form-control" ng-model="config.Registry" id="image_registry" placeholder="e.g. myregistry.mydomain">
</div>
<por-image-registry image="formValues.Image" registry="formValues.Registry"></por-image-registry>
</div>
<!-- !name-and-registry-inputs -->
<!-- !image-and-registry -->
<!-- tag-note -->
<div class="form-group">
<div class="col-sm-12">
@ -39,7 +29,7 @@
<!-- !tag-note -->
<div class="form-group">
<div class="col-sm-12">
<button type="button" class="btn btn-primary btn-sm" ng-disabled="!config.Image" ng-click="pullImage()">Pull</button>
<button type="button" class="btn btn-primary btn-sm" ng-disabled="!formValues.Image" ng-click="pullImage()">Pull</button>
<i id="pullImageSpinner" class="fa fa-cog fa-spin" style="margin-left: 5px; display: none;"></i>
</div>
</div>

View file

@ -7,7 +7,7 @@ function ($scope, $state, ImageService, Notifications, Pagination, ModalService)
$scope.sortReverse = true;
$scope.state.selectedItemCount = 0;
$scope.config = {
$scope.formValues = {
Image: '',
Registry: ''
};
@ -40,10 +40,11 @@ function ($scope, $state, ImageService, Notifications, Pagination, ModalService)
$scope.pullImage = function() {
$('#pullImageSpinner').show();
var image = $scope.config.Image;
var registry = $scope.config.Registry;
var image = $scope.formValues.Image;
var registry = $scope.formValues.Registry;
ImageService.pullImage(image, registry)
.then(function success(data) {
Notifications.success('Image successfully pulled', image);
$state.reload();
})
.catch(function error(err) {

View file

@ -0,0 +1,150 @@
<rd-header>
<rd-header-title title="Registries">
<a data-toggle="tooltip" title="Refresh" ui-sref="registries" ui-sref-opts="{reload: true}">
<i class="fa fa-refresh" aria-hidden="true"></i>
</a>
<i id="loadingViewSpinner" class="fa fa-cog fa-spin" style="margin-left: 5px;"></i>
</rd-header-title>
<rd-header-content>Registry management</rd-header-content>
</rd-header>
<div class="row" ng-if="dockerhub">
<div class="col-sm-12">
<rd-widget>
<rd-widget-header icon="fa-database" title="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 &amp; 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="dockerhub.Password">
</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="dockerhub.Authentication && (!dockerhub.Username || !dockerhub.Password)" ng-click="updateDockerHub()">Update</button>
<i id="updateDockerhubSpinner" class="fa fa-cog fa-spin" style="margin-left: 5px; display: none;"></i>
</div>
</div>
</form>
</rd-widget-body>
</rd-widget>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<rd-widget>
<rd-widget-header icon="fa-database" title="Available registries">
<div class="pull-right">
Items per page:
<select ng-model="state.pagination_count" ng-change="changePaginationCount()">
<option value="0">All</option>
<option value="10">10</option>
<option value="25">25</option>
<option value="50">50</option>
<option value="100">100</option>
</select>
</div>
</rd-widget-header>
<rd-widget-taskbar classes="col-lg-12">
<div class="pull-left">
<button type="button" class="btn btn-danger" ng-click="removeAction()" ng-disabled="!state.selectedItemCount"><i class="fa fa-trash space-right" aria-hidden="true"></i>Remove</button>
<a class="btn btn-primary" type="button" ui-sref="actions.create.registry"><i class="fa fa-plus space-right" aria-hidden="true"></i>Add registry</a>
</div>
<div class="pull-right">
<input type="text" id="filter" ng-model="state.filter" placeholder="Filter..." class="form-control input-sm" />
</div>
</rd-widget-taskbar>
<rd-widget-body classes="no-padding">
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>
<input type="checkbox" ng-model="allSelected" ng-change="selectItems(allSelected)" />
</th>
<th>
<a ui-sref="registries" ng-click="order('Name')">
Name
<span ng-show="sortType == 'Name' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'Name' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th>
<a ui-sref="registries" ng-click="order('URL')">
URL
<span ng-show="sortType == 'URL' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'URL' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th></th>
</tr>
</thead>
<tbody>
<tr dir-paginate="registry in (state.filteredRegistries = (registries | filter:state.filter | orderBy:sortType:sortReverse | itemsPerPage: state.pagination_count))">
<td><input type="checkbox" ng-model="registry.Checked" ng-change="selectItem(registry)" /></td>
<td>
<a ui-sref="registry({id: registry.Id})">{{ registry.Name }}</a>
<span ng-if="registry.Authentication" style="margin-left: 5px;">
<i class="fa fa-shield" aria-hidden="true" tooltip-placement="bottom" tooltip-class="portainer-tooltip" uib-tooltip="Authentication is enabled for this registry."></i>
</span>
</td>
<td>{{ registry.URL }}</td>
<td>
<span ng-if="applicationState.application.authentication">
<a ui-sref="registry.access({id: registry.Id})"><i class="fa fa-users" aria-hidden="true" style="margin-left: 7px;"></i> Manage access</a>
</span>
</td>
</tr>
<tr ng-if="!registries">
<td colspan="3" class="text-center text-muted">Loading...</td>
</tr>
<tr ng-if="registries.length == 0">
<td colspan="3" class="text-center text-muted">No registries available.</td>
</tr>
</tbody>
</table>
<div ng-if="registries" class="pull-left pagination-controls">
<dir-pagination-controls></dir-pagination-controls>
</div>
</div>
</rd-widget-body>
<rd-widget>
</div>
</div>

View file

@ -0,0 +1,113 @@
angular.module('registries', [])
.controller('RegistriesController', ['$q', '$scope', '$state', 'RegistryService', 'DockerHubService', 'ModalService', 'Notifications', 'Pagination',
function ($q, $scope, $state, RegistryService, DockerHubService, ModalService, Notifications, Pagination) {
$scope.state = {
selectedItemCount: 0,
pagination_count: Pagination.getPaginationCount('registries')
};
$scope.sortType = 'Name';
$scope.sortReverse = true;
$scope.updateDockerHub = function() {
$('#updateDockerhubSpinner').show();
var dockerhub = $scope.dockerhub;
DockerHubService.update(dockerhub)
.then(function success(data) {
Notifications.success('DockerHub registry updated');
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to update DockerHub details');
})
.finally(function final() {
$('#updateDockerhubSpinner').hide();
});
};
$scope.order = function(sortType) {
$scope.sortReverse = ($scope.sortType === sortType) ? !$scope.sortReverse : false;
$scope.sortType = sortType;
};
$scope.changePaginationCount = function() {
Pagination.setPaginationCount('endpoints', $scope.state.pagination_count);
};
$scope.selectItems = function (allSelected) {
angular.forEach($scope.state.filteredRegistries, function (registry) {
if (registry.Checked !== allSelected) {
registry.Checked = allSelected;
$scope.selectItem(registry);
}
});
};
$scope.selectItem = function (item) {
if (item.Checked) {
$scope.state.selectedItemCount++;
} else {
$scope.state.selectedItemCount--;
}
};
$scope.removeAction = function() {
ModalService.confirmDeletion(
'Do you want to remove the selected registries?',
function onConfirm(confirmed) {
if(!confirmed) { return; }
removeRegistries();
}
);
};
function removeRegistries() {
$('#loadingViewSpinner').show();
var counter = 0;
var complete = function () {
counter = counter - 1;
if (counter === 0) {
$('#loadingViewSpinner').hide();
}
};
var registries = $scope.registries;
angular.forEach(registries, function (registry) {
if (registry.Checked) {
counter = counter + 1;
RegistryService.deleteRegistry(registry.Id)
.then(function success(data) {
var index = registries.indexOf(registry);
registries.splice(index, 1);
Notifications.success('Registry deleted', registry.Name);
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to remove registry');
})
.finally(function final() {
complete();
});
}
});
}
function initView() {
$('#loadingViewSpinner').show();
$q.all({
registries: RegistryService.registries(),
dockerhub: DockerHubService.dockerhub()
})
.then(function success(data) {
$scope.registries = data.registries;
$scope.dockerhub = data.dockerhub;
})
.catch(function error(err) {
$scope.registries = [];
Notifications.error('Failure', err, 'Unable to retrieve registries');
})
.finally(function final() {
$('#loadingViewSpinner').hide();
});
}
initView();
}]);

View file

@ -0,0 +1,78 @@
<rd-header>
<rd-header-title title="Registry details">
<i id="loadingViewSpinner" class="fa fa-cog fa-spin"></i>
</rd-header-title>
<rd-header-content>
<a ui-sref="registries">Registries</a> > <a ui-sref="registry({id: registry.Id})">{{ registry.Name }}</a>
</rd-header-content>
</rd-header>
<div class="row">
<div class="col-sm-12">
<rd-widget>
<rd-widget-body>
<form class="form-horizontal">
<!-- 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" ng-model="registry.Name" placeholder="e.g. my-registry">
</div>
</div>
<!-- !name-input -->
<!-- registry-url-input -->
<div class="form-group">
<label for="registry_url" class="col-sm-3 col-lg-2 control-label text-left">
Registry URL
<portainer-tooltip position="bottom" message="URL or IP address of a Docker registry."></portainer-tooltip>
</label>
<div class="col-sm-9 col-lg-10">
<input type="text" class="form-control" id="registry_url" ng-model="registry.URL" placeholder="e.g. 10.0.0.10:5000 or myregistry.domain.tld">
</div>
</div>
<!-- !registry-url-input -->
<!-- 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 this registry."></portainer-tooltip>
</label>
<label class="switch" style="margin-left: 20px;">
<input type="checkbox" ng-model="registry.Authentication"><i></i>
</label>
</div>
</div>
<!-- !authentication-checkbox -->
<!-- authentication-credentials -->
<div ng-if="registry.Authentication">
<!-- credentials-user -->
<div class="form-group">
<label for="credentials_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="credentials_username" ng-model="registry.Username">
</div>
</div>
<!-- !credentials-user -->
<!-- credentials-password -->
<div class="form-group">
<label for="credentials_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="credentials_password" ng-model="registry.Password">
</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="!registry.Name || !registry.URL || (registry.Authentication && (!registry.Username || !registry.Password))" ng-click="updateRegistry()">Update registry</button>
<a type="button" class="btn btn-default btn-sm" ui-sref="registries">Cancel</a>
<i id="updateRegistrySpinner" class="fa fa-cog fa-spin" style="margin-left: 5px; display: none;"></i>
</div>
</div>
</form>
</rd-widget-body>
</rd-widget>
</div>
</div>

View file

@ -0,0 +1,37 @@
angular.module('registry', [])
.controller('RegistryController', ['$scope', '$state', '$stateParams', '$filter', 'RegistryService', 'Notifications',
function ($scope, $state, $stateParams, $filter, RegistryService, Notifications) {
$scope.updateRegistry = function() {
$('#updateRegistrySpinner').show();
var registry = $scope.registry;
RegistryService.updateRegistry(registry)
.then(function success(data) {
Notifications.success('Registry successfully updated');
$state.go('registries');
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to update registry');
})
.finally(function final() {
$('#updateRegistrySpinner').hide();
});
};
function initView() {
$('#loadingViewSpinner').show();
var registryID = $stateParams.id;
RegistryService.registry(registryID)
.then(function success(data) {
$scope.registry = data;
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to retrieve registry details');
})
.finally(function final() {
$('#loadingViewSpinner').hide();
});
}
initView();
}]);

View file

@ -0,0 +1,45 @@
<rd-header>
<rd-header-title title="Registry access">
<i id="loadingViewSpinner" class="fa fa-cog fa-spin"></i>
</rd-header-title>
<rd-header-content>
<a ui-sref="registries">Registries</a> > <a ui-sref="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="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>
<tr>
<td colspan="2">
<span class="small text-muted">
You can select which user or team can access this registry by moving them to the authorized accesses table. Simply click
on a user or team entry to move it from one table to the other.
</span>
</td>
</tr>
</tbody>
</table>
</rd-widget-body>
</rd-widget>
</div>
</div>
<por-access-management ng-if="registry" access-controlled-entity="registry" update-access="updateAccess(userAccesses, teamAccesses)">
</por-access-management>

View file

@ -0,0 +1,24 @@
angular.module('registryAccess', [])
.controller('RegistryAccessController', ['$scope', '$stateParams', 'RegistryService', 'Notifications',
function ($scope, $stateParams, RegistryService, Notifications) {
$scope.updateAccess = function(authorizedUsers, authorizedTeams) {
return RegistryService.updateAccess($stateParams.id, authorizedUsers, authorizedTeams);
};
function initView() {
$('#loadingViewSpinner').show();
RegistryService.registry($stateParams.id)
.then(function success(data) {
$scope.registry = data;
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to retrieve registry details');
})
.finally(function final(){
$('#loadingViewSpinner').hide();
});
}
initView();
}]);

View file

@ -64,6 +64,9 @@
<li class="sidebar-list" ng-if="!applicationState.application.authentication || isAdmin">
<a ui-sref="endpoints" ui-sref-active="active">Endpoints <span class="menu-icon fa fa-plug"></span></a>
</li>
<li class="sidebar-list" ng-if="!applicationState.application.authentication || isAdmin">
<a ui-sref="registries" ui-sref-active="active">Registries <span class="menu-icon fa fa-database"></span></a>
</li>
<li class="sidebar-list" ng-if="!applicationState.application.authentication || isAdmin">
<a ui-sref="settings" ui-sref-active="active">Settings <span class="menu-icon fa fa-cogs"></span></a>
</li>

View file

@ -3,6 +3,7 @@
<a data-toggle="tooltip" title="Refresh" ui-sref="swarm" ui-sref-opts="{reload: true}">
<i class="fa fa-refresh" aria-hidden="true"></i>
</a>
<i id="loadingViewSpinner" class="fa fa-cog fa-spin" style="margin-left: 5px;"></i>
</rd-header-title>
<rd-header-content>Swarm</rd-header-content>
</rd-header>

View file

@ -1,6 +1,6 @@
angular.module('swarm', [])
.controller('SwarmController', ['$scope', 'Info', 'Version', 'Node', 'Pagination',
function ($scope, Info, Version, Node, Pagination) {
.controller('SwarmController', ['$q', '$scope', 'SystemService', 'NodeService', 'Pagination', 'Notifications',
function ($q, $scope, SystemService, NodeService, Pagination, Notifications) {
$scope.state = {};
$scope.state.pagination_count = Pagination.getPaginationCount('swarm_nodes');
$scope.sortType = 'Spec.Role';
@ -20,30 +20,6 @@ function ($scope, Info, Version, Node, Pagination) {
Pagination.setPaginationCount('swarm_nodes', $scope.state.pagination_count);
};
Version.get({}, function (d) {
$scope.docker = d;
});
Info.get({}, function (d) {
$scope.info = d;
if ($scope.applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE') {
Node.query({}, function(d) {
$scope.nodes = d.map(function (node) {
return new NodeViewModel(node);
});
var CPU = 0, memory = 0;
angular.forEach(d, function(node) {
CPU += node.Description.Resources.NanoCPUs;
memory += node.Description.Resources.MemoryBytes;
});
$scope.totalCPU = CPU / 1000000000;
$scope.totalMemory = memory;
});
} else {
extractSwarmInfo(d);
}
});
function extractSwarmInfo(info) {
// Swarm info is available in SystemStatus object
var systemStatus = info.SystemStatus;
@ -84,4 +60,43 @@ function ($scope, Info, Version, Node, Pagination) {
node.version = info[offset + 8][1];
$scope.swarm.Status.push(node);
}
function processTotalCPUAndMemory(nodes) {
var CPU = 0, memory = 0;
angular.forEach(nodes, function(node) {
CPU += node.CPUs;
memory += node.Memory;
});
$scope.totalCPU = CPU / 1000000000;
$scope.totalMemory = memory;
}
function initView() {
$('#loadingViewSpinner').show();
var provider = $scope.applicationState.endpoint.mode.provider;
$q.all({
version: SystemService.version(),
info: SystemService.info(),
nodes: provider !== 'DOCKER_SWARM_MODE' || NodeService.nodes()
})
.then(function success(data) {
$scope.docker = data.version;
$scope.info = data.info;
if (provider === 'DOCKER_SWARM_MODE') {
var nodes = data.nodes;
processTotalCPUAndMemory(nodes);
$scope.nodes = nodes;
} else {
extractSwarmInfo(data.info);
}
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to retrieve cluster details');
})
.finally(function final() {
$('#loadingViewSpinner').hide();
});
}
initView();
}]);