1
0
Fork 0
mirror of https://github.com/portainer/portainer.git synced 2025-07-30 10:49:40 +02:00

feat(endpoints): add the ability to browse offline endpoints (#2253)

* feat(back): saved data in snapshot

* feat(endpoints): adding interceptors to retrieve saved data on offline endpoints

* feat(endpoints): offline dashboard working - need tests on offline views

* refactor(endpoints): interceptors cleaning and saving/loading offline endpoints data in/from localstorage

* feat(endpoints): browsing offline endpoints

* feat(endpoints): removing all the link in offline mode - sidebar not working when switching between off and on modes w/ stateManager logic

* feat(endpoints): endpoint status detection in real time

* fix(endpoints): offline swarm endpoint are not accessible anymore

* fix(endpoints): refactor message + disable offline browsing for an endpoint when no snapshot is available for it

* fix(endpoints): adding timeout and enabling loading bar for offline requests

* fix(endpoints): trying to access a down endpoint wont remove sidebar items if it fails

* feat(endpoints): disable checkboxes on offline views for offline mode

* feat(endpoints): updating endpoint status when detecting a change

* refactor(host): moved offline status panel from engine view to new host view

* fix(endpoints): missing endpoint update on ping from home view

* fix(api): rework EndpointUpdate operation

* refactor(offline): moved endpoint status to EndpointProvider and refactor the status-changed detection

* fix(offline): moved status detection to callback on views -> prevent displaying the offline message when endpoint is back online on view change

* fix(offline): offline message is now displayed online when browsing an offline endpoint

* fix(offline): sidebar updates correctly on endpoint status change

* fix(offline): offline panel not displayed and hidden on online mode

* refactor(offline): rework of OfflineMode management

* refactor(offline): extract information-panel for offlineMode into a component

* refactor(offline): remove redundant binding of informationPanel + endpointStatusInterceptor patter as service

* refactor(interceptors): moved interceptors pattern to service pattern

* feat(stacks): prevent inspection of a stack in offline mode

* feat(host): hide devices/disk panels in offline mode

* feat(host): disable browse action in offline mode

* refactor(home): remove comments
This commit is contained in:
baron_l 2018-10-28 10:27:06 +01:00 committed by Anthony Lapenna
parent 354fda31f1
commit a61654a35d
59 changed files with 637 additions and 212 deletions

View file

@ -52,6 +52,16 @@ func snapshot(cli *client.Client) (*portainer.Snapshot, error) {
return nil, err
}
err = snapshotNetworks(snapshot, cli)
if err != nil {
return nil, err
}
err = snapshotVersion(snapshot, cli)
if err != nil {
return nil, err
}
snapshot.Time = time.Now().Unix()
return snapshot, nil
}
@ -66,6 +76,7 @@ func snapshotInfo(snapshot *portainer.Snapshot, cli *client.Client) error {
snapshot.DockerVersion = info.ServerVersion
snapshot.TotalCPU = info.NCPU
snapshot.TotalMemory = info.MemTotal
snapshot.SnapshotRaw.Info = info
return nil
}
@ -132,6 +143,7 @@ func snapshotContainers(snapshot *portainer.Snapshot, cli *client.Client) error
snapshot.RunningContainerCount = runningContainers
snapshot.StoppedContainerCount = stoppedContainers
snapshot.StackCount += len(stacks)
snapshot.SnapshotRaw.Containers = containers
return nil
}
@ -142,6 +154,7 @@ func snapshotImages(snapshot *portainer.Snapshot, cli *client.Client) error {
}
snapshot.ImageCount = len(images)
snapshot.SnapshotRaw.Images = images
return nil
}
@ -152,5 +165,24 @@ func snapshotVolumes(snapshot *portainer.Snapshot, cli *client.Client) error {
}
snapshot.VolumeCount = len(volumes.Volumes)
snapshot.SnapshotRaw.Volumes = volumes
return nil
}
func snapshotNetworks(snapshot *portainer.Snapshot, cli *client.Client) error {
networks, err := cli.NetworkList(context.Background(), types.NetworkListOptions{})
if err != nil {
return err
}
snapshot.SnapshotRaw.Networks = networks
return nil
}
func snapshotVersion(snapshot *portainer.Snapshot, cli *client.Client) error {
version, err := cli.ServerVersion(context.Background())
if err != nil {
return err
}
snapshot.SnapshotRaw.Version = version
return nil
}

View file

@ -12,16 +12,17 @@ import (
)
type endpointUpdatePayload struct {
Name string
URL string
PublicURL string
GroupID int
TLS bool
TLSSkipVerify bool
TLSSkipClientVerify bool
AzureApplicationID string
AzureTenantID string
AzureAuthenticationKey string
Name *string
URL *string
PublicURL *string
GroupID *int
TLS *bool
TLSSkipVerify *bool
TLSSkipClientVerify *bool
Status *int
AzureApplicationID *string
AzureTenantID *string
AzureAuthenticationKey *string
Tags []string
}
@ -53,36 +54,49 @@ func (handler *Handler) endpointUpdate(w http.ResponseWriter, r *http.Request) *
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find an endpoint with the specified identifier inside the database", err}
}
if payload.Name != "" {
endpoint.Name = payload.Name
if payload.Name != nil {
endpoint.Name = *payload.Name
}
if payload.URL != "" {
endpoint.URL = payload.URL
if payload.URL != nil {
endpoint.URL = *payload.URL
}
if payload.PublicURL != "" {
endpoint.PublicURL = payload.PublicURL
if payload.PublicURL != nil {
endpoint.PublicURL = *payload.PublicURL
}
if payload.GroupID != 0 {
endpoint.GroupID = portainer.EndpointGroupID(payload.GroupID)
if payload.GroupID != nil {
endpoint.GroupID = portainer.EndpointGroupID(*payload.GroupID)
}
if payload.Tags != nil {
endpoint.Tags = payload.Tags
}
if payload.Status != nil {
switch *payload.Status {
case 1:
endpoint.Status = portainer.EndpointStatusUp
break
case 2:
endpoint.Status = portainer.EndpointStatusDown
break
default:
break
}
}
if endpoint.Type == portainer.AzureEnvironment {
credentials := endpoint.AzureCredentials
if payload.AzureApplicationID != "" {
credentials.ApplicationID = payload.AzureApplicationID
if payload.AzureApplicationID != nil {
credentials.ApplicationID = *payload.AzureApplicationID
}
if payload.AzureTenantID != "" {
credentials.TenantID = payload.AzureTenantID
if payload.AzureTenantID != nil {
credentials.TenantID = *payload.AzureTenantID
}
if payload.AzureAuthenticationKey != "" {
credentials.AuthenticationKey = payload.AzureAuthenticationKey
if payload.AzureAuthenticationKey != nil {
credentials.AuthenticationKey = *payload.AzureAuthenticationKey
}
httpClient := client.NewHTTPClient()
@ -93,44 +107,55 @@ func (handler *Handler) endpointUpdate(w http.ResponseWriter, r *http.Request) *
endpoint.AzureCredentials = credentials
}
folder := strconv.Itoa(endpointID)
if payload.TLS {
endpoint.TLSConfig.TLS = true
endpoint.TLSConfig.TLSSkipVerify = payload.TLSSkipVerify
if !payload.TLSSkipVerify {
caCertPath, _ := handler.FileService.GetPathForTLSFile(folder, portainer.TLSFileCA)
endpoint.TLSConfig.TLSCACertPath = caCertPath
} else {
endpoint.TLSConfig.TLSCACertPath = ""
handler.FileService.DeleteTLSFile(folder, portainer.TLSFileCA)
}
if payload.TLS != nil {
folder := strconv.Itoa(endpointID)
if *payload.TLS {
endpoint.TLSConfig.TLS = true
if payload.TLSSkipVerify != nil {
endpoint.TLSConfig.TLSSkipVerify = *payload.TLSSkipVerify
if !*payload.TLSSkipVerify {
caCertPath, _ := handler.FileService.GetPathForTLSFile(folder, portainer.TLSFileCA)
endpoint.TLSConfig.TLSCACertPath = caCertPath
} else {
endpoint.TLSConfig.TLSCACertPath = ""
handler.FileService.DeleteTLSFile(folder, portainer.TLSFileCA)
}
}
if payload.TLSSkipClientVerify != nil {
if !*payload.TLSSkipClientVerify {
certPath, _ := handler.FileService.GetPathForTLSFile(folder, portainer.TLSFileCert)
endpoint.TLSConfig.TLSCertPath = certPath
keyPath, _ := handler.FileService.GetPathForTLSFile(folder, portainer.TLSFileKey)
endpoint.TLSConfig.TLSKeyPath = keyPath
} else {
endpoint.TLSConfig.TLSCertPath = ""
handler.FileService.DeleteTLSFile(folder, portainer.TLSFileCert)
endpoint.TLSConfig.TLSKeyPath = ""
handler.FileService.DeleteTLSFile(folder, portainer.TLSFileKey)
}
}
if !payload.TLSSkipClientVerify {
certPath, _ := handler.FileService.GetPathForTLSFile(folder, portainer.TLSFileCert)
endpoint.TLSConfig.TLSCertPath = certPath
keyPath, _ := handler.FileService.GetPathForTLSFile(folder, portainer.TLSFileKey)
endpoint.TLSConfig.TLSKeyPath = keyPath
} else {
endpoint.TLSConfig.TLS = false
endpoint.TLSConfig.TLSSkipVerify = false
endpoint.TLSConfig.TLSCACertPath = ""
endpoint.TLSConfig.TLSCertPath = ""
handler.FileService.DeleteTLSFile(folder, portainer.TLSFileCert)
endpoint.TLSConfig.TLSKeyPath = ""
handler.FileService.DeleteTLSFile(folder, portainer.TLSFileKey)
}
} else {
endpoint.TLSConfig.TLS = false
endpoint.TLSConfig.TLSSkipVerify = false
endpoint.TLSConfig.TLSCACertPath = ""
endpoint.TLSConfig.TLSCertPath = ""
endpoint.TLSConfig.TLSKeyPath = ""
err = handler.FileService.DeleteTLSFiles(folder)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to remove TLS files from disk", err}
err = handler.FileService.DeleteTLSFiles(folder)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to remove TLS files from disk", err}
}
}
}
_, err = handler.ProxyManager.CreateAndRegisterProxy(endpoint)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to register HTTP proxy for the endpoint", err}
if payload.URL != nil || payload.TLS != nil || endpoint.Type == portainer.AzureEnvironment {
_, err = handler.ProxyManager.CreateAndRegisterProxy(endpoint)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to register HTTP proxy for the endpoint", err}
}
}
err = handler.EndpointService.UpdateEndpoint(endpoint.ID, endpoint)

View file

@ -245,17 +245,28 @@ type (
// Snapshot represents a snapshot of a specific endpoint at a specific time
Snapshot struct {
Time int64 `json:"Time"`
DockerVersion string `json:"DockerVersion"`
Swarm bool `json:"Swarm"`
TotalCPU int `json:"TotalCPU"`
TotalMemory int64 `json:"TotalMemory"`
RunningContainerCount int `json:"RunningContainerCount"`
StoppedContainerCount int `json:"StoppedContainerCount"`
VolumeCount int `json:"VolumeCount"`
ImageCount int `json:"ImageCount"`
ServiceCount int `json:"ServiceCount"`
StackCount int `json:"StackCount"`
Time int64 `json:"Time"`
DockerVersion string `json:"DockerVersion"`
Swarm bool `json:"Swarm"`
TotalCPU int `json:"TotalCPU"`
TotalMemory int64 `json:"TotalMemory"`
RunningContainerCount int `json:"RunningContainerCount"`
StoppedContainerCount int `json:"StoppedContainerCount"`
VolumeCount int `json:"VolumeCount"`
ImageCount int `json:"ImageCount"`
ServiceCount int `json:"ServiceCount"`
StackCount int `json:"StackCount"`
SnapshotRaw SnapshotRaw `json:"SnapshotRaw"`
}
// SnapshotRaw represents all the information related to a snapshot as returned by the Docker API
SnapshotRaw struct {
Containers interface{} `json:"Containers"`
Volumes interface{} `json:"Volumes"`
Networks interface{} `json:"Networks"`
Images interface{} `json:"Images"`
Info interface{} `json:"Info"`
Version interface{} `json:"Version"`
}
// EndpointGroupID represents an endpoint group identifier