chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:08:39 +08:00
commit a0df89c693
5252 changed files with 523444 additions and 0 deletions
@@ -0,0 +1,14 @@
import { buildStackUrl } from '../buildUrl';
export function buildCreateUrl(
stackType: 'kubernetes',
method: 'repository' | 'url' | 'string'
): string;
export function buildCreateUrl(
stackType: 'swarm' | 'standalone',
method: 'repository' | 'string' | 'file'
): string;
export function buildCreateUrl(stackType: string, method: string) {
return buildStackUrl(undefined, `create/${stackType}/${method}`);
}
@@ -0,0 +1,36 @@
import axios, { parseAxiosError } from '@/portainer/services/axios/axios';
import { EnvironmentId } from '@/react/portainer/environments/types';
import { Stack } from '../../types';
import { buildCreateUrl } from './buildUrl';
export interface KubernetesFileContentPayload {
/** Name of the stack */
stackName: string;
/** Content of the Stack file */
stackFileContent: string;
composeFormat: boolean;
namespace: string;
/** Whether the stack is from an app template */
fromAppTemplate?: boolean;
environmentId: EnvironmentId;
}
export async function createKubernetesStackFromFileContent({
environmentId,
...payload
}: KubernetesFileContentPayload) {
try {
const { data } = await axios.post<Stack>(
buildCreateUrl('kubernetes', 'string'),
payload,
{
params: { endpointId: environmentId },
}
);
return data;
} catch (e) {
throw parseAxiosError(e as Error);
}
}
@@ -0,0 +1,47 @@
import axios, { parseAxiosError } from '@/portainer/services/axios/axios';
import { AutoUpdateResponse } from '@/react/portainer/gitops/types';
import { EnvironmentId } from '@/react/portainer/environments/types';
import { Stack } from '../../types';
import { buildCreateUrl } from './buildUrl';
export type KubernetesGitRepositoryPayload = {
/** Name of the stack */
stackName: string;
composeFormat: boolean;
namespace: string;
/** When set, URL and auth are resolved from the stored Source record */
sourceId?: number;
/** Reference name of a Git repository hosting the Stack file */
repositoryReferenceName?: string;
/** Path to the Stack file inside the Git repository */
manifestFile?: string;
additionalFiles?: Array<string>;
/** Optional GitOps update configuration */
autoUpdate?: AutoUpdateResponse | null;
environmentId: EnvironmentId;
};
export async function createKubernetesStackFromGit({
environmentId,
...payload
}: KubernetesGitRepositoryPayload) {
try {
const { data } = await axios.post<Stack>(
buildCreateUrl('kubernetes', 'repository'),
payload,
{
params: { endpointId: environmentId },
}
);
return data;
} catch (e) {
throw parseAxiosError(e as Error);
}
}
@@ -0,0 +1,32 @@
import axios, { parseAxiosError } from '@/portainer/services/axios/axios';
import { EnvironmentId } from '@/react/portainer/environments/types';
import { Stack } from '../../types';
import { buildCreateUrl } from './buildUrl';
export interface KubernetesUrlPayload {
stackName: string;
composeFormat: boolean;
namespace: string;
manifestURL: string;
environmentId: EnvironmentId;
}
export async function createKubernetesStackFromUrl({
environmentId,
...payload
}: KubernetesUrlPayload) {
try {
const { data } = await axios.post<Stack>(
buildCreateUrl('kubernetes', 'url'),
payload,
{
params: { endpointId: environmentId },
}
);
return data;
} catch (e) {
throw parseAxiosError(e as Error);
}
}
@@ -0,0 +1,44 @@
import axios, { parseAxiosError } from '@/portainer/services/axios/axios';
import { Pair } from '@/react/portainer/settings/types';
import { EnvironmentId } from '@/react/portainer/environments/types';
import { RegistryId } from '@/react/portainer/registries/types/registry';
import { json2formData } from '@/portainer/helpers/json';
import { Stack } from '../../types';
import { buildCreateUrl } from './buildUrl';
export type StandaloneFileUploadPayload = {
/** Name of the stack */
Name: string;
file: File;
/** List of environment variables */
Env?: Array<Pair>;
/** A UUID to identify a webhook. The stack will be force updated and pull the latest image when the webhook was invoked. */
Webhook?: string;
environmentId: EnvironmentId;
Registries?: Array<RegistryId>;
};
export async function createStandaloneStackFromFile({
environmentId,
...payload
}: StandaloneFileUploadPayload) {
try {
const { data } = await axios.post<Stack>(
buildCreateUrl('standalone', 'file'),
json2formData(payload),
{
headers: {
'Content-Type': 'multipart/form-data',
},
params: { endpointId: environmentId },
}
);
return data;
} catch (e) {
throw parseAxiosError(e as Error);
}
}
@@ -0,0 +1,42 @@
import axios, { parseAxiosError } from '@/portainer/services/axios/axios';
import { Pair } from '@/react/portainer/settings/types';
import { EnvironmentId } from '@/react/portainer/environments/types';
import { RegistryId } from '@/react/portainer/registries/types/registry';
import { Stack } from '../../types';
import { buildCreateUrl } from './buildUrl';
export interface StandaloneFileContentPayload {
/** Name of the stack */
name: string;
stackFileContent: string;
/** List of environment variables */
env?: Array<Pair> | null;
/** Whether the stack is from an app template */
fromAppTemplate?: boolean;
/** A UUID to identify a webhook. The stack will be force updated and pull the latest image when the webhook was invoked. */
webhook?: string;
environmentId: EnvironmentId;
registries?: Array<RegistryId>;
}
export async function createStandaloneStackFromFileContent({
environmentId,
...payload
}: StandaloneFileContentPayload) {
try {
const { data } = await axios.post<Stack>(
buildCreateUrl('standalone', 'string'),
payload,
{
params: { endpointId: environmentId },
}
);
return data;
} catch (e) {
throw parseAxiosError(e as Error);
}
}
@@ -0,0 +1,59 @@
import axios, { parseAxiosError } from '@/portainer/services/axios/axios';
import { Pair } from '@/react/portainer/settings/types';
import { AutoUpdateResponse } from '@/react/portainer/gitops/types';
import { EnvironmentId } from '@/react/portainer/environments/types';
import { RegistryId } from '@/react/portainer/registries/types/registry';
import { Stack } from '../../types';
import { buildCreateUrl } from './buildUrl';
export type StandaloneGitRepositoryPayload = {
/** Name of the stack */
name: string;
/** List of environment variables */
env?: Array<Pair>;
/** Whether the stack is from an app template */
fromAppTemplate?: boolean;
/** URL of a Git repository hosting the Stack file (used for app templates) */
repositoryUrl?: string;
/** Reference name of a Git repository hosting the Stack file */
repositoryReferenceName?: string;
/** Path to the Stack file inside the Git repository */
composeFile?: string;
additionalFiles?: Array<string>;
/** Optional GitOps update configuration */
autoUpdate?: AutoUpdateResponse | null;
/** Whether the stack supports relative path volume */
supportRelativePath?: boolean;
/** Local filesystem path */
filesystemPath?: string;
/** ID of an existing Source. When set, repositoryUrl and authentication fields are ignored. */
sourceId?: number;
environmentId: EnvironmentId;
registries?: Array<RegistryId>;
};
export async function createStandaloneStackFromGit({
environmentId,
...payload
}: StandaloneGitRepositoryPayload) {
try {
const { data } = await axios.post<Stack>(
buildCreateUrl('standalone', 'repository'),
payload,
{
params: { endpointId: environmentId },
}
);
return data;
} catch (e) {
throw parseAxiosError(e as Error);
}
}
@@ -0,0 +1,51 @@
import axios, { parseAxiosError } from '@/portainer/services/axios/axios';
import { Pair } from '@/react/portainer/settings/types';
import { EnvironmentId } from '@/react/portainer/environments/types';
import { RegistryId } from '@/react/portainer/registries/types/registry';
import { json2formData } from '@/portainer/helpers/json';
import { Stack } from '../../types';
import { buildCreateUrl } from './buildUrl';
export type SwarmFileUploadPayload = {
/** Name of the stack */
Name: string;
/** List of environment variables */
Env?: Array<Pair>;
/** A UUID to identify a webhook. The stack will be force updated and pull the latest image when the webhook was invoked. */
Webhook?: string;
/** Swarm cluster identifier */
SwarmID: string;
file: File;
environmentId: EnvironmentId;
Registries?: Array<RegistryId>;
};
export async function createSwarmStackFromFile({
environmentId,
...payload
}: SwarmFileUploadPayload) {
try {
const { data } = await axios.post<Stack>(
buildCreateUrl('swarm', 'file'),
json2formData(payload),
{
headers: {
'Content-Type': 'multipart/form-data',
},
params: {
endpointId: environmentId,
},
}
);
return data;
} catch (e) {
throw parseAxiosError(e as Error);
}
}
@@ -0,0 +1,45 @@
import axios, { parseAxiosError } from '@/portainer/services/axios/axios';
import { Pair } from '@/react/portainer/settings/types';
import { EnvironmentId } from '@/react/portainer/environments/types';
import { RegistryId } from '@/react/portainer/registries/types/registry';
import { Stack } from '../../types';
import { buildCreateUrl } from './buildUrl';
export interface SwarmFileContentPayload {
/** Name of the stack */
name: string;
stackFileContent: string;
/** List of environment variables */
env?: Array<Pair> | null;
/** Whether the stack is from an app template */
fromAppTemplate?: boolean;
/** A UUID to identify a webhook. The stack will be force updated and pull the latest image when the webhook was invoked. */
webhook?: string;
/** Swarm cluster identifier */
swarmID: string;
environmentId: EnvironmentId;
registries?: Array<RegistryId>;
}
export async function createSwarmStackFromFileContent({
environmentId,
...payload
}: SwarmFileContentPayload) {
try {
const { data } = await axios.post<Stack>(
buildCreateUrl('swarm', 'string'),
payload,
{
params: { endpointId: environmentId },
}
);
return data;
} catch (e) {
throw parseAxiosError(e as Error);
}
}
@@ -0,0 +1,61 @@
import axios, { parseAxiosError } from '@/portainer/services/axios/axios';
import { Pair } from '@/react/portainer/settings/types';
import { AutoUpdateResponse } from '@/react/portainer/gitops/types';
import { EnvironmentId } from '@/react/portainer/environments/types';
import { RegistryId } from '@/react/portainer/registries/types/registry';
import { Stack } from '../../types';
import { buildCreateUrl } from './buildUrl';
export type SwarmGitRepositoryPayload = {
/** Name of the stack */
name: string;
/** List of environment variables */
env?: Array<Pair>;
/** Whether the stack is from an app template */
fromAppTemplate?: boolean;
/** Swarm cluster identifier */
swarmID: string;
/** URL of a Git repository hosting the Stack file (used for app templates) */
repositoryUrl?: string;
/** Reference name of a Git repository hosting the Stack file */
repositoryReferenceName?: string;
/** Path to the Stack file inside the Git repository */
composeFile?: string;
additionalFiles?: Array<string>;
/** Optional GitOps update configuration */
autoUpdate?: AutoUpdateResponse | null;
/** Whether the stack supports relative path volume */
supportRelativePath?: boolean;
/** Local filesystem path */
filesystemPath?: string;
/** ID of an existing Source. When set, repositoryUrl and authentication fields are ignored. */
sourceId?: number;
environmentId: EnvironmentId;
registries?: Array<RegistryId>;
};
export async function createSwarmStackFromGit({
environmentId,
...payload
}: SwarmGitRepositoryPayload) {
try {
const { data } = await axios.post<Stack>(
buildCreateUrl('swarm', 'repository'),
payload,
{
params: { endpointId: environmentId },
}
);
return data;
} catch (e) {
throw parseAxiosError(e as Error);
}
}
@@ -0,0 +1,312 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { EnvironmentId } from '@/react/portainer/environments/types';
import { Pair } from '@/react/portainer/settings/types';
import {
GitFormModel,
RelativePathModel,
} from '@/react/portainer/gitops/types';
import { applyResourceControl } from '@/react/portainer/access-control/access-control.service';
import { AccessControlFormData } from '@/react/portainer/access-control/types';
import PortainerError from '@/portainer/error';
import { withError, withInvalidate } from '@/react-tools/react-query';
import { transformAutoUpdateViewModel } from '@/react/portainer/gitops/AutoUpdateFieldset/utils';
import { RegistryId } from '@/react/portainer/registries/types/registry';
import { queryKeys } from '../query-keys';
import { createSwarmStackFromFile } from './createSwarmStackFromFile';
import { createSwarmStackFromGit } from './createSwarmStackFromGit';
import { createSwarmStackFromFileContent } from './createSwarmStackFromFileContent';
import { createStandaloneStackFromFile } from './createStandaloneStackFromFile';
import { createStandaloneStackFromGit } from './createStandaloneStackFromGit';
import { createStandaloneStackFromFileContent } from './createStandaloneStackFromFileContent';
import { createKubernetesStackFromUrl } from './createKubernetesStackFromUrl';
import { createKubernetesStackFromGit } from './createKubernetesStackFromGit';
import { createKubernetesStackFromFileContent } from './createKubernetesStackFromFileContent';
export function useCreateStack() {
const queryClient = useQueryClient();
return useMutation(createStack, {
...withError('Failed to create stack'),
...withInvalidate(queryClient, [queryKeys.base()]),
});
}
type BasePayload = {
name: string;
environmentId: EnvironmentId;
registries?: Array<RegistryId>;
};
type DockerBasePayload = BasePayload & {
env?: Array<Pair>;
accessControl: AccessControlFormData;
};
type SwarmBasePayload = DockerBasePayload & {
swarmId: string;
};
type KubernetesBasePayload = BasePayload & {
namespace: string;
composeFormat: boolean;
};
export type SwarmCreatePayload =
| {
method: 'file';
payload: SwarmBasePayload & {
/** File to upload */
file: File;
/** Optional webhook configuration */
webhook?: string;
};
}
| {
method: 'string';
payload: SwarmBasePayload & {
/** Content of the Stack file */
fileContent: string;
/** Optional webhook configuration */
webhook?: string;
fromAppTemplate?: boolean;
};
}
| {
method: 'git';
payload: SwarmBasePayload & {
git: GitFormModel;
relativePathSettings?: RelativePathModel;
fromAppTemplate?: boolean;
webhook?: string;
};
};
type StandaloneCreatePayload =
| {
method: 'file';
payload: DockerBasePayload & {
/** File to upload */
file: File;
/** Optional webhook configuration */
webhook?: string;
};
}
| {
method: 'string';
payload: DockerBasePayload & {
/** Content of the Stack file */
fileContent: string;
/** Optional webhook configuration */
webhook?: string;
fromAppTemplate?: boolean;
};
}
| {
method: 'git';
payload: DockerBasePayload & {
git: GitFormModel;
relativePathSettings?: RelativePathModel;
fromAppTemplate?: boolean;
webhook?: string;
};
};
type KubernetesCreatePayload =
| {
method: 'string';
payload: KubernetesBasePayload & {
/** Content of the Stack file */
fileContent: string;
/** Optional webhook configuration */
webhook?: string;
};
}
| {
method: 'git';
payload: KubernetesBasePayload & {
git: GitFormModel;
relativePathSettings?: RelativePathModel;
webhook?: string;
};
}
| {
method: 'url';
payload: KubernetesBasePayload & {
manifestUrl: string;
};
};
export type CreateStackPayload =
| ({ type: 'swarm' } & SwarmCreatePayload)
| ({ type: 'standalone' } & StandaloneCreatePayload)
| ({ type: 'kubernetes' } & KubernetesCreatePayload);
async function createStack(payload: CreateStackPayload) {
const stack = await createActualStack(payload);
if (payload.type === 'standalone' || payload.type === 'swarm') {
const resourceControl = stack.ResourceControl;
// Portainer will always return a resource control, but since types mark it as optional, we need to check it.
// Ignoring the missing value will result with bugs, hence it's better to throw an error
if (!resourceControl) {
throw new PortainerError('resource control expected after creation');
}
await applyResourceControl(
payload.payload.accessControl,
resourceControl.Id
);
}
return stack;
}
function createActualStack(payload: CreateStackPayload) {
switch (payload.type) {
case 'swarm':
return createSwarmStack(payload);
case 'standalone':
return createStandaloneStack(payload);
case 'kubernetes':
return createKubernetesStack(payload);
default:
throw new Error('Invalid type');
}
}
function createSwarmStack({ method, payload }: SwarmCreatePayload) {
switch (method) {
case 'file':
return createSwarmStackFromFile({
environmentId: payload.environmentId,
file: payload.file,
Name: payload.name,
SwarmID: payload.swarmId,
Env: payload.env,
Webhook: payload.webhook,
Registries: payload.registries,
});
case 'git':
return createSwarmStackFromGit({
name: payload.name,
env: payload.env,
repositoryUrl: payload.git.RepositoryURL,
repositoryReferenceName: payload.git.RepositoryReferenceName,
composeFile: payload.git.ComposeFilePathInRepository,
filesystemPath: payload.relativePathSettings?.FilesystemPath,
supportRelativePath: payload.relativePathSettings?.SupportRelativePath,
sourceId: payload.git.SourceId,
autoUpdate: transformAutoUpdateViewModel(
payload.git.AutoUpdate,
payload.webhook
),
environmentId: payload.environmentId,
swarmID: payload.swarmId,
additionalFiles: payload.git.AdditionalFiles,
fromAppTemplate: payload.fromAppTemplate,
registries: payload.registries,
});
case 'string':
return createSwarmStackFromFileContent({
name: payload.name,
env: payload.env,
environmentId: payload.environmentId,
stackFileContent: payload.fileContent,
webhook: payload.webhook,
swarmID: payload.swarmId,
fromAppTemplate: payload.fromAppTemplate,
registries: payload.registries,
});
default:
throw new Error('Invalid method');
}
}
function createStandaloneStack({ method, payload }: StandaloneCreatePayload) {
switch (method) {
case 'file':
return createStandaloneStackFromFile({
environmentId: payload.environmentId,
file: payload.file,
Name: payload.name,
Env: payload.env,
Webhook: payload.webhook,
Registries: payload.registries,
});
case 'git':
return createStandaloneStackFromGit({
name: payload.name,
env: payload.env,
repositoryUrl: payload.git.RepositoryURL,
repositoryReferenceName: payload.git.RepositoryReferenceName,
composeFile: payload.git.ComposeFilePathInRepository,
filesystemPath: payload.relativePathSettings?.FilesystemPath,
supportRelativePath: payload.relativePathSettings?.SupportRelativePath,
sourceId: payload.git.SourceId,
autoUpdate: transformAutoUpdateViewModel(
payload.git.AutoUpdate,
payload.webhook
),
environmentId: payload.environmentId,
additionalFiles: payload.git.AdditionalFiles,
fromAppTemplate: payload.fromAppTemplate,
registries: payload.registries,
});
case 'string':
return createStandaloneStackFromFileContent({
name: payload.name,
env: payload.env,
environmentId: payload.environmentId,
stackFileContent: payload.fileContent,
webhook: payload.webhook,
fromAppTemplate: payload.fromAppTemplate,
registries: payload.registries,
});
default:
throw new Error('Invalid method');
}
}
function createKubernetesStack({ method, payload }: KubernetesCreatePayload) {
switch (method) {
case 'string':
return createKubernetesStackFromFileContent({
stackName: payload.name,
environmentId: payload.environmentId,
stackFileContent: payload.fileContent,
composeFormat: payload.composeFormat,
namespace: payload.namespace,
});
case 'git':
return createKubernetesStackFromGit({
stackName: payload.name,
sourceId: payload.git.SourceId,
repositoryReferenceName: payload.git.RepositoryReferenceName,
manifestFile: payload.git.ComposeFilePathInRepository,
autoUpdate: transformAutoUpdateViewModel(
payload.git.AutoUpdate,
payload.webhook
),
environmentId: payload.environmentId,
additionalFiles: payload.git.AdditionalFiles,
composeFormat: payload.composeFormat,
namespace: payload.namespace,
});
case 'url':
return createKubernetesStackFromUrl({
stackName: payload.name,
composeFormat: payload.composeFormat,
environmentId: payload.environmentId,
manifestURL: payload.manifestUrl,
namespace: payload.namespace,
});
default:
throw new Error('Invalid method');
}
}