mirror of
https://github.com/portainer/portainer.git
synced 2025-08-05 05:45:22 +02:00
refactor(edge): move edge codebase to react (#7781)
This commit is contained in:
parent
75f40fe485
commit
1e4c4e2616
54 changed files with 254 additions and 187 deletions
153
app/react/edge/components/EdgeAsyncIntervalsForm.tsx
Normal file
153
app/react/edge/components/EdgeAsyncIntervalsForm.tsx
Normal file
|
@ -0,0 +1,153 @@
|
|||
import { number, object, SchemaOf } from 'yup';
|
||||
|
||||
import { FormControl } from '@@/form-components/FormControl';
|
||||
import { Select } from '@@/form-components/Input';
|
||||
|
||||
import { Options, useIntervalOptions } from './useIntervalOptions';
|
||||
|
||||
export const EDGE_ASYNC_INTERVAL_USE_DEFAULT = -1;
|
||||
|
||||
export interface EdgeAsyncIntervalsValues {
|
||||
PingInterval: number;
|
||||
SnapshotInterval: number;
|
||||
CommandInterval: number;
|
||||
}
|
||||
|
||||
export const options: Options = [
|
||||
{ label: 'Use default interval', value: -1, isDefault: true },
|
||||
{
|
||||
value: 0,
|
||||
label: 'disabled',
|
||||
},
|
||||
{
|
||||
value: 60,
|
||||
label: '1 minute',
|
||||
},
|
||||
{
|
||||
value: 60 * 60,
|
||||
label: '1 hour',
|
||||
},
|
||||
{
|
||||
value: 24 * 60 * 60,
|
||||
label: '1 day',
|
||||
},
|
||||
{
|
||||
value: 7 * 24 * 60 * 60,
|
||||
label: '1 week',
|
||||
},
|
||||
];
|
||||
|
||||
const defaultFieldSettings = {
|
||||
ping: {
|
||||
label: 'Ping interval',
|
||||
tooltip:
|
||||
'Interval used by this Edge agent to check in with the Portainer instance',
|
||||
},
|
||||
snapshot: {
|
||||
label: 'Snapshot interval',
|
||||
tooltip: 'Interval used by this Edge agent to snapshot the agent state',
|
||||
},
|
||||
command: {
|
||||
label: 'Command interval',
|
||||
tooltip:
|
||||
'Interval used by this Edge agent to fetch commands from the Portainer instance',
|
||||
},
|
||||
};
|
||||
|
||||
interface Props {
|
||||
values: EdgeAsyncIntervalsValues;
|
||||
isDefaultHidden?: boolean;
|
||||
readonly?: boolean;
|
||||
fieldSettings?: typeof defaultFieldSettings;
|
||||
onChange(value: EdgeAsyncIntervalsValues): void;
|
||||
}
|
||||
|
||||
export function EdgeAsyncIntervalsForm({
|
||||
onChange,
|
||||
values,
|
||||
isDefaultHidden = false,
|
||||
readonly = false,
|
||||
fieldSettings = defaultFieldSettings,
|
||||
}: Props) {
|
||||
const pingIntervalOptions = useIntervalOptions(
|
||||
'Edge.PingInterval',
|
||||
options,
|
||||
isDefaultHidden
|
||||
);
|
||||
|
||||
const snapshotIntervalOptions = useIntervalOptions(
|
||||
'Edge.SnapshotInterval',
|
||||
options,
|
||||
isDefaultHidden
|
||||
);
|
||||
|
||||
const commandIntervalOptions = useIntervalOptions(
|
||||
'Edge.CommandInterval',
|
||||
options,
|
||||
isDefaultHidden
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<FormControl
|
||||
inputId="edge_checkin_ping"
|
||||
label={fieldSettings.ping.label}
|
||||
tooltip={fieldSettings.ping.tooltip}
|
||||
>
|
||||
<Select
|
||||
value={values.PingInterval}
|
||||
name="PingInterval"
|
||||
onChange={handleChange}
|
||||
options={pingIntervalOptions}
|
||||
disabled={readonly}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormControl
|
||||
inputId="edge_checkin_snapshot"
|
||||
label={fieldSettings.snapshot.label}
|
||||
tooltip={fieldSettings.snapshot.tooltip}
|
||||
>
|
||||
<Select
|
||||
value={values.SnapshotInterval}
|
||||
name="SnapshotInterval"
|
||||
onChange={handleChange}
|
||||
options={snapshotIntervalOptions}
|
||||
disabled={readonly}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormControl
|
||||
inputId="edge_checkin_command"
|
||||
label={fieldSettings.command.label}
|
||||
tooltip={fieldSettings.command.tooltip}
|
||||
>
|
||||
<Select
|
||||
value={values.CommandInterval}
|
||||
name="CommandInterval"
|
||||
onChange={handleChange}
|
||||
options={commandIntervalOptions}
|
||||
disabled={readonly}
|
||||
/>
|
||||
</FormControl>
|
||||
</>
|
||||
);
|
||||
|
||||
function handleChange(e: React.ChangeEvent<HTMLSelectElement>) {
|
||||
onChange({ ...values, [e.target.name]: parseInt(e.target.value, 10) });
|
||||
}
|
||||
}
|
||||
|
||||
const intervals = options.map((option) => option.value);
|
||||
|
||||
export function edgeAsyncIntervalsValidation(): SchemaOf<EdgeAsyncIntervalsValues> {
|
||||
return object({
|
||||
PingInterval: number().required('This field is required.').oneOf(intervals),
|
||||
SnapshotInterval: number()
|
||||
.required('This field is required.')
|
||||
.oneOf(intervals),
|
||||
CommandInterval: number()
|
||||
.required('This field is required.')
|
||||
.oneOf(intervals),
|
||||
});
|
||||
}
|
67
app/react/edge/components/EdgeCheckInIntervalField.tsx
Normal file
67
app/react/edge/components/EdgeCheckInIntervalField.tsx
Normal file
|
@ -0,0 +1,67 @@
|
|||
import { FormControl, Size } from '@@/form-components/FormControl';
|
||||
import { Select } from '@@/form-components/Input';
|
||||
|
||||
import { Options, useIntervalOptions } from './useIntervalOptions';
|
||||
|
||||
interface Props {
|
||||
value: number;
|
||||
onChange(value: number): void;
|
||||
isDefaultHidden?: boolean;
|
||||
label?: string;
|
||||
tooltip?: string;
|
||||
readonly?: boolean;
|
||||
size?: Size;
|
||||
}
|
||||
|
||||
export const checkinIntervalOptions: Options = [
|
||||
{ label: 'Use default interval', value: 0, isDefault: true },
|
||||
{
|
||||
label: '5 seconds',
|
||||
value: 5,
|
||||
},
|
||||
{
|
||||
label: '10 seconds',
|
||||
value: 10,
|
||||
},
|
||||
{
|
||||
label: '30 seconds',
|
||||
value: 30,
|
||||
},
|
||||
{ label: '5 minutes', value: 300 },
|
||||
{ label: '1 hour', value: 3600 },
|
||||
{ label: '1 day', value: 86400 },
|
||||
];
|
||||
|
||||
export function EdgeCheckinIntervalField({
|
||||
value,
|
||||
readonly,
|
||||
onChange,
|
||||
isDefaultHidden = false,
|
||||
label = 'Poll frequency',
|
||||
tooltip = 'Interval used by this Edge agent to check in with the Portainer instance. Affects Edge environment management and Edge compute features.',
|
||||
size = 'small',
|
||||
}: Props) {
|
||||
const options = useIntervalOptions(
|
||||
'EdgeAgentCheckinInterval',
|
||||
checkinIntervalOptions,
|
||||
isDefaultHidden
|
||||
);
|
||||
|
||||
return (
|
||||
<FormControl
|
||||
inputId="edge_checkin"
|
||||
label={label}
|
||||
tooltip={tooltip}
|
||||
size={size}
|
||||
>
|
||||
<Select
|
||||
value={value}
|
||||
onChange={(e) => {
|
||||
onChange(parseInt(e.currentTarget.value, 10));
|
||||
}}
|
||||
options={options}
|
||||
disabled={readonly}
|
||||
/>
|
||||
</FormControl>
|
||||
);
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
import _ from 'lodash';
|
||||
|
||||
import { Select } from '@@/form-components/ReactSelect';
|
||||
|
||||
import { EdgeGroup } from '../edge-groups/types';
|
||||
|
||||
type SingleValue = EdgeGroup['Id'];
|
||||
|
||||
interface Props {
|
||||
items: EdgeGroup[];
|
||||
value: SingleValue[];
|
||||
onChange: (value: SingleValue[]) => void;
|
||||
}
|
||||
|
||||
export function EdgeGroupsSelector({ items, value, onChange }: Props) {
|
||||
const valueGroups = _.compact(
|
||||
value.map((id) => items.find((item) => item.Id === id))
|
||||
);
|
||||
|
||||
return (
|
||||
<Select
|
||||
aria-label="Edge groups"
|
||||
options={items}
|
||||
isMulti
|
||||
getOptionLabel={(item) => item.Name}
|
||||
getOptionValue={(item) => String(item.Id)}
|
||||
value={valueGroups}
|
||||
onChange={(value) => {
|
||||
onChange(value.map((item) => item.Id));
|
||||
}}
|
||||
placeholder="Select one or multiple group(s)"
|
||||
closeMenuOnSelect={false}
|
||||
/>
|
||||
);
|
||||
}
|
|
@ -14,18 +14,21 @@ const edgePropertiesFormInitialValues: ScriptFormValues = {
|
|||
platform: 'k8s' as Platform,
|
||||
nomadToken: '',
|
||||
authEnabled: true,
|
||||
tlsEnabled: false,
|
||||
};
|
||||
|
||||
interface Props {
|
||||
edgeInfo: EdgeInfo;
|
||||
commands: CommandTab[] | Partial<Record<OS, CommandTab[]>>;
|
||||
isNomadTokenVisible?: boolean;
|
||||
hideAsyncMode?: boolean;
|
||||
}
|
||||
|
||||
export function EdgeScriptForm({
|
||||
edgeInfo,
|
||||
commands,
|
||||
isNomadTokenVisible,
|
||||
hideAsyncMode,
|
||||
}: Props) {
|
||||
const showOsSelector = !(commands instanceof Array);
|
||||
|
||||
|
@ -60,6 +63,7 @@ export function EdgeScriptForm({
|
|||
onPlatformChange={(platform) =>
|
||||
setFieldValue('platform', platform)
|
||||
}
|
||||
hideAsyncMode={hideAsyncMode}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
|
|
|
@ -6,6 +6,17 @@ export function validationSchema(isNomadTokenVisible?: boolean) {
|
|||
return object().shape({
|
||||
allowSelfSignedCertificates: boolean(),
|
||||
envVars: string(),
|
||||
...(isNomadTokenVisible ? nomadTokenValidation() : {}),
|
||||
...nomadValidation(isNomadTokenVisible),
|
||||
});
|
||||
}
|
||||
|
||||
function nomadValidation(isNomadTokenVisible?: boolean) {
|
||||
if (!isNomadTokenVisible) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return {
|
||||
tlsEnabled: boolean().default(false),
|
||||
...nomadTokenValidation(),
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Field, useFormikContext } from 'formik';
|
||||
import { useFormikContext, Field } from 'formik';
|
||||
|
||||
import { FormControl } from '@@/form-components/FormControl';
|
||||
import { Input } from '@@/form-components/Input';
|
||||
|
@ -47,7 +47,22 @@ export function EdgeScriptSettingsFieldset({
|
|||
</>
|
||||
)}
|
||||
|
||||
{isNomadTokenVisible && <NomadTokenField />}
|
||||
{isNomadTokenVisible && (
|
||||
<>
|
||||
<NomadTokenField />
|
||||
|
||||
<div className="form-group">
|
||||
<div className="col-sm-12">
|
||||
<SwitchField
|
||||
label="TLS"
|
||||
labelClass="col-sm-3 col-lg-2"
|
||||
checked={values.tlsEnabled}
|
||||
onChange={(checked) => setFieldValue('tlsEnabled', checked)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
<FormControl
|
||||
label="Environment variables"
|
||||
|
|
|
@ -16,6 +16,7 @@ interface Props {
|
|||
commands: CommandTab[];
|
||||
platform?: Platform;
|
||||
onPlatformChange?(platform: Platform): void;
|
||||
hideAsyncMode?: boolean;
|
||||
}
|
||||
|
||||
export function ScriptTabs({
|
||||
|
@ -24,6 +25,7 @@ export function ScriptTabs({
|
|||
edgeId,
|
||||
commands,
|
||||
platform,
|
||||
hideAsyncMode = false,
|
||||
onPlatformChange = () => {},
|
||||
}: Props) {
|
||||
const agentDetails = useAgentDetails();
|
||||
|
@ -38,10 +40,17 @@ export function ScriptTabs({
|
|||
return null;
|
||||
}
|
||||
|
||||
const { agentSecret, agentVersion } = agentDetails;
|
||||
const { agentSecret, agentVersion, useEdgeAsyncMode } = agentDetails;
|
||||
|
||||
const options = commands.map((c) => {
|
||||
const cmd = c.command(agentVersion, edgeKey, values, edgeId, agentSecret);
|
||||
const cmd = c.command(
|
||||
agentVersion,
|
||||
edgeKey,
|
||||
values,
|
||||
!hideAsyncMode && useEdgeAsyncMode,
|
||||
edgeId,
|
||||
agentSecret
|
||||
);
|
||||
|
||||
return {
|
||||
id: c.id,
|
||||
|
|
|
@ -8,6 +8,7 @@ type CommandGenerator = (
|
|||
agentVersion: string,
|
||||
edgeKey: string,
|
||||
properties: ScriptFormValues,
|
||||
useAsyncMode: boolean,
|
||||
edgeId?: string,
|
||||
agentSecret?: string
|
||||
) => string;
|
||||
|
@ -34,6 +35,11 @@ export const commandsTabs: Record<string, CommandTab> = {
|
|||
label: 'Docker Standalone',
|
||||
command: buildLinuxStandaloneCommand,
|
||||
},
|
||||
nomadLinux: {
|
||||
id: 'nomad',
|
||||
label: 'Nomad',
|
||||
command: buildLinuxNomadCommand,
|
||||
},
|
||||
swarmWindows: {
|
||||
id: 'swarm',
|
||||
label: 'Docker Swarm',
|
||||
|
@ -58,6 +64,7 @@ export function buildLinuxStandaloneCommand(
|
|||
agentVersion: string,
|
||||
edgeKey: string,
|
||||
properties: ScriptFormValues,
|
||||
useAsyncMode: boolean,
|
||||
edgeId?: string,
|
||||
agentSecret?: string
|
||||
) {
|
||||
|
@ -69,7 +76,8 @@ export function buildLinuxStandaloneCommand(
|
|||
edgeKey,
|
||||
allowSelfSignedCertificates,
|
||||
!edgeIdGenerator ? edgeId : undefined,
|
||||
agentSecret
|
||||
agentSecret,
|
||||
useAsyncMode
|
||||
)
|
||||
);
|
||||
|
||||
|
@ -92,6 +100,7 @@ export function buildWindowsStandaloneCommand(
|
|||
agentVersion: string,
|
||||
edgeKey: string,
|
||||
properties: ScriptFormValues,
|
||||
useAsyncMode: boolean,
|
||||
edgeId?: string,
|
||||
agentSecret?: string
|
||||
) {
|
||||
|
@ -103,7 +112,8 @@ export function buildWindowsStandaloneCommand(
|
|||
edgeKey,
|
||||
allowSelfSignedCertificates,
|
||||
edgeIdGenerator ? '$Env:PORTAINER_EDGE_ID' : edgeId,
|
||||
agentSecret
|
||||
agentSecret,
|
||||
useAsyncMode
|
||||
)
|
||||
);
|
||||
|
||||
|
@ -127,6 +137,7 @@ export function buildLinuxSwarmCommand(
|
|||
agentVersion: string,
|
||||
edgeKey: string,
|
||||
properties: ScriptFormValues,
|
||||
useAsyncMode: boolean,
|
||||
edgeId?: string,
|
||||
agentSecret?: string
|
||||
) {
|
||||
|
@ -137,7 +148,8 @@ export function buildLinuxSwarmCommand(
|
|||
edgeKey,
|
||||
allowSelfSignedCertificates,
|
||||
!edgeIdGenerator ? edgeId : undefined,
|
||||
agentSecret
|
||||
agentSecret,
|
||||
useAsyncMode
|
||||
),
|
||||
'AGENT_CLUSTER_ADDR=tasks.portainer_edge_agent',
|
||||
]);
|
||||
|
@ -167,6 +179,7 @@ export function buildWindowsSwarmCommand(
|
|||
agentVersion: string,
|
||||
edgeKey: string,
|
||||
properties: ScriptFormValues,
|
||||
useAsyncMode: boolean,
|
||||
edgeId?: string,
|
||||
agentSecret?: string
|
||||
) {
|
||||
|
@ -177,7 +190,8 @@ export function buildWindowsSwarmCommand(
|
|||
edgeKey,
|
||||
allowSelfSignedCertificates,
|
||||
edgeIdGenerator ? '$Env:PORTAINER_EDGE_ID' : edgeId,
|
||||
agentSecret
|
||||
agentSecret,
|
||||
useAsyncMode
|
||||
),
|
||||
'AGENT_CLUSTER_ADDR=tasks.portainer_edge_agent',
|
||||
]);
|
||||
|
@ -208,13 +222,17 @@ export function buildLinuxKubernetesCommand(
|
|||
agentVersion: string,
|
||||
edgeKey: string,
|
||||
properties: ScriptFormValues,
|
||||
useAsyncMode: boolean,
|
||||
edgeId?: string,
|
||||
agentSecret?: string
|
||||
) {
|
||||
const { allowSelfSignedCertificates, edgeIdGenerator, envVars } = properties;
|
||||
|
||||
const agentShortVersion = getAgentShortVersion(agentVersion);
|
||||
const envVarsTrimmed = envVars.trim();
|
||||
let envVarsTrimmed = envVars.trim();
|
||||
if (useAsyncMode) {
|
||||
envVarsTrimmed += `EDGE_ASYNC=1`;
|
||||
}
|
||||
const idEnvVar = edgeIdGenerator
|
||||
? `PORTAINER_EDGE_ID=$(${edgeIdGenerator}) \n\n`
|
||||
: '';
|
||||
|
@ -224,11 +242,43 @@ export function buildLinuxKubernetesCommand(
|
|||
return `${idEnvVar}curl https://downloads.portainer.io/ee${agentShortVersion}/portainer-edge-agent-setup.sh | bash -s -- "${edgeIdVar}" "${edgeKey}" "${selfSigned}" "${agentSecret}" "${envVarsTrimmed}"`;
|
||||
}
|
||||
|
||||
export function buildLinuxNomadCommand(
|
||||
agentVersion: string,
|
||||
edgeKey: string,
|
||||
properties: ScriptFormValues,
|
||||
useAsyncMode: boolean,
|
||||
edgeId?: string,
|
||||
agentSecret?: string
|
||||
) {
|
||||
const {
|
||||
allowSelfSignedCertificates,
|
||||
edgeIdGenerator,
|
||||
envVars,
|
||||
nomadToken = '',
|
||||
tlsEnabled,
|
||||
} = properties;
|
||||
|
||||
const agentShortVersion = getAgentShortVersion(agentVersion);
|
||||
let envVarsTrimmed = envVars.trim();
|
||||
if (useAsyncMode) {
|
||||
envVarsTrimmed += `EDGE_ASYNC=1`;
|
||||
}
|
||||
|
||||
const selfSigned = allowSelfSignedCertificates ? '1' : '0';
|
||||
const idEnvVar = edgeIdGenerator
|
||||
? `PORTAINER_EDGE_ID=$(${edgeIdGenerator}) \n\n`
|
||||
: '';
|
||||
const edgeIdVar = !edgeIdGenerator && edgeId ? edgeId : '$PORTAINER_EDGE_ID';
|
||||
|
||||
return `${idEnvVar}curl https://downloads.portainer.io/ee${agentShortVersion}/portainer-edge-agent-nomad-setup.sh | bash -s -- "${nomadToken}" "${edgeIdVar}" "${edgeKey}" "${selfSigned}" "${envVarsTrimmed}" "${agentSecret}" "${tlsEnabled}"`;
|
||||
}
|
||||
|
||||
function buildDefaultEnvVars(
|
||||
edgeKey: string,
|
||||
allowSelfSignedCerts: boolean,
|
||||
edgeId = '$PORTAINER_EDGE_ID',
|
||||
agentSecret = ''
|
||||
agentSecret = '',
|
||||
useAsyncMode = false
|
||||
) {
|
||||
return _.compact([
|
||||
'EDGE=1',
|
||||
|
@ -236,5 +286,6 @@ function buildDefaultEnvVars(
|
|||
`EDGE_KEY=${edgeKey}`,
|
||||
`EDGE_INSECURE_POLL=${allowSelfSignedCerts ? 1 : 0}`,
|
||||
agentSecret ? `AGENT_SECRET=${agentSecret}` : ``,
|
||||
useAsyncMode ? 'EDGE_ASYNC=1' : '',
|
||||
]);
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ export type OS = 'win' | 'linux';
|
|||
export interface ScriptFormValues {
|
||||
nomadToken: string;
|
||||
authEnabled: boolean;
|
||||
tlsEnabled: boolean;
|
||||
|
||||
allowSelfSignedCertificates: boolean;
|
||||
envVars: string;
|
||||
|
|
67
app/react/edge/components/useIntervalOptions.ts
Normal file
67
app/react/edge/components/useIntervalOptions.ts
Normal file
|
@ -0,0 +1,67 @@
|
|||
import _ from 'lodash';
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
import { useSettings } from '@/react/portainer/settings/queries';
|
||||
|
||||
type Option = {
|
||||
label: string;
|
||||
value: number;
|
||||
};
|
||||
|
||||
type DefaultOption = Option & { isDefault: true };
|
||||
|
||||
export type Options = [DefaultOption, ...Option[]];
|
||||
|
||||
export function useIntervalOptions(
|
||||
fieldName:
|
||||
| 'Edge.PingInterval'
|
||||
| 'Edge.SnapshotInterval'
|
||||
| 'Edge.CommandInterval'
|
||||
| 'EdgeAgentCheckinInterval',
|
||||
initialOptions: Options,
|
||||
isDefaultHidden: boolean
|
||||
) {
|
||||
const [{ value: defaultValue }] = initialOptions;
|
||||
const [options, setOptions] = useState<Option[]>(initialOptions);
|
||||
|
||||
const settingsQuery = useSettings(
|
||||
(settings) => _.get(settings, fieldName, 0) as number,
|
||||
!isDefaultHidden
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (isDefaultHidden) {
|
||||
setOptions(initialOptions.slice(1));
|
||||
}
|
||||
|
||||
if (
|
||||
!isDefaultHidden &&
|
||||
typeof settingsQuery.data !== 'undefined' &&
|
||||
settingsQuery.data !== defaultValue
|
||||
) {
|
||||
setOptions((options) => {
|
||||
let label = `${settingsQuery.data} seconds`;
|
||||
const option = options.find((o) => o.value === settingsQuery.data);
|
||||
if (option) {
|
||||
label = option.label;
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
value: defaultValue,
|
||||
label: `Use default interval (${label})`,
|
||||
},
|
||||
...options.slice(1),
|
||||
];
|
||||
});
|
||||
}
|
||||
}, [
|
||||
settingsQuery.data,
|
||||
setOptions,
|
||||
isDefaultHidden,
|
||||
initialOptions,
|
||||
defaultValue,
|
||||
]);
|
||||
|
||||
return options;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue