feat(edge): EE-4621 support high latency for tunnel (#8302)

pull/8583/head
cmeng 2023-03-04 09:13:37 +13:00 committed by GitHub
parent 07df4b1591
commit 60275dd31c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 111 additions and 23 deletions

View File

@ -1,5 +1,7 @@
import angular from 'angular';
import { PortainerEndpointTypes } from 'Portainer/models/endpoint/models';
import { EnvironmentStatus } from '@/react/portainer/environments/types';
import { reactModule } from './react';
@ -16,14 +18,17 @@ angular.module('portainer.docker', ['portainer.app', reactModule]).config([
abstract: true,
onEnter: /* @ngInject */ function onEnter(endpoint, $async, $state, EndpointService, Notifications, StateManager, SystemService) {
return $async(async () => {
if (![1, 2, 4].includes(endpoint.Type)) {
const dockerTypes = [PortainerEndpointTypes.DockerEnvironment, PortainerEndpointTypes.AgentOnDockerEnvironment, PortainerEndpointTypes.EdgeAgentOnDockerEnvironment];
if (!dockerTypes.includes(endpoint.Type)) {
$state.go('portainer.home');
return;
}
try {
const status = await checkEndpointStatus(endpoint);
if (endpoint.Type !== 4) {
if (endpoint.Type !== PortainerEndpointTypes.EdgeAgentOnDockerEnvironment) {
await updateEndpointStatus(endpoint, status);
}
endpoint.Status = status;
@ -34,16 +39,22 @@ angular.module('portainer.docker', ['portainer.app', reactModule]).config([
await StateManager.updateEndpointState(endpoint);
} catch (e) {
Notifications.error('Failed loading environment', e);
$state.go('portainer.home', {}, { reload: true });
let params = {};
if (endpoint.Type == PortainerEndpointTypes.EdgeAgentOnDockerEnvironment) {
params = { redirect: true, environmentId: endpoint.Id, environmentName: endpoint.Name, route: 'docker.dashboard' };
} else {
Notifications.error('Failed loading environment', e);
}
$state.go('portainer.home', params, { reload: true, inherit: false });
}
async function checkEndpointStatus(endpoint) {
try {
await SystemService.ping(endpoint.Id);
return 1;
return EnvironmentStatus.Up;
} catch (e) {
return 2;
return EnvironmentStatus.Down;
}
}

View File

@ -1,3 +1,7 @@
import { EnvironmentStatus } from '@/react/portainer/environments/types';
import { PortainerEndpointTypes } from 'Portainer/models/endpoint/models';
import registriesModule from './registries';
import customTemplateModule from './custom-templates';
import { reactModule } from './react';
@ -16,31 +20,43 @@ angular.module('portainer.kubernetes', ['portainer.app', registriesModule, custo
onEnter: /* @ngInject */ function onEnter($async, $state, endpoint, KubernetesHealthService, KubernetesNamespaceService, Notifications, StateManager) {
return $async(async () => {
if (![5, 6, 7].includes(endpoint.Type)) {
const kubeTypes = [
PortainerEndpointTypes.KubernetesLocalEnvironment,
PortainerEndpointTypes.AgentOnKubernetesEnvironment,
PortainerEndpointTypes.EdgeAgentOnKubernetesEnvironment,
];
if (!kubeTypes.includes(endpoint.Type)) {
$state.go('portainer.home');
return;
}
try {
if (endpoint.Type === 7) {
if (endpoint.Type === PortainerEndpointTypes.EdgeAgentOnKubernetesEnvironment) {
//edge
try {
await KubernetesHealthService.ping(endpoint.Id);
endpoint.Status = 1;
endpoint.Status = EnvironmentStatus.Up;
} catch (e) {
endpoint.Status = 2;
endpoint.Status = EnvironmentStatus.Down;
}
}
await StateManager.updateEndpointState(endpoint);
if (endpoint.Type === 7 && endpoint.Status === 2) {
if (endpoint.Type === PortainerEndpointTypes.EdgeAgentOnKubernetesEnvironment && endpoint.Status === EnvironmentStatus.Down) {
throw new Error('Unable to contact Edge agent, please ensure that the agent is properly running on the remote environment.');
}
await KubernetesNamespaceService.get();
} catch (e) {
Notifications.error('Failed loading environment', e);
$state.go('portainer.home', {}, { reload: true });
let params = {};
if (endpoint.Type == PortainerEndpointTypes.EdgeAgentOnKubernetesEnvironment) {
params = { redirect: true, environmentId: endpoint.Id, environmentName: endpoint.Name, route: 'kubernetes.dashboard' };
} else {
Notifications.error('Failed loading environment', e);
}
$state.go('portainer.home', params, { reload: true, inherit: false });
}
});
},

View File

@ -300,7 +300,7 @@ angular
var home = {
name: 'portainer.home',
url: '/home',
url: '/home?redirect&environmentId&environmentName&route',
views: {
'content@': {
component: 'homeView',

View File

@ -1,4 +1,4 @@
import { ReactNode } from 'react';
import { ReactNode, useEffect, useState, useRef } from 'react';
import { Button } from '@@/buttons';
@ -26,6 +26,37 @@ export function Dialog<T>({
}: Props<T>) {
const ariaLabel = requireString(title) || requireString(message) || 'Dialog';
const [count, setCount] = useState<number>(0);
const countRef = useRef(count);
countRef.current = count;
useEffect(() => {
let retFn;
// only countdown the first button with non-zero timeout
for (let i = 0; i < buttons.length; i++) {
const button = buttons[i];
if (button.timeout) {
setCount(button.timeout as number);
const intervalID = setInterval(() => {
const count = countRef.current;
setCount(count - 1);
if (count === 1) {
onSubmit(button.value);
}
}, 1000);
retFn = () => clearInterval(intervalID);
break;
}
}
return retFn;
}, [buttons, onSubmit]);
return (
<Modal onDismiss={() => onSubmit()} aria-label={ariaLabel}>
{title && <Modal.Header title={title} modalType={modalType} />}
@ -39,7 +70,7 @@ export function Dialog<T>({
key={index}
size="medium"
>
{button.label}
{button.label} {button.timeout && count ? `(${count})` : null}
</Button>
))}
</Modal.Footer>

View File

@ -7,6 +7,7 @@ export interface ButtonOptions<TValue = undefined> {
className?: string;
color?: ComponentProps<typeof Button>['color'];
value?: TValue;
timeout?: number;
}
export interface ButtonsOptions<T> {

View File

@ -6,9 +6,10 @@ import { ButtonOptions } from './types';
export function buildConfirmButton(
label = 'Confirm',
color: ComponentProps<typeof Button>['color'] = 'primary'
color: ComponentProps<typeof Button>['color'] = 'primary',
timeout = 0
): ButtonOptions<true> {
return { label, color, value: true };
return { label, color, value: true, timeout };
}
export function buildCancelButton(label = 'Cancel'): ButtonOptions<false> {

View File

@ -3,7 +3,7 @@
height: 100%;
text-align: center;
display: flex;
flex-direction: column;
flex-direction: row;
align-items: center;
justify-content: center;
}

View File

@ -1,5 +1,5 @@
import { useRouter } from '@uirouter/react';
import { useState } from 'react';
import { useCurrentStateAndParams, useRouter } from '@uirouter/react';
import { useEffect, useState } from 'react';
import { Environment } from '@/react/portainer/environments/types';
import { snapshotEndpoints } from '@/react/portainer/environments/environment.service';
@ -9,6 +9,7 @@ import * as notifications from '@/portainer/services/notifications';
import { confirm } from '@@/modals/confirm';
import { PageHeader } from '@@/PageHeader';
import { ModalType } from '@@/modals';
import { buildConfirmButton } from '@@/modals/utils';
import { EnvironmentList } from './EnvironmentList';
import { EdgeLoadingSpinner } from './EdgeLoadingSpinner';
@ -17,10 +18,37 @@ import { LicenseNodePanel } from './LicenseNodePanel';
import { BackupFailedPanel } from './BackupFailedPanel';
export function HomeView() {
const [connectingToEdgeEndpoint, setConnectingToEdgeEndpoint] =
useState(false);
const { params } = useCurrentStateAndParams();
const [connectingToEdgeEndpoint, setConnectingToEdgeEndpoint] = useState(
!!params.redirect
);
const router = useRouter();
useEffect(() => {
async function redirect() {
const options = {
title: `Failed connecting to ${params.environmentName}`,
message: `There was an issue connecting to edge agent via tunnel. Click 'Retry' below to retry now, or wait 10 seconds to automatically retry.`,
confirmButton: buildConfirmButton('Retry', 'primary', 10),
modalType: ModalType.Destructive,
};
if (await confirm(options)) {
setConnectingToEdgeEndpoint(true);
router.stateService.go(params.route, {
endpointId: params.environmentId,
});
} else {
router.stateService.go('portainer.home', {}, { inherit: false });
}
}
if (params.redirect) {
redirect();
}
}, [params, setConnectingToEdgeEndpoint, router]);
return (
<>
<PageHeader