mirror of https://github.com/portainer/portainer
feat(wizard):first UX experience for adding environment EE-1089 (#5581)
* first UX experience for adding environment EE-1089pull/5605/head
parent
2a60b8fcdf
commit
d8b88d1004
|
@ -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);
|
||||
|
|
|
@ -6,6 +6,9 @@
|
|||
</div>
|
||||
|
||||
<div class="actionBar" ng-if="$ctrl.showSnapshotAction">
|
||||
<div style="margin-bottom: 10px;" ng-if="$ctrl.endpoints.length"
|
||||
><i class="fa fa-exclamation-circle blue-icon" style="margin-right: 5px;"></i>Click on an environment to manage</div
|
||||
>
|
||||
<button type="button" class="btn btn-sm btn-primary" ng-click="$ctrl.snapshotAction()" data-cy="home-refreshEndpointsButton">
|
||||
<i class="fa fa-sync space-right" aria-hidden="true"></i>Refresh
|
||||
</button>
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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');
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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,
|
||||
});
|
|
@ -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,
|
||||
});
|
|
@ -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: '<',
|
||||
},
|
||||
});
|
|
@ -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: '',
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
<form class="form-horizontal" name="aciWizardForm">
|
||||
<box-selector radio-name="ACI" ng-model="$ctrl.state.endpointType" options="$ctrl.state.availableOptions"></box-selector>
|
||||
<!-- docker form section-->
|
||||
<div class="form-group wizard-form">
|
||||
<label for="acir_name" class="col-sm-3 col-lg-2 control-label text-left">Name<span class="wizard-form-required">*</span></label>
|
||||
<div class="col-sm-9 col-lg-10" style="margin-bottom: 15px;">
|
||||
<input type="text" class="form-control" name="aci_name" ng-model="$ctrl.formValues.name" placeholder="e.g. docker-prod01 / kubernetes-cluster01" required auto-focus />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="azure_credential_appid" class="col-sm-3 col-lg-2 control-label text-left">Application ID:<span class="wizard-form-required">*</span></label>
|
||||
<div class="col-sm-9 col-lg-10" style="margin-bottom: 15px;">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
name="azure_credential_appid"
|
||||
ng-model="$ctrl.formValues.azureApplicationId"
|
||||
placeholder="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="azure_credential_tenantid" class="col-sm-3 col-lg-2 control-label text-left">Tenant ID:<span class="wizard-form-required">*</span></label>
|
||||
<div class="col-sm-9 col-lg-10" style="margin-bottom: 15px;">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
name="azure_credential_tenantid"
|
||||
ng-model="$ctrl.formValues.azureTenantId"
|
||||
placeholder="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="azure_credential_authkey" class="col-sm-3 col-lg-2 control-label text-left">Authentication key<span class="wizard-form-required">*</span></label>
|
||||
<div class="col-sm-9 col-lg-10">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
name="azure_credential_authkey"
|
||||
ng-model="$ctrl.formValues.azureAuthenticationKey"
|
||||
placeholder="cOrXoK/1D35w8YQ8nH1/8ZGwzz45JIYD5jxHKXEQknk="
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-primary btn-sm wizard-connect-button"
|
||||
ng-disabled="!$ctrl.formValues.name || !$ctrl.formValues.azureApplicationId || !$ctrl.formValues.azureTenantId || !$ctrl.formValues.azureAuthenticationKey || $ctrl.state.actionInProgress"
|
||||
ng-click="$ctrl.addAciEndpoint()"
|
||||
button-spinner="$ctrl.state.actionInProgress"
|
||||
>
|
||||
<span ng-hide="$ctrl.state.actionInProgress"><i class="fa fa-plug" style="margin-right: 5px;"></i> Connect </span>
|
||||
<span ng-show="$ctrl.state.actionInProgress">Connecting environment...</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
|
@ -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: '<',
|
||||
},
|
||||
});
|
|
@ -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 `,
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,172 @@
|
|||
<form class="form-horizontal" name="dockerWizardForm">
|
||||
<!-- docker tab selection -->
|
||||
<box-selector radio-name="Docker" ng-click="$ctrl.clearForm()" ng-model="$ctrl.state.endpointType" options="$ctrl.state.availableOptions"></box-selector>
|
||||
<!-- docker tab selection -->
|
||||
<div style="padding-left: 10px;">
|
||||
<div class="form-group">
|
||||
<div ng-if="$ctrl.state.endpointType === 'agent'" class="wizard-code">
|
||||
<uib-tabset>
|
||||
<uib-tab index="0" heading="Linux">
|
||||
<code style="display: block; white-space: pre-wrap; padding: 16px 10px;"
|
||||
><h6 style="color: #000;">CLI script for installing agent on your Linux environment with Docker Swarm</h6>{{ $ctrl.command.linuxCommand
|
||||
}}<i class="fas fa-copy wizard-copy-button" ng-click="$ctrl.copyLinuxCommand()"></i
|
||||
><i id="linuxCommandNotification" class="fa fa-check green-icon" aria-hidden="true" style="margin-left: 7px; display: none;"></i
|
||||
></code>
|
||||
</uib-tab>
|
||||
|
||||
<uib-tab index="1" heading="Windows">
|
||||
<code style="display: block; white-space: pre-wrap; padding: 16px 10px;"
|
||||
><h6 style="color: #000;">CLI script for installing agent on your Windows environment with Docker Swarm</h6>{{ $ctrl.command.winCommand
|
||||
}}<i class="fas fa-copy wizard-copy-button" ng-click="$ctrl.copyWinCommand()"></i
|
||||
><i id="winCommandNotification" class="fa fa-check green-icon" aria-hidden="true" style="margin-left: 7px; display: none;"></i
|
||||
></code>
|
||||
</uib-tab>
|
||||
</uib-tabset>
|
||||
</div>
|
||||
|
||||
<div ng-if="$ctrl.state.endpointType === 'api' || $ctrl.state.endpointType === 'socket'" class="wizard-code">
|
||||
<uib-tabset active="state.deploymentTab">
|
||||
<uib-tab index="0" heading="Linux">
|
||||
<code style="display: block; white-space: pre-wrap; padding: 16px 10px;"
|
||||
><h6 style="color: #000;">When using the socket, ensure that you have started the Portainer container with the following Docker flag on Linux</h6
|
||||
>{{ $ctrl.command.linuxSocket }}<i class="fas fa-copy wizard-copy-button" ng-click="$ctrl.copyLinuxSocket()"></i
|
||||
><i id="linuxSocketNotification" class="fa fa-check green-icon" aria-hidden="true" style="margin-left: 7px; display: none;"></i
|
||||
></code>
|
||||
</uib-tab>
|
||||
|
||||
<uib-tab index="1" heading="Windows">
|
||||
<code style="display: block; white-space: pre-wrap; padding: 16px 10px;"
|
||||
><h6 style="color: #000;">When using the socket, ensure that you have started the Portainer container with the following Docker flag on Windows</h6
|
||||
>{{ $ctrl.command.winSocket }}<i class="fas fa-copy wizard-copy-button" ng-click="$ctrl.copyWinSocket()"></i
|
||||
><i id="winSocketNotification" class="fa fa-check green-icon" aria-hidden="true" style="margin-left: 7px; display: none;"></i
|
||||
></code>
|
||||
</uib-tab>
|
||||
</uib-tabset>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- docker form section-->
|
||||
<div class="form-group wizard-form">
|
||||
<label for="endpoint_name" class="col-sm-3 col-lg-2 control-label text-left">Name<span class="wizard-form-required">*</span></label>
|
||||
<div class="col-sm-9 col-lg-10">
|
||||
<input type="text" class="form-control" name="endpoint_name" ng-model="$ctrl.formValues.name" placeholder="e.g. docker-prod01 / kubernetes-cluster01" auto-focus />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ng-if="$ctrl.state.endpointType === 'agent'" class="form-group">
|
||||
<label for="endpoint_url" class="col-sm-3 col-lg-2 control-label text-left"> Environments URL<span class="wizard-form-required">*</span> </label>
|
||||
|
||||
<div class="col-sm-9 col-lg-10" style="margin-bottom: 15px;">
|
||||
<input
|
||||
ng-if="$ctrl.state.endpointType === 'agent'"
|
||||
type="text"
|
||||
class="form-control"
|
||||
name="endpoint_url"
|
||||
ng-model="$ctrl.formValues.url"
|
||||
placeholder="e.g. 10.0.0.10:9001 or tasks.portainer_agent:9001"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ng-if="$ctrl.state.endpointType === 'api'">
|
||||
<div class="form-group">
|
||||
<label for="dockerapi_url" class="col-sm-3 col-lg-2 control-label text-left"> Docker API URL<span class="wizard-form-required">*</span> </label>
|
||||
|
||||
<div class="col-sm-9 col-lg-10" style="margin-bottom: 15px;">
|
||||
<input
|
||||
ng-if="$ctrl.state.endpointType === 'api'"
|
||||
type="text"
|
||||
class="form-control"
|
||||
name="dockerapi_url"
|
||||
ng-model="$ctrl.formValues.url"
|
||||
placeholder="e.g. 10.0.0.10:2375 or mydocker.mydomain.com:2375"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" style="padding-left: 15px; width: 15%;">
|
||||
<por-switch-field ng-model="$ctrl.formValues.tls" name="connect_socket" label="TLS" label-class="col-sm-12 col-lg-4"></por-switch-field>
|
||||
</div>
|
||||
|
||||
<div class="form-group" style="padding-left: 15px; width: 40%;">
|
||||
<por-switch-field
|
||||
ng-if="$ctrl.formValues.tls"
|
||||
ng-model="$ctrl.formValues.skipCertification"
|
||||
name="skip_certification"
|
||||
label="Skip Certification Verification"
|
||||
label-class="col-sm-12 col-lg-4"
|
||||
></por-switch-field>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<wizard-tls ng-if="!$ctrl.formValues.skipCertification && $ctrl.formValues.tls" form-data="$ctrl.formValues.securityFormData" onChange="($ctrl.onChangeFile)"></wizard-tls>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ng-if="$ctrl.state.endpointType === 'socket'" class="form-group" style="padding-left: 15px;">
|
||||
<div class="form-group" style="padding-left: 15px;">
|
||||
<label for="override_socket" class="col-sm_12 control-label text-left">
|
||||
Override default socket path
|
||||
</label>
|
||||
<label class="switch" style="margin-left: 20px;"> <input type="checkbox" ng-model="$ctrl.formValues.overrideSocket" /><i></i></label>
|
||||
</div>
|
||||
|
||||
<div ng-if="$ctrl.formValues.overrideSocket">
|
||||
<div class="form-group">
|
||||
<label for="socket_path" class="col-sm-3 col-lg-2 control-label text-left">
|
||||
Socket path
|
||||
<portainer-tooltip position="bottom" message="Path to the Docker socket. Remember to bind-mount the socket, see the important notice above for more information.">
|
||||
</portainer-tooltip>
|
||||
</label>
|
||||
<div class="col-sm-9 col-lg-10">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
name="socket_path"
|
||||
ng-model="$ctrl.formValues.socketPath"
|
||||
placeholder="e.g. /var/run/docker.sock (on Linux) or //./pipe/docker_engine (on Windows)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<button
|
||||
ng-if="$ctrl.state.endpointType === 'agent'"
|
||||
type="submit"
|
||||
class="btn btn-primary btn-sm wizard-connect-button"
|
||||
ng-disabled="!$ctrl.formValues.name || !$ctrl.formValues.url || $ctrl.state.actionInProgress"
|
||||
ng-click="$ctrl.connectEnvironment('agent')"
|
||||
button-spinner="$ctrl.state.actionInProgress"
|
||||
>
|
||||
<span ng-hide="$ctrl.state.actionInProgress"><i class="fa fa-plug" style="margin-right: 5px;"></i>Connect </span>
|
||||
<span ng-show="$ctrl.state.actionInProgress">Connecting environment...</span>
|
||||
</button>
|
||||
<button
|
||||
ng-if="$ctrl.state.endpointType === 'api'"
|
||||
type="submit"
|
||||
class="btn btn-primary btn-sm wizard-connect-button"
|
||||
ng-disabled="!$ctrl.formValues.name || !$ctrl.formValues.url || $ctrl.state.actionInProgress"
|
||||
ng-click="$ctrl.connectEnvironment('api')"
|
||||
button-spinner="$ctrl.state.actionInProgress"
|
||||
>
|
||||
<span ng-hide="$ctrl.state.actionInProgress"><i class="fa fa-plug" style="margin-right: 5px;"></i>Connect </span>
|
||||
<span ng-show="$ctrl.state.actionInProgress">Connecting environment...</span>
|
||||
</button>
|
||||
<button
|
||||
ng-if="$ctrl.state.endpointType === 'socket'"
|
||||
type="submit"
|
||||
class="btn btn-primary btn-sm wizard-connect-button"
|
||||
ng-disabled="!$ctrl.formValues.name || $ctrl.state.actionInProgress"
|
||||
ng-click="$ctrl.connectEnvironment('socket')"
|
||||
button-spinner="$ctrl.state.actionInProgress"
|
||||
>
|
||||
<span ng-hide="$ctrl.state.actionInProgress"><i class="fa fa-plug" style="margin-right: 5px;"></i>Connect </span>
|
||||
<span ng-show="$ctrl.state.actionInProgress">Connecting environment...</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
|
@ -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: '<',
|
||||
},
|
||||
});
|
|
@ -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 `,
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
<form class="form-horizontal" name="kubernetesWizardForm">
|
||||
<box-selector radio-name="Kubernetes" ng-model="$ctrl.state.endpointType" options="$ctrl.state.availableOptions"></box-selector>
|
||||
<!-- docker tab selection -->
|
||||
<div style="padding-left: 10px;">
|
||||
<div class="form-group">
|
||||
<div class="wizard-code">
|
||||
<uib-tabset active="state.deploymentTab">
|
||||
<uib-tab index="0" heading="Kubernetes via load balancer">
|
||||
<code style="display: block; white-space: pre-wrap; padding: 16px 10px;"
|
||||
><h6 style="color: #000;">CLI script for installing agent on your endpoint</h6>{{ $ctrl.command.loadBalancer
|
||||
}}<i class="fas fa-copy wizard-copy-button" ng-click="$ctrl.copyLoadBalancer()"></i
|
||||
><i id="loadBalancerNotification" class="fa fa-check green-icon" aria-hidden="true" style="margin-left: 7px; display: none;"></i
|
||||
></code>
|
||||
</uib-tab>
|
||||
|
||||
<uib-tab index="1" heading="Kubernetes via node port">
|
||||
<code style="display: block; white-space: pre-wrap; padding: 16px 10px;"
|
||||
><h6 style="color: #000;">CLI script for installing agent on your endpoint</h6>{{ $ctrl.command.nodePort
|
||||
}}<i class="fas fa-copy wizard-copy-button" ng-click="$ctrl.copyNodePort()"></i
|
||||
><i id="nodePortNotification" class="fa fa-check green-icon" aria-hidden="true" style="margin-left: 7px; display: none;"></i
|
||||
></code>
|
||||
</uib-tab>
|
||||
</uib-tabset>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group wizard-form">
|
||||
<label for="endpoint_name" class="col-sm-3 col-lg-2 control-label text-left">Name<span class="wizard-form-required">*</span></label>
|
||||
<div class="col-sm-9 col-lg-10" style="margin-bottom: 15px;">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
name="endpoint_name"
|
||||
ng-model="$ctrl.state.formValues.name"
|
||||
placeholder="e.g. docker-prod01 / kubernetes-cluster01"
|
||||
required
|
||||
auto-focus
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="endpoint_url" class="col-sm-3 col-lg-2 control-label text-left"> Environments URL<span class="wizard-form-required">*</span> </label>
|
||||
|
||||
<div class="col-sm-9 col-lg-10" style="margin-bottom: 15px;">
|
||||
<input type="text" class="form-control" name="endpoint_url" ng-model="$ctrl.state.formValues.url" placeholder="e.g. 10.0.0.10:9001 or tasks.portainer_agent:9001" required />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-primary btn-sm wizard-connect-button"
|
||||
ng-disabled="!$ctrl.state.formValues.name || !$ctrl.state.formValues.url || $ctrl.state.actionInProgress"
|
||||
ng-click="$ctrl.addKubernetesAgent()"
|
||||
button-spinner="$ctrl.state.actionInProgress"
|
||||
>
|
||||
<span ng-hide="$ctrl.state.actionInProgress"><i class="fa fa-plug" style="margin-right: 5px;"></i> Connect </span>
|
||||
<span ng-show="$ctrl.state.actionInProgress">Connecting environment...</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
|
@ -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: '<',
|
||||
},
|
||||
});
|
|
@ -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);
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
<rd-widget>
|
||||
<rd-widget-header icon="fa-plug" title-text="Connected Environments"> </rd-widget-header>
|
||||
<rd-widget-body>
|
||||
<div class="wizard-list-wrapper" ng-repeat="endpoint in $ctrl.endpointList">
|
||||
<div class="wizard-list-image"><i ng-class="endpoint.Type | endpointtypeicon" aria-hidden="true" style="margin-right: 2px;"></i></div>
|
||||
<div class="wizard-list-title">{{ endpoint.Name }}</div>
|
||||
<div class="wizard-list-subtitle">URL: {{ endpoint.URL | stripprotocol }}</div>
|
||||
<div class="wizard-list-type">Type: {{ endpoint.Type | endpointtypename }}</div>
|
||||
</div>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
|
@ -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;
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -0,0 +1,96 @@
|
|||
<rd-header>
|
||||
<rd-header-title title-text="Quick Setup"></rd-header-title>
|
||||
<rd-header-content>Environment Wizard</rd-header-content>
|
||||
</rd-header>
|
||||
|
||||
<div class="wizard-wrapper" ng-if="$ctrl.state.currentStep !== 0">
|
||||
<div class="wizard-main">
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<rd-widget>
|
||||
<rd-widget-header icon="fa-magic" title-text="Environment Wizard"> </rd-widget-header>
|
||||
<rd-widget-body>
|
||||
<!-- Stepper -->
|
||||
<wizard-stepper endpoint-selections="$ctrl.state.selections"></wizard-stepper>
|
||||
<!-- Stepper -->
|
||||
|
||||
<div class="col-sm-12 form-section-title wizard-main-title"> Connect to your {{ $ctrl.state.section }} environment </div>
|
||||
|
||||
<div ng-switch="$ctrl.state.section" class="wizard-endpoint-section">
|
||||
<wizard-docker ng-switch-when="docker" on-update="($ctrl.updateEndpoint)" on-analytics="($ctrl.addAnalytics)"></wizard-docker>
|
||||
<wizard-aci ng-switch-when="aci" on-update="($ctrl.updateEndpoint)" on-analytics="($ctrl.addAnalytics)"></wizard-aci>
|
||||
<wizard-kubernetes ng-switch-when="kubernetes" on-update="($ctrl.updateEndpoint)" on-analytics="($ctrl.addAnalytics)"></wizard-kubernetes>
|
||||
</div>
|
||||
|
||||
<div class="wizard-step-action">
|
||||
<button
|
||||
ng-click="$ctrl.previousStep()"
|
||||
ng-show="$ctrl.state.currentStep !== 0"
|
||||
type="submit"
|
||||
class="btn btn-primary btn-sm previous-btn"
|
||||
ng-disabled="$ctrl.state.currentStep === 1"
|
||||
>
|
||||
<i class="fas fa-arrow-left space-right"></i>{{ $ctrl.state.previousStep }}
|
||||
</button>
|
||||
<button ng-click="$ctrl.nextStep()" ng-show="$ctrl.state.currentStep !== 0" type="submit" class="btn btn-primary btn-sm next-btn">
|
||||
{{ $ctrl.state.nextStep }} <i class="fas fa-arrow-right space-left"></i
|
||||
></button>
|
||||
</div>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="wizard-aside" ng-if="$ctrl.state.currentStep !== 0">
|
||||
<wizard-endpoint-list endpoint-list="$ctrl.endpoints"></wizard-endpoint-list>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row" ng-if="$ctrl.state.currentStep === 0">
|
||||
<div class="col-sm-12">
|
||||
<rd-widget>
|
||||
<rd-widget-header icon="fa-magic" title-text="Environment Wizard"> </rd-widget-header>
|
||||
<rd-widget-body>
|
||||
<div class="row">
|
||||
<div class="col-sm-12 form-section-title">
|
||||
Select your environment(s)
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-muted small">You can onboard different types of environments, select all that apply.</span>
|
||||
</div>
|
||||
<div class="wizard-section">
|
||||
<wizard-endpoint-type
|
||||
endpoint-title="Docker"
|
||||
description="Connect to Docker Standalone / Swarm via URL/IP, API or Socket"
|
||||
icon="fab fa-docker"
|
||||
active="$ctrl.state.dockerActive"
|
||||
ng-click="$ctrl.endpointSelect('docker')"
|
||||
></wizard-endpoint-type>
|
||||
|
||||
<wizard-endpoint-type
|
||||
endpoint-title="Kubernetes"
|
||||
description="Connect to a kubernetes environment via URL/IP"
|
||||
icon="fas fa-dharmachakra"
|
||||
active="$ctrl.state.kubernetesActive"
|
||||
ng-click="$ctrl.endpointSelect('kubernetes')"
|
||||
></wizard-endpoint-type>
|
||||
|
||||
<wizard-endpoint-type
|
||||
endpoint-title="ACI"
|
||||
description="Connect to ACI environment via API"
|
||||
icon="fab fa-microsoft"
|
||||
active="$ctrl.state.aciActive"
|
||||
ng-click="$ctrl.endpointSelect('aci')"
|
||||
></wizard-endpoint-type>
|
||||
</div>
|
||||
|
||||
<div class="wizard-section">
|
||||
<div class="wizard-section-action">
|
||||
<button ng-click="$ctrl.startWizard()" ng-disabled="$ctrl.state.selections.length === 0" type="submit" class="btn btn-primary btn-sm no-margin">Start Wizard</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,9 @@
|
|||
import angular from 'angular';
|
||||
import './wizard-stepper.css';
|
||||
|
||||
angular.module('portainer.app').component('wizardStepper', {
|
||||
templateUrl: './wizard-stepper.html',
|
||||
bindings: {
|
||||
endpointSelections: '<',
|
||||
},
|
||||
});
|
|
@ -0,0 +1,11 @@
|
|||
import angular from 'angular';
|
||||
|
||||
angular.module('portainer.app').component('wizardEndpointType', {
|
||||
templateUrl: './wizard-endpoint-type.html',
|
||||
bindings: {
|
||||
endpointTitle: '@',
|
||||
description: '@',
|
||||
icon: '@',
|
||||
active: '<',
|
||||
},
|
||||
});
|
|
@ -0,0 +1,12 @@
|
|||
<div>
|
||||
<div ng-click="$ctrl.endpointSelect('docker')">
|
||||
<div class="wizard-endpoints {{ $ctrl.active }}">
|
||||
<div style="text-align: center; padding: 10px;"><i class="{{ $ctrl.icon }}" style="font-size: 80px;"></i></div>
|
||||
|
||||
<div style="margin-top: 15px; text-align: center;">
|
||||
<h3>{{ $ctrl.endpointTitle }}</h3>
|
||||
<h5>{{ $ctrl.description }}</h5>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -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;
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
<div class="stepper-wrapper">
|
||||
<div ng-repeat="selection in $ctrl.endpointSelections" class="stepper-item {{ selection.stage }} ">
|
||||
<div class="step-counter">{{ $index + 1 }}</div>
|
||||
<div class="step-name {{ selection.nameClass }}">{{ selection.endpoint }}</div>
|
||||
</div>
|
||||
</div>
|
|
@ -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: '<',
|
||||
},
|
||||
});
|
|
@ -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;
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
<div class="wizard-button">
|
||||
<div style="text-align: center; padding: 10px;"><i class="{{ $ctrl.icon }}" style="font-size: 80px;"></i></div>
|
||||
<div style="margin-top: 15px; text-align: center;">
|
||||
<h3>{{ $ctrl.linkTitle }}</h3>
|
||||
<h5>{{ $ctrl.description }}</h5>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,9 @@
|
|||
import angular from 'angular';
|
||||
|
||||
angular.module('portainer.app').component('wizardTls', {
|
||||
templateUrl: './wizard-tls.html',
|
||||
bindings: {
|
||||
formData: '<',
|
||||
onChange: '<',
|
||||
},
|
||||
});
|
|
@ -0,0 +1,49 @@
|
|||
<div>
|
||||
<div>
|
||||
<!-- tls-file-ca -->
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 col-lg-2 control-label text-left">TLS CA certificate</label>
|
||||
<div class="col-sm-9 col-lg-10">
|
||||
<button type="button" class="btn btn-sm btn-primary" ngf-select="$ctrl.onChange($file)" ng-model="$ctrl.formData.TLSCACert">Select file</button>
|
||||
<span class="space-left">
|
||||
{{ $ctrl.formData.TLSCACert.name }}
|
||||
<i class="fa fa-check green-icon" ng-if="$ctrl.formData.TLSCACert" aria-hidden="true"></i>
|
||||
<i class="fa fa-times red-icon" ng-if="!$ctrl.formData.TLSCACert" aria-hidden="true"></i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !tls-file-ca -->
|
||||
|
||||
<!-- tls-files-cert-key -->
|
||||
<div>
|
||||
<!-- tls-file-cert -->
|
||||
<div class="form-group">
|
||||
<label for="tls_cert" class="col-sm-3 col-lg-2 control-label text-left">TLS certificate</label>
|
||||
<div class="col-sm-9 col-lg-10">
|
||||
<button type="button" class="btn btn-sm btn-primary" ngf-select="$ctrl.onChange($file)" ng-model="$ctrl.formData.TLSCert">Select file</button>
|
||||
<span class="space-left">
|
||||
{{ $ctrl.formData.TLSCert.name }}
|
||||
<i class="fa fa-check green-icon" ng-if="$ctrl.formData.TLSCert" aria-hidden="true"></i>
|
||||
<i class="fa fa-times red-icon" ng-if="!$ctrl.formData.TLSCert" aria-hidden="true"></i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !tls-file-cert -->
|
||||
<!-- tls-file-key -->
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 col-lg-2 control-label text-left">TLS key</label>
|
||||
<div class="col-sm-9 col-lg-10">
|
||||
<button type="button" class="btn btn-sm btn-primary" ngf-select="$ctrl.onChange($file)" ng-model="$ctrl.formData.TLSKey">Select file</button>
|
||||
<span class="space-left">
|
||||
{{ $ctrl.formData.TLSKey.name }}
|
||||
<i class="fa fa-check green-icon" ng-if="$ctrl.formData.TLSKey" aria-hidden="true"></i>
|
||||
<i class="fa fa-times red-icon" ng-if="!$ctrl.formData.TLSKey" aria-hidden="true"></i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !tls-file-key -->
|
||||
</div>
|
||||
<!-- tls-files-cert-key -->
|
||||
</div>
|
||||
<!-- !tls-file-upload -->
|
||||
</div>
|
|
@ -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';
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
<rd-header>
|
||||
<rd-header-title title-text="Quick Setup"></rd-header-title>
|
||||
<rd-header-content>Endpoint Wizard</rd-header-content>
|
||||
</rd-header>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<rd-widget>
|
||||
<rd-widget-header icon="fa-magic" title-text="Environment Wizard"> </rd-widget-header>
|
||||
<rd-widget-body>
|
||||
<div class="row">
|
||||
<div class="col-sm-12 form-section-title">
|
||||
Welcome to Portainer
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-muted small" ng-show="$ctrl.state.endpoint.added">
|
||||
We have connected your local environment of {{ $ctrl.state.endpoint.connected }} to Portainer. <br
|
||||
/></span>
|
||||
<span class="text-muted small" ng-show="!$ctrl.state.endpoint.loading && !$ctrl.state.endpoint.added">
|
||||
We could not connect your local environment to Portainer. <br />
|
||||
Please ensure your environment is correctly exposed. For help with installation vist
|
||||
<a href="https://documentation.portainer.io/quickstart/">https://documentation.portainer.io/quickstart</a><br />
|
||||
</span>
|
||||
<span class="text-muted small">
|
||||
Get started below with your local portainer or connect more container environments.
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<wizard-link
|
||||
ng-show="$ctrl.state.endpoint.added"
|
||||
icon="$ctrl.state.local.icon"
|
||||
title="Get Started"
|
||||
link-title="Get Started"
|
||||
description="Proceed using the local environment which Portainer is running in"
|
||||
ui-sref="portainer.home"
|
||||
analytics-on
|
||||
analytics-category="portainer"
|
||||
analytics-event="endpoint-wizard-endpoint-select"
|
||||
analytics-properties="{ metadata: { environment: 'Get-started-local-environment' }}"
|
||||
></wizard-link>
|
||||
<wizard-link
|
||||
title="Add Environments"
|
||||
link-title="Add Environments"
|
||||
icon="$ctrl.state.remote.icon"
|
||||
description="Connect to other environments"
|
||||
ui-sref="portainer.wizard.endpoints"
|
||||
></wizard-link>
|
||||
</div>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
</div>
|
Loading…
Reference in New Issue