diff --git a/api/cmd/portainer/main.go b/api/cmd/portainer/main.go
index 4c9ddfea6..01a73d41f 100644
--- a/api/cmd/portainer/main.go
+++ b/api/cmd/portainer/main.go
@@ -106,8 +106,12 @@ func initClientFactory(signatureService portainer.DigitalSignatureService) *dock
return docker.NewClientFactory(signatureService)
}
-func initJobScheduler(endpointService portainer.EndpointService, clientFactory *docker.ClientFactory, flags *portainer.CLIFlags) (portainer.JobScheduler, error) {
- jobScheduler := cron.NewJobScheduler(endpointService, clientFactory)
+func initSnapshotter(clientFactory *docker.ClientFactory) portainer.Snapshotter {
+ return docker.NewSnapshotter(clientFactory)
+}
+
+func initJobScheduler(endpointService portainer.EndpointService, snapshotter portainer.Snapshotter, flags *portainer.CLIFlags) (portainer.JobScheduler, error) {
+ jobScheduler := cron.NewJobScheduler(endpointService, snapshotter)
if *flags.ExternalEndpoints != "" {
log.Println("Using external endpoint definition. Endpoint management via the API will be disabled.")
@@ -394,7 +398,9 @@ func main() {
clientFactory := initClientFactory(digitalSignatureService)
- jobScheduler, err := initJobScheduler(store.EndpointService, clientFactory, flags)
+ snapshotter := initSnapshotter(clientFactory)
+
+ jobScheduler, err := initJobScheduler(store.EndpointService, snapshotter, flags)
if err != nil {
log.Fatal(err)
}
@@ -498,6 +504,7 @@ func main() {
GitService: gitService,
SignatureService: digitalSignatureService,
JobScheduler: jobScheduler,
+ Snapshotter: snapshotter,
SSL: *flags.SSL,
SSLCert: *flags.SSLCert,
SSLKey: *flags.SSLKey,
diff --git a/api/cron/scheduler.go b/api/cron/scheduler.go
index 3847e6bbd..9f65b6d5a 100644
--- a/api/cron/scheduler.go
+++ b/api/cron/scheduler.go
@@ -4,7 +4,6 @@ import (
"log"
"github.com/portainer/portainer"
- "github.com/portainer/portainer/docker"
"github.com/robfig/cron"
)
@@ -19,11 +18,11 @@ type JobScheduler struct {
}
// NewJobScheduler initializes a new service.
-func NewJobScheduler(endpointService portainer.EndpointService, clientFactory *docker.ClientFactory) *JobScheduler {
+func NewJobScheduler(endpointService portainer.EndpointService, snapshotter portainer.Snapshotter) *JobScheduler {
return &JobScheduler{
cron: cron.New(),
endpointService: endpointService,
- snapshotter: docker.NewSnapshotter(clientFactory),
+ snapshotter: snapshotter,
}
}
diff --git a/api/http/handler/endpoints/endpoint_create.go b/api/http/handler/endpoints/endpoint_create.go
index 6c1e1f894..9ac29b649 100644
--- a/api/http/handler/endpoints/endpoint_create.go
+++ b/api/http/handler/endpoints/endpoint_create.go
@@ -1,6 +1,7 @@
package endpoints
import (
+ "log"
"net/http"
"runtime"
"strconv"
@@ -224,9 +225,9 @@ func (handler *Handler) createUnsecuredEndpoint(payload *endpointCreatePayload)
Snapshots: []portainer.Snapshot{},
}
- err := handler.EndpointService.CreateEndpoint(endpoint)
+ err := handler.snapshotAndPersistEndpoint(endpoint)
if err != nil {
- return nil, &httperror.HandlerError{http.StatusInternalServerError, "Unable to persist endpoint inside the database", err}
+ return nil, err
}
return endpoint, nil
@@ -266,9 +267,9 @@ func (handler *Handler) createTLSSecuredEndpoint(payload *endpointCreatePayload)
Snapshots: []portainer.Snapshot{},
}
- err = handler.EndpointService.CreateEndpoint(endpoint)
- if err != nil {
- return nil, &httperror.HandlerError{http.StatusInternalServerError, "Unable to persist endpoint inside the database", err}
+ endpointCreationError := handler.snapshotAndPersistEndpoint(endpoint)
+ if endpointCreationError != nil {
+ return nil, endpointCreationError
}
filesystemError := handler.storeTLSFiles(endpoint, payload)
@@ -285,6 +286,26 @@ func (handler *Handler) createTLSSecuredEndpoint(payload *endpointCreatePayload)
return endpoint, nil
}
+func (handler *Handler) snapshotAndPersistEndpoint(endpoint *portainer.Endpoint) *httperror.HandlerError {
+ snapshot, err := handler.Snapshotter.CreateSnapshot(endpoint)
+ endpoint.Status = portainer.EndpointStatusUp
+ if err != nil {
+ log.Printf("http error: endpoint snapshot error (endpoint=%s, URL=%s) (err=%s)\n", endpoint.Name, endpoint.URL, err)
+ endpoint.Status = portainer.EndpointStatusDown
+ }
+
+ if snapshot != nil {
+ endpoint.Snapshots = []portainer.Snapshot{*snapshot}
+ }
+
+ err = handler.EndpointService.CreateEndpoint(endpoint)
+ if err != nil {
+ return &httperror.HandlerError{http.StatusInternalServerError, "Unable to persist endpoint inside the database", err}
+ }
+
+ return nil
+}
+
func (handler *Handler) storeTLSFiles(endpoint *portainer.Endpoint, payload *endpointCreatePayload) *httperror.HandlerError {
folder := strconv.Itoa(int(endpoint.ID))
diff --git a/api/http/handler/endpoints/handler.go b/api/http/handler/endpoints/handler.go
index 6a593479c..69440037e 100644
--- a/api/http/handler/endpoints/handler.go
+++ b/api/http/handler/endpoints/handler.go
@@ -29,6 +29,7 @@ type Handler struct {
EndpointGroupService portainer.EndpointGroupService
FileService portainer.FileService
ProxyManager *proxy.Manager
+ Snapshotter portainer.Snapshotter
}
// NewHandler creates a handler to manage endpoint operations.
diff --git a/api/http/server.go b/api/http/server.go
index 564e24e3b..4babc109b 100644
--- a/api/http/server.go
+++ b/api/http/server.go
@@ -41,6 +41,7 @@ type Server struct {
CryptoService portainer.CryptoService
SignatureService portainer.DigitalSignatureService
JobScheduler portainer.JobScheduler
+ Snapshotter portainer.Snapshotter
DockerHubService portainer.DockerHubService
EndpointService portainer.EndpointService
EndpointGroupService portainer.EndpointGroupService
@@ -102,6 +103,7 @@ func (server *Server) Start() error {
endpointHandler.EndpointGroupService = server.EndpointGroupService
endpointHandler.FileService = server.FileService
endpointHandler.ProxyManager = proxyManager
+ endpointHandler.Snapshotter = server.Snapshotter
var endpointGroupHandler = endpointgroups.NewHandler(requestBouncer)
endpointGroupHandler.EndpointGroupService = server.EndpointGroupService
diff --git a/app/portainer/components/datatables/endpoints-snapshot-datatable/endpointsSnapshotDatatable.html b/app/portainer/components/datatables/endpoints-snapshot-datatable/endpointsSnapshotDatatable.html
deleted file mode 100644
index a94ca0b37..000000000
--- a/app/portainer/components/datatables/endpoints-snapshot-datatable/endpointsSnapshotDatatable.html
+++ /dev/null
@@ -1,113 +0,0 @@
-
diff --git a/app/portainer/components/datatables/endpoints-snapshot-datatable/endpointsSnapshotDatatable.js b/app/portainer/components/datatables/endpoints-snapshot-datatable/endpointsSnapshotDatatable.js
deleted file mode 100644
index a7d9c7711..000000000
--- a/app/portainer/components/datatables/endpoints-snapshot-datatable/endpointsSnapshotDatatable.js
+++ /dev/null
@@ -1,13 +0,0 @@
-angular.module('portainer.app').component('endpointsSnapshotDatatable', {
- templateUrl: 'app/portainer/components/datatables/endpoints-snapshot-datatable/endpointsSnapshotDatatable.html',
- controller: 'GenericDatatableController',
- bindings: {
- titleText: '@',
- titleIcon: '@',
- dataset: '<',
- tableKey: '@',
- orderBy: '@',
- reverseOrder: '<',
- dashboardAction: '<'
- }
-});
diff --git a/app/portainer/components/datatables/endpoints-snapshot-datatable/snapshot-details/snapshotDetails.html b/app/portainer/components/datatables/endpoints-snapshot-datatable/snapshot-details/snapshotDetails.html
deleted file mode 100644
index 00efe0ae9..000000000
--- a/app/portainer/components/datatables/endpoints-snapshot-datatable/snapshot-details/snapshotDetails.html
+++ /dev/null
@@ -1,29 +0,0 @@
-
-
- {{ $ctrl.snapshot.StackCount }} stacks
-
-
- {{ $ctrl.snapshot.ServiceCount }} services
-
-
- {{ $ctrl.snapshot.RunningContainerCount + $ctrl.snapshot.StoppedContainerCount }} containers
-
- -
- {{ $ctrl.snapshot.RunningContainerCount }}
- {{ $ctrl.snapshot.StoppedContainerCount }}
-
-
-
- {{ $ctrl.snapshot.VolumeCount }} volumes
-
-
- {{ $ctrl.snapshot.ImageCount }} images
-
-
- {{ $ctrl.snapshot.TotalMemory | humansize }}
- {{ $ctrl.snapshot.TotalCPU }}
-
-
- {{ $ctrl.snapshot.Swarm ? 'Swarm' : 'Standalone' }} {{ $ctrl.snapshot.DockerVersion }}
-
-
diff --git a/app/portainer/components/datatables/endpoints-snapshot-datatable/snapshot-details/snapshotDetails.js b/app/portainer/components/datatables/endpoints-snapshot-datatable/snapshot-details/snapshotDetails.js
deleted file mode 100644
index 1190be07e..000000000
--- a/app/portainer/components/datatables/endpoints-snapshot-datatable/snapshot-details/snapshotDetails.js
+++ /dev/null
@@ -1,6 +0,0 @@
-angular.module('portainer.app').component('snapshotDetails', {
- templateUrl: 'app/portainer/components/datatables/endpoints-snapshot-datatable/snapshot-details/snapshotDetails.html',
- bindings: {
- snapshot: '<'
- }
-});
diff --git a/app/portainer/components/endpoint-list/endpoint-item/endpointItem.html b/app/portainer/components/endpoint-list/endpoint-item/endpointItem.html
new file mode 100644
index 000000000..fd50950ce
--- /dev/null
+++ b/app/portainer/components/endpoint-list/endpoint-item/endpointItem.html
@@ -0,0 +1,75 @@
+
+
+
+
+
+
+
+
+
+
+
+ {{ $ctrl.model.Name }}
+
+
+
+ {{ $ctrl.model.Status === 1 ? 'up' : 'down' }}
+
+
+ {{ $ctrl.model.Snapshots[0].Time | getisodatefromtimestamp }}
+
+
+
+
+
+ {{ $ctrl.model.GroupName }}
+
+
+
+
+
+
+
+ {{ $ctrl.model.Snapshots[0].StackCount }} stacks
+
+
+ {{ $ctrl.model.Snapshots[0].ServiceCount }} services
+
+
+ {{ $ctrl.model.Snapshots[0].RunningContainerCount + $ctrl.model.Snapshots[0].StoppedContainerCount }} containers
+
+ -
+ {{ $ctrl.model.Snapshots[0].RunningContainerCount }}
+ {{ $ctrl.model.Snapshots[0].StoppedContainerCount }}
+
+
+
+ {{ $ctrl.model.Snapshots[0].VolumeCount }} volumes
+
+
+ {{ $ctrl.model.Snapshots[0].ImageCount }} images
+
+
+
+
+ {{ $ctrl.model.Snapshots[0].Swarm ? 'Swarm' : 'Standalone' }} {{ $ctrl.model.Snapshots[0].DockerVersion }} + Agent
+
+
+
+
+
+
+ No tags
+
+
+
+
+ {{ tag }}{{ $last? '' : ', ' }}
+
+
+
+
+
+
+
+
diff --git a/app/portainer/components/endpoint-list/endpoint-item/endpointItem.js b/app/portainer/components/endpoint-list/endpoint-item/endpointItem.js
new file mode 100644
index 000000000..d04fb25cf
--- /dev/null
+++ b/app/portainer/components/endpoint-list/endpoint-item/endpointItem.js
@@ -0,0 +1,7 @@
+angular.module('portainer.app').component('endpointItem', {
+ templateUrl: 'app/portainer/components/endpoint-list/endpoint-item/endpointItem.html',
+ bindings: {
+ model: '<',
+ onSelect: '<'
+ }
+});
diff --git a/app/portainer/components/endpoint-list/endpoint-list.js b/app/portainer/components/endpoint-list/endpoint-list.js
new file mode 100644
index 000000000..9d551591f
--- /dev/null
+++ b/app/portainer/components/endpoint-list/endpoint-list.js
@@ -0,0 +1,16 @@
+angular.module('portainer.app').component('endpointList', {
+ templateUrl: 'app/portainer/components/endpoint-list/endpointList.html',
+ controller: function() {
+ var ctrl = this;
+
+ this.state = {
+ textFilter: ''
+ };
+ },
+ bindings: {
+ titleText: '@',
+ titleIcon: '@',
+ endpoints: '<',
+ dashboardAction: '<'
+ }
+});
diff --git a/app/portainer/components/endpoint-list/endpointList.html b/app/portainer/components/endpoint-list/endpointList.html
new file mode 100644
index 000000000..f6b904408
--- /dev/null
+++ b/app/portainer/components/endpoint-list/endpointList.html
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Loading...
+
+
+ No endpoint available.
+
+
+
+
+
+
diff --git a/app/portainer/components/template-list/template-item/templateItem.html b/app/portainer/components/template-list/template-item/templateItem.html
index 32983f372..e287c36c9 100644
--- a/app/portainer/components/template-list/template-item/templateItem.html
+++ b/app/portainer/components/template-list/template-item/templateItem.html
@@ -1,23 +1,23 @@
-
-
+
+
-
+
-
+
-
-
+
+
-
+
{{ $ctrl.model.Title }}
-
+
&
@@ -38,17 +38,17 @@
-
-
-
-
+
+
+
+
{{ $ctrl.model.Description }}
{{ $ctrl.model.Categories.join(', ') }}
-
+
diff --git a/app/portainer/components/template-list/templateList.html b/app/portainer/components/template-list/templateList.html
index fc5b3be7c..76aa514fb 100644
--- a/app/portainer/components/template-list/templateList.html
+++ b/app/portainer/components/template-list/templateList.html
@@ -40,7 +40,7 @@
-
+
Loading...
-
+
No templates available.
diff --git a/app/portainer/services/fileUpload.js b/app/portainer/services/fileUpload.js
index 2321d489c..3cfb71b39 100644
--- a/app/portainer/services/fileUpload.js
+++ b/app/portainer/services/fileUpload.js
@@ -80,7 +80,7 @@ angular.module('portainer.app')
data: {
Name: name,
EndpointType: 3,
- GroupID: groupID,
+ GroupID: groupId,
Tags: Upload.json(tags),
AzureApplicationID: applicationId,
AzureTenantID: tenantId,
diff --git a/app/portainer/views/home/home.html b/app/portainer/views/home/home.html
index eabdfe8a0..a1396f7ab 100644
--- a/app/portainer/views/home/home.html
+++ b/app/portainer/views/home/home.html
@@ -7,18 +7,17 @@
Endpoints
-
-
-
+
+
+
+ Welcome to Portainer ! Click on any endpoint in the list below to access management features.
+
+
You do not have access to any environment. Please contact your administrator.
-
-
-
-
-
+
Endpoint snapshot is disabled.
@@ -27,11 +26,10 @@
diff --git a/assets/css/app.css b/assets/css/app.css
index 344afa8fc..e13a1e66a 100644
--- a/assets/css/app.css
+++ b/assets/css/app.css
@@ -119,7 +119,7 @@ a[ng-click]{
color: white;
}
-.fa.blue-icon {
+.fa.blue-icon, .fab.blue-icon {
color: #337ab7;
}
@@ -158,21 +158,13 @@ a[ng-click]{
overflow-y: auto;
}
-.template-list {
+.blocklist {
display: flex;
flex-direction: column;
padding: 10px;
- border-top: 2px solid #e2e2e2;
}
-.template-logo {
- width: 100%;
- max-width: 60px;
- height: 100%;
- max-height: 60px;
-}
-
-.template-container {
+.blocklist-item {
padding: 0.7rem;
margin-bottom: 0.7rem;
cursor: pointer;
@@ -181,46 +173,70 @@ a[ng-click]{
box-shadow: 0 3px 10px -2px rgba(161, 170, 166, 0.5);
}
-.template-container--selected {
+.blocklist-item--selected {
border: 2px solid #333333;
background-color: #ececec;
color: #2d3e63;
}
-.template-container:hover {
+.blocklist-item:hover {
background-color: #ececec;
color: #2d3e63;
}
-.template-main {
+.blocklist-item-box {
display: flex;
}
+.blocklist-item-line.endpoint-item {
+ padding: 4px;
+}
+
+.blocklist-item-line {
+ display: flex;
+ justify-content: space-between;
+}
+
+.blocklist-item-logo {
+ width: 100%;
+ max-width: 60px;
+ height: 100%;
+ max-height: 60px;
+}
+
+.blocklist-item-logo.endpoint-item {
+ margin: 10px 4px 0 6px;
+}
+
+.blocklist-item-logo.endpoint-item.azure {
+ margin: 0 0 0 10px;
+}
+
+.blocklist-item-title {
+ font-size: 1.8em;
+ font-weight: bold;
+}
+
+.blocklist-item-title.endpoint-item {
+ font-size: 1em;
+ font-weight: bold;
+}
+
+.blocklist-item-subtitle {
+ font-size: 0.9em;
+ padding-right: 1em;
+}
+
+.blocklist-item-desc {
+ font-size: 0.9em;
+ padding-right: 1em;
+}
+
.template-note {
padding: 0.5em;
font-size: 0.9em;
}
-.template-title {
- font-size: 1.8em;
- font-weight: bold;
-}
-
-.template-type {
- font-size: 0.9em;
- padding-right: 1em;
-}
-
-.template-description {
- font-size: 0.9em;
- padding-right: 1em;
-}
-
-.template-line {
- display: flex;
- justify-content: space-between;
-}
-
.nopadding {
padding: 0 !important;
}