feat(server): remove external endpoint feature (#3837)

* fix(prettier): auto format html files (#3836)

* refactor(main): remove reference to external endpoints

* refactor(cli): remove parsing of external endpoints param

* refactor(portainer): remove types for external endpoints

* refactor(endpoints): remove warning for external endpoints

* refactor(endpoints): remove endpoint management setting

* refactor(endpoints): remove ref to endpoint management

* fix(main): remove endpoint management
2.0
Chaim Lev-Ari 2020-05-18 11:29:37 +03:00 committed by Anthony Lapenna
parent d9665bc939
commit c074a714cf
18 changed files with 20 additions and 382 deletions

View File

@ -20,9 +20,7 @@ const (
errInvalidEndpointProtocol = portainer.Error("Invalid endpoint protocol: Portainer only supports unix://, npipe:// or tcp://")
errSocketOrNamedPipeNotFound = portainer.Error("Unable to locate Unix socket or named pipe")
errEndpointsFileNotFound = portainer.Error("Unable to locate external endpoints file")
errInvalidSyncInterval = portainer.Error("Invalid synchronization interval")
errInvalidSnapshotInterval = portainer.Error("Invalid snapshot interval")
errEndpointExcludeExternal = portainer.Error("Cannot use the -H flag mutually with --external-endpoints")
errNoAuthExcludeAdminPassword = portainer.Error("Cannot use --no-auth with --admin-password or --admin-password-file")
errAdminPassExcludeAdminPassFile = portainer.Error("Cannot use --admin-password with --admin-password-file")
)
@ -38,7 +36,6 @@ func (*Service) ParseFlags(version string) (*portainer.CLIFlags, error) {
Assets: kingpin.Flag("assets", "Path to the assets").Default(defaultAssetsDirectory).Short('a').String(),
Data: kingpin.Flag("data", "Path to the folder where the data is stored").Default(defaultDataDirectory).Short('d').String(),
EndpointURL: kingpin.Flag("host", "Endpoint URL").Short('H').String(),
ExternalEndpoints: kingpin.Flag("external-endpoints", "Path to a file defining available endpoints (deprecated)").String(),
NoAuth: kingpin.Flag("no-auth", "Disable authentication (deprecated)").Default(defaultNoAuth).Bool(),
NoAnalytics: kingpin.Flag("no-analytics", "Disable Analytics in app").Default(defaultNoAnalytics).Bool(),
TLS: kingpin.Flag("tlsverify", "TLS support").Default(defaultTLS).Bool(),
@ -49,7 +46,6 @@ func (*Service) ParseFlags(version string) (*portainer.CLIFlags, error) {
SSL: kingpin.Flag("ssl", "Secure Portainer instance using SSL").Default(defaultSSL).Bool(),
SSLCert: kingpin.Flag("sslcert", "Path to the SSL certificate used to secure the Portainer instance").Default(defaultSSLCertPath).String(),
SSLKey: kingpin.Flag("sslkey", "Path to the SSL key used to secure the Portainer instance").Default(defaultSSLKeyPath).String(),
SyncInterval: kingpin.Flag("sync-interval", "Duration between each synchronization via the external endpoints source (deprecated)").Default(defaultSyncInterval).String(),
SnapshotInterval: kingpin.Flag("snapshot-interval", "Duration between each endpoint snapshot job").Default(defaultSnapshotInterval).String(),
AdminPassword: kingpin.Flag("admin-password", "Hashed admin password").String(),
AdminPasswordFile: kingpin.Flag("admin-password-file", "Path to the file containing the password for the admin user").String(),
@ -76,25 +72,11 @@ func (*Service) ValidateFlags(flags *portainer.CLIFlags) error {
displayDeprecationWarnings(flags)
if *flags.EndpointURL != "" && *flags.ExternalEndpoints != "" {
return errEndpointExcludeExternal
}
err := validateEndpointURL(*flags.EndpointURL)
if err != nil {
return err
}
err = validateExternalEndpoints(*flags.ExternalEndpoints)
if err != nil {
return err
}
err = validateSyncInterval(*flags.SyncInterval)
if err != nil {
return err
}
err = validateSnapshotInterval(*flags.SnapshotInterval)
if err != nil {
return err
@ -112,14 +94,6 @@ func (*Service) ValidateFlags(flags *portainer.CLIFlags) error {
}
func displayDeprecationWarnings(flags *portainer.CLIFlags) {
if *flags.ExternalEndpoints != "" {
log.Println("Warning: the --external-endpoint flag is deprecated and will likely be removed in a future version of Portainer.")
}
if *flags.SyncInterval != defaultSyncInterval {
log.Println("Warning: the --sync-interval flag is deprecated and will likely be removed in a future version of Portainer.")
}
if *flags.NoAuth {
log.Println("Warning: the --no-auth flag is deprecated and will likely be removed in a future version of Portainer.")
}
@ -145,28 +119,6 @@ func validateEndpointURL(endpointURL string) error {
return nil
}
func validateExternalEndpoints(externalEndpoints string) error {
if externalEndpoints != "" {
if _, err := os.Stat(externalEndpoints); err != nil {
if os.IsNotExist(err) {
return errEndpointsFileNotFound
}
return err
}
}
return nil
}
func validateSyncInterval(syncInterval string) error {
if syncInterval != defaultSyncInterval {
_, err := time.ParseDuration(syncInterval)
if err != nil {
return errInvalidSyncInterval
}
}
return nil
}
func validateSnapshotInterval(snapshotInterval string) error {
if snapshotInterval != defaultSnapshotInterval {
_, err := time.ParseDuration(snapshotInterval)

View File

@ -18,6 +18,5 @@ const (
defaultSSL = "false"
defaultSSLCertPath = "/certs/portainer.crt"
defaultSSLKeyPath = "/certs/portainer.key"
defaultSyncInterval = "60s"
defaultSnapshotInterval = "5m"
)

View File

@ -16,6 +16,5 @@ const (
defaultSSL = "false"
defaultSSLCertPath = "C:\\certs\\portainer.crt"
defaultSSLKeyPath = "C:\\certs\\portainer.key"
defaultSyncInterval = "60s"
defaultSnapshotInterval = "5m"
)

View File

@ -157,45 +157,6 @@ func loadSnapshotSystemSchedule(jobScheduler portainer.JobScheduler, snapshotter
return nil
}
func loadEndpointSyncSystemSchedule(jobScheduler portainer.JobScheduler, scheduleService portainer.ScheduleService, endpointService portainer.EndpointService, flags *portainer.CLIFlags) error {
if *flags.ExternalEndpoints == "" {
return nil
}
log.Println("Using external endpoint definition. Endpoint management via the API will be disabled.")
schedules, err := scheduleService.SchedulesByJobType(portainer.EndpointSyncJobType)
if err != nil {
return err
}
if len(schedules) != 0 {
return nil
}
endpointSyncJob := &portainer.EndpointSyncJob{}
endpointSyncSchedule := &portainer.Schedule{
ID: portainer.ScheduleID(scheduleService.GetNextIdentifier()),
Name: "system_endpointsync",
CronExpression: "@every " + *flags.SyncInterval,
Recurring: true,
JobType: portainer.EndpointSyncJobType,
EndpointSyncJob: endpointSyncJob,
Created: time.Now().Unix(),
}
endpointSyncJobContext := cron.NewEndpointSyncJobContext(endpointService, *flags.ExternalEndpoints)
endpointSyncJobRunner := cron.NewEndpointSyncJobRunner(endpointSyncSchedule, endpointSyncJobContext)
err = jobScheduler.ScheduleJob(endpointSyncJobRunner)
if err != nil {
return err
}
return scheduleService.CreateSchedule(endpointSyncSchedule)
}
func loadSchedulesFromDatabase(jobScheduler portainer.JobScheduler, jobService portainer.JobService, scheduleService portainer.ScheduleService, endpointService portainer.EndpointService, fileService portainer.FileService, reverseTunnelService portainer.ReverseTunnelService) error {
schedules, err := scheduleService.Schedules()
if err != nil {
@ -225,12 +186,11 @@ func loadSchedulesFromDatabase(jobScheduler portainer.JobScheduler, jobService p
return nil
}
func initStatus(endpointManagement bool, flags *portainer.CLIFlags) *portainer.Status {
func initStatus(flags *portainer.CLIFlags) *portainer.Status {
return &portainer.Status{
Analytics: !*flags.NoAnalytics,
Authentication: !*flags.NoAuth,
EndpointManagement: endpointManagement,
Version: portainer.APIVersion,
Analytics: !*flags.NoAnalytics,
Authentication: !*flags.NoAuth,
Version: portainer.APIVersion,
}
}
@ -460,11 +420,6 @@ func main() {
snapshotter := initSnapshotter(clientFactory)
endpointManagement := true
if *flags.ExternalEndpoints != "" {
endpointManagement = false
}
swarmStackManager, err := initSwarmStackManager(*flags.Assets, *flags.Data, digitalSignatureService, fileService, reverseTunnelService)
if err != nil {
log.Fatal(err)
@ -486,11 +441,6 @@ func main() {
log.Fatal(err)
}
err = loadEndpointSyncSystemSchedule(jobScheduler, store.ScheduleService, store.EndpointService, flags)
if err != nil {
log.Fatal(err)
}
err = loadSnapshotSystemSchedule(jobScheduler, snapshotter, store.ScheduleService, store.EndpointService, store.SettingsService)
if err != nil {
log.Fatal(err)
@ -498,7 +448,7 @@ func main() {
jobScheduler.Start()
applicationStatus := initStatus(endpointManagement, flags)
applicationStatus := initStatus(flags)
err = initEndpoint(flags, store.EndpointService, snapshotter)
if err != nil {
@ -557,7 +507,6 @@ func main() {
BindAddress: *flags.Addr,
AssetsPath: *flags.Assets,
AuthDisabled: *flags.NoAuth,
EndpointManagement: endpointManagement,
RoleService: store.RoleService,
UserService: store.UserService,
TeamService: store.TeamService,

View File

@ -1,214 +0,0 @@
package cron
import (
"encoding/json"
"io/ioutil"
"log"
"strings"
"github.com/portainer/portainer/api"
)
// EndpointSyncJobRunner is used to run a EndpointSyncJob
type EndpointSyncJobRunner struct {
schedule *portainer.Schedule
context *EndpointSyncJobContext
}
// EndpointSyncJobContext represents the context of execution of a EndpointSyncJob
type EndpointSyncJobContext struct {
endpointService portainer.EndpointService
endpointFilePath string
}
// NewEndpointSyncJobContext returns a new context that can be used to execute a EndpointSyncJob
func NewEndpointSyncJobContext(endpointService portainer.EndpointService, endpointFilePath string) *EndpointSyncJobContext {
return &EndpointSyncJobContext{
endpointService: endpointService,
endpointFilePath: endpointFilePath,
}
}
// NewEndpointSyncJobRunner returns a new runner that can be scheduled
func NewEndpointSyncJobRunner(schedule *portainer.Schedule, context *EndpointSyncJobContext) *EndpointSyncJobRunner {
return &EndpointSyncJobRunner{
schedule: schedule,
context: context,
}
}
type synchronization struct {
endpointsToCreate []*portainer.Endpoint
endpointsToUpdate []*portainer.Endpoint
endpointsToDelete []*portainer.Endpoint
}
type fileEndpoint struct {
Name string `json:"Name"`
URL string `json:"URL"`
TLS bool `json:"TLS,omitempty"`
TLSSkipVerify bool `json:"TLSSkipVerify,omitempty"`
TLSCACert string `json:"TLSCACert,omitempty"`
TLSCert string `json:"TLSCert,omitempty"`
TLSKey string `json:"TLSKey,omitempty"`
}
// GetSchedule returns the schedule associated to the runner
func (runner *EndpointSyncJobRunner) GetSchedule() *portainer.Schedule {
return runner.schedule
}
// Run triggers the execution of the endpoint synchronization process.
func (runner *EndpointSyncJobRunner) Run() {
data, err := ioutil.ReadFile(runner.context.endpointFilePath)
if endpointSyncError(err) {
return
}
var fileEndpoints []fileEndpoint
err = json.Unmarshal(data, &fileEndpoints)
if endpointSyncError(err) {
return
}
if len(fileEndpoints) == 0 {
log.Println("background job error (endpoint synchronization). External endpoint source is empty")
return
}
storedEndpoints, err := runner.context.endpointService.Endpoints()
if endpointSyncError(err) {
return
}
convertedFileEndpoints := convertFileEndpoints(fileEndpoints)
sync := prepareSyncData(storedEndpoints, convertedFileEndpoints)
if sync.requireSync() {
err = runner.context.endpointService.Synchronize(sync.endpointsToCreate, sync.endpointsToUpdate, sync.endpointsToDelete)
if endpointSyncError(err) {
return
}
log.Printf("Endpoint synchronization ended. [created: %v] [updated: %v] [deleted: %v]", len(sync.endpointsToCreate), len(sync.endpointsToUpdate), len(sync.endpointsToDelete))
}
}
func endpointSyncError(err error) bool {
if err != nil {
log.Printf("background job error (endpoint synchronization). Unable to synchronize endpoints (err=%s)\n", err)
return true
}
return false
}
func isValidEndpoint(endpoint *portainer.Endpoint) bool {
if endpoint.Name != "" && endpoint.URL != "" {
if !strings.HasPrefix(endpoint.URL, "unix://") && !strings.HasPrefix(endpoint.URL, "tcp://") {
return false
}
return true
}
return false
}
func convertFileEndpoints(fileEndpoints []fileEndpoint) []portainer.Endpoint {
convertedEndpoints := make([]portainer.Endpoint, 0)
for _, e := range fileEndpoints {
endpoint := portainer.Endpoint{
Name: e.Name,
URL: e.URL,
TLSConfig: portainer.TLSConfiguration{},
}
if e.TLS {
endpoint.TLSConfig.TLS = true
endpoint.TLSConfig.TLSSkipVerify = e.TLSSkipVerify
endpoint.TLSConfig.TLSCACertPath = e.TLSCACert
endpoint.TLSConfig.TLSCertPath = e.TLSCert
endpoint.TLSConfig.TLSKeyPath = e.TLSKey
}
convertedEndpoints = append(convertedEndpoints, endpoint)
}
return convertedEndpoints
}
func endpointExists(endpoint *portainer.Endpoint, endpoints []portainer.Endpoint) int {
for idx, v := range endpoints {
if endpoint.Name == v.Name && isValidEndpoint(&v) {
return idx
}
}
return -1
}
func mergeEndpointIfRequired(original, updated *portainer.Endpoint) *portainer.Endpoint {
var endpoint *portainer.Endpoint
if original.URL != updated.URL || original.TLSConfig.TLS != updated.TLSConfig.TLS ||
(updated.TLSConfig.TLS && original.TLSConfig.TLSSkipVerify != updated.TLSConfig.TLSSkipVerify) ||
(updated.TLSConfig.TLS && original.TLSConfig.TLSCACertPath != updated.TLSConfig.TLSCACertPath) ||
(updated.TLSConfig.TLS && original.TLSConfig.TLSCertPath != updated.TLSConfig.TLSCertPath) ||
(updated.TLSConfig.TLS && original.TLSConfig.TLSKeyPath != updated.TLSConfig.TLSKeyPath) {
endpoint = original
endpoint.URL = updated.URL
if updated.TLSConfig.TLS {
endpoint.TLSConfig.TLS = true
endpoint.TLSConfig.TLSSkipVerify = updated.TLSConfig.TLSSkipVerify
endpoint.TLSConfig.TLSCACertPath = updated.TLSConfig.TLSCACertPath
endpoint.TLSConfig.TLSCertPath = updated.TLSConfig.TLSCertPath
endpoint.TLSConfig.TLSKeyPath = updated.TLSConfig.TLSKeyPath
} else {
endpoint.TLSConfig.TLS = false
endpoint.TLSConfig.TLSSkipVerify = false
endpoint.TLSConfig.TLSCACertPath = ""
endpoint.TLSConfig.TLSCertPath = ""
endpoint.TLSConfig.TLSKeyPath = ""
}
}
return endpoint
}
func (sync synchronization) requireSync() bool {
if len(sync.endpointsToCreate) != 0 || len(sync.endpointsToUpdate) != 0 || len(sync.endpointsToDelete) != 0 {
return true
}
return false
}
func prepareSyncData(storedEndpoints, fileEndpoints []portainer.Endpoint) *synchronization {
endpointsToCreate := make([]*portainer.Endpoint, 0)
endpointsToUpdate := make([]*portainer.Endpoint, 0)
endpointsToDelete := make([]*portainer.Endpoint, 0)
for idx := range storedEndpoints {
fidx := endpointExists(&storedEndpoints[idx], fileEndpoints)
if fidx != -1 {
endpoint := mergeEndpointIfRequired(&storedEndpoints[idx], &fileEndpoints[fidx])
if endpoint != nil {
log.Printf("New definition for a stored endpoint found in file, updating database. [name: %v] [url: %v]\n", endpoint.Name, endpoint.URL)
endpointsToUpdate = append(endpointsToUpdate, endpoint)
}
} else {
log.Printf("Stored endpoint not found in file (definition might be invalid), removing from database. [name: %v] [url: %v]", storedEndpoints[idx].Name, storedEndpoints[idx].URL)
endpointsToDelete = append(endpointsToDelete, &storedEndpoints[idx])
}
}
for idx, endpoint := range fileEndpoints {
if !isValidEndpoint(&endpoint) {
log.Printf("Invalid file endpoint definition, skipping. [name: %v] [url: %v]", endpoint.Name, endpoint.URL)
continue
}
sidx := endpointExists(&fileEndpoints[idx], storedEndpoints)
if sidx == -1 {
log.Printf("File endpoint not found in database, adding to database. [name: %v] [url: %v]", fileEndpoints[idx].Name, fileEndpoints[idx].URL)
endpointsToCreate = append(endpointsToCreate, &fileEndpoints[idx])
}
}
return &synchronization{
endpointsToCreate: endpointsToCreate,
endpointsToUpdate: endpointsToUpdate,
endpointsToDelete: endpointsToDelete,
}
}

View File

@ -131,10 +131,6 @@ func (payload *endpointCreatePayload) Validate(r *http.Request) error {
// POST request on /api/endpoints
func (handler *Handler) endpointCreate(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
if !handler.authorizeEndpointManagement {
return &httperror.HandlerError{http.StatusServiceUnavailable, "Endpoint management is disabled", ErrEndpointManagementDisabled}
}
payload := &endpointCreatePayload{}
err := payload.Validate(r)
if err != nil {

View File

@ -12,10 +12,6 @@ import (
// DELETE request on /api/endpoints/:id
func (handler *Handler) endpointDelete(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
if !handler.authorizeEndpointManagement {
return &httperror.HandlerError{http.StatusServiceUnavailable, "Endpoint management is disabled", ErrEndpointManagementDisabled}
}
endpointID, err := request.RetrieveNumericRouteVariableValue(r, "id")
if err != nil {
return &httperror.HandlerError{http.StatusBadRequest, "Invalid endpoint identifier route variable", err}

View File

@ -35,10 +35,6 @@ func (payload *endpointUpdatePayload) Validate(r *http.Request) error {
// PUT request on /api/endpoints/:id
func (handler *Handler) endpointUpdate(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
if !handler.authorizeEndpointManagement {
return &httperror.HandlerError{http.StatusServiceUnavailable, "Endpoint management is disabled", ErrEndpointManagementDisabled}
}
endpointID, err := request.RetrieveNumericRouteVariableValue(r, "id")
if err != nil {
return &httperror.HandlerError{http.StatusBadRequest, "Invalid endpoint identifier route variable", err}

View File

@ -11,12 +11,6 @@ import (
"github.com/gorilla/mux"
)
const (
// ErrEndpointManagementDisabled is an error raised when trying to access the endpoints management endpoints
// when the server has been started with the --external-endpoints flag
ErrEndpointManagementDisabled = portainer.Error("Endpoint management is disabled")
)
func hideFields(endpoint *portainer.Endpoint) {
endpoint.AzureCredentials = portainer.AzureCredentials{}
if len(endpoint.Snapshots) > 0 {
@ -27,7 +21,6 @@ func hideFields(endpoint *portainer.Endpoint) {
// Handler is the HTTP handler used to handle endpoint operations.
type Handler struct {
*mux.Router
authorizeEndpointManagement bool
requestBouncer *security.RequestBouncer
AuthorizationService *portainer.AuthorizationService
EdgeGroupService portainer.EdgeGroupService
@ -45,10 +38,9 @@ type Handler struct {
}
// NewHandler creates a handler to manage endpoint operations.
func NewHandler(bouncer *security.RequestBouncer, authorizeEndpointManagement bool) *Handler {
func NewHandler(bouncer *security.RequestBouncer) *Handler {
h := &Handler{
Router: mux.NewRouter(),
authorizeEndpointManagement: authorizeEndpointManagement,
requestBouncer: bouncer,
}

View File

@ -48,7 +48,6 @@ type Server struct {
BindAddress string
AssetsPath string
AuthDisabled bool
EndpointManagement bool
Status *portainer.Status
ReverseTunnelService portainer.ReverseTunnelService
ExtensionManager portainer.ExtensionManager
@ -170,7 +169,7 @@ func (server *Server) Start() error {
var edgeTemplatesHandler = edgetemplates.NewHandler(requestBouncer)
edgeTemplatesHandler.SettingsService = server.SettingsService
var endpointHandler = endpoints.NewHandler(requestBouncer, server.EndpointManagement)
var endpointHandler = endpoints.NewHandler(requestBouncer)
endpointHandler.AuthorizationService = authorizationService
endpointHandler.EdgeGroupService = server.EdgeGroupService
endpointHandler.EdgeStackService = server.EdgeStackService

View File

@ -42,7 +42,6 @@ type (
Assets *string
Data *string
EndpointURL *string
ExternalEndpoints *string
Labels *[]Pair
Logo *string
NoAuth *bool
@ -56,7 +55,6 @@ type (
SSL *bool
SSLCert *string
SSLKey *string
SyncInterval *string
SnapshotInterval *string
}
@ -209,6 +207,8 @@ type (
EndpointStatus int
// EndpointSyncJob represents a scheduled job that synchronize endpoints based on an external file
//
// Deprecated
EndpointSyncJob struct{}
// EndpointType represents the type of an endpoint
@ -402,7 +402,9 @@ type (
EdgeSchedule *EdgeSchedule
ScriptExecutionJob *ScriptExecutionJob
SnapshotJob *SnapshotJob
EndpointSyncJob *EndpointSyncJob
// Deprecated fields
EndpointSyncJob *EndpointSyncJob
}
// ScheduleID represents a schedule identifier.
@ -490,10 +492,9 @@ type (
// Status represents the application status
Status struct {
Authentication bool `json:"Authentication"`
EndpointManagement bool `json:"EndpointManagement"`
Analytics bool `json:"Analytics"`
Version string `json:"Version"`
Authentication bool `json:"Authentication"`
Analytics bool `json:"Analytics"`
Version string `json:"Version"`
}
// Tag represents a tag that can be associated to a resource
@ -1097,7 +1098,7 @@ const (
// SnapshotJobType is a system job used to create endpoint snapshots
SnapshotJobType
// EndpointSyncJobType is a system job used to synchronize endpoints from
// an external definition store
// an external definition store (Deprecated)
EndpointSyncJobType
)

View File

@ -3164,10 +3164,6 @@ definitions:
type: "boolean"
example: true
description: "Is authentication enabled"
EndpointManagement:
type: "boolean"
example: true
description: "Is endpoint management enabled"
Analytics:
type: "boolean"
example: true

View File

@ -4,7 +4,7 @@
<div class="toolBar">
<div class="toolBarTitle"> <i class="fa" ng-class="$ctrl.titleIcon" aria-hidden="true" style="margin-right: 2px;"></i> {{ $ctrl.titleText }} </div>
</div>
<div class="actionBar" ng-if="$ctrl.endpointManagement">
<div class="actionBar">
<button type="button" class="btn btn-sm btn-danger" ng-disabled="$ctrl.state.selectedItemCount === 0" ng-click="$ctrl.removeAction($ctrl.state.selectedItems)">
<i class="fa fa-trash-alt space-right" aria-hidden="true"></i>Remove
</button>
@ -27,7 +27,7 @@
<thead>
<tr>
<th>
<span class="md-checkbox" ng-if="$ctrl.endpointManagement">
<span class="md-checkbox">
<input id="select_all" type="checkbox" ng-model="$ctrl.state.selectAll" ng-change="$ctrl.selectAll()" />
<label for="select_all"></label>
</span>
@ -68,12 +68,11 @@
ng-class="{ active: item.Checked }"
>
<td>
<span class="md-checkbox" ng-if="$ctrl.endpointManagement">
<span class="md-checkbox">
<input id="select_{{ $index }}" type="checkbox" ng-model="item.Checked" ng-click="$ctrl.selectItem(item, $event)" />
<label for="select_{{ $index }}"></label>
</span>
<a ui-sref="portainer.endpoints.endpoint({id: item.Id})" ng-if="$ctrl.endpointManagement">{{ item.Name }}</a>
<span ng-if="!$ctrl.endpointManagement">{{ item.Name }}</span>
<a ui-sref="portainer.endpoints.endpoint({id: item.Id})">{{ item.Name }}</a>
</td>
<td>
<span>

View File

@ -7,7 +7,6 @@ angular.module('portainer.app').component('endpointsDatatable', {
tableKey: '@',
orderBy: '@',
reverseOrder: '<',
endpointManagement: '<',
accessManagement: '<',
removeAction: '<',
retrievePage: '<',

View File

@ -1,7 +1,6 @@
export function StatusViewModel(data) {
this.Authentication = data.Authentication;
this.Snapshot = data.Snapshot;
this.EndpointManagement = data.EndpointManagement;
this.Analytics = data.Analytics;
this.Version = data.Version;
}

View File

@ -79,7 +79,6 @@ angular.module('portainer.app').factory('StateManager', [
function assignStateFromStatusAndSettings(status, settings) {
state.application.authentication = status.Authentication;
state.application.analytics = status.Analytics;
state.application.endpointManagement = status.EndpointManagement;
state.application.version = status.Version;
state.application.logo = settings.LogoURL;
state.application.snapshotInterval = settings.SnapshotInterval;

View File

@ -19,10 +19,6 @@ angular
Notifications,
Authentication
) {
if (!$scope.applicationState.application.endpointManagement) {
$state.go('portainer.endpoints');
}
$scope.state = {
uploadInProgress: false,
actionInProgress: false,

View File

@ -7,20 +7,6 @@
<rd-header-content>Endpoint management</rd-header-content>
</rd-header>
<div class="row" ng-if="!applicationState.application.endpointManagement">
<div class="col-sm-12">
<rd-widget>
<rd-widget-header icon="fa-exclamation-triangle" title-text="Endpoint management is not available"> </rd-widget-header>
<rd-widget-body>
<span class="small text-muted"
>Portainer has been started using the <code>--external-endpoints</code> flag. Endpoint management via the UI is disabled.
<span ng-if="applicationState.application.authentication">You can still manage endpoint access.</span>
</span>
</rd-widget-body>
</rd-widget>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<endpoints-datatable
@ -28,7 +14,6 @@
title-icon="fa-plug"
table-key="endpoints"
order-by="Name"
endpoint-management="applicationState.application.endpointManagement"
access-management="applicationState.application.authentication"
remove-action="removeAction"
retrieve-page="getPaginatedEndpoints"