mirror of https://github.com/portainer/portainer
feat(home): update endpoint list (#2060)
parent
48179b9e3d
commit
3c6f6cf5bf
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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))
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,113 +0,0 @@
|
|||
<div class="datatable">
|
||||
<rd-widget>
|
||||
<rd-widget-body classes="no-padding">
|
||||
<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="searchBar">
|
||||
<i class="fa fa-search searchIcon" aria-hidden="true"></i>
|
||||
<input type="text" class="searchInput" ng-model="$ctrl.state.textFilter" placeholder="Search by name, group, tag..." auto-focus>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table nowrap-cells">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<a ng-click="$ctrl.changeOrderBy('Name')">
|
||||
Name
|
||||
<i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Name' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Name' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="$ctrl.changeOrderBy('GroupName')">
|
||||
Group
|
||||
<i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'GroupName' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'GroupName' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="$ctrl.changeOrderBy('Status')">
|
||||
Status
|
||||
<i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Status' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Status' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="$ctrl.changeOrderBy('Type')">
|
||||
Type
|
||||
<i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Type' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Type' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="$ctrl.changeOrderBy('Snapshots[0].Time')">
|
||||
Last snapshot
|
||||
<i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Snapshots[0].Time' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Snapshots[0].Time' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr dir-paginate-start="item in ($ctrl.state.filteredDataSet = ($ctrl.dataset | filter:$ctrl.state.textFilter | orderBy:$ctrl.state.orderBy:$ctrl.state.reverseOrder | itemsPerPage: $ctrl.state.paginatedItemLimit))">
|
||||
<td>
|
||||
<a ng-click="$ctrl.dashboardAction(item)"><i class="fa fa-sign-in-alt" aria-hidden="true"></i> {{ item.Name }}</a>
|
||||
</td>
|
||||
<td>{{ item.GroupName }}</td>
|
||||
<td>
|
||||
<span class="label label-{{ item.Status|endpointstatusbadge }}">{{ item.Status === 1 ? 'up' : 'down' }}</span>
|
||||
</td>
|
||||
<td>
|
||||
<span>
|
||||
<i ng-class="item.Type | endpointtypeicon" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||
{{ item.Type | endpointtypename }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<span ng-if="item.Snapshots.length > 0">
|
||||
{{ item.Snapshots[0].Time | getisodatefromtimestamp }}
|
||||
</span>
|
||||
<span ng-if="item.Snapshots.length === 0">-</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr dir-paginate-end class="text-muted" ng-if="item.Snapshots.length > 0">
|
||||
<td colspan="5" style="border: 0; text-align:center;">
|
||||
<snapshot-details
|
||||
snapshot="item.Snapshots[0]"
|
||||
></snapshot-details
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="!$ctrl.dataset">
|
||||
<td colspan="5" class="text-center text-muted">Loading...</td>
|
||||
</tr>
|
||||
<tr ng-if="$ctrl.state.filteredDataSet.length === 0">
|
||||
<td colspan="5" class="text-center text-muted">No endpoint available.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="footer" ng-if="$ctrl.dataset">
|
||||
<div class="paginationControls">
|
||||
<form class="form-inline">
|
||||
<span class="limitSelector">
|
||||
<span style="margin-right: 5px;">
|
||||
Items per page
|
||||
</span>
|
||||
<select class="form-control" ng-model="$ctrl.state.paginatedItemLimit" ng-change="$ctrl.changePaginationLimit()">
|
||||
<option value="0">All</option>
|
||||
<option value="10">10</option>
|
||||
<option value="25">25</option>
|
||||
<option value="50">50</option>
|
||||
<option value="100">100</option>
|
||||
</select>
|
||||
</span>
|
||||
<dir-pagination-controls max-size="5"></dir-pagination-controls>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
|
@ -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: '<'
|
||||
}
|
||||
});
|
|
@ -1,29 +0,0 @@
|
|||
<span style="border-top: 2px solid #e2e2e2; padding: 7px;">
|
||||
<span style="padding: 0 7px 0 7px;">
|
||||
<i class="fa fa-th-list space-right" aria-hidden="true"></i>{{ $ctrl.snapshot.StackCount }} stacks
|
||||
</span>
|
||||
<span style="padding: 0 7px 0 7px;" ng-if="$ctrl.snapshot.Swarm">
|
||||
<i class="fa fa-list-alt space-right" aria-hidden="true"></i>{{ $ctrl.snapshot.ServiceCount }} services
|
||||
</span>
|
||||
<span style="padding: 0 7px 0 7px;">
|
||||
<i class="fa fa-server space-right" aria-hidden="true"></i>{{ $ctrl.snapshot.RunningContainerCount + $ctrl.snapshot.StoppedContainerCount }} containers
|
||||
<span ng-if="$ctrl.snapshot.RunningContainerCount > 0 || $ctrl.snapshot.StoppedContainerCount > 0">
|
||||
-
|
||||
<i class="fa fa-heartbeat green-icon" aria-hidden="true"></i> {{ $ctrl.snapshot.RunningContainerCount }}
|
||||
<i class="fa fa-heartbeat red-icon" aria-hidden="true"></i> {{ $ctrl.snapshot.StoppedContainerCount }}
|
||||
</span>
|
||||
</span>
|
||||
<span style="padding: 0 7px 0 7px;">
|
||||
<i class="fa fa-cubes space-right" aria-hidden="true"></i>{{ $ctrl.snapshot.VolumeCount }} volumes
|
||||
</span>
|
||||
<span style="padding: 0 7px 0 7px;">
|
||||
<i class="fa fa-clone space-right" aria-hidden="true"></i>{{ $ctrl.snapshot.ImageCount }} images
|
||||
</span>
|
||||
<span style="padding: 0 7px 0 7px; border-left: 2px solid #e2e2e2;">
|
||||
<i class="fa fa-memory" aria-hidden="true"></i> {{ $ctrl.snapshot.TotalMemory | humansize }}
|
||||
<i class="fa fa-microchip space-left" aria-hidden="true"></i> {{ $ctrl.snapshot.TotalCPU }}
|
||||
</span>
|
||||
<span style="padding: 0 7px 0 7px; border-left: 2px solid #e2e2e2;">
|
||||
{{ $ctrl.snapshot.Swarm ? 'Swarm' : 'Standalone' }} {{ $ctrl.snapshot.DockerVersion }}
|
||||
</span>
|
||||
</span>
|
|
@ -1,6 +0,0 @@
|
|||
angular.module('portainer.app').component('snapshotDetails', {
|
||||
templateUrl: 'app/portainer/components/datatables/endpoints-snapshot-datatable/snapshot-details/snapshotDetails.html',
|
||||
bindings: {
|
||||
snapshot: '<'
|
||||
}
|
||||
});
|
|
@ -0,0 +1,75 @@
|
|||
<div class="blocklist-item" ng-click="$ctrl.onSelect($ctrl.model)">
|
||||
<div class="blocklist-item-box">
|
||||
<span ng-class="['blocklist-item-logo', 'endpoint-item', { azure: $ctrl.model.Type === 3 }]">
|
||||
<i ng-class="$ctrl.model.Type | endpointtypeicon" class="fa-4x blue-icon" aria-hidden="true"></i>
|
||||
</span>
|
||||
|
||||
<span class="col-sm-12">
|
||||
|
||||
<div class="blocklist-item-line endpoint-item">
|
||||
<span>
|
||||
<span class="blocklist-item-title endpoint-item">
|
||||
{{ $ctrl.model.Name }}
|
||||
</span>
|
||||
<span class="space-left blocklist-item-subtitle">
|
||||
<span class="label label-{{ $ctrl.model.Status|endpointstatusbadge }}">
|
||||
{{ $ctrl.model.Status === 1 ? 'up' : 'down' }}
|
||||
</span>
|
||||
<span class="space-left small text-muted" ng-if="$ctrl.model.Snapshots[0]">
|
||||
{{ $ctrl.model.Snapshots[0].Time | getisodatefromtimestamp }}
|
||||
</span>
|
||||
|
||||
</span>
|
||||
</span>
|
||||
<span class="small">
|
||||
{{ $ctrl.model.GroupName }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="blocklist-item-line endpoint-item" ng-if="$ctrl.model.Snapshots[0]">
|
||||
<span class="blocklist-item-desc">
|
||||
<span>
|
||||
<span style="padding: 0 7px 0 0;">
|
||||
<i class="fa fa-th-list space-right" aria-hidden="true"></i>{{ $ctrl.model.Snapshots[0].StackCount }} stacks
|
||||
</span>
|
||||
<span style="padding: 0 7px 0 7px;" ng-if="$ctrl.model.Snapshots[0].Swarm">
|
||||
<i class="fa fa-list-alt space-right" aria-hidden="true"></i>{{ $ctrl.model.Snapshots[0].ServiceCount }} services
|
||||
</span>
|
||||
<span style="padding: 0 7px 0 7px;">
|
||||
<i class="fa fa-server space-right" aria-hidden="true"></i>{{ $ctrl.model.Snapshots[0].RunningContainerCount + $ctrl.model.Snapshots[0].StoppedContainerCount }} containers
|
||||
<span ng-if="$ctrl.model.Snapshots[0].RunningContainerCount > 0 || $ctrl.model.Snapshots[0].StoppedContainerCount > 0">
|
||||
-
|
||||
<i class="fa fa-heartbeat green-icon" aria-hidden="true"></i> {{ $ctrl.model.Snapshots[0].RunningContainerCount }}
|
||||
<i class="fa fa-heartbeat red-icon" aria-hidden="true"></i> {{ $ctrl.model.Snapshots[0].StoppedContainerCount }}
|
||||
</span>
|
||||
</span>
|
||||
<span style="padding: 0 7px 0 7px;">
|
||||
<i class="fa fa-cubes space-right" aria-hidden="true"></i>{{ $ctrl.model.Snapshots[0].VolumeCount }} volumes
|
||||
</span>
|
||||
<span style="padding: 0 7px 0 7px;">
|
||||
<i class="fa fa-clone space-right" aria-hidden="true"></i>{{ $ctrl.model.Snapshots[0].ImageCount }} images
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
<span class="small text-muted">
|
||||
{{ $ctrl.model.Snapshots[0].Swarm ? 'Swarm' : 'Standalone' }} {{ $ctrl.model.Snapshots[0].DockerVersion }} <span ng-if="$ctrl.model.Type === 2">+ <i class="fa fa-bolt" aria-hidden="true"></i> Agent</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="blocklist-item-line endpoint-item">
|
||||
<span class="small text-muted">
|
||||
<span ng-if="$ctrl.model.Tags.length === 0">
|
||||
<i class="fa fa-tags" aria-hidden="true"></i> No tags
|
||||
</span>
|
||||
<span ng-if="$ctrl.model.Tags.length > 0">
|
||||
<i class="fa fa-tags" aria-hidden="true"></i>
|
||||
<span ng-repeat="tag in $ctrl.model.Tags">
|
||||
{{ tag }}{{ $last? '' : ', ' }}
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,7 @@
|
|||
angular.module('portainer.app').component('endpointItem', {
|
||||
templateUrl: 'app/portainer/components/endpoint-list/endpoint-item/endpointItem.html',
|
||||
bindings: {
|
||||
model: '<',
|
||||
onSelect: '<'
|
||||
}
|
||||
});
|
|
@ -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: '<'
|
||||
}
|
||||
});
|
|
@ -0,0 +1,32 @@
|
|||
<div class="datatable">
|
||||
<rd-widget>
|
||||
<rd-widget-body classes="no-padding">
|
||||
|
||||
<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="searchBar">
|
||||
<i class="fa fa-search searchIcon" aria-hidden="true"></i>
|
||||
<input type="text" class="searchInput" ng-model="$ctrl.state.textFilter" placeholder="Search by name, group, tag..." auto-focus>
|
||||
</div>
|
||||
|
||||
<div class="blocklist">
|
||||
<endpoint-item
|
||||
ng-repeat="endpoint in $ctrl.endpoints | filter:$ctrl.state.textFilter"
|
||||
model="endpoint"
|
||||
on-select="$ctrl.dashboardAction"
|
||||
></endpoint-item>
|
||||
<div ng-if="!$ctrl.endpoints" class="text-center text-muted">
|
||||
Loading...
|
||||
</div>
|
||||
<div ng-if="($ctrl.endpoints | filter:$ctrl.state.textFilter).length === 0" class="text-center text-muted">
|
||||
No endpoint available.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
|
@ -1,23 +1,23 @@
|
|||
<!-- template -->
|
||||
<div ng-class="{ 'template-container--selected': $ctrl.model.Selected }" class="template-container" ng-click="$ctrl.onSelect($ctrl.model)">
|
||||
<div class="template-main">
|
||||
<div ng-class="{ 'blocklist-item--selected': $ctrl.model.Selected }" class="blocklist-item" ng-click="$ctrl.onSelect($ctrl.model)">
|
||||
<div class="blocklist-item-box">
|
||||
<!-- template-image -->
|
||||
<span ng-if="$ctrl.model.Logo">
|
||||
<img class="template-logo" ng-src="{{ $ctrl.model.Logo }}" />
|
||||
<img class="blocklist-item-logo" ng-src="{{ $ctrl.model.Logo }}" />
|
||||
</span>
|
||||
<span class="template-logo" ng-if="!$ctrl.model.Logo">
|
||||
<span class="blocklist-item-logo" ng-if="!$ctrl.model.Logo">
|
||||
<i class="fa fa-rocket fa-4x blue-icon" aria-hidden="true"></i>
|
||||
</span>
|
||||
<!-- !template-image -->
|
||||
<!-- template-details -->
|
||||
<span class="col-sm-12">
|
||||
<!-- template-line1 -->
|
||||
<div class="template-line">
|
||||
<!-- blocklist-item-line1 -->
|
||||
<div class="blocklist-item-line">
|
||||
<span>
|
||||
<span class="template-title">
|
||||
<span class="blocklist-item-title">
|
||||
{{ $ctrl.model.Title }}
|
||||
</span>
|
||||
<span class="space-left template-type">
|
||||
<span class="space-left blocklist-item-subtitle">
|
||||
<span>
|
||||
<i class="fab fa-linux" aria-hidden="true" ng-if="$ctrl.model.Platform === 'linux' || !$ctrl.model.Platform"></i>
|
||||
<span ng-if="!$ctrl.model.Platform"> & </span>
|
||||
|
@ -38,17 +38,17 @@
|
|||
</btn>
|
||||
</span>
|
||||
</div>
|
||||
<!-- !template-line1 -->
|
||||
<!-- template-line2 -->
|
||||
<div class="template-line">
|
||||
<span class="template-description">
|
||||
<!-- !blocklist-item-line1 -->
|
||||
<!-- blocklist-item-line2 -->
|
||||
<div class="blocklist-item-line">
|
||||
<span class="blocklist-item-desc">
|
||||
{{ $ctrl.model.Description }}
|
||||
</span>
|
||||
<span class="small text-muted" ng-if="$ctrl.model.Categories.length > 0">
|
||||
{{ $ctrl.model.Categories.join(', ') }}
|
||||
</span>
|
||||
</div>
|
||||
<!-- !template-line2 -->
|
||||
<!-- !blocklist-item-line2 -->
|
||||
</span>
|
||||
<!-- !template-details -->
|
||||
</div>
|
||||
|
|
|
@ -40,7 +40,7 @@
|
|||
<input type="text" class="searchInput" ng-model="$ctrl.state.textFilter" placeholder="Search..." auto-focus>
|
||||
</div>
|
||||
|
||||
<div class="template-list">
|
||||
<div class="blocklist">
|
||||
<template-item
|
||||
ng-repeat="template in $ctrl.templates | filter: $ctrl.filterByType | filter:$ctrl.filterByCategory | filter:$ctrl.state.textFilter"
|
||||
model="template"
|
||||
|
@ -52,7 +52,7 @@
|
|||
<div ng-if="!$ctrl.templates" class="text-center text-muted">
|
||||
Loading...
|
||||
</div>
|
||||
<div ng-if="($ctrl.templates | filter: $ctrl.filterByType | filter:$ctrl.filterByCategory | filter:$ctrl.state.textFilter).length == 0" class="text-center text-muted">
|
||||
<div ng-if="($ctrl.templates | filter: $ctrl.filterByType | filter:$ctrl.filterByCategory | filter:$ctrl.state.textFilter).length === 0" class="text-center text-muted">
|
||||
No templates available.
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -7,18 +7,17 @@
|
|||
<rd-header-content>Endpoints</rd-header-content>
|
||||
</rd-header>
|
||||
|
||||
<information-panel title-text="Information" ng-if="!isAdmin && endpoints.length === 0">
|
||||
<span class="small">
|
||||
<p class="text-muted">
|
||||
<information-panel title-text="Information">
|
||||
<span class="small text-muted">
|
||||
<p ng-if="endpoints.length > 0">
|
||||
Welcome to Portainer ! Click on any endpoint in the list below to access management features.
|
||||
</p>
|
||||
<p ng-if="!isAdmin && endpoints.length === 0">
|
||||
<i class="fa fa-exclamation-circle orange-icon" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||
You do not have access to any environment. Please contact your administrator.
|
||||
</p>
|
||||
</span>
|
||||
</information-panel>
|
||||
|
||||
<information-panel title-text="Information" ng-if="isAdmin && !applicationState.application.snapshot">
|
||||
<span class="small">
|
||||
<p class="text-muted">
|
||||
<p ng-if="isAdmin && !applicationState.application.snapshot">
|
||||
<i class="fa fa-exclamation-circle orange-icon" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||
Endpoint snapshot is disabled.
|
||||
</p>
|
||||
|
@ -27,11 +26,10 @@
|
|||
|
||||
<div class="row" ng-if="endpoints.length > 0">
|
||||
<div class="col-sm-12">
|
||||
<endpoints-snapshot-datatable
|
||||
<endpoint-list
|
||||
title-text="Endpoints" title-icon="fa-plug"
|
||||
dataset="endpoints" table-key="endpoints"
|
||||
order-by="Name"
|
||||
endpoints="endpoints"
|
||||
dashboard-action="goToDashboard"
|
||||
></endpoints-snapshot-datatable>
|
||||
></endpoint-list>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue