mirror of https://github.com/portainer/portainer
feat(networks): add UAC (#1196)
parent
774738110b
commit
a2b4cd8050
|
@ -78,6 +78,8 @@ func (handler *ResourceHandler) handlePostResources(w http.ResponseWriter, r *ht
|
|||
resourceControlType = portainer.ServiceResourceControl
|
||||
case "volume":
|
||||
resourceControlType = portainer.VolumeResourceControl
|
||||
case "network":
|
||||
resourceControlType = portainer.NetworkResourceControl
|
||||
default:
|
||||
httperror.WriteErrorResponse(w, portainer.ErrInvalidResourceControlType, http.StatusBadRequest, handler.Logger)
|
||||
return
|
||||
|
|
|
@ -82,6 +82,30 @@ func decorateServiceList(serviceData []interface{}, resourceControls []portainer
|
|||
return decoratedServiceData, nil
|
||||
}
|
||||
|
||||
// decorateNetworkList loops through all networks and will decorate any network with an existing resource control.
|
||||
// Network object schema reference: https://docs.docker.com/engine/api/v1.28/#operation/NetworkList
|
||||
func decorateNetworkList(networkData []interface{}, resourceControls []portainer.ResourceControl) ([]interface{}, error) {
|
||||
decoratedNetworkData := make([]interface{}, 0)
|
||||
|
||||
for _, network := range networkData {
|
||||
|
||||
networkObject := network.(map[string]interface{})
|
||||
if networkObject[networkIdentifier] == nil {
|
||||
return nil, ErrDockerNetworkIdentifierNotFound
|
||||
}
|
||||
|
||||
networkID := networkObject[networkIdentifier].(string)
|
||||
resourceControl := getResourceControlByResourceID(networkID, resourceControls)
|
||||
if resourceControl != nil {
|
||||
networkObject = decorateObject(networkObject, resourceControl)
|
||||
}
|
||||
|
||||
decoratedNetworkData = append(decoratedNetworkData, networkObject)
|
||||
}
|
||||
|
||||
return decoratedNetworkData, nil
|
||||
}
|
||||
|
||||
func decorateObject(object map[string]interface{}, resourceControl *portainer.ResourceControl) map[string]interface{} {
|
||||
metadata := make(map[string]interface{})
|
||||
metadata["ResourceControl"] = resourceControl
|
||||
|
|
|
@ -110,3 +110,28 @@ func filterServiceList(serviceData []interface{}, resourceControls []portainer.R
|
|||
|
||||
return filteredServiceData, nil
|
||||
}
|
||||
|
||||
// filterNetworkList loops through all networks, filters networks without any resource control (public resources) or with
|
||||
// any resource control giving access to the user (these networks will be decorated).
|
||||
// Network object schema reference: https://docs.docker.com/engine/api/v1.28/#operation/NetworkList
|
||||
func filterNetworkList(networkData []interface{}, resourceControls []portainer.ResourceControl, userID portainer.UserID, userTeamIDs []portainer.TeamID) ([]interface{}, error) {
|
||||
filteredNetworkData := make([]interface{}, 0)
|
||||
|
||||
for _, network := range networkData {
|
||||
networkObject := network.(map[string]interface{})
|
||||
if networkObject[networkIdentifier] == nil {
|
||||
return nil, ErrDockerNetworkIdentifierNotFound
|
||||
}
|
||||
|
||||
networkID := networkObject[networkIdentifier].(string)
|
||||
resourceControl := getResourceControlByResourceID(networkID, resourceControls)
|
||||
if resourceControl == nil {
|
||||
filteredNetworkData = append(filteredNetworkData, networkObject)
|
||||
} else if resourceControl != nil && canUserAccessResource(userID, userTeamIDs, resourceControl) {
|
||||
networkObject = decorateObject(networkObject, resourceControl)
|
||||
filteredNetworkData = append(filteredNetworkData, networkObject)
|
||||
}
|
||||
}
|
||||
|
||||
return filteredNetworkData, nil
|
||||
}
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
package proxy
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/portainer/portainer"
|
||||
)
|
||||
|
||||
const (
|
||||
// ErrDockerNetworkIdentifierNotFound defines an error raised when Portainer is unable to find a network identifier
|
||||
ErrDockerNetworkIdentifierNotFound = portainer.Error("Docker network identifier not found")
|
||||
networkIdentifier = "Id"
|
||||
)
|
||||
|
||||
// networkListOperation extracts the response as a JSON object, loop through the networks array
|
||||
// decorate and/or filter the networks based on resource controls before rewriting the response
|
||||
func networkListOperation(request *http.Request, response *http.Response, executor *operationExecutor) error {
|
||||
var err error
|
||||
// NetworkList response is a JSON array
|
||||
// https://docs.docker.com/engine/api/v1.28/#operation/NetworkList
|
||||
responseArray, err := getResponseAsJSONArray(response)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if executor.operationContext.isAdmin {
|
||||
responseArray, err = decorateNetworkList(responseArray, executor.operationContext.resourceControls)
|
||||
} else {
|
||||
responseArray, err = filterNetworkList(responseArray, executor.operationContext.resourceControls,
|
||||
executor.operationContext.userID, executor.operationContext.userTeamIDs)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return rewriteResponse(response, responseArray, http.StatusOK)
|
||||
}
|
||||
|
||||
// networkInspectOperation extracts the response as a JSON object, verify that the user
|
||||
// has access to the network based on resource control and either rewrite an access denied response
|
||||
// or a decorated network.
|
||||
func networkInspectOperation(request *http.Request, response *http.Response, executor *operationExecutor) error {
|
||||
// NetworkInspect response is a JSON object
|
||||
// https://docs.docker.com/engine/api/v1.28/#operation/NetworkInspect
|
||||
responseObject, err := getResponseAsJSONOBject(response)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if responseObject[networkIdentifier] == nil {
|
||||
return ErrDockerNetworkIdentifierNotFound
|
||||
}
|
||||
networkID := responseObject[networkIdentifier].(string)
|
||||
|
||||
resourceControl := getResourceControlByResourceID(networkID, executor.operationContext.resourceControls)
|
||||
if resourceControl != nil {
|
||||
if executor.operationContext.isAdmin || canUserAccessResource(executor.operationContext.userID,
|
||||
executor.operationContext.userTeamIDs, resourceControl) {
|
||||
responseObject = decorateObject(responseObject, resourceControl)
|
||||
} else {
|
||||
return rewriteAccessDeniedResponse(response)
|
||||
}
|
||||
}
|
||||
|
||||
return rewriteResponse(response, responseObject, http.StatusOK)
|
||||
}
|
|
@ -53,17 +53,20 @@ func (p *proxyTransport) executeDockerRequest(request *http.Request) (*http.Resp
|
|||
func (p *proxyTransport) proxyDockerRequest(request *http.Request) (*http.Response, error) {
|
||||
path := request.URL.Path
|
||||
|
||||
if strings.HasPrefix(path, "/containers") {
|
||||
switch {
|
||||
case strings.HasPrefix(path, "/containers"):
|
||||
return p.proxyContainerRequest(request)
|
||||
} else if strings.HasPrefix(path, "/services") {
|
||||
case strings.HasPrefix(path, "/services"):
|
||||
return p.proxyServiceRequest(request)
|
||||
} else if strings.HasPrefix(path, "/volumes") {
|
||||
case strings.HasPrefix(path, "/volumes"):
|
||||
return p.proxyVolumeRequest(request)
|
||||
} else if strings.HasPrefix(path, "/swarm") {
|
||||
case strings.HasPrefix(path, "/networks"):
|
||||
return p.proxyNetworkRequest(request)
|
||||
case strings.HasPrefix(path, "/swarm"):
|
||||
return p.proxySwarmRequest(request)
|
||||
default:
|
||||
return p.executeDockerRequest(request)
|
||||
}
|
||||
|
||||
return p.executeDockerRequest(request)
|
||||
}
|
||||
|
||||
func (p *proxyTransport) proxyContainerRequest(request *http.Request) (*http.Response, error) {
|
||||
|
@ -145,6 +148,24 @@ func (p *proxyTransport) proxyVolumeRequest(request *http.Request) (*http.Respon
|
|||
}
|
||||
}
|
||||
|
||||
func (p *proxyTransport) proxyNetworkRequest(request *http.Request) (*http.Response, error) {
|
||||
switch requestPath := request.URL.Path; requestPath {
|
||||
case "/networks/create":
|
||||
return p.executeDockerRequest(request)
|
||||
|
||||
case "/networks":
|
||||
return p.rewriteOperation(request, networkListOperation)
|
||||
|
||||
default:
|
||||
// assume /networks/{name}
|
||||
if request.Method == http.MethodGet {
|
||||
return p.rewriteOperation(request, networkInspectOperation)
|
||||
}
|
||||
volumeID := path.Base(requestPath)
|
||||
return p.restrictedOperation(request, volumeID)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *proxyTransport) proxySwarmRequest(request *http.Request) (*http.Response, error) {
|
||||
return p.administratorOperation(request)
|
||||
}
|
||||
|
|
|
@ -399,4 +399,6 @@ const (
|
|||
ServiceResourceControl
|
||||
// VolumeResourceControl represents a resource control associated to a Docker volume
|
||||
VolumeResourceControl
|
||||
// NetworkResourceControl represents a resource control associated to a Docker network
|
||||
NetworkResourceControl
|
||||
)
|
||||
|
|
|
@ -1,12 +1,17 @@
|
|||
angular.module('createNetwork', [])
|
||||
.controller('CreateNetworkController', ['$q', '$scope', '$state', 'PluginService', 'Notifications', 'Network', 'LabelHelper',
|
||||
function ($q, $scope, $state, PluginService, Notifications, Network, LabelHelper) {
|
||||
.controller('CreateNetworkController', ['$q', '$scope', '$state', 'PluginService', 'Notifications', 'NetworkService', 'LabelHelper', 'Authentication', 'ResourceControlService', 'FormValidator',
|
||||
function ($q, $scope, $state, PluginService, Notifications, NetworkService, LabelHelper, Authentication, ResourceControlService, FormValidator) {
|
||||
|
||||
$scope.formValues = {
|
||||
DriverOptions: [],
|
||||
Subnet: '',
|
||||
Gateway: '',
|
||||
Labels: []
|
||||
Labels: [],
|
||||
AccessControlData: new AccessControlFormData()
|
||||
};
|
||||
|
||||
$scope.state = {
|
||||
formValidationError: ''
|
||||
};
|
||||
|
||||
$scope.availableNetworkDrivers = [];
|
||||
|
@ -40,23 +45,6 @@ function ($q, $scope, $state, PluginService, Notifications, Network, LabelHelper
|
|||
$scope.formValues.Labels.splice(index, 1);
|
||||
};
|
||||
|
||||
function createNetwork(config) {
|
||||
$('#createNetworkSpinner').show();
|
||||
Network.create(config, function (d) {
|
||||
if (d.message) {
|
||||
$('#createNetworkSpinner').hide();
|
||||
Notifications.error('Unable to create network', {}, d.message);
|
||||
} else {
|
||||
Notifications.success('Network created', d.Id);
|
||||
$('#createNetworkSpinner').hide();
|
||||
$state.go('networks', {}, {reload: true});
|
||||
}
|
||||
}, function (e) {
|
||||
$('#createNetworkSpinner').hide();
|
||||
Notifications.error('Failure', e, 'Unable to create network');
|
||||
});
|
||||
}
|
||||
|
||||
function prepareIPAMConfiguration(config) {
|
||||
if ($scope.formValues.Subnet) {
|
||||
var ipamConfig = {};
|
||||
|
@ -88,9 +76,47 @@ function ($q, $scope, $state, PluginService, Notifications, Network, LabelHelper
|
|||
return config;
|
||||
}
|
||||
|
||||
function validateForm(accessControlData, isAdmin) {
|
||||
$scope.state.formValidationError = '';
|
||||
var error = '';
|
||||
error = FormValidator.validateAccessControl(accessControlData, isAdmin);
|
||||
|
||||
if (error) {
|
||||
$scope.state.formValidationError = error;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
$scope.create = function () {
|
||||
var config = prepareConfiguration();
|
||||
createNetwork(config);
|
||||
$('#createResourceSpinner').show();
|
||||
|
||||
var networkConfiguration = prepareConfiguration();
|
||||
var accessControlData = $scope.formValues.AccessControlData;
|
||||
var userDetails = Authentication.getUserDetails();
|
||||
var isAdmin = userDetails.role === 1 ? true : false;
|
||||
|
||||
if (!validateForm(accessControlData, isAdmin)) {
|
||||
$('#createResourceSpinner').hide();
|
||||
return;
|
||||
}
|
||||
|
||||
NetworkService.create(networkConfiguration)
|
||||
.then(function success(data) {
|
||||
var networkIdentifier = data.Id;
|
||||
var userId = userDetails.ID;
|
||||
return ResourceControlService.applyResourceControl('network', networkIdentifier, userId, accessControlData, []);
|
||||
})
|
||||
.then(function success() {
|
||||
Notifications.success('Network successfully created');
|
||||
$state.go('networks', {}, {reload: true});
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'An error occured during network creation');
|
||||
})
|
||||
.finally(function final() {
|
||||
$('#createResourceSpinner').hide();
|
||||
});
|
||||
};
|
||||
|
||||
function initView() {
|
||||
|
|
|
@ -121,6 +121,9 @@
|
|||
</div>
|
||||
</div>
|
||||
<!-- !internal -->
|
||||
<!-- access-control -->
|
||||
<por-access-control-form form-data="formValues.AccessControlData" ng-if="applicationState.application.authentication"></por-access-control-form>
|
||||
<!-- !access-control -->
|
||||
<!-- actions -->
|
||||
<div class="col-sm-12 form-section-title">
|
||||
Actions
|
||||
|
@ -129,7 +132,8 @@
|
|||
<div class="col-sm-12">
|
||||
<button type="button" class="btn btn-primary btn-sm" ng-disabled="!config.Name" ng-click="create()">Create network</button>
|
||||
<a type="button" class="btn btn-default btn-sm" ui-sref="networks">Cancel</a>
|
||||
<i id="createNetworkSpinner" class="fa fa-cog fa-spin" style="margin-left: 5px; display: none;"></i>
|
||||
<i id="createResourceSpinner" class="fa fa-cog fa-spin" style="margin-left: 5px; display: none;"></i>
|
||||
<span class="text-danger" ng-if="state.formValidationError" style="margin-left: 5px;">{{ state.formValidationError }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !actions -->
|
||||
|
|
|
@ -48,6 +48,15 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- access-control-panel -->
|
||||
<por-access-control-panel
|
||||
ng-if="network && applicationState.application.authentication"
|
||||
resource-id="network.Id"
|
||||
resource-control="network.ResourceControl"
|
||||
resource-type="'network'">
|
||||
</por-access-control-panel>
|
||||
<!-- !access-control-panel -->
|
||||
|
||||
<div class="row" ng-if="!(network.Options | emptyobject)">
|
||||
<div class="col-lg-12 col-md-12 col-xs-12">
|
||||
<rd-widget>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
angular.module('network', [])
|
||||
.controller('NetworkController', ['$scope', '$state', '$stateParams', '$filter', 'Network', 'Container', 'ContainerHelper', 'Notifications',
|
||||
function ($scope, $state, $stateParams, $filter, Network, Container, ContainerHelper, Notifications) {
|
||||
.controller('NetworkController', ['$scope', '$state', '$stateParams', '$filter', 'Network', 'NetworkService', 'Container', 'ContainerHelper', 'Notifications',
|
||||
function ($scope, $state, $stateParams, $filter, Network, NetworkService, Container, ContainerHelper, Notifications) {
|
||||
|
||||
$scope.removeNetwork = function removeNetwork(networkId) {
|
||||
$('#loadingViewSpinner').show();
|
||||
|
@ -82,7 +82,7 @@ function ($scope, $state, $stateParams, $filter, Network, Container, ContainerHe
|
|||
|
||||
function initView() {
|
||||
$('#loadingViewSpinner').show();
|
||||
Network.get({id: $stateParams.id}).$promise
|
||||
NetworkService.network($stateParams.id)
|
||||
.then(function success(data) {
|
||||
$scope.network = data;
|
||||
var endpointProvider = $scope.applicationState.endpoint.mode.provider;
|
||||
|
|
|
@ -8,46 +8,6 @@
|
|||
<rd-header-content>Networks</rd-header-content>
|
||||
</rd-header>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12 col-md-12 col-xs-12">
|
||||
<rd-widget>
|
||||
<rd-widget-header icon="fa-plus" title="Add a network">
|
||||
</rd-widget-header>
|
||||
<rd-widget-body>
|
||||
<form class="form-horizontal">
|
||||
<!-- name-input -->
|
||||
<div class="form-group">
|
||||
<label for="network_name" class="col-sm-1 control-label text-left">Name</label>
|
||||
<div class="col-sm-11">
|
||||
<input type="text" class="form-control" ng-model="config.Name" id="network_name" placeholder="e.g. myNetwork">
|
||||
</div>
|
||||
</div>
|
||||
<!-- !name-input -->
|
||||
<!-- tag-note -->
|
||||
<div class="form-group" ng-if="applicationState.endpoint.mode.provider === 'DOCKER_SWARM' || applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE'">
|
||||
<div class="col-sm-12">
|
||||
<span class="small text-muted">Note: The network will be created using the overlay driver and will allow containers to communicate across the hosts of your cluster.</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" ng-if="applicationState.endpoint.mode.provider === 'DOCKER_STANDALONE' || applicationState.endpoint.mode.provider === 'VMWARE_VIC'">
|
||||
<div class="col-sm-12">
|
||||
<span class="small text-muted">Note: The network will be created using the bridge driver.</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !tag-note -->
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<button type="button" class="btn btn-primary btn-sm" ng-disabled="!config.Name" ng-click="createNetwork()">Create</button>
|
||||
<button type="button" class="btn btn-primary btn-sm" ui-sref="actions.create.network">Advanced settings...</button>
|
||||
<i id="createNetworkSpinner" 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-lg-12 col-md-12 col-xs-12">
|
||||
<rd-widget>
|
||||
|
@ -66,6 +26,7 @@
|
|||
<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.network"><i class="fa fa-plus space-right" aria-hidden="true"></i>Add network</a>
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
<input type="text" id="filter" ng-model="state.filter" placeholder="Filter..." class="form-control input-sm" />
|
||||
|
@ -80,54 +41,61 @@
|
|||
<input type="checkbox" ng-model="allSelected" ng-change="selectItems(allSelected)" />
|
||||
</th>
|
||||
<th>
|
||||
<a ui-sref="networks" ng-click="order('Name')">
|
||||
<a 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="networks" ng-click="order('Id')">
|
||||
<a ng-click="order('Id')">
|
||||
Id
|
||||
<span ng-show="sortType == 'Id' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'Id' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ui-sref="networks" ng-click="order('Scope')">
|
||||
<a ng-click="order('Scope')">
|
||||
Scope
|
||||
<span ng-show="sortType == 'Scope' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'Scope' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ui-sref="networks" ng-click="order('Driver')">
|
||||
<a ng-click="order('Driver')">
|
||||
Driver
|
||||
<span ng-show="sortType == 'Driver' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'Driver' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ui-sref="networks" ng-click="order('IPAM.Driver')">
|
||||
<a ng-click="order('IPAM.Driver')">
|
||||
IPAM Driver
|
||||
<span ng-show="sortType == 'IPAM.Driver' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'IPAM.Driver' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ui-sref="networks" ng-click="order('IPAM.Config[0].Subnet')">
|
||||
<a ng-click="order('IPAM.Config[0].Subnet')">
|
||||
IPAM Subnet
|
||||
<span ng-show="sortType == 'IPAM.Config[0].Subnet' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'IPAM.Config[0].Subnet' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ui-sref="networks" ng-click="order('IPAM.Config[0].Gateway')">
|
||||
<a ng-click="order('IPAM.Config[0].Gateway')">
|
||||
IPAM Gateway
|
||||
<span ng-show="sortType == 'IPAM.Config[0].Gateway' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'IPAM.Config[0].Gateway' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th ng-if="applicationState.application.authentication">
|
||||
<a ng-click="order('ResourceControl.Ownership')">
|
||||
Ownership
|
||||
<span ng-show="sortType == 'ResourceControl.Ownership' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'ResourceControl.Ownership' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
@ -140,12 +108,18 @@
|
|||
<td>{{ network.IPAM.Driver }}</td>
|
||||
<td>{{ network.IPAM.Config[0].Subnet ? network.IPAM.Config[0].Subnet : '-' }}</td>
|
||||
<td>{{ network.IPAM.Config[0].Gateway ? network.IPAM.Config[0].Gateway : '-' }}</td>
|
||||
<td ng-if="applicationState.application.authentication">
|
||||
<span>
|
||||
<i ng-class="network.ResourceControl.Ownership | ownershipicon" aria-hidden="true"></i>
|
||||
{{ network.ResourceControl.Ownership ? network.ResourceControl.Ownership : network.ResourceControl.Ownership = 'public' }}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="!networks">
|
||||
<td colspan="8" class="text-center text-muted">Loading...</td>
|
||||
<td colspan="9" class="text-center text-muted">Loading...</td>
|
||||
</tr>
|
||||
<tr ng-if="networks.length == 0">
|
||||
<td colspan="8" class="text-center text-muted">No networks available.</td>
|
||||
<td colspan="9" class="text-center text-muted">No networks available.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
|
@ -1,51 +1,17 @@
|
|||
angular.module('networks', [])
|
||||
.controller('NetworksController', ['$scope', '$state', 'Network', 'Notifications', 'Pagination',
|
||||
function ($scope, $state, Network, Notifications, Pagination) {
|
||||
.controller('NetworksController', ['$scope', '$state', 'Network', 'NetworkService', 'Notifications', 'Pagination',
|
||||
function ($scope, $state, Network, NetworkService, Notifications, Pagination) {
|
||||
$scope.state = {};
|
||||
$scope.state.pagination_count = Pagination.getPaginationCount('networks');
|
||||
$scope.state.selectedItemCount = 0;
|
||||
$scope.state.advancedSettings = false;
|
||||
$scope.sortType = 'Name';
|
||||
$scope.sortReverse = false;
|
||||
$scope.config = {
|
||||
Name: ''
|
||||
};
|
||||
|
||||
$scope.changePaginationCount = function() {
|
||||
Pagination.setPaginationCount('networks', $scope.state.pagination_count);
|
||||
};
|
||||
|
||||
function prepareNetworkConfiguration() {
|
||||
var config = angular.copy($scope.config);
|
||||
if ($scope.applicationState.endpoint.mode.provider === 'DOCKER_SWARM' || $scope.applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE') {
|
||||
config.Driver = 'overlay';
|
||||
// Force IPAM Driver to 'default', should not be required.
|
||||
// See: https://github.com/docker/docker/issues/25735
|
||||
config.IPAM = {
|
||||
Driver: 'default'
|
||||
};
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
$scope.createNetwork = function() {
|
||||
$('#createNetworkSpinner').show();
|
||||
var config = prepareNetworkConfiguration();
|
||||
Network.create(config, function (d) {
|
||||
if (d.message) {
|
||||
$('#createNetworkSpinner').hide();
|
||||
Notifications.error('Unable to create network', {}, d.message);
|
||||
} else {
|
||||
Notifications.success('Network created', d.Id);
|
||||
$('#createNetworkSpinner').hide();
|
||||
$state.reload();
|
||||
}
|
||||
}, function (e) {
|
||||
$('#createNetworkSpinner').hide();
|
||||
Notifications.error('Failure', e, 'Unable to create network');
|
||||
});
|
||||
};
|
||||
|
||||
$scope.order = function(sortType) {
|
||||
$scope.sortReverse = ($scope.sortType === sortType) ? !$scope.sortReverse : false;
|
||||
$scope.sortType = sortType;
|
||||
|
@ -99,13 +65,17 @@ function ($scope, $state, Network, Notifications, Pagination) {
|
|||
|
||||
function initView() {
|
||||
$('#loadNetworksSpinner').show();
|
||||
Network.query({}, function (d) {
|
||||
$scope.networks = d;
|
||||
$('#loadNetworksSpinner').hide();
|
||||
}, function (e) {
|
||||
$('#loadNetworksSpinner').hide();
|
||||
Notifications.error('Failure', e, 'Unable to retrieve networks');
|
||||
|
||||
NetworkService.networks(true, true, true, true)
|
||||
.then(function success(data) {
|
||||
$scope.networks = data;
|
||||
})
|
||||
.catch(function error(err) {
|
||||
$scope.networks = [];
|
||||
Notifications.error('Failure', err, 'Unable to retrieve networks');
|
||||
})
|
||||
.finally(function final() {
|
||||
$('#loadNetworksSpinner').hide();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
function NetworkViewModel(data) {
|
||||
this.Id = data.Id;
|
||||
this.Name = data.Name;
|
||||
this.Scope = data.Scope;
|
||||
this.Driver = data.Driver;
|
||||
this.Attachable = data.Attachable;
|
||||
this.IPAM = data.IPAM;
|
||||
this.Containers = data.Containers;
|
||||
this.Options = data.Options;
|
||||
|
||||
if (data.Portainer) {
|
||||
if (data.Portainer.ResourceControl) {
|
||||
this.ResourceControl = new ResourceControlViewModel(data.Portainer.ResourceControl);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,6 +3,35 @@ angular.module('portainer.services')
|
|||
'use strict';
|
||||
var service = {};
|
||||
|
||||
service.create = function(networkConfiguration) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
Network.create(networkConfiguration).$promise
|
||||
.then(function success(data) {
|
||||
deferred.resolve(data);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to create network', err: err });
|
||||
});
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
service.network = function(id) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
Network.get({id: id}).$promise
|
||||
.then(function success(data) {
|
||||
var network = new NetworkViewModel(data);
|
||||
deferred.resolve(network);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({msg: 'Unable to retrieve network details', err: err});
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
|
||||
service.networks = function(localNetworks, swarmNetworks, swarmAttachableNetworks, globalNetworks) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
|
@ -23,6 +52,8 @@ angular.module('portainer.services')
|
|||
if (globalNetworks && network.Scope === 'global') {
|
||||
return network;
|
||||
}
|
||||
}).map(function (item) {
|
||||
return new NetworkViewModel(item);
|
||||
});
|
||||
|
||||
deferred.resolve(filteredNetworks);
|
||||
|
|
|
@ -13,6 +13,8 @@ angular.module('portainer.services')
|
|||
msg = e.data.message;
|
||||
} else if (e.message) {
|
||||
msg = e.message;
|
||||
} else if (e.err && e.err.data && e.err.data.message) {
|
||||
msg = e.err.data.message;
|
||||
} else if (e.data && e.data.length > 0 && e.data[0].message) {
|
||||
msg = e.data[0].message;
|
||||
} else if (e.err && e.err.data && e.err.data.length > 0 && e.err.data[0].message) {
|
||||
|
|
Loading…
Reference in New Issue