mirror of
https://github.com/portainer/portainer.git
synced 2025-07-24 15:59:41 +02:00
feat(edge): EE-4621 support high latency for tunnel (#8302)
This commit is contained in:
parent
07df4b1591
commit
60275dd31c
8 changed files with 111 additions and 23 deletions
|
@ -1,5 +1,7 @@
|
||||||
import angular from 'angular';
|
import angular from 'angular';
|
||||||
|
|
||||||
|
import { PortainerEndpointTypes } from 'Portainer/models/endpoint/models';
|
||||||
|
|
||||||
import { EnvironmentStatus } from '@/react/portainer/environments/types';
|
import { EnvironmentStatus } from '@/react/portainer/environments/types';
|
||||||
|
|
||||||
import { reactModule } from './react';
|
import { reactModule } from './react';
|
||||||
|
@ -16,14 +18,17 @@ angular.module('portainer.docker', ['portainer.app', reactModule]).config([
|
||||||
abstract: true,
|
abstract: true,
|
||||||
onEnter: /* @ngInject */ function onEnter(endpoint, $async, $state, EndpointService, Notifications, StateManager, SystemService) {
|
onEnter: /* @ngInject */ function onEnter(endpoint, $async, $state, EndpointService, Notifications, StateManager, SystemService) {
|
||||||
return $async(async () => {
|
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');
|
$state.go('portainer.home');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const status = await checkEndpointStatus(endpoint);
|
const status = await checkEndpointStatus(endpoint);
|
||||||
|
|
||||||
if (endpoint.Type !== 4) {
|
if (endpoint.Type !== PortainerEndpointTypes.EdgeAgentOnDockerEnvironment) {
|
||||||
await updateEndpointStatus(endpoint, status);
|
await updateEndpointStatus(endpoint, status);
|
||||||
}
|
}
|
||||||
endpoint.Status = status;
|
endpoint.Status = status;
|
||||||
|
@ -34,16 +39,22 @@ angular.module('portainer.docker', ['portainer.app', reactModule]).config([
|
||||||
|
|
||||||
await StateManager.updateEndpointState(endpoint);
|
await StateManager.updateEndpointState(endpoint);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Notifications.error('Failed loading environment', e);
|
let params = {};
|
||||||
$state.go('portainer.home', {}, { reload: true });
|
|
||||||
|
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) {
|
async function checkEndpointStatus(endpoint) {
|
||||||
try {
|
try {
|
||||||
await SystemService.ping(endpoint.Id);
|
await SystemService.ping(endpoint.Id);
|
||||||
return 1;
|
return EnvironmentStatus.Up;
|
||||||
} catch (e) {
|
} 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 registriesModule from './registries';
|
||||||
import customTemplateModule from './custom-templates';
|
import customTemplateModule from './custom-templates';
|
||||||
import { reactModule } from './react';
|
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) {
|
onEnter: /* @ngInject */ function onEnter($async, $state, endpoint, KubernetesHealthService, KubernetesNamespaceService, Notifications, StateManager) {
|
||||||
return $async(async () => {
|
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');
|
$state.go('portainer.home');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
if (endpoint.Type === 7) {
|
if (endpoint.Type === PortainerEndpointTypes.EdgeAgentOnKubernetesEnvironment) {
|
||||||
//edge
|
//edge
|
||||||
try {
|
try {
|
||||||
await KubernetesHealthService.ping(endpoint.Id);
|
await KubernetesHealthService.ping(endpoint.Id);
|
||||||
endpoint.Status = 1;
|
endpoint.Status = EnvironmentStatus.Up;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
endpoint.Status = 2;
|
endpoint.Status = EnvironmentStatus.Down;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await StateManager.updateEndpointState(endpoint);
|
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.');
|
throw new Error('Unable to contact Edge agent, please ensure that the agent is properly running on the remote environment.');
|
||||||
}
|
}
|
||||||
|
|
||||||
await KubernetesNamespaceService.get();
|
await KubernetesNamespaceService.get();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Notifications.error('Failed loading environment', e);
|
let params = {};
|
||||||
$state.go('portainer.home', {}, { reload: true });
|
|
||||||
|
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 = {
|
var home = {
|
||||||
name: 'portainer.home',
|
name: 'portainer.home',
|
||||||
url: '/home',
|
url: '/home?redirect&environmentId&environmentName&route',
|
||||||
views: {
|
views: {
|
||||||
'content@': {
|
'content@': {
|
||||||
component: 'homeView',
|
component: 'homeView',
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { ReactNode } from 'react';
|
import { ReactNode, useEffect, useState, useRef } from 'react';
|
||||||
|
|
||||||
import { Button } from '@@/buttons';
|
import { Button } from '@@/buttons';
|
||||||
|
|
||||||
|
@ -26,6 +26,37 @@ export function Dialog<T>({
|
||||||
}: Props<T>) {
|
}: Props<T>) {
|
||||||
const ariaLabel = requireString(title) || requireString(message) || 'Dialog';
|
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 (
|
return (
|
||||||
<Modal onDismiss={() => onSubmit()} aria-label={ariaLabel}>
|
<Modal onDismiss={() => onSubmit()} aria-label={ariaLabel}>
|
||||||
{title && <Modal.Header title={title} modalType={modalType} />}
|
{title && <Modal.Header title={title} modalType={modalType} />}
|
||||||
|
@ -39,7 +70,7 @@ export function Dialog<T>({
|
||||||
key={index}
|
key={index}
|
||||||
size="medium"
|
size="medium"
|
||||||
>
|
>
|
||||||
{button.label}
|
{button.label} {button.timeout && count ? `(${count})` : null}
|
||||||
</Button>
|
</Button>
|
||||||
))}
|
))}
|
||||||
</Modal.Footer>
|
</Modal.Footer>
|
||||||
|
|
|
@ -7,6 +7,7 @@ export interface ButtonOptions<TValue = undefined> {
|
||||||
className?: string;
|
className?: string;
|
||||||
color?: ComponentProps<typeof Button>['color'];
|
color?: ComponentProps<typeof Button>['color'];
|
||||||
value?: TValue;
|
value?: TValue;
|
||||||
|
timeout?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ButtonsOptions<T> {
|
export interface ButtonsOptions<T> {
|
||||||
|
|
|
@ -6,9 +6,10 @@ import { ButtonOptions } from './types';
|
||||||
|
|
||||||
export function buildConfirmButton(
|
export function buildConfirmButton(
|
||||||
label = 'Confirm',
|
label = 'Confirm',
|
||||||
color: ComponentProps<typeof Button>['color'] = 'primary'
|
color: ComponentProps<typeof Button>['color'] = 'primary',
|
||||||
|
timeout = 0
|
||||||
): ButtonOptions<true> {
|
): ButtonOptions<true> {
|
||||||
return { label, color, value: true };
|
return { label, color, value: true, timeout };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildCancelButton(label = 'Cancel'): ButtonOptions<false> {
|
export function buildCancelButton(label = 'Cancel'): ButtonOptions<false> {
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
height: 100%;
|
height: 100%;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { useRouter } from '@uirouter/react';
|
import { useCurrentStateAndParams, useRouter } from '@uirouter/react';
|
||||||
import { useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
import { Environment } from '@/react/portainer/environments/types';
|
import { Environment } from '@/react/portainer/environments/types';
|
||||||
import { snapshotEndpoints } from '@/react/portainer/environments/environment.service';
|
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 { confirm } from '@@/modals/confirm';
|
||||||
import { PageHeader } from '@@/PageHeader';
|
import { PageHeader } from '@@/PageHeader';
|
||||||
import { ModalType } from '@@/modals';
|
import { ModalType } from '@@/modals';
|
||||||
|
import { buildConfirmButton } from '@@/modals/utils';
|
||||||
|
|
||||||
import { EnvironmentList } from './EnvironmentList';
|
import { EnvironmentList } from './EnvironmentList';
|
||||||
import { EdgeLoadingSpinner } from './EdgeLoadingSpinner';
|
import { EdgeLoadingSpinner } from './EdgeLoadingSpinner';
|
||||||
|
@ -17,10 +18,37 @@ import { LicenseNodePanel } from './LicenseNodePanel';
|
||||||
import { BackupFailedPanel } from './BackupFailedPanel';
|
import { BackupFailedPanel } from './BackupFailedPanel';
|
||||||
|
|
||||||
export function HomeView() {
|
export function HomeView() {
|
||||||
const [connectingToEdgeEndpoint, setConnectingToEdgeEndpoint] =
|
const { params } = useCurrentStateAndParams();
|
||||||
useState(false);
|
const [connectingToEdgeEndpoint, setConnectingToEdgeEndpoint] = useState(
|
||||||
|
!!params.redirect
|
||||||
|
);
|
||||||
|
|
||||||
const router = useRouter();
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<PageHeader
|
<PageHeader
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue