feat(edge): create edge device with wizard [EE-3096] (#7029)

pull/7373/head
Chaim Lev-Ari 2022-07-28 15:34:22 +02:00 committed by GitHub
parent d574a71cb1
commit 762c664948
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 129 additions and 953 deletions

View File

@ -21,6 +21,11 @@ interface Props {
showWaitingRoomLink: boolean;
}
enum DeployType {
FDO = 'FDO',
MANUAL = 'MANUAL',
}
export function EdgeDevicesDatatableActions({
selectedItems,
isOpenAMTEnabled,
@ -105,38 +110,37 @@ export function EdgeDevicesDatatableActions({
}
async function onAddNewDeviceClick() {
if (!isFDOEnabled) {
router.stateService.go('portainer.endpoints.newEdgeDevice');
return;
}
const result = await promptAsync({
title: 'How would you like to add an Edge Device?',
inputType: 'radio',
inputOptions: [
{
text: 'Provision bare-metal using Intel FDO',
value: 'FDO',
},
{
text: 'Deploy agent manually',
value: 'MANUAL',
},
],
buttons: {
confirm: {
label: 'Confirm',
className: 'btn-primary',
},
},
});
const result = isFDOEnabled
? await promptAsync({
title: 'How would you like to add an Edge Device?',
inputType: 'radio',
inputOptions: [
{
text: 'Provision bare-metal using Intel FDO',
value: DeployType.FDO,
},
{
text: 'Deploy agent manually',
value: DeployType.MANUAL,
},
],
buttons: {
confirm: {
label: 'Confirm',
className: 'btn-primary',
},
},
})
: DeployType.MANUAL;
switch (result) {
case 'FDO':
case DeployType.FDO:
router.stateService.go('portainer.endpoints.importDevice');
break;
case 'MANUAL':
router.stateService.go('portainer.endpoints.newEdgeDevice');
case DeployType.MANUAL:
router.stateService.go('portainer.wizard.endpoints', {
edgeDevice: true,
});
break;
default:
break;

View File

@ -188,20 +188,6 @@ angular
},
};
var edgeDeviceCreation = {
name: 'portainer.endpoints.newEdgeDevice',
url: '/newEdgeDevice',
params: {
isEdgeDevice: true,
},
views: {
'content@': {
templateUrl: './views/endpoints/create/createendpoint.html',
controller: 'CreateEndpointController',
},
},
};
var deviceImport = {
name: 'portainer.endpoints.importDevice',
url: '/device',
@ -471,7 +457,6 @@ angular
$stateRegistryProvider.register(endpoint);
$stateRegistryProvider.register(endpointAccess);
$stateRegistryProvider.register(endpointKVM);
$stateRegistryProvider.register(edgeDeviceCreation);
$stateRegistryProvider.register(deviceImport);
$stateRegistryProvider.register(addFDOProfile);
$stateRegistryProvider.register(editFDOProfile);

View File

@ -29,6 +29,19 @@ function config($stateRegistryProvider: StateRegistry) {
},
});
$stateRegistryProvider.register({
name: 'portainer.wizard.endpoints',
url: '/endpoints?edgeDevice',
views: {
'content@': {
component: 'wizardEnvironmentTypeSelectView',
},
},
params: {
localEndpointId: 0,
},
});
$stateRegistryProvider.register({
name: 'portainer.wizard.endpoints.create',
url: '/create?envType',
@ -41,17 +54,4 @@ function config($stateRegistryProvider: StateRegistry) {
envType: '',
},
});
$stateRegistryProvider.register({
name: 'portainer.wizard.endpoints',
url: '/endpoints',
views: {
'content@': {
component: 'wizardEnvironmentTypeSelectView',
},
},
params: {
localEndpointId: 0,
},
});
}

View File

@ -1,357 +0,0 @@
import { PortainerEndpointCreationTypes, PortainerEndpointTypes } from 'Portainer/models/endpoint/models';
import { getAgentShortVersion } from 'Portainer/views/endpoints/helpers';
import { baseHref } from '@/portainer/helpers/pathHelper';
import { EndpointSecurityFormData } from '../../../components/endpointSecurity/porEndpointSecurityModel';
angular
.module('portainer.app')
.controller(
'CreateEndpointController',
function CreateEndpointController(
$async,
$analytics,
$q,
$scope,
$state,
$filter,
clipboard,
EndpointService,
GroupService,
SettingsService,
Notifications,
Authentication,
StateManager
) {
$scope.onChangeCheckInInterval = onChangeCheckInInterval;
$scope.setFieldValue = setFieldValue;
$scope.state = {
EnvironmentType: $state.params.isEdgeDevice ? 'edge_agent' : 'agent',
PlatformType: 'linux',
actionInProgress: false,
deploymentTab: 0,
allowCreateTag: Authentication.isAdmin(),
isEdgeDevice: $state.params.isEdgeDevice,
};
const agentVersion = StateManager.getState().application.version;
const agentShortVersion = getAgentShortVersion(agentVersion);
$scope.agentSecret = '';
$scope.deployCommands = {
kubeLoadBalancer: `curl -L https://downloads.portainer.io/ce${agentShortVersion}/portainer-agent-k8s-lb.yaml -o portainer-agent-k8s.yaml; kubectl apply -f portainer-agent-k8s.yaml`,
kubeNodePort: `curl -L https://downloads.portainer.io/ce${agentShortVersion}/portainer-agent-k8s-nodeport.yaml -o portainer-agent-k8s.yaml; kubectl apply -f portainer-agent-k8s.yaml`,
agentLinux: agentLinuxSwarmCommand,
agentWindows: agentWindowsSwarmCommand,
};
$scope.formValues = {
Name: '',
URL: '',
PublicURL: '',
GroupId: 1,
SecurityFormData: new EndpointSecurityFormData(),
AzureApplicationId: '',
AzureTenantId: '',
AzureAuthenticationKey: '',
TagIds: [],
CheckinInterval: 0,
};
$scope.copyAgentCommand = function () {
let command = '';
if ($scope.state.deploymentTab === 2 && $scope.state.PlatformType === 'linux') {
command = $scope.deployCommands.agentLinux($scope.agentSecret);
} else if ($scope.state.deploymentTab === 2 && $scope.state.PlatformType === 'windows') {
command = $scope.deployCommands.agentWindows($scope.agentSecret);
} else if ($scope.state.deploymentTab === 1) {
command = $scope.deployCommands.kubeNodePort;
} else {
command = $scope.deployCommands.kubeLoadBalancer;
}
clipboard.copyText(command.trim());
$('#copyNotification').show().fadeOut(2500);
};
$scope.setDefaultPortainerInstanceURL = function () {
let url;
if (window.location.origin.startsWith('http')) {
const path = baseHref() !== '/' ? path : '';
url = `${window.location.origin}${path}`;
} else {
url = baseHref().replace(/\/$/, '');
}
$scope.formValues.URL = url;
};
$scope.resetEndpointURL = function () {
$scope.formValues.URL = '';
};
$scope.onChangeTags = function onChangeTags(value) {
return $scope.$evalAsync(() => {
$scope.formValues.TagIds = value;
});
};
function onChangeCheckInInterval(value) {
setFieldValue('EdgeCheckinInterval', value);
}
function setFieldValue(name, value) {
return $scope.$evalAsync(() => {
$scope.formValues = {
...$scope.formValues,
[name]: value,
};
});
}
$scope.addDockerEndpoint = function () {
var name = $scope.formValues.Name;
var URL = $filter('stripprotocol')($scope.formValues.URL);
var publicURL = $scope.formValues.PublicURL;
var groupId = $scope.formValues.GroupId;
var tagIds = $scope.formValues.TagIds;
if ($scope.formValues.ConnectSocket) {
URL = $scope.formValues.SocketPath;
$scope.state.actionInProgress = true;
EndpointService.createLocalEndpoint(name, URL, publicURL, groupId, tagIds)
.then(function success() {
Notifications.success('Environment created', name);
$state.go('portainer.endpoints', {}, { reload: true });
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to create environment');
})
.finally(function final() {
$scope.state.actionInProgress = false;
});
} else {
if (publicURL === '') {
publicURL = URL.split(':')[0];
}
var securityData = $scope.formValues.SecurityFormData;
var TLS = securityData.TLS;
var TLSMode = securityData.TLSMode;
var TLSSkipVerify = TLS && (TLSMode === 'tls_client_noca' || TLSMode === 'tls_only');
var TLSSkipClientVerify = TLS && (TLSMode === 'tls_ca' || TLSMode === 'tls_only');
var TLSCAFile = TLSSkipVerify ? null : securityData.TLSCACert;
var TLSCertFile = TLSSkipClientVerify ? null : securityData.TLSCert;
var TLSKeyFile = TLSSkipClientVerify ? null : securityData.TLSKey;
addEndpoint(
name,
PortainerEndpointCreationTypes.LocalDockerEnvironment,
URL,
publicURL,
groupId,
tagIds,
TLS,
TLSSkipVerify,
TLSSkipClientVerify,
TLSCAFile,
TLSCertFile,
TLSKeyFile
);
}
};
$scope.addKubernetesEndpoint = function () {
var name = $scope.formValues.Name;
var tagIds = $scope.formValues.TagIds;
$scope.state.actionInProgress = true;
EndpointService.createLocalKubernetesEndpoint(name, tagIds)
.then(function success(result) {
Notifications.success('Environment created', name);
$state.go('kubernetes.cluster.setup', { endpoinId: result.Id });
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to create environment');
})
.finally(function final() {
$scope.state.actionInProgress = false;
});
};
$scope.addAgentEndpoint = addAgentEndpoint;
async function addAgentEndpoint() {
return $async(async () => {
const name = $scope.formValues.Name;
const URL = $scope.formValues.URL;
const publicURL = $scope.formValues.PublicURL === '' ? URL.split(':')[0] : $scope.formValues.PublicURL;
const groupId = $scope.formValues.GroupId;
const tagIds = $scope.formValues.TagIds;
const endpoint = await addEndpoint(name, PortainerEndpointCreationTypes.AgentEnvironment, URL, publicURL, groupId, tagIds, true, true, true, null, null, null);
$analytics.eventTrack('portainer-endpoint-creation', { category: 'portainer', metadata: { type: 'agent', platform: platformLabel(endpoint.Type) } });
});
function platformLabel(type) {
switch (type) {
case PortainerEndpointTypes.DockerEnvironment:
case PortainerEndpointTypes.AgentOnDockerEnvironment:
case PortainerEndpointTypes.EdgeAgentOnDockerEnvironment:
return 'docker';
case PortainerEndpointTypes.KubernetesLocalEnvironment:
case PortainerEndpointTypes.AgentOnKubernetesEnvironment:
case PortainerEndpointTypes.EdgeAgentOnKubernetesEnvironment:
return 'kubernetes';
}
}
}
$scope.addEdgeAgentEndpoint = function () {
var name = $scope.formValues.Name;
var groupId = $scope.formValues.GroupId;
var tagIds = $scope.formValues.TagIds;
var URL = $scope.formValues.URL;
addEndpoint(name, PortainerEndpointCreationTypes.EdgeAgentEnvironment, URL, '', groupId, tagIds, false, false, false, null, null, null, $scope.formValues.CheckinInterval);
};
$scope.addAzureEndpoint = function () {
var name = $scope.formValues.Name;
var applicationId = $scope.formValues.AzureApplicationId;
var tenantId = $scope.formValues.AzureTenantId;
var authenticationKey = $scope.formValues.AzureAuthenticationKey;
var groupId = $scope.formValues.GroupId;
var tagIds = $scope.formValues.TagIds;
createAzureEndpoint(name, applicationId, tenantId, authenticationKey, groupId, tagIds);
};
function createAzureEndpoint(name, applicationId, tenantId, authenticationKey, groupId, tagIds) {
$scope.state.actionInProgress = true;
EndpointService.createAzureEndpoint(name, applicationId, tenantId, authenticationKey, groupId, tagIds)
.then(function success() {
Notifications.success('Environment created', name);
$state.go('portainer.endpoints', {}, { reload: true });
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to create environment');
})
.finally(function final() {
$scope.state.actionInProgress = false;
});
}
async function addEndpoint(
name,
creationType,
URL,
PublicURL,
groupId,
tagIds,
TLS,
TLSSkipVerify,
TLSSkipClientVerify,
TLSCAFile,
TLSCertFile,
TLSKeyFile,
CheckinInterval
) {
return $async(async () => {
$scope.state.actionInProgress = true;
try {
const endpoint = await EndpointService.createRemoteEndpoint(
name,
creationType,
URL,
PublicURL,
groupId,
tagIds,
TLS,
TLSSkipVerify,
TLSSkipClientVerify,
TLSCAFile,
TLSCertFile,
TLSKeyFile,
CheckinInterval,
$scope.state.isEdgeDevice
);
Notifications.success('Environment created', name);
switch (endpoint.Type) {
case PortainerEndpointTypes.EdgeAgentOnDockerEnvironment:
case PortainerEndpointTypes.EdgeAgentOnKubernetesEnvironment:
$state.go('portainer.endpoints.endpoint', { id: endpoint.Id });
break;
case PortainerEndpointTypes.AgentOnKubernetesEnvironment:
$state.go('kubernetes.cluster.setup', { endpoinId: endpoint.Id });
break;
default:
$state.go('portainer.endpoints', {}, { reload: true });
break;
}
return endpoint;
} catch (err) {
Notifications.error('Failure', err, 'Unable to create environment');
} finally {
$scope.state.actionInProgress = false;
}
});
}
function initView() {
$q.all({
groups: GroupService.groups(),
settings: SettingsService.settings(),
})
.then(function success(data) {
$scope.groups = data.groups;
const settings = data.settings;
$scope.agentSecret = settings.AgentSecret;
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to load groups');
});
}
function agentLinuxSwarmCommand(agentSecret) {
let secret = agentSecret == '' ? '' : `\\\n -e AGENT_SECRET=${agentSecret} `;
return `
docker network create \\
--driver overlay \\
portainer_agent_network
docker service create \\
--name portainer_agent \\
--network portainer_agent_network \\
-p 9001:9001/tcp ${secret}\\
--mode global \\
--constraint 'node.platform.os == linux' \\
--mount type=bind,src=//var/run/docker.sock,dst=/var/run/docker.sock \\
--mount type=bind,src=//var/lib/docker/volumes,dst=/var/lib/docker/volumes \\
portainer/agent:${agentVersion}
`;
}
function agentWindowsSwarmCommand(agentSecret) {
let secret = agentSecret == '' ? '' : `\\\n -e AGENT_SECRET=${agentSecret} `;
return `
docker network create \\
--driver overlay \\
portainer_agent_network && \\
docker service create \\
--name portainer_agent \\
--network portainer_agent_network \\
-p 9001:9001/tcp ${secret}\\
--mode global \\
--constraint 'node.platform.os == windows' \\
--mount type=npipe,src=\\\\.\\pipe\\docker_engine,dst=\\\\.\\pipe\\docker_engine \\
--mount type=bind,src=C:\\ProgramData\\docker\\volumes,dst=C:\\ProgramData\\docker\\volumes \\
portainer/agent:${agentVersion}
`;
}
initView();
}
);

View File

@ -1,521 +0,0 @@
<page-header title="'Create environment'" breadcrumbs="[{label:'Environments', link:'portainer.endpoints'}, 'Add environment']"> </page-header>
<div class="row">
<div class="col-sm-12">
<rd-widget>
<rd-widget-body>
<form class="form-horizontal" name="endpointCreationForm">
<div class="col-sm-12 form-section-title"> Environment type </div>
<div class="form-group"></div>
<div class="form-group" style="margin-bottom: 0">
<div class="boxselector_wrapper">
<div ng-show="!state.isEdgeDevice" ng-click="resetEndpointURL()">
<input type="radio" id="agent_endpoint" ng-model="state.EnvironmentType" value="agent" />
<label for="agent_endpoint" data-cy="endpointCreate-agentSelectButton">
<div class="boxselector_header">
<i class="fa fa-bolt" aria-hidden="true" style="margin-right: 2px"></i>
Agent
</div>
<p>Portainer agent</p>
</label>
</div>
<div ng-click="setDefaultPortainerInstanceURL()">
<input type="radio" id="edge_agent_endpoint" ng-model="state.EnvironmentType" value="edge_agent" />
<label for="edge_agent_endpoint" data-cy="endpointCreate-edgeAgentSelectButton">
<div class="boxselector_header vertical-center">
<pr-icon icon="'cloud'" feather="true"></pr-icon>
Edge Agent
</div>
<p>Portainer Edge agent</p>
</label>
</div>
<div ng-show="!state.isEdgeDevice" ng-click="resetEndpointURL()">
<input type="radio" id="docker_endpoint" ng-model="state.EnvironmentType" value="docker" />
<label for="docker_endpoint" data-cy="endpointCreate-dockerSelectButton">
<div class="boxselector_header">
<i class="fab fa-docker" aria-hidden="true" style="margin-right: 2px"></i>
Docker
</div>
<p>Directly connect to the Docker API</p>
</label>
</div>
<div ng-show="!state.isEdgeDevice" ng-click="resetEndpointURL()">
<input type="radio" id="kubernetes_endpoint" ng-model="state.EnvironmentType" value="kubernetes" />
<label for="kubernetes_endpoint" data-cy="endpointCreate-kubeSelectButton">
<div class="boxselector_header">
<i class="fas fa-dharmachakra" aria-hidden="true" style="margin-right: 2px"></i>
Kubernetes
</div>
<p>Local Kubernetes environment</p>
</label>
</div>
<div ng-show="!state.isEdgeDevice">
<input type="radio" id="azure_endpoint" ng-model="state.EnvironmentType" value="azure" />
<label for="azure_endpoint" data-cy="endpointCreate-azureSelectButton">
<div class="boxselector_header">
<i class="fab fa-microsoft" aria-hidden="true" style="margin-right: 2px"></i>
Azure
</div>
<p>Connect to Microsoft Azure ACI</p>
</label>
</div>
</div>
</div>
<div ng-if="state.EnvironmentType === 'docker'">
<div class="col-sm-12 form-section-title"> Important notice </div>
<div class="form-group">
<p class="col-sm-12 text-muted small">
You can connect Portainer to a Docker environment via socket or via TCP. You can find more information about how to expose the Docker API over TCP
<a href="https://docs.docker.com/engine/security/https/"> in the Docker documentation</a>.
</p>
<p class="col-sm-12 text-muted small">
When using the socket, ensure that you have started the Portainer container with the following Docker flag
<code> -v "/var/run/docker.sock:/var/run/docker.sock" </code>
(on Linux) or
<code> -v \.\pipe\docker_engine:\.\pipe\docker_engine </code>
(on Windows).
</p>
</div>
</div>
<div ng-if="state.EnvironmentType === 'agent'">
<div class="col-sm-12 form-section-title"> Information </div>
<div class="form-group">
<span class="col-sm-12 text-muted small">
Ensure that you have deployed the Portainer agent in your cluster first. Refer to the platform related command below to deploy it.
<div class="input-group input-group-sm" style="margin-top: 10px; margin-bottom: 10px">
<div class="btn-group btn-group-sm">
<label class="btn btn-primary" ng-model="state.PlatformType" uib-btn-radio="'linux'"><i class="fab fa-linux" style="margin-right: 2px"></i> Linux</label>
<label class="btn btn-primary" ng-model="state.PlatformType" uib-btn-radio="'windows'"><i class="fab fa-windows" style="margin-right: 2px"></i> Windows</label>
</div>
</div>
<div style="margin-top: 10px">
<uib-tabset active="state.deploymentTab">
<uib-tab index="0" ng-if="state.PlatformType === 'linux'" heading="Kubernetes via load balancer">
<p ng-if="agentSecret != ''" style="margin-top: 16px; margin-bottom: 16px">
<i class="fa fa-info-circle blue-icon" aria-hidden="true" style="margin-right: 2px"></i>
Note that the environment variable AGENT_SECRET will need to be set to <code>{{ agentSecret }}</code
>. Please update the manifest that will be downloaded from the following script.
</p>
<code style="display: block; padding: 16px 45px">{{ deployCommands.kubeLoadBalancer }}</code>
</uib-tab>
<uib-tab index="1" ng-if="state.PlatformType === 'linux'" heading="Kubernetes via node port">
<p ng-if="agentSecret != ''" style="margin-top: 16px; margin-bottom: 16px">
<i class="fa fa-info-circle blue-icon" aria-hidden="true" style="margin-right: 2px"></i>
Note that the environment variable AGENT_SECRET will need to be set to <code>{{ agentSecret }}</code
>. Please update the manifest that will be downloaded from the following script.
</p>
<code style="display: block; padding: 16px 45px">{{ deployCommands.kubeNodePort }}</code>
</uib-tab>
<uib-tab index="2" heading="Docker Swarm">
<code ng-if="state.PlatformType === 'linux'" style="display: block; white-space: pre-wrap; padding: 16px 45px">{{
deployCommands.agentLinux(agentSecret)
}}</code>
<code ng-if="state.PlatformType === 'windows'" style="display: block; white-space: pre-wrap; padding: 16px 45px">{{
deployCommands.agentWindows(agentSecret)
}}</code>
</uib-tab>
</uib-tabset>
<div style="margin-top: 10px">
<span class="btn btn-primary btn-sm space-left" ng-click="copyAgentCommand()"><i class="fa fa-copy space-right" aria-hidden="true"></i>Copy command</span>
<span>
<i id="copyNotification" class="fa fa-check green-icon" aria-hidden="true" style="margin-left: 7px; display: none"></i>
</span>
</div>
</div>
</span>
</div>
</div>
<div ng-if="state.EnvironmentType === 'edge_agent'">
<div class="col-sm-12 form-section-title"> Information </div>
<div class="form-group">
<span class="col-sm-12 text-muted small">
<p>
Allows you to create an environment that can be registered with an Edge agent. The Edge agent will initiate the communications with the Portainer instance. All
the required information on how to connect an Edge agent to this environment will be available after environment creation.
</p>
<p> You can read more about the Edge agent in the userguide available <a href="https://downloads.portainer.io/edge_agent_guide.pdf">here.</a> </p>
</span>
</div>
</div>
<div ng-if="state.EnvironmentType === 'kubernetes'">
<div class="col-sm-12 form-section-title"> Important notice </div>
<div class="form-group">
<p class="col-sm-12 text-muted small"> This will allow you to manage the Kubernetes environment where Portainer is running. </p>
<p class="col-sm-12 text-muted small">
<i class="fa fa-info-circle blue-icon" aria-hidden="true" style="margin-right: 2px"></i>
In order to manage a remote Kubernetes environment, please use the Agent or Edge agent options.
</p>
</div>
</div>
<div ng-if="state.EnvironmentType === 'azure'">
<div class="col-sm-12 form-section-title"> Information </div>
<div class="form-group">
<div class="col-sm-12">
<span class="small">
<p class="text-muted"> <i class="fa fa-flask orange-icon" aria-hidden="true" style="margin-right: 2px"></i> This feature is experimental. </p>
<p class="text-primary"> Connect to Microsoft Azure to manage Azure Container Instances (ACI). </p>
<p class="text-muted">
<i class="fa fa-info-circle blue-icon" aria-hidden="true" style="margin-right: 2px"></i>
Have a look at
<a href="https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-create-service-principal-portal" target="_blank"
>the Azure documentation</a
>
to retrieve the credentials required below.
</p>
</span>
</div>
</div>
</div>
<div class="col-sm-12 form-section-title"> Environment details </div>
<!-- name-input -->
<div class="form-group">
<label for="container_name" class="col-sm-3 col-lg-2 control-label text-left">Name</label>
<div class="col-sm-9 col-lg-10">
<input
type="text"
class="form-control"
name="container_name"
ng-model="formValues.Name"
placeholder="e.g. docker-prod01 / kubernetes-cluster01"
required
auto-focus
data-cy="endpointCreate-nameInput"
/>
</div>
</div>
<div class="form-group" ng-show="endpointCreationForm.container_name.$invalid">
<div class="col-sm-12 small text-warning">
<div ng-messages="endpointCreationForm.container_name.$error">
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
</div>
</div>
</div>
<!-- !name-input -->
<!-- connect-via-socket-input -->
<div ng-if="state.EnvironmentType === 'docker'">
<div class="form-group" style="padding-left: 15px">
<label for="connect_socket" class="col-sm_12 control-label text-left"> Connect via socket </label>
<label class="switch" style="margin-left: 20px">
<input type="checkbox" ng-model="formValues.ConnectSocket" /><i data-cy="endpointCreate-connectSocketSwitch"></i>
</label>
</div>
</div>
<div ng-if="state.EnvironmentType === 'docker' && formValues.ConnectSocket">
<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="formValues.OverrideSocket" /><i data-cy="endpointCreate-overrideSocketSwitch"></i>
</label>
</div>
<div ng-if="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 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="formValues.SocketPath"
placeholder="e.g. /var/run/docker.sock (on Linux) or //./pipe/docker_engine (on Windows)"
required
data-cy="endpointCreate-socketPathInput"
/>
</div>
</div>
<div class="form-group" ng-show="endpointCreationForm.socket_path.$invalid">
<div class="col-sm-12 small text-warning">
<div ng-messages="endpointCreationForm.socket_path.$error">
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
</div>
</div>
</div>
</div>
</div>
<!-- !connect-via-socket-input -->
<!-- endpoint-url-input -->
<div ng-if="(state.EnvironmentType === 'docker' && !formValues.ConnectSocket) || state.EnvironmentType === 'agent'">
<div class="form-group">
<label for="endpoint_url" class="col-sm-3 col-lg-2 control-label text-left">
Environment URL
<portainer-tooltip
message="'URL or IP address of a Docker host. The Docker API must be exposed over a TCP port. Please refer to the Docker documentation to configure it.'"
>
</portainer-tooltip>
</label>
<div class="col-sm-9 col-lg-10">
<input
ng-if="state.EnvironmentType === 'docker'"
type="text"
class="form-control"
name="endpoint_url"
ng-model="formValues.URL"
placeholder="e.g. 10.0.0.10:2375 or mydocker.mydomain.com:2375"
required
data-cy="endpointCreate-endpointUrlDockerInput"
/>
<input
ng-if="state.EnvironmentType === 'agent'"
type="text"
class="form-control"
name="endpoint_url"
ng-model="formValues.URL"
placeholder="e.g. 10.0.0.10:9001 or tasks.portainer_agent:9001"
required
data-cy="endpointCreate-endpointUrlAgentInput"
/>
</div>
</div>
<div class="form-group" ng-show="endpointCreationForm.endpoint_url.$invalid">
<div class="col-sm-12 small text-warning">
<div ng-messages="endpointCreationForm.endpoint_url.$error">
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
</div>
</div>
</div>
</div>
<!-- !endpoint-url-input -->
<!-- portainer-instance-input -->
<div ng-if="state.EnvironmentType === 'edge_agent'">
<div class="form-group">
<label for="endpoint_url" class="col-sm-3 col-lg-2 control-label text-left">
Portainer server URL
<portainer-tooltip message="'URL of the Portainer instance that the agent will use to initiate the communications.'"></portainer-tooltip>
</label>
<div class="col-sm-9 col-lg-10">
<input
type="text"
class="form-control"
name="endpoint_url"
ng-model="formValues.URL"
placeholder="e.g. 10.0.0.10:9443 or portainer.mydomain.com"
required
data-cy="endpointCreate-portainerServerUrlInput"
/>
</div>
</div>
<div class="form-group" ng-show="endpointCreationForm.endpoint_url.$invalid">
<div class="col-sm-12 small text-warning">
<div ng-messages="endpointCreationForm.endpoint_url.$error">
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
</div>
</div>
</div>
<!-- !portainer-instance-input -->
<div>
<div class="col-sm-12 form-section-title"> Check-in Intervals </div>
<edge-checkin-interval-field value="formValues.EdgeCheckinInterval" on-change="(onChangeCheckInInterval)"></edge-checkin-interval-field>
</div>
</div>
<!-- endpoint-public-url-input -->
<div ng-if="state.EnvironmentType === 'docker' || state.EnvironmentType === 'agent'">
<div class="form-group">
<label for="endpoint_public_url" class="col-sm-3 col-lg-2 control-label text-left">
Public IP
<portainer-tooltip message="'URL or IP address where exposed containers will be reachable. This field is optional and will default to the environment URL.'">
</portainer-tooltip>
</label>
<div class="col-sm-9 col-lg-10">
<input
type="text"
class="form-control"
id="endpoint_public_url"
ng-model="formValues.PublicURL"
placeholder="e.g. 10.0.0.10 or mydocker.mydomain.com"
data-cy="endpointCreate-publicIpInput"
/>
</div>
</div>
</div>
<!-- !endpoint-public-url-input -->
<!-- azure-details -->
<div ng-if="state.EnvironmentType === 'azure'">
<!-- applicationId-input -->
<div class="form-group">
<label for="azure_credential_appid" class="col-sm-3 col-lg-2 control-label text-left">Application ID</label>
<div class="col-sm-9 col-lg-10">
<input
type="text"
class="form-control"
name="azure_credential_appid"
ng-model="formValues.AzureApplicationId"
placeholder="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
required
data-cy="endpointCreate-azureAppIdInput"
/>
</div>
</div>
<div class="form-group" ng-show="endpointCreationForm.azure_credential_appid.$invalid">
<div class="col-sm-12 small text-warning">
<div ng-messages="endpointCreationForm.azure_credential_appid.$error">
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
</div>
</div>
</div>
<!-- !applicationId-input -->
<!-- tenantId-input -->
<div class="form-group">
<label for="azure_credential_tenantid" class="col-sm-3 col-lg-2 control-label text-left">Tenant ID</label>
<div class="col-sm-9 col-lg-10">
<input
type="text"
class="form-control"
name="azure_credential_tenantid"
ng-model="formValues.AzureTenantId"
placeholder="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
required
data-cy="endpointCreate-azureTenantIdInput"
/>
</div>
</div>
<div class="form-group" ng-show="endpointCreationForm.azure_credential_tenantid.$invalid">
<div class="col-sm-12 small text-warning">
<div ng-messages="endpointCreationForm.azure_credential_tenantid.$error">
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
</div>
</div>
</div>
<!-- !tenantId-input -->
<!-- authenticationkey-input -->
<div class="form-group">
<label for="azure_credential_authkey" class="col-sm-3 col-lg-2 control-label text-left">Authentication key</label>
<div class="col-sm-9 col-lg-10">
<input
type="text"
class="form-control"
name="azure_credential_authkey"
ng-model="formValues.AzureAuthenticationKey"
placeholder="cOrXoK/1D35w8YQ8nH1/8ZGwzz45JIYD5jxHKXEQknk="
required
data-cy="endpointCreate-azureAuthKeyInput"
/>
</div>
</div>
<div class="form-group" ng-show="endpointCreationForm.azure_credential_authkey.$invalid">
<div class="col-sm-12 small text-warning">
<div ng-messages="endpointCreationForm.azure_credential_authkey.$error">
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
</div>
</div>
</div>
<!-- !authenticationkey-input -->
</div>
<!-- !azure-details -->
<!-- endpoint-security -->
<por-endpoint-security ng-if="state.EnvironmentType === 'docker' && !formValues.ConnectSocket" form-data="formValues.SecurityFormData"></por-endpoint-security>
<!-- !endpoint-security -->
<div class="col-sm-12 form-section-title"> Metadata </div>
<!-- group -->
<div class="form-group">
<label for="endpoint_group" class="col-sm-3 col-lg-2 control-label text-left"> Group </label>
<div class="col-sm-9 col-lg-10">
<select
ng-options="group.Id as group.Name for group in groups"
ng-model="formValues.GroupId"
id="endpoint_group"
class="form-control"
data-cy="endpointCreate-endpointGroup"
></select>
</div>
</div>
<!-- !group -->
<tag-selector ng-if="formValues" value="formValues.TagIds" allow-create="state.allowCreateTag" on-change="(onChangeTags)"> </tag-selector>
<div class="col-sm-12 form-section-title"> Actions </div>
<!-- actions -->
<div class="form-group">
<div class="col-sm-12">
<button
ng-if="state.EnvironmentType === 'docker'"
type="submit"
class="btn btn-primary btn-sm"
ng-disabled="state.actionInProgress || !endpointCreationForm.$valid || (formValues.TLS && ((formValues.TLSVerify && !formValues.TLSCACert) || (formValues.TLSClientCert && (!formValues.TLSCert || !formValues.TLSKey))))"
ng-click="addDockerEndpoint()"
button-spinner="state.actionInProgress"
data-cy="endpointCreate-createDockerEndpoint"
analytics-on
analytics-category="portainer"
analytics-event="portainer-endpoint-creation"
analytics-properties="{ metadata: { type: 'docker-api' } }"
>
<span ng-hide="state.actionInProgress"><i class="fa fa-plus" aria-hidden="true"></i> Add environment</span>
<span ng-show="state.actionInProgress">Creating environment...</span>
</button>
<button
ng-if="state.EnvironmentType === 'agent'"
type="submit"
class="btn btn-primary btn-sm"
ng-disabled="state.actionInProgress || !endpointCreationForm.$valid"
ng-click="addAgentEndpoint()"
button-spinner="state.actionInProgress"
data-cy="endpointCreate-createAgentEndpoint"
>
<span ng-hide="state.actionInProgress"><i class="fa fa-plus" aria-hidden="true"></i> Add environment</span>
<span ng-show="state.actionInProgress">Creating environment...</span>
</button>
<button
ng-if="state.EnvironmentType === 'edge_agent'"
type="submit"
class="btn btn-primary btn-sm"
ng-disabled="state.actionInProgress || !endpointCreationForm.$valid"
ng-click="addEdgeAgentEndpoint()"
button-spinner="state.actionInProgress"
data-cy="endpointCreate-createEdgeAgentEndpoint"
analytics-on
analytics-category="portainer"
analytics-event="portainer-endpoint-creation"
analytics-properties="{ metadata: { type: 'edge-agent' } }"
>
<span ng-hide="state.actionInProgress"><i class="fa fa-plus" aria-hidden="true"></i> Add environment</span>
<span ng-show="state.actionInProgress">Creating environment...</span>
</button>
<button
ng-if="state.EnvironmentType === 'kubernetes'"
type="submit"
class="btn btn-primary btn-sm"
ng-disabled="state.actionInProgress || !endpointCreationForm.$valid"
ng-click="addKubernetesEndpoint()"
button-spinner="state.actionInProgress"
analytics-on
analytics-category="portainer"
analytics-event="portainer-endpoint-creation"
analytics-properties="{ metadata: { type: 'kubernetes-api' } }"
>
<span ng-hide="state.actionInProgress"><i class="fa fa-plus" aria-hidden="true"></i> Add environment</span>
<span ng-show="state.actionInProgress">Creating environment...</span>
</button>
<button
ng-if="state.EnvironmentType === 'azure'"
type="submit"
class="btn btn-primary btn-sm"
ng-disabled="state.actionInProgress || !endpointCreationForm.$valid"
ng-click="addAzureEndpoint()"
button-spinner="state.actionInProgress"
data-cy="endpointCreate-createAzureEndpoint"
analytics-on
analytics-category="portainer"
analytics-event="portainer-endpoint-creation"
analytics-properties="{ metadata: { type: 'azure-api' } }"
>
<span ng-hide="state.actionInProgress"><pr-icon icon="'plus'" feather="true"></pr-icon> Add environment</span>
<span ng-show="state.actionInProgress">Creating environment...</span>
</button>
</div>
</div>
<!-- !actions -->
</form>
</rd-widget-body>
</rd-widget>
</div>
</div>

View File

@ -8,6 +8,8 @@ import { Button } from '@@/buttons';
import { PageHeader } from '@@/PageHeader';
import { Widget, WidgetBody, WidgetTitle } from '@@/Widget';
import { useCreateEdgeDeviceParam } from '../hooks/useCreateEdgeDeviceParam';
import {
EnvironmentSelector,
EnvironmentSelectorValue,
@ -15,6 +17,8 @@ import {
import { environmentTypes } from './environment-types';
export function EnvironmentTypeSelectView() {
const createEdgeDevice = useCreateEdgeDeviceParam();
const [types, setTypes] = useState<EnvironmentSelectorValue[]>([]);
const { trackEvent } = useAnalytics();
const router = useRouter();
@ -31,7 +35,11 @@ export function EnvironmentTypeSelectView() {
<Widget>
<WidgetTitle icon="fa-magic" title="Environment Wizard" />
<WidgetBody>
<EnvironmentSelector value={types} onChange={setTypes} />
<EnvironmentSelector
value={types}
onChange={setTypes}
createEdgeDevice={createEdgeDevice}
/>
<Button
disabled={types.length === 0}
onClick={() => startWizard()}
@ -63,6 +71,7 @@ export function EnvironmentTypeSelectView() {
router.stateService.go('portainer.wizard.endpoints.create', {
envType: types,
...(createEdgeDevice ? { edgeDevice: createEdgeDevice } : {}),
});
}
}

View File

@ -9,9 +9,16 @@ export type EnvironmentSelectorValue = typeof environmentTypes[number]['id'];
interface Props {
value: EnvironmentSelectorValue[];
onChange(value: EnvironmentSelectorValue[]): void;
createEdgeDevice?: boolean;
}
export function EnvironmentSelector({ value, onChange }: Props) {
const hasEdge: EnvironmentSelectorValue[] = ['docker', 'kubernetes'];
export function EnvironmentSelector({
value,
onChange,
createEdgeDevice,
}: Props) {
return (
<div className="row">
<FormSection title="Select your environment(s)">
@ -20,17 +27,19 @@ export function EnvironmentSelector({ value, onChange }: Props) {
apply.
</p>
<div className="flex gap-4 flex-wrap">
{environmentTypes.map((eType) => (
<Option
key={eType.id}
featureId={eType.featureId}
title={eType.title}
description={eType.description}
icon={eType.icon}
active={value.includes(eType.id)}
onClick={() => handleClick(eType.id)}
/>
))}
{filterEdgeDevicesIfNeed(environmentTypes, createEdgeDevice).map(
(eType) => (
<Option
key={eType.id}
featureId={eType.featureId}
title={eType.title}
description={eType.description}
icon={eType.icon}
active={value.includes(eType.id)}
onClick={() => handleClick(eType.id)}
/>
)
)}
</div>
</FormSection>
</div>
@ -45,3 +54,14 @@ export function EnvironmentSelector({ value, onChange }: Props) {
onChange([...value, eType]);
}
}
function filterEdgeDevicesIfNeed(
types: typeof environmentTypes,
createEdgeDevice?: boolean
) {
if (!createEdgeDevice) {
return types;
}
return types.filter((eType) => hasEdge.includes(eType.id));
}

View File

@ -7,6 +7,7 @@ import { BoxSelector, type BoxSelectorOption } from '@@/BoxSelector';
import { AnalyticsStateKey } from '../types';
import { EdgeAgentTab } from '../shared/EdgeAgentTab';
import { useFilterEdgeOptionsIfNeeded } from '../useOnlyEdgeOptions';
import { AgentTab } from './AgentTab';
import { APITab } from './APITab';
@ -16,7 +17,9 @@ interface Props {
onCreate(environment: Environment, analytics: AnalyticsStateKey): void;
}
const options: BoxSelectorOption<'agent' | 'api' | 'socket' | 'edgeAgent'>[] = [
const defaultOptions: BoxSelectorOption<
'agent' | 'api' | 'socket' | 'edgeAgent'
>[] = [
{
id: 'agent',
icon: 'fa fa-bolt',
@ -49,6 +52,8 @@ const options: BoxSelectorOption<'agent' | 'api' | 'socket' | 'edgeAgent'>[] = [
];
export function WizardDocker({ onCreate }: Props) {
const options = useFilterEdgeOptionsIfNeeded(defaultOptions, 'edgeAgent');
const [creationType, setCreationType] = useState(options[0].value);
const tab = getTab(creationType);

View File

@ -13,6 +13,7 @@ import { BEFeatureIndicator } from '@@/BEFeatureIndicator';
import { AnalyticsStateKey } from '../types';
import { EdgeAgentTab } from '../shared/EdgeAgentTab';
import { useFilterEdgeOptionsIfNeeded } from '../useOnlyEdgeOptions';
import { AgentPanel } from './AgentPanel';
import { KubeConfigTeaserForm } from './KubeConfigTeaserForm';
@ -21,11 +22,7 @@ interface Props {
onCreate(environment: Environment, analytics: AnalyticsStateKey): void;
}
const options: BoxSelectorOption<
| EnvironmentCreationTypes.AgentEnvironment
| EnvironmentCreationTypes.EdgeAgentEnvironment
| EnvironmentCreationTypes.KubeConfigEnvironment
>[] = [
const defaultOptions: BoxSelectorOption<EnvironmentCreationTypes>[] = [
{
id: 'agent_endpoint',
icon: 'fa fa-bolt',
@ -52,6 +49,11 @@ const options: BoxSelectorOption<
];
export function WizardKubernetes({ onCreate }: Props) {
const options = useFilterEdgeOptionsIfNeeded(
defaultOptions,
EnvironmentCreationTypes.EdgeAgentEnvironment
);
const [creationType, setCreationType] = useState(options[0].value);
const tab = getTab(creationType);

View File

@ -0,0 +1,18 @@
import { EnvironmentCreationTypes } from '@/portainer/environments/types';
import { BoxSelectorOption } from '@@/BoxSelector';
import { useCreateEdgeDeviceParam } from '../hooks/useCreateEdgeDeviceParam';
export function useFilterEdgeOptionsIfNeeded<T = EnvironmentCreationTypes>(
options: BoxSelectorOption<T>[],
edgeValue: T
) {
const createEdgeDevice = useCreateEdgeDeviceParam();
if (!createEdgeDevice) {
return options;
}
return options.filter((option) => option.value === edgeValue);
}

View File

@ -0,0 +1,11 @@
import { useCurrentStateAndParams } from '@uirouter/react';
export function useCreateEdgeDeviceParam() {
const {
params: { edgeDevice: edgeDeviceParam },
} = useCurrentStateAndParams();
const createEdgeDevice = edgeDeviceParam ? edgeDeviceParam === 'true' : false;
return createEdgeDevice;
}