Merge branch 'main' into feat-automatic-migrate-windows-hosts

This commit is contained in:
Martin Angers
2024-12-03 11:07:25 -05:00
committed by GitHub
90 changed files with 919 additions and 268 deletions
+49
View File
@@ -1,3 +1,52 @@
## Fleet 4.60.0 (Nov 27, 2024)
### Endpoint operations
- Added support for labels_include_any to gitops.
- Added major improvements to keyboard accessibility throughout app (e.g. checkboxes, dropdowns, table navigation).
- Added activity item for `fleetd` enrollment with host serial and display name.
- Added capability for Fleet to serve YARA rules to agents over HTTPS authenticated via node key (requires osquery 5.14+).
- Added a query to allow users to turn on/off automations while being transparent of the current log destination.
- Updated UI to allow users to view scripts (from both the scripts page and host details page) without downloading them.
- Updated activity feed to generate an activity when activity automations are enabled, edited, or disabled.
- Cancelled pending script executions when a script is edited or deleted.
### Device management (MDM)
- Added better handling of timeout and insufficient permissions errors in NDES SCEP proxy.
- Added info banner for cloud customers to help with their windows autoenrollment setup.
- Added DB support for "include any" label profile deployment.
- Added support for "include any" label/profile relationships to the profile reconciliation machinery.
- Added `team_identifier` signature information to Apple macOS applications to the `/api/latest/fleet/hosts/:id/software` API endpoint.
- Added indicator of how fresh a software title's host and version counts are on the title's details page.
- Added UI for allowing users to install custom profiles on hosts that include any of the defined labels.
- Added UI features supporting disk encryption for Ubuntu and Fedora Linux.
- Added support for deb packages compressed with zstd.
### Vulnerability management
- Allowed skipping computationally heavy population of vulnerability details when populating host software on hosts list endpoint (`GET /api/latest/fleet/hosts`) when using Fleet Premium (`populate_software=without_vulnerability_descriptions`).
### Bug fixes and improvements
- Improved memory usage of the Fleet server when uploading a large software installer file. Note that the installer will now use (temporary) disk space and sufficient storage space is required.
- Improved performance of adding and removing profiles to large teams by an order of magnitude.
- Disabled accessibility via keyboard for forms that are disabled via a slider.
- Updated software batch endpoint status code from 200 (OK) to 202 (Accepted).
- Updated a package used for testing (msw) to improve security.
- Updated to reboot linux machine on unlock to work around GDM bug on Ubuntu 24.04.
- Updated GitOps to return an error if the deprecated `apple_bm_default_team` key is used and there are more than 1 ABM tokens in Fleet.
- Dismissed error flash on the my device page when navigating to another URL.
- Modified the Fleet setup experience feature to not run if there is no software or script configured for the setup experience.
- Set a more accurate minimum height for the Add hosts > ChromeOS > Policy for extension field, avoiding a scrollbar.
- Added UI prompt for user to reenter the password if SCEP/NDES url or username has changed.
- Updated ABM public key to download as as PEM format instead of CRT.
- Fixed issue with uploading macOS software packages that do not have a top level `Distribution.xml`, but do have a top level `PackageInfo.xml`. For example, Okta Verify.app.
- Fixed some cases where Fleet Maintained Apps generated incorrect uninstall scripts.
- Fixed a bug where a device that was removed from ABM and then added back wouldn't properly re-enroll in Fleet MDM.
- Fixed name/version parsing issue with PE (EXE) installer self-extracting archives such as Opera.
- Fixed a bug where the create and update label endpoints could return outdated information in a deployment using a mysql replica.
- Fixed the MDM configuration profiles deployment when based on excluded labels.
- Fixed gitops path resolution for installer queries and scripts to always be relative to where the query file or script is referenced. This change breaks existing YAML files that had to account for previous inconsistent behavior (e.g. installers in a subdirectory referencing scripts elsewhere).
- Fixed issue where minimum OS version enforcement was not being applied during Apple ADE if MDM IdP integration was enabled.
- Fixed a bug where users would be allowed to attempt an install of an App Store app on a host that was not MDM enrolled.
## Fleet 4.59.1 (Nov 18, 2024)
### Bug fixes
-1
View File
@@ -1 +0,0 @@
* Added capability for Fleet to serve yara rules to agents over HTTPS authenticated via node key (requires osquery 5.14+).
@@ -1 +0,0 @@
* Improved memory usage of the Fleet server when uploading a large software installer file. Note that the installer will now use (temporary) disk space and sufficient storage space is required.
@@ -1 +0,0 @@
- Speed up adding and removing profiles to large teams by an order of magnitude
@@ -1 +0,0 @@
- add info banner for cloud customers to help with their windows autoenrollment setup
@@ -1 +0,0 @@
* Generate an activity when activity automations are enabled, edited, or disabled.
-1
View File
@@ -1 +0,0 @@
* Cancelled pending script executions when a script is edited or deleted.
+1
View File
@@ -0,0 +1 @@
* Replaced the internal use of the deprecated `go.mozilla.org/pkcs7` package with the maintained fork `github.com/smallstep/pkcs7`.
@@ -1 +0,0 @@
* Fixed the MDM configuration profiles deployment when based on excluded labels - prior to this fix, hosts were considered "not a member" of the label by default, even if they had not yet returned results for the excluded labels. The fix checks the label's creation time vs the host's last reported label results timestamp to prevent deploying a configuration profile if it does not yet know if the host is a member or not of those labels.
@@ -1 +0,0 @@
* GitOps: Fixed path resolution for installer queries and scripts to always be relative to where the query file or script is referenced. This change breaks existing YAML files that had to account for previous inconsistent behavior (e.g. installers in a subdirectory referencing scripts elsewhere).
-1
View File
@@ -1 +0,0 @@
- Creating a query allow users to turn on/off automations while being transparent of the current log destination
-1
View File
@@ -1 +0,0 @@
* Added indicator of how fresh a software title's host and version counts are on the title's details page
-2
View File
@@ -1,2 +0,0 @@
- Updates GitOps to return an error if the deprecated `apple_bm_default_team` key is used and there
are more than 1 ABM tokens in Fleet.
-2
View File
@@ -1,2 +0,0 @@
- Fixed issue where minimum OS version enforcement was not being applied during Apple ADE if MDM
IdP integration was enabled.
-1
View File
@@ -1 +0,0 @@
- Reboot linux machine on unlock to work around GDM bug on Ubuntu 24.04
-1
View File
@@ -1 +0,0 @@
- Users can view scripts in the UI (from both the scripts page and host details page) without downloading them
-2
View File
@@ -1,2 +0,0 @@
- add UI for allowing users to install custom profiles on hosts that include any of the defined
labels
-1
View File
@@ -1 +0,0 @@
- Add support for labels_include_any to gitops
-1
View File
@@ -1 +0,0 @@
- Adds DB support for "include any" label profile deployment
-1
View File
@@ -1 +0,0 @@
- Adds support for "include any" label/profile relationships to the profile reconciliation machinery.
-1
View File
@@ -1 +0,0 @@
- Fleet UI: Major improvements to keyboard accessibility throughout app (e.g. checkboxes, dropdowns, table navigation)
-1
View File
@@ -1 +0,0 @@
- Added UI features supporting disk encryption for Ubuntu and Fedora Linux.
-1
View File
@@ -1 +0,0 @@
- Fix some cases where Fleet Maintained Apps generated incorrect uninstall scripts
-1
View File
@@ -1 +0,0 @@
Added activity item for fleetd enrollment with host serial and display name.
-1
View File
@@ -1 +0,0 @@
- Add support for deb packages compressed with zstd
@@ -1 +0,0 @@
- Fleet UI: Disable accessibility via keyboard for forms that are disabled via a slider
@@ -1,2 +0,0 @@
* Set a more elegant minimum height for the Add hosts > ChromeOS > Policy for extension field,
avoiding a scrollbar.
-1
View File
@@ -1 +0,0 @@
- Download ABM public key as PEM format instead of CRT
@@ -1 +0,0 @@
* Allowed skipping computationally heavy population of vulnerability details when populating host software on hosts list endpoint (`GET /api/latest/fleet/hosts`) when using Fleet Premium (`populate_software=without_vulnerability_descriptions`)
@@ -1 +0,0 @@
- update a package used for testing (msw) to improve security
-2
View File
@@ -1,2 +0,0 @@
- Fixes a bug where a device that was removed from ABM and then added back wouldn't properly
re-enroll in Fleet MDM
-1
View File
@@ -1 +0,0 @@
Fixed issue with uploading macOS software packages that do not have a top level Distribution.xml, but do have a top level PackageInfo.xml. For example, Okta Verify.app
-2
View File
@@ -1,2 +0,0 @@
- Fixes a bug where users would be allowed to attempt an install of an App Store app on a host that
was not MDM enrolled.
@@ -0,0 +1 @@
- Display Windows MDM WSTEP flags in `fleet --help`.
-1
View File
@@ -1 +0,0 @@
* Updated software batch endpoint status code from 200 (OK) to 202 (Accepted)
-1
View File
@@ -1 +0,0 @@
Added better handling of timeout and insufficient permissions errors in NDES SCEP proxy.
-1
View File
@@ -1 +0,0 @@
Fixed name/version parsing issue with PE (EXE) installer self-extracting archives such as Opera.
@@ -1 +0,0 @@
* Fixed a bug where the create and update label endpoints could return outdated information in a deployment using a mysql replica.
-1
View File
@@ -1 +0,0 @@
- Fleet UI: Prompt user to reenter the password if SCEP/NDES url or username has changed
@@ -1 +0,0 @@
* Dismiss error flash on the my device page when navigating to another URL.
-2
View File
@@ -1,2 +0,0 @@
- Modifies the Fleet setup experience feature to not run if there is no software or script
configured for the setup experience.
+2 -2
View File
@@ -4,11 +4,11 @@ name: fleet
keywords:
- fleet
- osquery
version: v6.2.2
version: v6.2.3
home: https://github.com/fleetdm/fleet
sources:
- https://github.com/fleetdm/fleet.git
appVersion: v4.59.1
appVersion: v4.60.0
dependencies:
- name: mysql
condition: mysql.enabled
+1 -1
View File
@@ -3,7 +3,7 @@
hostName: fleet.localhost
replicas: 3 # The number of Fleet instances to deploy
imageRepository: fleetdm/fleet
imageTag: v4.59.1 # Version of Fleet to deploy
imageTag: v4.60.0 # Version of Fleet to deploy
podAnnotations: {} # Additional annotations to add to the Fleet pod
serviceAccountAnnotations: {} # Additional annotations to add to the Fleet service account
resources:
-5
View File
@@ -37,7 +37,6 @@ export interface IModalProps {
* */
disableClosingModal?: boolean;
className?: string;
actionsFooter?: JSX.Element;
}
const Modal = ({
@@ -51,7 +50,6 @@ const Modal = ({
isContentDisabled = false,
disableClosingModal = false,
className,
actionsFooter,
}: IModalProps): JSX.Element => {
useEffect(() => {
const closeWithEscapeKey = (e: KeyboardEvent) => {
@@ -127,9 +125,6 @@ const Modal = ({
)}
<div className={contentClasses}>{children}</div>
</div>
{actionsFooter && (
<div className={`${baseClass}__actions-footer`}>{actionsFooter}</div>
)}
</div>
</div>
);
+5 -8
View File
@@ -25,17 +25,9 @@
&__content-wrapper {
margin-top: $pad-large;
font-size: $x-small;
// New pattern of max height modals pushed to 4.61 with PR #24019
overflow: visible;
.input-field {
width: 100%;
&::placeholder {
font-size: $x-small;
font-style: italic;
line-height: 24px;
}
}
form .modal-cta-wrap,
@@ -142,6 +134,11 @@
}
}
.modal-scrollable-content {
overflow-y: auto;
max-height: 705px;
}
.modal-cta-wrap {
align-self: flex-end;
display: flex;
@@ -0,0 +1,63 @@
/* eslint-disable no-alert */
import React from "react";
import { Meta, Story } from "@storybook/react";
import Button from "components/buttons/Button";
import Icon from "components/Icon";
import ActionsDropdown from "components/ActionsDropdown";
import ModalFooter from "./ModalFooter";
export default {
title: "Components/ModalFooter",
component: ModalFooter,
} as Meta;
const Template: Story = (args) => (
<ModalFooter primaryButtons={<></>} {...args} />
);
export const Default = Template.bind({});
Default.args = {
primaryButtons: (
<>
<ActionsDropdown
className="modal-footer__manage-automations-dropdown"
onChange={(value) => alert(`Selected action: ${value}`)}
placeholder="More actions"
isSearchable={false}
options={[
{ value: "action1", label: "Action 1" },
{ value: "action2", label: "Action 2" },
]}
menuPlacement="top"
/>
<Button onClick={() => alert("Done clicked")} variant="brand">
Done
</Button>
</>
),
secondaryButtons: (
<>
<Button variant="icon" onClick={() => alert("Download clicked")}>
<Icon name="download" />
</Button>
<Button variant="icon" onClick={() => alert("Delete clicked")}>
<Icon name="trash" color="ui-fleet-black-75" />
</Button>
</>
),
isTopScrolling: false,
};
export const WithTopScrolling = Template.bind({});
WithTopScrolling.args = {
...Default.args,
isTopScrolling: true,
};
export const WithoutSecondaryButtons = Template.bind({});
WithoutSecondaryButtons.args = {
primaryButtons: Default.args.primaryButtons,
secondaryButtons: undefined,
isTopScrolling: false,
};
@@ -0,0 +1,44 @@
import React from "react";
import classnames from "classnames";
import { COLORS } from "styles/var/colors";
const baseClass = "modal-footer";
interface IModalFooterProps {
primaryButtons: JSX.Element;
secondaryButtons?: JSX.Element;
className?: string;
/** Renders a line above action buttons to indicate scrollability */
isTopScrolling?: boolean;
}
const ModalFooter = ({
primaryButtons,
secondaryButtons,
className,
isTopScrolling = false,
}: IModalFooterProps): JSX.Element => {
const classes = classnames(className, `${baseClass}__content-wrapper`);
return (
<div
className={classes}
style={{
borderTop: isTopScrolling
? `1px solid ${COLORS["ui-fleet-black-50"]}`
: "none",
}}
>
<div className={`${baseClass}__primary-buttons-wrapper`}>
{primaryButtons}
</div>
{secondaryButtons && (
<div className={`${baseClass}__secondary-buttons-wrapper`}>
{secondaryButtons}
</div>
)}
</div>
);
};
export default ModalFooter;
@@ -0,0 +1,27 @@
.modal-footer {
&__content-wrapper {
align-self: flex-end;
display: flex;
flex-direction: row-reverse;
padding-top: $pad-medium;
justify-content: space-between;
}
// Styles both primary-actions and secondary-actions
&__primary-buttons-wrapper,
&__secondary-buttons_wrapper {
display: flex;
justify-content: space-between;
gap: $pad-medium;
align-items: center;
}
// Align primary actions right if no secondary actions
> :last-child {
margin-left: auto;
}
.button__text-icon {
padding: 11px;
}
}
+1
View File
@@ -0,0 +1 @@
export { default } from "./ModalFooter";
@@ -18,7 +18,7 @@ import {
import Icon from "components/Icon";
interface INumberDropdownOption extends Omit<IDropdownOption, "value"> {
export interface INumberDropdownOption extends Omit<IDropdownOption, "value"> {
value: number; // Redefine the value property to be just number
}
@@ -1,7 +1,8 @@
// Used with old react-select dropdown and
// New react-select-5 ActionsDropdown.tsx
// New react-select-5: ActionsDropdown.tsx, DropdownWrapper.tsx
.Select > .Select-menu-outer,
.actions-dropdown {
.actions-dropdown,
.react-select__option {
.is-disabled * {
color: $ui-fleet-black-50;
}
@@ -0,0 +1,62 @@
// stories/DropdownWrapper.stories.tsx
import React from "react";
import { Meta, Story } from "@storybook/react";
import DropdownWrapper, {
IDropdownWrapper,
CustomOptionType,
} from "./DropdownWrapper";
// Define metadata for the story
export default {
title: "Components/DropdownWrapper",
component: DropdownWrapper,
argTypes: {
onChange: { action: "changed" },
},
} as Meta;
// Define a template for the stories
const Template: Story<IDropdownWrapper> = (args) => (
<DropdownWrapper {...args} />
);
// Sample options to be used in the dropdown
const sampleOptions: CustomOptionType[] = [
{ label: "Option 1", value: "option1", helpText: "Help text for option 1" },
{
label: "Option 2",
value: "option2",
tooltipContent: "Tooltip for option 2",
},
{ label: "Option 3", value: "option3", isDisabled: true },
];
// Default story
export const Default = Template.bind({});
Default.args = {
options: sampleOptions,
name: "dropdown-example",
label: "Select an option",
};
// Disabled story
export const Disabled = Template.bind({});
Disabled.args = {
...Default.args,
isDisabled: true,
};
// With Help Text story
export const WithHelpText = Template.bind({});
WithHelpText.args = {
...Default.args,
helpText: "This is some help text for the dropdown",
};
// With Error story
export const WithError = Template.bind({});
WithError.args = {
...Default.args,
error: "This is an error message",
};
@@ -0,0 +1,102 @@
import React from "react";
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import DropdownWrapper, { CustomOptionType } from "./DropdownWrapper";
const sampleOptions: CustomOptionType[] = [
{
label: "Option 1",
value: "option1",
tooltipContent: "Tooltip 1",
helpText: "Help text 1",
},
{
label: "Option 2",
value: "option2",
tooltipContent: "Tooltip 2",
helpText: "Help text 2",
},
];
describe("DropdownWrapper Component", () => {
const mockOnChange = jest.fn();
beforeEach(() => {
jest.clearAllMocks();
});
test("renders with help text", () => {
render(
<DropdownWrapper
options={sampleOptions}
value="option1"
onChange={mockOnChange}
name="test-dropdown"
label="Test Dropdown"
helpText="This is a help text."
/>
);
expect(screen.getByText(/test dropdown/i)).toBeInTheDocument();
expect(screen.getByText(/this is a help text/i)).toBeInTheDocument();
});
test("calls onChange when an option is selected", async () => {
render(
<DropdownWrapper
options={sampleOptions}
value="option1"
onChange={mockOnChange}
name="test-dropdown"
label="Test Dropdown"
placeholder="Choose option"
/>
);
// Open the dropdown
await userEvent.click(screen.getByText(/option 1/i));
// Select Option 2
await userEvent.click(screen.getByText(/option 2/i));
expect(mockOnChange).toHaveBeenCalledWith({
helpText: "Help text 2",
label: "Option 2",
tooltipContent: "Tooltip 2",
value: "option2",
});
});
test("renders error message when provided", () => {
render(
<DropdownWrapper
options={sampleOptions}
value="option1"
onChange={mockOnChange}
name="test-dropdown"
label="Test Dropdown"
error="This is an error message."
/>
);
expect(screen.getByText(/this is an error message/i)).toBeInTheDocument();
});
test("displays no options message when no options are available", async () => {
render(
<DropdownWrapper
options={[]}
value=""
onChange={mockOnChange}
name="test-dropdown"
label="Test Dropdown"
placeholder="Choose option"
/>
);
// Open dropdown
await userEvent.click(screen.getByText(/choose option/i));
expect(screen.getByText(/no results found/i)).toBeInTheDocument();
});
});
@@ -0,0 +1,340 @@
/**
* This is a new component built off react-select 5.4
* meant to replace Dropdown.jsx built off react-select 1.3
*
* See storybook component for current functionality
*
* Prototyped on UserForm.tsx but added and tested the following:
* Options: text, disabled, option helptext, option tooltip
* Other: label text, dropdown help text, dropdown error
*/
import classnames from "classnames";
import React from "react";
import Select, {
StylesConfig,
DropdownIndicatorProps,
OptionProps,
components,
PropsValue,
SingleValue,
} from "react-select-5";
import { COLORS } from "styles/var/colors";
import { PADDING } from "styles/var/padding";
import FormField from "components/forms/FormField";
import DropdownOptionTooltipWrapper from "components/forms/fields/Dropdown/DropdownOptionTooltipWrapper";
import Icon from "components/Icon";
const getOptionBackgroundColor = (state: any) => {
return state.isSelected || state.isFocused
? COLORS["ui-vibrant-blue-10"]
: "transparent";
};
export interface CustomOptionType {
label: string;
value: string;
tooltipContent?: string;
helpText?: string;
isDisabled?: boolean;
}
export interface IDropdownWrapper {
options: CustomOptionType[];
value?: PropsValue<CustomOptionType> | string;
onChange: (newValue: SingleValue<CustomOptionType>) => void;
name: string;
className?: string;
labelClassname?: string;
error?: string;
label?: JSX.Element | string;
helpText?: JSX.Element | string;
isSearchable?: boolean;
isDisabled?: boolean;
placeholder?: string;
menuPortalTarget?: HTMLElement | null;
}
const baseClass = "dropdown-wrapper";
const DropdownWrapper = ({
options,
value,
onChange,
name,
className,
labelClassname,
error,
label,
helpText,
isSearchable,
isDisabled = false,
placeholder,
menuPortalTarget,
}: IDropdownWrapper) => {
const wrapperClassNames = classnames(baseClass, className);
const handleChange = (newValue: SingleValue<CustomOptionType>) => {
onChange(newValue);
};
// Ability to handle value of type string or CustomOptionType
const getCurrentValue = () => {
if (typeof value === "string") {
return options.find((option) => option.value === value) || null;
}
return value;
};
interface CustomOptionProps
extends Omit<OptionProps<CustomOptionType, false>, "data"> {
data: CustomOptionType;
}
const CustomOption = (props: CustomOptionProps) => {
const { data, ...rest } = props;
const optionContent = (
<div className={`${baseClass}__option`}>
{data.label}
{data.helpText && (
<span className={`${baseClass}__help-text`}>{data.helpText}</span>
)}
</div>
);
return (
<components.Option {...rest} data={data}>
{data.tooltipContent ? (
<DropdownOptionTooltipWrapper tipContent={data.tooltipContent}>
{optionContent}
</DropdownOptionTooltipWrapper>
) : (
optionContent
)}
</components.Option>
);
};
const CustomDropdownIndicator = (
props: DropdownIndicatorProps<any, false, any>
) => {
const { isFocused, selectProps } = props;
const color =
isFocused || selectProps.menuIsOpen
? "core-fleet-blue"
: "core-fleet-black";
return (
<components.DropdownIndicator
{...props}
className={`${baseClass}__indicator`}
>
<Icon
name="chevron-down"
color={color}
className={`${baseClass}__icon`}
/>
</components.DropdownIndicator>
);
};
const customStyles: StylesConfig<CustomOptionType, false> = {
container: (provided) => ({
...provided,
width: "100%",
height: "40px",
}),
control: (provided, state) => ({
...provided,
display: "flex",
flexDirection: "row",
width: "100%",
backgroundColor: COLORS["ui-off-white"],
paddingLeft: "8px", // TODO: Update to match styleguide of (16px) when updating rest of UI (8px)
paddingRight: "8px",
cursor: "pointer",
boxShadow: "none",
borderRadius: "4px",
borderColor: state.isFocused
? COLORS["core-fleet-blue"]
: COLORS["ui-fleet-black-10"],
"&:hover": {
boxShadow: "none",
borderColor: COLORS["core-fleet-blue"],
".dropdown-wrapper__single-value": {
color: COLORS["core-vibrant-blue-over"],
},
".dropdown-wrapper__indicator path": {
stroke: COLORS["core-vibrant-blue-over"],
},
},
// When tabbing
// Relies on --is-focused for styling as &:focus-visible cannot be applied
"&.dropdown-wrapper__control--is-focused": {
".dropdown-wrapper__single-value": {
color: COLORS["core-vibrant-blue-over"],
},
".dropdown-wrapper__indicator path": {
stroke: COLORS["core-vibrant-blue-over"],
},
},
...(state.isDisabled && {
".dropdown-wrapper__single-value": {
color: COLORS["ui-fleet-black-50"],
},
".dropdown-wrapper__indicator path": {
stroke: COLORS["ui-fleet-black-50"],
},
}),
"&:active": {
".dropdown-wrapper__single-value": {
color: COLORS["core-vibrant-blue-down"],
},
".dropdown-wrapper__indicator path": {
stroke: COLORS["core-vibrant-blue-down"],
},
},
...(state.menuIsOpen && {
".dropdown-wrapper__indicator svg": {
transform: "rotate(180deg)",
transition: "transform 0.25s ease",
},
}),
}),
singleValue: (provided) => ({
...provided,
fontSize: "16px",
margin: 0,
padding: 0,
}),
dropdownIndicator: (provided) => ({
...provided,
display: "flex",
padding: "2px",
svg: {
transition: "transform 0.25s ease",
},
}),
menu: (provided) => ({
...provided,
boxShadow: "0 2px 6px rgba(0, 0, 0, 0.1)",
borderRadius: "4px",
zIndex: 6,
overflow: "hidden",
border: 0,
marginTop: 0,
maxHeight: "none",
position: "absolute",
left: "0",
animation: "fade-in 150ms ease-out",
}),
menuList: (provided) => ({
...provided,
padding: PADDING["pad-small"],
}),
valueContainer: (provided) => ({
...provided,
padding: 0,
}),
option: (provided, state) => ({
...provided,
padding: "10px 8px",
fontSize: "14px",
backgroundColor: getOptionBackgroundColor(state),
color: COLORS["core-fleet-black"],
"&:hover": {
backgroundColor: state.isDisabled
? "transparent"
: COLORS["ui-vibrant-blue-10"],
},
"&:active": {
backgroundColor: state.isDisabled
? "transparent"
: COLORS["ui-vibrant-blue-10"],
},
...(state.isDisabled && {
color: COLORS["ui-fleet-black-50"],
fontStyle: "italic",
cursor: "not-allowed",
pointerEvents: "none",
}),
// Styles for custom option
".dropdown-wrapper__option": {
display: "flex",
flexDirection: "column",
gap: "8px",
width: "100%",
},
".dropdown-wrapper__help-text": {
fontSize: "12px",
whiteSpace: "normal",
color: COLORS["ui-fleet-black-50"],
fontStyle: "italic",
},
}),
menuPortal: (base) => ({ ...base, zIndex: 999 }), // Not hidden beneath scrollable sections
noOptionsMessage: (provided) => ({
...provided,
textAlign: "left",
fontSize: "14px",
padding: "10px 8px",
}),
};
const renderLabel = () => {
const labelWrapperClasses = classnames(
`${baseClass}__label`,
labelClassname,
{
[`${baseClass}__label--error`]: !!error,
[`${baseClass}__label--disabled`]: isDisabled,
}
);
if (!label) {
return "";
}
return (
<label className={labelWrapperClasses} htmlFor={name}>
{error || label}
</label>
);
};
return (
<FormField
name={name}
label={renderLabel()}
helpText={helpText}
type="dropdown"
className={wrapperClassNames}
>
<Select<CustomOptionType, false>
classNamePrefix="react-select"
isSearchable={isSearchable}
styles={customStyles}
options={options}
components={{
Option: CustomOption,
DropdownIndicator: CustomDropdownIndicator,
IndicatorSeparator: () => null,
}}
value={getCurrentValue()}
onChange={handleChange}
isDisabled={isDisabled}
menuPortalTarget={
menuPortalTarget === undefined ? document.body : menuPortalTarget
}
noOptionsMessage={() => "No results found"}
tabIndex={isDisabled ? -1 : 0} // Ensures disabled dropdown has no keyboard accessibility
placeholder={placeholder}
/>
</FormField>
);
};
export default DropdownWrapper;
@@ -0,0 +1,14 @@
// react-select's <Select/> styles prop customizes the styling of
// the internal components and not external elements like labels
// See customStyles in DropdownWrappr.tsx
// https://react-select.com/styles
.dropdown-wrapper {
&__label {
&--error {
color: $ui-error;
}
&--disabled {
color: $ui-fleet-black-50;
}
}
}
@@ -0,0 +1 @@
export { default } from "./DropdownWrapper";
-16
View File
@@ -1,16 +0,0 @@
import PropTypes from "prop-types";
import { UserRole } from "./user";
export default PropTypes.shape({
disabled: PropTypes.bool,
label: PropTypes.string,
value: PropTypes.any, // eslint-disable-line react/forbid-prop-types
helpText: PropTypes.string,
});
export interface IRole {
disabled: boolean;
label: string;
value: UserRole;
helpText?: string;
}
@@ -1,4 +1,4 @@
import React from "react";
import React, { useState, useEffect, useRef } from "react";
import { useQuery } from "react-query";
import scriptsAPI, { IScriptResultResponse } from "services/entities/scripts";
@@ -10,6 +10,7 @@ import Icon from "components/Icon";
import Textarea from "components/Textarea";
import DataError from "components/DataError/DataError";
import Spinner from "components/Spinner/Spinner";
import ModalFooter from "components/ModalFooter";
const baseClass = "run-script-details-modal";
@@ -166,6 +167,17 @@ const RunScriptDetailsModal = ({
onCancel,
isHidden = false,
}: IRunScriptDetailsModalProps) => {
// For scrollable modal
const [isTopScrolling, setIsTopScrolling] = useState(false);
const topDivRef = useRef<HTMLDivElement>(null);
const checkScroll = () => {
if (topDivRef.current) {
const isScrolling =
topDivRef.current.scrollHeight > topDivRef.current.clientHeight;
setIsTopScrolling(isScrolling);
}
};
const { data, isLoading, isError } = useQuery<IScriptResultResponse>(
["runScriptDetailsModal", scriptExecutionId],
() => {
@@ -174,6 +186,13 @@ const RunScriptDetailsModal = ({
{ refetchOnWindowFocus: false, enabled: !!scriptExecutionId }
);
// For scrollable modal
useEffect(() => {
checkScroll();
window.addEventListener("resize", checkScroll);
return () => window.removeEventListener("resize", checkScroll);
}, [data]); // Re-run when data changes
const renderContent = () => {
let content = <></>;
@@ -206,18 +225,24 @@ const RunScriptDetailsModal = ({
}
return (
<>
<div className={`${baseClass}__modal-content`}>{content}</div>
</>
<div
className={`${baseClass}__modal-content modal-scrollable-content`}
ref={topDivRef}
>
{content}
</div>
);
};
const renderFooter = () => (
<div className={`primary-actions ${baseClass}__host-script-actions`}>
<Button onClick={onCancel} variant="brand">
Done
</Button>
</div>
<ModalFooter
isTopScrolling={isTopScrolling}
primaryButtons={
<Button onClick={onCancel} variant="brand">
Done
</Button>
}
/>
);
return (
<Modal
@@ -226,9 +251,11 @@ const RunScriptDetailsModal = ({
onEnter={onCancel}
className={baseClass}
isHidden={isHidden}
actionsFooter={renderFooter()}
>
{renderContent()}
<>
{renderContent()}
{renderFooter()}
</>
</Modal>
);
};
@@ -1,4 +1,10 @@
import React, { useCallback, useContext, ReactNode } from "react";
import React, {
useCallback,
useContext,
useRef,
useState,
useEffect,
} from "react";
import { format } from "date-fns";
import {
useQuery,
@@ -15,6 +21,7 @@ import { IHostScript } from "interfaces/script";
import { IApiError, getErrorReason } from "interfaces/errors";
import Modal from "components/Modal";
import ModalFooter from "components/ModalFooter";
import Button from "components/buttons/Button";
import Spinner from "components/Spinner";
import Icon from "components/Icon";
@@ -68,6 +75,17 @@ const ScriptDetailsModal = ({
isHidden = false,
onClickRunDetails,
}: IScriptDetailsModalProps) => {
// For scrollable modal
const [isTopScrolling, setIsTopScrolling] = useState(false);
const topDivRef = useRef<HTMLDivElement>(null);
const checkScroll = () => {
if (topDivRef.current) {
const isScrolling =
topDivRef.current.scrollHeight > topDivRef.current.clientHeight;
setIsTopScrolling(isScrolling);
}
};
const { currentUser } = useContext(AppContext);
const { renderFlash } = useContext(NotificationContext);
@@ -87,6 +105,14 @@ const ScriptDetailsModal = ({
enabled: !selectedScriptContent && !!selectedScriptDetails?.script_id,
}
);
// For scrollable modal
useEffect(() => {
checkScroll();
window.addEventListener("resize", checkScroll);
return () => window.removeEventListener("resize", checkScroll);
}, [scriptContent]); // Re-run when data changes
const getScriptContent = async () => {
try {
const content = selectedScriptContent || scriptContent;
@@ -157,60 +183,64 @@ const ScriptDetailsModal = ({
]
);
const shouldShowFooter = () => {
return !isLoadingScriptContent && selectedScriptDetails !== undefined;
};
const shouldShowFooter =
!isLoadingScriptContent && selectedScriptDetails !== undefined;
const renderFooter = () => {
if (!shouldShowFooter) {
return <></>;
return null;
}
return (
<>
<div className={`secondary-actions ${baseClass}__script-actions`}>
<Button
className={`${baseClass}__action-button`}
variant="icon"
onClick={() => onClickDownload()}
>
<Icon name="download" />
</Button>
<Button
className={`${baseClass}__action-button`}
variant="icon"
onClick={onDelete}
>
<Icon name="trash" color="ui-fleet-black-75" />
</Button>
</div>
<div className={`primary-actions ${baseClass}__host-script-actions`}>
{showHostScriptActions && selectedScriptDetails && (
<div className={`${baseClass}__manage-automations-wrapper`}>
<ActionsDropdown
className={`${baseClass}__manage-automations-dropdown`}
onChange={(value) =>
onSelectMoreActions(
value,
<ModalFooter
isTopScrolling={isTopScrolling}
secondaryButtons={
<>
<Button
className={`${baseClass}__action-button`}
variant="icon"
onClick={() => onClickDownload()}
>
<Icon name="download" />
</Button>
<Button
className={`${baseClass}__action-button`}
variant="icon"
onClick={onDelete}
>
<Icon name="trash" color="ui-fleet-black-75" />
</Button>
</>
}
primaryButtons={
<>
{showHostScriptActions && selectedScriptDetails && (
<div className={`${baseClass}__manage-automations-wrapper`}>
<ActionsDropdown
className={`${baseClass}__manage-automations-dropdown`}
onChange={(value) =>
onSelectMoreActions(
value,
selectedScriptDetails as IHostScript
)
}
placeholder="More actions"
isSearchable={false}
options={generateActionDropdownOptions(
currentUser,
hostTeamId || null,
selectedScriptDetails as IHostScript
)
}
placeholder="More actions"
isSearchable={false}
options={generateActionDropdownOptions(
currentUser,
hostTeamId || null,
selectedScriptDetails as IHostScript
)}
menuPlacement="top"
/>
</div>
)}
<Button onClick={onCancel} variant="brand">
Done
</Button>
</div>
</>
)}
menuPlacement="top"
/>
</div>
)}
<Button onClick={onCancel} variant="brand">
Done
</Button>
</>
}
/>
);
};
@@ -224,7 +254,10 @@ const ScriptDetailsModal = ({
}
return (
<div className={`${baseClass}__script-content`}>
<div
className={`${baseClass}__script-content modal-scrollable-content`}
ref={topDivRef}
>
<span>Script content:</span>
<Textarea className={`${baseClass}__script-content-textarea`}>
{scriptContent}
@@ -249,10 +282,12 @@ const ScriptDetailsModal = ({
title={selectedScriptDetails?.name || "Script details"}
width="large"
onExit={onCancel}
actionsFooter={shouldShowFooter() ? renderFooter() : undefined}
isHidden={isHidden}
>
{renderContent()}
<>
{renderContent()}
{shouldShowFooter ? renderFooter() : undefined}
</>
</Modal>
);
};
@@ -7,7 +7,8 @@
&__script-content-textarea {
font-family: "SourceCodePro";
min-height: 150px;
max-height: 572px;
overflow: auto;
overflow: scroll;
}
}
@@ -1,10 +1,4 @@
import React, {
useCallback,
useContext,
useMemo,
useState,
useEffect,
} from "react";
import React, { useCallback, useContext, useMemo, useState } from "react";
import { useQuery } from "react-query";
import { Link } from "react-router";
@@ -1,7 +1,5 @@
import React from "react";
import { InjectedRouter } from "react-router";
import SandboxGate from "components/Sandbox/SandboxGate";
import SandboxMessage from "components/Sandbox/SandboxMessage";
import UsersTable from "./components/UsersTable";
const baseClass = "user-management";
@@ -17,17 +15,7 @@ const UserManagementPage = ({ router }: IUserManagementProps): JSX.Element => {
Create new users, customize user permissions, and remove users from
Fleet.
</p>
<SandboxGate
fallbackComponent={() => (
<SandboxMessage
message="User management is only available in self-managed Fleet"
utmSource="fleet-ui-users-page"
className={`${baseClass}__sandbox-message`}
/>
)}
>
<UsersTable router={router} />
</SandboxGate>
<UsersTable router={router} />
</div>
);
};
@@ -1,10 +1,9 @@
import React, { useState, useContext } from "react";
import { ITeam } from "interfaces/team";
import { UserRole } from "interfaces/user";
// ignore TS error for now until these are rewritten in ts.
// @ts-ignore
import Dropdown from "components/forms/fields/Dropdown";
import { SingleValue } from "react-select-5";
import DropdownWrapper from "components/forms/fields/DropdownWrapper";
import { CustomOptionType } from "components/forms/fields/DropdownWrapper/DropdownWrapper";
import { AppContext } from "context/app";
import { roleOptions } from "../../helpers/userManagementHelpers";
@@ -13,29 +12,19 @@ interface ISelectRoleFormProps {
currentTeam?: ITeam;
teams: ITeam[];
onFormChange: (teams: ITeam[]) => void;
label: string | string[];
isApiOnly?: boolean;
}
const generateSelectedTeamData = (
allTeams: ITeam[],
updatedTeam?: any
updatedTeam?: Partial<ITeam>
): ITeam[] => {
const filtered = allTeams.map(
(teamItem): ITeam => {
const teamRole =
teamItem.id === updatedTeam?.id ? updatedTeam.role : teamItem.role;
return {
description: teamItem.description,
id: teamItem.id,
host_count: teamItem.host_count,
user_count: teamItem.user_count,
name: teamItem.name,
role: teamRole,
};
}
return allTeams.map(
(teamItem): ITeam => ({
...teamItem,
role: teamItem.id === updatedTeam?.id ? updatedTeam.role! : teamItem.role,
})
);
return filtered;
};
const SelectRoleForm = ({
@@ -43,33 +32,33 @@ const SelectRoleForm = ({
currentTeam,
teams,
onFormChange,
label,
isApiOnly,
}: ISelectRoleFormProps): JSX.Element => {
const { isPremiumTier } = useContext(AppContext);
const [selectedRole, setSelectedRole] = useState(
defaultTeamRole.toLowerCase()
);
const [selectedRole, setSelectedRole] = useState<CustomOptionType>({
value: defaultTeamRole.toLowerCase(),
label: defaultTeamRole,
});
const updateSelectedRole = (newRoleValue: UserRole) => {
const updatedTeam = { ...currentTeam };
updatedTeam.role = newRoleValue;
onFormChange(generateSelectedTeamData(teams, updatedTeam));
setSelectedRole(newRoleValue);
const updateSelectedRole = (newRoleValue: SingleValue<CustomOptionType>) => {
if (newRoleValue) {
const updatedTeam = {
...currentTeam,
role: newRoleValue.value as UserRole,
};
onFormChange(generateSelectedTeamData(teams, updatedTeam));
setSelectedRole(newRoleValue);
}
};
return (
<Dropdown
label={label}
value={selectedRole}
<DropdownWrapper
name="Team role"
options={roleOptions({ isPremiumTier, isApiOnly })}
searchable={false}
onChange={(newRoleValue: UserRole) => updateSelectedRole(newRoleValue)}
testId={`${name}-checkbox`}
value={selectedRole}
onChange={updateSelectedRole}
isSearchable={false}
/>
);
};
@@ -3,8 +3,9 @@ import React, { useState } from "react";
import { ITeam } from "interfaces/team";
import { UserRole } from "interfaces/user";
import Checkbox from "components/forms/fields/Checkbox";
// @ts-ignore
import Dropdown from "components/forms/fields/Dropdown";
import { SingleValue } from "react-select-5";
import DropdownWrapper from "components/forms/fields/DropdownWrapper";
import { CustomOptionType } from "components/forms/fields/DropdownWrapper/DropdownWrapper";
import { roleOptions } from "../../helpers/userManagementHelpers";
interface ITeamCheckboxListItem extends ITeam {
@@ -62,7 +63,7 @@ const generateSelectedTeamData = (
const updateFormState = (
prevTeamItems: ITeamCheckboxListItem[],
teamId: number,
newValue: UserRole | boolean | undefined
newValue: SingleValue<CustomOptionType> | boolean | undefined
): ITeamCheckboxListItem[] => {
const prevItemIndex = prevTeamItems.findIndex((item) => item.id === teamId);
const prevItem = prevTeamItems[prevItemIndex];
@@ -70,7 +71,7 @@ const updateFormState = (
if (typeof newValue === "boolean") {
prevItem.isChecked = newValue;
} else {
prevItem.role = newValue;
prevItem.role = newValue?.value as UserRole;
}
return [...prevTeamItems];
@@ -87,7 +88,7 @@ const useSelectedTeamState = (
const updateSelectedTeams = (
teamId: number,
newValue: UserRole | boolean
newValue: CustomOptionType | boolean
) => {
setTeamsFormList((prevState) => {
const updatedTeamFormList = updateFormState(prevState, teamId, newValue);
@@ -127,15 +128,15 @@ const SelectedTeamsForm = ({
>
{name}
</Checkbox>
<Dropdown
<DropdownWrapper
name={name}
value={role}
className={`${baseClass}__role-dropdown`}
options={roleOptions({ isPremiumTier: true, isApiOnly })}
searchable={false}
onChange={(newValue: UserRole) =>
updateSelectedTeams(teamItem.id, newValue)
isSearchable={false}
onChange={(newValue: SingleValue<CustomOptionType>) =>
updateSelectedTeams(teamItem.id, newValue as CustomOptionType)
}
testId={`${name}-checkbox`}
/>
</div>
);
@@ -13,7 +13,7 @@
justify-content: space-between;
.form-field--dropdown {
width: auto;
width: 154px; // Matches dropdown
}
}
@@ -370,7 +370,6 @@ const UserForm = ({
</>
) : (
<SelectRoleForm
label="Role"
currentTeam={currentTeam || formData.teams[0]}
teams={formData.teams}
defaultTeamRole={defaultTeamRole || "observer"}
@@ -1,8 +1,8 @@
import { isEqual } from "lodash";
import { CustomOptionType } from "components/forms/fields/DropdownWrapper/DropdownWrapper";
import { IInvite } from "interfaces/invite";
import { IUser, IUserUpdateBody, IUpdateUserFormData } from "interfaces/user";
import { IRole } from "interfaces/role";
import { IFormData } from "../components/UserForm/UserForm";
type ICurrentUserData = Pick<
@@ -10,7 +10,7 @@ type ICurrentUserData = Pick<
"global_role" | "teams" | "name" | "email" | "sso_enabled"
>;
interface IRoleOptionsParams {
export interface IRoleOptionsParams {
isPremiumTier?: boolean;
isApiOnly?: boolean;
}
@@ -58,20 +58,17 @@ const generateUpdateData = (
export const roleOptions = ({
isPremiumTier,
isApiOnly,
}: IRoleOptionsParams): IRole[] => {
const roles: IRole[] = [
}: IRoleOptionsParams): CustomOptionType[] => {
const roles: CustomOptionType[] = [
{
disabled: false,
label: "Observer",
value: "observer",
},
{
disabled: false,
label: "Maintainer",
value: "maintainer",
},
{
disabled: false,
label: "Admin",
value: "admin",
},
@@ -79,14 +76,12 @@ export const roleOptions = ({
if (isPremiumTier) {
roles.splice(1, 0, {
disabled: false,
label: "Observer+",
value: "observer_plus",
});
if (isApiOnly) {
roles.splice(3, 0, {
disabled: false,
label: "GitOps",
value: "gitops",
});
+2 -2
View File
@@ -74,6 +74,7 @@ require (
github.com/mattermost/xml-roundtrip-validator v0.0.0-20201213122252-bcd7e1b9601e
github.com/mattn/go-sqlite3 v1.14.22
github.com/micromdm/micromdm v1.9.0
github.com/micromdm/nanolib v0.2.0
github.com/mitchellh/go-ps v1.0.0
github.com/mitchellh/gon v0.2.6-0.20231031204852-2d4f161ccecd
github.com/mna/redisc v1.3.2
@@ -118,7 +119,6 @@ require (
go.elastic.co/apm/module/apmsql/v2 v2.6.2
go.elastic.co/apm/v2 v2.6.2
go.etcd.io/bbolt v1.3.9
go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352
go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux v0.56.0
go.opentelemetry.io/otel v1.31.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0
@@ -279,7 +279,6 @@ require (
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.9 // indirect
github.com/mattn/go-tty v0.0.3 // indirect
github.com/micromdm/nanolib v0.2.0 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/mapstructure v1.4.3 // indirect
@@ -323,6 +322,7 @@ require (
github.com/yashtewari/glob-intersection v0.1.0 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
go.elastic.co/fastjson v1.1.0 // indirect
go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352 // indirect
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
-2
View File
@@ -846,8 +846,6 @@ github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/macadmins/osquery-extension v1.2.1 h1:p7tAAhfEjUjoMQJNb+X7Qc3FraVqGZqMhZ1BYJbrlaw=
github.com/macadmins/osquery-extension v1.2.1/go.mod h1:q0BnBuYocHBRB+m3AQwdQNETH5a2KzVT3S8TKMHo9Lk=
github.com/macadmins/osquery-extension v1.2.3 h1:PAAQVRBnpOwnzEUROiJbrjDf9RPwcAfJrNAkXUcjS3Y=
github.com/macadmins/osquery-extension v1.2.3/go.mod h1:cNd/9INYpAYJFjfmAEJKgiuHgDkGuFMPu6GVrn7oups=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
@@ -56,7 +56,7 @@ variable "database_name" {
variable "fleet_image" {
description = "the name of the container image to run"
default = "fleetdm/fleet:v4.59.1"
default = "fleetdm/fleet:v4.60.0"
}
variable "software_inventory" {
@@ -68,7 +68,7 @@ variable "redis_mem" {
}
variable "image" {
default = "fleetdm/fleet:v4.59.1"
default = "fleetdm/fleet:v4.60.0"
}
variable "software_installers_bucket_name" {
+2 -2
View File
@@ -2,8 +2,8 @@
# Manual edits may be lost in future updates.
provider "registry.terraform.io/hashicorp/aws" {
version = "4.59.1"
constraints = ">= 3.0.0, >= 4.8.0, >= 4.9.0, ~> 4.59.1"
version = "4.60.0"
constraints = ">= 3.0.0, >= 4.8.0, >= 4.9.0, ~> 4.60.0"
hashes = [
"h1:fuIdjl9f2JEH0TLoq5kc9NIPbJAAV7YBbZ8fvNp5XSg=",
"zh:0341a460210463a0bebd5c12ce13dc49bd8cae2399b215418c5efa607fed84e4",
+1 -1
View File
@@ -2,7 +2,7 @@ terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 4.59.1"
version = "~> 4.60.0"
}
}
backend "s3" {
@@ -2,7 +2,7 @@ terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 4.59.1"
version = "~> 4.60.0"
}
}
backend "s3" {
@@ -2,8 +2,8 @@
# Manual edits may be lost in future updates.
provider "registry.terraform.io/hashicorp/aws" {
version = "4.59.1"
constraints = ">= 3.63.0, ~> 4.59.1"
version = "4.60.0"
constraints = ">= 3.63.0, ~> 4.60.0"
hashes = [
"h1:fuIdjl9f2JEH0TLoq5kc9NIPbJAAV7YBbZ8fvNp5XSg=",
"zh:0341a460210463a0bebd5c12ce13dc49bd8cae2399b215418c5efa607fed84e4",
@@ -20,7 +20,7 @@ terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 4.59.1"
version = "~> 4.60.0"
}
}
backend "s3" {
@@ -2,8 +2,8 @@
# Manual edits may be lost in future updates.
provider "registry.terraform.io/hashicorp/aws" {
version = "4.59.1"
constraints = ">= 3.0.0, >= 4.8.0, >= 4.9.0, ~> 4.59.1"
version = "4.60.0"
constraints = ">= 3.0.0, >= 4.8.0, >= 4.9.0, ~> 4.60.0"
hashes = [
"h1:fuIdjl9f2JEH0TLoq5kc9NIPbJAAV7YBbZ8fvNp5XSg=",
"zh:0341a460210463a0bebd5c12ce13dc49bd8cae2399b215418c5efa607fed84e4",
@@ -15,7 +15,7 @@ terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 4.59.1"
version = "~> 4.60.0"
}
}
backend "s3" {
@@ -2,7 +2,7 @@ terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 4.59.1"
version = "~> 4.60.0"
}
}
backend "s3" {
-13
View File
@@ -1316,19 +1316,6 @@ func (man Manager) addConfigs() {
"calendar.periodicity", 0,
"How much time to wait between processing calendar integration.",
)
// Hide Microsoft/Windows MDM flags as we don't want it to be discoverable for users for now
betaMDMFlags := []string{
"mdm.windows_wstep_identity_cert",
"mdm.windows_wstep_identity_key",
"mdm.windows_wstep_identity_cert_bytes",
"mdm.windows_wstep_identity_key_bytes",
}
for _, mdmFlag := range betaMDMFlags {
if flag := man.command.PersistentFlags().Lookup(flagNameFromConfigKey(mdmFlag)); flag != nil {
flag.Hidden = true
}
}
}
func (man Manager) hideConfig(name string) {
+1 -1
View File
@@ -51,7 +51,7 @@ import (
"github.com/go-kit/log/level"
"github.com/google/uuid"
"github.com/groob/plist"
"go.mozilla.org/pkcs7"
"github.com/smallstep/pkcs7"
)
const (
+1 -1
View File
@@ -38,9 +38,9 @@ import (
"github.com/groob/plist"
"github.com/jmoiron/sqlx"
micromdm "github.com/micromdm/micromdm/mdm/mdm"
"github.com/smallstep/pkcs7"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.mozilla.org/pkcs7"
)
type profileAssignmentReq struct {
@@ -24,7 +24,7 @@ variable "fleet_config" {
vuln_processing_cpu = optional(number, 2048)
vuln_data_stream_mem = optional(number, 1024)
vuln_data_stream_cpu = optional(number, 512)
image = optional(string, "fleetdm/fleet:v4.59.1")
image = optional(string, "fleetdm/fleet:v4.60.0")
family = optional(string, "fleet-vuln-processing")
sidecars = optional(list(any), [])
extra_environment_variables = optional(map(string), {})
@@ -82,7 +82,7 @@ variable "fleet_config" {
vuln_processing_cpu = 2048
vuln_data_stream_mem = 1024
vuln_data_stream_cpu = 512
image = "fleetdm/fleet:v4.59.1"
image = "fleetdm/fleet:v4.60.0"
family = "fleet-vuln-processing"
sidecars = []
extra_environment_variables = {}
@@ -16,7 +16,7 @@ variable "fleet_config" {
mem = optional(number, 4096)
cpu = optional(number, 512)
pid_mode = optional(string, null)
image = optional(string, "fleetdm/fleet:v4.59.1")
image = optional(string, "fleetdm/fleet:v4.60.0")
family = optional(string, "fleet")
sidecars = optional(list(any), [])
depends_on = optional(list(any), [])
@@ -119,7 +119,7 @@ variable "fleet_config" {
mem = 512
cpu = 256
pid_mode = null
image = "fleetdm/fleet:v4.59.1"
image = "fleetdm/fleet:v4.60.0"
family = "fleet"
sidecars = []
depends_on = []
+2 -2
View File
@@ -77,7 +77,7 @@ variable "fleet_config" {
mem = optional(number, 4096)
cpu = optional(number, 512)
pid_mode = optional(string, null)
image = optional(string, "fleetdm/fleet:v4.59.1")
image = optional(string, "fleetdm/fleet:v4.60.0")
family = optional(string, "fleet")
sidecars = optional(list(any), [])
depends_on = optional(list(any), [])
@@ -205,7 +205,7 @@ variable "fleet_config" {
mem = 512
cpu = 256
pid_mode = null
image = "fleetdm/fleet:v4.59.1"
image = "fleetdm/fleet:v4.60.0"
family = "fleet"
sidecars = []
depends_on = []
+1 -1
View File
@@ -17,7 +17,7 @@ provider "aws" {
}
locals {
fleet_image = "fleetdm/fleet:v4.59.1"
fleet_image = "fleetdm/fleet:v4.60.0"
domain_name = "example.com"
}
+2 -2
View File
@@ -170,7 +170,7 @@ variable "fleet_config" {
mem = optional(number, 4096)
cpu = optional(number, 512)
pid_mode = optional(string, null)
image = optional(string, "fleetdm/fleet:v4.59.1")
image = optional(string, "fleetdm/fleet:v4.60.0")
family = optional(string, "fleet")
sidecars = optional(list(any), [])
depends_on = optional(list(any), [])
@@ -298,7 +298,7 @@ variable "fleet_config" {
mem = 512
cpu = 256
pid_mode = null
image = "fleetdm/fleet:v4.59.1"
image = "fleetdm/fleet:v4.60.0"
family = "fleet"
sidecars = []
depends_on = []
+2 -2
View File
@@ -63,8 +63,8 @@ module "fleet" {
fleet_config = {
# To avoid pull-rate limiting from dockerhub, consider using our quay.io mirror
# for the Fleet image. e.g. "quay.io/fleetdm/fleet:v4.59.1"
image = "fleetdm/fleet:v4.59.1" # override default to deploy the image you desire
# for the Fleet image. e.g. "quay.io/fleetdm/fleet:v4.60.0"
image = "fleetdm/fleet:v4.60.0" # override default to deploy the image you desire
# See https://fleetdm.com/docs/deploy/reference-architectures#aws for appropriate scaling
# memory and cpu.
autoscaling = {
+2 -2
View File
@@ -218,7 +218,7 @@ variable "fleet_config" {
mem = optional(number, 4096)
cpu = optional(number, 512)
pid_mode = optional(string, null)
image = optional(string, "fleetdm/fleet:v4.59.1")
image = optional(string, "fleetdm/fleet:v4.60.0")
family = optional(string, "fleet")
sidecars = optional(list(any), [])
depends_on = optional(list(any), [])
@@ -346,7 +346,7 @@ variable "fleet_config" {
mem = 512
cpu = 256
pid_mode = null
image = "fleetdm/fleet:v4.59.1"
image = "fleetdm/fleet:v4.60.0"
family = "fleet"
sidecars = []
depends_on = []
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "fleetctl",
"version": "v4.59.1",
"version": "v4.60.0",
"description": "Installer for the fleetctl CLI tool",
"bin": {
"fleetctl": "./run.js"