mirror of
https://github.com/portainer/portainer.git
synced 2025-07-25 08:19:40 +02:00
feat(global): multi endpoint management (#407)
This commit is contained in:
parent
a08ea134fc
commit
d54d30a7be
47 changed files with 1837 additions and 161 deletions
175
app/components/endpoints/endpoints.html
Normal file
175
app/components/endpoints/endpoints.html
Normal file
|
@ -0,0 +1,175 @@
|
|||
<rd-header>
|
||||
<rd-header-title title="Endpoints">
|
||||
<a data-toggle="tooltip" title="Refresh" ui-sref="endpoints" ui-sref-opts="{reload: true}">
|
||||
<i class="fa fa-refresh" aria-hidden="true"></i>
|
||||
</a>
|
||||
</rd-header-title>
|
||||
<rd-header-content>Endpoint management</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 new endpoint">
|
||||
</rd-widget-header>
|
||||
<rd-widget-body>
|
||||
<form class="form-horizontal">
|
||||
<!-- name-input -->
|
||||
<div class="form-group">
|
||||
<label for="container_name" class="col-sm-2 control-label text-left">Name</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" class="form-control" id="container_name" ng-model="formValues.Name" placeholder="e.g. docker-prod01">
|
||||
</div>
|
||||
</div>
|
||||
<!-- !name-input -->
|
||||
<!-- endpoint-url-input -->
|
||||
<div class="form-group">
|
||||
<label for="endpoint_url" class="col-sm-2 control-label text-left">Endpoint URL</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" class="form-control" id="endpoint_url" ng-model="formValues.URL" placeholder="e.g. 10.0.0.10:2375 or mydocker.mydomain.com:2375">
|
||||
</div>
|
||||
</div>
|
||||
<!-- !endpoint-url-input -->
|
||||
<!-- tls-checkbox -->
|
||||
<div class="form-group">
|
||||
<label for="tls" class="col-sm-2 control-label text-left">TLS</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="checkbox" name="tls" ng-model="formValues.TLS">
|
||||
</div>
|
||||
</div>
|
||||
<!-- !tls-checkbox -->
|
||||
<!-- tls-certs -->
|
||||
<div ng-if="formValues.TLS">
|
||||
<!-- ca-input -->
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label text-left">TLS CA certificate</label>
|
||||
<div class="col-sm-10">
|
||||
<button class="btn btn-sm btn-primary" ngf-select ng-model="formValues.TLSCACert">Select file</button>
|
||||
<span style="margin-left: 5px;">
|
||||
{{ formValues.TLSCACert.name }}
|
||||
<i class="fa fa-times red-icon" ng-if="!formValues.TLSCACert" aria-hidden="true"></i>
|
||||
<i class="fa fa-circle-o-notch fa-spin" ng-if="state.uploadInProgress"></i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !ca-input -->
|
||||
<!-- cert-input -->
|
||||
<div class="form-group">
|
||||
<label for="tls_cert" class="col-sm-2 control-label text-left">TLS certificate</label>
|
||||
<div class="col-sm-10">
|
||||
<button class="btn btn-sm btn-primary" ngf-select ng-model="formValues.TLSCert">Select file</button>
|
||||
<span style="margin-left: 5px;">
|
||||
{{ formValues.TLSCert.name }}
|
||||
<i class="fa fa-times red-icon" ng-if="!formValues.TLSCert" aria-hidden="true"></i>
|
||||
<i class="fa fa-circle-o-notch fa-spin" ng-if="state.uploadInProgress"></i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !cert-input -->
|
||||
<!-- key-input -->
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label text-left">TLS key</label>
|
||||
<div class="col-sm-10">
|
||||
<button class="btn btn-sm btn-primary" ngf-select ng-model="formValues.TLSKey">Select file</button>
|
||||
<span style="margin-left: 5px;">
|
||||
{{ formValues.TLSKey.name }}
|
||||
<i class="fa fa-times red-icon" ng-if="!formValues.TLSKey" aria-hidden="true"></i>
|
||||
<i class="fa fa-circle-o-notch fa-spin" ng-if="state.uploadInProgress"></i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !key-input -->
|
||||
</div>
|
||||
<!-- !tls-certs -->
|
||||
<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.TLS && (!formValues.TLSCACert || !formValues.TLSCert || !formValues.TLSKey))" ng-click="addEndpoint()">Add endpoint</button>
|
||||
<i id="createEndpointSpinner" class="fa fa-cog fa-spin" style="margin-left: 5px; display: none;"></i>
|
||||
<span class="text-danger" ng-if="state.error" style="margin: 5px;">
|
||||
<i class="fa fa-exclamation-circle" aria-hidden="true"></i> {{ state.error }}
|
||||
</span>
|
||||
</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>
|
||||
<rd-widget-header icon="fa-plug" title="Endpoints">
|
||||
<div class="pull-right">
|
||||
<i id="loadEndpointsSpinner" class="fa fa-cog fa-2x fa-spin" style="margin-top: 5px;"></i>
|
||||
</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>
|
||||
</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></th>
|
||||
<th>
|
||||
<a ui-sref="endpoints" 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="endpoints" 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>
|
||||
<a ui-sref="endpoints" ng-click="order('TLS')">
|
||||
TLS
|
||||
<span ng-show="sortType == 'TLS' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'TLS' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr dir-paginate="endpoint in (state.filteredEndpoints = (endpoints | filter:state.filter | orderBy:sortType:sortReverse | itemsPerPage: pagination_count))">
|
||||
<td><input type="checkbox" ng-model="endpoint.Checked" ng-change="selectItem(endpoint)" /></td>
|
||||
<td><i class="fa fa-star" aria-hidden="true" ng-if="endpoint.Id === activeEndpoint.Id"></i> {{ endpoint.Name }}</td>
|
||||
<td>{{ endpoint.URL | stripprotocol }}</td>
|
||||
<td><i class="fa fa-shield" aria-hidden="true" ng-if="endpoint.TLS"></i></td>
|
||||
<td>
|
||||
<span ng-if="endpoint.Id !== activeEndpoint.Id">
|
||||
<a ui-sref="endpoint({id: endpoint.Id})"><i class="fa fa-pencil-square-o" aria-hidden="true"></i> Edit</a>
|
||||
</span>
|
||||
<span class="small text-muted" ng-if="endpoint.Id === activeEndpoint.Id">
|
||||
<i class="fa fa-lock" aria-hidden="true"></i> You cannot edit the active endpoint
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="!endpoints">
|
||||
<td colspan="5" class="text-center text-muted">Loading...</td>
|
||||
</tr>
|
||||
<tr ng-if="endpoints.length == 0">
|
||||
<td colspan="5" class="text-center text-muted">No endpoints available.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div ng-if="endpoints" class="pull-left pagination-controls">
|
||||
<dir-pagination-controls></dir-pagination-controls>
|
||||
</div>
|
||||
</div>
|
||||
</rd-widget-body>
|
||||
<rd-widget>
|
||||
</div>
|
||||
</div>
|
100
app/components/endpoints/endpointsController.js
Normal file
100
app/components/endpoints/endpointsController.js
Normal file
|
@ -0,0 +1,100 @@
|
|||
angular.module('endpoints', [])
|
||||
.controller('EndpointsController', ['$scope', '$state', 'EndpointService', 'Settings', 'Messages',
|
||||
function ($scope, $state, EndpointService, Settings, Messages) {
|
||||
$scope.state = {
|
||||
error: '',
|
||||
uploadInProgress: false,
|
||||
selectedItemCount: 0
|
||||
};
|
||||
$scope.sortType = 'Name';
|
||||
$scope.sortReverse = true;
|
||||
$scope.pagination_count = Settings.pagination_count;
|
||||
|
||||
$scope.formValues = {
|
||||
Name: '',
|
||||
URL: '',
|
||||
TLS: false,
|
||||
TLSCACert: null,
|
||||
TLSCert: null,
|
||||
TLSKey: null
|
||||
};
|
||||
|
||||
$scope.order = function(sortType) {
|
||||
$scope.sortReverse = ($scope.sortType === sortType) ? !$scope.sortReverse : false;
|
||||
$scope.sortType = sortType;
|
||||
};
|
||||
|
||||
$scope.selectItem = function (item) {
|
||||
if (item.Checked) {
|
||||
$scope.state.selectedItemCount++;
|
||||
} else {
|
||||
$scope.state.selectedItemCount--;
|
||||
}
|
||||
};
|
||||
|
||||
$scope.addEndpoint = function() {
|
||||
$scope.state.error = '';
|
||||
var name = $scope.formValues.Name;
|
||||
var URL = $scope.formValues.URL;
|
||||
var TLS = $scope.formValues.TLS;
|
||||
var TLSCAFile = $scope.formValues.TLSCACert;
|
||||
var TLSCertFile = $scope.formValues.TLSCert;
|
||||
var TLSKeyFile = $scope.formValues.TLSKey;
|
||||
EndpointService.createRemoteEndpoint(name, URL, TLS, TLSCAFile, TLSCertFile, TLSKeyFile, false).then(function success(data) {
|
||||
Messages.send("Endpoint created", name);
|
||||
$state.reload();
|
||||
}, function error(err) {
|
||||
$scope.state.uploadInProgress = false;
|
||||
$scope.state.error = err.msg;
|
||||
}, function update(evt) {
|
||||
if (evt.upload) {
|
||||
$scope.state.uploadInProgress = evt.upload;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$scope.removeAction = function () {
|
||||
$('#loadEndpointsSpinner').show();
|
||||
var counter = 0;
|
||||
var complete = function () {
|
||||
counter = counter - 1;
|
||||
if (counter === 0) {
|
||||
$('#loadEndpointsSpinner').hide();
|
||||
}
|
||||
};
|
||||
angular.forEach($scope.endpoints, function (endpoint) {
|
||||
if (endpoint.Checked) {
|
||||
counter = counter + 1;
|
||||
EndpointService.deleteEndpoint(endpoint.Id).then(function success(data) {
|
||||
Messages.send("Endpoint deleted", endpoint.Name);
|
||||
var index = $scope.endpoints.indexOf(endpoint);
|
||||
$scope.endpoints.splice(index, 1);
|
||||
complete();
|
||||
}, function error(err) {
|
||||
Messages.error("Failure", err, 'Unable to remove endpoint');
|
||||
complete();
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
function fetchEndpoints() {
|
||||
$('#loadEndpointsSpinner').show();
|
||||
EndpointService.endpoints().then(function success(data) {
|
||||
$scope.endpoints = data;
|
||||
EndpointService.getActive().then(function success(data) {
|
||||
$scope.activeEndpoint = data;
|
||||
$('#loadEndpointsSpinner').hide();
|
||||
}, function error(err) {
|
||||
$('#loadEndpointsSpinner').hide();
|
||||
Messages.error("Failure", err, "Unable to retrieve active endpoint");
|
||||
});
|
||||
}, function error(err) {
|
||||
$('#loadEndpointsSpinner').hide();
|
||||
Messages.error("Failure", err, "Unable to retrieve endpoints");
|
||||
$scope.endpoints = [];
|
||||
});
|
||||
}
|
||||
|
||||
fetchEndpoints();
|
||||
}]);
|
Loading…
Add table
Add a link
Reference in a new issue