feat(environments): add edge device [EE-4840] (#8246)

* feat(environments): add edge device [EE-4840]

fix [EE-4840]

* fix(home): fix tests
pull/6820/head
Chaim Lev-Ari 2 years ago committed by GitHub
parent 6c193a8a45
commit baf9c3db0a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -32,8 +32,8 @@ export const viewsModule = angular
)
.component(
'settingsEdgeCompute',
r2a(withReactQuery(withCurrentUser(EdgeComputeSettingsView)), [
'onSubmit',
'settings',
])
r2a(
withUIRouter(withReactQuery(withCurrentUser(EdgeComputeSettingsView))),
['onSubmit', 'settings']
)
).name;

@ -1,6 +1,5 @@
import { createMockEnvironment } from '@/react-tools/test-mocks';
import { renderWithQueryClient } from '@/react-tools/test-utils';
import { rest, server } from '@/setup-tests/server';
import { EdgeIndicator } from './EdgeIndicator';
@ -25,8 +24,6 @@ async function renderComponent(
checkInInterval = 0,
queryDate = 0
) {
server.use(rest.get('/api/settings', (req, res, ctx) => res(ctx.json({}))));
const environment = createMockEnvironment();
environment.EdgeID = edgeId;

@ -1,6 +1,6 @@
import { Environment } from '@/react/portainer/environments/types';
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) {
const associated = !!environment.EdgeID;
@ -30,7 +30,7 @@ export function useHasHeartbeat(environment: Environment) {
function getCheckinInterval(
environment: Environment,
settings: PublicSettingsViewModel
settings: PublicSettingsResponse
) {
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 { validationSchema } from './EdgeComputeSettings.validation';
export interface FormValues {
EdgeAgentCheckinInterval: number;
EnableEdgeComputeFeatures: boolean;
EnforceEdgeID: boolean;
}
import { FormValues } from './types';
import { AddDeviceButton } from './AddDeviceButton';
interface Props {
settings?: Settings;
@ -33,7 +29,16 @@ export function EdgeComputeSettings({ settings, onSubmit }: Props) {
return (
<div className="row">
<Widget>
<WidgetTitle icon={Laptop} title="Edge Compute settings" />
<WidgetTitle
icon={Laptop}
title={
<>
<span className="mr-3">Edge Compute settings</span>
{settings.EnableEdgeComputeFeatures && <AddDeviceButton />}
</>
}
/>
<WidgetBody>
<Formik
initialValues={settings}

@ -1,7 +1,5 @@
export interface Settings {
EdgeAgentCheckinInterval: number;
export interface FormValues {
EnableEdgeComputeFeatures: boolean;
TrustOnFirstConnect: boolean;
EnforceEdgeID: boolean;
EdgePortainerUrl: string;
EdgeAgentCheckinInterval: number;
}

@ -5,7 +5,6 @@ import {
withError,
withInvalidate,
} from '@/react-tools/react-query';
import { PublicSettingsViewModel } from '@/portainer/models/settings';
import {
getSettings,
@ -13,18 +12,18 @@ import {
getPublicSettings,
updateDefaultRegistry,
} 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,
select,
onSuccess,
}: {
select?: (settings: PublicSettingsViewModel) => T;
select?: (settings: PublicSettingsResponse) => T;
enabled?: boolean;
onSuccess?: (data: T) => void;
} = {}) {
return useQuery(['settings', 'public'], () => getPublicSettings(), {
return useQuery(['settings', 'public'], getPublicSettings, {
select,
...withError('Unable to retrieve public settings'),
enabled,

@ -1,14 +1,13 @@
import { PublicSettingsViewModel } from '@/portainer/models/settings';
import axios, { parseAxiosError } from '@/portainer/services/axios';
import { DefaultRegistry, PublicSettingsResponse, Settings } from './types';
import { PublicSettingsResponse, DefaultRegistry, Settings } from './types';
export async function getPublicSettings() {
try {
const { data } = await axios.get<PublicSettingsResponse>(
buildUrl('public')
);
return new PublicSettingsViewModel(data);
return data;
} catch (e) {
throw parseAxiosError(
e as Error,

@ -137,23 +137,65 @@ export interface Settings {
};
}
interface GlobalDeploymentOptions {
/** Hide manual deploy forms in portainer */
hideAddWithForm: boolean;
/** Configure this per environment or globally */
perEnvOverride: boolean;
/** Hide the web editor in the remaining visible forms */
hideWebEditor: boolean;
/** Hide the file upload option in the remaining visible forms */
hideFileUpload: boolean;
}
export interface PublicSettingsResponse {
// 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
/** 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 */
LogoURL: string;
// Active authentication method for the Portainer instance. Valid values are: 1 for internal, 2 for LDAP, or 3 for oauth
/** The content in plaintext used to display in the login page. Will hide when value is empty string (only on BE) */
CustomLoginBanner: string;
/** Active authentication method for the Portainer instance. Valid values are: 1 for internal, 2 for LDAP, or 3 for oauth */
AuthenticationMethod: AuthenticationMethod;
// Whether edge compute features are enabled
/** 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: Record<string, boolean>;
// The URL used for oauth login
/** Supported feature flags */
Features: { [key: Feature]: boolean };
/** The URL used for oauth login */
OAuthLoginURI: string;
// The URL used for oauth logout
/** The URL used for oauth logout */
OAuthLogoutURI: string;
// Whether portainer internal auth view will be hidden
/** Whether portainer internal auth view will be hidden (only on BE) */
OAuthHideInternalAuth: boolean;
// Whether telemetry is enabled
/** Whether telemetry is enabled */
EnableTelemetry: boolean;
// The expiry of a Kubeconfig
/** 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>>(
'/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>>(
'/api/status',

Loading…
Cancel
Save