feat(home): update endpoint list (#2060)

pull/2061/head
Anthony Lapenna 2018-07-23 09:51:33 +02:00 committed by GitHub
parent 48179b9e3d
commit 3c6f6cf5bf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 247 additions and 234 deletions

View File

@ -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,

View File

@ -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,
}
}

View File

@ -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))

View File

@ -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.

View File

@ -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

View File

@ -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>

View File

@ -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: '<'
}
});

View File

@ -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>

View File

@ -1,6 +0,0 @@
angular.module('portainer.app').component('snapshotDetails', {
templateUrl: 'app/portainer/components/datatables/endpoints-snapshot-datatable/snapshot-details/snapshotDetails.html',
bindings: {
snapshot: '<'
}
});

View File

@ -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>

View File

@ -0,0 +1,7 @@
angular.module('portainer.app').component('endpointItem', {
templateUrl: 'app/portainer/components/endpoint-list/endpoint-item/endpointItem.html',
bindings: {
model: '<',
onSelect: '<'
}
});

View File

@ -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: '<'
}
});

View File

@ -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>

View File

@ -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"> &amp; </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>

View File

@ -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>

View File

@ -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,

View File

@ -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>

View File

@ -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;
}