import { AlertTriangle, Loader2 } from 'lucide-react';
import { EnvironmentId } from '@/react/portainer/environments/types';
import {
Stack,
StackDeploymentStatus,
StackStatus,
StackType,
} from '@/react/common/stacks/types';
import { GitReferenceCard } from '@/react/portainer/gitops/GitReferenceCard';
import { Alert } from '@@/Alert';
import { Icon } from '@@/Icon';
import { FormSection } from '@@/form-components/FormSection';
import { useSwarmStackResources } from '../useSwarmStackServices';
import { useComposeStackContainers } from '../useComposeStackContainers';
import { StackDuplicationForm } from './StackDuplicationForm/StackDuplicationForm';
import { StackActions } from './StackActions';
import { AssociateStackForm } from './AssociateStackForm';
interface StackInfoTabProps {
stack?: Stack; // will be loaded only if regular or orphaned
stackName: string;
stackFileContent?: string;
isRegular?: boolean;
isExternal: boolean;
isOrphaned: boolean;
isOrphanedRunning: boolean;
environmentId: number;
yamlError?: string;
}
export function StackInfoTab({
stack,
stackName,
stackFileContent,
isRegular,
isExternal,
isOrphaned,
isOrphanedRunning,
environmentId,
yamlError,
}: StackInfoTabProps) {
const status = useStackStatus({
status: stack?.Status,
environmentId,
name: stackName,
type: stack?.Type,
});
return (
<>
{stack && (
)}
{stackName}
{stack && (
)}
{stack && (
<>
{isOrphaned ? (
) : (
{!!stack.GitConfig &&
!stack.FromAppTemplate &&
!!stack.GitSourceId && (
)}
{isRegular && !!stackFileContent && (
)}
)}
>
)}
>
);
}
function DeploymentStatusSection({
status,
deploymentStatus,
}: {
status: StackStatus;
deploymentStatus?: StackDeploymentStatus[];
}) {
if (status === StackStatus.Deploying) {
return (
Deployment in progress...
);
}
if (status === StackStatus.Error) {
const errorMessage = getLastDeploymentError(deploymentStatus);
return (
{errorMessage || 'Deployment failed.'}
);
}
return null;
}
function getLastDeploymentError(
deploymentStatus?: StackDeploymentStatus[]
): string | undefined {
if (!deploymentStatus?.length) return undefined;
const last = deploymentStatus[deploymentStatus.length - 1];
return last.Status === StackStatus.Error ? last.Message : undefined;
}
function ExternalOrphanedWarning({
isExternal,
isOrphaned,
}: {
isExternal: boolean;
isOrphaned: boolean;
}) {
if (!isExternal && !isOrphaned) return null;
return (
{isExternal && (
This stack was created outside of Portainer. Control over this
stack is limited.
)}
{isOrphaned && (
This stack is orphaned. You can re-associate it with the current
environment using the "Associate to this environment"
feature.
)}
);
}
function useStackStatus({
status,
name,
type,
environmentId,
}: {
status: Stack['Status'] | undefined;
name: string;
type: Stack['Type'] | undefined;
environmentId: EnvironmentId;
}) {
const servicesQuery = useSwarmStackResources(name, {
enabled: type === StackType.DockerSwarm && !status,
});
const containersQuery = useComposeStackContainers(
{ environmentId, stackName: name },
{
enabled: type === StackType.DockerCompose && !status,
}
);
const derivedSwarmStatus = servicesQuery.data?.length
? StackStatus.Active
: StackStatus.Inactive;
const derivedComposeStatus = containersQuery.data?.length
? StackStatus.Active
: StackStatus.Inactive;
const derivedStatus =
type === StackType.DockerSwarm ? derivedSwarmStatus : derivedComposeStatus;
return status || derivedStatus;
}