@balena/ui-shared-components 12.1.0-build-secure-boot-acb8e3d7ac5b0201d6d8bbbb6031f9ade9d1e28c-1 → 12.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/DownloadImageDialog/ApplicationInstructions.d.ts +0 -1
- package/dist/components/DownloadImageDialog/ApplicationInstructions.js +33 -26
- package/dist/components/DownloadImageDialog/ImageForm.d.ts +0 -2
- package/dist/components/DownloadImageDialog/ImageForm.js +82 -147
- package/dist/components/DownloadImageDialog/VariantSelector.js +1 -1
- package/dist/components/DownloadImageDialog/index.d.ts +0 -1
- package/dist/components/DownloadImageDialog/index.js +3 -10
- package/dist/components/HighlightedName/index.js +1 -3
- package/dist/components/RJST/DataTypes/object.js +4 -2
- package/dist/components/RJST/Lenses/types/index.d.ts +3 -4
- package/dist/components/RJST/Lenses/types/table.js +5 -2
- package/dist/components/RJST/components/Table/AddTagHandler.d.ts +1 -1
- package/dist/components/RJST/components/Table/TableActions.d.ts +1 -1
- package/dist/components/RJST/components/Table/TableCell.d.ts +1 -1
- package/dist/components/RJST/components/Table/TableHeader.d.ts +1 -1
- package/dist/components/RJST/components/Table/TableRow.d.ts +1 -1
- package/dist/components/RJST/components/Table/TableToolbar.d.ts +1 -1
- package/dist/components/RJST/components/Table/index.d.ts +3 -3
- package/dist/components/RJST/components/Table/index.js +2 -1
- package/dist/components/RJST/components/Table/useColumns.d.ts +3 -3
- package/dist/components/RJST/components/Table/utils.d.ts +3 -1
- package/dist/components/RJST/index.d.ts +0 -13
- package/dist/components/RJST/oData/jsonToOData.d.ts +1 -1
- package/dist/components/RJST/oData/jsonToOData.js +2 -2
- package/dist/components/RJST/schemaOps.d.ts +13 -0
- package/package.json +3 -4
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import has from 'lodash/has';
|
|
2
3
|
import { interpolateMustache } from './utils';
|
|
3
4
|
import { Box, List, Tab, Tabs, Typography } from '@mui/material';
|
|
4
5
|
import { memo, useEffect, useMemo, useState } from 'react';
|
|
5
6
|
import { MUILinkWithTracking } from '../MUILinkWithTracking';
|
|
6
7
|
import { OrderedListItem } from '../OrderedListItem';
|
|
7
8
|
import { Markdown } from '../Markdown';
|
|
8
|
-
import { Chip } from '../Chip';
|
|
9
|
-
import { token } from '../../utils/token';
|
|
10
9
|
export const getUserOs = () => {
|
|
11
10
|
const platform = window.navigator.platform.toLowerCase();
|
|
12
11
|
if (platform.includes('win')) {
|
|
@@ -20,13 +19,12 @@ export const getUserOs = () => {
|
|
|
20
19
|
}
|
|
21
20
|
return 'Unknown';
|
|
22
21
|
};
|
|
23
|
-
const SECURE_BOOT_INSTRUCTION_TO_REPLACE_START = 'Press the F10 key while BIOS';
|
|
24
22
|
const dtJsonTocontractOsKeyMap = {
|
|
25
23
|
windows: 'Windows',
|
|
26
24
|
osx: 'MacOS',
|
|
27
25
|
linux: 'Linux',
|
|
28
26
|
};
|
|
29
|
-
export const ApplicationInstructions = memo(function ApplicationInstructions({ deviceType, templateData,
|
|
27
|
+
export const ApplicationInstructions = memo(function ApplicationInstructions({ deviceType, templateData, }) {
|
|
30
28
|
var _a;
|
|
31
29
|
const [currentOs, setCurrentOs] = useState(getUserOs());
|
|
32
30
|
const instructions = useMemo(() => {
|
|
@@ -66,28 +64,37 @@ export const ApplicationInstructions = memo(function ApplicationInstructions({ d
|
|
|
66
64
|
setCurrentOs(value !== null && value !== void 0 ? value : 'Unknown');
|
|
67
65
|
}, "aria-label": "os tabs", children: Object.keys(instructions).map((os) => {
|
|
68
66
|
return _jsx(Tab, { label: os, value: os }, os);
|
|
69
|
-
}) }) }) })), _jsx(InstructionsList, { instructions: finalInstructions
|
|
67
|
+
}) }) }) })), _jsx(InstructionsList, { instructions: finalInstructions }), _jsx(Box, { mt: 2, children: _jsxs(Typography, { children: ["For more details please refer to our", ' ', _jsx(MUILinkWithTracking, { href: `https://www.balena.io/docs/learn/getting-started/${deviceType.slug}/nodejs/`, children: "Getting Started Guide" }), "."] }) })] }));
|
|
70
68
|
});
|
|
71
|
-
const InstructionsItem = ({
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
69
|
+
const InstructionsItem = ({ node, index }) => {
|
|
70
|
+
const hasChildren = has(node, 'children');
|
|
71
|
+
let text = null;
|
|
72
|
+
if (typeof node === 'string') {
|
|
73
|
+
text = node;
|
|
74
|
+
}
|
|
75
|
+
if (node === null || node === void 0 ? void 0 : node.text) {
|
|
76
|
+
text = node.text;
|
|
77
|
+
}
|
|
78
|
+
return (_jsxs(OrderedListItem, { index: index + 1, sx: { maxWidth: '100%' }, children: [_jsx(Markdown, { components: {
|
|
79
|
+
code: (codeProps) => {
|
|
80
|
+
return (_jsx("code", Object.assign({ style: {
|
|
81
|
+
maxWidth: '100%',
|
|
82
|
+
display: 'inline-block',
|
|
83
|
+
whiteSpace: 'normal',
|
|
84
|
+
wordBreak: 'break-all',
|
|
85
|
+
wordWrap: 'break-word',
|
|
86
|
+
} }, codeProps)));
|
|
87
|
+
},
|
|
88
|
+
br: () => {
|
|
89
|
+
return _jsx("p", {});
|
|
90
|
+
},
|
|
91
|
+
p: ({ children }) => (_jsx("p", { style: { marginTop: 0, marginBottom: 0 }, children: children })),
|
|
92
|
+
}, children: text }), hasChildren && (_jsx(List, { children: node.children.map((item, i) => {
|
|
93
|
+
return _jsx(InstructionsItem, { node: item, index: i }, i);
|
|
94
|
+
}) }))] }));
|
|
88
95
|
};
|
|
89
|
-
const InstructionsList = ({ instructions
|
|
90
|
-
return (_jsx(List, { children: instructions.map((
|
|
91
|
-
return
|
|
96
|
+
const InstructionsList = ({ instructions }) => {
|
|
97
|
+
return (_jsx(List, { children: instructions.map((item, i) => {
|
|
98
|
+
return _jsx(InstructionsItem, { node: item, index: i }, `${item}_${i}`);
|
|
92
99
|
}) }));
|
|
93
100
|
};
|
|
@@ -16,7 +16,5 @@ interface ImageFormProps {
|
|
|
16
16
|
onSelectedOsTypeChange: (osType: string) => void;
|
|
17
17
|
onChange: (key: keyof DownloadImageFormModel, value: DownloadImageFormModel[keyof DownloadImageFormModel]) => void;
|
|
18
18
|
}
|
|
19
|
-
export declare const GENERIC_X86_SLUG = "generic-amd64";
|
|
20
|
-
export declare const GENERIC_X86_MINIMUM_SUPPORTED_SECUREBOOT_VERSION = "5.3.15";
|
|
21
19
|
export declare const ImageForm: import("react").NamedExoticComponent<ImageFormProps>;
|
|
22
20
|
export {};
|
|
@@ -1,25 +1,22 @@
|
|
|
1
1
|
import { __rest } from "tslib";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
-
import { Avatar, Box, Checkbox, Chip, Divider, FormControl, FormControlLabel, FormLabel, InputAdornment, InputLabel, Radio, RadioGroup, TextField, Tooltip, Typography, IconButton, Autocomplete, Stack,
|
|
3
|
+
import { Avatar, Box, Button, Checkbox, Chip, Collapse, Divider, FormControl, FormControlLabel, FormLabel, InputAdornment, InputLabel, Radio, RadioGroup, TextField, Tooltip, Typography, IconButton, Autocomplete, Stack, } from '@mui/material';
|
|
4
4
|
import HelpIcon from '@mui/icons-material/Help';
|
|
5
5
|
import { memo, useCallback, useEffect, useMemo, useState } from 'react';
|
|
6
6
|
import { getPreferredVersionOpts, transformVersions } from './version';
|
|
7
7
|
import { OsTypeSelector } from './OsTypeSelector';
|
|
8
8
|
import { VariantSelector } from './VariantSelector';
|
|
9
|
+
import AddIcon from '@mui/icons-material/Add';
|
|
10
|
+
import RemoveIcon from '@mui/icons-material/Remove';
|
|
9
11
|
import ArticleIcon from '@mui/icons-material/Article';
|
|
10
12
|
import { MUILinkWithTracking } from '../MUILinkWithTracking';
|
|
11
13
|
import { Visibility, VisibilityOff } from '@mui/icons-material';
|
|
12
14
|
import { FALLBACK_LOGO_UNKNOWN_DEVICE } from './utils';
|
|
13
15
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
|
14
|
-
import {
|
|
15
|
-
import * as semver from 'balena-semver';
|
|
16
|
+
import { faTriangleExclamation } from '@fortawesome/free-solid-svg-icons';
|
|
16
17
|
import { Callout } from '../Callout';
|
|
17
18
|
import { token } from '../../utils/token';
|
|
18
|
-
import { getFromLocalStorage, setToLocalStorage } from '../../utils/storage';
|
|
19
|
-
import { NewChip } from '../NewChip';
|
|
20
|
-
import { useAnalyticsContext } from '../../contexts/AnalyticsContext';
|
|
21
19
|
const POLL_INTERVAL_DOCS = 'https://www.balena.io/docs/reference/supervisor/bandwidth-reduction/#side-effects--warnings';
|
|
22
|
-
const SECURE_BOOT_AND_FULL_DISK_ENCRYPTION_DOCS = 'https://docs.balena.io/reference/OS/secure-boot-and-full-disk-encryption/overview/';
|
|
23
20
|
const getCategorizedVersions = (deviceTypeOsVersions, deviceType, osType) => {
|
|
24
21
|
var _a;
|
|
25
22
|
const osVersions = (_a = deviceTypeOsVersions[deviceType.slug]) !== null && _a !== void 0 ? _a : [];
|
|
@@ -39,14 +36,9 @@ const lineMap = {
|
|
|
39
36
|
sunset: 'yellow',
|
|
40
37
|
outdated: 'red',
|
|
41
38
|
};
|
|
42
|
-
export const GENERIC_X86_SLUG = 'generic-amd64';
|
|
43
|
-
export const GENERIC_X86_MINIMUM_SUPPORTED_SECUREBOOT_VERSION = '5.3.15';
|
|
44
39
|
export const ImageForm = memo(function ImageForm({ compatibleDeviceTypes, osVersions, isInitialDefault, osType, osTypes, hasEsrVersions, model, formElement, downloadUrl, applicationId, authToken, onSelectedOsTypeChange, onChange, }) {
|
|
45
40
|
var _a, _b;
|
|
46
|
-
const { state } = useAnalyticsContext();
|
|
47
41
|
const [showAdvancedSettings, setShowAdvancedSettings] = useState(false);
|
|
48
|
-
const [showSecureBootConfirmationDialog, setShowSecureBootConfirmationDialog,] = useState(false);
|
|
49
|
-
const [dontShowSecureBootWarningAgain, setDontShowSecureBootWarningAgain] = useState(false);
|
|
50
42
|
const [showPassword, setShowPassword] = useState(false);
|
|
51
43
|
const [version, setVersion] = useState();
|
|
52
44
|
const [variant, setVariant] = useState('prod');
|
|
@@ -54,17 +46,6 @@ export const ImageForm = memo(function ImageForm({ compatibleDeviceTypes, osVers
|
|
|
54
46
|
const { selectionOpts, preferredSelectionOpts } = useMemo(() => getCategorizedVersions(osVersions, model.deviceType, osType), [osVersions, model.deviceType, osType]);
|
|
55
47
|
const versionSelectionOpts = useMemo(() => (showAllVersions ? selectionOpts : preferredSelectionOpts), [preferredSelectionOpts, selectionOpts, showAllVersions]);
|
|
56
48
|
const showAllVersionsToggle = useMemo(() => preferredSelectionOpts.length < selectionOpts.length, [preferredSelectionOpts.length, selectionOpts.length]);
|
|
57
|
-
const supportsSecureBoot = useMemo(() => {
|
|
58
|
-
return (model.deviceType.slug === GENERIC_X86_SLUG &&
|
|
59
|
-
semver.gte(model.version, GENERIC_X86_MINIMUM_SUPPORTED_SECUREBOOT_VERSION));
|
|
60
|
-
}, [model.deviceType.slug, model.version]);
|
|
61
|
-
const secureBootDontShowAgainKey = `${model.deviceType.slug}_secureboot_warning_do_not_show_again`;
|
|
62
|
-
const dismissSecureBootWarning = useCallback((accepted, dontShowAgain) => {
|
|
63
|
-
var _a;
|
|
64
|
-
setShowSecureBootConfirmationDialog(false);
|
|
65
|
-
(_a = state.webTracker) === null || _a === void 0 ? void 0 : _a.track('Application Add Device Modal Hide Secure Boot Warning', { accepted, dontShowAgain });
|
|
66
|
-
setDontShowSecureBootWarningAgain(false);
|
|
67
|
-
}, [state.webTracker]);
|
|
68
49
|
const handleVariantChange = useCallback((v) => {
|
|
69
50
|
setVariant(v);
|
|
70
51
|
onChange('developmentMode', v === 'dev');
|
|
@@ -118,132 +99,86 @@ export const ImageForm = memo(function ImageForm({ compatibleDeviceTypes, osVers
|
|
|
118
99
|
onChange('deviceType', newDeviceType);
|
|
119
100
|
}, [compatibleDeviceTypes, model.deviceType.slug, onChange]);
|
|
120
101
|
const recommendedVersion = useMemo(() => { var _a; return (_a = versionSelectionOpts.find((v) => { var _a; return !((_a = v.knownIssueList) === null || _a === void 0 ? void 0 : _a.length); })) === null || _a === void 0 ? void 0 : _a.value; }, [versionSelectionOpts]);
|
|
121
|
-
return (_jsxs(
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
if (!value) {
|
|
132
|
-
return;
|
|
133
|
-
}
|
|
134
|
-
handleSelectedDeviceTypeChange(value);
|
|
135
|
-
}, disableClearable: true,
|
|
136
|
-
// TODO: consider whether there is a better solution than letting the width vary as you search
|
|
137
|
-
slotProps: {
|
|
138
|
-
popper: { sx: { width: 'fit-content' } },
|
|
139
|
-
} })] })), (!isInitialDefault || osType) &&
|
|
140
|
-
hasEsrVersions &&
|
|
141
|
-
model.deviceType && (_jsx(OsTypeSelector, { supportedOsTypes: osTypes, hasEsrVersions: hasEsrVersions !== null && hasEsrVersions !== void 0 ? hasEsrVersions : false, selectedOsTypeSlug: osType, onSelectedOsTypeChange: onSelectedOsTypeChange }))] }), !isInitialDefault && version && (_jsxs(Box, { display: "flex", flexWrap: "wrap", maxWidth: "100%", children: [_jsxs(Stack, { maxWidth: "100%", flex: 1, children: [_jsx(InputLabel, { sx: { mb: 2 }, htmlFor: "e2e-download-image-versions-list", children: "Select version" }), _jsx(Autocomplete, { fullWidth: true, id: "e2e-download-image-versions-list", value: version, getOptionLabel: (option) => option.value, isOptionEqualToValue: (option, value) => option.value === value.value, options: versionSelectionOpts, onChange: (_event, ver) => {
|
|
142
|
-
handleVersionChange(ver);
|
|
143
|
-
}, placeholder: "Choose a version...", renderOption: (props, option) => (_jsx(Box, Object.assign({ component: "li" }, props, { children: _jsx(VersionSelectItem, { option: option, isRecommended: option.value === recommendedVersion }) }))), renderInput: (_a) => {
|
|
144
|
-
var { InputProps } = _a, params = __rest(_a, ["InputProps"]);
|
|
145
|
-
return (_jsx(TextField, Object.assign({}, params, { slotProps: {
|
|
146
|
-
input: Object.assign(Object.assign({}, InputProps), { endAdornment: (_jsxs(_Fragment, { children: [version.value === recommendedVersion && (_jsx(Chip, { sx: { ml: 1 }, color: "green", label: "recommended" })), !!(version === null || version === void 0 ? void 0 : version.knownIssueList) && (_jsx(Tooltip, { title: version.knownIssueList, children: _jsx(FontAwesomeIcon, { icon: faTriangleExclamation, color: token('color.icon.warning') }) })), InputProps.endAdornment] })) }),
|
|
147
|
-
} })));
|
|
148
|
-
}, disableClearable: true })] }), showAllVersionsToggle && (_jsx(Box, { mx: 2, display: "flex", alignItems: "center", alignSelf: "flex-end",
|
|
149
|
-
// TODO: find a better way to center the checkbox with the input only (without label)
|
|
150
|
-
height: 54, children: _jsx(FormControlLabel, { control: _jsx(Checkbox, { id: "e2e-show-all-versions-check", checked: showAllVersions, onChange: handleShowAllVersions }), label: "Show outdated versions" }) }))] })), _jsx(Divider, { variant: "fullWidth", sx: { my: 3, borderStyle: 'dashed' } }), (!isInitialDefault || !variant) && (_jsx(Box, { sx: { mt: 3 }, children: _jsx(VariantSelector, { version: version, variant: variant, onVariantChange: (v) => {
|
|
151
|
-
handleVariantChange(v ? 'dev' : 'prod');
|
|
152
|
-
} }) })), _jsx(Divider, { variant: "fullWidth", sx: { my: 3, borderStyle: 'dashed' } }), _jsxs(Stack, { children: [_jsxs(FormControl, { children: [_jsx(FormLabel, { id: "network-radio-buttons-group-label", children: _jsx(Typography, { variant: "titleSm", children: "Network" }) }), _jsxs(RadioGroup, { "aria-labelledby": "network-radio-buttons-group-label", value: model.network, name: "network", onChange: (event) => {
|
|
153
|
-
onChange('network', event.target.value);
|
|
154
|
-
}, children: [_jsx(FormControlLabel, { value: "ethernet", control: _jsx(Radio, {}), label: "Ethernet only" }), _jsx(FormControlLabel, { value: "wifi", control: _jsx(Radio, {}), label: "Wifi + Ethernet" })] })] }), model.network === 'wifi' && (_jsxs(_Fragment, { children: [_jsx(InputLabel, { htmlFor: "device-wifi-ssid", sx: { mb: 2 }, children: "WiFi SSID" }), _jsx(TextField, { value: model.wifiSsid, id: "device-wifi-ssid", slotProps: {
|
|
155
|
-
htmlInput: {
|
|
156
|
-
name: 'wifiSsid',
|
|
157
|
-
autoComplete: 'wifiSsid-auto-complete',
|
|
158
|
-
},
|
|
159
|
-
}, onChange: (event) => {
|
|
160
|
-
onChange('wifiSsid', event.target.value);
|
|
161
|
-
} }), _jsx(InputLabel, { htmlFor: "device-wifi-password", sx: { my: 2 }, children: "Wifi Passphrase" }), _jsx(TextField, { type: showPassword ? 'text' : 'password', id: "device-wifi-password", value: model.wifiKey, slotProps: {
|
|
162
|
-
htmlInput: {
|
|
163
|
-
name: 'wifiKey',
|
|
164
|
-
},
|
|
165
|
-
// input and htmlInput are different https://mui.com/material-ui/api/text-field/#text-field-prop-slotProps
|
|
166
|
-
input: {
|
|
167
|
-
endAdornment: (_jsx(InputAdornment, { position: "end", children: _jsx(IconButton, { onClick: () => {
|
|
168
|
-
setShowPassword((show) => !show);
|
|
169
|
-
}, onMouseDown: (event) => {
|
|
170
|
-
event.preventDefault();
|
|
171
|
-
}, edge: "end", children: showPassword ? _jsx(VisibilityOff, {}) : _jsx(Visibility, {}) }) })),
|
|
172
|
-
},
|
|
173
|
-
}, onChange: (event) => {
|
|
174
|
-
onChange('wifiKey', event.target.value);
|
|
175
|
-
} })] }))] }), supportsSecureBoot && (_jsxs(_Fragment, { children: [_jsx(Divider, { variant: "fullWidth", sx: { my: 3, borderStyle: 'dashed' } }), _jsxs(FormControl, { children: [_jsx(FormLabel, { id: "secure-boot-and-full-disk-encryption-label", children: _jsxs(Stack, { direction: "row", alignItems: "center", gap: 1, children: [_jsx(Typography, { variant: "titleSm", children: "Secure Boot and Full Disk Encryption" }), _jsx(NewChip, { expiryTimestamp: "2025-06-01" })] }) }), _jsx(FormControlLabel, { control: _jsx(Switch, { onClick: (event) => {
|
|
176
|
-
if (!model.secureboot &&
|
|
177
|
-
!getFromLocalStorage(secureBootDontShowAgainKey)) {
|
|
178
|
-
event.preventDefault();
|
|
179
|
-
setShowSecureBootConfirmationDialog(true);
|
|
180
|
-
if (state.webTracker) {
|
|
181
|
-
state.webTracker.track('Application Add Device Modal Show Secure Boot Warning');
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
}, onChange: (event) => {
|
|
185
|
-
onChange('secureboot', event.target.checked);
|
|
186
|
-
}, checked: model.secureboot }), label: _jsxs(Stack, { direction: "row", children: [_jsx(Typography, { children: "Enable Secure Boot and Full Disk Encryption" }), _jsx(MUILinkWithTracking, { eventProperties: {
|
|
187
|
-
source: 'Application Add Device Modal Secure Boot Doc Icon',
|
|
188
|
-
}, href: SECURE_BOOT_AND_FULL_DISK_ENCRYPTION_DOCS, sx: {
|
|
189
|
-
display: 'flex',
|
|
190
|
-
alignItems: 'center',
|
|
191
|
-
height: '1.5rem',
|
|
192
|
-
}, children: _jsx(ArticleIcon, { sx: { ml: 1, fontSize: '1.15rem' } }) })] }) })] })] })), _jsx(Divider, { variant: "fullWidth", sx: { my: 3, borderStyle: 'dashed' } }), _jsxs(Accordion, { disableGutters: true, elevation: 0, expanded: showAdvancedSettings, onChange: () => {
|
|
193
|
-
setShowAdvancedSettings(!showAdvancedSettings);
|
|
194
|
-
}, sx: {
|
|
195
|
-
border: 'none',
|
|
196
|
-
'&::before': {
|
|
197
|
-
display: 'none',
|
|
198
|
-
},
|
|
199
|
-
}, children: [_jsx(AccordionSummary, { expandIcon: _jsx(FontAwesomeIcon, { icon: faChevronRight }), sx: { flexDirection: 'row-reverse', gap: 1 }, children: _jsx(Typography, { variant: "titleSm", children: "Advanced settings" }) }), _jsx(AccordionDetails, { children: _jsxs(Stack, { children: [_jsxs(FormControl, { children: [_jsxs(FormLabel, { htmlFor: "poll-interval-label", sx: { display: 'flex', alignItems: 'center' }, children: ["Check for updates every X minutes", ' ', _jsx(MUILinkWithTracking, { eventProperties: {
|
|
200
|
-
source: 'Application Add Device Modal Poll Interval Doc Icon',
|
|
201
|
-
}, href: POLL_INTERVAL_DOCS, sx: {
|
|
202
|
-
display: 'flex',
|
|
203
|
-
alignItems: 'center',
|
|
204
|
-
height: '1.5rem',
|
|
205
|
-
}, children: _jsx(ArticleIcon, { sx: { ml: 1, fontSize: '1.15rem' } }) })] }), _jsx(TextField, { id: "poll-interval-label", "aria-labelledby": "poll-interval-label", value: model.appUpdatePollInterval, slotProps: {
|
|
206
|
-
htmlInput: {
|
|
207
|
-
name: 'appUpdatePollInterval',
|
|
208
|
-
autoComplete: 'appUpdatePollInterval-auto-complete',
|
|
209
|
-
},
|
|
210
|
-
}, onChange: (event) => {
|
|
211
|
-
onChange('appUpdatePollInterval', event.target.value);
|
|
212
|
-
} })] }), _jsx(InputLabel, { htmlFor: "provision-key-name", sx: { my: 2 }, children: "Provisioning Key name" }), _jsx(TextField, { name: "provisioningKeyName", id: "provision-key-name", value: (_a = model.provisioningKeyName) !== null && _a !== void 0 ? _a : '', slotProps: {
|
|
213
|
-
htmlInput: {
|
|
214
|
-
name: 'provisioningKeyName',
|
|
215
|
-
autoComplete: 'provisioningKeyName-auto-complete',
|
|
216
|
-
},
|
|
217
|
-
}, onChange: (event) => {
|
|
218
|
-
onChange('provisioningKeyName', event.target.value);
|
|
219
|
-
} }), _jsx(InputLabel, { htmlFor: "provision-key-expiring", sx: { my: 2 }, children: "Provisioning Key expiring on" }), _jsx(TextField, { type: "date", id: "provision-key-expiring", value: (_b = model.provisioningKeyExpiryDate) !== null && _b !== void 0 ? _b : '', slotProps: {
|
|
220
|
-
htmlInput: {
|
|
221
|
-
name: 'provisioningKeyExpiryDate',
|
|
222
|
-
autoComplete: 'provisioningKeyExpiryDate-auto-complete',
|
|
223
|
-
},
|
|
224
|
-
}, onChange: (event) => {
|
|
225
|
-
onChange('provisioningKeyExpiryDate', event.target.value);
|
|
226
|
-
} })] }) })] })] }), _jsxs(Dialog, { open: showSecureBootConfirmationDialog, onClose: () => {
|
|
227
|
-
dismissSecureBootWarning(false, dontShowSecureBootWarningAgain);
|
|
228
|
-
}, children: [_jsx(DialogTitle, { children: "Enabling Secure Boot and Full Disk Encryption" }), _jsx(DialogContent, { children: _jsxs(Stack, { children: ["Enabling Secure Boot and Full Disk Encryption has important implications:", _jsxs("ul", { children: [_jsxs("li", { children: ["The image you are about to download is", ' ', _jsx(MUILinkWithTracking, { eventProperties: {
|
|
229
|
-
source: "Application Add Device Modal Secure Boot Warning signed with balena's main platform key Link",
|
|
230
|
-
}, href: "https://docs.balena.io/reference/OS/secure-boot-and-full-disk-encryption/overview/#keys-and-certificates-in-secure-boot", children: "signed with balena's main platform key" })] }), _jsx("li", { children: "Full Disk Encryption (FDE): All data on the disk will be encrypted with a unique key per device, ensuring that data extraction or retrieval from a powered-off device is impossible." }), _jsxs("li", { children: ["Secure Boot: Ensures only OS images signed by balena can unlock the disks and access data stored on the device. Hardened Mode Limitations:", _jsxs("ul", { children: [_jsx("li", { children: "Unsigned kernel modules cannot be loaded." }), _jsx("li", { children: "Boot parameters cannot be modified." }), _jsx("li", { children: "Debugging early boot processes is practically impossible." })] })] })] }), _jsxs(Typography, { children: ["If you need to load out-of-tree kernel drivers or require a unique signing key,", ' ', _jsx(MUILinkWithTracking, { eventProperties: {
|
|
231
|
-
source: 'Application Add Device Modal Secure Boot Warning Contact Us Link',
|
|
232
|
-
}, href: "mailto:sales@balena.io", target: "_self", children: "contact us" }), ' ', "to discuss your specific requirements."] }), _jsx(MUILinkWithTracking, { eventProperties: {
|
|
233
|
-
source: 'Application Add Device Modal Secure Boot Warning Learn More Link',
|
|
234
|
-
}, href: "https://docs.balena.io/reference/OS/secure-boot-and-full-disk-encryption/overview/", children: "Learn more about Secure Boot and Full Disk Encryption." }), _jsx(FormControlLabel, { control: _jsx(Checkbox, { onChange: (event) => {
|
|
235
|
-
setDontShowSecureBootWarningAgain(event.target.checked);
|
|
236
|
-
} }), label: "Don't show me this warning again for this device type", sx: { mt: 2 } })] }) }), _jsxs(DialogActions, { children: [_jsx(Button, { "aria-label": "Go back and do not acknowledge Secure Boot and Full Disk Encryption warning", onClick: () => {
|
|
237
|
-
dismissSecureBootWarning(false, dontShowSecureBootWarningAgain);
|
|
238
|
-
}, variant: "outlined", color: "secondary", children: "Cancel" }), _jsx(Button, { "aria-label": "Acknowledge Secure Boot and Full Disk Encryption warning", onClick: () => {
|
|
239
|
-
onChange('secureboot', true);
|
|
240
|
-
if (dontShowSecureBootWarningAgain) {
|
|
241
|
-
setToLocalStorage(secureBootDontShowAgainKey, 'true');
|
|
102
|
+
return (_jsxs(Box, { action: downloadUrl, method: "post", component: "form", noValidate: true, autoComplete: "off", p: 2, ref: formElement, children: [_jsx("input", { type: "hidden", name: "deviceType", value: model.deviceType.slug }), _jsx("input", { type: "hidden", name: "_token", value: authToken }), _jsx("input", { type: "hidden", name: "appId", value: applicationId }), _jsx("input", { type: "hidden", name: "fileType", value: ".zip" }), _jsx("input", { type: "hidden", name: "version", value: model.version }), _jsxs(Box, { py: 3, display: "flex", flexWrap: "wrap", gap: 2, children: [compatibleDeviceTypes && compatibleDeviceTypes.length > 1 && (_jsxs(Box, { display: "flex", flexDirection: "column", flex: "1", maxWidth: "100%", children: [_jsxs(InputLabel, { htmlFor: "device-type-select", sx: { display: 'flex', alignItems: 'center', mb: 2 }, children: ["Select device type", ' ', _jsx(Tooltip, { title: "Applications can support any devices that share the same architecture as their default device type.", children: _jsx(HelpIcon, { color: "info", sx: { fontSize: '1rem', marginLeft: 1 } }) })] }), _jsx(Autocomplete, { fullWidth: true, id: "device-type-select", value: model.deviceType, options: compatibleDeviceTypes, getOptionLabel: (option) => option.name, renderOption: (props, option) => {
|
|
103
|
+
var _a;
|
|
104
|
+
return (_jsxs(Box, Object.assign({ component: "li" }, props, { children: [_jsx(Avatar, { variant: "square", src: (_a = option.logo) !== null && _a !== void 0 ? _a : FALLBACK_LOGO_UNKNOWN_DEVICE, sx: { mr: 3, width: '20px', height: '20px' } }), _jsx(Typography, { noWrap: true, children: option.name })] })));
|
|
105
|
+
}, renderInput: (_a) => {
|
|
106
|
+
var _b;
|
|
107
|
+
var { InputProps } = _a, params = __rest(_a, ["InputProps"]);
|
|
108
|
+
return (_jsx(TextField, Object.assign({}, params, { InputProps: Object.assign(Object.assign({}, InputProps), { startAdornment: (_jsx(Avatar, { variant: "square", src: (_b = model.deviceType.logo) !== null && _b !== void 0 ? _b : FALLBACK_LOGO_UNKNOWN_DEVICE, sx: { mr: 3, width: '20px', height: '20px' } })) }) })));
|
|
109
|
+
}, onChange: (_event, value) => {
|
|
110
|
+
if (!value) {
|
|
111
|
+
return;
|
|
242
112
|
}
|
|
243
|
-
|
|
244
|
-
},
|
|
113
|
+
handleSelectedDeviceTypeChange(value);
|
|
114
|
+
}, disableClearable: true,
|
|
115
|
+
// TODO: consider whether there is a better solution than letting the width vary as you search
|
|
116
|
+
componentsProps: {
|
|
117
|
+
popper: { sx: { width: 'fit-content' } },
|
|
118
|
+
} })] })), (!isInitialDefault || osType) &&
|
|
119
|
+
hasEsrVersions &&
|
|
120
|
+
model.deviceType && (_jsx(OsTypeSelector, { supportedOsTypes: osTypes, hasEsrVersions: hasEsrVersions !== null && hasEsrVersions !== void 0 ? hasEsrVersions : false, selectedOsTypeSlug: osType, onSelectedOsTypeChange: onSelectedOsTypeChange }))] }), !isInitialDefault && version && (_jsxs(Box, { display: "flex", flexWrap: "wrap", maxWidth: "100%", children: [_jsxs(Box, { display: "flex", flexDirection: "column", maxWidth: "100%", flex: 1, children: [_jsx(InputLabel, { sx: { mb: 2 }, htmlFor: "e2e-download-image-versions-list", children: "Select version" }), _jsx(Autocomplete, { fullWidth: true, id: "e2e-download-image-versions-list", value: version, getOptionLabel: (option) => option.value, isOptionEqualToValue: (option, value) => option.value === value.value, options: versionSelectionOpts, onChange: (_event, ver) => {
|
|
121
|
+
handleVersionChange(ver);
|
|
122
|
+
}, placeholder: "Choose a version...", renderOption: (props, option) => (_jsx(Box, Object.assign({ component: "li" }, props, { children: _jsx(VersionSelectItem, { option: option, isRecommended: option.value === recommendedVersion }) }))), renderInput: (_a) => {
|
|
123
|
+
var { InputProps } = _a, params = __rest(_a, ["InputProps"]);
|
|
124
|
+
return (_jsx(TextField, Object.assign({}, params, { InputProps: Object.assign(Object.assign({}, InputProps), { endAdornment: (_jsxs(_Fragment, { children: [version.value === recommendedVersion && (_jsx(Chip, { sx: { ml: 1 }, color: "green", label: "recommended" })), !!(version === null || version === void 0 ? void 0 : version.knownIssueList) && (_jsx(Tooltip, { title: version.knownIssueList, children: _jsx(FontAwesomeIcon, { icon: faTriangleExclamation, color: token('color.icon.warning') }) })), InputProps.endAdornment] })) }) })));
|
|
125
|
+
}, disableClearable: true })] }), showAllVersionsToggle && (_jsx(Box, { mx: 2, display: "flex", alignItems: "center", alignSelf: "flex-end",
|
|
126
|
+
// TODO: find a better way to center the checkbox with the input only (without label)
|
|
127
|
+
height: 54, children: _jsx(FormControlLabel, { control: _jsx(Checkbox, { id: "e2e-show-all-versions-check", checked: showAllVersions, onChange: handleShowAllVersions }), label: "Show outdated versions" }) }))] })), (!isInitialDefault || !variant) && (_jsx(Box, { sx: { mt: 3 }, children: _jsx(VariantSelector, { version: version, variant: variant, onVariantChange: (v) => {
|
|
128
|
+
handleVariantChange(v ? 'dev' : 'prod');
|
|
129
|
+
} }) })), _jsx(Divider, { variant: "fullWidth", sx: { my: 3, borderStyle: 'dashed' } }), _jsxs(Box, { display: "flex", flexDirection: "column", children: [_jsxs(FormControl, { children: [_jsx(FormLabel, { id: "network-radio-buttons-group-label", children: "Network" }), _jsxs(RadioGroup, { "aria-labelledby": "network-radio-buttons-group-label", value: model.network, name: "network", onChange: (event) => {
|
|
130
|
+
onChange('network', event.target.value);
|
|
131
|
+
}, children: [_jsx(FormControlLabel, { value: "ethernet", control: _jsx(Radio, {}), label: "Ethernet only" }), _jsx(FormControlLabel, { value: "wifi", control: _jsx(Radio, {}), label: "Wifi + Ethernet" })] })] }), model.network === 'wifi' && (_jsxs(_Fragment, { children: [_jsx(InputLabel, { htmlFor: "device-wifi-ssid", sx: { mb: 2 }, children: "WiFi SSID" }), _jsx(TextField, { value: model.wifiSsid, id: "device-wifi-ssid", slotProps: {
|
|
132
|
+
htmlInput: {
|
|
133
|
+
name: 'wifiSsid',
|
|
134
|
+
autoComplete: 'wifiSsid-auto-complete',
|
|
135
|
+
},
|
|
136
|
+
}, onChange: (event) => {
|
|
137
|
+
onChange('wifiSsid', event.target.value);
|
|
138
|
+
} }), _jsx(InputLabel, { htmlFor: "device-wifi-password", sx: { my: 2 }, children: "Wifi Passphrase" }), _jsx(TextField, { type: showPassword ? 'text' : 'password', id: "device-wifi-password", value: model.wifiKey, slotProps: {
|
|
139
|
+
htmlInput: {
|
|
140
|
+
name: 'wifiKey',
|
|
141
|
+
},
|
|
142
|
+
// input and htmlInput are different https://mui.com/material-ui/api/text-field/#text-field-prop-slotProps
|
|
143
|
+
input: {
|
|
144
|
+
endAdornment: (_jsx(InputAdornment, { position: "end", children: _jsx(IconButton, { onClick: () => {
|
|
145
|
+
setShowPassword((show) => !show);
|
|
146
|
+
}, onMouseDown: (event) => {
|
|
147
|
+
event.preventDefault();
|
|
148
|
+
}, edge: "end", children: showPassword ? _jsx(VisibilityOff, {}) : _jsx(Visibility, {}) }) })),
|
|
149
|
+
},
|
|
150
|
+
}, onChange: (event) => {
|
|
151
|
+
onChange('wifiKey', event.target.value);
|
|
152
|
+
} })] }))] }), _jsx(Divider, { variant: "fullWidth", sx: { my: 3, borderStyle: 'dashed' } }), _jsxs(Button, { onClick: () => {
|
|
153
|
+
setShowAdvancedSettings(!showAdvancedSettings);
|
|
154
|
+
}, variant: "outlined", sx: { mb: 2 }, children: [showAdvancedSettings ? _jsx(RemoveIcon, {}) : _jsx(AddIcon, {}), " Advanced settings"] }), _jsx(Collapse, { in: showAdvancedSettings, collapsedSize: 0, children: _jsxs(Box, { display: "flex", flexDirection: "column", children: [_jsxs(FormControl, { children: [_jsxs(FormLabel, { htmlFor: "poll-interval-label", sx: { display: 'flex' }, children: ["Check for updates every X minutes", ' ', _jsx(MUILinkWithTracking, { href: POLL_INTERVAL_DOCS, sx: {
|
|
155
|
+
display: 'flex',
|
|
156
|
+
alignItems: 'center',
|
|
157
|
+
height: '1.5rem',
|
|
158
|
+
}, children: _jsx(ArticleIcon, { sx: { ml: 1, fontSize: '1.15rem' } }) })] }), _jsx(TextField, { id: "poll-interval-label", "aria-labelledby": "poll-interval-label", value: model.appUpdatePollInterval, slotProps: {
|
|
159
|
+
htmlInput: {
|
|
160
|
+
name: 'appUpdatePollInterval',
|
|
161
|
+
autoComplete: 'appUpdatePollInterval-auto-complete',
|
|
162
|
+
},
|
|
163
|
+
}, onChange: (event) => {
|
|
164
|
+
onChange('appUpdatePollInterval', event.target.value);
|
|
165
|
+
} })] }), _jsx(InputLabel, { htmlFor: "provision-key-name", sx: { my: 2 }, children: "Provisioning Key name" }), _jsx(TextField, { name: "provisioningKeyName", id: "provision-key-name", value: (_a = model.provisioningKeyName) !== null && _a !== void 0 ? _a : '', slotProps: {
|
|
166
|
+
htmlInput: {
|
|
167
|
+
name: 'provisioningKeyName',
|
|
168
|
+
autoComplete: 'provisioningKeyName-auto-complete',
|
|
169
|
+
},
|
|
170
|
+
}, onChange: (event) => {
|
|
171
|
+
onChange('provisioningKeyName', event.target.value);
|
|
172
|
+
} }), _jsx(InputLabel, { htmlFor: "provision-key-expiring", sx: { my: 2 }, children: "Provisioning Key expiring on" }), _jsx(TextField, { type: "date", id: "provision-key-expiring", value: (_b = model.provisioningKeyExpiryDate) !== null && _b !== void 0 ? _b : '', slotProps: {
|
|
173
|
+
htmlInput: {
|
|
174
|
+
name: 'provisioningKeyExpiryDate',
|
|
175
|
+
autoComplete: 'provisioningKeyExpiryDate-auto-complete',
|
|
176
|
+
},
|
|
177
|
+
}, onChange: (event) => {
|
|
178
|
+
onChange('provisioningKeyExpiryDate', event.target.value);
|
|
179
|
+
} })] }) })] }));
|
|
245
180
|
});
|
|
246
181
|
// TODO: We need a better way than just copying the styling. Consider creating a component to export
|
|
247
182
|
const VersionSelectItem = ({ option, isRecommended, }) => {
|
|
248
|
-
return (_jsxs(Stack, { flexWrap: "wrap", maxWidth: "100%", rowGap: 1, children: [_jsxs(Typography, { noWrap: true, maxWidth: "100%", variant: "titleSm", children: [option.title, !!option.line && (_jsx(Chip, { sx: { ml: 1 }, label: option.line, color: lineMap[option.line] })), isRecommended && (_jsx(Chip, { sx: { ml: 1 }, color: "green", label: "recommended" }))] }), !!option.knownIssueList && (_jsx(Callout, { severity: "warning", size: "sm", children: option.knownIssueList }))] }));
|
|
183
|
+
return (_jsxs(Stack, { direction: "column", flexWrap: "wrap", maxWidth: "100%", rowGap: 1, children: [_jsxs(Typography, { noWrap: true, maxWidth: "100%", variant: "titleSm", children: [option.title, !!option.line && (_jsx(Chip, { sx: { ml: 1 }, label: option.line, color: lineMap[option.line] })), isRecommended && (_jsx(Chip, { sx: { ml: 1 }, color: "green", label: "recommended" }))] }), !!option.knownIssueList && (_jsx(Callout, { severity: "warning", size: "sm", children: option.knownIssueList }))] }));
|
|
249
184
|
};
|
|
@@ -16,7 +16,7 @@ const variantInfo = {
|
|
|
16
16
|
};
|
|
17
17
|
const BuildVariants = ['dev', 'prod'];
|
|
18
18
|
export const VariantSelector = ({ version, variant, onVariantChange, }) => {
|
|
19
|
-
return (_jsxs(FormControl, { children: [_jsx(FormLabel, { children:
|
|
19
|
+
return (_jsxs(FormControl, { children: [_jsx(FormLabel, { children: "Select edition" }), _jsx(RadioGroup, { "aria-labelledby": "variant-radio-buttons-group", name: "developmentMode", value: variant === 'dev', onChange: (event) => {
|
|
20
20
|
onVariantChange(event.target.value === 'true');
|
|
21
21
|
}, children: BuildVariants.map((buildVariant, index) => {
|
|
22
22
|
const isDev = buildVariant === 'dev';
|
|
@@ -2,7 +2,7 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
|
|
|
2
2
|
import { Avatar, Box, DialogActions, DialogContent, Divider, Grid2 as Grid, Typography, } from '@mui/material';
|
|
3
3
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
4
4
|
import { FALLBACK_LOGO_UNKNOWN_DEVICE, isUrlAccessible, stripVersionBuild, } from './utils';
|
|
5
|
-
import {
|
|
5
|
+
import { ImageForm } from './ImageForm';
|
|
6
6
|
import { ApplicationInstructions } from './ApplicationInstructions';
|
|
7
7
|
import { DropDownButton } from '../DropDownButton';
|
|
8
8
|
import DownloadIcon from '@mui/icons-material/Download';
|
|
@@ -15,7 +15,6 @@ import { enqueueSnackbar } from 'notistack';
|
|
|
15
15
|
import { DialogWithCloseButton } from '../DialogWithCloseButton';
|
|
16
16
|
import { Callout } from '../Callout';
|
|
17
17
|
import { Spinner } from '../Spinner';
|
|
18
|
-
import * as semver from 'balena-semver';
|
|
19
18
|
const etcherLogoBase64 = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iODAwIiBoZWlnaHQ9IjYwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KIDxnPgogIDx0aXRsZT5FdGNoZXI8L3RpdGxlPgogIDxnIGlkPSJzdmdfMSIgc3Ryb2tlPSJudWxsIj4KICAgPHBhdGggaWQ9InN2Z18yIiBjbGFzcz0ic3QxIiBkPSJtNDEyLjkwMzgzLDM1OC4wNjcxM2wwLDE3MS40OTU4M2M3LjQ5MjU0LC0xLjY2NTAxIDE0LjE1MjU3LC0zLjMzMDAyIDIwLjgxMjYsLTcuNDkyNTRsMTQyLjM1ODE5LC04MS41ODUzOWMyMC44MTI2LC0xMS42NTUwNiAzMy4zMDAxNiwtMzQuMTMyNjYgMzMuMzAwMTYsLTU4LjI3NTI4bDAsLTE2Mi4zMzgyOGMwLC02LjY2MDAzIC0wLjgzMjUsLTEzLjMyMDA2IC0zLjMzMDAyLC0xOS4xNDc1OWwtMTU0LjAxMzI0LDg5LjA3NzkzYy0zMi40Njc2NiwyMi40Nzc2MSAtMzkuMTI3NjksNDMuMjkwMjEgLTM5LjEyNzY5LDY4LjI2NTMzbDAsLTAuMDAwMDF6IiBmaWxsPSIjQTVERTM3IiBzdHJva2U9Im51bGwiLz4KICAgPHBhdGggaWQ9InN2Z18zIiBjbGFzcz0ic3QyIiBkPSJtNjYyLjY1NTAzLDE2Ny40MjM3MWwtNTYuNjEwMjcsMzIuNDY3NjZjMS42NjUwMSw1LjgyNzUzIDMuMzMwMDIsMTIuNDg3NTYgMy4zMzAwMiwxOS4xNDc1OWwwLDE2My4xNzA3OWMwLDI0LjE0MjYyIC0xMy4zMjAwNiw0Ni42MjAyMiAtMzMuMzAwMTYsNTguMjc1MjhsLTE0Mi4zNTgxOSw4MS41ODUzOWMtNi42NjAwMywzLjMzMDAyIC0xMy4zMjAwNiw1LjgyNzUzIC0yMC44MTI2LDcuNDkyNTRsMCw2NC45MzUzMWM5Ljk5MDA1LC0xLjY2NTAxIDE5Ljk4MDEsLTQuOTk1MDIgMjguMzA1MTQsLTkuOTkwMDVsMTg0LjgxNTg5LC0xMDUuNzI4MDFjMjUuODA3NjIsLTE0Ljk4NTA3IDQxLjYyNTIsLTQyLjQ1NzcgNDEuNjI1MiwtNzIuNDI3ODVsMCwtMjExLjQ1NjAyYzAsLTkuMTU3NTQgLTEuNjY1MDEsLTE4LjMxNTA5IC00Ljk5NTAyLC0yNy40NzI2M2wtMC4wMDAwMSwweiIgZmlsbD0iI0M4RjE3OCIgc3Ryb2tlPSJudWxsIi8+CiAgIDxwYXRoIGlkPSJzdmdfNCIgY2xhc3M9InN0MSIgZD0ibTM5OS41ODM3NiwzMDMuOTU0MzZjOC4zMjUwNCwtMTMuMzIwMDYgMjAuODEyNiwtMjUuODA3NjIgMzkuMTI3NjksLTM2LjYzMDE4bDE1NS42NzgyNSwtODkuOTEwNDNjLTQuOTk1MDIsLTYuNjYwMDMgLTExLjY1NTA2LC0xMi40ODc1NiAtMTguMzE1MDksLTE2LjY1MDA4bC0xNDIuMzU4MTksLTgxLjU4NTM5Yy0yMC44MTI2LC0xMS42NTUwNiAtNDYuNjIwMjIsLTExLjY1NTA2IC02Ny40MzI4MiwwbC0xNDEuNTI1NjgsODEuNTg1MzljLTcuNDkyNTQsNC4xNjI1MiAtMTMuMzIwMDYsOS45OTAwNSAtMTkuMTQ3NTksMTYuNjUwMDhsMTU0Ljg0NTc1LDg5LjkxMDQzYzE4LjMxNTA5LDExLjY1NTA2IDMwLjgwMjY1LDIzLjMxMDExIDM5LjEyNzY5LDM2LjYzMDE4bC0wLjAwMDAxLDB6IiBmaWxsPSIjQTVERTM3IiBzdHJva2U9Im51bGwiLz4KICAgPHBhdGggaWQ9InN2Z181IiBjbGFzcz0ic3QyIiBkPSJtMjI0Ljc1NzkyLDE2MS41OTYxOGwxNDEuNTI1NjgsLTgxLjU4NTM5YzIwLjgxMjYsLTExLjY1NTA2IDQ2LjYyMDIyLC0xMS42NTUwNiA2Ny40MzI4MiwwbDE0Mi4zNTgxOSw4MS41ODUzOWM3LjQ5MjU0LDQuMTYyNTIgMTMuMzIwMDYsOS45OTAwNSAxOC4zMTUwOSwxNi42NTAwOGw1Ni42MTAyNywtMzIuNDY3NjZjLTYuNjYwMDMsLTkuMTU3NTQgLTE0Ljk4NTA3LC0xNi42NTAwOCAtMjQuOTc1MTIsLTIxLjY0NTFsLTE4NC44MTU4OSwtMTA3LjM5MzAyYy0yNS44MDc2MiwtMTQuOTg1MDcgLTU3LjQ0Mjc4LC0xNC45ODUwNyAtODMuMjUwNCwwbC0xODMuMTUwODgsMTA2LjU2MDUxYy05Ljk5MDA1LDUuODI3NTMgLTE4LjMxNTA5LDEzLjMyMDA2IC0yNC45NzUxMiwyMi40Nzc2MWw1Ni42MTAyNywzMi40Njc2NmM0LjE2MjUyLC02LjY2MDAzIDEwLjgyMjU1LC0xMi40ODc1NiAxOC4zMTUwOSwtMTYuNjUwMDh6IiBmaWxsPSIjQzhGMTc4IiBzdHJva2U9Im51bGwiLz4KICAgPHBhdGggaWQ9InN2Z182IiBjbGFzcz0ic3QyIiBkPSJtMzY2LjI4MzYsNTIyLjA3MDQxbC0xNDEuNTI1NjgsLTgxLjU4NTM5Yy0yMC44MTI2LC0xMS42NTUwNiAtMzMuMzAwMTYsLTM0LjEzMjY2IC0zMy4zMDAxNiwtNTguMjc1MjhsMCwtMTYzLjE3MDc5YzAsLTYuNjYwMDMgMC44MzI1LC0xMi40ODc1NiAyLjQ5NzUxLC0xOC4zMTUwOWwtNTYuNjEwMjcsLTMyLjQ2NzY2Yy0zLjMzMDAyLDkuMTU3NTQgLTQuOTk1MDIsMTcuNDgyNTggLTQuOTk1MDIsMjYuNjQwMTNsMCwyMTIuMjg4NTJjMCwyOS45NzAxNCAxNS44MTc1OCw1Ny40NDI3OCA0MS42MjUyLDcxLjU5NTM0bDE4My45ODMzOSwxMDUuNzI4MDFjOC4zMjUwNCw0Ljk5NTAyIDE4LjMxNTA5LDguMzI1MDQgMjguMzA1MTQsOS45OTAwNWwwLC02NC45MzUzMWMtNi42NjAwMywtMC44MzI1IC0xMy4zMjAwNiwtMy4zMzAwMiAtMTkuOTgwMSwtNy40OTI1NGwtMC4wMDAwMSwwLjAwMDAxeiIgZmlsbD0iI0M4RjE3OCIgc3Ryb2tlPSJudWxsIi8+CiAgIDxwYXRoIGlkPSJzdmdfNyIgY2xhc3M9InN0MSIgZD0ibTM0Ny4xMzYwMSwyODguOTY5MjlsLTE1My4xODA3NCwtODguMjQ1NDJjLTEuNjY1MDEsNS44Mjc1MyAtMi40OTc1MSwxMi40ODc1NiAtMi40OTc1MSwxOC4zMTUwOWwwLDE2My4xNzA3OWMwLDI0LjE0MjYyIDEyLjQ4NzU2LDQ2LjYyMDIyIDMzLjMwMDE2LDU4LjI3NTI4bDE0MS41MjU2OCw4MS41ODUzOWM2LjY2MDAzLDMuMzMwMDIgMTMuMzIwMDYsNS44Mjc1MyAyMC44MTI2LDcuNDkyNTRsMCwtMTcxLjQ5NTgzYy0wLjgzMjUsLTI0Ljk3NTEyIC03LjQ5MjU0LC00NS43ODc3MiAtMzkuOTYwMTksLTY5LjA5NzgzbDAsLTAuMDAwMDF6IiBmaWxsPSIjQTVERTM3IiBzdHJva2U9Im51bGwiLz4KICA8L2c+CiA8L2c+Cgo8L3N2Zz4=';
|
|
20
19
|
const ETCHER_OPEN_IMAGE_URL = 'https://www.balena.io/etcher/open-image-url';
|
|
21
20
|
export var ActionType;
|
|
@@ -65,7 +64,6 @@ const getInitialState = (deviceType, applicationId, releaseId) => ({
|
|
|
65
64
|
deviceType,
|
|
66
65
|
version: '',
|
|
67
66
|
network: 'ethernet',
|
|
68
|
-
secureboot: false,
|
|
69
67
|
developmentMode: false,
|
|
70
68
|
appUpdatePollInterval: 10,
|
|
71
69
|
wifiSsid: undefined,
|
|
@@ -172,7 +170,6 @@ export const DownloadImageDialog = ({ open, applicationId, releaseId, compatible
|
|
|
172
170
|
return dropDownButtonActions;
|
|
173
171
|
}, [
|
|
174
172
|
authToken,
|
|
175
|
-
isValidatingUrl,
|
|
176
173
|
downloadConfig,
|
|
177
174
|
downloadSize,
|
|
178
175
|
downloadUrl,
|
|
@@ -251,11 +248,7 @@ export const DownloadImageDialog = ({ open, applicationId, releaseId, compatible
|
|
|
251
248
|
newFormModelState = getInitialState(value, applicationId, releaseId);
|
|
252
249
|
}
|
|
253
250
|
else {
|
|
254
|
-
newFormModelState = Object.assign(Object.assign({}, formModel), {
|
|
255
|
-
formModel.deviceType.slug === GENERIC_X86_SLUG &&
|
|
256
|
-
semver.lt(value, GENERIC_X86_MINIMUM_SUPPORTED_SECUREBOOT_VERSION)
|
|
257
|
-
? false
|
|
258
|
-
: formModel.secureboot, [key]: value });
|
|
251
|
+
newFormModelState = Object.assign(Object.assign({}, formModel), { [key]: value });
|
|
259
252
|
onFieldChange === null || onFieldChange === void 0 ? void 0 : onFieldChange(newFormModelState);
|
|
260
253
|
}
|
|
261
254
|
setFormModel(newFormModelState);
|
|
@@ -282,7 +275,7 @@ export const DownloadImageDialog = ({ open, applicationId, releaseId, compatible
|
|
|
282
275
|
dockerImage: formModel.version
|
|
283
276
|
? getDockerArtifact(formModel.deviceType.slug, stripVersionBuild(formModel.version))
|
|
284
277
|
: '',
|
|
285
|
-
}
|
|
278
|
+
} })] }), ((_d = formModel.deviceType.imageDownloadAlerts) !== null && _d !== void 0 ? _d : []).map((alert) => {
|
|
286
279
|
return (_jsx(Grid, { pt: 0, size: 12, children: _jsx(Callout, { severity: alert.type, children: alert.message }, alert.message) }, alert.message));
|
|
287
280
|
})] }) }) }), _jsx(DialogActions, { sx: {
|
|
288
281
|
display: 'flex',
|
|
@@ -20,9 +20,7 @@ export const HighlightedName = (_a) => {
|
|
|
20
20
|
borderRadius: token('shape.borderRadius.xs'),
|
|
21
21
|
display: 'inline-block',
|
|
22
22
|
p: 2,
|
|
23
|
-
color: token(color || isLight(bgColor)
|
|
24
|
-
? 'color.text'
|
|
25
|
-
: 'color.text.inverse'),
|
|
23
|
+
color: token(color || isLight(bgColor) ? 'color.text' : 'color.text.inverse'),
|
|
26
24
|
background: bgColor,
|
|
27
25
|
} }, props, { children: children })));
|
|
28
26
|
};
|
|
@@ -6,12 +6,14 @@ import findKey from 'lodash/findKey';
|
|
|
6
6
|
import pick from 'lodash/pick';
|
|
7
7
|
import mapValues from 'lodash/mapValues';
|
|
8
8
|
const getKeyLabel = (schema) => {
|
|
9
|
+
var _a;
|
|
9
10
|
const s = find(schema.properties, { description: 'key' });
|
|
10
|
-
return (s === null || s === void 0 ? void 0 : s.title) ?
|
|
11
|
+
return (_a = s === null || s === void 0 ? void 0 : s.title) !== null && _a !== void 0 ? _a : 'key';
|
|
11
12
|
};
|
|
12
13
|
const getValueLabel = (schema) => {
|
|
14
|
+
var _a;
|
|
13
15
|
const s = find(schema.properties, { description: 'value' });
|
|
14
|
-
return (s === null || s === void 0 ? void 0 : s.title) ?
|
|
16
|
+
return (_a = s === null || s === void 0 ? void 0 : s.title) !== null && _a !== void 0 ? _a : 'value';
|
|
15
17
|
};
|
|
16
18
|
export const isKeyValueObj = (schema) => !!find(schema.properties, { description: 'key' }) ||
|
|
17
19
|
!!find(schema.properties, { description: 'value' });
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import type { RJSTEntityPropertyDefinition } from '../../';
|
|
2
|
-
import type { RJSTContext, RJSTModel } from '../../schemaOps';
|
|
1
|
+
import type { RJSTContext, RJSTEntityPropertyDefinition, RJSTModel } from '../../schemaOps';
|
|
3
2
|
import type { CheckedState, Pagination, TableSortOptions } from '../../components/Table/utils';
|
|
4
3
|
import type { BoxProps } from '@mui/material';
|
|
5
4
|
export { table } from './table';
|
|
@@ -18,11 +17,11 @@ export interface CollectionLensRendererProps<T extends {
|
|
|
18
17
|
filtered: T[];
|
|
19
18
|
selected?: Array<Subset<T>>;
|
|
20
19
|
checkedState?: CheckedState;
|
|
21
|
-
sort: TableSortOptions | null;
|
|
20
|
+
sort: TableSortOptions<T> | null;
|
|
22
21
|
changeSelected: (selected: T[] | undefined, allChecked?: CheckedState) => void;
|
|
23
22
|
data: T[] | undefined;
|
|
24
23
|
onPageChange?: (page: number, itemsPerPage: number) => void;
|
|
25
|
-
onSort?: (sort: TableSortOptions) => void;
|
|
24
|
+
onSort?: (sort: TableSortOptions<T>) => void;
|
|
26
25
|
pagination: Pagination;
|
|
27
26
|
rowKey?: keyof T;
|
|
28
27
|
}
|
|
@@ -118,14 +118,17 @@ const TableRenderer = ({ filtered, selected, properties, hasUpdateActions, check
|
|
|
118
118
|
return;
|
|
119
119
|
}
|
|
120
120
|
const additionalColumns = selectedTagColumns.map((key, index) => {
|
|
121
|
+
const field = rjstContext.tagField;
|
|
121
122
|
return {
|
|
122
123
|
title: key,
|
|
123
124
|
label: `Tag: ${key}`,
|
|
124
125
|
key: `${TAG_COLUMN_PREFIX}${key}`,
|
|
125
126
|
selected: true,
|
|
126
127
|
type: 'predefined',
|
|
127
|
-
field
|
|
128
|
-
sortable: pagination.serverSide
|
|
128
|
+
field,
|
|
129
|
+
sortable: pagination.serverSide
|
|
130
|
+
? `${field}(tag_key='${key}')/value`
|
|
131
|
+
: true,
|
|
129
132
|
index: index + 1 + columns.length,
|
|
130
133
|
priority: '',
|
|
131
134
|
render: tagKeyRender(key),
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { RJSTEntityPropertyDefinition } from '
|
|
1
|
+
import type { RJSTEntityPropertyDefinition } from '../../schemaOps';
|
|
2
2
|
import type { MenuItemProps } from '@mui/material';
|
|
3
3
|
import type { ColumnPreferencesChangeProp } from './index';
|
|
4
4
|
interface TableActionsProps<T> {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import type { RJSTEntityPropertyDefinition } from '
|
|
2
|
+
import type { RJSTEntityPropertyDefinition } from '../../schemaOps';
|
|
3
3
|
export interface TableCellProps<T> {
|
|
4
4
|
href: string | undefined;
|
|
5
5
|
onRowClick: ((entity: T, event: React.MouseEvent<HTMLAnchorElement>) => void) | undefined;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type * as React from 'react';
|
|
2
2
|
import type { CheckedState, Order } from './utils';
|
|
3
|
-
import type { RJSTEntityPropertyDefinition } from '
|
|
3
|
+
import type { RJSTEntityPropertyDefinition } from '../../schemaOps';
|
|
4
4
|
interface TableHeaderProps<T> {
|
|
5
5
|
columns: Array<RJSTEntityPropertyDefinition<T>>;
|
|
6
6
|
data: T[];
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type * as React from 'react';
|
|
2
2
|
import type { CheckedState } from './utils';
|
|
3
3
|
import { type TableCellProps } from './TableCell';
|
|
4
|
-
import type { RJSTEntityPropertyDefinition } from '
|
|
4
|
+
import type { RJSTEntityPropertyDefinition } from '../../schemaOps';
|
|
5
5
|
export interface TableRowProps<T> {
|
|
6
6
|
row: T;
|
|
7
7
|
rowKey: keyof T;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { MenuItemProps } from '@mui/material';
|
|
2
|
-
import type { RJSTEntityPropertyDefinition } from '
|
|
2
|
+
import type { RJSTEntityPropertyDefinition } from '../../schemaOps';
|
|
3
3
|
import type { ColumnPreferencesChangeProp } from './index';
|
|
4
4
|
interface TableToolbarProps<T> {
|
|
5
5
|
numSelected?: number;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import type { CheckedState, Pagination, TableSortOptions } from './utils';
|
|
3
3
|
import type { MenuItemProps } from '@mui/material';
|
|
4
|
-
import type { RJSTEntityPropertyDefinition } from '
|
|
4
|
+
import type { RJSTEntityPropertyDefinition } from '../../schemaOps';
|
|
5
5
|
export type ColumnPreferencesChangeProp<T> = (columns: Array<RJSTEntityPropertyDefinition<T>>, changeType: 'display' | 'reorder') => void;
|
|
6
6
|
interface TableProps<T> {
|
|
7
7
|
rowKey: keyof T;
|
|
@@ -10,10 +10,10 @@ interface TableProps<T> {
|
|
|
10
10
|
checkedState?: CheckedState;
|
|
11
11
|
columns: Array<RJSTEntityPropertyDefinition<T>>;
|
|
12
12
|
pagination: Pagination;
|
|
13
|
-
sort: TableSortOptions
|
|
13
|
+
sort: TableSortOptions<T>;
|
|
14
14
|
actions?: MenuItemProps[];
|
|
15
15
|
onCheck?: (selected: T[] | undefined, allChecked?: CheckedState) => void;
|
|
16
|
-
onSort?: (sort: TableSortOptions) => void;
|
|
16
|
+
onSort?: (sort: TableSortOptions<T>) => void;
|
|
17
17
|
getRowHref: ((entry: any) => string) | undefined;
|
|
18
18
|
onRowClick?: (entity: T, event: React.MouseEvent<HTMLAnchorElement>) => void;
|
|
19
19
|
onPageChange?: (page: number, itemsPerPage: number) => void;
|
|
@@ -55,7 +55,7 @@ export const Table = ({ rowKey, data, checkedItems = [], checkedState, columns,
|
|
|
55
55
|
? new Map(visibleRows.map((row) => [row[rowKey], row]))
|
|
56
56
|
: null;
|
|
57
57
|
}, [visibleRows, rowKey]);
|
|
58
|
-
const handleOnSort = React.useCallback((_event, { key, field, refScheme }) => {
|
|
58
|
+
const handleOnSort = React.useCallback((_event, { key, field, refScheme, sortable }) => {
|
|
59
59
|
if (!sort) {
|
|
60
60
|
return;
|
|
61
61
|
}
|
|
@@ -66,6 +66,7 @@ export const Table = ({ rowKey, data, checkedItems = [], checkedState, columns,
|
|
|
66
66
|
const sortObj = {
|
|
67
67
|
direction: newOrder,
|
|
68
68
|
field: field,
|
|
69
|
+
sortable,
|
|
69
70
|
key: key,
|
|
70
71
|
refScheme: refScheme,
|
|
71
72
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type React from 'react';
|
|
2
2
|
import type { TagField } from './utils';
|
|
3
|
-
import type { RJSTEntityPropertyDefinition } from '
|
|
3
|
+
import type { RJSTEntityPropertyDefinition } from '../../schemaOps';
|
|
4
4
|
export declare const TAG_COLUMN_PREFIX = "tag_column_";
|
|
5
5
|
export declare function useColumns<T>(resourceName: string, defaultColumns: Array<RJSTEntityPropertyDefinition<T>>, tagKeyRender: (key: string) => (tags: TagField[] | undefined) => React.ReactElement | null): readonly [{
|
|
6
6
|
render: ((tags: TagField[] | undefined) => React.ReactElement | null) | ((value: any, row: T) => string | number | JSX.Element | null | undefined);
|
|
@@ -10,7 +10,7 @@ export declare function useColumns<T>(resourceName: string, defaultColumns: Arra
|
|
|
10
10
|
label: string | JSX.Element;
|
|
11
11
|
field: Extract<keyof T, string>;
|
|
12
12
|
key: string;
|
|
13
|
-
sortable: boolean | ((a: T, b: T) => number);
|
|
13
|
+
sortable: string | boolean | ((a: T, b: T) => number);
|
|
14
14
|
type: string;
|
|
15
15
|
priority: string;
|
|
16
16
|
refScheme?: string;
|
|
@@ -22,7 +22,7 @@ export declare function useColumns<T>(resourceName: string, defaultColumns: Arra
|
|
|
22
22
|
label: string | JSX.Element;
|
|
23
23
|
field: Extract<keyof T, string>;
|
|
24
24
|
key: string;
|
|
25
|
-
sortable: boolean | ((a: T, b: T) => number);
|
|
25
|
+
sortable: string | boolean | ((a: T, b: T) => number);
|
|
26
26
|
type: string;
|
|
27
27
|
priority: string;
|
|
28
28
|
refScheme?: string;
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import type { RJSTEntityPropertyDefinition } from '../../schemaOps';
|
|
1
2
|
export type Order = 'asc' | 'desc';
|
|
2
3
|
export type CheckedState = 'none' | 'some' | 'all';
|
|
3
|
-
export interface TableSortOptions {
|
|
4
|
+
export interface TableSortOptions<T> {
|
|
4
5
|
direction: Order;
|
|
5
6
|
field: string;
|
|
6
7
|
key: string;
|
|
8
|
+
sortable: RJSTEntityPropertyDefinition<T>['sortable'];
|
|
7
9
|
refScheme?: string;
|
|
8
10
|
}
|
|
9
11
|
export type Pagination = {
|
|
@@ -59,16 +59,3 @@ export interface RJSTProps<T> extends Omit<BoxProps, 'onChange'> {
|
|
|
59
59
|
}
|
|
60
60
|
export declare const RJST: <T extends RJSTBaseResource<T>>({ model: modelRaw, data, formats, actions, sdk, customSort, refresh, getBaseUrl, onEntityClick, onChange, pagination, customLenses, loading, rowKey, noDataInfo, persistFilters, ...boxProps }: RJSTProps<T>) => import("react/jsx-runtime").JSX.Element;
|
|
61
61
|
export { rjstRunTransformers, rjstDefaultPermissions, rjstGetModelForCollection, rjstAddToSchema, type RJSTAction, type RJSTBaseResource, type RJSTRawModel, type RJSTModel, rjstJsonSchemaPick, rjstGetDisabledReason, getPropertyScheme, getSubSchemaFromRefScheme, parseDescription, parseDescriptionProperty, generateSchemaFromRefScheme, };
|
|
62
|
-
export type RJSTEntityPropertyDefinition<T> = {
|
|
63
|
-
title: string;
|
|
64
|
-
label: string | JSX.Element;
|
|
65
|
-
field: Extract<keyof T, string>;
|
|
66
|
-
key: string;
|
|
67
|
-
selected: boolean;
|
|
68
|
-
index: number;
|
|
69
|
-
sortable: boolean | ((a: T, b: T) => number);
|
|
70
|
-
render: (value: any, row: T) => string | number | JSX.Element | null | undefined;
|
|
71
|
-
type: string;
|
|
72
|
-
priority: string;
|
|
73
|
-
refScheme?: string;
|
|
74
|
-
};
|
|
@@ -7,5 +7,5 @@ interface FilterMutation extends JSONSchema {
|
|
|
7
7
|
$or?: any[];
|
|
8
8
|
}
|
|
9
9
|
export declare const convertToPineClientFilter: (parentKeys: string[], filter: FilterMutation | FilterMutation[]) => PineFilterObject | undefined;
|
|
10
|
-
export declare const orderbyBuilder: <T>(sortInfo: TableSortOptions | null, customSort: RJSTContext<T>["customSort"]) => string[] | null;
|
|
10
|
+
export declare const orderbyBuilder: <T>(sortInfo: TableSortOptions<T> | null, customSort: RJSTContext<T>["customSort"]) => string[] | null;
|
|
11
11
|
export {};
|
|
@@ -177,7 +177,7 @@ export const convertToPineClientFilter = (parentKeys, filter) => {
|
|
|
177
177
|
return handlePrimitiveFilter(parentKeys, filter);
|
|
178
178
|
};
|
|
179
179
|
export const orderbyBuilder = (sortInfo, customSort) => {
|
|
180
|
-
var _a;
|
|
180
|
+
var _a, _b;
|
|
181
181
|
if (!sortInfo) {
|
|
182
182
|
return null;
|
|
183
183
|
}
|
|
@@ -188,7 +188,7 @@ export const orderbyBuilder = (sortInfo, customSort) => {
|
|
|
188
188
|
// TODO: Refactor this logic to create an object structure and retrieve the correct property using the refScheme.
|
|
189
189
|
// The customSort should look like: { user: { owns_items: [{ uuid: 'xx09x0' }] } }
|
|
190
190
|
// The refScheme will reference the property path, e.g., owns_items[0].uuid.
|
|
191
|
-
const customOrderByKey = (_a = customSort === null || customSort === void 0 ? void 0 : customSort[`${field}_${refScheme}`]) !== null && _a !== void 0 ? _a : customSort === null || customSort === void 0 ? void 0 : customSort[field];
|
|
191
|
+
const customOrderByKey = (_b = (_a = customSort === null || customSort === void 0 ? void 0 : customSort[`${field}_${refScheme}`]) !== null && _a !== void 0 ? _a : customSort === null || customSort === void 0 ? void 0 : customSort[field]) !== null && _b !== void 0 ? _b : (typeof sortInfo.sortable === 'string' ? sortInfo.sortable : undefined);
|
|
192
192
|
if (typeof customOrderByKey === 'string') {
|
|
193
193
|
return [`${customOrderByKey} ${direction}`, `id ${direction}`];
|
|
194
194
|
}
|
|
@@ -33,6 +33,19 @@ export interface RJSTModel<T> {
|
|
|
33
33
|
permissions: Permissions<T>;
|
|
34
34
|
priorities?: Priorities<T>;
|
|
35
35
|
}
|
|
36
|
+
export type RJSTEntityPropertyDefinition<T> = {
|
|
37
|
+
title: string;
|
|
38
|
+
label: string | JSX.Element;
|
|
39
|
+
field: Extract<keyof T, string>;
|
|
40
|
+
key: string;
|
|
41
|
+
selected: boolean;
|
|
42
|
+
sortable: boolean | ((a: T, b: T) => number) | string;
|
|
43
|
+
index: number;
|
|
44
|
+
render: (value: any, row: T) => string | number | JSX.Element | null | undefined;
|
|
45
|
+
type: string;
|
|
46
|
+
priority: string;
|
|
47
|
+
refScheme?: string;
|
|
48
|
+
};
|
|
36
49
|
export interface CustomSchemaDescription {
|
|
37
50
|
'x-ref-scheme'?: string[];
|
|
38
51
|
'x-foreign-key-scheme'?: string[];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@balena/ui-shared-components",
|
|
3
|
-
"version": "12.1.0
|
|
3
|
+
"version": "12.1.0",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"files": [
|
|
@@ -33,7 +33,6 @@
|
|
|
33
33
|
"ajv-formats": "^3.0.1",
|
|
34
34
|
"ajv-keywords": "^5.1.0",
|
|
35
35
|
"analytics-client": "^2.0.1",
|
|
36
|
-
"balena-semver": "^3.0.5",
|
|
37
36
|
"color": "^5.0.0",
|
|
38
37
|
"color-hash": "^2.0.2",
|
|
39
38
|
"date-fns": "^4.1.0",
|
|
@@ -55,7 +54,7 @@
|
|
|
55
54
|
"zxcvbn": "^4.4.2"
|
|
56
55
|
},
|
|
57
56
|
"devDependencies": {
|
|
58
|
-
"@balena/lint": "^9.1
|
|
57
|
+
"@balena/lint": "^9.2.1",
|
|
59
58
|
"@storybook/addon-essentials": "^8.6.12",
|
|
60
59
|
"@storybook/addon-interactions": "^8.6.12",
|
|
61
60
|
"@storybook/addon-links": "^8.6.12",
|
|
@@ -139,6 +138,6 @@
|
|
|
139
138
|
},
|
|
140
139
|
"homepage": "https://github.com/balena-io/ui-shared-components#readme",
|
|
141
140
|
"versionist": {
|
|
142
|
-
"publishedAt": "2025-04-
|
|
141
|
+
"publishedAt": "2025-04-09T13:16:39.918Z"
|
|
143
142
|
}
|
|
144
143
|
}
|