mirror of
https://github.com/portainer/portainer.git
synced 2025-08-08 23:35:31 +02:00
feat(podman): support add podman envs in the wizard [r8s-20] (#12056)
Some checks failed
ci / build_images (map[arch:amd64 platform:linux version:]) (push) Has been cancelled
ci / build_images (map[arch:amd64 platform:windows version:1809]) (push) Has been cancelled
ci / build_images (map[arch:amd64 platform:windows version:ltsc2022]) (push) Has been cancelled
ci / build_images (map[arch:arm platform:linux version:]) (push) Has been cancelled
ci / build_images (map[arch:arm64 platform:linux version:]) (push) Has been cancelled
ci / build_images (map[arch:ppc64le platform:linux version:]) (push) Has been cancelled
/ triage (push) Has been cancelled
Lint / Run linters (push) Has been cancelled
Test / test-client (push) Has been cancelled
Test / test-server (map[arch:amd64 platform:linux]) (push) Has been cancelled
Test / test-server (map[arch:amd64 platform:windows version:1809]) (push) Has been cancelled
Test / test-server (map[arch:amd64 platform:windows version:ltsc2022]) (push) Has been cancelled
Test / test-server (map[arch:arm64 platform:linux]) (push) Has been cancelled
ci / build_manifests (push) Has been cancelled
Some checks failed
ci / build_images (map[arch:amd64 platform:linux version:]) (push) Has been cancelled
ci / build_images (map[arch:amd64 platform:windows version:1809]) (push) Has been cancelled
ci / build_images (map[arch:amd64 platform:windows version:ltsc2022]) (push) Has been cancelled
ci / build_images (map[arch:arm platform:linux version:]) (push) Has been cancelled
ci / build_images (map[arch:arm64 platform:linux version:]) (push) Has been cancelled
ci / build_images (map[arch:ppc64le platform:linux version:]) (push) Has been cancelled
/ triage (push) Has been cancelled
Lint / Run linters (push) Has been cancelled
Test / test-client (push) Has been cancelled
Test / test-server (map[arch:amd64 platform:linux]) (push) Has been cancelled
Test / test-server (map[arch:amd64 platform:windows version:1809]) (push) Has been cancelled
Test / test-server (map[arch:amd64 platform:windows version:ltsc2022]) (push) Has been cancelled
Test / test-server (map[arch:arm64 platform:linux]) (push) Has been cancelled
ci / build_manifests (push) Has been cancelled
This commit is contained in:
parent
db616bc8a5
commit
32e94d4e4e
108 changed files with 1921 additions and 272 deletions
|
@ -1,17 +1,25 @@
|
|||
import { DockerSnapshot } from '@/react/docker/snapshots/types';
|
||||
import { useIsPodman } from '@/react/portainer/environments/queries/useIsPodman';
|
||||
import {
|
||||
Environment,
|
||||
PlatformType,
|
||||
KubernetesSnapshot,
|
||||
} from '@/react/portainer/environments/types';
|
||||
import { getPlatformType } from '@/react/portainer/environments/utils';
|
||||
import { getDockerEnvironmentType } from '@/react/portainer/environments/utils/getDockerEnvironmentType';
|
||||
|
||||
export function EngineVersion({ environment }: { environment: Environment }) {
|
||||
const platform = getPlatformType(environment.Type);
|
||||
const isPodman = useIsPodman(environment.Id);
|
||||
|
||||
switch (platform) {
|
||||
case PlatformType.Docker:
|
||||
return <DockerEngineVersion snapshot={environment.Snapshots[0]} />;
|
||||
return (
|
||||
<DockerEngineVersion
|
||||
snapshot={environment.Snapshots[0]}
|
||||
isPodman={isPodman}
|
||||
/>
|
||||
);
|
||||
case PlatformType.Kubernetes:
|
||||
return (
|
||||
<KubernetesEngineVersion
|
||||
|
@ -23,14 +31,21 @@ export function EngineVersion({ environment }: { environment: Environment }) {
|
|||
}
|
||||
}
|
||||
|
||||
function DockerEngineVersion({ snapshot }: { snapshot?: DockerSnapshot }) {
|
||||
function DockerEngineVersion({
|
||||
snapshot,
|
||||
isPodman,
|
||||
}: {
|
||||
snapshot?: DockerSnapshot;
|
||||
isPodman?: boolean;
|
||||
}) {
|
||||
if (!snapshot) {
|
||||
return null;
|
||||
}
|
||||
const type = getDockerEnvironmentType(snapshot.Swarm, isPodman);
|
||||
|
||||
return (
|
||||
<span className="small text-muted vertical-center">
|
||||
{snapshot.Swarm ? 'Swarm' : 'Standalone'} {snapshot.DockerVersion}
|
||||
{type} {snapshot.DockerVersion}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,43 +1,86 @@
|
|||
import { environmentTypeIcon } from '@/portainer/filters/filters';
|
||||
import dockerEdge from '@/assets/images/edge_endpoint.png';
|
||||
import { getEnvironmentTypeIcon } from '@/react/portainer/environments/utils';
|
||||
import dockerEdge from '@/assets/ico/docker-edge-environment.svg';
|
||||
import podmanEdge from '@/assets/ico/podman-edge-environment.svg';
|
||||
import kube from '@/assets/images/kubernetes_endpoint.png';
|
||||
import kubeEdge from '@/assets/images/kubernetes_edge_endpoint.png';
|
||||
import { EnvironmentType } from '@/react/portainer/environments/types';
|
||||
import kubeEdge from '@/assets/ico/kubernetes-edge-environment.svg';
|
||||
import {
|
||||
ContainerEngine,
|
||||
EnvironmentType,
|
||||
} from '@/react/portainer/environments/types';
|
||||
import azure from '@/assets/ico/vendor/azure.svg';
|
||||
import docker from '@/assets/ico/vendor/docker.svg';
|
||||
import podman from '@/assets/ico/vendor/podman.svg';
|
||||
|
||||
import { Icon } from '@@/Icon';
|
||||
|
||||
interface Props {
|
||||
type: EnvironmentType;
|
||||
containerEngine?: ContainerEngine;
|
||||
}
|
||||
|
||||
export function EnvironmentIcon({ type }: Props) {
|
||||
export function EnvironmentIcon({ type, containerEngine }: Props) {
|
||||
switch (type) {
|
||||
case EnvironmentType.AgentOnDocker:
|
||||
case EnvironmentType.Docker:
|
||||
if (containerEngine === ContainerEngine.Podman) {
|
||||
return (
|
||||
<img
|
||||
src={podman}
|
||||
width="60"
|
||||
alt="podman environment"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<img src={docker} width="60" alt="docker endpoint" aria-hidden="true" />
|
||||
<img
|
||||
src={docker}
|
||||
width="60"
|
||||
alt="docker environment"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
);
|
||||
case EnvironmentType.Azure:
|
||||
return (
|
||||
<img src={azure} width="60" alt="azure endpoint" aria-hidden="true" />
|
||||
<img
|
||||
src={azure}
|
||||
width="60"
|
||||
alt="azure environment"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
);
|
||||
case EnvironmentType.EdgeAgentOnDocker:
|
||||
if (containerEngine === ContainerEngine.Podman) {
|
||||
return (
|
||||
<img
|
||||
src={podmanEdge}
|
||||
alt="podman edge environment"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<img src={dockerEdge} alt="docker edge endpoint" aria-hidden="true" />
|
||||
<img
|
||||
src={dockerEdge}
|
||||
alt="docker edge environment"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
);
|
||||
case EnvironmentType.KubernetesLocal:
|
||||
case EnvironmentType.AgentOnKubernetes:
|
||||
return <img src={kube} alt="kubernetes endpoint" aria-hidden="true" />;
|
||||
return <img src={kube} alt="kubernetes environment" aria-hidden="true" />;
|
||||
case EnvironmentType.EdgeAgentOnKubernetes:
|
||||
return (
|
||||
<img src={kubeEdge} alt="kubernetes edge endpoint" aria-hidden="true" />
|
||||
<img
|
||||
src={kubeEdge}
|
||||
alt="kubernetes edge environment"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<Icon
|
||||
icon={environmentTypeIcon(type)}
|
||||
icon={getEnvironmentTypeIcon(type, containerEngine)}
|
||||
className="blue-icon !h-16 !w-16"
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -66,7 +66,10 @@ export function EnvironmentItem({
|
|||
params={dashboardRoute.params}
|
||||
>
|
||||
<div className="ml-2 flex justify-center self-center">
|
||||
<EnvironmentIcon type={environment.Type} />
|
||||
<EnvironmentIcon
|
||||
type={environment.Type}
|
||||
containerEngine={environment.ContainerEngine}
|
||||
/>
|
||||
</div>
|
||||
<div className="ml-3 mr-auto flex flex-col items-start justify-center gap-3">
|
||||
<div className="flex flex-wrap items-center gap-x-4 gap-y-2">
|
||||
|
|
|
@ -265,6 +265,12 @@ export function EnvironmentList({ onClickBrowse, onRefresh }: Props) {
|
|||
EnvironmentType.AgentOnDocker,
|
||||
EnvironmentType.EdgeAgentOnDocker,
|
||||
],
|
||||
// for podman keep the env type as docker (the containerEngine distinguishes podman from docker)
|
||||
[PlatformType.Podman]: [
|
||||
EnvironmentType.Docker,
|
||||
EnvironmentType.AgentOnDocker,
|
||||
EnvironmentType.EdgeAgentOnDocker,
|
||||
],
|
||||
[PlatformType.Azure]: [EnvironmentType.Azure],
|
||||
[PlatformType.Kubernetes]: [
|
||||
EnvironmentType.KubernetesLocal,
|
||||
|
|
|
@ -171,6 +171,13 @@ function getConnectionTypeOptions(platformTypes: PlatformType[]) {
|
|||
ConnectionType.EdgeAgentStandard,
|
||||
ConnectionType.EdgeAgentAsync,
|
||||
],
|
||||
[PlatformType.Podman]: [
|
||||
// api includes a socket connection, so keep this for podman
|
||||
ConnectionType.API,
|
||||
ConnectionType.Agent,
|
||||
ConnectionType.EdgeAgentStandard,
|
||||
ConnectionType.EdgeAgentAsync,
|
||||
],
|
||||
[PlatformType.Azure]: [ConnectionType.API],
|
||||
[PlatformType.Kubernetes]: [
|
||||
ConnectionType.Agent,
|
||||
|
|
|
@ -5,7 +5,7 @@ import { Select } from '@@/form-components/Input';
|
|||
|
||||
const typeOptions = [
|
||||
{ label: 'Swarm', value: StackType.DockerSwarm },
|
||||
{ label: 'Standalone', value: StackType.DockerCompose },
|
||||
{ label: 'Standalone / Podman', value: StackType.DockerCompose },
|
||||
];
|
||||
|
||||
export function TemplateTypeSelector({
|
||||
|
|
|
@ -1,28 +1,41 @@
|
|||
import { CellContext } from '@tanstack/react-table';
|
||||
|
||||
import { environmentTypeIcon } from '@/portainer/filters/filters';
|
||||
import {
|
||||
Environment,
|
||||
EnvironmentType,
|
||||
} from '@/react/portainer/environments/types';
|
||||
import { getPlatformTypeName } from '@/react/portainer/environments/utils';
|
||||
getEnvironmentTypeIcon,
|
||||
getPlatformTypeName,
|
||||
} from '@/react/portainer/environments/utils';
|
||||
|
||||
import { Icon } from '@@/Icon';
|
||||
|
||||
import { EnvironmentListItem } from '../types';
|
||||
import { EnvironmentType, ContainerEngine } from '../../types';
|
||||
|
||||
import { columnHelper } from './helper';
|
||||
|
||||
export const type = columnHelper.accessor('Type', {
|
||||
header: 'Type',
|
||||
cell: Cell,
|
||||
});
|
||||
type TypeCellContext = {
|
||||
type: EnvironmentType;
|
||||
containerEngine?: ContainerEngine;
|
||||
};
|
||||
|
||||
function Cell({ getValue }: CellContext<Environment, EnvironmentType>) {
|
||||
const type = getValue();
|
||||
export const type = columnHelper.accessor(
|
||||
(rowItem): TypeCellContext => ({
|
||||
type: rowItem.Type,
|
||||
containerEngine: rowItem.ContainerEngine,
|
||||
}),
|
||||
{
|
||||
header: 'Type',
|
||||
cell: Cell,
|
||||
id: 'Type',
|
||||
}
|
||||
);
|
||||
|
||||
function Cell({ getValue }: CellContext<EnvironmentListItem, TypeCellContext>) {
|
||||
const { type, containerEngine } = getValue();
|
||||
|
||||
return (
|
||||
<span className="flex items-center gap-1">
|
||||
<Icon icon={environmentTypeIcon(type)} />
|
||||
{getPlatformTypeName(type)}
|
||||
<Icon icon={getEnvironmentTypeIcon(type, containerEngine)} />
|
||||
{getPlatformTypeName(type, containerEngine)}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -7,7 +7,11 @@ import { type EnvironmentGroupId } from '@/react/portainer/environments/environm
|
|||
import { type TagId } from '@/portainer/tags/types';
|
||||
import { EdgeAsyncIntervalsValues } from '@/react/edge/components/EdgeAsyncIntervalsForm';
|
||||
|
||||
import { type Environment, EnvironmentCreationTypes } from '../types';
|
||||
import {
|
||||
type Environment,
|
||||
ContainerEngine,
|
||||
EnvironmentCreationTypes,
|
||||
} from '../types';
|
||||
|
||||
import { buildUrl } from './utils';
|
||||
|
||||
|
@ -21,6 +25,7 @@ interface CreateLocalDockerEnvironment {
|
|||
socketPath?: string;
|
||||
publicUrl?: string;
|
||||
meta?: EnvironmentMetadata;
|
||||
containerEngine?: ContainerEngine;
|
||||
}
|
||||
|
||||
export async function createLocalDockerEnvironment({
|
||||
|
@ -28,6 +33,7 @@ export async function createLocalDockerEnvironment({
|
|||
socketPath = '',
|
||||
publicUrl = '',
|
||||
meta = { tagIds: [] },
|
||||
containerEngine,
|
||||
}: CreateLocalDockerEnvironment) {
|
||||
const url = prefixPath(socketPath);
|
||||
|
||||
|
@ -38,6 +44,7 @@ export async function createLocalDockerEnvironment({
|
|||
url,
|
||||
publicUrl,
|
||||
meta,
|
||||
containerEngine,
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -115,6 +122,7 @@ export interface EnvironmentOptions {
|
|||
pollFrequency?: number;
|
||||
edge?: EdgeSettings;
|
||||
tunnelServerAddr?: string;
|
||||
containerEngine?: ContainerEngine;
|
||||
}
|
||||
|
||||
interface CreateRemoteEnvironment {
|
||||
|
@ -125,6 +133,7 @@ interface CreateRemoteEnvironment {
|
|||
>;
|
||||
url: string;
|
||||
options?: Omit<EnvironmentOptions, 'url'>;
|
||||
containerEngine?: ContainerEngine;
|
||||
}
|
||||
|
||||
export async function createRemoteEnvironment({
|
||||
|
@ -143,11 +152,13 @@ export interface CreateAgentEnvironmentValues {
|
|||
name: string;
|
||||
environmentUrl: string;
|
||||
meta: EnvironmentMetadata;
|
||||
containerEngine?: ContainerEngine;
|
||||
}
|
||||
|
||||
export function createAgentEnvironment({
|
||||
name,
|
||||
environmentUrl,
|
||||
containerEngine = ContainerEngine.Docker,
|
||||
meta = { tagIds: [] },
|
||||
}: CreateAgentEnvironmentValues) {
|
||||
return createRemoteEnvironment({
|
||||
|
@ -160,6 +171,7 @@ export function createAgentEnvironment({
|
|||
skipVerify: true,
|
||||
skipClientVerify: true,
|
||||
},
|
||||
containerEngine,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
@ -171,6 +183,7 @@ interface CreateEdgeAgentEnvironment {
|
|||
meta?: EnvironmentMetadata;
|
||||
pollFrequency: number;
|
||||
edge: EdgeSettings;
|
||||
containerEngine: ContainerEngine;
|
||||
}
|
||||
|
||||
export function createEdgeAgentEnvironment({
|
||||
|
@ -179,6 +192,7 @@ export function createEdgeAgentEnvironment({
|
|||
meta = { tagIds: [] },
|
||||
pollFrequency,
|
||||
edge,
|
||||
containerEngine,
|
||||
}: CreateEdgeAgentEnvironment) {
|
||||
return createEnvironment(
|
||||
name,
|
||||
|
@ -192,6 +206,7 @@ export function createEdgeAgentEnvironment({
|
|||
pollFrequency,
|
||||
edge,
|
||||
meta,
|
||||
containerEngine,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
@ -207,7 +222,8 @@ async function createEnvironment(
|
|||
};
|
||||
|
||||
if (options) {
|
||||
const { groupId, tagIds = [] } = options.meta || {};
|
||||
const { tls, azure, meta, containerEngine } = options;
|
||||
const { groupId, tagIds = [] } = meta || {};
|
||||
|
||||
payload = {
|
||||
...payload,
|
||||
|
@ -216,10 +232,9 @@ async function createEnvironment(
|
|||
GroupID: groupId,
|
||||
TagIds: arrayToJson(tagIds),
|
||||
EdgeCheckinInterval: options.pollFrequency,
|
||||
ContainerEngine: containerEngine,
|
||||
};
|
||||
|
||||
const { tls, azure } = options;
|
||||
|
||||
if (tls) {
|
||||
payload = {
|
||||
...payload,
|
||||
|
|
15
app/react/portainer/environments/queries/useIsPodman.ts
Normal file
15
app/react/portainer/environments/queries/useIsPodman.ts
Normal file
|
@ -0,0 +1,15 @@
|
|||
import { ContainerEngine, EnvironmentId } from '../types';
|
||||
|
||||
import { useEnvironment } from './useEnvironment';
|
||||
|
||||
/**
|
||||
* useIsPodman returns true if the current environment is using podman as container engine.
|
||||
* @returns isPodman boolean, can also be undefined if the environment hasn't loaded yet.
|
||||
*/
|
||||
export function useIsPodman(envId: EnvironmentId) {
|
||||
const { data: isPodman } = useEnvironment(
|
||||
envId,
|
||||
(env) => env.ContainerEngine === ContainerEngine.Podman
|
||||
);
|
||||
return isPodman;
|
||||
}
|
|
@ -4,6 +4,9 @@ import { DockerSnapshot } from '@/react/docker/snapshots/types';
|
|||
|
||||
export type EnvironmentId = number;
|
||||
|
||||
/**
|
||||
* matches portainer.EndpointType in app/portainer.go
|
||||
*/
|
||||
export enum EnvironmentType {
|
||||
// Docker represents an environment(endpoint) connected to a Docker environment(endpoint)
|
||||
Docker = 1,
|
||||
|
@ -124,6 +127,7 @@ export type Environment = {
|
|||
Agent: { Version: string };
|
||||
Id: EnvironmentId;
|
||||
Type: EnvironmentType;
|
||||
ContainerEngine?: ContainerEngine;
|
||||
TagIds: TagId[];
|
||||
GroupId: EnvironmentGroupId;
|
||||
DeploymentOptions: DeploymentOptions | null;
|
||||
|
@ -168,8 +172,14 @@ export enum EnvironmentCreationTypes {
|
|||
KubeConfigEnvironment,
|
||||
}
|
||||
|
||||
export enum ContainerEngine {
|
||||
Docker = 'docker',
|
||||
Podman = 'podman',
|
||||
}
|
||||
|
||||
export enum PlatformType {
|
||||
Docker,
|
||||
Kubernetes,
|
||||
Azure,
|
||||
Podman,
|
||||
}
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
import { getPlatformType } from '@/react/portainer/environments/utils';
|
||||
import {
|
||||
ContainerEngine,
|
||||
EnvironmentType,
|
||||
PlatformType,
|
||||
} from '@/react/portainer/environments/types';
|
||||
import Podman from '@/assets/ico/vendor/podman.svg?c';
|
||||
|
||||
import Docker from './docker.svg?c';
|
||||
import Azure from './azure.svg?c';
|
||||
|
@ -12,12 +14,16 @@ const icons: {
|
|||
[key in PlatformType]: SvgrComponent;
|
||||
} = {
|
||||
[PlatformType.Docker]: Docker,
|
||||
[PlatformType.Podman]: Podman,
|
||||
[PlatformType.Kubernetes]: Kubernetes,
|
||||
[PlatformType.Azure]: Azure,
|
||||
};
|
||||
|
||||
export function getPlatformIcon(type: EnvironmentType) {
|
||||
const platform = getPlatformType(type);
|
||||
export function getPlatformIcon(
|
||||
type: EnvironmentType,
|
||||
containerEngine?: ContainerEngine
|
||||
) {
|
||||
const platform = getPlatformType(type, containerEngine);
|
||||
|
||||
return icons[platform];
|
||||
}
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
export function getDockerEnvironmentType(isSwarm: boolean, isPodman?: boolean) {
|
||||
if (isPodman) {
|
||||
return 'Podman';
|
||||
}
|
||||
return isSwarm ? 'Swarm' : 'Standalone';
|
||||
}
|
|
@ -1,6 +1,21 @@
|
|||
import { Environment, EnvironmentType, PlatformType } from '../types';
|
||||
import { Cloud } from 'lucide-react';
|
||||
|
||||
export function getPlatformType(envType: EnvironmentType) {
|
||||
import Kube from '@/assets/ico/kube.svg?c';
|
||||
import PodmanIcon from '@/assets/ico/vendor/podman-icon.svg?c';
|
||||
import DockerIcon from '@/assets/ico/vendor/docker-icon.svg?c';
|
||||
import MicrosoftIcon from '@/assets/ico/vendor/microsoft-icon.svg?c';
|
||||
|
||||
import {
|
||||
Environment,
|
||||
EnvironmentType,
|
||||
ContainerEngine,
|
||||
PlatformType,
|
||||
} from '../types';
|
||||
|
||||
export function getPlatformType(
|
||||
envType: EnvironmentType,
|
||||
containerEngine?: ContainerEngine
|
||||
) {
|
||||
switch (envType) {
|
||||
case EnvironmentType.KubernetesLocal:
|
||||
case EnvironmentType.AgentOnKubernetes:
|
||||
|
@ -9,6 +24,9 @@ export function getPlatformType(envType: EnvironmentType) {
|
|||
case EnvironmentType.Docker:
|
||||
case EnvironmentType.AgentOnDocker:
|
||||
case EnvironmentType.EdgeAgentOnDocker:
|
||||
if (containerEngine === ContainerEngine.Podman) {
|
||||
return PlatformType.Podman;
|
||||
}
|
||||
return PlatformType.Docker;
|
||||
case EnvironmentType.Azure:
|
||||
return PlatformType.Azure;
|
||||
|
@ -25,8 +43,11 @@ export function isKubernetesEnvironment(envType: EnvironmentType) {
|
|||
return getPlatformType(envType) === PlatformType.Kubernetes;
|
||||
}
|
||||
|
||||
export function getPlatformTypeName(envType: EnvironmentType): string {
|
||||
return PlatformType[getPlatformType(envType)];
|
||||
export function getPlatformTypeName(
|
||||
envType: EnvironmentType,
|
||||
containerEngine?: ContainerEngine
|
||||
): string {
|
||||
return PlatformType[getPlatformType(envType, containerEngine)];
|
||||
}
|
||||
|
||||
export function isAgentEnvironment(envType: EnvironmentType) {
|
||||
|
@ -104,3 +125,27 @@ export function getDashboardRoute(environment: Environment) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function getEnvironmentTypeIcon(
|
||||
type: EnvironmentType,
|
||||
containerEngine?: ContainerEngine
|
||||
) {
|
||||
switch (type) {
|
||||
case EnvironmentType.Azure:
|
||||
return MicrosoftIcon;
|
||||
case EnvironmentType.EdgeAgentOnDocker:
|
||||
return Cloud;
|
||||
case EnvironmentType.AgentOnKubernetes:
|
||||
case EnvironmentType.EdgeAgentOnKubernetes:
|
||||
case EnvironmentType.KubernetesLocal:
|
||||
return Kube;
|
||||
case EnvironmentType.AgentOnDocker:
|
||||
case EnvironmentType.Docker:
|
||||
if (containerEngine === ContainerEngine.Podman) {
|
||||
return PodmanIcon;
|
||||
}
|
||||
return DockerIcon;
|
||||
default:
|
||||
throw new Error(`type ${type}-${EnvironmentType[type]} is not supported`);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ import {
|
|||
EnvironmentOptionValue,
|
||||
existingEnvironmentTypes,
|
||||
newEnvironmentTypes,
|
||||
environmentTypes,
|
||||
} from './environment-types';
|
||||
|
||||
export function EnvironmentTypeSelectView() {
|
||||
|
@ -65,6 +66,7 @@ export function EnvironmentTypeSelectView() {
|
|||
disabled={types.length === 0}
|
||||
data-cy="start-wizard-button"
|
||||
onClick={() => startWizard()}
|
||||
className="!ml-0"
|
||||
>
|
||||
Start Wizard
|
||||
</Button>
|
||||
|
@ -80,11 +82,6 @@ export function EnvironmentTypeSelectView() {
|
|||
return;
|
||||
}
|
||||
|
||||
const environmentTypes = [
|
||||
...existingEnvironmentTypes,
|
||||
...newEnvironmentTypes,
|
||||
];
|
||||
|
||||
const steps = _.compact(
|
||||
types.map((id) => environmentTypes.find((eType) => eType.id === id))
|
||||
);
|
|
@ -1,5 +1,6 @@
|
|||
import { FeatureId } from '@/react/portainer/feature-flags/enums';
|
||||
import Docker from '@/assets/ico/vendor/docker.svg?c';
|
||||
import Podman from '@/assets/ico/vendor/podman.svg?c';
|
||||
import Kubernetes from '@/assets/ico/vendor/kubernetes.svg?c';
|
||||
import Azure from '@/assets/ico/vendor/azure.svg?c';
|
||||
import KaaS from '@/assets/ico/vendor/kaas-icon.svg?c';
|
||||
|
@ -10,6 +11,7 @@ import { BoxSelectorOption } from '@@/BoxSelector';
|
|||
export type EnvironmentOptionValue =
|
||||
| 'dockerStandalone'
|
||||
| 'dockerSwarm'
|
||||
| 'podman'
|
||||
| 'kubernetes'
|
||||
| 'aci'
|
||||
| 'kaas'
|
||||
|
@ -20,7 +22,6 @@ export interface EnvironmentOption
|
|||
id: EnvironmentOptionValue;
|
||||
value: EnvironmentOptionValue;
|
||||
}
|
||||
|
||||
export const existingEnvironmentTypes: EnvironmentOption[] = [
|
||||
{
|
||||
id: 'dockerStandalone',
|
||||
|
@ -38,6 +39,14 @@ export const existingEnvironmentTypes: EnvironmentOption[] = [
|
|||
iconType: 'logo',
|
||||
description: 'Connect to Docker Swarm via URL/IP, API or Socket',
|
||||
},
|
||||
{
|
||||
id: 'podman',
|
||||
value: 'podman',
|
||||
label: 'Podman',
|
||||
icon: Podman,
|
||||
iconType: 'logo',
|
||||
description: 'Connect to Podman via URL/IP or Socket',
|
||||
},
|
||||
{
|
||||
id: 'kubernetes',
|
||||
value: 'kubernetes',
|
||||
|
@ -80,7 +89,7 @@ export const newEnvironmentTypes: EnvironmentOption[] = [
|
|||
},
|
||||
];
|
||||
|
||||
export const environmentTypes = [
|
||||
export const environmentTypes: EnvironmentOption[] = [
|
||||
...existingEnvironmentTypes,
|
||||
...newEnvironmentTypes,
|
||||
];
|
||||
|
@ -88,6 +97,7 @@ export const environmentTypes = [
|
|||
export const formTitles: Record<EnvironmentOptionValue, string> = {
|
||||
dockerStandalone: 'Connect to your Docker Standalone environment',
|
||||
dockerSwarm: 'Connect to your Docker Swarm environment',
|
||||
podman: 'Connect to your Podman environment',
|
||||
kubernetes: 'Connect to your Kubernetes environment',
|
||||
aci: 'Connect to your ACI environment',
|
||||
kaas: 'Provision a KaaS environment',
|
||||
|
|
|
@ -1 +1 @@
|
|||
export { EnvironmentTypeSelectView } from './EndpointTypeView';
|
||||
export { EnvironmentTypeSelectView } from './EnvironmentTypeSelectView';
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
.wizard-wrapper {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 400px;
|
||||
grid-template-columns: 2fr minmax(300px, 1fr);
|
||||
grid-template-areas:
|
||||
'main sidebar'
|
||||
'footer sidebar';
|
||||
|
|
|
@ -22,6 +22,7 @@ import {
|
|||
EnvironmentOptionValue,
|
||||
environmentTypes,
|
||||
formTitles,
|
||||
EnvironmentOption,
|
||||
} from '../EnvironmentTypeSelectView/environment-types';
|
||||
|
||||
import { WizardDocker } from './WizardDocker';
|
||||
|
@ -30,6 +31,7 @@ import { WizardKubernetes } from './WizardKubernetes';
|
|||
import { AnalyticsState, AnalyticsStateKey } from './types';
|
||||
import styles from './EnvironmentsCreationView.module.css';
|
||||
import { WizardEndpointsList } from './WizardEndpointsList';
|
||||
import { WizardPodman } from './WizardPodman';
|
||||
|
||||
export function EnvironmentCreationView() {
|
||||
const {
|
||||
|
@ -161,7 +163,7 @@ function useParamEnvironmentTypes(): EnvironmentOptionValue[] {
|
|||
}
|
||||
|
||||
function useStepper(
|
||||
steps: (typeof environmentTypes)[number][],
|
||||
steps: EnvironmentOption[][number][],
|
||||
onFinish: () => void
|
||||
) {
|
||||
const [currentStepIndex, setCurrentStepIndex] = useState(0);
|
||||
|
@ -197,6 +199,8 @@ function useStepper(
|
|||
case 'dockerStandalone':
|
||||
case 'dockerSwarm':
|
||||
return WizardDocker;
|
||||
case 'podman':
|
||||
return WizardPodman;
|
||||
case 'aci':
|
||||
return WizardAzure;
|
||||
case 'kubernetes':
|
||||
|
@ -211,14 +215,18 @@ function useAnalyticsState() {
|
|||
const [analytics, setAnalyticsState] = useState<AnalyticsState>({
|
||||
dockerAgent: 0,
|
||||
dockerApi: 0,
|
||||
dockerEdgeAgentAsync: 0,
|
||||
dockerEdgeAgentStandard: 0,
|
||||
podmanAgent: 0,
|
||||
podmanEdgeAgentAsync: 0,
|
||||
podmanEdgeAgentStandard: 0,
|
||||
podmanLocalEnvironment: 0,
|
||||
kubernetesAgent: 0,
|
||||
kubernetesEdgeAgentAsync: 0,
|
||||
kubernetesEdgeAgentStandard: 0,
|
||||
kaasAgent: 0,
|
||||
aciApi: 0,
|
||||
localEndpoint: 0,
|
||||
dockerEdgeAgentAsync: 0,
|
||||
dockerEdgeAgentStandard: 0,
|
||||
});
|
||||
|
||||
return { analytics, setAnalytics };
|
||||
|
|
|
@ -4,6 +4,7 @@ import { CopyButton } from '@@/buttons/CopyButton';
|
|||
import { Code } from '@@/Code';
|
||||
import { NavTabs } from '@@/NavTabs';
|
||||
import { NavContainer } from '@@/NavTabs/NavContainer';
|
||||
import { TextTip } from '@@/Tip/TextTip';
|
||||
|
||||
const deployments = [
|
||||
{
|
||||
|
@ -45,10 +46,10 @@ interface DeployCodeProps {
|
|||
function DeployCode({ code }: DeployCodeProps) {
|
||||
return (
|
||||
<>
|
||||
<span className="text-muted small">
|
||||
<TextTip color="blue" className="mb-1">
|
||||
When using the socket, ensure that you have started the Portainer
|
||||
container with the following Docker flag:
|
||||
</span>
|
||||
</TextTip>
|
||||
|
||||
<Code>{code}</Code>
|
||||
<div className="mt-2">
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
import { Environment } from '@/react/portainer/environments/types';
|
||||
import {
|
||||
ContainerEngine,
|
||||
Environment,
|
||||
} from '@/react/portainer/environments/types';
|
||||
|
||||
import { AgentForm } from '../../shared/AgentForm/AgentForm';
|
||||
|
||||
|
@ -15,7 +18,10 @@ export function AgentTab({ onCreate, isDockerStandalone }: Props) {
|
|||
<DeploymentScripts isDockerStandalone={isDockerStandalone} />
|
||||
|
||||
<div className="mt-5">
|
||||
<AgentForm onCreate={onCreate} />
|
||||
<AgentForm
|
||||
onCreate={onCreate}
|
||||
containerEngine={ContainerEngine.Docker}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -4,7 +4,10 @@ import { Plug2 } from 'lucide-react';
|
|||
|
||||
import { useCreateLocalDockerEnvironmentMutation } from '@/react/portainer/environments/queries/useCreateEnvironmentMutation';
|
||||
import { notifySuccess } from '@/portainer/services/notifications';
|
||||
import { Environment } from '@/react/portainer/environments/types';
|
||||
import {
|
||||
ContainerEngine,
|
||||
Environment,
|
||||
} from '@/react/portainer/environments/types';
|
||||
|
||||
import { LoadingButton } from '@@/buttons/LoadingButton';
|
||||
import { FormControl } from '@@/form-components/FormControl';
|
||||
|
@ -19,9 +22,10 @@ import { FormValues } from './types';
|
|||
|
||||
interface Props {
|
||||
onCreate(environment: Environment): void;
|
||||
containerEngine: ContainerEngine;
|
||||
}
|
||||
|
||||
export function SocketForm({ onCreate }: Props) {
|
||||
export function SocketForm({ onCreate, containerEngine }: Props) {
|
||||
const [formKey, clearForm] = useReducer((state) => state + 1, 0);
|
||||
const initialValues: FormValues = {
|
||||
name: '',
|
||||
|
@ -74,6 +78,7 @@ export function SocketForm({ onCreate }: Props) {
|
|||
name: values.name,
|
||||
socketPath: values.overridePath ? values.socketPath : '',
|
||||
meta: values.meta,
|
||||
containerEngine,
|
||||
},
|
||||
{
|
||||
onSuccess(environment) {
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
import { Environment } from '@/react/portainer/environments/types';
|
||||
import {
|
||||
ContainerEngine,
|
||||
Environment,
|
||||
} from '@/react/portainer/environments/types';
|
||||
|
||||
import { DeploymentScripts } from '../APITab/DeploymentScripts';
|
||||
|
||||
|
@ -14,7 +17,10 @@ export function SocketTab({ onCreate }: Props) {
|
|||
<DeploymentScripts />
|
||||
|
||||
<div className="mt-5">
|
||||
<SocketForm onCreate={onCreate} />
|
||||
<SocketForm
|
||||
onCreate={onCreate}
|
||||
containerEngine={ContainerEngine.Docker}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -2,7 +2,10 @@ import { useState } from 'react';
|
|||
import { Zap, Network, Plug2 } from 'lucide-react';
|
||||
import _ from 'lodash';
|
||||
|
||||
import { Environment } from '@/react/portainer/environments/types';
|
||||
import {
|
||||
ContainerEngine,
|
||||
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';
|
||||
|
@ -64,6 +67,8 @@ const options: BoxSelectorOption<
|
|||
},
|
||||
]);
|
||||
|
||||
const containerEngine = ContainerEngine.Docker;
|
||||
|
||||
export function WizardDocker({ onCreate, isDockerStandalone }: Props) {
|
||||
const [creationType, setCreationType] = useState(options[0].value);
|
||||
|
||||
|
@ -135,6 +140,7 @@ export function WizardDocker({ onCreate, isDockerStandalone }: Props) {
|
|||
? [commandsTabs.standaloneWindow]
|
||||
: [commandsTabs.swarmWindows],
|
||||
}}
|
||||
containerEngine={containerEngine}
|
||||
/>
|
||||
);
|
||||
case 'edgeAgentAsync':
|
||||
|
@ -152,6 +158,7 @@ export function WizardDocker({ onCreate, isDockerStandalone }: Props) {
|
|||
? [commandsTabs.standaloneWindow]
|
||||
: [commandsTabs.swarmWindows],
|
||||
}}
|
||||
containerEngine={containerEngine}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
|
|
|
@ -22,8 +22,6 @@
|
|||
|
||||
.wizard-list-image {
|
||||
grid-area: image;
|
||||
font-size: 35px;
|
||||
color: #337ab7;
|
||||
}
|
||||
|
||||
.wizard-list-title {
|
||||
|
|
|
@ -1,15 +1,13 @@
|
|||
import { Plug2 } from 'lucide-react';
|
||||
import clsx from 'clsx';
|
||||
|
||||
import { endpointTypeName, stripProtocol } from '@/portainer/filters/filters';
|
||||
import {
|
||||
environmentTypeIcon,
|
||||
endpointTypeName,
|
||||
stripProtocol,
|
||||
} from '@/portainer/filters/filters';
|
||||
import { EnvironmentId } from '@/react/portainer/environments/types';
|
||||
import {
|
||||
getEnvironmentTypeIcon,
|
||||
isEdgeEnvironment,
|
||||
isUnassociatedEdgeEnvironment,
|
||||
} from '@/react/portainer/environments/utils';
|
||||
import { EnvironmentId } from '@/react/portainer/environments/types';
|
||||
import {
|
||||
ENVIRONMENTS_POLLING_INTERVAL,
|
||||
useEnvironmentList,
|
||||
|
@ -51,9 +49,17 @@ export function WizardEndpointsList({ environmentIds }: Props) {
|
|||
<WidgetBody>
|
||||
{environments.map((environment) => (
|
||||
<div className={styles.wizardListWrapper} key={environment.Id}>
|
||||
<div className={styles.wizardListImage}>
|
||||
<div
|
||||
className={clsx(
|
||||
styles.wizardListImage,
|
||||
'text-blue-8 th-dark:text-blue-7 th-highcontrast:text-white text-5xl'
|
||||
)}
|
||||
>
|
||||
<Icon
|
||||
icon={environmentTypeIcon(environment.Type)}
|
||||
icon={getEnvironmentTypeIcon(
|
||||
environment.Type,
|
||||
environment.ContainerEngine
|
||||
)}
|
||||
className="mr-1"
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
import {
|
||||
ContainerEngine,
|
||||
Environment,
|
||||
} from '@/react/portainer/environments/types';
|
||||
|
||||
import { AgentForm } from '../../shared/AgentForm/AgentForm';
|
||||
|
||||
import { DeploymentScripts } from './DeploymentScripts';
|
||||
|
||||
interface Props {
|
||||
onCreate(environment: Environment): void;
|
||||
}
|
||||
|
||||
export function AgentTab({ onCreate }: Props) {
|
||||
return (
|
||||
<>
|
||||
<DeploymentScripts />
|
||||
|
||||
<div className="mt-5">
|
||||
<AgentForm
|
||||
onCreate={onCreate}
|
||||
containerEngine={ContainerEngine.Podman}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
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';
|
||||
import { NavContainer } from '@@/NavTabs/NavContainer';
|
||||
|
||||
const deploymentPodman = [
|
||||
{
|
||||
id: 'all',
|
||||
label: 'Linux (CentOS)',
|
||||
command: linuxPodmanCommandRootful,
|
||||
},
|
||||
];
|
||||
|
||||
export function DeploymentScripts() {
|
||||
const deployments = deploymentPodman;
|
||||
const [deployType, setDeployType] = useState(deployments[0].id);
|
||||
|
||||
const agentDetailsQuery = useAgentDetails();
|
||||
|
||||
if (!agentDetailsQuery) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { agentVersion, agentSecret } = agentDetailsQuery;
|
||||
|
||||
const options = deployments.map((c) => {
|
||||
const code = c.command(agentVersion, agentSecret);
|
||||
|
||||
return {
|
||||
id: c.id,
|
||||
label: c.label,
|
||||
children: <DeployCode code={code} />,
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<NavContainer>
|
||||
<NavTabs
|
||||
options={options}
|
||||
onSelect={(id: string) => setDeployType(id)}
|
||||
selectedId={deployType}
|
||||
/>
|
||||
</NavContainer>
|
||||
);
|
||||
}
|
||||
|
||||
interface DeployCodeProps {
|
||||
code: string;
|
||||
}
|
||||
|
||||
function DeployCode({ code }: DeployCodeProps) {
|
||||
return (
|
||||
<>
|
||||
<div className="code-script">
|
||||
<Code>{code}</Code>
|
||||
</div>
|
||||
<div className="mt-2">
|
||||
<CopyButton copyText={code} data-cy="copy-deployment-script">
|
||||
Copy command
|
||||
</CopyButton>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function linuxPodmanCommandRootful(agentVersion: string, agentSecret: string) {
|
||||
const secret =
|
||||
agentSecret === '' ? '' : `\\\n -e AGENT_SECRET=${agentSecret} `;
|
||||
|
||||
return `sudo systemctl enable --now podman.socket\n
|
||||
sudo podman volume create portainer\n
|
||||
sudo podman run -d \\
|
||||
-p 9001:9001 ${secret}\\
|
||||
--name portainer_agent \\
|
||||
--restart=always \\
|
||||
--privileged \\
|
||||
-v /run/podman/podman.sock:/var/run/docker.sock \\
|
||||
-v /var/lib/containers/storage/volumes:/var/lib/docker/volumes \\
|
||||
-v /:/host \\
|
||||
portainer/agent:${agentVersion}
|
||||
`;
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
export { AgentTab } from './AgentTab';
|
|
@ -0,0 +1,68 @@
|
|||
import { useState } from 'react';
|
||||
|
||||
import { CopyButton } from '@@/buttons/CopyButton';
|
||||
import { Code } from '@@/Code';
|
||||
import { NavTabs } from '@@/NavTabs';
|
||||
import { NavContainer } from '@@/NavTabs/NavContainer';
|
||||
import { TextTip } from '@@/Tip/TextTip';
|
||||
|
||||
const deployments = [
|
||||
{
|
||||
id: 'linux',
|
||||
label: 'Linux (CentOS)',
|
||||
command: `sudo systemctl enable --now podman.socket`,
|
||||
},
|
||||
];
|
||||
|
||||
export function DeploymentScripts() {
|
||||
const [deployType, setDeployType] = useState(deployments[0].id);
|
||||
|
||||
const options = deployments.map((c) => ({
|
||||
id: c.id,
|
||||
label: c.label,
|
||||
children: <DeployCode code={c.command} />,
|
||||
}));
|
||||
|
||||
return (
|
||||
<NavContainer>
|
||||
<NavTabs
|
||||
options={options}
|
||||
onSelect={(id: string) => setDeployType(id)}
|
||||
selectedId={deployType}
|
||||
/>
|
||||
</NavContainer>
|
||||
);
|
||||
}
|
||||
|
||||
interface DeployCodeProps {
|
||||
code: string;
|
||||
}
|
||||
|
||||
function DeployCode({ code }: DeployCodeProps) {
|
||||
const bindMountCode = `-v "/run/podman/podman.sock:/var/run/docker.sock"`;
|
||||
return (
|
||||
<>
|
||||
<TextTip color="blue" className="mb-1">
|
||||
When using the socket, ensure that you have started the Portainer
|
||||
container with the following Podman flag:
|
||||
</TextTip>
|
||||
<Code>{bindMountCode}</Code>
|
||||
<div className="mt-2 mb-4">
|
||||
<CopyButton copyText={bindMountCode} data-cy="copy-deployment-command">
|
||||
Copy command
|
||||
</CopyButton>
|
||||
</div>
|
||||
|
||||
<TextTip color="blue" className="mb-1">
|
||||
To use the socket, ensure that you have started the Podman rootful
|
||||
socket:
|
||||
</TextTip>
|
||||
<Code>{code}</Code>
|
||||
<div className="mt-2">
|
||||
<CopyButton copyText={code} data-cy="copy-deployment-command">
|
||||
Copy command
|
||||
</CopyButton>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,125 @@
|
|||
import { Field, Form, Formik, useFormikContext } from 'formik';
|
||||
import { useReducer } from 'react';
|
||||
import { Plug2 } from 'lucide-react';
|
||||
|
||||
import { notifySuccess } from '@/portainer/services/notifications';
|
||||
import { useCreateLocalDockerEnvironmentMutation } from '@/react/portainer/environments/queries/useCreateEnvironmentMutation';
|
||||
import {
|
||||
ContainerEngine,
|
||||
Environment,
|
||||
} from '@/react/portainer/environments/types';
|
||||
|
||||
import { LoadingButton } from '@@/buttons/LoadingButton';
|
||||
import { FormControl } from '@@/form-components/FormControl';
|
||||
import { Input } from '@@/form-components/Input';
|
||||
import { SwitchField } from '@@/form-components/SwitchField';
|
||||
|
||||
import { NameField } from '../../shared/NameField';
|
||||
import { MoreSettingsSection } from '../../shared/MoreSettingsSection';
|
||||
|
||||
import { useValidation } from './SocketForm.validation';
|
||||
import { FormValues } from './types';
|
||||
|
||||
interface Props {
|
||||
onCreate(environment: Environment): void;
|
||||
containerEngine: ContainerEngine;
|
||||
}
|
||||
|
||||
export function SocketForm({ onCreate, containerEngine }: Props) {
|
||||
const [formKey, clearForm] = useReducer((state) => state + 1, 0);
|
||||
const initialValues: FormValues = {
|
||||
name: '',
|
||||
socketPath: '',
|
||||
overridePath: false,
|
||||
meta: { groupId: 1, tagIds: [] },
|
||||
};
|
||||
|
||||
const mutation = useCreateLocalDockerEnvironmentMutation();
|
||||
const validation = useValidation();
|
||||
|
||||
return (
|
||||
<Formik
|
||||
initialValues={initialValues}
|
||||
onSubmit={handleSubmit}
|
||||
validationSchema={validation}
|
||||
validateOnMount
|
||||
key={formKey}
|
||||
>
|
||||
{({ isValid, dirty }) => (
|
||||
<Form>
|
||||
<NameField />
|
||||
|
||||
<OverrideSocketFieldset />
|
||||
|
||||
<MoreSettingsSection />
|
||||
|
||||
<div className="form-group">
|
||||
<div className="col-sm-12">
|
||||
<LoadingButton
|
||||
className="wizard-connect-button vertical-center"
|
||||
data-cy="docker-socket-connect-button"
|
||||
loadingText="Connecting environment..."
|
||||
isLoading={mutation.isLoading}
|
||||
disabled={!dirty || !isValid}
|
||||
icon={Plug2}
|
||||
>
|
||||
Connect
|
||||
</LoadingButton>
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
);
|
||||
|
||||
function handleSubmit(values: FormValues) {
|
||||
mutation.mutate(
|
||||
{
|
||||
name: values.name,
|
||||
socketPath: values.overridePath ? values.socketPath : '',
|
||||
meta: values.meta,
|
||||
containerEngine,
|
||||
},
|
||||
{
|
||||
onSuccess(environment) {
|
||||
notifySuccess('Environment created', environment.Name);
|
||||
clearForm();
|
||||
onCreate(environment);
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function OverrideSocketFieldset() {
|
||||
const { values, setFieldValue, errors } = useFormikContext<FormValues>();
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="form-group">
|
||||
<div className="col-sm-12">
|
||||
<SwitchField
|
||||
checked={values.overridePath}
|
||||
data-cy="create-docker-env-socket-override-switch"
|
||||
onChange={(checked) => setFieldValue('overridePath', checked)}
|
||||
label="Override default socket path"
|
||||
labelClass="col-sm-3 col-lg-2"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{values.overridePath && (
|
||||
<FormControl
|
||||
label="Socket Path"
|
||||
tooltip="Path to the Podman socket. Remember to bind-mount the socket, see the important notice above for more information."
|
||||
errors={errors.socketPath}
|
||||
>
|
||||
<Field
|
||||
name="socketPath"
|
||||
as={Input}
|
||||
placeholder="e.g. /run/podman/podman.sock (on Linux)"
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
import { boolean, object, SchemaOf, string } from 'yup';
|
||||
|
||||
import { metadataValidation } from '../../shared/MetadataFieldset/validation';
|
||||
import { useNameValidation } from '../../shared/NameField';
|
||||
|
||||
import { FormValues } from './types';
|
||||
|
||||
export function useValidation(): SchemaOf<FormValues> {
|
||||
return object({
|
||||
name: useNameValidation(),
|
||||
meta: metadataValidation(),
|
||||
overridePath: boolean().default(false),
|
||||
socketPath: string()
|
||||
.default('')
|
||||
.when('overridePath', (overridePath, schema) =>
|
||||
overridePath
|
||||
? schema.required(
|
||||
'Socket Path is required when override path is enabled'
|
||||
)
|
||||
: schema
|
||||
),
|
||||
});
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
import {
|
||||
ContainerEngine,
|
||||
Environment,
|
||||
} from '@/react/portainer/environments/types';
|
||||
|
||||
import { TextTip } from '@@/Tip/TextTip';
|
||||
|
||||
import { DeploymentScripts } from './DeploymentScripts';
|
||||
import { SocketForm } from './SocketForm';
|
||||
|
||||
interface Props {
|
||||
onCreate(environment: Environment): void;
|
||||
}
|
||||
|
||||
export function SocketTab({ onCreate }: Props) {
|
||||
return (
|
||||
<>
|
||||
<TextTip color="orange" className="mb-2" inline={false}>
|
||||
To connect via socket, Portainer server must be running in a Podman
|
||||
container.
|
||||
</TextTip>
|
||||
|
||||
<DeploymentScripts />
|
||||
|
||||
<div className="mt-5">
|
||||
<SocketForm
|
||||
onCreate={onCreate}
|
||||
containerEngine={ContainerEngine.Podman}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
export { SocketTab } from './SocketTab';
|
|
@ -0,0 +1,8 @@
|
|||
import { EnvironmentMetadata } from '@/react/portainer/environments/environment.service/create';
|
||||
|
||||
export interface FormValues {
|
||||
name: string;
|
||||
socketPath: string;
|
||||
overridePath: boolean;
|
||||
meta: EnvironmentMetadata;
|
||||
}
|
|
@ -0,0 +1,133 @@
|
|||
import { useState } from 'react';
|
||||
import { Zap, Plug2 } from 'lucide-react';
|
||||
import _ from 'lodash';
|
||||
|
||||
import {
|
||||
ContainerEngine,
|
||||
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';
|
||||
import { TextTip } from '@@/Tip/TextTip';
|
||||
|
||||
import { AnalyticsStateKey } from '../types';
|
||||
import { EdgeAgentTab } from '../shared/EdgeAgentTab';
|
||||
|
||||
import { AgentTab } from './AgentTab';
|
||||
import { SocketTab } from './SocketTab';
|
||||
|
||||
interface Props {
|
||||
onCreate(environment: Environment, analytics: AnalyticsStateKey): void;
|
||||
}
|
||||
|
||||
const options: BoxSelectorOption<
|
||||
'agent' | 'api' | 'socket' | 'edgeAgentStandard' | 'edgeAgentAsync'
|
||||
>[] = _.compact([
|
||||
{
|
||||
id: 'agent',
|
||||
icon: <BadgeIcon icon={Zap} size="3xl" />,
|
||||
label: 'Agent',
|
||||
description: '',
|
||||
value: 'agent',
|
||||
},
|
||||
{
|
||||
id: 'socket',
|
||||
icon: <BadgeIcon icon={Plug2} size="3xl" />,
|
||||
label: 'Socket',
|
||||
description: '',
|
||||
value: 'socket',
|
||||
},
|
||||
{
|
||||
id: 'edgeAgentStandard',
|
||||
icon: <BadgeIcon icon={EdgeAgentStandardIcon} size="3xl" />,
|
||||
label: 'Edge Agent Standard',
|
||||
description: '',
|
||||
value: 'edgeAgentStandard',
|
||||
},
|
||||
isBE && {
|
||||
id: 'edgeAgentAsync',
|
||||
icon: <BadgeIcon icon={EdgeAgentAsyncIcon} size="3xl" />,
|
||||
label: 'Edge Agent Async',
|
||||
description: '',
|
||||
value: 'edgeAgentAsync',
|
||||
},
|
||||
]);
|
||||
|
||||
const containerEngine = ContainerEngine.Podman;
|
||||
|
||||
export function WizardPodman({ onCreate }: Props) {
|
||||
const [creationType, setCreationType] = useState(options[0].value);
|
||||
|
||||
const tab = getTab(creationType);
|
||||
|
||||
return (
|
||||
<div className="form-horizontal">
|
||||
<BoxSelector
|
||||
onChange={(v) => setCreationType(v)}
|
||||
options={options}
|
||||
value={creationType}
|
||||
radioName="creation-type"
|
||||
/>
|
||||
<TextTip color="orange" className="mb-2" inline={false}>
|
||||
Currently, Portainer only supports <b>Podman 5</b> running in rootful
|
||||
(privileged) mode on <b>CentOS 9</b> Linux environments. Rootless mode
|
||||
and other Linux distros may work, but aren't officially supported.
|
||||
</TextTip>
|
||||
{tab}
|
||||
</div>
|
||||
);
|
||||
|
||||
function getTab(
|
||||
creationType:
|
||||
| 'agent'
|
||||
| 'api'
|
||||
| 'socket'
|
||||
| 'edgeAgentStandard'
|
||||
| 'edgeAgentAsync'
|
||||
) {
|
||||
switch (creationType) {
|
||||
case 'agent':
|
||||
return (
|
||||
<AgentTab
|
||||
onCreate={(environment) => onCreate(environment, 'podmanAgent')}
|
||||
/>
|
||||
);
|
||||
case 'socket':
|
||||
return (
|
||||
<SocketTab
|
||||
onCreate={(environment) =>
|
||||
onCreate(environment, 'podmanLocalEnvironment')
|
||||
}
|
||||
/>
|
||||
);
|
||||
case 'edgeAgentStandard':
|
||||
return (
|
||||
<EdgeAgentTab
|
||||
onCreate={(environment) =>
|
||||
onCreate(environment, 'podmanEdgeAgentStandard')
|
||||
}
|
||||
commands={[commandsTabs.podmanLinux]}
|
||||
containerEngine={containerEngine}
|
||||
/>
|
||||
);
|
||||
case 'edgeAgentAsync':
|
||||
return (
|
||||
<EdgeAgentTab
|
||||
asyncMode
|
||||
onCreate={(environment) =>
|
||||
onCreate(environment, 'podmanEdgeAgentAsync')
|
||||
}
|
||||
commands={[commandsTabs.podmanLinux]}
|
||||
containerEngine={containerEngine}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
export { WizardPodman } from './WizardPodman';
|
|
@ -4,7 +4,10 @@ import { Plug2 } from 'lucide-react';
|
|||
|
||||
import { useCreateAgentEnvironmentMutation } from '@/react/portainer/environments/queries/useCreateEnvironmentMutation';
|
||||
import { notifySuccess } from '@/portainer/services/notifications';
|
||||
import { Environment } from '@/react/portainer/environments/types';
|
||||
import {
|
||||
ContainerEngine,
|
||||
Environment,
|
||||
} from '@/react/portainer/environments/types';
|
||||
import { CreateAgentEnvironmentValues } from '@/react/portainer/environments/environment.service/create';
|
||||
|
||||
import { LoadingButton } from '@@/buttons/LoadingButton';
|
||||
|
@ -18,6 +21,7 @@ import { useValidation } from './AgentForm.validation';
|
|||
interface Props {
|
||||
onCreate(environment: Environment): void;
|
||||
envDefaultPort?: string;
|
||||
containerEngine?: ContainerEngine;
|
||||
}
|
||||
|
||||
const initialValues: CreateAgentEnvironmentValues = {
|
||||
|
@ -29,7 +33,11 @@ const initialValues: CreateAgentEnvironmentValues = {
|
|||
},
|
||||
};
|
||||
|
||||
export function AgentForm({ onCreate, envDefaultPort }: Props) {
|
||||
export function AgentForm({
|
||||
onCreate,
|
||||
envDefaultPort,
|
||||
containerEngine = ContainerEngine.Docker,
|
||||
}: Props) {
|
||||
const [formKey, clearForm] = useReducer((state) => state + 1, 0);
|
||||
|
||||
const mutation = useCreateAgentEnvironmentMutation();
|
||||
|
@ -70,12 +78,15 @@ export function AgentForm({ onCreate, envDefaultPort }: Props) {
|
|||
);
|
||||
|
||||
function handleSubmit(values: CreateAgentEnvironmentValues) {
|
||||
mutation.mutate(values, {
|
||||
onSuccess(environment) {
|
||||
notifySuccess('Environment created', environment.Name);
|
||||
clearForm();
|
||||
onCreate(environment);
|
||||
},
|
||||
});
|
||||
mutation.mutate(
|
||||
{ ...values, containerEngine },
|
||||
{
|
||||
onSuccess(environment) {
|
||||
notifySuccess('Environment created', environment.Name);
|
||||
clearForm();
|
||||
onCreate(environment);
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { object, SchemaOf, string } from 'yup';
|
||||
import { mixed, object, SchemaOf, string } from 'yup';
|
||||
|
||||
import { CreateAgentEnvironmentValues } from '@/react/portainer/environments/environment.service/create';
|
||||
|
||||
|
@ -10,6 +10,7 @@ export function useValidation(): SchemaOf<CreateAgentEnvironmentValues> {
|
|||
name: useNameValidation(),
|
||||
environmentUrl: environmentValidation(),
|
||||
meta: metadataValidation(),
|
||||
containerEngine: mixed().oneOf(['docker', 'podman']),
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
import { Formik, Form } from 'formik';
|
||||
import { Plug2 } from 'lucide-react';
|
||||
|
||||
import { Environment } from '@/react/portainer/environments/types';
|
||||
import {
|
||||
ContainerEngine,
|
||||
Environment,
|
||||
} from '@/react/portainer/environments/types';
|
||||
import { useCreateEdgeAgentEnvironmentMutation } from '@/react/portainer/environments/queries/useCreateEnvironmentMutation';
|
||||
import { Settings } from '@/react/portainer/settings/types';
|
||||
import { EdgeCheckinIntervalField } from '@/react/edge/components/EdgeCheckInIntervalField';
|
||||
|
@ -26,9 +29,15 @@ interface Props {
|
|||
onCreate(environment: Environment): void;
|
||||
readonly: boolean;
|
||||
asyncMode: boolean;
|
||||
containerEngine: ContainerEngine;
|
||||
}
|
||||
|
||||
export function EdgeAgentForm({ onCreate, readonly, asyncMode }: Props) {
|
||||
export function EdgeAgentForm({
|
||||
onCreate,
|
||||
readonly,
|
||||
asyncMode,
|
||||
containerEngine,
|
||||
}: Props) {
|
||||
const settingsQuery = useSettings();
|
||||
|
||||
const createMutation = useCreateEdgeAgentEnvironmentMutation();
|
||||
|
@ -100,6 +109,7 @@ export function EdgeAgentForm({ onCreate, readonly, asyncMode }: Props) {
|
|||
...values.edge,
|
||||
asyncMode,
|
||||
},
|
||||
containerEngine,
|
||||
},
|
||||
{
|
||||
onSuccess(environment) {
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
import { v4 as uuid } from 'uuid';
|
||||
import { useReducer, useState } from 'react';
|
||||
|
||||
import { Environment } from '@/react/portainer/environments/types';
|
||||
import {
|
||||
ContainerEngine,
|
||||
Environment,
|
||||
} from '@/react/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';
|
||||
|
@ -15,9 +18,15 @@ interface Props {
|
|||
onCreate: (environment: Environment) => void;
|
||||
commands: CommandTab[] | Partial<Record<OS, CommandTab[]>>;
|
||||
asyncMode?: boolean;
|
||||
containerEngine?: ContainerEngine;
|
||||
}
|
||||
|
||||
export function EdgeAgentTab({ onCreate, commands, asyncMode = false }: Props) {
|
||||
export function EdgeAgentTab({
|
||||
onCreate,
|
||||
commands,
|
||||
asyncMode = false,
|
||||
containerEngine = ContainerEngine.Docker,
|
||||
}: Props) {
|
||||
const [edgeInfo, setEdgeInfo] = useState<EdgeInfo>();
|
||||
const [formKey, clearForm] = useReducer((state) => state + 1, 0);
|
||||
|
||||
|
@ -28,6 +37,7 @@ export function EdgeAgentTab({ onCreate, commands, asyncMode = false }: Props) {
|
|||
readonly={!!edgeInfo}
|
||||
key={formKey}
|
||||
asyncMode={asyncMode}
|
||||
containerEngine={containerEngine}
|
||||
/>
|
||||
|
||||
{edgeInfo && (
|
||||
|
|
|
@ -3,12 +3,16 @@ export interface AnalyticsState {
|
|||
dockerApi: number;
|
||||
dockerEdgeAgentStandard: number;
|
||||
dockerEdgeAgentAsync: number;
|
||||
podmanAgent: number;
|
||||
podmanEdgeAgentStandard: number;
|
||||
podmanEdgeAgentAsync: number;
|
||||
podmanLocalEnvironment: number; // podman socket
|
||||
kubernetesAgent: number;
|
||||
kubernetesEdgeAgentStandard: number;
|
||||
kubernetesEdgeAgentAsync: number;
|
||||
kaasAgent: number;
|
||||
aciApi: number;
|
||||
localEndpoint: number;
|
||||
localEndpoint: number; // docker socket
|
||||
}
|
||||
|
||||
export type AnalyticsStateKey = keyof AnalyticsState;
|
||||
|
|
|
@ -3,6 +3,7 @@ import { useMutation } from '@tanstack/react-query';
|
|||
|
||||
import { useEnvironmentList } from '@/react/portainer/environments/queries/useEnvironmentList';
|
||||
import {
|
||||
ContainerEngine,
|
||||
Environment,
|
||||
EnvironmentType,
|
||||
} from '@/react/portainer/environments/types';
|
||||
|
@ -62,11 +63,30 @@ function getStatus(
|
|||
}
|
||||
|
||||
async function createLocalEnvironment() {
|
||||
try {
|
||||
return await createLocalKubernetesEnvironment({ name: 'local' });
|
||||
} catch (err) {
|
||||
return await createLocalDockerEnvironment({ name: 'local' });
|
||||
const name = 'local';
|
||||
const attempts = [
|
||||
() => createLocalKubernetesEnvironment({ name }),
|
||||
() =>
|
||||
createLocalDockerEnvironment({
|
||||
name,
|
||||
containerEngine: ContainerEngine.Podman,
|
||||
}),
|
||||
() =>
|
||||
createLocalDockerEnvironment({
|
||||
name,
|
||||
containerEngine: ContainerEngine.Docker,
|
||||
}),
|
||||
];
|
||||
|
||||
for (let i = 0; i < attempts.length; i++) {
|
||||
try {
|
||||
return await attempts[i]();
|
||||
} catch (err) {
|
||||
// Continue to next attempt
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('Failed to create local environment with any method');
|
||||
}
|
||||
|
||||
function useFetchLocalEnvironment() {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
|
||||
import { useIsSwarm } from '@/react/docker/proxy/queries/useInfo';
|
||||
import { StackType } from '@/react/common/stacks/types';
|
||||
import { ContainerEngine } from '@/react/portainer/environments/types';
|
||||
|
||||
import { PageHeader } from '@@/PageHeader';
|
||||
import { Widget } from '@@/Widget';
|
||||
|
@ -12,16 +13,18 @@ import { CreateForm } from './CreateForm';
|
|||
export function CreateView() {
|
||||
const viewType = useViewType();
|
||||
const environmentId = useEnvironmentId(false);
|
||||
const isSwarm = useIsSwarm(environmentId, { enabled: viewType === 'docker' });
|
||||
const isSwarm = useIsSwarm(environmentId, {
|
||||
enabled: viewType === ContainerEngine.Docker,
|
||||
});
|
||||
const defaultType = getDefaultType(viewType, isSwarm);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<PageHeader
|
||||
title="Create Custom template"
|
||||
title="Create Custom Template"
|
||||
breadcrumbs={[
|
||||
{ label: 'Custom Templates', link: '^' },
|
||||
'Create Custom template',
|
||||
'Create Custom Template',
|
||||
]}
|
||||
/>
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { notifySuccess } from '@/portainer/services/notifications';
|
||||
import { useParamState } from '@/react/hooks/useParamState';
|
||||
import { ContainerEngine } from '@/react/portainer/environments/types';
|
||||
|
||||
import { PageHeader } from '@@/PageHeader';
|
||||
import { confirmDelete } from '@@/modals/confirm';
|
||||
|
@ -28,7 +29,7 @@ export function ListView() {
|
|||
<>
|
||||
<PageHeader title="Custom Templates" breadcrumbs="Custom Templates" />
|
||||
|
||||
{viewType === 'docker' && !!selectedTemplateId && (
|
||||
{viewType === ContainerEngine.Docker && !!selectedTemplateId && (
|
||||
<StackFromCustomTemplateFormWidget templateId={selectedTemplateId} />
|
||||
)}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue