THIS IS A TEST INSTANCE ONLY! REPOSITORIES CAN BE DELETED AT ANY TIME!

Browse Source

style(extensions): minor update to extension UX/UI (#2538)

* style(extensions): update extension icons

* style(extensions): style update

* feat(extensions): update extension UX

* style(extensions): update extension style

* style(extension-details): update screenshot default size

* style(extensions): update overview diagram image

* refactor(support): fix support URLs
tags/1.20.0
Anthony Lapenna GitHub 1 year ago
parent
commit
5c2e714e69
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 171 additions and 95 deletions
  1. +4
    -0
      api/http/handler/extensions/extension_inspect.go
  2. +1
    -1
      api/http/handler/extensions/extension_list.go
  3. +2
    -1
      api/portainer.go
  4. +2
    -2
      app/extensions/registry-management/views/configure/configureregistry.html
  5. +2
    -2
      app/portainer/components/datatables/registries-datatable/registriesDatatable.html
  6. +4
    -3
      app/portainer/components/extension-list/extension-item/extensionItem.html
  7. +16
    -14
      app/portainer/components/extension-list/extension-item/extensionItemController.js
  8. +0
    -1
      app/portainer/components/extension-tooltip/extension-tooltip.html
  9. +0
    -3
      app/portainer/components/extension-tooltip/extension-tooltip.js
  10. +1
    -1
      app/portainer/models/extension.js
  11. +57
    -3
      app/portainer/views/extensions/extensions.html
  12. +58
    -57
      app/portainer/views/extensions/extensionsController.js
  13. +18
    -6
      app/portainer/views/extensions/inspect/extension.html
  14. +1
    -1
      app/portainer/views/support/product/product.html
  15. +5
    -0
      assets/css/app.css
  16. BIN
      assets/images/extensions_overview_diagram.png

+ 4
- 0
api/http/handler/extensions/extension_inspect.go View File

@@ -35,6 +35,10 @@ func (handler *Handler) extensionInspect(w http.ResponseWriter, r *http.Request)
for _, p := range extensions {
if p.ID == extensionID {
extension = p
if extension.DescriptionURL != "" {
description, _ := client.Get(extension.DescriptionURL, 10)
extension.Description = string(description)
}
break
}
}


+ 1
- 1
api/http/handler/extensions/extension_list.go View File

@@ -22,7 +22,7 @@ func (handler *Handler) extensionList(w http.ResponseWriter, r *http.Request) *h
if storeDetails {
definitions, err := handler.ExtensionManager.FetchExtensionDefinitions()
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve extension definitions", err}
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve extensions", err}
}

for idx := range definitions {


+ 2
- 1
api/portainer.go View File

@@ -485,6 +485,7 @@ type (
Name string `json:"Name,omitempty"`
ShortDescription string `json:"ShortDescription,omitempty"`
Description string `json:"Description,omitempty"`
DescriptionURL string `json:"DescriptionURL,omitempty"`
Price string `json:"Price,omitempty"`
PriceDescription string `json:"PriceDescription,omitempty"`
Deal bool `json:"Deal,omitempty"`
@@ -492,7 +493,7 @@ type (
License LicenseInformation `json:"License,omitempty"`
Version string `json:"Version"`
UpdateAvailable bool `json:"UpdateAvailable"`
ProductID int `json:"ProductId,omitempty"`
ShopURL string `json:"ShopURL,omitempty"`
Images []string `json:"Images,omitempty"`
Logo string `json:"Logo,omitempty"`
}


+ 2
- 2
app/extensions/registry-management/views/configure/configureregistry.html View File

@@ -149,8 +149,8 @@
<span ng-show="state.testInProgress">Test in progress...</span>
</button>
<button type="button" class="btn btn-primary btn-sm" ng-disabled="state.updateInProgress || !state.validConfiguration" ng-click="updateConfiguration()" button-spinner="state.updateInProgress">
<span ng-hide="state.updateInProgress">Update configuration</span>
<span ng-show="state.updateInProgress">Updating configuration...</span>
<span ng-hide="state.updateInProgress">Save configuration</span>
<span ng-show="state.updateInProgress">Saving configuration...</span>
</button>
</div>
</div>


+ 2
- 2
app/portainer/components/datatables/registries-datatable/registriesDatatable.html View File

@@ -64,8 +64,8 @@
<a ui-sref="portainer.registries.registry.repositories({id: item.Id})" ng-if="$ctrl.registryManagement" class="space-left">
<i class="fa fa-search" aria-hidden="true"></i> Browse
</a>
<a ui-sref="portainer.extensions.extension({id: 1})" ng-if="!$ctrl.registryManagement" class="space-left">
<i class="fa fa-search" aria-hidden="true"></i> Browse ( <extension-tooltip></extension-tooltip> )
<a ui-sref="portainer.extensions.extension({id: 1})" ng-if="!$ctrl.registryManagement" class="space-left" style="color: #767676" tooltip-append-to-body="true" tooltip-placement="bottom" tooltip-class="portainer-tooltip" uib-tooltip="Feature available via an extension">
<i class="fa fa-search" aria-hidden="true"></i> Browse (extension)
</a>
</td>
</tr>


+ 4
- 3
app/portainer/components/extension-list/extension-item/extensionItem.html View File

@@ -1,9 +1,10 @@
<!-- extension -->
<div class="blocklist-item" ng-click="$ctrl.goToExtensionView()">
<div class="blocklist-item" ng-click="$ctrl.goToExtensionView()" ng-class="{ 'blocklist-item--disabled': !$ctrl.model.Available }">
<div class="blocklist-item-box">
<!-- extension-image -->
<span ng-if="$ctrl.model.Logo">
<img class="blocklist-item-logo" ng-src="{{ $ctrl.model.Logo }}" />
<span ng-if="$ctrl.model.Logo" style="width: 75px; text-align: center;">
<!-- <img class="blocklist-item-logo" ng-src="{{ $ctrl.model.Logo }}" /> -->
<i class="{{ $ctrl.model.Logo }} fa fa-4x blue-icon" aria-hidden="true"></i>
</span>
<span class="blocklist-item-logo" ng-if="!$ctrl.model.Logo">
<i class="fa fa-bolt fa-4x blue-icon" style="margin-left: 14px;" aria-hidden="true"></i>


+ 16
- 14
app/portainer/components/extension-list/extension-item/extensionItemController.js View File

@@ -1,18 +1,20 @@
angular.module('portainer.app')
.controller('ExtensionItemController', ['$state',
function ($state) {
.controller('ExtensionItemController', ['$state',
function($state) {

var ctrl = this;
ctrl.$onInit = $onInit;
ctrl.goToExtensionView = goToExtensionView;
var ctrl = this;
ctrl.$onInit = $onInit;
ctrl.goToExtensionView = goToExtensionView;

function goToExtensionView() {
$state.go('portainer.extensions.extension', { id: ctrl.model.Id });
}
function goToExtensionView() {
if (ctrl.model.Available) {
$state.go('portainer.extensions.extension', { id: ctrl.model.Id });
}
}

function $onInit() {
if (ctrl.currentDate === ctrl.model.License.Expiration) {
ctrl.model.Expired = true;
}
}
}]);
function $onInit() {
if (ctrl.currentDate === ctrl.model.License.Expiration) {
ctrl.model.Expired = true;
}
}
}]);

+ 0
- 1
app/portainer/components/extension-tooltip/extension-tooltip.html View File

@@ -1 +0,0 @@
<i class="fa fa-bolt orange-icon" aria-hidden="true" tooltip-append-to-body="true" tooltip-placement="bottom" tooltip-class="portainer-tooltip" uib-tooltip="Feature available via a plug-in"></i>

+ 0
- 3
app/portainer/components/extension-tooltip/extension-tooltip.js View File

@@ -1,3 +0,0 @@
angular.module('portainer.app').component('extensionTooltip', {
templateUrl: 'app/portainer/components/extension-tooltip/extension-tooltip.html'
});

+ 1
- 1
app/portainer/models/extension.js View File

@@ -11,7 +11,7 @@ function ExtensionViewModel(data) {
this.License = data.License;
this.Version = data.Version;
this.UpdateAvailable = data.UpdateAvailable;
this.ProductId = data.ProductId;
this.ShopURL = data.ShopURL;
this.Images = data.Images;
this.Logo = data.Logo;
}

+ 57
- 3
app/portainer/views/extensions/extensions.html View File

@@ -4,9 +4,54 @@
</rd-header>

<information-panel title-text="Information">
<span class="small text-muted">
<span class="text-muted" style="font-size: 90%;">
<p>
Portainer CE is a great way of managing clusters, provisioning containers and services and
managing container environment lifecycles. To extend the benefit of Portainer CE even
more, and to address the needs of larger, complex or critical environments, the Portainer
team provides a growing range of low-cost Extensions.
</p>

<p>
As the diagram shows, running a successful production container environment requires a
range of capability across a number of complex technical areas.
</p>

<p style="text-align: center; margin: 15px 0 15px 0;">
<img src="images/extensions_overview_diagram.png" alt="extensions overview">
</p>

<p>
Content to be defined
Available through a simple subscription process from the menu, Portainer Extensions
answers this need and provides a simple way to enhance the functionality that Portainer
makes available through incremental capability in important areas.
</p>

<p>
The vision for Portainer is to be the standard management layer for container platforms. In
order to achieve this vision, Portainer CE will be extended across a range of new functional
areas. In order to ensure that Portainer remains the best choice for managing production
container platforms, the Portainer team have chosen a modular extensible design approach,
where additional capability can be added to the Portainer CE core as needed, and at very
low cost.
</p>

<p>
The advantage of an extensible design is clear: While a range of capability is available, only
necessary functionality is added as and when needed.
</p>

<p>
Our first extension is <a ui-sref="portainer.extensions.extension({id: 1})">Registry Manager</a>, available now. Others (such as
Single Sign On and Operations Management) are scheduled for the early part of 2019.
</p>

<p>
Portainer CE is the core of the Portainer management environments. Portainer CE will
continue to be developed and made freely available as part of our deep commitment to our
Open Source heritage and our user community. Portainer CE will always deliver great
functionality and remain the industry standard toolset for managing container-based
platforms.
</p>
</span>
</information-panel>
@@ -61,7 +106,7 @@
</div>
</div>

<div class="row" ng-if="extensions">
<div class="row" ng-if="extensions && extensions.length > 0">
<div class="col-sm-12">
<extension-list
current-date="state.currentDate"
@@ -69,3 +114,12 @@
></extension-list>
</div>
</div>

<information-panel title-text="Error" ng-if="extensions && extensions.length === 0">
<span class="small text-muted">
<p>
<i class="fa fa-exclamation-triangle orange-icon" aria-hidden="true"></i>
Portainer must be connected to the Internet to fetch the list of available extensions.
</p>
</span>
</information-panel>

+ 58
- 57
app/portainer/views/extensions/extensionsController.js View File

@@ -1,58 +1,59 @@
angular.module('portainer.app')
.controller('ExtensionsController', ['$scope', '$state', 'ExtensionService', 'Notifications',
function ($scope, $state, ExtensionService, Notifications) {

$scope.state = {
actionInProgress: false,
currentDate: moment().format('YYYY-MM-dd')
};

$scope.formValues = {
License: ''
};

function initView() {
ExtensionService.extensions(true)
.then(function onSuccess(data) {
$scope.extensions = data;
})
.catch(function onError(err) {
Notifications.error('Failure', err, 'Unable to access extension store');
});
}

$scope.enableExtension = function() {
var license = $scope.formValues.License;

$scope.state.actionInProgress = true;
ExtensionService.enable(license)
.then(function onSuccess() {
Notifications.success('Extension successfully enabled');
$state.reload();
})
.catch(function onError(err) {
Notifications.error('Failure', err, 'Unable to enable extension');
})
.finally(function final() {
$scope.state.actionInProgress = false;
});
};


$scope.isValidLicenseFormat = function(form) {
var valid = true;

if (!$scope.formValues.License) {
return;
}

if (isNaN($scope.formValues.License[0])) {
valid = false;
}

form.extension_license.$setValidity('invalidLicense', valid);
};


initView();
}]);
.controller('ExtensionsController', ['$scope', '$state', 'ExtensionService', 'Notifications',
function($scope, $state, ExtensionService, Notifications) {

$scope.state = {
actionInProgress: false,
currentDate: moment().format('YYYY-MM-dd')
};

$scope.formValues = {
License: ''
};

function initView() {
ExtensionService.extensions(true)
.then(function onSuccess(data) {
$scope.extensions = data;
})
.catch(function onError(err) {
$scope.extensions = [];
Notifications.error('Failure', err, 'Unable to access extension store');
});
}

$scope.enableExtension = function() {
var license = $scope.formValues.License;

$scope.state.actionInProgress = true;
ExtensionService.enable(license)
.then(function onSuccess() {
Notifications.success('Extension successfully enabled');
$state.reload();
})
.catch(function onError(err) {
Notifications.error('Failure', err, 'Unable to enable extension');
})
.finally(function final() {
$scope.state.actionInProgress = false;
});
};


$scope.isValidLicenseFormat = function(form) {
var valid = true;

if (!$scope.formValues.License) {
return;
}

if (isNaN($scope.formValues.License[0])) {
valid = false;
}

form.extension_license.$setValidity('invalidLicense', valid);
};


initView();
}]);

+ 18
- 6
app/portainer/views/extensions/inspect/extension.html View File

@@ -51,11 +51,17 @@
</div>

<div style="margin-top: 15px;" ng-if="!extension.Enabled && extension.Available">
<a href="https://2-portainer.pi.bypronto.com/checkout/?add-to-cart={{ extension.ProductId }}&quantity={{ formValues.instances }}" target="_blank" class="btn btn-primary btn-sm" style="width: 100%; margin-left: 0;">
<a href="{{ extension.ShopURL }}&quantity={{ formValues.instances }}" target="_blank" class="btn btn-primary btn-sm" style="width: 100%; margin-left: 0;">
Buy
</a>
</div>

<div style="margin-top: 10px;" ng-if="!extension.Enabled && extension.Available">
<a ui-sref="portainer.extensions" class="btn btn-primary btn-sm" style="width: 100%; margin-left: 0;">
Add license key
</a>
</div>

<div style="margin-top: 15px;" ng-if="!extension.Enabled && !extension.Available">
<btn class="btn btn-primary btn-sm" style="width: 100%; margin-left: 0;" disabled>Coming soon</btn>
</div>
@@ -92,10 +98,16 @@
Description
</span>
</div>
<div class="form-group">
<span class="small text-muted">
{{ extension.Description }}
</span>
<div class="form-group" ng-if="extension.Description">
<div class="text-muted" style="font-size: 90%;" ng-bind-html="extension.Description"></div>
</div>
<div class="form-group" ng-if="!extension.Description">
<div class="small text-muted">
<p>
<i class="fa fa-exclamation-triangle orange-icon" aria-hidden="true"></i>
Description for this extension unavailable at the moment.
</p>
</div>
</div>
</rd-widget-body>
</rd-widget>
@@ -113,7 +125,7 @@
</div>
<div style="text-align: center;">
<div ng-repeat="image in extension.Images" style="margin-top: 25px; cursor: zoom-in;">
<img ng-src="{{image}}" ng-click="enlargeImage(image)"/>
<img ng-src="{{image}}" ng-click="enlargeImage(image)" style="max-width: 1024px;" />
</div>
</div>
</rd-widget-body>


+ 1
- 1
app/portainer/views/support/product/product.html View File

@@ -52,7 +52,7 @@
</div>

<div style="margin-top: 15px;" ng-disabled="!formValues.hostCount">
<a href="https://2-portainer.pi.bypronto.com/checkout/?add-to-cart={{ product.ProductId }}&quantity={{ formValues.hostCount }}" target="_blank" class="btn btn-primary btn-sm" style="width: 100%; margin-left: 0;">
<a href="https://portainer.io/checkout/?add-to-cart={{ product.ProductId }}&quantity={{ formValues.hostCount }}" target="_blank" class="btn btn-primary btn-sm" style="width: 100%; margin-left: 0;">
Buy
</a>
</div>


+ 5
- 0
assets/css/app.css View File

@@ -174,6 +174,11 @@ a[ng-click]{
box-shadow: 0 3px 10px -2px rgba(161, 170, 166, 0.5);
}

.blocklist-item--disabled {
cursor: auto;
background-color: #ececec;
}

.blocklist-item--selected {
border: 2px solid #bbbbbb;
background-color: #ececec;


BIN
assets/images/extensions_overview_diagram.png View File

Before After
Width: 512  |  Height: 196  |  Size: 79 KiB

Loading…
Cancel
Save