mirror of https://github.com/portainer/portainer
feat(wizard): add edge form [EE-3000] (#6979)
parent
e686d64011
commit
ac096dda46
|
@ -11,6 +11,7 @@ interface Props {
|
||||||
isDefaultHidden?: boolean;
|
isDefaultHidden?: boolean;
|
||||||
label?: string;
|
label?: string;
|
||||||
tooltip?: string;
|
tooltip?: string;
|
||||||
|
readonly?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const checkinIntervalOptions = [
|
export const checkinIntervalOptions = [
|
||||||
|
@ -34,6 +35,7 @@ export const checkinIntervalOptions = [
|
||||||
|
|
||||||
export function EdgeCheckinIntervalField({
|
export function EdgeCheckinIntervalField({
|
||||||
value,
|
value,
|
||||||
|
readonly,
|
||||||
onChange,
|
onChange,
|
||||||
isDefaultHidden = false,
|
isDefaultHidden = false,
|
||||||
label = 'Poll frequency',
|
label = 'Poll frequency',
|
||||||
|
@ -49,6 +51,7 @@ export function EdgeCheckinIntervalField({
|
||||||
onChange(parseInt(e.currentTarget.value, 10));
|
onChange(parseInt(e.currentTarget.value, 10));
|
||||||
}}
|
}}
|
||||||
options={options}
|
options={options}
|
||||||
|
disabled={readonly}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
);
|
);
|
||||||
|
@ -60,6 +63,7 @@ export const EdgeCheckinIntervalFieldAngular = r2a(EdgeCheckinIntervalField, [
|
||||||
'isDefaultHidden',
|
'isDefaultHidden',
|
||||||
'tooltip',
|
'tooltip',
|
||||||
'label',
|
'label',
|
||||||
|
'readonly',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
function useOptions(isDefaultHidden: boolean) {
|
function useOptions(isDefaultHidden: boolean) {
|
||||||
|
|
|
@ -1,60 +0,0 @@
|
||||||
import { useState } from 'react';
|
|
||||||
|
|
||||||
import { useStatus } from '@/portainer/services/api/status.service';
|
|
||||||
import { r2a } from '@/react-tools/react2angular';
|
|
||||||
import { useSettings } from '@/portainer/settings/queries';
|
|
||||||
|
|
||||||
import { EdgePropertiesForm } from './EdgePropertiesForm';
|
|
||||||
import { ScriptTabs } from './ScriptTabs';
|
|
||||||
import { EdgeProperties } from './types';
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
edgeKey: string;
|
|
||||||
edgeId?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function EdgeScriptForm({ edgeKey, edgeId }: Props) {
|
|
||||||
const [edgeProperties, setEdgeProperties] = useState<EdgeProperties>({
|
|
||||||
allowSelfSignedCertificates: true,
|
|
||||||
envVars: '',
|
|
||||||
edgeIdGenerator: '',
|
|
||||||
os: 'linux',
|
|
||||||
platform: 'k8s',
|
|
||||||
});
|
|
||||||
|
|
||||||
const settingsQuery = useSettings((settings) => settings.AgentSecret);
|
|
||||||
|
|
||||||
const versionQuery = useStatus((status) => status.Version);
|
|
||||||
|
|
||||||
if (!versionQuery.data) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const agentVersion = versionQuery.data;
|
|
||||||
const agentSecret = settingsQuery.data;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<EdgePropertiesForm
|
|
||||||
setFieldValue={(key, value) =>
|
|
||||||
setEdgeProperties({ ...edgeProperties, [key]: value })
|
|
||||||
}
|
|
||||||
values={edgeProperties}
|
|
||||||
hideIdGetter={edgeId !== undefined}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<ScriptTabs
|
|
||||||
values={edgeProperties}
|
|
||||||
agentVersion={agentVersion}
|
|
||||||
edgeKey={edgeKey}
|
|
||||||
onPlatformChange={(platform) =>
|
|
||||||
setEdgeProperties({ ...edgeProperties, platform })
|
|
||||||
}
|
|
||||||
edgeId={edgeId}
|
|
||||||
agentSecret={agentSecret}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export const EdgeScriptFormAngular = r2a(EdgeScriptForm, ['edgeKey', 'edgeId']);
|
|
|
@ -1 +0,0 @@
|
||||||
export { EdgeScriptForm, EdgeScriptFormAngular } from './EdgeScriptForm';
|
|
|
@ -1,10 +0,0 @@
|
||||||
export type Platform = 'standalone' | 'swarm' | 'k8s';
|
|
||||||
export type OS = 'win' | 'linux';
|
|
||||||
|
|
||||||
export interface EdgeProperties {
|
|
||||||
os: OS;
|
|
||||||
allowSelfSignedCertificates: boolean;
|
|
||||||
envVars: string;
|
|
||||||
edgeIdGenerator: string;
|
|
||||||
platform: Platform;
|
|
||||||
}
|
|
|
@ -1,9 +1,14 @@
|
||||||
import angular from 'angular';
|
import angular from 'angular';
|
||||||
|
|
||||||
|
import { r2a } from '@/react-tools/react2angular';
|
||||||
|
import { EdgeScriptForm } from '@/react/edge/components/EdgeScriptForm';
|
||||||
|
|
||||||
import { EdgeCheckinIntervalFieldAngular } from './EdgeCheckInIntervalField';
|
import { EdgeCheckinIntervalFieldAngular } from './EdgeCheckInIntervalField';
|
||||||
import { EdgeScriptFormAngular } from './EdgeScriptForm';
|
|
||||||
|
|
||||||
export const componentsModule = angular
|
export const componentsModule = angular
|
||||||
.module('app.edge.components', [])
|
.module('app.edge.components', [])
|
||||||
.component('edgeCheckinIntervalField', EdgeCheckinIntervalFieldAngular)
|
.component(
|
||||||
.component('edgeScriptForm', EdgeScriptFormAngular).name;
|
'edgeScriptForm',
|
||||||
|
r2a(EdgeScriptForm, ['edgeInfo', 'commands', 'isNomadTokenVisible'])
|
||||||
|
)
|
||||||
|
.component('edgeCheckinIntervalField', EdgeCheckinIntervalFieldAngular).name;
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
.boxselector_wrapper {
|
.boxselector_wrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-flow: row wrap;
|
flex-flow: row wrap;
|
||||||
margin: 5px;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,8 @@ import { react2angular } from '@/react-tools/react2angular';
|
||||||
import { BoxSelector, buildOption } from './BoxSelector';
|
import { BoxSelector, buildOption } from './BoxSelector';
|
||||||
import { BoxSelectorAngular } from './BoxSelectorAngular';
|
import { BoxSelectorAngular } from './BoxSelectorAngular';
|
||||||
|
|
||||||
|
export { type BoxSelectorOption } from './types';
|
||||||
|
|
||||||
export { BoxSelector, buildOption };
|
export { BoxSelector, buildOption };
|
||||||
const BoxSelectorReact = react2angular(BoxSelector, [
|
const BoxSelectorReact = react2angular(BoxSelector, [
|
||||||
'value',
|
'value',
|
||||||
|
|
|
@ -60,6 +60,7 @@ export type Environment = {
|
||||||
TagIds: TagId[];
|
TagIds: TagId[];
|
||||||
GroupId: EnvironmentGroupId;
|
GroupId: EnvironmentGroupId;
|
||||||
EdgeID?: string;
|
EdgeID?: string;
|
||||||
|
EdgeKey: string;
|
||||||
EdgeCheckinInterval?: number;
|
EdgeCheckinInterval?: number;
|
||||||
QueryDate?: number;
|
QueryDate?: number;
|
||||||
LastCheckInDate?: number;
|
LastCheckInDate?: number;
|
||||||
|
@ -73,7 +74,6 @@ export type Environment = {
|
||||||
UserTrusted: boolean;
|
UserTrusted: boolean;
|
||||||
AMTDeviceGUID?: string;
|
AMTDeviceGUID?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TS reference of endpoint_create.go#EndpointCreationType iota
|
* TS reference of endpoint_create.go#EndpointCreationType iota
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -70,5 +70,6 @@ function mockEnvironment(type: EnvironmentType): Environment {
|
||||||
},
|
},
|
||||||
URL: 'url',
|
URL: 'url',
|
||||||
UserTrusted: false,
|
UserTrusted: false,
|
||||||
|
EdgeKey: '',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@ test('loads component', async () => {
|
||||||
Kubernetes: { Snapshots: [] },
|
Kubernetes: { Snapshots: [] },
|
||||||
Id: 3,
|
Id: 3,
|
||||||
UserTrusted: false,
|
UserTrusted: false,
|
||||||
|
EdgeKey: '',
|
||||||
};
|
};
|
||||||
const { getByText } = renderComponent(env);
|
const { getByText } = renderComponent(env);
|
||||||
|
|
||||||
|
@ -44,6 +45,7 @@ test('shows group name', async () => {
|
||||||
Kubernetes: { Snapshots: [] },
|
Kubernetes: { Snapshots: [] },
|
||||||
Id: 3,
|
Id: 3,
|
||||||
UserTrusted: false,
|
UserTrusted: false,
|
||||||
|
EdgeKey: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
const { findByText } = renderComponent(env, { Name: groupName });
|
const { findByText } = renderComponent(env, { Name: groupName });
|
||||||
|
|
|
@ -2,12 +2,23 @@ import { useMutation } from 'react-query';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
import { Widget, WidgetBody, WidgetTitle } from '@/portainer/components/widget';
|
import { Widget, WidgetBody, WidgetTitle } from '@/portainer/components/widget';
|
||||||
import { EdgeScriptForm } from '@/edge/components/EdgeScriptForm';
|
|
||||||
import { generateKey } from '@/portainer/environments/environment.service/edge';
|
import { generateKey } from '@/portainer/environments/environment.service/edge';
|
||||||
import { useSettings } from '@/portainer/settings/queries';
|
import { useSettings } from '@/portainer/settings/queries';
|
||||||
|
import { EdgeScriptForm } from '@/react/edge/components/EdgeScriptForm';
|
||||||
|
import { commandsTabs } from '@/react/edge/components/EdgeScriptForm/scripts';
|
||||||
|
|
||||||
import { AutoEnvCreationSettingsForm } from './AutoEnvCreationSettingsForm';
|
import { AutoEnvCreationSettingsForm } from './AutoEnvCreationSettingsForm';
|
||||||
|
|
||||||
|
const commands = {
|
||||||
|
linux: [
|
||||||
|
commandsTabs.k8sLinux,
|
||||||
|
commandsTabs.swarmLinux,
|
||||||
|
commandsTabs.standaloneLinux,
|
||||||
|
commandsTabs.nomadLinux,
|
||||||
|
],
|
||||||
|
win: [commandsTabs.swarmWindows, commandsTabs.standaloneWindow],
|
||||||
|
};
|
||||||
|
|
||||||
export function AutomaticEdgeEnvCreation() {
|
export function AutomaticEdgeEnvCreation() {
|
||||||
const edgeKeyMutation = useGenerateKeyMutation();
|
const edgeKeyMutation = useGenerateKeyMutation();
|
||||||
const { mutate: generateKey } = edgeKeyMutation;
|
const { mutate: generateKey } = edgeKeyMutation;
|
||||||
|
@ -39,7 +50,13 @@ export function AutomaticEdgeEnvCreation() {
|
||||||
{edgeKeyMutation.isLoading ? (
|
{edgeKeyMutation.isLoading ? (
|
||||||
<div>Generating key for {url} ... </div>
|
<div>Generating key for {url} ... </div>
|
||||||
) : (
|
) : (
|
||||||
edgeKey && <EdgeScriptForm edgeKey={edgeKey} />
|
edgeKey && (
|
||||||
|
<EdgeScriptForm
|
||||||
|
edgeInfo={{ key: edgeKey }}
|
||||||
|
commands={commands}
|
||||||
|
isNomadTokenVisible
|
||||||
|
/>
|
||||||
|
)
|
||||||
)}
|
)}
|
||||||
</WidgetBody>
|
</WidgetBody>
|
||||||
</Widget>
|
</Widget>
|
||||||
|
|
|
@ -54,7 +54,12 @@
|
||||||
</p>
|
</p>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<edge-script-form edge-key="endpoint.EdgeKey" edge-id="endpoint.EdgeID"></edge-script-form>
|
<div class="col-sm-12 form-section-title"> Edge agent deployment script </div>
|
||||||
|
<edge-script-form
|
||||||
|
edge-info="{ key: endpoint.EdgeKey, id: endpoint.EdgeID }"
|
||||||
|
commands="state.edgeScriptCommands"
|
||||||
|
is-nomad-token-visible="state.showNomad"
|
||||||
|
></edge-script-form>
|
||||||
|
|
||||||
<span class="small text-muted">
|
<span class="small text-muted">
|
||||||
<div class="col-sm-12 form-section-title" style="margin-top: 25px"> Join token </div>
|
<div class="col-sm-12 form-section-title" style="margin-top: 25px"> Join token </div>
|
||||||
|
|
|
@ -8,6 +8,8 @@ import { getAMTInfo } from 'Portainer/hostmanagement/open-amt/open-amt.service';
|
||||||
import { confirmAsync } from '@/portainer/services/modal.service/confirm';
|
import { confirmAsync } from '@/portainer/services/modal.service/confirm';
|
||||||
import { isEdgeEnvironment } from '@/portainer/environments/utils';
|
import { isEdgeEnvironment } from '@/portainer/environments/utils';
|
||||||
|
|
||||||
|
import { commandsTabs } from '@/react/edge/components/EdgeScriptForm/scripts';
|
||||||
|
|
||||||
angular.module('portainer.app').controller('EndpointController', EndpointController);
|
angular.module('portainer.app').controller('EndpointController', EndpointController);
|
||||||
|
|
||||||
/* @ngInject */
|
/* @ngInject */
|
||||||
|
@ -29,6 +31,7 @@ function EndpointController(
|
||||||
$scope.onChangeCheckInInterval = onChangeCheckInInterval;
|
$scope.onChangeCheckInInterval = onChangeCheckInInterval;
|
||||||
$scope.setFieldValue = setFieldValue;
|
$scope.setFieldValue = setFieldValue;
|
||||||
$scope.onChangeTags = onChangeTags;
|
$scope.onChangeTags = onChangeTags;
|
||||||
|
const isBE = process.env.PORTAINER_EDITION === 'BE';
|
||||||
|
|
||||||
$scope.state = {
|
$scope.state = {
|
||||||
uploadInProgress: false,
|
uploadInProgress: false,
|
||||||
|
@ -41,6 +44,11 @@ function EndpointController(
|
||||||
allowCreate: Authentication.isAdmin(),
|
allowCreate: Authentication.isAdmin(),
|
||||||
allowSelfSignedCerts: true,
|
allowSelfSignedCerts: true,
|
||||||
showAMTInfo: false,
|
showAMTInfo: false,
|
||||||
|
showNomad: isBE,
|
||||||
|
edgeScriptCommands: {
|
||||||
|
linux: _.compact([commandsTabs.k8sLinux, commandsTabs.swarmLinux, commandsTabs.standaloneLinux, isBE && commandsTabs.nomadLinux]),
|
||||||
|
win: [commandsTabs.swarmWindows, commandsTabs.standaloneWindow],
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.formValues = {
|
$scope.formValues = {
|
||||||
|
|
|
@ -0,0 +1,70 @@
|
||||||
|
import { Formik } from 'formik';
|
||||||
|
|
||||||
|
import { OsSelector } from './OsSelector';
|
||||||
|
import { CommandTab } from './scripts';
|
||||||
|
import { ScriptTabs } from './ScriptTabs';
|
||||||
|
import { EdgeScriptSettingsFieldset } from './EdgeScriptSettingsFieldset';
|
||||||
|
import { validationSchema } from './EdgeScriptForm.validation';
|
||||||
|
import { ScriptFormValues, OS, Platform, EdgeInfo } from './types';
|
||||||
|
|
||||||
|
const edgePropertiesFormInitialValues: ScriptFormValues = {
|
||||||
|
allowSelfSignedCertificates: true,
|
||||||
|
envVars: '',
|
||||||
|
os: 'linux' as OS,
|
||||||
|
platform: 'k8s' as Platform,
|
||||||
|
nomadToken: '',
|
||||||
|
authEnabled: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
edgeInfo: EdgeInfo;
|
||||||
|
commands: CommandTab[] | Partial<Record<OS, CommandTab[]>>;
|
||||||
|
isNomadTokenVisible?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function EdgeScriptForm({
|
||||||
|
edgeInfo,
|
||||||
|
commands,
|
||||||
|
isNomadTokenVisible,
|
||||||
|
}: Props) {
|
||||||
|
const showOsSelector = !(commands instanceof Array);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="form-horizontal">
|
||||||
|
<Formik
|
||||||
|
initialValues={edgePropertiesFormInitialValues}
|
||||||
|
validationSchema={() => validationSchema(isNomadTokenVisible)}
|
||||||
|
onSubmit={() => {}}
|
||||||
|
>
|
||||||
|
{({ values, setFieldValue }) => (
|
||||||
|
<>
|
||||||
|
<EdgeScriptSettingsFieldset
|
||||||
|
isNomadTokenVisible={
|
||||||
|
isNomadTokenVisible && values.platform === 'nomad'
|
||||||
|
}
|
||||||
|
hideIdGetter={edgeInfo.id !== undefined}
|
||||||
|
/>
|
||||||
|
<div className="mt-8">
|
||||||
|
{showOsSelector && (
|
||||||
|
<OsSelector
|
||||||
|
value={values.os}
|
||||||
|
onChange={(value) => setFieldValue('os', value)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<ScriptTabs
|
||||||
|
edgeId={edgeInfo.id}
|
||||||
|
edgeKey={edgeInfo.key}
|
||||||
|
values={values}
|
||||||
|
commands={showOsSelector ? commands[values.os] || [] : commands}
|
||||||
|
platform={values.platform}
|
||||||
|
onPlatformChange={(platform) =>
|
||||||
|
setFieldValue('platform', platform)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Formik>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { object, boolean, string } from 'yup';
|
||||||
|
|
||||||
|
import { validation as nomadTokenValidation } from './NomadTokenField';
|
||||||
|
|
||||||
|
export function validationSchema(isNomadTokenVisible?: boolean) {
|
||||||
|
return object().shape({
|
||||||
|
allowSelfSignedCertificates: boolean(),
|
||||||
|
envVars: string(),
|
||||||
|
...(isNomadTokenVisible ? nomadTokenValidation() : {}),
|
||||||
|
});
|
||||||
|
}
|
|
@ -1,32 +1,26 @@
|
||||||
|
import { useFormikContext, Field } from 'formik';
|
||||||
|
|
||||||
import { FormControl } from '@/portainer/components/form-components/FormControl';
|
import { FormControl } from '@/portainer/components/form-components/FormControl';
|
||||||
import { Input } from '@/portainer/components/form-components/Input';
|
import { Input } from '@/portainer/components/form-components/Input';
|
||||||
import { FormSectionTitle } from '@/portainer/components/form-components/FormSectionTitle';
|
|
||||||
import { SwitchField } from '@/portainer/components/form-components/SwitchField';
|
import { SwitchField } from '@/portainer/components/form-components/SwitchField';
|
||||||
import { TextTip } from '@/portainer/components/Tip/TextTip';
|
import { TextTip } from '@/portainer/components/Tip/TextTip';
|
||||||
|
|
||||||
import { OsSelector } from './OsSelector';
|
import { NomadTokenField } from './NomadTokenField';
|
||||||
import { EdgeProperties } from './types';
|
import { ScriptFormValues } from './types';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
setFieldValue<T>(key: string, value: T): void;
|
isNomadTokenVisible?: boolean;
|
||||||
values: EdgeProperties;
|
hideIdGetter?: boolean;
|
||||||
hideIdGetter: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function EdgePropertiesForm({
|
export function EdgeScriptSettingsFieldset({
|
||||||
setFieldValue,
|
isNomadTokenVisible,
|
||||||
values,
|
|
||||||
hideIdGetter,
|
hideIdGetter,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
|
const { values, setFieldValue } = useFormikContext<ScriptFormValues>();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form className="form-horizontal">
|
<>
|
||||||
<FormSectionTitle>Edge agent deployment script</FormSectionTitle>
|
|
||||||
|
|
||||||
<OsSelector
|
|
||||||
value={values.os}
|
|
||||||
onChange={(os) => setFieldValue('os', os)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{!hideIdGetter && (
|
{!hideIdGetter && (
|
||||||
<>
|
<>
|
||||||
<FormControl
|
<FormControl
|
||||||
|
@ -42,40 +36,44 @@ export function EdgePropertiesForm({
|
||||||
onChange={(e) => setFieldValue(e.target.name, e.target.value)}
|
onChange={(e) => setFieldValue(e.target.name, e.target.value)}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
<div className="form-group">
|
||||||
<TextTip color="blue">
|
<div className="col-sm-12">
|
||||||
<code>PORTAINER_EDGE_ID</code> environment variable is required to
|
<TextTip color="blue">
|
||||||
successfully connect the edge agent to Portainer
|
<code>PORTAINER_EDGE_ID</code> environment variable is required
|
||||||
</TextTip>
|
to successfully connect the edge agent to Portainer
|
||||||
|
</TextTip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{isNomadTokenVisible && <NomadTokenField />}
|
||||||
|
|
||||||
|
<FormControl
|
||||||
|
label="Environment variables"
|
||||||
|
tooltip="Comma separated list of environment variables that will be sourced from the host where the agent is deployed."
|
||||||
|
inputId="env-variables-input"
|
||||||
|
>
|
||||||
|
<Field
|
||||||
|
name="envVars"
|
||||||
|
as={Input}
|
||||||
|
placeholder="foo=bar,myvar"
|
||||||
|
id="env-variables-input"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<div className="col-sm-12">
|
<div className="col-sm-12">
|
||||||
<SwitchField
|
<SwitchField
|
||||||
checked={values.allowSelfSignedCertificates}
|
checked={values.allowSelfSignedCertificates}
|
||||||
label="Allow self-signed certificates"
|
onChange={(value) =>
|
||||||
tooltip="When allowing self-signed certificates the edge agent will ignore the domain validation when connecting to Portainer via HTTPS"
|
setFieldValue('allowSelfSignedCertificates', value)
|
||||||
onChange={(checked) =>
|
|
||||||
setFieldValue('allowSelfSignedCertificates', checked)
|
|
||||||
}
|
}
|
||||||
|
label="Allow self-signed certs"
|
||||||
|
tooltip="When allowing self-signed certificates the edge agent will ignore the domain validation when connecting to Portainer via HTTPS"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
<FormControl
|
|
||||||
label="Environment variables"
|
|
||||||
tooltip="Comma separated list of environment variables that will be sourced from the host where the agent is deployed."
|
|
||||||
inputId="env-vars-input"
|
|
||||||
>
|
|
||||||
<Input
|
|
||||||
type="text"
|
|
||||||
name="envVars"
|
|
||||||
value={values.envVars}
|
|
||||||
id="env-vars-input"
|
|
||||||
onChange={(e) => setFieldValue(e.target.name, e.target.value)}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
</form>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
import { Field, useFormikContext } from 'formik';
|
||||||
|
import { string, boolean } from 'yup';
|
||||||
|
|
||||||
|
import { FormControl } from '@/portainer/components/form-components/FormControl';
|
||||||
|
import { SwitchField } from '@/portainer/components/form-components/SwitchField';
|
||||||
|
import { Input } from '@/portainer/components/form-components/Input';
|
||||||
|
|
||||||
|
import { ScriptFormValues } from './types';
|
||||||
|
|
||||||
|
export function NomadTokenField() {
|
||||||
|
const { values, setFieldValue, errors } =
|
||||||
|
useFormikContext<ScriptFormValues>();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="form-group">
|
||||||
|
<div className="col-sm-12">
|
||||||
|
<SwitchField
|
||||||
|
checked={values.authEnabled}
|
||||||
|
onChange={(value) => {
|
||||||
|
if (!value) {
|
||||||
|
setFieldValue('nomadToken', '');
|
||||||
|
}
|
||||||
|
setFieldValue('authEnabled', value);
|
||||||
|
}}
|
||||||
|
label="Nomad Authentication Enabled"
|
||||||
|
tooltip="Nomad authentication is only required if you have ACL enabled"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{values.authEnabled && (
|
||||||
|
<FormControl
|
||||||
|
label="Nomad Token"
|
||||||
|
inputId="nomad-token-input"
|
||||||
|
errors={errors.nomadToken}
|
||||||
|
>
|
||||||
|
<Field name="nomadToken" as={Input} id="nomad-token-input" />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function validation() {
|
||||||
|
return {
|
||||||
|
nomadToken: string().when('authEnabled', {
|
||||||
|
is: true,
|
||||||
|
then: string().required('Token is required'),
|
||||||
|
}),
|
||||||
|
authEnabled: boolean(),
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
|
import { Code } from '@/portainer/components/Code';
|
||||||
|
import { CopyButton } from '@/portainer/components/Button/CopyButton';
|
||||||
|
import { NavTabs } from '@/portainer/components/NavTabs/NavTabs';
|
||||||
|
import { useAgentDetails } from '@/portainer/environments/queries/useAgentDetails';
|
||||||
|
|
||||||
|
import { ScriptFormValues, Platform } from './types';
|
||||||
|
import { CommandTab } from './scripts';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
values: ScriptFormValues;
|
||||||
|
edgeKey: string;
|
||||||
|
edgeId?: string;
|
||||||
|
commands: CommandTab[];
|
||||||
|
platform?: Platform;
|
||||||
|
onPlatformChange?(platform: Platform): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ScriptTabs({
|
||||||
|
values,
|
||||||
|
edgeKey,
|
||||||
|
edgeId,
|
||||||
|
commands,
|
||||||
|
platform,
|
||||||
|
onPlatformChange = () => {},
|
||||||
|
}: Props) {
|
||||||
|
const agentDetails = useAgentDetails();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (commands.length > 0 && commands.every((p) => p.id !== platform)) {
|
||||||
|
onPlatformChange(commands[0].id);
|
||||||
|
}
|
||||||
|
}, [platform, onPlatformChange, commands]);
|
||||||
|
|
||||||
|
if (!agentDetails) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { agentSecret, agentVersion } = agentDetails;
|
||||||
|
|
||||||
|
const options = commands.map((c) => {
|
||||||
|
const cmd = c.command(agentVersion, edgeKey, values, edgeId, agentSecret);
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: c.id,
|
||||||
|
label: c.label,
|
||||||
|
children: (
|
||||||
|
<>
|
||||||
|
<Code>{cmd}</Code>
|
||||||
|
<CopyButton copyText={cmd}>Copy</CopyButton>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="row">
|
||||||
|
<div className="col-sm-12">
|
||||||
|
<NavTabs
|
||||||
|
selectedId={platform}
|
||||||
|
options={options}
|
||||||
|
onSelect={(id: Platform) => onPlatformChange(id)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
export { EdgeScriptForm } from './EdgeScriptForm';
|
|
@ -1,111 +1,50 @@
|
||||||
import { useEffect } from 'react';
|
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
|
||||||
import { Code } from '@/portainer/components/Code';
|
|
||||||
import { CopyButton } from '@/portainer/components/Button/CopyButton';
|
|
||||||
import { NavTabs } from '@/portainer/components/NavTabs/NavTabs';
|
|
||||||
import { getAgentShortVersion } from '@/portainer/views/endpoints/helpers';
|
import { getAgentShortVersion } from '@/portainer/views/endpoints/helpers';
|
||||||
|
|
||||||
import { EdgeProperties, Platform } from './types';
|
import { ScriptFormValues, Platform } from './types';
|
||||||
|
|
||||||
const commandsByOs = {
|
type CommandGenerator = (
|
||||||
linux: [
|
agentVersion: string,
|
||||||
{
|
edgeKey: string,
|
||||||
id: 'k8s',
|
properties: ScriptFormValues,
|
||||||
label: 'Kubernetes',
|
edgeId?: string,
|
||||||
command: buildKubernetesCommand,
|
agentSecret?: string
|
||||||
},
|
) => string;
|
||||||
{
|
|
||||||
id: 'swarm',
|
export type CommandTab = {
|
||||||
label: 'Docker Swarm',
|
id: Platform;
|
||||||
command: buildLinuxSwarmCommand,
|
label: string;
|
||||||
},
|
command: CommandGenerator;
|
||||||
{
|
|
||||||
id: 'standalone',
|
|
||||||
label: 'Docker Standalone',
|
|
||||||
command: buildLinuxStandaloneCommand,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
win: [
|
|
||||||
{
|
|
||||||
id: 'swarm',
|
|
||||||
label: 'Docker Swarm',
|
|
||||||
command: buildWindowsSwarmCommand,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'standalone',
|
|
||||||
label: 'Docker Standalone',
|
|
||||||
command: buildWindowsStandaloneCommand,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
interface Props {
|
export const commandsTabs: Record<string, CommandTab> = {
|
||||||
values: EdgeProperties;
|
k8sLinux: {
|
||||||
edgeKey: string;
|
id: 'k8s',
|
||||||
agentVersion: string;
|
label: 'Kubernetes',
|
||||||
edgeId?: string;
|
command: buildLinuxKubernetesCommand,
|
||||||
agentSecret?: string;
|
},
|
||||||
onPlatformChange(platform: Platform): void;
|
swarmLinux: {
|
||||||
}
|
id: 'swarm',
|
||||||
|
label: 'Docker Swarm',
|
||||||
export function ScriptTabs({
|
command: buildLinuxSwarmCommand,
|
||||||
agentVersion,
|
},
|
||||||
values,
|
standaloneLinux: {
|
||||||
edgeKey,
|
id: 'standalone',
|
||||||
edgeId,
|
label: 'Docker Standalone',
|
||||||
agentSecret,
|
command: buildLinuxStandaloneCommand,
|
||||||
onPlatformChange,
|
},
|
||||||
}: Props) {
|
swarmWindows: {
|
||||||
const {
|
id: 'swarm',
|
||||||
os,
|
label: 'Docker Swarm',
|
||||||
allowSelfSignedCertificates,
|
command: buildWindowsSwarmCommand,
|
||||||
edgeIdGenerator,
|
},
|
||||||
envVars,
|
standaloneWindow: {
|
||||||
platform,
|
id: 'standalone',
|
||||||
} = values;
|
label: 'Docker Standalone',
|
||||||
|
command: buildWindowsStandaloneCommand,
|
||||||
useEffect(() => {
|
},
|
||||||
if (!commandsByOs[os].find((p) => p.id === platform)) {
|
} as const;
|
||||||
onPlatformChange('swarm');
|
|
||||||
}
|
|
||||||
}, [os, platform, onPlatformChange]);
|
|
||||||
|
|
||||||
const options = commandsByOs[os].map((c) => {
|
|
||||||
const cmd = c.command(
|
|
||||||
agentVersion,
|
|
||||||
edgeIdGenerator,
|
|
||||||
edgeKey,
|
|
||||||
allowSelfSignedCertificates,
|
|
||||||
envVars,
|
|
||||||
edgeId,
|
|
||||||
agentSecret
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
id: c.id,
|
|
||||||
label: c.label,
|
|
||||||
children: (
|
|
||||||
<>
|
|
||||||
<Code>{cmd}</Code>
|
|
||||||
<CopyButton copyText={cmd}>Copy</CopyButton>
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="row">
|
|
||||||
<div className="col-sm-12">
|
|
||||||
<NavTabs
|
|
||||||
selectedId={platform}
|
|
||||||
options={options}
|
|
||||||
onSelect={(id: Platform) => onPlatformChange(id)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildDockerEnvVars(envVars: string, defaultVars: string[]) {
|
function buildDockerEnvVars(envVars: string, defaultVars: string[]) {
|
||||||
const vars = defaultVars.concat(
|
const vars = defaultVars.concat(
|
||||||
|
@ -115,26 +54,28 @@ function buildDockerEnvVars(envVars: string, defaultVars: string[]) {
|
||||||
return vars.map((s) => `-e ${s}`).join(' \\\n ');
|
return vars.map((s) => `-e ${s}`).join(' \\\n ');
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildLinuxStandaloneCommand(
|
export function buildLinuxStandaloneCommand(
|
||||||
agentVersion: string,
|
agentVersion: string,
|
||||||
edgeIdScript: string,
|
|
||||||
edgeKey: string,
|
edgeKey: string,
|
||||||
allowSelfSignedCerts: boolean,
|
properties: ScriptFormValues,
|
||||||
envVars: string,
|
|
||||||
edgeId?: string,
|
edgeId?: string,
|
||||||
agentSecret?: string
|
agentSecret?: string
|
||||||
) {
|
) {
|
||||||
|
const { allowSelfSignedCertificates, edgeIdGenerator, envVars } = properties;
|
||||||
|
|
||||||
const env = buildDockerEnvVars(
|
const env = buildDockerEnvVars(
|
||||||
envVars,
|
envVars,
|
||||||
buildDefaultEnvVars(
|
buildDefaultEnvVars(
|
||||||
edgeKey,
|
edgeKey,
|
||||||
allowSelfSignedCerts,
|
allowSelfSignedCertificates,
|
||||||
!edgeIdScript ? edgeId : undefined,
|
!edgeIdGenerator ? edgeId : undefined,
|
||||||
agentSecret
|
agentSecret
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
return `${edgeIdScript ? `PORTAINER_EDGE_ID=$(${edgeIdScript}) \n\n` : ''}\
|
return `${
|
||||||
|
edgeIdGenerator ? `PORTAINER_EDGE_ID=$(${edgeIdGenerator}) \n\n` : ''
|
||||||
|
}\
|
||||||
docker run -d \\
|
docker run -d \\
|
||||||
-v /var/run/docker.sock:/var/run/docker.sock \\
|
-v /var/run/docker.sock:/var/run/docker.sock \\
|
||||||
-v /var/lib/docker/volumes:/var/lib/docker/volumes \\
|
-v /var/lib/docker/volumes:/var/lib/docker/volumes \\
|
||||||
|
@ -147,27 +88,29 @@ docker run -d \\
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildWindowsStandaloneCommand(
|
export function buildWindowsStandaloneCommand(
|
||||||
agentVersion: string,
|
agentVersion: string,
|
||||||
edgeIdScript: string,
|
|
||||||
edgeKey: string,
|
edgeKey: string,
|
||||||
allowSelfSignedCerts: boolean,
|
properties: ScriptFormValues,
|
||||||
envVars: string,
|
|
||||||
edgeId?: string,
|
edgeId?: string,
|
||||||
agentSecret?: string
|
agentSecret?: string
|
||||||
) {
|
) {
|
||||||
|
const { allowSelfSignedCertificates, edgeIdGenerator, envVars } = properties;
|
||||||
|
|
||||||
const env = buildDockerEnvVars(
|
const env = buildDockerEnvVars(
|
||||||
envVars,
|
envVars,
|
||||||
buildDefaultEnvVars(
|
buildDefaultEnvVars(
|
||||||
edgeKey,
|
edgeKey,
|
||||||
allowSelfSignedCerts,
|
allowSelfSignedCertificates,
|
||||||
edgeIdScript ? '$Env:PORTAINER_EDGE_ID' : edgeId,
|
edgeIdGenerator ? '$Env:PORTAINER_EDGE_ID' : edgeId,
|
||||||
agentSecret
|
agentSecret
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
return `${
|
return `${
|
||||||
edgeIdScript ? `$Env:PORTAINER_EDGE_ID = "@(${edgeIdScript})" \n\n` : ''
|
edgeIdGenerator
|
||||||
|
? `$Env:PORTAINER_EDGE_ID = "@(${edgeIdGenerator})" \n\n`
|
||||||
|
: ''
|
||||||
}\
|
}\
|
||||||
docker run -d \\
|
docker run -d \\
|
||||||
--mount type=npipe,src=\\\\.\\pipe\\docker_engine,dst=\\\\.\\pipe\\docker_engine \\
|
--mount type=npipe,src=\\\\.\\pipe\\docker_engine,dst=\\\\.\\pipe\\docker_engine \\
|
||||||
|
@ -180,30 +123,32 @@ docker run -d \\
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildLinuxSwarmCommand(
|
export function buildLinuxSwarmCommand(
|
||||||
agentVersion: string,
|
agentVersion: string,
|
||||||
edgeIdScript: string,
|
|
||||||
edgeKey: string,
|
edgeKey: string,
|
||||||
allowSelfSignedCerts: boolean,
|
properties: ScriptFormValues,
|
||||||
envVars: string,
|
|
||||||
edgeId?: string,
|
edgeId?: string,
|
||||||
agentSecret?: string
|
agentSecret?: string
|
||||||
) {
|
) {
|
||||||
|
const { allowSelfSignedCertificates, edgeIdGenerator, envVars } = properties;
|
||||||
|
|
||||||
const env = buildDockerEnvVars(envVars, [
|
const env = buildDockerEnvVars(envVars, [
|
||||||
...buildDefaultEnvVars(
|
...buildDefaultEnvVars(
|
||||||
edgeKey,
|
edgeKey,
|
||||||
allowSelfSignedCerts,
|
allowSelfSignedCertificates,
|
||||||
!edgeIdScript ? edgeId : undefined,
|
!edgeIdGenerator ? edgeId : undefined,
|
||||||
agentSecret
|
agentSecret
|
||||||
),
|
),
|
||||||
'AGENT_CLUSTER_ADDR=tasks.portainer_edge_agent',
|
'AGENT_CLUSTER_ADDR=tasks.portainer_edge_agent',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return `${edgeIdScript ? `PORTAINER_EDGE_ID=$(${edgeIdScript}) \n\n` : ''}\
|
return `${
|
||||||
|
edgeIdGenerator ? `PORTAINER_EDGE_ID=$(${edgeIdGenerator}) \n\n` : ''
|
||||||
|
}\
|
||||||
docker network create \\
|
docker network create \\
|
||||||
--driver overlay \\
|
--driver overlay \\
|
||||||
portainer_agent_network;
|
portainer_agent_network;
|
||||||
|
|
||||||
docker service create \\
|
docker service create \\
|
||||||
--name portainer_edge_agent \\
|
--name portainer_edge_agent \\
|
||||||
--network portainer_agent_network \\
|
--network portainer_agent_network \\
|
||||||
|
@ -218,28 +163,30 @@ docker service create \\
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildWindowsSwarmCommand(
|
export function buildWindowsSwarmCommand(
|
||||||
agentVersion: string,
|
agentVersion: string,
|
||||||
edgeIdScript: string,
|
|
||||||
edgeKey: string,
|
edgeKey: string,
|
||||||
allowSelfSignedCerts: boolean,
|
properties: ScriptFormValues,
|
||||||
envVars: string,
|
|
||||||
edgeId?: string,
|
edgeId?: string,
|
||||||
agentSecret?: string
|
agentSecret?: string
|
||||||
) {
|
) {
|
||||||
|
const { allowSelfSignedCertificates, edgeIdGenerator, envVars } = properties;
|
||||||
|
|
||||||
const env = buildDockerEnvVars(envVars, [
|
const env = buildDockerEnvVars(envVars, [
|
||||||
...buildDefaultEnvVars(
|
...buildDefaultEnvVars(
|
||||||
edgeKey,
|
edgeKey,
|
||||||
allowSelfSignedCerts,
|
allowSelfSignedCertificates,
|
||||||
edgeIdScript ? '$Env:PORTAINER_EDGE_ID' : edgeId,
|
edgeIdGenerator ? '$Env:PORTAINER_EDGE_ID' : edgeId,
|
||||||
agentSecret
|
agentSecret
|
||||||
),
|
),
|
||||||
'AGENT_CLUSTER_ADDR=tasks.portainer_edge_agent',
|
'AGENT_CLUSTER_ADDR=tasks.portainer_edge_agent',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return `${
|
return `${
|
||||||
edgeIdScript ? `$Env:PORTAINER_EDGE_ID = "@(${edgeIdScript})" \n\n` : ''
|
edgeIdGenerator
|
||||||
}
|
? `$Env:PORTAINER_EDGE_ID = "@(${edgeIdGenerator})" \n\n`
|
||||||
|
: ''
|
||||||
|
}\
|
||||||
docker network create \\
|
docker network create \\
|
||||||
--driver overlay \\
|
--driver overlay \\
|
||||||
portainer_agent_network;
|
portainer_agent_network;
|
||||||
|
@ -257,24 +204,24 @@ docker service create \\
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildKubernetesCommand(
|
export function buildLinuxKubernetesCommand(
|
||||||
agentVersion: string,
|
agentVersion: string,
|
||||||
edgeIdScript: string,
|
|
||||||
edgeKey: string,
|
edgeKey: string,
|
||||||
allowSelfSignedCerts: boolean,
|
properties: ScriptFormValues,
|
||||||
envVars: string,
|
|
||||||
edgeId?: string,
|
edgeId?: string,
|
||||||
agentSecret = ''
|
agentSecret?: string
|
||||||
) {
|
) {
|
||||||
const agentShortVersion = getAgentShortVersion(agentVersion);
|
const { allowSelfSignedCertificates, edgeIdGenerator, envVars } = properties;
|
||||||
const idEnvVar = edgeIdScript
|
|
||||||
? `PORTAINER_EDGE_ID=$(${edgeIdScript}) \n\n`
|
|
||||||
: '';
|
|
||||||
const envVarsTrimmed = envVars.trim();
|
|
||||||
const edgeIdVar = !edgeIdScript && edgeId ? edgeId : '$PORTAINER_EDGE_ID';
|
|
||||||
const selfSigned = allowSelfSignedCerts ? '1' : '0';
|
|
||||||
|
|
||||||
return `${idEnvVar}curl https://downloads.portainer.io/ce${agentShortVersion}/portainer-edge-agent-setup.sh | bash -s -- "${edgeIdVar}" "${edgeKey}" "${selfSigned}" "${agentSecret}" "${envVarsTrimmed}"`;
|
const agentShortVersion = getAgentShortVersion(agentVersion);
|
||||||
|
const envVarsTrimmed = envVars.trim();
|
||||||
|
const idEnvVar = edgeIdGenerator
|
||||||
|
? `PORTAINER_EDGE_ID=$(${edgeIdGenerator}) \n\n`
|
||||||
|
: '';
|
||||||
|
const edgeIdVar = !edgeIdGenerator && edgeId ? edgeId : '$PORTAINER_EDGE_ID';
|
||||||
|
const selfSigned = allowSelfSignedCertificates ? '1' : '0';
|
||||||
|
|
||||||
|
return `${idEnvVar}curl https://downloads.portainer.io/ee${agentShortVersion}/portainer-edge-agent-setup.sh | bash -s -- "${edgeIdVar}" "${edgeKey}" "${selfSigned}" "${agentSecret}" "${envVarsTrimmed}"`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildDefaultEnvVars(
|
function buildDefaultEnvVars(
|
|
@ -0,0 +1,20 @@
|
||||||
|
export type Platform = 'standalone' | 'swarm' | 'k8s' | 'nomad';
|
||||||
|
export type OS = 'win' | 'linux';
|
||||||
|
|
||||||
|
export interface ScriptFormValues {
|
||||||
|
nomadToken: string;
|
||||||
|
authEnabled: boolean;
|
||||||
|
|
||||||
|
allowSelfSignedCertificates: boolean;
|
||||||
|
envVars: string;
|
||||||
|
|
||||||
|
os: OS;
|
||||||
|
platform: Platform;
|
||||||
|
|
||||||
|
edgeIdGenerator?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EdgeInfo {
|
||||||
|
id?: string;
|
||||||
|
key: string;
|
||||||
|
}
|
|
@ -193,6 +193,7 @@ function useAnalyticsState() {
|
||||||
aciApi: 0,
|
aciApi: 0,
|
||||||
localEndpoint: 0,
|
localEndpoint: 0,
|
||||||
nomadEdgeAgent: 0,
|
nomadEdgeAgent: 0,
|
||||||
|
dockerEdgeAgent: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
return { analytics, setAnalytics };
|
return { analytics, setAnalytics };
|
||||||
|
|
|
@ -13,8 +13,8 @@ import { EnvironmentMetadata } from '@/portainer/environments/environment.servic
|
||||||
|
|
||||||
import { NameField, nameValidation } from '../shared/NameField';
|
import { NameField, nameValidation } from '../shared/NameField';
|
||||||
import { AnalyticsStateKey } from '../types';
|
import { AnalyticsStateKey } from '../types';
|
||||||
import { MetadataFieldset } from '../shared/MetadataFieldset';
|
|
||||||
import { metadataValidation } from '../shared/MetadataFieldset/validation';
|
import { metadataValidation } from '../shared/MetadataFieldset/validation';
|
||||||
|
import { MoreSettingsSection } from '../shared/MoreSettingsSection';
|
||||||
|
|
||||||
interface FormValues {
|
interface FormValues {
|
||||||
name: string;
|
name: string;
|
||||||
|
@ -109,7 +109,7 @@ export function WizardAzure({ onCreate }: Props) {
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<MetadataFieldset />
|
<MoreSettingsSection />
|
||||||
|
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col-sm-12">
|
<div className="col-sm-12">
|
||||||
|
|
|
@ -12,7 +12,7 @@ import {
|
||||||
} from '@/portainer/environments/types';
|
} from '@/portainer/environments/types';
|
||||||
|
|
||||||
import { NameField } from '../../shared/NameField';
|
import { NameField } from '../../shared/NameField';
|
||||||
import { MetadataFieldset } from '../../shared/MetadataFieldset';
|
import { MoreSettingsSection } from '../../shared/MoreSettingsSection';
|
||||||
|
|
||||||
import { validation } from './APIForm.validation';
|
import { validation } from './APIForm.validation';
|
||||||
import { FormValues } from './types';
|
import { FormValues } from './types';
|
||||||
|
@ -66,7 +66,7 @@ export function APIForm({ onCreate }: Props) {
|
||||||
|
|
||||||
<TLSFieldset />
|
<TLSFieldset />
|
||||||
|
|
||||||
<MetadataFieldset />
|
<MoreSettingsSection />
|
||||||
|
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<div className="col-sm-12">
|
<div className="col-sm-12">
|
||||||
|
|
|
@ -12,7 +12,7 @@ export function APITab({ onCreate }: Props) {
|
||||||
<>
|
<>
|
||||||
<DeploymentScripts />
|
<DeploymentScripts />
|
||||||
|
|
||||||
<div className="wizard-form">
|
<div className="mt-5">
|
||||||
<APIForm onCreate={onCreate} />
|
<APIForm onCreate={onCreate} />
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -55,9 +55,7 @@ function DeployCode({ code }: DeployCodeProps) {
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<Code>{code}</Code>
|
<Code>{code}</Code>
|
||||||
<CopyButton copyText={code} className="my-6">
|
<CopyButton copyText={code}>Copy command</CopyButton>
|
||||||
Copy command
|
|
||||||
</CopyButton>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ export function AgentTab({ onCreate }: Props) {
|
||||||
<>
|
<>
|
||||||
<DeploymentScripts />
|
<DeploymentScripts />
|
||||||
|
|
||||||
<div className="wizard-form">
|
<div className="mt-5">
|
||||||
<AgentForm onCreate={onCreate} />
|
<AgentForm onCreate={onCreate} />
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -10,7 +10,7 @@ import { SwitchField } from '@/portainer/components/form-components/SwitchField'
|
||||||
import { Environment } from '@/portainer/environments/types';
|
import { Environment } from '@/portainer/environments/types';
|
||||||
|
|
||||||
import { NameField } from '../../shared/NameField';
|
import { NameField } from '../../shared/NameField';
|
||||||
import { MetadataFieldset } from '../../shared/MetadataFieldset';
|
import { MoreSettingsSection } from '../../shared/MoreSettingsSection';
|
||||||
|
|
||||||
import { validation } from './SocketForm.validation';
|
import { validation } from './SocketForm.validation';
|
||||||
import { FormValues } from './types';
|
import { FormValues } from './types';
|
||||||
|
@ -44,8 +44,7 @@ export function SocketForm({ onCreate }: Props) {
|
||||||
|
|
||||||
<OverrideSocketFieldset />
|
<OverrideSocketFieldset />
|
||||||
|
|
||||||
<MetadataFieldset />
|
<MoreSettingsSection />
|
||||||
|
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<div className="col-sm-12">
|
<div className="col-sm-12">
|
||||||
<LoadingButton
|
<LoadingButton
|
||||||
|
|
|
@ -13,7 +13,7 @@ export function SocketTab({ onCreate }: Props) {
|
||||||
<>
|
<>
|
||||||
<DeploymentScripts />
|
<DeploymentScripts />
|
||||||
|
|
||||||
<div className="wizard-form">
|
<div className="mt-5">
|
||||||
<SocketForm onCreate={onCreate} />
|
<SocketForm onCreate={onCreate} />
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -1,9 +1,14 @@
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
import { BoxSelector, buildOption } from '@/portainer/components/BoxSelector';
|
import {
|
||||||
|
BoxSelector,
|
||||||
|
BoxSelectorOption,
|
||||||
|
} from '@/portainer/components/BoxSelector';
|
||||||
import { Environment } from '@/portainer/environments/types';
|
import { Environment } from '@/portainer/environments/types';
|
||||||
|
import { commandsTabs } from '@/react/edge/components/EdgeScriptForm/scripts';
|
||||||
|
|
||||||
import { AnalyticsStateKey } from '../types';
|
import { AnalyticsStateKey } from '../types';
|
||||||
|
import { EdgeAgentTab } from '../shared/EdgeAgentTab';
|
||||||
|
|
||||||
import { AgentTab } from './AgentTab';
|
import { AgentTab } from './AgentTab';
|
||||||
import { APITab } from './APITab';
|
import { APITab } from './APITab';
|
||||||
|
@ -13,16 +18,41 @@ interface Props {
|
||||||
onCreate(environment: Environment, analytics: AnalyticsStateKey): void;
|
onCreate(environment: Environment, analytics: AnalyticsStateKey): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const options = [
|
const options: BoxSelectorOption<'agent' | 'api' | 'socket' | 'edgeAgent'>[] = [
|
||||||
buildOption('Agent', 'fa fa-bolt', 'Agent', '', 'agent'),
|
{
|
||||||
buildOption('API', 'fa fa-cloud', 'API', '', 'api'),
|
id: 'agent',
|
||||||
buildOption('Socket', 'fab fa-docker', 'Socket', '', 'socket'),
|
icon: 'fa fa-bolt',
|
||||||
|
label: 'Agent',
|
||||||
|
description: '',
|
||||||
|
value: 'agent',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'api',
|
||||||
|
icon: 'fa fa-cloud',
|
||||||
|
label: 'API',
|
||||||
|
description: '',
|
||||||
|
value: 'api',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'socket',
|
||||||
|
icon: 'fab fa-docker',
|
||||||
|
label: 'Socket',
|
||||||
|
description: '',
|
||||||
|
value: 'socket',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'edgeAgent',
|
||||||
|
icon: 'fa fa-cloud', // Todo cloud with docker
|
||||||
|
label: 'Edge Agent',
|
||||||
|
description: '',
|
||||||
|
value: 'edgeAgent',
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export function WizardDocker({ onCreate }: Props) {
|
export function WizardDocker({ onCreate }: Props) {
|
||||||
const [creationType, setCreationType] = useState(options[0].value);
|
const [creationType, setCreationType] = useState(options[0].value);
|
||||||
|
|
||||||
const form = getForm(creationType);
|
const tab = getTab(creationType);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="form-horizontal">
|
<div className="form-horizontal">
|
||||||
|
@ -37,11 +67,11 @@ export function WizardDocker({ onCreate }: Props) {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{form}
|
{tab}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
function getForm(creationType: 'agent' | 'api' | 'socket') {
|
function getTab(creationType: 'agent' | 'api' | 'socket' | 'edgeAgent') {
|
||||||
switch (creationType) {
|
switch (creationType) {
|
||||||
case 'agent':
|
case 'agent':
|
||||||
return (
|
return (
|
||||||
|
@ -61,6 +91,16 @@ export function WizardDocker({ onCreate }: Props) {
|
||||||
onCreate={(environment) => onCreate(environment, 'localEndpoint')}
|
onCreate={(environment) => onCreate(environment, 'localEndpoint')}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
case 'edgeAgent':
|
||||||
|
return (
|
||||||
|
<EdgeAgentTab
|
||||||
|
onCreate={(environment) => onCreate(environment, 'dockerEdgeAgent')}
|
||||||
|
commands={{
|
||||||
|
linux: [commandsTabs.swarmLinux, commandsTabs.standaloneLinux],
|
||||||
|
win: [commandsTabs.swarmWindows, commandsTabs.standaloneWindow],
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
import { Environment } from '@/portainer/environments/types';
|
import { Environment } from '@/portainer/environments/types';
|
||||||
|
|
||||||
import { AgentForm } from '../shared/AgentForm/AgentForm';
|
import { AgentForm } from '../shared/AgentForm';
|
||||||
import { AnalyticsStateKey } from '../types';
|
|
||||||
|
|
||||||
import { DeploymentScripts } from './DeploymentScripts';
|
import { DeploymentScripts } from './DeploymentScripts';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
onCreate(environment: Environment, analytics: AnalyticsStateKey): void;
|
onCreate(environment: Environment): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function AgentPanel({ onCreate }: Props) {
|
export function AgentPanel({ onCreate }: Props) {
|
||||||
|
@ -14,9 +13,9 @@ export function AgentPanel({ onCreate }: Props) {
|
||||||
<>
|
<>
|
||||||
<DeploymentScripts />
|
<DeploymentScripts />
|
||||||
|
|
||||||
<AgentForm
|
<div className="mt-5">
|
||||||
onCreate={(environment) => onCreate(environment, 'kubernetesAgent')}
|
<AgentForm onCreate={onCreate} />
|
||||||
/>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -106,9 +106,7 @@ function DeployCode({
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
<Code>{code}</Code>
|
<Code>{code}</Code>
|
||||||
<CopyButton copyText={code} className="my-6">
|
<CopyButton copyText={code}>Copy command</CopyButton>
|
||||||
Copy command
|
|
||||||
</CopyButton>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,8 +6,10 @@ import {
|
||||||
EnvironmentCreationTypes,
|
EnvironmentCreationTypes,
|
||||||
} from '@/portainer/environments/types';
|
} from '@/portainer/environments/types';
|
||||||
import { BoxSelectorOption } from '@/portainer/components/BoxSelector/types';
|
import { BoxSelectorOption } from '@/portainer/components/BoxSelector/types';
|
||||||
|
import { commandsTabs } from '@/react/edge/components/EdgeScriptForm/scripts';
|
||||||
|
|
||||||
import { AnalyticsStateKey } from '../types';
|
import { AnalyticsStateKey } from '../types';
|
||||||
|
import { EdgeAgentTab } from '../shared/EdgeAgentTab';
|
||||||
|
|
||||||
import { AgentPanel } from './AgentPanel';
|
import { AgentPanel } from './AgentPanel';
|
||||||
|
|
||||||
|
@ -15,21 +17,30 @@ interface Props {
|
||||||
onCreate(environment: Environment, analytics: AnalyticsStateKey): void;
|
onCreate(environment: Environment, analytics: AnalyticsStateKey): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const options: BoxSelectorOption<EnvironmentCreationTypes.AgentEnvironment>[] =
|
const options: BoxSelectorOption<
|
||||||
[
|
| EnvironmentCreationTypes.AgentEnvironment
|
||||||
{
|
| EnvironmentCreationTypes.EdgeAgentEnvironment
|
||||||
id: 'agent_endpoint',
|
>[] = [
|
||||||
icon: 'fa fa-bolt',
|
{
|
||||||
label: 'Agent',
|
id: 'agent_endpoint',
|
||||||
value: EnvironmentCreationTypes.AgentEnvironment,
|
icon: 'fa fa-bolt',
|
||||||
description: '',
|
label: 'Agent',
|
||||||
},
|
value: EnvironmentCreationTypes.AgentEnvironment,
|
||||||
];
|
description: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'edgeAgent',
|
||||||
|
icon: 'fa fa-cloud', // Todo cloud with docker
|
||||||
|
label: 'Edge Agent',
|
||||||
|
description: '',
|
||||||
|
value: EnvironmentCreationTypes.EdgeAgentEnvironment,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
export function WizardKubernetes({ onCreate }: Props) {
|
export function WizardKubernetes({ onCreate }: Props) {
|
||||||
const [creationType, setCreationType] = useState(options[0].value);
|
const [creationType, setCreationType] = useState(options[0].value);
|
||||||
|
|
||||||
const Component = getPanel(creationType);
|
const tab = getTab(creationType);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="form-horizontal">
|
<div className="form-horizontal">
|
||||||
|
@ -40,16 +51,29 @@ export function WizardKubernetes({ onCreate }: Props) {
|
||||||
radioName="creation-type"
|
radioName="creation-type"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Component onCreate={onCreate} />
|
{tab}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
function getPanel(type: typeof options[number]['value']) {
|
function getTab(type: typeof options[number]['value']) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case EnvironmentCreationTypes.AgentEnvironment:
|
case EnvironmentCreationTypes.AgentEnvironment:
|
||||||
return AgentPanel;
|
return (
|
||||||
default:
|
<AgentPanel
|
||||||
throw new Error('Creation type not supported');
|
onCreate={(environment) => onCreate(environment, 'kubernetesAgent')}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
case EnvironmentCreationTypes.EdgeAgentEnvironment:
|
||||||
|
return (
|
||||||
|
<EdgeAgentTab
|
||||||
|
onCreate={(environment) =>
|
||||||
|
onCreate(environment, 'kubernetesEdgeAgent')
|
||||||
|
}
|
||||||
|
commands={[{ ...commandsTabs.k8sLinux, label: 'Linux' }]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
throw new Error('Creation type not supported');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ import { Environment } from '@/portainer/environments/types';
|
||||||
import { CreateAgentEnvironmentValues } from '@/portainer/environments/environment.service/create';
|
import { CreateAgentEnvironmentValues } from '@/portainer/environments/environment.service/create';
|
||||||
|
|
||||||
import { NameField } from '../NameField';
|
import { NameField } from '../NameField';
|
||||||
import { MetadataFieldset } from '../MetadataFieldset';
|
import { MoreSettingsSection } from '../MoreSettingsSection';
|
||||||
|
|
||||||
import { EnvironmentUrlField } from './EnvironmentUrlField';
|
import { EnvironmentUrlField } from './EnvironmentUrlField';
|
||||||
import { validation } from './AgentForm.validation';
|
import { validation } from './AgentForm.validation';
|
||||||
|
@ -44,7 +44,7 @@ export function AgentForm({ onCreate }: Props) {
|
||||||
<NameField />
|
<NameField />
|
||||||
<EnvironmentUrlField />
|
<EnvironmentUrlField />
|
||||||
|
|
||||||
<MetadataFieldset />
|
<MoreSettingsSection />
|
||||||
|
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<div className="col-sm-12">
|
<div className="col-sm-12">
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { NameField } from '../../NameField';
|
||||||
|
|
||||||
|
import { PortainerUrlField } from './PortainerUrlField';
|
||||||
|
|
||||||
|
interface EdgeAgentFormProps {
|
||||||
|
readonly?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function EdgeAgentFieldset({ readonly }: EdgeAgentFormProps) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<NameField readonly={readonly} />
|
||||||
|
<PortainerUrlField fieldName="portainerUrl" readonly={readonly} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,90 @@
|
||||||
|
import { Formik, Form } from 'formik';
|
||||||
|
|
||||||
|
import { LoadingButton } from '@/portainer/components/Button/LoadingButton';
|
||||||
|
import { Environment } from '@/portainer/environments/types';
|
||||||
|
import { useCreateEdgeAgentEnvironmentMutation } from '@/portainer/environments/queries/useCreateEnvironmentMutation';
|
||||||
|
import { baseHref } from '@/portainer/helpers/pathHelper';
|
||||||
|
import { FormSection } from '@/portainer/components/form-components/FormSection';
|
||||||
|
import { EdgeCheckinIntervalField } from '@/edge/components/EdgeCheckInIntervalField';
|
||||||
|
|
||||||
|
import { MoreSettingsSection } from '../../MoreSettingsSection';
|
||||||
|
|
||||||
|
import { EdgeAgentFieldset } from './EdgeAgentFieldset';
|
||||||
|
import { validationSchema } from './EdgeAgentForm.validation';
|
||||||
|
import { FormValues } from './types';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
onCreate(environment: Environment): void;
|
||||||
|
readonly: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialValues = buildInitialValues();
|
||||||
|
|
||||||
|
export function EdgeAgentForm({ onCreate, readonly }: Props) {
|
||||||
|
const createMutation = useCreateEdgeAgentEnvironmentMutation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Formik<FormValues>
|
||||||
|
initialValues={initialValues}
|
||||||
|
onSubmit={handleSubmit}
|
||||||
|
validateOnMount
|
||||||
|
validationSchema={validationSchema}
|
||||||
|
>
|
||||||
|
{({ isValid, setFieldValue, values }) => (
|
||||||
|
<Form>
|
||||||
|
<EdgeAgentFieldset readonly={readonly} />
|
||||||
|
|
||||||
|
<MoreSettingsSection>
|
||||||
|
<FormSection title="Check-in Intervals">
|
||||||
|
<EdgeCheckinIntervalField
|
||||||
|
readonly={readonly}
|
||||||
|
onChange={(value) => setFieldValue('pollFrequency', value)}
|
||||||
|
value={values.pollFrequency}
|
||||||
|
/>
|
||||||
|
</FormSection>
|
||||||
|
</MoreSettingsSection>
|
||||||
|
|
||||||
|
{!readonly && (
|
||||||
|
<div className="row">
|
||||||
|
<div className="col-sm-12">
|
||||||
|
<LoadingButton
|
||||||
|
isLoading={createMutation.isLoading}
|
||||||
|
loadingText="Creating environment..."
|
||||||
|
disabled={!isValid}
|
||||||
|
>
|
||||||
|
<i className="fa fa-plug space-right" />
|
||||||
|
Create
|
||||||
|
</LoadingButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Form>
|
||||||
|
)}
|
||||||
|
</Formik>
|
||||||
|
);
|
||||||
|
|
||||||
|
function handleSubmit(values: typeof initialValues) {
|
||||||
|
createMutation.mutate(values, {
|
||||||
|
onSuccess(environment) {
|
||||||
|
onCreate(environment);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildInitialValues(): FormValues {
|
||||||
|
return {
|
||||||
|
name: '',
|
||||||
|
portainerUrl: defaultPortainerUrl(),
|
||||||
|
pollFrequency: 0,
|
||||||
|
meta: {
|
||||||
|
groupId: 1,
|
||||||
|
tagIds: [],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
function defaultPortainerUrl() {
|
||||||
|
const baseHREF = baseHref();
|
||||||
|
return window.location.origin + (baseHREF !== '/' ? baseHREF : '');
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { number, object, SchemaOf } from 'yup';
|
||||||
|
|
||||||
|
import { metadataValidation } from '../../MetadataFieldset/validation';
|
||||||
|
import { nameValidation } from '../../NameField';
|
||||||
|
|
||||||
|
import { validation as urlValidation } from './PortainerUrlField';
|
||||||
|
import { FormValues } from './types';
|
||||||
|
|
||||||
|
export function validationSchema(): SchemaOf<FormValues> {
|
||||||
|
return object().shape({
|
||||||
|
name: nameValidation(),
|
||||||
|
portainerUrl: urlValidation(),
|
||||||
|
pollFrequency: number().required(),
|
||||||
|
meta: metadataValidation(),
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
import { Field, useField } from 'formik';
|
||||||
|
import { string } from 'yup';
|
||||||
|
|
||||||
|
import { FormControl } from '@/portainer/components/form-components/FormControl';
|
||||||
|
import { Input } from '@/portainer/components/form-components/Input';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
fieldName: string;
|
||||||
|
readonly?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function validation() {
|
||||||
|
return string()
|
||||||
|
.test(
|
||||||
|
'url',
|
||||||
|
'URL should be a valid URI and cannot include localhost',
|
||||||
|
(value) => {
|
||||||
|
if (!value) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const url = new URL(value);
|
||||||
|
return url.hostname !== 'localhost';
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.required('URL is required');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function PortainerUrlField({ fieldName, readonly }: Props) {
|
||||||
|
const [, metaProps] = useField(fieldName);
|
||||||
|
const id = `${fieldName}-input`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormControl
|
||||||
|
label="Portainer server URL"
|
||||||
|
tooltip="URL of the Portainer instance that the agent will use to initiate the communications."
|
||||||
|
required
|
||||||
|
errors={metaProps.error}
|
||||||
|
inputId={id}
|
||||||
|
>
|
||||||
|
<Field
|
||||||
|
id={id}
|
||||||
|
name={fieldName}
|
||||||
|
as={Input}
|
||||||
|
placeholder="e.g. 10.0.0.10:9443 or portainer.mydomain.com"
|
||||||
|
required
|
||||||
|
data-cy="endpointCreate-portainerServerUrlInput"
|
||||||
|
readOnly={readonly}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
export { EdgeAgentForm } from './EdgeAgentForm';
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { EnvironmentMetadata } from '@/portainer/environments/environment.service/create';
|
||||||
|
|
||||||
|
export interface FormValues {
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
portainerUrl: string;
|
||||||
|
pollFrequency: number;
|
||||||
|
meta: EnvironmentMetadata;
|
||||||
|
}
|
|
@ -0,0 +1,66 @@
|
||||||
|
import { v4 as uuid } from 'uuid';
|
||||||
|
import { useReducer, useState } from 'react';
|
||||||
|
|
||||||
|
import { Button } from '@/portainer/components/Button';
|
||||||
|
import { Environment } from '@/portainer/environments/types';
|
||||||
|
import { EdgeScriptForm } from '@/react/edge/components/EdgeScriptForm';
|
||||||
|
import { CommandTab } from '@/react/edge/components/EdgeScriptForm/scripts';
|
||||||
|
import { OS, EdgeInfo } from '@/react/edge/components/EdgeScriptForm/types';
|
||||||
|
|
||||||
|
import { EdgeAgentForm } from './EdgeAgentForm';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
onCreate: (environment: Environment) => void;
|
||||||
|
commands: CommandTab[] | Partial<Record<OS, CommandTab[]>>;
|
||||||
|
isNomadTokenVisible?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function EdgeAgentTab({
|
||||||
|
onCreate,
|
||||||
|
commands,
|
||||||
|
isNomadTokenVisible,
|
||||||
|
}: Props) {
|
||||||
|
const [edgeInfo, setEdgeInfo] = useState<EdgeInfo>();
|
||||||
|
|
||||||
|
const [formKey, clearForm] = useReducer((state) => state + 1, 0);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<EdgeAgentForm
|
||||||
|
onCreate={handleCreate}
|
||||||
|
readonly={!!edgeInfo}
|
||||||
|
key={formKey}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{edgeInfo && (
|
||||||
|
<>
|
||||||
|
<EdgeScriptForm
|
||||||
|
edgeInfo={edgeInfo}
|
||||||
|
commands={commands}
|
||||||
|
isNomadTokenVisible={isNomadTokenVisible}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
<div className="row">
|
||||||
|
<div className="flex justify-end">
|
||||||
|
<Button color="primary" type="reset" onClick={handleReset}>
|
||||||
|
Add another environment
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
function handleCreate(environment: Environment) {
|
||||||
|
setEdgeInfo({ key: environment.EdgeKey, id: uuid() });
|
||||||
|
onCreate(environment);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleReset() {
|
||||||
|
setEdgeInfo(undefined);
|
||||||
|
clearForm();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
export { EdgeAgentTab } from './EdgeAgentTab';
|
|
@ -12,7 +12,7 @@ export function MetadataFieldset() {
|
||||||
const { isAdmin } = useUser();
|
const { isAdmin } = useUser();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormSection title="Metadata" isFoldable>
|
<FormSection title="Metadata">
|
||||||
<GroupField />
|
<GroupField />
|
||||||
|
|
||||||
<TagSelector
|
<TagSelector
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { PropsWithChildren } from 'react';
|
||||||
|
|
||||||
|
import { FormSection } from '@/portainer/components/form-components/FormSection';
|
||||||
|
|
||||||
|
import { MetadataFieldset } from './MetadataFieldset';
|
||||||
|
|
||||||
|
export function MoreSettingsSection({ children }: PropsWithChildren<unknown>) {
|
||||||
|
return (
|
||||||
|
<FormSection title="More settings" isFoldable>
|
||||||
|
<div className="ml-8">
|
||||||
|
{children}
|
||||||
|
|
||||||
|
<MetadataFieldset />
|
||||||
|
</div>
|
||||||
|
</FormSection>
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
export interface AnalyticsState {
|
export interface AnalyticsState {
|
||||||
dockerAgent: number;
|
dockerAgent: number;
|
||||||
dockerApi: number;
|
dockerApi: number;
|
||||||
|
dockerEdgeAgent: number;
|
||||||
kubernetesAgent: number;
|
kubernetesAgent: number;
|
||||||
kubernetesEdgeAgent: number;
|
kubernetesEdgeAgent: number;
|
||||||
kaasAgent: number;
|
kaasAgent: number;
|
||||||
|
|
|
@ -172,6 +172,7 @@
|
||||||
"@types/react-table": "^7.7.6",
|
"@types/react-table": "^7.7.6",
|
||||||
"@types/sanitize-html": "^2.5.0",
|
"@types/sanitize-html": "^2.5.0",
|
||||||
"@types/toastr": "^2.1.39",
|
"@types/toastr": "^2.1.39",
|
||||||
|
"@types/uuid": "^8.3.4",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.7.0",
|
"@typescript-eslint/eslint-plugin": "^5.7.0",
|
||||||
"@typescript-eslint/parser": "^5.7.0",
|
"@typescript-eslint/parser": "^5.7.0",
|
||||||
"auto-ngtemplate-loader": "^2.0.1",
|
"auto-ngtemplate-loader": "^2.0.1",
|
||||||
|
|
|
@ -3565,6 +3565,11 @@
|
||||||
resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.6.tgz#250a7b16c3b91f672a24552ec64678eeb1d3a08d"
|
resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.6.tgz#250a7b16c3b91f672a24552ec64678eeb1d3a08d"
|
||||||
integrity sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==
|
integrity sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==
|
||||||
|
|
||||||
|
"@types/uuid@^8.3.4":
|
||||||
|
version "8.3.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc"
|
||||||
|
integrity sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==
|
||||||
|
|
||||||
"@types/webpack-env@^1.16.0":
|
"@types/webpack-env@^1.16.0":
|
||||||
version "1.16.3"
|
version "1.16.3"
|
||||||
resolved "https://registry.yarnpkg.com/@types/webpack-env/-/webpack-env-1.16.3.tgz#b776327a73e561b71e7881d0cd6d34a1424db86a"
|
resolved "https://registry.yarnpkg.com/@types/webpack-env/-/webpack-env-1.16.3.tgz#b776327a73e561b71e7881d0cd6d34a1424db86a"
|
||||||
|
|
Loading…
Reference in New Issue