mirror of https://github.com/portainer/portainer
feat(edge/stacks): info for old agent status [EE-5792] (#10012)
parent
f5cc245c63
commit
faa1387110
|
@ -50,7 +50,7 @@ func (store *Store) MigrateData() error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = errors.Wrap(err, "failed to migrate database")
|
err = errors.Wrap(err, "failed to migrate database")
|
||||||
|
|
||||||
log.Warn().Msg("migration failed, restoring database to previous version")
|
log.Warn().Err(err).Msg("migration failed, restoring database to previous version")
|
||||||
err = store.restoreWithOptions(&BackupOptions{BackupPath: backupPath})
|
err = store.restoreWithOptions(&BackupOptions{BackupPath: backupPath})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "failed to restore database")
|
return errors.Wrap(err, "failed to restore database")
|
||||||
|
|
|
@ -115,10 +115,16 @@ func (m *Migrator) updateEdgeStackStatusForDB100() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if environmentStatus.Details.Ok {
|
if environmentStatus.Details.Ok {
|
||||||
statusArray = append(statusArray, portainer.EdgeStackDeploymentStatus{
|
statusArray = append(statusArray,
|
||||||
Type: portainer.EdgeStackStatusRunning,
|
portainer.EdgeStackDeploymentStatus{
|
||||||
Time: time.Now().Unix(),
|
Type: portainer.EdgeStackStatusDeploymentReceived,
|
||||||
})
|
Time: time.Now().Unix(),
|
||||||
|
},
|
||||||
|
portainer.EdgeStackDeploymentStatus{
|
||||||
|
Type: portainer.EdgeStackStatusRunning,
|
||||||
|
Time: time.Now().Unix(),
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if environmentStatus.Details.ImagesPulled {
|
if environmentStatus.Details.ImagesPulled {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { semverCompare } from './utils';
|
import { semverCompare } from './semver-utils';
|
||||||
|
|
||||||
describe('semverCompare', () => {
|
describe('semverCompare', () => {
|
||||||
test('sort array', () => {
|
test('sort array', () => {
|
|
@ -0,0 +1,27 @@
|
||||||
|
/**
|
||||||
|
* Compares two semver strings.
|
||||||
|
*
|
||||||
|
* returns:
|
||||||
|
* - `-1` if `a < b`
|
||||||
|
* - `0` if `a == b`
|
||||||
|
* - `1` if `a > b`
|
||||||
|
*/
|
||||||
|
export function semverCompare(a: string, b: string) {
|
||||||
|
if (a.startsWith(`${b}-`)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (b.startsWith(`${a}-`)) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return a.localeCompare(b, undefined, {
|
||||||
|
numeric: true,
|
||||||
|
sensitivity: 'case',
|
||||||
|
caseFirst: 'upper',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isVersionSmaller(a: string, b: string) {
|
||||||
|
return semverCompare(a, b) < 0;
|
||||||
|
}
|
|
@ -5,9 +5,14 @@ import {
|
||||||
type Icon as IconType,
|
type Icon as IconType,
|
||||||
Loader2,
|
Loader2,
|
||||||
XCircle,
|
XCircle,
|
||||||
|
MinusCircle,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
|
|
||||||
|
import { useEnvironmentList } from '@/react/portainer/environments/queries';
|
||||||
|
import { isVersionSmaller } from '@/react/common/semver-utils';
|
||||||
|
|
||||||
import { Icon, IconMode } from '@@/Icon';
|
import { Icon, IconMode } from '@@/Icon';
|
||||||
|
import { Tooltip } from '@@/Tip/Tooltip';
|
||||||
|
|
||||||
import { DeploymentStatus, EdgeStack, StatusType } from '../../types';
|
import { DeploymentStatus, EdgeStack, StatusType } from '../../types';
|
||||||
|
|
||||||
|
@ -15,28 +20,51 @@ export function EdgeStackStatus({ edgeStack }: { edgeStack: EdgeStack }) {
|
||||||
const status = Object.values(edgeStack.Status);
|
const status = Object.values(edgeStack.Status);
|
||||||
const lastStatus = _.compact(status.map((s) => _.last(s.Status)));
|
const lastStatus = _.compact(status.map((s) => _.last(s.Status)));
|
||||||
|
|
||||||
const { icon, label, mode, spin } = getStatus(
|
const environmentsQuery = useEnvironmentList({ edgeStackId: edgeStack.Id });
|
||||||
|
|
||||||
|
if (environmentsQuery.isLoading) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasOldVersion = environmentsQuery.environments.some((env) =>
|
||||||
|
isVersionSmaller(env.Agent.Version, '2.19.0')
|
||||||
|
);
|
||||||
|
|
||||||
|
const { icon, label, mode, spin, tooltip } = getStatus(
|
||||||
edgeStack.NumDeployments,
|
edgeStack.NumDeployments,
|
||||||
lastStatus
|
lastStatus,
|
||||||
|
hasOldVersion
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx-auto inline-flex items-center gap-2">
|
<div className="mx-auto inline-flex items-center gap-2">
|
||||||
{icon && <Icon icon={icon} spin={spin} mode={mode} />}
|
{icon && <Icon icon={icon} spin={spin} mode={mode} />}
|
||||||
{label}
|
{label}
|
||||||
|
{tooltip && <Tooltip message={tooltip} />}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getStatus(
|
function getStatus(
|
||||||
numDeployments: number,
|
numDeployments: number,
|
||||||
envStatus: Array<DeploymentStatus>
|
envStatus: Array<DeploymentStatus>,
|
||||||
|
hasOldVersion: boolean
|
||||||
): {
|
): {
|
||||||
label: string;
|
label: string;
|
||||||
icon?: IconType;
|
icon?: IconType;
|
||||||
spin?: boolean;
|
spin?: boolean;
|
||||||
mode?: IconMode;
|
mode?: IconMode;
|
||||||
|
tooltip?: string;
|
||||||
} {
|
} {
|
||||||
|
if (!numDeployments || hasOldVersion) {
|
||||||
|
return {
|
||||||
|
label: 'Unavailable',
|
||||||
|
icon: MinusCircle,
|
||||||
|
mode: 'secondary',
|
||||||
|
tooltip: getUnavailableTooltip(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (envStatus.length < numDeployments) {
|
if (envStatus.length < numDeployments) {
|
||||||
return {
|
return {
|
||||||
label: 'Deploying',
|
label: 'Deploying',
|
||||||
|
@ -56,7 +84,11 @@ function getStatus(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const allRunning = envStatus.every((s) => s.Type === StatusType.Running);
|
const allRunning = envStatus.every(
|
||||||
|
(s) =>
|
||||||
|
s.Type === StatusType.Running ||
|
||||||
|
(s.Type === StatusType.DeploymentReceived && hasOldVersion)
|
||||||
|
);
|
||||||
|
|
||||||
if (allRunning) {
|
if (allRunning) {
|
||||||
return {
|
return {
|
||||||
|
@ -84,4 +116,16 @@ function getStatus(
|
||||||
spin: true,
|
spin: true,
|
||||||
mode: 'primary',
|
mode: 'primary',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function getUnavailableTooltip() {
|
||||||
|
if (!numDeployments) {
|
||||||
|
return 'Your edge stack is currently unavailable due to the absence of an available environment in your edge group';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasOldVersion) {
|
||||||
|
return 'Please note that the new status feature for the Edge stack is only available for Edge Agent versions 2.19.0 and above. To access the status of your edge stack, it is essential to upgrade your Edge Agent to a corresponding version that is compatible with your Portainer server.';
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,13 +45,19 @@ export const columns = _.compact([
|
||||||
(item) => item.aggregatedStatus[StatusType.ImagesPulled] || 0,
|
(item) => item.aggregatedStatus[StatusType.ImagesPulled] || 0,
|
||||||
{
|
{
|
||||||
header: 'Images pre-pulled',
|
header: 'Images pre-pulled',
|
||||||
cell: ({ getValue, row }) => (
|
cell: ({ getValue, row: { original: item } }) => {
|
||||||
<DeploymentCounter
|
if (!item.PrePullImage) {
|
||||||
count={getValue()}
|
return <div className="text-center">-</div>;
|
||||||
type={StatusType.ImagesPulled}
|
}
|
||||||
total={row.original.NumDeployments}
|
|
||||||
/>
|
return (
|
||||||
),
|
<DeploymentCounter
|
||||||
|
count={getValue()}
|
||||||
|
type={StatusType.ImagesPulled}
|
||||||
|
total={item.NumDeployments}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
enableSorting: false,
|
enableSorting: false,
|
||||||
enableHiding: false,
|
enableHiding: false,
|
||||||
meta: {
|
meta: {
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
|
||||||
import { Environment } from '@/react/portainer/environments/types';
|
import { Environment } from '@/react/portainer/environments/types';
|
||||||
|
import { semverCompare } from '@/react/common/semver-utils';
|
||||||
|
|
||||||
import { TextTip } from '@@/Tip/TextTip';
|
import { TextTip } from '@@/Tip/TextTip';
|
||||||
|
|
||||||
import { VersionSelect } from './VersionSelect';
|
import { VersionSelect } from './VersionSelect';
|
||||||
import { ScheduledTimeField } from './ScheduledTimeField';
|
import { ScheduledTimeField } from './ScheduledTimeField';
|
||||||
import { semverCompare } from './utils';
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
environments: Environment[];
|
environments: Environment[];
|
||||||
|
|
|
@ -1,18 +1,4 @@
|
||||||
export function semverCompare(a: string, b: string) {
|
import { semverCompare } from '@/react/common/semver-utils';
|
||||||
if (a.startsWith(`${b}-`)) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (b.startsWith(`${a}-`)) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return a.localeCompare(b, undefined, {
|
|
||||||
numeric: true,
|
|
||||||
sensitivity: 'case',
|
|
||||||
caseFirst: 'upper',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function compareVersion(
|
export function compareVersion(
|
||||||
currentVersion: string,
|
currentVersion: string,
|
||||||
|
|
|
@ -2,8 +2,7 @@ import { useQuery } from 'react-query';
|
||||||
|
|
||||||
import axios, { parseAxiosError } from '@/portainer/services/axios';
|
import axios, { parseAxiosError } from '@/portainer/services/axios';
|
||||||
import { withError } from '@/react-tools/react-query';
|
import { withError } from '@/react-tools/react-query';
|
||||||
|
import { semverCompare } from '@/react/common/semver-utils';
|
||||||
import { semverCompare } from '../common/utils';
|
|
||||||
|
|
||||||
import { queryKeys } from './query-keys';
|
import { queryKeys } from './query-keys';
|
||||||
import { buildUrl } from './urls';
|
import { buildUrl } from './urls';
|
||||||
|
|
Loading…
Reference in New Issue