{options
diff --git a/app/react/components/TagSelector/TagSelector.tsx b/app/react/components/TagSelector/TagSelector.tsx
index d50b44a1c..79ee52fca 100644
--- a/app/react/components/TagSelector/TagSelector.tsx
+++ b/app/react/components/TagSelector/TagSelector.tsx
@@ -60,6 +60,7 @@ export function TagSelector({ value, allowCreate = false, onChange }: Props) {
{selectedTags.map((tag) => (
>;
isNomadTokenVisible?: boolean;
- hideAsyncMode?: boolean;
+ asyncMode?: boolean;
}
export function EdgeScriptForm({
edgeInfo,
commands,
isNomadTokenVisible,
- hideAsyncMode,
-}: Props) {
+ asyncMode,
+ children,
+}: PropsWithChildren) {
const showOsSelector = !(commands instanceof Array);
return (
@@ -41,6 +43,8 @@ export function EdgeScriptForm({
>
{({ values, setFieldValue }) => (
<>
+ {children}
+
setFieldValue('platform', platform)
}
- hideAsyncMode={hideAsyncMode}
+ asyncMode={asyncMode}
/>
>
diff --git a/app/react/edge/components/EdgeScriptForm/ScriptTabs.tsx b/app/react/edge/components/EdgeScriptForm/ScriptTabs.tsx
index 7b0a8c57f..653d2b5ff 100644
--- a/app/react/edge/components/EdgeScriptForm/ScriptTabs.tsx
+++ b/app/react/edge/components/EdgeScriptForm/ScriptTabs.tsx
@@ -16,7 +16,7 @@ interface Props {
commands: CommandTab[];
platform?: Platform;
onPlatformChange?(platform: Platform): void;
- hideAsyncMode?: boolean;
+ asyncMode?: boolean;
}
export function ScriptTabs({
@@ -25,7 +25,7 @@ export function ScriptTabs({
edgeId,
commands,
platform,
- hideAsyncMode = false,
+ asyncMode = false,
onPlatformChange = () => {},
}: Props) {
const agentDetails = useAgentDetails();
@@ -40,14 +40,14 @@ export function ScriptTabs({
return null;
}
- const { agentSecret, agentVersion, useEdgeAsyncMode } = agentDetails;
+ const { agentSecret, agentVersion } = agentDetails;
const options = commands.map((c) => {
const cmd = c.command(
agentVersion,
edgeKey,
values,
- !hideAsyncMode && useEdgeAsyncMode,
+ asyncMode,
edgeId,
agentSecret
);
@@ -67,14 +67,10 @@ export function ScriptTabs({
});
return (
-
-
- onPlatformChange(id)}
- />
-
-
+
onPlatformChange(id)}
+ />
);
}
diff --git a/app/react/edge/components/edge-agent-async.svg b/app/react/edge/components/edge-agent-async.svg
new file mode 100644
index 000000000..fa1a0b9ee
--- /dev/null
+++ b/app/react/edge/components/edge-agent-async.svg
@@ -0,0 +1,10 @@
+
diff --git a/app/react/edge/components/edge-agent-standard.svg b/app/react/edge/components/edge-agent-standard.svg
new file mode 100644
index 000000000..8590d5130
--- /dev/null
+++ b/app/react/edge/components/edge-agent-standard.svg
@@ -0,0 +1,10 @@
+
diff --git a/app/react/edge/edge-devices/WaitingRoomView/WaitingRoomView.tsx b/app/react/edge/edge-devices/WaitingRoomView/WaitingRoomView.tsx
index c6aa397df..cce80f494 100644
--- a/app/react/edge/edge-devices/WaitingRoomView/WaitingRoomView.tsx
+++ b/app/react/edge/edge-devices/WaitingRoomView/WaitingRoomView.tsx
@@ -12,7 +12,6 @@ export default withLimitToBE(WaitingRoomView);
function WaitingRoomView() {
const { environments, isLoading, totalCount } = useEnvironmentList({
- edgeDevice: true,
edgeDeviceUntrusted: true,
excludeSnapshots: true,
types: EdgeTypes,
diff --git a/app/react/portainer/HomeView/EnvironmentList/EnvironmentItem/AgentDetails.tsx b/app/react/portainer/HomeView/EnvironmentList/EnvironmentItem/AgentDetails.tsx
index b1abf5899..a292dfeb2 100644
--- a/app/react/portainer/HomeView/EnvironmentList/EnvironmentItem/AgentDetails.tsx
+++ b/app/react/portainer/HomeView/EnvironmentList/EnvironmentItem/AgentDetails.tsx
@@ -1,5 +1,3 @@
-import { Globe } from 'lucide-react';
-
import { Environment } from '@/react/portainer/environments/types';
import { isAgentEnvironment } from '@/react/portainer/environments/utils';
@@ -8,15 +6,5 @@ export function AgentDetails({ environment }: { environment: Environment }) {
return null;
}
- return (
- <>
- {environment.Agent.Version}
- {environment.Edge.AsyncMode && (
-
-
- Async Environment
-
- )}
- >
- );
+ return {environment.Agent.Version};
}
diff --git a/app/react/portainer/HomeView/EnvironmentList/EnvironmentItem/EnvironmentTypeTag.tsx b/app/react/portainer/HomeView/EnvironmentList/EnvironmentItem/EnvironmentTypeTag.tsx
index 6bf323e64..bbae9aee2 100644
--- a/app/react/portainer/HomeView/EnvironmentList/EnvironmentItem/EnvironmentTypeTag.tsx
+++ b/app/react/portainer/HomeView/EnvironmentList/EnvironmentItem/EnvironmentTypeTag.tsx
@@ -31,12 +31,12 @@ export function EnvironmentTypeTag({
}
function getTypeLabel(environment: Environment) {
- if (environment.IsEdgeDevice) {
- return 'Edge Device';
- }
-
if (isEdgeEnvironment(environment.Type)) {
- return 'Edge Agent';
+ if (environment.Edge.AsyncMode) {
+ return 'Edge Agent Async';
+ }
+
+ return 'Edge Agent Standard';
}
if (isLocalEnvironment(environment)) {
diff --git a/app/react/portainer/HomeView/EnvironmentList/EnvironmentList.tsx b/app/react/portainer/HomeView/EnvironmentList/EnvironmentList.tsx
index 2a452e446..94e3e7eb0 100644
--- a/app/react/portainer/HomeView/EnvironmentList/EnvironmentList.tsx
+++ b/app/react/portainer/HomeView/EnvironmentList/EnvironmentList.tsx
@@ -110,6 +110,7 @@ export function EnvironmentList({ onClickBrowse, onRefresh }: Props) {
tagsPartialMatch: true,
agentVersions: agentVersions.map((a) => a.value),
updateInformation: isBE,
+ edgeAsync: getEdgeAsyncValue(connectionTypes),
};
const queryWithSort = {
@@ -282,8 +283,8 @@ export function EnvironmentList({ onClickBrowse, onRefresh }: Props) {
EnvironmentType.AgentOnDocker,
EnvironmentType.AgentOnKubernetes,
],
- [ConnectionType.EdgeAgent]: EdgeTypes,
- [ConnectionType.EdgeDevice]: EdgeTypes,
+ [ConnectionType.EdgeAgentStandard]: EdgeTypes,
+ [ConnectionType.EdgeAgentAsync]: EdgeTypes,
};
const selectedTypesByPlatform = platformTypes.flatMap(
@@ -405,3 +406,21 @@ function renderItems(
return items;
}
+
+function getEdgeAsyncValue(connectionTypes: Filter[]) {
+ const hasEdgeAsync = connectionTypes.some(
+ (connectionType) => connectionType.value === ConnectionType.EdgeAgentAsync
+ );
+
+ const hasEdgeStandard = connectionTypes.some(
+ (connectionType) =>
+ connectionType.value === ConnectionType.EdgeAgentStandard
+ );
+
+ // If both are selected, we don't want to filter on either, and same for if both are not selected
+ if (hasEdgeAsync === hasEdgeStandard) {
+ return undefined;
+ }
+
+ return hasEdgeAsync;
+}
diff --git a/app/react/portainer/HomeView/EnvironmentList/EnvironmentListFilters.tsx b/app/react/portainer/HomeView/EnvironmentList/EnvironmentListFilters.tsx
index 65338698a..8f20ddcd6 100644
--- a/app/react/portainer/HomeView/EnvironmentList/EnvironmentListFilters.tsx
+++ b/app/react/portainer/HomeView/EnvironmentList/EnvironmentListFilters.tsx
@@ -176,22 +176,26 @@ function getConnectionTypeOptions(platformTypes: Filter[]) {
[PlatformType.Docker]: [
ConnectionType.API,
ConnectionType.Agent,
- ConnectionType.EdgeAgent,
- ConnectionType.EdgeDevice,
+ ConnectionType.EdgeAgentStandard,
+ ConnectionType.EdgeAgentAsync,
],
[PlatformType.Azure]: [ConnectionType.API],
[PlatformType.Kubernetes]: [
ConnectionType.Agent,
- ConnectionType.EdgeAgent,
- ConnectionType.EdgeDevice,
+ ConnectionType.EdgeAgentStandard,
+ ConnectionType.EdgeAgentAsync,
+ ],
+ [PlatformType.Nomad]: [
+ ConnectionType.EdgeAgentStandard,
+ ConnectionType.EdgeAgentAsync,
],
- [PlatformType.Nomad]: [ConnectionType.EdgeAgent, ConnectionType.EdgeDevice],
};
const connectionTypesDefaultOptions = [
{ value: ConnectionType.API, label: 'API' },
{ value: ConnectionType.Agent, label: 'Agent' },
- { value: ConnectionType.EdgeAgent, label: 'Edge Agent' },
+ { value: ConnectionType.EdgeAgentStandard, label: 'Edge Agent Standard' },
+ { value: ConnectionType.EdgeAgentAsync, label: 'Edge Agent Async' },
];
if (platformTypes.length === 0) {
@@ -226,12 +230,12 @@ function getPlatformTypeOptions(connectionTypes: Filter[]) {
const connectionTypePlatformType = {
[ConnectionType.API]: [PlatformType.Docker, PlatformType.Azure],
[ConnectionType.Agent]: [PlatformType.Docker, PlatformType.Kubernetes],
- [ConnectionType.EdgeAgent]: [
+ [ConnectionType.EdgeAgentStandard]: [
PlatformType.Kubernetes,
PlatformType.Nomad,
PlatformType.Docker,
],
- [ConnectionType.EdgeDevice]: [
+ [ConnectionType.EdgeAgentAsync]: [
PlatformType.Nomad,
PlatformType.Docker,
PlatformType.Kubernetes,
diff --git a/app/react/portainer/HomeView/EnvironmentList/types.ts b/app/react/portainer/HomeView/EnvironmentList/types.ts
index 81c81af68..7030c45a3 100644
--- a/app/react/portainer/HomeView/EnvironmentList/types.ts
+++ b/app/react/portainer/HomeView/EnvironmentList/types.ts
@@ -6,6 +6,6 @@ export interface Filter {
export enum ConnectionType {
API,
Agent,
- EdgeAgent,
- EdgeDevice,
+ EdgeAgentStandard,
+ EdgeAgentAsync,
}
diff --git a/app/react/portainer/common/PortainerTunnelAddrField.tsx b/app/react/portainer/common/PortainerTunnelAddrField.tsx
new file mode 100644
index 000000000..4f03f0a47
--- /dev/null
+++ b/app/react/portainer/common/PortainerTunnelAddrField.tsx
@@ -0,0 +1,65 @@
+import { Field, useField } from 'formik';
+import { string } from 'yup';
+
+import { FormControl } from '@@/form-components/FormControl';
+import { Input } from '@@/form-components/Input';
+
+interface Props {
+ fieldName: string;
+ readonly?: boolean;
+ required?: boolean;
+}
+
+export function PortainerTunnelAddrField({
+ fieldName,
+ readonly,
+ required,
+}: Props) {
+ const [, metaProps] = useField(fieldName);
+ const id = `${fieldName}-input`;
+
+ return (
+
+
+
+ );
+}
+
+export function validation() {
+ return string()
+ .required('Tunnel server address is required')
+ .test(
+ 'valid tunnel server URL',
+ 'The tunnel server address must be a valid address (localhost cannot be used)',
+ (value) => {
+ if (!value) {
+ return false;
+ }
+
+ return !value.startsWith('localhost');
+ }
+ );
+}
+
+/**
+ * Returns an address that can be used as a default value for the Portainer tunnel server address
+ * based on the current window location.
+ * Used for Edge Compute.
+ *
+ */
+export function buildDefaultValue() {
+ return `${window.location.hostname}:8000`;
+}
diff --git a/app/react/portainer/environments/wizard/EnvironmentsCreationView/shared/EdgeAgentTab/EdgeAgentForm/PortainerUrlField.tsx b/app/react/portainer/common/PortainerUrlField.tsx
similarity index 50%
rename from app/react/portainer/environments/wizard/EnvironmentsCreationView/shared/EdgeAgentTab/EdgeAgentForm/PortainerUrlField.tsx
rename to app/react/portainer/common/PortainerUrlField.tsx
index 4e12c7b2f..826b8fee2 100644
--- a/app/react/portainer/environments/wizard/EnvironmentsCreationView/shared/EdgeAgentTab/EdgeAgentForm/PortainerUrlField.tsx
+++ b/app/react/portainer/common/PortainerUrlField.tsx
@@ -7,36 +7,23 @@ import { Input } from '@@/form-components/Input';
interface Props {
fieldName: string;
readonly?: boolean;
+ required?: boolean;
+ tooltip?: string;
}
-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) {
+export function PortainerUrlField({
+ fieldName,
+ readonly,
+ required,
+ tooltip = 'URL of the Portainer instance that the agent will use to initiate the communications.',
+}: Props) {
const [, metaProps] = useField(fieldName);
const id = `${fieldName}-input`;
return (
);
}
+
+export function validation() {
+ return string()
+ .required('API server URL is required')
+ .test(
+ 'valid API server URL',
+ 'The API server URL must be a valid URL (localhost cannot be used)',
+ (value) => {
+ if (!value) {
+ return false;
+ }
+
+ try {
+ const url = new URL(value);
+ return !!url.hostname && url.hostname !== 'localhost';
+ } catch {
+ return false;
+ }
+ }
+ );
+}
+
+/**
+ * Returns a URL that can be used as a default value for the Portainer server API URL
+ * based on the current window location.
+ * Used for Edge Compute.
+ *
+ */
+export function buildDefaultValue() {
+ return `${window.location.protocol}//${window.location.host}`;
+}
diff --git a/app/react/portainer/environments/EdgeAutoCreateScriptView/AutomaticEdgeEnvCreation/AutomaticEdgeEnvCreation.tsx b/app/react/portainer/environments/EdgeAutoCreateScriptView/AutomaticEdgeEnvCreation/AutomaticEdgeEnvCreation.tsx
new file mode 100644
index 000000000..5d5f91d81
--- /dev/null
+++ b/app/react/portainer/environments/EdgeAutoCreateScriptView/AutomaticEdgeEnvCreation/AutomaticEdgeEnvCreation.tsx
@@ -0,0 +1,180 @@
+import { useMutation } from 'react-query';
+import { useEffect, useState } from 'react';
+import { Laptop } from 'lucide-react';
+
+import { generateKey } from '@/react/portainer/environments/environment.service/edge';
+import { EdgeScriptForm } from '@/react/edge/components/EdgeScriptForm';
+import { commandsTabs } from '@/react/edge/components/EdgeScriptForm/scripts';
+import { useSettings } from '@/react/portainer/settings/queries';
+import EdgeAgentStandardIcon from '@/react/edge/components/edge-agent-standard.svg?c';
+import EdgeAgentAsyncIcon from '@/react/edge/components/edge-agent-async.svg?c';
+
+import { Widget, WidgetBody, WidgetTitle } from '@@/Widget';
+import { TextTip } from '@@/Tip/TextTip';
+import { BoxSelector } from '@@/BoxSelector';
+import { FormSection } from '@@/form-components/FormSection';
+import { CopyButton } from '@@/buttons';
+import { Link } from '@@/Link';
+import { FormControl } from '@@/form-components/FormControl';
+import { Input } from '@@/form-components/Input';
+
+const commands = {
+ linux: [
+ commandsTabs.k8sLinux,
+ commandsTabs.swarmLinux,
+ commandsTabs.standaloneLinux,
+ commandsTabs.nomadLinux,
+ ],
+ win: [commandsTabs.swarmWindows, commandsTabs.standaloneWindow],
+};
+
+const asyncModeOptions = [
+ {
+ icon: EdgeAgentStandardIcon,
+ id: 'standard',
+ label: 'Edge Agent Standard',
+ value: false,
+ iconType: 'badge',
+ },
+ {
+ icon: EdgeAgentAsyncIcon,
+ id: 'async',
+ label: 'Edge Agent Async',
+ value: true,
+ iconType: 'badge',
+ },
+] as const;
+
+export function AutomaticEdgeEnvCreation() {
+ const edgeKeyMutation = useGenerateKeyMutation();
+ const { mutate: generateKey, reset: resetKey } = edgeKeyMutation;
+ const settingsQuery = useSettings();
+ const [asyncMode, setAsyncMode] = useState(false);
+
+ const url = settingsQuery.data?.EdgePortainerUrl;
+
+ const settings = settingsQuery.data;
+ const edgeKey = edgeKeyMutation.data;
+ const edgeComputeConfigurationOK = validateConfiguration();
+
+ useEffect(() => {
+ if (edgeComputeConfigurationOK) {
+ generateKey();
+ } else {
+ resetKey();
+ }
+ }, [generateKey, edgeComputeConfigurationOK, resetKey]);
+
+ if (!settingsQuery.data) {
+ return null;
+ }
+
+ return (
+
+
+
+ {!edgeComputeConfigurationOK ? (
+
+ In order to use this feature, please turn on Edge Compute features{' '}
+ here and have
+ Portainer API server URL and tunnel server address properly
+ configured.
+
+ ) : (
+ <>
+
+
+
+ >
+ )}
+
+
+ );
+
+ function handleChangeAsyncMode(asyncMode: boolean) {
+ setAsyncMode(asyncMode);
+ }
+
+ function validateConfiguration() {
+ return !!(
+ settings &&
+ settings.EnableEdgeComputeFeatures &&
+ settings.EdgePortainerUrl &&
+ settings.Edge.TunnelServerAddress
+ );
+ }
+}
+
+// using mutation because we want this action to run only when required
+function useGenerateKeyMutation() {
+ return useMutation(generateKey);
+}
+
+function EdgeKeyInfo({
+ isLoading,
+ edgeKey,
+ url,
+ tunnelUrl,
+ asyncMode,
+}: {
+ isLoading: boolean;
+ edgeKey?: string;
+ url?: string;
+ tunnelUrl?: string;
+ asyncMode: boolean;
+}) {
+ if (isLoading || !edgeKey) {
+ return Generating key for {url} ...
;
+ }
+
+ return (
+ <>
+
+
+
+
+ {edgeKey}
+
+
+ Copy token
+
+
+
+
+
+
+
+
+
+ {!asyncMode && (
+
+
+
+ )}
+
+
+ Portainer Server URL{' '}
+ {!asyncMode ? 'and tunnel server address are' : 'is'} set{' '}
+ here
+
+
+ >
+ );
+}
diff --git a/app/react/portainer/environments/EdgeAutoCreateScriptView/AutomaticEdgeEnvCreation/index.ts b/app/react/portainer/environments/EdgeAutoCreateScriptView/AutomaticEdgeEnvCreation/index.ts
new file mode 100644
index 000000000..1a10043f7
--- /dev/null
+++ b/app/react/portainer/environments/EdgeAutoCreateScriptView/AutomaticEdgeEnvCreation/index.ts
@@ -0,0 +1 @@
+export { AutomaticEdgeEnvCreation } from './AutomaticEdgeEnvCreation';
diff --git a/app/react/portainer/environments/EdgeAutoCreateScriptView/EdgeAutoCreateScriptView.tsx b/app/react/portainer/environments/EdgeAutoCreateScriptView/EdgeAutoCreateScriptView.tsx
new file mode 100644
index 000000000..de6666511
--- /dev/null
+++ b/app/react/portainer/environments/EdgeAutoCreateScriptView/EdgeAutoCreateScriptView.tsx
@@ -0,0 +1,27 @@
+import { withLimitToBE } from '@/react/hooks/useLimitToBE';
+
+import { PageHeader } from '@@/PageHeader';
+
+import { AutomaticEdgeEnvCreation } from './AutomaticEdgeEnvCreation';
+
+export const EdgeAutoCreateScriptViewWrapper = withLimitToBE(
+ EdgeAutoCreateScriptView
+);
+
+function EdgeAutoCreateScriptView() {
+ return (
+ <>
+
+
+
+ >
+ );
+}
diff --git a/app/react/portainer/environments/EdgeAutoCreateScriptView/index.ts b/app/react/portainer/environments/EdgeAutoCreateScriptView/index.ts
new file mode 100644
index 000000000..609cb24fe
--- /dev/null
+++ b/app/react/portainer/environments/EdgeAutoCreateScriptView/index.ts
@@ -0,0 +1 @@
+export { EdgeAutoCreateScriptViewWrapper as EdgeAutoCreateScriptView } from './EdgeAutoCreateScriptView';
diff --git a/app/react/portainer/environments/ListView/ImportFdoDeviceButton.tsx b/app/react/portainer/environments/ListView/ImportFdoDeviceButton.tsx
new file mode 100644
index 000000000..82a6752f6
--- /dev/null
+++ b/app/react/portainer/environments/ListView/ImportFdoDeviceButton.tsx
@@ -0,0 +1,28 @@
+import { Plus } from 'lucide-react';
+
+import { Button } from '@@/buttons';
+import { Link } from '@@/Link';
+
+import { useSettings } from '../../settings/queries';
+
+export function ImportFdoDeviceButton() {
+ const isFDOEnabledQuery = useSettings(
+ (settings) => settings.fdoConfiguration.enabled
+ );
+
+ if (!isFDOEnabledQuery.data) {
+ return null;
+ }
+
+ return (
+
+ );
+}
diff --git a/app/react/portainer/environments/environment.service/create.ts b/app/react/portainer/environments/environment.service/create.ts
index f565be622..78347d0ef 100644
--- a/app/react/portainer/environments/environment.service/create.ts
+++ b/app/react/portainer/environments/environment.service/create.ts
@@ -2,6 +2,7 @@ import { Gpu } from '@/react/portainer/environments/wizard/EnvironmentsCreationV
import axios, { parseAxiosError } from '@/portainer/services/axios';
import { type EnvironmentGroupId } from '@/react/portainer/environments/environment-groups/types';
import { type TagId } from '@/portainer/tags/types';
+import { EdgeAsyncIntervalsValues } from '@/react/edge/components/EdgeAsyncIntervalsForm';
import { type Environment, EnvironmentCreationTypes } from '../types';
@@ -101,6 +102,10 @@ interface TLSSettings {
keyFile?: File;
}
+interface EdgeSettings extends EdgeAsyncIntervalsValues {
+ asyncMode: boolean;
+}
+
export interface EnvironmentOptions {
url?: string;
publicUrl?: string;
@@ -110,6 +115,8 @@ export interface EnvironmentOptions {
isEdgeDevice?: boolean;
gpus?: Gpu[];
pollFrequency?: number;
+ edge?: EdgeSettings;
+ tunnelServerAddr?: string;
}
interface CreateRemoteEnvironment {
@@ -163,10 +170,12 @@ export function createAgentEnvironment({
interface CreateEdgeAgentEnvironment {
name: string;
portainerUrl: string;
+ tunnelServerAddr?: string;
meta?: EnvironmentMetadata;
pollFrequency: number;
gpus?: Gpu[];
isEdgeDevice?: boolean;
+ edge: EdgeSettings;
}
export function createEdgeAgentEnvironment({
@@ -176,6 +185,7 @@ export function createEdgeAgentEnvironment({
gpus = [],
isEdgeDevice,
pollFrequency,
+ edge,
}: CreateEdgeAgentEnvironment) {
return createEnvironment(
name,
@@ -189,6 +199,7 @@ export function createEdgeAgentEnvironment({
gpus,
isEdgeDevice,
pollFrequency,
+ edge,
meta,
}
);
@@ -240,6 +251,16 @@ async function createEnvironment(
AzureAuthenticationKey: azure.authenticationKey,
};
}
+
+ if (options.edge?.asyncMode) {
+ payload = {
+ ...payload,
+ EdgeAsyncMode: true,
+ EdgePingInterval: options.edge?.PingInterval,
+ EdgeSnapshotInterval: options.edge?.SnapshotInterval,
+ EdgeCommandInterval: options.edge?.CommandInterval,
+ };
+ }
}
const formPayload = json2formData(payload);
diff --git a/app/react/portainer/environments/environment.service/index.ts b/app/react/portainer/environments/environment.service/index.ts
index 3131c7c62..4c6763a55 100644
--- a/app/react/portainer/environments/environment.service/index.ts
+++ b/app/react/portainer/environments/environment.service/index.ts
@@ -22,7 +22,7 @@ export interface EnvironmentsQueryParams {
tagsPartialMatch?: boolean;
groupIds?: EnvironmentGroupId[];
status?: EnvironmentStatus[];
- edgeDevice?: boolean;
+ edgeAsync?: boolean;
edgeDeviceUntrusted?: boolean;
excludeSnapshots?: boolean;
provisioned?: boolean;
diff --git a/app/react/portainer/environments/queries/useAgentDetails.ts b/app/react/portainer/environments/queries/useAgentDetails.ts
index fc066f687..78a51cfa2 100644
--- a/app/react/portainer/environments/queries/useAgentDetails.ts
+++ b/app/react/portainer/environments/queries/useAgentDetails.ts
@@ -15,6 +15,5 @@ export function useAgentDetails() {
return {
agentVersion,
agentSecret: settingsQuery.data.AgentSecret,
- useEdgeAsyncMode: settingsQuery.data.Edge.AsyncMode,
};
}
diff --git a/app/react/portainer/environments/wizard/EnvironmentsCreationView/EnvironmentsCreationView.tsx b/app/react/portainer/environments/wizard/EnvironmentsCreationView/EnvironmentsCreationView.tsx
index 0d75ade82..bd78764ef 100644
--- a/app/react/portainer/environments/wizard/EnvironmentsCreationView/EnvironmentsCreationView.tsx
+++ b/app/react/portainer/environments/wizard/EnvironmentsCreationView/EnvironmentsCreationView.tsx
@@ -205,12 +205,14 @@ function useAnalyticsState() {
dockerAgent: 0,
dockerApi: 0,
kubernetesAgent: 0,
- kubernetesEdgeAgent: 0,
+ kubernetesEdgeAgentAsync: 0,
+ kubernetesEdgeAgentStandard: 0,
kaasAgent: 0,
aciApi: 0,
localEndpoint: 0,
- nomadEdgeAgent: 0,
- dockerEdgeAgent: 0,
+ nomadEdgeAgentStandard: 0,
+ dockerEdgeAgentAsync: 0,
+ dockerEdgeAgentStandard: 0,
});
return { analytics, setAnalytics };
diff --git a/app/react/portainer/environments/wizard/EnvironmentsCreationView/WizardDocker/APITab/DeploymentScripts.tsx b/app/react/portainer/environments/wizard/EnvironmentsCreationView/WizardDocker/APITab/DeploymentScripts.tsx
index 35ce8ef46..bbc8eec12 100644
--- a/app/react/portainer/environments/wizard/EnvironmentsCreationView/WizardDocker/APITab/DeploymentScripts.tsx
+++ b/app/react/portainer/environments/wizard/EnvironmentsCreationView/WizardDocker/APITab/DeploymentScripts.tsx
@@ -1,7 +1,5 @@
import { useState } from 'react';
-import { useAgentDetails } from '@/react/portainer/environments/queries/useAgentDetails';
-
import { CopyButton } from '@@/buttons/CopyButton';
import { Code } from '@@/Code';
import { NavTabs } from '@@/NavTabs';
@@ -22,12 +20,6 @@ const deployments = [
export function DeploymentScripts() {
const [deployType, setDeployType] = useState(deployments[0].id);
- const agentDetailsQuery = useAgentDetails();
-
- if (!agentDetailsQuery) {
- return null;
- }
-
const options = deployments.map((c) => ({
id: c.id,
label: c.label,
diff --git a/app/react/portainer/environments/wizard/EnvironmentsCreationView/WizardDocker/WizardDocker.tsx b/app/react/portainer/environments/wizard/EnvironmentsCreationView/WizardDocker/WizardDocker.tsx
index 177497b28..4e8fb2746 100644
--- a/app/react/portainer/environments/wizard/EnvironmentsCreationView/WizardDocker/WizardDocker.tsx
+++ b/app/react/portainer/environments/wizard/EnvironmentsCreationView/WizardDocker/WizardDocker.tsx
@@ -1,8 +1,12 @@
import { useState } from 'react';
-import { Zap, Cloud, Network, Plug2 } from 'lucide-react';
+import { Zap, Network, Plug2 } from 'lucide-react';
+import _ from 'lodash';
import { Environment } from '@/react/portainer/environments/types';
import { commandsTabs } from '@/react/edge/components/EdgeScriptForm/scripts';
+import { isBE } from '@/react/portainer/feature-flags/feature-flags.service';
+import EdgeAgentStandardIcon from '@/react/edge/components/edge-agent-standard.svg?c';
+import EdgeAgentAsyncIcon from '@/react/edge/components/edge-agent-async.svg?c';
import { BoxSelector, type BoxSelectorOption } from '@@/BoxSelector';
import { BadgeIcon } from '@@/BadgeIcon';
@@ -21,8 +25,8 @@ interface Props {
}
const defaultOptions: BoxSelectorOption<
- 'agent' | 'api' | 'socket' | 'edgeAgent'
->[] = [
+ 'agent' | 'api' | 'socket' | 'edgeAgentStandard' | 'edgeAgentAsync'
+>[] = _.compact([
{
id: 'agent',
icon: ,
@@ -45,17 +49,28 @@ const defaultOptions: BoxSelectorOption<
value: 'socket',
},
{
- id: 'edgeAgent',
- icon: ,
- label: 'Edge Agent',
+ id: 'edgeAgentStandard',
+ icon: EdgeAgentStandardIcon,
+ iconType: 'badge',
+ label: 'Edge Agent Standard',
description: '',
- value: 'edgeAgent',
- hide: window.ddExtension,
+ value: 'edgeAgentStandard',
},
-];
+ isBE && {
+ id: 'edgeAgentAsync',
+ icon: EdgeAgentAsyncIcon,
+ iconType: 'badge',
+ label: 'Edge Agent Async',
+ description: '',
+ value: 'edgeAgentAsync',
+ },
+]);
export function WizardDocker({ onCreate, isDockerStandalone }: Props) {
- const options = useFilterEdgeOptionsIfNeeded(defaultOptions, 'edgeAgent');
+ const options = useFilterEdgeOptionsIfNeeded(
+ defaultOptions,
+ 'edgeAgentStandard'
+ );
const [creationType, setCreationType] = useState(options[0].value);
@@ -74,7 +89,14 @@ export function WizardDocker({ onCreate, isDockerStandalone }: Props) {
);
- function getTab(creationType: 'agent' | 'api' | 'socket' | 'edgeAgent') {
+ function getTab(
+ creationType:
+ | 'agent'
+ | 'api'
+ | 'socket'
+ | 'edgeAgentStandard'
+ | 'edgeAgentAsync'
+ ) {
switch (creationType) {
case 'agent':
return (
@@ -95,10 +117,29 @@ export function WizardDocker({ onCreate, isDockerStandalone }: Props) {
onCreate={(environment) => onCreate(environment, 'localEndpoint')}
/>
);
- case 'edgeAgent':
+ case 'edgeAgentStandard':
return (