From d8b88d1004a4ba4bf8ee36cb711f3e5b1768c999 Mon Sep 17 00:00:00 2001 From: Richard Wei <54336863+WaysonWei@users.noreply.github.com> Date: Fri, 10 Sep 2021 14:25:49 +1200 Subject: [PATCH] feat(wizard):first UX experience for adding environment EE-1089 (#5581) * first UX experience for adding environment EE-1089 --- app/portainer/__module.js | 22 ++ .../endpoint-list/endpointList.html | 3 + app/portainer/services/nameValidator.js | 20 ++ app/portainer/views/auth/authController.js | 2 +- .../views/init/admin/initAdminController.js | 4 +- app/portainer/views/wizard/index.js | 7 + .../views/wizard/wizard-endpoints/index.js | 8 + .../wizard-endpoint-aci/index.js | 11 + .../wizard-aci.controller.js | 63 ++++++ .../wizard-endpoint-aci/wizard-aci.html | 67 ++++++ .../wizard-endpoint-docker/index.js | 11 + .../wizard-docker.controller.js | 211 ++++++++++++++++++ .../wizard-endpoint-docker/wizard-docker.html | 172 ++++++++++++++ .../wizard-endpoint-kubernetes/index.js | 11 + .../wizard-kubernetes.controller.js | 105 +++++++++ .../wizard-kubernetes.html | 65 ++++++ .../wizard-endpoint-list/index.js | 9 + .../wizard-endpoint-list.css | 41 ++++ .../wizard-endpoint-list.html | 11 + .../wizard-endpoints.controller.js | 196 ++++++++++++++++ .../wizard-endpoints/wizard-endpoints.css | 155 +++++++++++++ .../wizard-endpoints/wizard-endpoints.html | 96 ++++++++ .../wizard-endpoints/wizard-stepper/index.js | 9 + .../wizard-endpoint-type/index.js | 11 + .../wizard-endpoint-type.html | 12 + .../wizard-stepper/wizard-stepper.css | 95 ++++++++ .../wizard-stepper/wizard-stepper.html | 6 + .../views/wizard/wizard-link/index.js | 11 + .../views/wizard/wizard-link/wizard-link.css | 41 ++++ .../views/wizard/wizard-link/wizard-link.html | 7 + .../views/wizard/wizard-tls/index.js | 9 + .../views/wizard/wizard-tls/wizard-tls.html | 49 ++++ .../views/wizard/wizard-view.controller.js | 87 ++++++++ app/portainer/views/wizard/wizard-view.html | 52 +++++ 34 files changed, 1676 insertions(+), 3 deletions(-) create mode 100644 app/portainer/services/nameValidator.js create mode 100644 app/portainer/views/wizard/index.js create mode 100644 app/portainer/views/wizard/wizard-endpoints/index.js create mode 100644 app/portainer/views/wizard/wizard-endpoints/wizard-endpoint-aci/index.js create mode 100644 app/portainer/views/wizard/wizard-endpoints/wizard-endpoint-aci/wizard-aci.controller.js create mode 100644 app/portainer/views/wizard/wizard-endpoints/wizard-endpoint-aci/wizard-aci.html create mode 100644 app/portainer/views/wizard/wizard-endpoints/wizard-endpoint-docker/index.js create mode 100644 app/portainer/views/wizard/wizard-endpoints/wizard-endpoint-docker/wizard-docker.controller.js create mode 100644 app/portainer/views/wizard/wizard-endpoints/wizard-endpoint-docker/wizard-docker.html create mode 100644 app/portainer/views/wizard/wizard-endpoints/wizard-endpoint-kubernetes/index.js create mode 100644 app/portainer/views/wizard/wizard-endpoints/wizard-endpoint-kubernetes/wizard-kubernetes.controller.js create mode 100644 app/portainer/views/wizard/wizard-endpoints/wizard-endpoint-kubernetes/wizard-kubernetes.html create mode 100644 app/portainer/views/wizard/wizard-endpoints/wizard-endpoint-list/index.js create mode 100644 app/portainer/views/wizard/wizard-endpoints/wizard-endpoint-list/wizard-endpoint-list.css create mode 100644 app/portainer/views/wizard/wizard-endpoints/wizard-endpoint-list/wizard-endpoint-list.html create mode 100644 app/portainer/views/wizard/wizard-endpoints/wizard-endpoints.controller.js create mode 100644 app/portainer/views/wizard/wizard-endpoints/wizard-endpoints.css create mode 100644 app/portainer/views/wizard/wizard-endpoints/wizard-endpoints.html create mode 100644 app/portainer/views/wizard/wizard-endpoints/wizard-stepper/index.js create mode 100644 app/portainer/views/wizard/wizard-endpoints/wizard-stepper/wizard-endpoint-type/index.js create mode 100644 app/portainer/views/wizard/wizard-endpoints/wizard-stepper/wizard-endpoint-type/wizard-endpoint-type.html create mode 100644 app/portainer/views/wizard/wizard-endpoints/wizard-stepper/wizard-stepper.css create mode 100644 app/portainer/views/wizard/wizard-endpoints/wizard-stepper/wizard-stepper.html create mode 100644 app/portainer/views/wizard/wizard-link/index.js create mode 100644 app/portainer/views/wizard/wizard-link/wizard-link.css create mode 100644 app/portainer/views/wizard/wizard-link/wizard-link.html create mode 100644 app/portainer/views/wizard/wizard-tls/index.js create mode 100644 app/portainer/views/wizard/wizard-tls/wizard-tls.html create mode 100644 app/portainer/views/wizard/wizard-view.controller.js create mode 100644 app/portainer/views/wizard/wizard-view.html diff --git a/app/portainer/__module.js b/app/portainer/__module.js index a2432ba9b..3ee842a5a 100644 --- a/app/portainer/__module.js +++ b/app/portainer/__module.js @@ -251,6 +251,26 @@ angular.module('portainer.app', ['portainer.oauth', componentsModule, settingsMo }, }; + const wizard = { + name: 'portainer.wizard', + url: '/wizard', + views: { + 'content@': { + component: 'wizardView', + }, + }, + }; + + const wizardEndpoints = { + name: 'portainer.wizard.endpoints', + url: '/endpoints', + views: { + 'content@': { + component: 'wizardEndpoints', + }, + }, + }; + var initEndpoint = { name: 'portainer.init.endpoint', url: '/endpoint', @@ -410,6 +430,8 @@ angular.module('portainer.app', ['portainer.oauth', componentsModule, settingsMo $stateRegistryProvider.register(groupCreation); $stateRegistryProvider.register(home); $stateRegistryProvider.register(init); + $stateRegistryProvider.register(wizard); + $stateRegistryProvider.register(wizardEndpoints); $stateRegistryProvider.register(initEndpoint); $stateRegistryProvider.register(initAdmin); $stateRegistryProvider.register(registries); diff --git a/app/portainer/components/endpoint-list/endpointList.html b/app/portainer/components/endpoint-list/endpointList.html index cd2b2a5a8..7f835a95b 100644 --- a/app/portainer/components/endpoint-list/endpointList.html +++ b/app/portainer/components/endpoint-list/endpointList.html @@ -6,6 +6,9 @@
+
Click on an environment to manage
diff --git a/app/portainer/services/nameValidator.js b/app/portainer/services/nameValidator.js new file mode 100644 index 000000000..71dd94be4 --- /dev/null +++ b/app/portainer/services/nameValidator.js @@ -0,0 +1,20 @@ +import angular from 'angular'; + +angular.module('portainer.app').factory('NameValidator', NameValidatorFactory); +/* @ngInject */ +function NameValidatorFactory(EndpointService, Notifications) { + return { + validateEnvironmentName, + }; + + async function validateEnvironmentName(environmentName) { + try { + const endpoints = await EndpointService.endpoints(); + const endpointArray = endpoints.value; + const nameDuplicated = endpointArray.filter((item) => item.Name === environmentName); + return nameDuplicated.length > 0; + } catch (err) { + Notifications.error('Failure', err, 'Unable to retrieve environment details'); + } + } +} diff --git a/app/portainer/views/auth/authController.js b/app/portainer/views/auth/authController.js index 1735c66bd..5a095cc2f 100644 --- a/app/portainer/views/auth/authController.js +++ b/app/portainer/views/auth/authController.js @@ -124,7 +124,7 @@ class AuthenticationController { const isAdmin = this.Authentication.isAdmin(); if (endpoints.value.length === 0 && isAdmin) { - return this.$state.go('portainer.init.endpoint'); + return this.$state.go('portainer.wizard'); } else { return this.$state.go('portainer.home'); } diff --git a/app/portainer/views/init/admin/initAdminController.js b/app/portainer/views/init/admin/initAdminController.js index 31b0c34bc..9ab139442 100644 --- a/app/portainer/views/init/admin/initAdminController.js +++ b/app/portainer/views/init/admin/initAdminController.js @@ -54,7 +54,7 @@ angular.module('portainer.app').controller('InitAdminController', [ }) .then(function success(data) { if (data.value.length === 0) { - $state.go('portainer.init.endpoint'); + $state.go('portainer.wizard'); } else { $state.go('portainer.home'); } @@ -71,7 +71,7 @@ angular.module('portainer.app').controller('InitAdminController', [ UserService.administratorExists() .then(function success(exists) { if (exists) { - $state.go('portainer.home'); + $state.go('portainer.wizard'); } }) .catch(function error(err) { diff --git a/app/portainer/views/wizard/index.js b/app/portainer/views/wizard/index.js new file mode 100644 index 000000000..d343c724a --- /dev/null +++ b/app/portainer/views/wizard/index.js @@ -0,0 +1,7 @@ +import angular from 'angular'; +import controller from './wizard-view.controller.js'; + +angular.module('portainer.app').component('wizardView', { + templateUrl: './wizard-view.html', + controller, +}); diff --git a/app/portainer/views/wizard/wizard-endpoints/index.js b/app/portainer/views/wizard/wizard-endpoints/index.js new file mode 100644 index 000000000..ea92654c6 --- /dev/null +++ b/app/portainer/views/wizard/wizard-endpoints/index.js @@ -0,0 +1,8 @@ +import angular from 'angular'; +import controller from './wizard-endpoints.controller.js'; +import './wizard-endpoints.css'; + +angular.module('portainer.app').component('wizardEndpoints', { + templateUrl: './wizard-endpoints.html', + controller, +}); diff --git a/app/portainer/views/wizard/wizard-endpoints/wizard-endpoint-aci/index.js b/app/portainer/views/wizard/wizard-endpoints/wizard-endpoint-aci/index.js new file mode 100644 index 000000000..5b7a5d254 --- /dev/null +++ b/app/portainer/views/wizard/wizard-endpoints/wizard-endpoint-aci/index.js @@ -0,0 +1,11 @@ +import angular from 'angular'; +import controller from './wizard-aci.controller.js'; + +angular.module('portainer.app').component('wizardAci', { + templateUrl: './wizard-aci.html', + controller, + bindings: { + onUpdate: '<', + onAnalytics: '<', + }, +}); diff --git a/app/portainer/views/wizard/wizard-endpoints/wizard-endpoint-aci/wizard-aci.controller.js b/app/portainer/views/wizard/wizard-endpoints/wizard-endpoint-aci/wizard-aci.controller.js new file mode 100644 index 000000000..a698d7775 --- /dev/null +++ b/app/portainer/views/wizard/wizard-endpoints/wizard-endpoint-aci/wizard-aci.controller.js @@ -0,0 +1,63 @@ +import { buildOption } from '@/portainer/components/box-selector'; + +export default class WizardAciController { + /* @ngInject */ + constructor($async, EndpointService, Notifications, NameValidator) { + this.$async = $async; + this.EndpointService = EndpointService; + this.Notifications = Notifications; + this.NameValidator = NameValidator; + } + + addAciEndpoint() { + return this.$async(async () => { + const { name, azureApplicationId, azureTenantId, azureAuthenticationKey } = this.formValues; + const groupId = 1; + const tagIds = []; + + try { + this.state.actionInProgress = true; + // Check name is duplicated or not + let nameUsed = await this.NameValidator.validateEnvironmentName(name); + if (nameUsed) { + this.Notifications.error('Failure', true, 'This name is been used, please try another one'); + return; + } + await this.EndpointService.createAzureEndpoint(name, azureApplicationId, azureTenantId, azureAuthenticationKey, groupId, tagIds); + this.Notifications.success('Environment connected', name); + this.clearForm(); + this.onUpdate(); + this.onAnalytics('aci-api'); + } catch (err) { + this.Notifications.error('Failure', err, 'Unable to connect your environment'); + } finally { + this.state.actionInProgress = false; + } + }); + } + + clearForm() { + this.formValues = { + name: '', + azureApplicationId: '', + azureTenantId: '', + azureAuthenticationKey: '', + }; + } + + $onInit() { + return this.$async(async () => { + this.state = { + actionInProgress: false, + endpointType: 'api', + availableOptions: [buildOption('API', 'fa fa-bolt', 'API', '', 'api')], + }; + this.formValues = { + name: '', + azureApplicationId: '', + azureTenantId: '', + azureAuthenticationKey: '', + }; + }); + } +} diff --git a/app/portainer/views/wizard/wizard-endpoints/wizard-endpoint-aci/wizard-aci.html b/app/portainer/views/wizard/wizard-endpoints/wizard-endpoint-aci/wizard-aci.html new file mode 100644 index 000000000..0232d4bcb --- /dev/null +++ b/app/portainer/views/wizard/wizard-endpoints/wizard-endpoint-aci/wizard-aci.html @@ -0,0 +1,67 @@ +
+ + +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+
+ +
+
+
diff --git a/app/portainer/views/wizard/wizard-endpoints/wizard-endpoint-docker/index.js b/app/portainer/views/wizard/wizard-endpoints/wizard-endpoint-docker/index.js new file mode 100644 index 000000000..3ec63022e --- /dev/null +++ b/app/portainer/views/wizard/wizard-endpoints/wizard-endpoint-docker/index.js @@ -0,0 +1,11 @@ +import angular from 'angular'; +import controller from './wizard-docker.controller.js'; + +angular.module('portainer.app').component('wizardDocker', { + templateUrl: './wizard-docker.html', + controller, + bindings: { + onUpdate: '<', + onAnalytics: '<', + }, +}); diff --git a/app/portainer/views/wizard/wizard-endpoints/wizard-endpoint-docker/wizard-docker.controller.js b/app/portainer/views/wizard/wizard-endpoints/wizard-endpoint-docker/wizard-docker.controller.js new file mode 100644 index 000000000..5d82d6197 --- /dev/null +++ b/app/portainer/views/wizard/wizard-endpoints/wizard-endpoint-docker/wizard-docker.controller.js @@ -0,0 +1,211 @@ +//import { getAgentShortVersion } from 'Portainer/views/endpoints/helpers'; +import { PortainerEndpointCreationTypes } from 'Portainer/models/endpoint/models'; +import { buildOption } from '@/portainer/components/box-selector'; +import { EndpointSecurityFormData } from 'Portainer/components/endpointSecurity/porEndpointSecurityModel'; + +export default class WizardDockerController { + /* @ngInject */ + constructor($async, EndpointService, StateManager, Notifications, clipboard, $filter, NameValidator) { + this.$async = $async; + this.EndpointService = EndpointService; + this.StateManager = StateManager; + this.Notifications = Notifications; + this.clipboard = clipboard; + this.$filter = $filter; + this.NameValidator = NameValidator; + } + + copyLinuxCommand() { + this.clipboard.copyText(this.command.linuxCommand); + $('#linuxCommandNotification').show().fadeOut(2500); + } + + copyWinCommand() { + this.clipboard.copyText(this.command.winCommand); + $('#winCommandNotification').show().fadeOut(2500); + } + + copyLinuxSocket() { + this.clipboard.copyText(this.command.linuxSocket); + $('#linuxSocketNotification').show().fadeOut(2500); + } + + copyWinSocket() { + this.clipboard.copyText(this.command.winSocket); + $('#winSocketNotification').show().fadeOut(2500); + } + + onChangeFile(file) { + this.formValues.securityFormData = file; + } + + // connect docker environment + connectEnvironment(type) { + return this.$async(async () => { + const name = this.formValues.name; + const url = this.$filter('stripprotocol')(this.formValues.url); + const publicUrl = url.split(':')[0]; + const overrideUrl = this.formValues.socketPath; + const groupId = this.formValues.groupId; + const tagIds = this.formValues.tagIds; + const securityData = this.formValues.securityFormData; + const socketUrl = this.formValues.overrideSocket ? overrideUrl : url; + + var creationType = null; + + if (type === 'agent') { + creationType = PortainerEndpointCreationTypes.AgentEnvironment; + } + + if (type === 'api') { + creationType = PortainerEndpointCreationTypes.LocalDockerEnvironment; + } + + // Check name is duplicated or not + const nameUsed = await this.NameValidator.validateEnvironmentName(name); + if (nameUsed) { + this.Notifications.error('Failure', true, 'This name is been used, please try another one'); + return; + } + switch (type) { + case 'agent': + await this.addDockerAgentEndpoint(name, creationType, url, publicUrl, groupId, tagIds); + break; + case 'api': + await this.addDockerApiEndpoint(name, creationType, url, publicUrl, groupId, tagIds, securityData); + break; + case 'socket': + await this.addDockerLocalEndpoint(name, socketUrl, publicUrl, groupId, tagIds); + break; + } + }); + } + + // Docker Agent Endpoint + async addDockerAgentEndpoint(name, creationType, url, publicUrl, groupId, tagIds) { + const tsl = true; + const tlsSkipVerify = true; + const tlsSkipClientVerify = true; + const tlsCaFile = null; + const tlsCertFile = null; + const tlsKeyFile = null; + + await this.addRemoteEndpoint(name, creationType, url, publicUrl, groupId, tagIds, tsl, tlsSkipVerify, tlsSkipClientVerify, tlsCaFile, tlsCertFile, tlsKeyFile); + } + + // Docker Api Endpoint + async addDockerApiEndpoint(name, creationType, url, publicUrl, groupId, tagIds, securityData) { + const tsl = this.formValues.tls; + const tlsSkipVerify = this.formValues.skipCertification; + const tlsSkipClientVerify = this.formValues.skipCertification; + const tlsCaFile = tlsSkipVerify ? null : securityData.TLSCACert; + const tlsCertFile = tlsSkipClientVerify ? null : securityData.TLSCert; + const tlsKeyFile = tlsSkipClientVerify ? null : securityData.TLSKey; + + await this.addRemoteEndpoint(name, creationType, url, publicUrl, groupId, tagIds, tsl, tlsSkipVerify, tlsSkipClientVerify, tlsCaFile, tlsCertFile, tlsKeyFile); + } + + async addDockerLocalEndpoint(name, url, publicUrl, groupId, tagIds) { + this.state.actionInProgress = true; + try { + await this.EndpointService.createLocalEndpoint(name, url, publicUrl, groupId, tagIds); + this.Notifications.success('Environment connected', name); + this.clearForm(); + this.onUpdate(); + } catch (err) { + this.Notifications.error('Failure', err, 'Unable to connect your environment'); + } finally { + this.state.actionInProgress = false; + } + } + + async addRemoteEndpoint(name, creationType, url, publicURL, groupId, tagIds, TLS, tlsSkipVerify, tlsSkipClientVerify, tlsCaFile, tlsCertFile, tlsKeyFile) { + this.state.actionInProgress = true; + try { + await this.EndpointService.createRemoteEndpoint( + name, + creationType, + url, + publicURL, + groupId, + tagIds, + TLS, + tlsSkipVerify, + tlsSkipClientVerify, + tlsCaFile, + tlsCertFile, + tlsKeyFile + ); + this.Notifications.success('Environment connected', name); + this.clearForm(); + this.onUpdate(); + + if (creationType === PortainerEndpointCreationTypes.AgentEnvironment) { + this.onAnalytics('docker-agent'); + } + + if (creationType === PortainerEndpointCreationTypes.LocalDockerEnvironment) { + this.onAnalytics('docker-api'); + } + } catch (err) { + this.Notifications.error('Failure', err, 'Unable to connect your environment'); + } finally { + this.state.actionInProgress = false; + } + } + + clearForm() { + this.formValues = { + name: '', + url: '', + publicURL: '', + groupId: 1, + tagIds: [], + environmentUrl: '', + dockerApiurl: '', + socketPath: '', + overrodeSocket: false, + skipCertification: false, + tls: false, + securityFormData: new EndpointSecurityFormData(), + }; + } + + $onInit() { + return this.$async(async () => { + this.state = { + endpointType: 'agent', + ConnectSocket: false, + actionInProgress: false, + endpoints: [], + availableOptions: [ + buildOption('Agent', 'fa fa-bolt', 'Agent', '', 'agent'), + buildOption('API', 'fa fa-cloud', 'API', '', 'api'), + buildOption('Socket', 'fab fa-docker', 'Socket', '', 'socket'), + ], + }; + + this.formValues = { + name: '', + url: '', + publicURL: '', + groupId: 1, + tagIds: [], + environmentUrl: '', + dockerApiurl: '', + socketPath: '', + overrideSocket: false, + skipCertification: false, + tls: false, + securityFormData: new EndpointSecurityFormData(), + }; + + this.command = { + linuxCommand: `curl -L https://downloads.portainer.io/agent-stack.yml -o agent-stack.yml && docker stack deploy --compose-file=agent-stack.yml portainer-agent `, + winCommand: `curl -L https://downloads.portainer.io/agent-stack-windows.yml -o agent-stack-windows.yml && docker stack deploy --compose-file=agent-stack-windows.yml portainer-agent `, + linuxSocket: `-v "/var/run/docker.sock:/var/run/docker.sock" `, + winSocket: `-v \.\pipe\docker_engine:\.\pipe\docker_engine `, + }; + }); + } +} diff --git a/app/portainer/views/wizard/wizard-endpoints/wizard-endpoint-docker/wizard-docker.html b/app/portainer/views/wizard/wizard-endpoints/wizard-endpoint-docker/wizard-docker.html new file mode 100644 index 000000000..18f1884ca --- /dev/null +++ b/app/portainer/views/wizard/wizard-endpoints/wizard-endpoint-docker/wizard-docker.html @@ -0,0 +1,172 @@ +
+ + + +
+
+
+ + +
CLI script for installing agent on your Linux environment with Docker Swarm
{{ $ctrl.command.linuxCommand + }}
+
+ + +
CLI script for installing agent on your Windows environment with Docker Swarm
{{ $ctrl.command.winCommand + }}
+
+
+
+ +
+ + +
When using the socket, ensure that you have started the Portainer container with the following Docker flag on Linux
{{ $ctrl.command.linuxSocket }}
+
+ + +
When using the socket, ensure that you have started the Portainer container with the following Docker flag on Windows
{{ $ctrl.command.winSocket }}
+
+
+
+
+
+ + +
+ +
+ +
+
+ +
+ + +
+ +
+
+ +
+
+ + +
+ +
+
+ +
+ +
+ +
+ +
+ +
+ +
+
+ +
+
+ + +
+ +
+
+ +
+ +
+
+
+
+ +
+
+ + + +
+
+
diff --git a/app/portainer/views/wizard/wizard-endpoints/wizard-endpoint-kubernetes/index.js b/app/portainer/views/wizard/wizard-endpoints/wizard-endpoint-kubernetes/index.js new file mode 100644 index 000000000..86d40a5aa --- /dev/null +++ b/app/portainer/views/wizard/wizard-endpoints/wizard-endpoint-kubernetes/index.js @@ -0,0 +1,11 @@ +import angular from 'angular'; +import controller from './wizard-kubernetes.controller.js'; + +angular.module('portainer.app').component('wizardKubernetes', { + templateUrl: './wizard-kubernetes.html', + controller, + bindings: { + onUpdate: '<', + onAnalytics: '<', + }, +}); diff --git a/app/portainer/views/wizard/wizard-endpoints/wizard-endpoint-kubernetes/wizard-kubernetes.controller.js b/app/portainer/views/wizard/wizard-endpoints/wizard-endpoint-kubernetes/wizard-kubernetes.controller.js new file mode 100644 index 000000000..0971e8b9b --- /dev/null +++ b/app/portainer/views/wizard/wizard-endpoints/wizard-endpoint-kubernetes/wizard-kubernetes.controller.js @@ -0,0 +1,105 @@ +import { PortainerEndpointCreationTypes } from 'Portainer/models/endpoint/models'; +//import { getAgentShortVersion } from 'Portainer/views/endpoints/helpers'; +import { buildOption } from '@/portainer/components/box-selector'; + +export default class WizardKubernetesController { + /* @ngInject */ + constructor($async, EndpointService, StateManager, Notifications, $filter, clipboard, NameValidator) { + this.$async = $async; + this.EndpointService = EndpointService; + this.StateManager = StateManager; + this.Notifications = Notifications; + this.$filter = $filter; + this.clipboard = clipboard; + this.NameValidator = NameValidator; + } + + addKubernetesAgent() { + return this.$async(async () => { + const name = this.state.formValues.name; + const groupId = 1; + const tagIds = []; + const url = this.$filter('stripprotocol')(this.state.formValues.url); + const publicUrl = url.split(':')[0]; + const creationType = PortainerEndpointCreationTypes.AgentEnvironment; + const tls = true; + const tlsSkipVerify = true; + const tlsSkipClientVerify = true; + const tlsCaFile = null; + const tlsCertFile = null; + const tlsKeyFile = null; + + // Check name is duplicated or not + let nameUsed = await this.NameValidator.validateEnvironmentName(name); + if (nameUsed) { + this.Notifications.error('Failure', true, 'This name is been used, please try another one'); + return; + } + await this.addRemoteEndpoint(name, creationType, url, publicUrl, groupId, tagIds, tls, tlsSkipVerify, tlsSkipClientVerify, tlsCaFile, tlsCertFile, tlsKeyFile); + }); + } + + async addRemoteEndpoint(name, creationType, url, publicURL, groupId, tagIds, tls, tlsSkipVerify, tlsSkipClientVerify, tlsCaFile, tlsCertFile, tlsKeyFile) { + this.state.actionInProgress = true; + try { + await this.EndpointService.createRemoteEndpoint( + name, + creationType, + url, + publicURL, + groupId, + tagIds, + tls, + tlsSkipVerify, + tlsSkipClientVerify, + tlsCaFile, + tlsCertFile, + tlsKeyFile + ); + this.Notifications.success('Environment connected', name); + this.clearForm(); + this.onUpdate(); + this.onAnalytics('kubernetes-agent'); + } catch (err) { + this.Notifications.error('Failure', err, 'Unable to conect your environment'); + } finally { + this.state.actionInProgress = false; + } + } + + copyLoadBalancer() { + this.clipboard.copyText(this.command.loadBalancer); + $('#loadBalancerNotification').show().fadeOut(2500); + } + + copyNodePort() { + this.clipboard.copyText(this.command.nodePort); + $('#nodePortNotification').show().fadeOut(2500); + } + + clearForm() { + this.state.formValues = { + name: '', + url: '', + }; + } + + $onInit() { + return this.$async(async () => { + this.state = { + endpointType: 'agent', + actionInProgress: false, + formValues: { + name: '', + url: '', + }, + availableOptions: [buildOption('Agent', 'fa fa-bolt', 'Agent', '', 'agent')], + }; + + this.command = { + loadBalancer: `curl -L https://downloads.portainer.io/portainer-agent-k8s-lb.yaml -o portainer-agent-k8s.yaml; kubectl apply -f portainer-agent-k8s.yaml `, + nodePort: `curl -L https://downloads.portainer.io/portainer-agent-k8s-nodeport.yaml -o portainer-agent-k8s.yaml; kubectl apply -f portainer-agent-k8s.yaml `, + }; + }); + } +} diff --git a/app/portainer/views/wizard/wizard-endpoints/wizard-endpoint-kubernetes/wizard-kubernetes.html b/app/portainer/views/wizard/wizard-endpoints/wizard-endpoint-kubernetes/wizard-kubernetes.html new file mode 100644 index 000000000..cd6cac537 --- /dev/null +++ b/app/portainer/views/wizard/wizard-endpoints/wizard-endpoint-kubernetes/wizard-kubernetes.html @@ -0,0 +1,65 @@ +
+ + +
+
+
+ + +
CLI script for installing agent on your endpoint
{{ $ctrl.command.loadBalancer + }}
+
+ + +
CLI script for installing agent on your endpoint
{{ $ctrl.command.nodePort + }}
+
+
+
+
+
+ +
+ +
+ +
+
+ +
+ + +
+ +
+
+ +
+
+ +
+
+
diff --git a/app/portainer/views/wizard/wizard-endpoints/wizard-endpoint-list/index.js b/app/portainer/views/wizard/wizard-endpoints/wizard-endpoint-list/index.js new file mode 100644 index 000000000..26c5f21db --- /dev/null +++ b/app/portainer/views/wizard/wizard-endpoints/wizard-endpoint-list/index.js @@ -0,0 +1,9 @@ +import angular from 'angular'; +import './wizard-endpoint-list.css'; + +angular.module('portainer.app').component('wizardEndpointList', { + templateUrl: './wizard-endpoint-list.html', + bindings: { + endpointList: '<', + }, +}); diff --git a/app/portainer/views/wizard/wizard-endpoints/wizard-endpoint-list/wizard-endpoint-list.css b/app/portainer/views/wizard/wizard-endpoints/wizard-endpoint-list/wizard-endpoint-list.css new file mode 100644 index 000000000..82be4836e --- /dev/null +++ b/app/portainer/views/wizard/wizard-endpoints/wizard-endpoint-list/wizard-endpoint-list.css @@ -0,0 +1,41 @@ +.wizard-list-wrapper { + display: grid; + grid-template-columns: 50px 1fr; + grid-template-areas: + 'image title' + 'image subtitle' + 'image type'; + border: 1px solid rgb(221, 221, 221); + border-radius: 5px; + margin-bottom: 10px; + margin-top: 3px; + padding: 10px; + box-shadow: 0 3px 10px -2px rgb(161 170 166 / 20%); +} + +.wizard-list-image { + grid-area: image; + font-size: 35px; + color: #337ab7; +} + +.wizard-list-title { + grid-column: title; + padding: 0px 5px; + font-weight: bold; + font-size: 14px; +} + +.wizard-list-subtitle { + grid-column: subtitle; + padding: 0px 5px; + font-size: 10px; + color: rgb(129, 129, 129); +} + +.wizard-list-type { + grid-column: type; + padding: 0px 5px; + font-size: 10px; + color: rgb(129, 129, 129); +} diff --git a/app/portainer/views/wizard/wizard-endpoints/wizard-endpoint-list/wizard-endpoint-list.html b/app/portainer/views/wizard/wizard-endpoints/wizard-endpoint-list/wizard-endpoint-list.html new file mode 100644 index 000000000..2feaa7c74 --- /dev/null +++ b/app/portainer/views/wizard/wizard-endpoints/wizard-endpoint-list/wizard-endpoint-list.html @@ -0,0 +1,11 @@ + + + +
+
+
{{ endpoint.Name }}
+
URL: {{ endpoint.URL | stripprotocol }}
+
Type: {{ endpoint.Type | endpointtypename }}
+
+
+
diff --git a/app/portainer/views/wizard/wizard-endpoints/wizard-endpoints.controller.js b/app/portainer/views/wizard/wizard-endpoints/wizard-endpoints.controller.js new file mode 100644 index 000000000..57a164304 --- /dev/null +++ b/app/portainer/views/wizard/wizard-endpoints/wizard-endpoints.controller.js @@ -0,0 +1,196 @@ +export default class WizardEndpointsController { + /* @ngInject */ + constructor($async, $scope, $state, EndpointService, $analytics) { + this.$async = $async; + this.$scope = $scope; + this.$state = $state; + this.EndpointService = EndpointService; + this.$analytics = $analytics; + + this.updateEndpoint = this.updateEndpoint.bind(this); + this.addAnalytics = this.addAnalytics.bind(this); + } + /** + * WIZARD ENDPOINT SECTION + */ + + async updateEndpoint() { + const updateEndpoints = await this.EndpointService.endpoints(); + this.endpoints = updateEndpoints.value; + } + + startWizard() { + const options = this.state.options; + this.state.selections = options.filter((item) => item.selected === true); + this.state.maxStep = this.state.selections.length; + + if (this.state.selections.length !== 0) { + this.state.section = this.state.selections[this.state.currentStep].endpoint; + this.state.selections[this.state.currentStep].stage = 'active'; + } + + if (this.state.currentStep === this.state.maxStep - 1) { + this.state.nextStep = 'Finish'; + } + + this.$analytics.eventTrack('endpoint-wizard-endpoint-select', { + category: 'portainer', + metadata: { + environment: this.state.analytics.docker + this.state.analytics.kubernetes + this.state.analytics.aci, + }, + }); + this.state.currentStep++; + } + + previousStep() { + this.state.section = this.state.selections[this.state.currentStep - 2].endpoint; + this.state.selections[this.state.currentStep - 2].stage = 'active'; + this.state.selections[this.state.currentStep - 1].stage = ''; + this.state.nextStep = 'Next Step'; + this.state.currentStep--; + } + + async nextStep() { + if (this.state.currentStep >= this.state.maxStep - 1) { + this.state.nextStep = 'Finish'; + } + if (this.state.currentStep === this.state.maxStep) { + // the Local Endpoint Counter from endpoints array due to including Local Endpoint been added Automatic before Wizard start + const endpointsAdded = await this.EndpointService.endpoints(); + const endpointsArray = endpointsAdded.value; + const filter = endpointsArray.filter((item) => item.Type === 1 || item.Type === 5); + // NOTICE: This is the temporary fix for excluded docker api endpoint been counted as local endpoint + this.state.counter.localEndpoint = filter.length - this.state.counter.dockerApi; + + this.$analytics.eventTrack('endpoint-wizard-environment-add-finish', { + category: 'portainer', + metadata: { + 'docker-agent': this.state.counter.dockerAgent, + 'docker-api': this.state.counter.dockerApi, + 'kubernetes-agent': this.state.counter.kubernetesAgent, + 'aci-api': this.state.counter.aciApi, + 'local-endpoint': this.state.counter.localEndpoint, + }, + }); + this.$state.go('portainer.home'); + } else { + this.state.section = this.state.selections[this.state.currentStep].endpoint; + this.state.selections[this.state.currentStep].stage = 'active'; + this.state.selections[this.state.currentStep - 1].stage = 'completed'; + this.state.currentStep++; + } + } + + addAnalytics(endpoint) { + switch (endpoint) { + case 'docker-agent': + this.state.counter.dockerAgent++; + break; + case 'docker-api': + this.state.counter.dockerApi++; + break; + case 'kubernetes-agent': + this.state.counter.kubernetesAgent++; + break; + case 'aci-api': + this.state.counter.aciApi++; + break; + } + } + + endpointSelect(endpoint) { + switch (endpoint) { + case 'docker': + if (this.state.options[0].selected) { + this.state.options[0].selected = false; + this.state.dockerActive = ''; + this.state.analytics.docker = ''; + } else { + this.state.options[0].selected = true; + this.state.dockerActive = 'wizard-active'; + this.state.analytics.docker = 'Docker/'; + } + break; + case 'kubernetes': + if (this.state.options[1].selected) { + this.state.options[1].selected = false; + this.state.kubernetesActive = ''; + this.state.analytics.kubernetes = ''; + } else { + this.state.options[1].selected = true; + this.state.kubernetesActive = 'wizard-active'; + this.state.analytics.kubernetes = 'Kubernetes/'; + } + break; + case 'aci': + if (this.state.options[2].selected) { + this.state.options[2].selected = false; + this.state.aciActive = ''; + this.state.analytics.aci = ''; + } else { + this.state.options[2].selected = true; + this.state.aciActive = 'wizard-active'; + this.state.analytics.aci = 'ACI'; + } + break; + } + const options = this.state.options; + this.state.selections = options.filter((item) => item.selected === true); + } + + $onInit() { + return this.$async(async () => { + (this.state = { + currentStep: 0, + section: '', + dockerActive: '', + kubernetesActive: '', + aciActive: '', + maxStep: '', + previousStep: 'Previous', + nextStep: 'Next Step', + selections: [], + analytics: { + docker: '', + kubernetes: '', + aci: '', + }, + counter: { + dockerAgent: 0, + dockerApi: 0, + kubernetesAgent: 0, + aciApi: 0, + localEndpoint: 0, + }, + options: [ + { + endpoint: 'docker', + selected: false, + stage: '', + nameClass: 'docker', + icon: 'fab fa-docker', + }, + { + endpoint: 'kubernetes', + selected: false, + stage: '', + nameClass: 'kubernetes', + icon: 'fas fa-dharmachakra', + }, + { + endpoint: 'aci', + selected: false, + stage: '', + nameClass: 'aci', + icon: 'fab fa-microsoft', + }, + ], + selectOption: '', + }), + (this.endpoints = []); + + const endpoints = await this.EndpointService.endpoints(); + this.endpoints = endpoints.value; + }); + } +} diff --git a/app/portainer/views/wizard/wizard-endpoints/wizard-endpoints.css b/app/portainer/views/wizard/wizard-endpoints/wizard-endpoints.css new file mode 100644 index 000000000..8b6a8a56f --- /dev/null +++ b/app/portainer/views/wizard/wizard-endpoints/wizard-endpoints.css @@ -0,0 +1,155 @@ +.wizard-endpoints { + display: block; + width: 200px; + height: 300px; + border: 1px solid rgb(163, 163, 163); + border-radius: 5px; + float: left; + margin-right: 15px; + padding: 25px 20px; + cursor: pointer; + box-shadow: 0 3px 10px -2px rgb(161 170 166 / 60%); +} + +.wizard-endpoints:hover { + box-shadow: 0 3px 10px -2px rgb(161 170 166 / 80%); + border: 1px solid #3ca4ff; + color: #337ab7; +} + +.wizard-active:hover { + color: #fff; +} + +.wizard-active { + background: #337ab7; + color: #fff; + border: 1px solid #3ca4ff; + box-shadow: 0 3px 10px -2px rgb(161 170 166 / 80%); +} + +.wizard-form-required { + color: rgb(255, 24, 24); + padding: 0px 5px; +} + +.wizard-form { + margin-top: 40px; +} + +.wizard-code { + margin-right: 15px; +} + +.wizard-action { + margin-top: 20px; +} + +.wizard-connect-button { + margin-left: 0px !important; + margin-top: 40px; +} + +.wizard-copy-button { + color: #444; + cursor: pointer; +} + +.wizard-step-action { + padding-top: 40px; + padding-bottom: 40px; + text-align: right; + border-top: 1px solid #777; +} + +.next-btn { + float: right; +} + +.previous-btn { + float: left; + margin-left: 0px !important; +} + +.wizard-wrapper { + display: grid; + grid-template-columns: 1fr 400px; + grid-template-areas: + 'main sidebar' + 'footer sidebar'; +} + +.wizard-main { + grid-column: main; +} + +.wizard-aside { + grid-column: sidebar; + margin-right: 15px; +} + +.wizard-footer { + grid-column: footer; +} + +.wizard-endpoint-section { + padding-right: 10px; +} + +.wizard-main-title { + margin-bottom: 10px; +} + +.wizard-env-section { + display: block; + padding: 10px; + border: 1px solid red; + width: 80%; + margin-left: auto; + margin-right: auto; + height: 600px; + text-align: center; +} + +.wizard-env-icon { + margin-left: auto; + margin-right: auto; +} + +.wizard-content-wrapper { + position: relative; + left: 50%; +} + +.wizard-content { + float: left; + position: relative; + left: -50%; +} + +.wizard-section { + display: grid; + justify-content: left; + align-content: left; + gap: 10px; + grid-auto-flow: column; + margin-bottom: 20px; +} + +.wizard-section-title { + font-size: 32px; + margin-top: 30px; + margin-bottom: 15px; +} +.wizard-setion-subtitle { + font-size: 18px; +} + +.wizard-section-action { + margin-top: 50px; + margin-bottom: 20px; +} + +.no-margin { + margin-left: 0px; +} diff --git a/app/portainer/views/wizard/wizard-endpoints/wizard-endpoints.html b/app/portainer/views/wizard/wizard-endpoints/wizard-endpoints.html new file mode 100644 index 000000000..d54eb5bd7 --- /dev/null +++ b/app/portainer/views/wizard/wizard-endpoints/wizard-endpoints.html @@ -0,0 +1,96 @@ + + + Environment Wizard + + +
+
+
+
+ + + + + + + +
Connect to your {{ $ctrl.state.section }} environment
+ +
+ + + +
+ +
+ + +
+
+
+
+
+
+
+ +
+
+ +
+
+ + + +
+
+ Select your environment(s) +
+
+ You can onboard different types of environments, select all that apply. +
+
+ + + + + +
+ +
+
+ +
+
+
+
+
+
+
diff --git a/app/portainer/views/wizard/wizard-endpoints/wizard-stepper/index.js b/app/portainer/views/wizard/wizard-endpoints/wizard-stepper/index.js new file mode 100644 index 000000000..5e0e78b0b --- /dev/null +++ b/app/portainer/views/wizard/wizard-endpoints/wizard-stepper/index.js @@ -0,0 +1,9 @@ +import angular from 'angular'; +import './wizard-stepper.css'; + +angular.module('portainer.app').component('wizardStepper', { + templateUrl: './wizard-stepper.html', + bindings: { + endpointSelections: '<', + }, +}); diff --git a/app/portainer/views/wizard/wizard-endpoints/wizard-stepper/wizard-endpoint-type/index.js b/app/portainer/views/wizard/wizard-endpoints/wizard-stepper/wizard-endpoint-type/index.js new file mode 100644 index 000000000..fb51b1401 --- /dev/null +++ b/app/portainer/views/wizard/wizard-endpoints/wizard-stepper/wizard-endpoint-type/index.js @@ -0,0 +1,11 @@ +import angular from 'angular'; + +angular.module('portainer.app').component('wizardEndpointType', { + templateUrl: './wizard-endpoint-type.html', + bindings: { + endpointTitle: '@', + description: '@', + icon: '@', + active: '<', + }, +}); diff --git a/app/portainer/views/wizard/wizard-endpoints/wizard-stepper/wizard-endpoint-type/wizard-endpoint-type.html b/app/portainer/views/wizard/wizard-endpoints/wizard-stepper/wizard-endpoint-type/wizard-endpoint-type.html new file mode 100644 index 000000000..3088e3616 --- /dev/null +++ b/app/portainer/views/wizard/wizard-endpoints/wizard-stepper/wizard-endpoint-type/wizard-endpoint-type.html @@ -0,0 +1,12 @@ +
+
+
+
+ +
+

{{ $ctrl.endpointTitle }}

+
{{ $ctrl.description }}
+
+
+
+
diff --git a/app/portainer/views/wizard/wizard-endpoints/wizard-stepper/wizard-stepper.css b/app/portainer/views/wizard/wizard-endpoints/wizard-stepper/wizard-stepper.css new file mode 100644 index 000000000..2bff2354e --- /dev/null +++ b/app/portainer/views/wizard/wizard-endpoints/wizard-stepper/wizard-stepper.css @@ -0,0 +1,95 @@ +.stepper-wrapper { + width: 60%; + margin-top: auto; + display: flex; + justify-content: space-between; + margin-bottom: 20px; + margin-left: 10px; +} +.stepper-item { + position: relative; + display: flex; + flex-direction: column; + align-items: stretch; + flex: 1; +} + +.docker { + margin-left: -5px; + text-transform: capitalize; +} +.kubernetes { + margin-left: -20px; + text-transform: capitalize; +} +.aci { + margin-left: 5px; + text-transform: uppercase; +} + +.stepper-item::before { + position: absolute; + content: ''; + border-bottom: 5px solid rgb(231, 231, 231); + width: 100%; + top: 20px; + left: -100%; + z-index: 2; +} + +.stepper-item::after { + position: absolute; + content: ''; + border-bottom: 5px solid rgb(231, 231, 231); + width: 100%; + top: 20px; + left: 0; + z-index: 2; +} + +.stepper-item .step-counter { + position: relative; + z-index: 5; + display: flex; + justify-content: center; + align-items: center; + width: 40px; + height: 40px; + border-radius: 50%; + background: rgb(231, 231, 231); + margin-bottom: 6px; +} + +.stepper-item.active { + font-weight: bold; + background: #fff; + content: none; +} + +.stepper-item.active .step-counter { + background: #337ab7; + color: #fff; +} + +.stepper-item.completed .step-counter { + background-color: #48b400; + color: #fff; +} + +.stepper-item.completed::after { + position: absolute; + content: ''; + border-bottom: 5px solid #48b400; + width: 100%; + top: 20px; + left: 0; + z-index: 3; +} + +.stepper-item:first-child::before { + content: none; +} + +.stepper-item:last-child::after { + content: none; +} diff --git a/app/portainer/views/wizard/wizard-endpoints/wizard-stepper/wizard-stepper.html b/app/portainer/views/wizard/wizard-endpoints/wizard-stepper/wizard-stepper.html new file mode 100644 index 000000000..741487c9d --- /dev/null +++ b/app/portainer/views/wizard/wizard-endpoints/wizard-stepper/wizard-stepper.html @@ -0,0 +1,6 @@ +
+
+
{{ $index + 1 }}
+
{{ selection.endpoint }}
+
+
diff --git a/app/portainer/views/wizard/wizard-link/index.js b/app/portainer/views/wizard/wizard-link/index.js new file mode 100644 index 000000000..e0f8cbb8d --- /dev/null +++ b/app/portainer/views/wizard/wizard-link/index.js @@ -0,0 +1,11 @@ +import angular from 'angular'; +import './wizard-link.css'; + +angular.module('portainer.app').component('wizardLink', { + templateUrl: './wizard-link.html', + bindings: { + linkTitle: '@', + description: '@', + icon: '<', + }, +}); diff --git a/app/portainer/views/wizard/wizard-link/wizard-link.css b/app/portainer/views/wizard/wizard-link/wizard-link.css new file mode 100644 index 000000000..f62148e0c --- /dev/null +++ b/app/portainer/views/wizard/wizard-link/wizard-link.css @@ -0,0 +1,41 @@ +.wizard-button { + display: block; + border: 1px solid rgb(163, 163, 163); + border-radius: 5px; + width: 200px; + height: 300px; + float: left; + margin-right: 30px; + cursor: pointer; + padding: 25px 20px; + box-shadow: 0 3px 10px -2px rgb(161 170 166 / 60%); +} + +.wizard-button:hover { + box-shadow: 0 3px 10px -2px rgb(161 170 166 / 80%); + border: 1px solid #3ca4ff; + color: #337ab7; +} + +.wizard-link { + color: #000; +} + +.wizard-title { + color: #000; +} + +.wizard-button-subtitle { + color: rgb(112, 112, 112); + margin-bottom: 30px; + text-align: center; +} + +.wizard-button-title { + font-size: 12px; + margin-bottom: 15px; +} + +.wizard-link-section { + margin-top: 15px; +} diff --git a/app/portainer/views/wizard/wizard-link/wizard-link.html b/app/portainer/views/wizard/wizard-link/wizard-link.html new file mode 100644 index 000000000..5f32b07ca --- /dev/null +++ b/app/portainer/views/wizard/wizard-link/wizard-link.html @@ -0,0 +1,7 @@ +
+
+
+

{{ $ctrl.linkTitle }}

+
{{ $ctrl.description }}
+
+
diff --git a/app/portainer/views/wizard/wizard-tls/index.js b/app/portainer/views/wizard/wizard-tls/index.js new file mode 100644 index 000000000..3f1cf61cd --- /dev/null +++ b/app/portainer/views/wizard/wizard-tls/index.js @@ -0,0 +1,9 @@ +import angular from 'angular'; + +angular.module('portainer.app').component('wizardTls', { + templateUrl: './wizard-tls.html', + bindings: { + formData: '<', + onChange: '<', + }, +}); diff --git a/app/portainer/views/wizard/wizard-tls/wizard-tls.html b/app/portainer/views/wizard/wizard-tls/wizard-tls.html new file mode 100644 index 000000000..32e7e2fd5 --- /dev/null +++ b/app/portainer/views/wizard/wizard-tls/wizard-tls.html @@ -0,0 +1,49 @@ +
+
+ +
+ +
+ + + {{ $ctrl.formData.TLSCACert.name }} + + + +
+
+ + + +
+ +
+ +
+ + + {{ $ctrl.formData.TLSCert.name }} + + + +
+
+ + +
+ +
+ + + {{ $ctrl.formData.TLSKey.name }} + + + +
+
+ +
+ +
+ +
diff --git a/app/portainer/views/wizard/wizard-view.controller.js b/app/portainer/views/wizard/wizard-view.controller.js new file mode 100644 index 000000000..f615b69ef --- /dev/null +++ b/app/portainer/views/wizard/wizard-view.controller.js @@ -0,0 +1,87 @@ +import { PortainerEndpointCreationTypes } from 'Portainer/models/endpoint/models'; + +export default class WizardViewController { + /* @ngInject */ + constructor($async, $state, EndpointService, $analytics) { + this.$async = $async; + this.$state = $state; + this.EndpointService = EndpointService; + this.$analytics = $analytics; + } + + /** + * WIZARD APPLICATION + */ + manageLocalEndpoint() { + this.$state.go('portainer.home'); + } + + addRemoteEndpoint() { + this.$state.go('portainer.wizard.endpoints'); + } + + async createLocalKubernetesEndpoint() { + this.state.endpoint.loading = true; + try { + await this.EndpointService.createLocalKubernetesEndpoint(); + this.state.endpoint.loading = false; + this.state.endpoint.added = true; + this.state.endpoint.connected = 'kubernetes'; + this.state.local.icon = 'fas fa-dharmachakra'; + } catch (err) { + this.state.endpoint.kubernetesError = true; + } + } + + async createLocalDockerEndpoint() { + try { + await this.EndpointService.createLocalEndpoint(); + this.state.endpoint.loading = false; + this.state.endpoint.added = true; + this.state.endpoint.connected = 'docker'; + this.state.local.icon = 'fab fa-docker'; + } finally { + this.state.endpoint.loading = false; + } + } + + $onInit() { + return this.$async(async () => { + this.state = { + local: { + icon: '', + }, + remote: { + icon: 'fa fa-plug', + }, + endpoint: { + kubernetesError: false, + connected: '', + loading: false, + added: false, + }, + }; + + const endpoints = await this.EndpointService.endpoints(); + if (endpoints.totalCount === '0') { + await this.createLocalKubernetesEndpoint(); + if (this.state.endpoint.kubernetesError) { + await this.createLocalDockerEndpoint(); + } + } else { + const addedLocalEndpoint = endpoints.value[0]; + if (addedLocalEndpoint.Type === PortainerEndpointCreationTypes.LocalDockerEnvironment) { + this.state.endpoint.added = true; + this.state.endpoint.connected = 'docker'; + this.state.local.icon = 'fab fa-docker'; + } + + if (addedLocalEndpoint.Type === PortainerEndpointCreationTypes.LocalKubernetesEnvironment) { + this.state.endpoint.added = true; + this.state.endpoint.connected = 'kubernetes'; + this.state.local.icon = 'fas fa-dharmachakra'; + } + } + }); + } +} diff --git a/app/portainer/views/wizard/wizard-view.html b/app/portainer/views/wizard/wizard-view.html new file mode 100644 index 000000000..db8c880b6 --- /dev/null +++ b/app/portainer/views/wizard/wizard-view.html @@ -0,0 +1,52 @@ + + + Endpoint Wizard + + +
+
+ + + +
+
+ Welcome to Portainer +
+
+ + We have connected your local environment of {{ $ctrl.state.endpoint.connected }} to Portainer.
+ + We could not connect your local environment to Portainer.
+ Please ensure your environment is correctly exposed. For help with installation vist + https://documentation.portainer.io/quickstart
+
+ + Get started below with your local portainer or connect more container environments. + +
+ + + +
+
+
+
+