mirror of https://github.com/portainer/portainer
feat(environments): add edge device [EE-4840] (#8246)
* feat(environments): add edge device [EE-4840] fix [EE-4840] * fix(home): fix testspull/6820/head
parent
6c193a8a45
commit
baf9c3db0a
|
@ -32,8 +32,8 @@ export const viewsModule = angular
|
||||||
)
|
)
|
||||||
.component(
|
.component(
|
||||||
'settingsEdgeCompute',
|
'settingsEdgeCompute',
|
||||||
r2a(withReactQuery(withCurrentUser(EdgeComputeSettingsView)), [
|
r2a(
|
||||||
'onSubmit',
|
withUIRouter(withReactQuery(withCurrentUser(EdgeComputeSettingsView))),
|
||||||
'settings',
|
['onSubmit', 'settings']
|
||||||
])
|
)
|
||||||
).name;
|
).name;
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import { createMockEnvironment } from '@/react-tools/test-mocks';
|
import { createMockEnvironment } from '@/react-tools/test-mocks';
|
||||||
import { renderWithQueryClient } from '@/react-tools/test-utils';
|
import { renderWithQueryClient } from '@/react-tools/test-utils';
|
||||||
import { rest, server } from '@/setup-tests/server';
|
|
||||||
|
|
||||||
import { EdgeIndicator } from './EdgeIndicator';
|
import { EdgeIndicator } from './EdgeIndicator';
|
||||||
|
|
||||||
|
@ -25,8 +24,6 @@ async function renderComponent(
|
||||||
checkInInterval = 0,
|
checkInInterval = 0,
|
||||||
queryDate = 0
|
queryDate = 0
|
||||||
) {
|
) {
|
||||||
server.use(rest.get('/api/settings', (req, res, ctx) => res(ctx.json({}))));
|
|
||||||
|
|
||||||
const environment = createMockEnvironment();
|
const environment = createMockEnvironment();
|
||||||
|
|
||||||
environment.EdgeID = edgeId;
|
environment.EdgeID = edgeId;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Environment } from '@/react/portainer/environments/types';
|
import { Environment } from '@/react/portainer/environments/types';
|
||||||
import { usePublicSettings } from '@/react/portainer/settings/queries';
|
import { usePublicSettings } from '@/react/portainer/settings/queries';
|
||||||
import { PublicSettingsViewModel } from '@/portainer/models/settings';
|
import { PublicSettingsResponse } from '@/react/portainer/settings/types';
|
||||||
|
|
||||||
export function useHasHeartbeat(environment: Environment) {
|
export function useHasHeartbeat(environment: Environment) {
|
||||||
const associated = !!environment.EdgeID;
|
const associated = !!environment.EdgeID;
|
||||||
|
@ -30,7 +30,7 @@ export function useHasHeartbeat(environment: Environment) {
|
||||||
|
|
||||||
function getCheckinInterval(
|
function getCheckinInterval(
|
||||||
environment: Environment,
|
environment: Environment,
|
||||||
settings: PublicSettingsViewModel
|
settings: PublicSettingsResponse
|
||||||
) {
|
) {
|
||||||
const asyncMode = environment.Edge.AsyncMode;
|
const asyncMode = environment.Edge.AsyncMode;
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,71 @@
|
||||||
|
import { useRouter } from '@uirouter/react';
|
||||||
|
import { Plus } from 'lucide-react';
|
||||||
|
|
||||||
|
import { promptAsync } from '@/portainer/services/modal.service/prompt';
|
||||||
|
|
||||||
|
import { Button } from '@@/buttons';
|
||||||
|
|
||||||
|
import { usePublicSettings } from '../../queries';
|
||||||
|
|
||||||
|
enum DeployType {
|
||||||
|
FDO = 'FDO',
|
||||||
|
MANUAL = 'MANUAL',
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AddDeviceButton() {
|
||||||
|
const router = useRouter();
|
||||||
|
const isFDOEnabledQuery = usePublicSettings({
|
||||||
|
select: (settings) => settings.IsFDOEnabled,
|
||||||
|
});
|
||||||
|
const isFDOEnabled = !!isFDOEnabledQuery.data;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button onClick={handleNewDeviceClick} icon={Plus}>
|
||||||
|
Add Device
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
|
||||||
|
async function handleNewDeviceClick() {
|
||||||
|
const result = await getDeployType();
|
||||||
|
|
||||||
|
switch (result) {
|
||||||
|
case DeployType.FDO:
|
||||||
|
router.stateService.go('portainer.endpoints.importDevice');
|
||||||
|
break;
|
||||||
|
case DeployType.MANUAL:
|
||||||
|
router.stateService.go('portainer.wizard.endpoints', {
|
||||||
|
edgeDevice: true,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDeployType(): Promise<DeployType> {
|
||||||
|
if (!isFDOEnabled) {
|
||||||
|
return Promise.resolve(DeployType.MANUAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
return promptAsync({
|
||||||
|
title: 'How would you like to add an Edge Device?',
|
||||||
|
inputType: 'radio',
|
||||||
|
inputOptions: [
|
||||||
|
{
|
||||||
|
text: 'Provision bare-metal using Intel FDO',
|
||||||
|
value: DeployType.FDO,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Deploy agent manually',
|
||||||
|
value: DeployType.MANUAL,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
buttons: {
|
||||||
|
confirm: {
|
||||||
|
label: 'Confirm',
|
||||||
|
className: 'btn-primary',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}) as Promise<DeployType>;
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,12 +13,8 @@ import { FormSectionTitle } from '@@/form-components/FormSectionTitle';
|
||||||
import { Settings } from '../types';
|
import { Settings } from '../types';
|
||||||
|
|
||||||
import { validationSchema } from './EdgeComputeSettings.validation';
|
import { validationSchema } from './EdgeComputeSettings.validation';
|
||||||
|
import { FormValues } from './types';
|
||||||
export interface FormValues {
|
import { AddDeviceButton } from './AddDeviceButton';
|
||||||
EdgeAgentCheckinInterval: number;
|
|
||||||
EnableEdgeComputeFeatures: boolean;
|
|
||||||
EnforceEdgeID: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
settings?: Settings;
|
settings?: Settings;
|
||||||
|
@ -33,7 +29,16 @@ export function EdgeComputeSettings({ settings, onSubmit }: Props) {
|
||||||
return (
|
return (
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<Widget>
|
<Widget>
|
||||||
<WidgetTitle icon={Laptop} title="Edge Compute settings" />
|
<WidgetTitle
|
||||||
|
icon={Laptop}
|
||||||
|
title={
|
||||||
|
<>
|
||||||
|
<span className="mr-3">Edge Compute settings</span>
|
||||||
|
{settings.EnableEdgeComputeFeatures && <AddDeviceButton />}
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
<WidgetBody>
|
<WidgetBody>
|
||||||
<Formik
|
<Formik
|
||||||
initialValues={settings}
|
initialValues={settings}
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
export interface Settings {
|
export interface FormValues {
|
||||||
EdgeAgentCheckinInterval: number;
|
|
||||||
EnableEdgeComputeFeatures: boolean;
|
EnableEdgeComputeFeatures: boolean;
|
||||||
TrustOnFirstConnect: boolean;
|
|
||||||
EnforceEdgeID: boolean;
|
EnforceEdgeID: boolean;
|
||||||
EdgePortainerUrl: string;
|
EdgeAgentCheckinInterval: number;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,6 @@ import {
|
||||||
withError,
|
withError,
|
||||||
withInvalidate,
|
withInvalidate,
|
||||||
} from '@/react-tools/react-query';
|
} from '@/react-tools/react-query';
|
||||||
import { PublicSettingsViewModel } from '@/portainer/models/settings';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
getSettings,
|
getSettings,
|
||||||
|
@ -13,18 +12,18 @@ import {
|
||||||
getPublicSettings,
|
getPublicSettings,
|
||||||
updateDefaultRegistry,
|
updateDefaultRegistry,
|
||||||
} from './settings.service';
|
} from './settings.service';
|
||||||
import { DefaultRegistry, Settings } from './types';
|
import { DefaultRegistry, PublicSettingsResponse, Settings } from './types';
|
||||||
|
|
||||||
export function usePublicSettings<T = PublicSettingsViewModel>({
|
export function usePublicSettings<T = PublicSettingsResponse>({
|
||||||
enabled,
|
enabled,
|
||||||
select,
|
select,
|
||||||
onSuccess,
|
onSuccess,
|
||||||
}: {
|
}: {
|
||||||
select?: (settings: PublicSettingsViewModel) => T;
|
select?: (settings: PublicSettingsResponse) => T;
|
||||||
enabled?: boolean;
|
enabled?: boolean;
|
||||||
onSuccess?: (data: T) => void;
|
onSuccess?: (data: T) => void;
|
||||||
} = {}) {
|
} = {}) {
|
||||||
return useQuery(['settings', 'public'], () => getPublicSettings(), {
|
return useQuery(['settings', 'public'], getPublicSettings, {
|
||||||
select,
|
select,
|
||||||
...withError('Unable to retrieve public settings'),
|
...withError('Unable to retrieve public settings'),
|
||||||
enabled,
|
enabled,
|
||||||
|
|
|
@ -1,14 +1,13 @@
|
||||||
import { PublicSettingsViewModel } from '@/portainer/models/settings';
|
|
||||||
import axios, { parseAxiosError } from '@/portainer/services/axios';
|
import axios, { parseAxiosError } from '@/portainer/services/axios';
|
||||||
|
|
||||||
import { DefaultRegistry, PublicSettingsResponse, Settings } from './types';
|
import { PublicSettingsResponse, DefaultRegistry, Settings } from './types';
|
||||||
|
|
||||||
export async function getPublicSettings() {
|
export async function getPublicSettings() {
|
||||||
try {
|
try {
|
||||||
const { data } = await axios.get<PublicSettingsResponse>(
|
const { data } = await axios.get<PublicSettingsResponse>(
|
||||||
buildUrl('public')
|
buildUrl('public')
|
||||||
);
|
);
|
||||||
return new PublicSettingsViewModel(data);
|
return data;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw parseAxiosError(
|
throw parseAxiosError(
|
||||||
e as Error,
|
e as Error,
|
||||||
|
|
|
@ -137,23 +137,65 @@ export interface Settings {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PublicSettingsResponse {
|
interface GlobalDeploymentOptions {
|
||||||
// URL to a logo that will be displayed on the login page as well as on top of the sidebar. Will use default Portainer logo when value is empty string
|
/** Hide manual deploy forms in portainer */
|
||||||
LogoURL: string;
|
hideAddWithForm: boolean;
|
||||||
// Active authentication method for the Portainer instance. Valid values are: 1 for internal, 2 for LDAP, or 3 for oauth
|
/** Configure this per environment or globally */
|
||||||
AuthenticationMethod: AuthenticationMethod;
|
perEnvOverride: boolean;
|
||||||
// Whether edge compute features are enabled
|
/** Hide the web editor in the remaining visible forms */
|
||||||
EnableEdgeComputeFeatures: boolean;
|
hideWebEditor: boolean;
|
||||||
// Supported feature flags
|
/** Hide the file upload option in the remaining visible forms */
|
||||||
Features: Record<string, boolean>;
|
hideFileUpload: boolean;
|
||||||
// The URL used for oauth login
|
}
|
||||||
OAuthLoginURI: string;
|
|
||||||
// The URL used for oauth logout
|
export interface PublicSettingsResponse {
|
||||||
OAuthLogoutURI: string;
|
/** URL to a logo that will be displayed on the login page as well as on top of the sidebar. Will use default Portainer logo when value is empty string */
|
||||||
// Whether portainer internal auth view will be hidden
|
LogoURL: string;
|
||||||
OAuthHideInternalAuth: boolean;
|
/** The content in plaintext used to display in the login page. Will hide when value is empty string (only on BE) */
|
||||||
// Whether telemetry is enabled
|
CustomLoginBanner: string;
|
||||||
EnableTelemetry: boolean;
|
/** Active authentication method for the Portainer instance. Valid values are: 1 for internal, 2 for LDAP, or 3 for oauth */
|
||||||
// The expiry of a Kubeconfig
|
AuthenticationMethod: AuthenticationMethod;
|
||||||
KubeconfigExpiry: string;
|
/** The minimum required length for a password of any user when using internal auth mode */
|
||||||
|
RequiredPasswordLength: number;
|
||||||
|
/** Deployment options for encouraging deployment as code (only on BE) */
|
||||||
|
GlobalDeploymentOptions: GlobalDeploymentOptions;
|
||||||
|
/** Show the Kompose build option (discontinued in 2.18) */
|
||||||
|
ShowKomposeBuildOption: boolean;
|
||||||
|
/** Whether edge compute features are enabled */
|
||||||
|
EnableEdgeComputeFeatures: boolean;
|
||||||
|
/** Supported feature flags */
|
||||||
|
Features: { [key: Feature]: boolean };
|
||||||
|
/** The URL used for oauth login */
|
||||||
|
OAuthLoginURI: string;
|
||||||
|
/** The URL used for oauth logout */
|
||||||
|
OAuthLogoutURI: string;
|
||||||
|
/** Whether portainer internal auth view will be hidden (only on BE) */
|
||||||
|
OAuthHideInternalAuth: boolean;
|
||||||
|
/** Whether telemetry is enabled */
|
||||||
|
EnableTelemetry: boolean;
|
||||||
|
/** The expiry of a Kubeconfig */
|
||||||
|
KubeconfigExpiry: string;
|
||||||
|
/** Whether team sync is enabled */
|
||||||
|
TeamSync: boolean;
|
||||||
|
/** Whether FDO is enabled */
|
||||||
|
IsFDOEnabled: boolean;
|
||||||
|
/** Whether AMT is enabled */
|
||||||
|
IsAMTEnabled: boolean;
|
||||||
|
|
||||||
|
/** Whether to hide default registry (only on BE) */
|
||||||
|
DefaultRegistry: {
|
||||||
|
Hide: boolean;
|
||||||
|
};
|
||||||
|
Edge: {
|
||||||
|
/** Whether the device has been started in edge async mode */
|
||||||
|
AsyncMode: boolean;
|
||||||
|
/** The ping interval for edge agent - used in edge async mode [seconds] */
|
||||||
|
PingInterval: number;
|
||||||
|
/** The snapshot interval for edge agent - used in edge async mode [seconds] */
|
||||||
|
SnapshotInterval: number;
|
||||||
|
/** The command list interval for edge agent - used in edge async mode [seconds] */
|
||||||
|
CommandInterval: number;
|
||||||
|
/** The check in interval for edge agent (in seconds) - used in non async mode [seconds] */
|
||||||
|
CheckinInterval: number;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -74,7 +74,18 @@ export const handlers = [
|
||||||
}),
|
}),
|
||||||
rest.get<DefaultRequestBody, PathParams, Partial<PublicSettingsResponse>>(
|
rest.get<DefaultRequestBody, PathParams, Partial<PublicSettingsResponse>>(
|
||||||
'/api/settings/public',
|
'/api/settings/public',
|
||||||
(req, res, ctx) => res(ctx.json({}))
|
(req, res, ctx) =>
|
||||||
|
res(
|
||||||
|
ctx.json({
|
||||||
|
Edge: {
|
||||||
|
AsyncMode: false,
|
||||||
|
CheckinInterval: 60,
|
||||||
|
CommandInterval: 60,
|
||||||
|
PingInterval: 60,
|
||||||
|
SnapshotInterval: 60,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
)
|
||||||
),
|
),
|
||||||
rest.get<DefaultRequestBody, PathParams, Partial<StatusResponse>>(
|
rest.get<DefaultRequestBody, PathParams, Partial<StatusResponse>>(
|
||||||
'/api/status',
|
'/api/status',
|
||||||
|
|
Loading…
Reference in New Issue