mirror of https://github.com/portainer/portainer
feat(edge): EE-4621 support high latency for tunnel (#8302)
parent
07df4b1591
commit
60275dd31c
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 });
|
||||
}
|
||||
});
|
||||
},
|
||||
|
|
|
@ -300,7 +300,7 @@ angular
|
|||
|
||||
var home = {
|
||||
name: 'portainer.home',
|
||||
url: '/home',
|
||||
url: '/home?redirect&environmentId&environmentName&route',
|
||||
views: {
|
||||
'content@': {
|
||||
component: 'homeView',
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -7,6 +7,7 @@ export interface ButtonOptions<TValue = undefined> {
|
|||
className?: string;
|
||||
color?: ComponentProps<typeof Button>['color'];
|
||||
value?: TValue;
|
||||
timeout?: number;
|
||||
}
|
||||
|
||||
export interface ButtonsOptions<T> {
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
height: 100%;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue