mirror of
https://github.com/fleetdm/fleet
synced 2026-05-24 09:28:54 +00:00
Add ability to see Google Chrome profiles on the Hosts page (#5839)
This commit is contained in:
parent
2db2c16511
commit
bbc1891420
11 changed files with 216 additions and 90 deletions
3
changes/issue-3320-device-mapping
Normal file
3
changes/issue-3320-device-mapping
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
- Added google chrome profile information to hosts page
|
||||
- Increased MySQL `group_concat_max_len` setting from default 1024 to 4194304 (which corresponds to
|
||||
`max_allowed_packet` size)
|
||||
|
|
@ -71,6 +71,22 @@ describe("Hosts flow", () => {
|
|||
});
|
||||
}
|
||||
});
|
||||
it(`hides and shows "Used by" column`, () => {
|
||||
cy.visit("/hosts/manage");
|
||||
cy.getAttached("thead").within(() =>
|
||||
cy.findByText(/used by/i).should("not.exist")
|
||||
);
|
||||
cy.getAttached(".table-container").within(() => {
|
||||
cy.contains("button", /edit columns/i).click();
|
||||
});
|
||||
cy.getAttached(".edit-columns-modal").within(() => {
|
||||
cy.findByLabelText(/used by/i).check({ force: true });
|
||||
cy.contains("button", /save/i).click();
|
||||
});
|
||||
cy.getAttached("thead").within(() =>
|
||||
cy.findByText(/used by/i).should("exist")
|
||||
);
|
||||
});
|
||||
});
|
||||
describe("Manage policies page", () => {
|
||||
beforeEach(() => {
|
||||
|
|
|
|||
|
|
@ -2,8 +2,10 @@
|
|||
// disable this rule as it was throwing an error in Header and Cell component
|
||||
// definitions for the selection row for some reason when we dont really need it.
|
||||
import React from "react";
|
||||
import { Column } from "react-table";
|
||||
import ReactTooltip from "react-tooltip";
|
||||
|
||||
import { IHost } from "interfaces/host";
|
||||
import { IDeviceUser, IHost } from "interfaces/host";
|
||||
import Checkbox from "components/forms/fields/Checkbox";
|
||||
import HeaderCell from "components/TableContainer/DataTable/HeaderCell/HeaderCell";
|
||||
import IssueCell from "components/TableContainer/DataTable/IssueCell/IssueCell";
|
||||
|
|
@ -18,6 +20,7 @@ import {
|
|||
hostTeamName,
|
||||
} from "utilities/helpers";
|
||||
import { IConfig } from "interfaces/config";
|
||||
import { IDataColumn } from "interfaces/datatable_config";
|
||||
import { ITeamSummary } from "interfaces/team";
|
||||
import { IUser } from "interfaces/user";
|
||||
import PATHS from "router/paths";
|
||||
|
|
@ -51,16 +54,40 @@ interface ICellProps {
|
|||
};
|
||||
}
|
||||
|
||||
interface IHostDataColumn {
|
||||
Header: ((props: IHeaderProps) => JSX.Element) | string;
|
||||
Cell: (props: ICellProps) => JSX.Element;
|
||||
id?: string;
|
||||
title?: string;
|
||||
accessor?: string;
|
||||
disableHidden?: boolean;
|
||||
disableSortBy?: boolean;
|
||||
interface IDeviceUserCellProps {
|
||||
cell: {
|
||||
value: IDeviceUser[];
|
||||
};
|
||||
row: {
|
||||
original: IHost;
|
||||
};
|
||||
}
|
||||
|
||||
const condenseDeviceUsers = (users: IDeviceUser[]): string[] => {
|
||||
if (!users?.length) {
|
||||
return [];
|
||||
}
|
||||
const condensed =
|
||||
users
|
||||
.slice(-3)
|
||||
.map((u) => u.email)
|
||||
.reverse() || [];
|
||||
return users.length > 3
|
||||
? condensed.concat(`+${users.length - 3} more`) // TODO: confirm limit
|
||||
: condensed;
|
||||
};
|
||||
|
||||
const tooltipTextWithLineBreaks = (lines: string[]) => {
|
||||
return lines.map((line) => {
|
||||
return (
|
||||
<span key={Math.random().toString().slice(2)}>
|
||||
{line}
|
||||
<br />
|
||||
</span>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const lastSeenTime = (status: string, seenTime: string): string => {
|
||||
if (status !== "online") {
|
||||
return `Last Seen: ${humanHostLastSeen(seenTime)} UTC`;
|
||||
|
|
@ -68,7 +95,7 @@ const lastSeenTime = (status: string, seenTime: string): string => {
|
|||
return "Online";
|
||||
};
|
||||
|
||||
const allHostTableHeaders: IHostDataColumn[] = [
|
||||
const allHostTableHeaders: IDataColumn[] = [
|
||||
// We are using React Table useRowSelect functionality for the selection header.
|
||||
// More information on its API can be found here
|
||||
// https://react-table.tanstack.com/docs/api/useRowSelect
|
||||
|
|
@ -95,14 +122,14 @@ const allHostTableHeaders: IHostDataColumn[] = [
|
|||
},
|
||||
{
|
||||
title: "Hostname",
|
||||
Header: (cellProps) => (
|
||||
Header: (cellProps: IHeaderProps) => (
|
||||
<HeaderCell
|
||||
value={cellProps.column.title}
|
||||
isSortedDesc={cellProps.column.isSortedDesc}
|
||||
/>
|
||||
),
|
||||
accessor: "hostname",
|
||||
Cell: (cellProps) => (
|
||||
Cell: (cellProps: ICellProps) => (
|
||||
<LinkCell
|
||||
value={cellProps.cell.value}
|
||||
path={PATHS.HOST_DETAILS(cellProps.row.original)}
|
||||
|
|
@ -116,14 +143,14 @@ const allHostTableHeaders: IHostDataColumn[] = [
|
|||
},
|
||||
{
|
||||
title: "Team",
|
||||
Header: (cellProps) => (
|
||||
Header: (cellProps: IHeaderProps) => (
|
||||
<HeaderCell
|
||||
value={cellProps.column.title}
|
||||
isSortedDesc={cellProps.column.isSortedDesc}
|
||||
/>
|
||||
),
|
||||
accessor: "team_name",
|
||||
Cell: (cellProps) => (
|
||||
Cell: (cellProps: ICellProps) => (
|
||||
<TextCell value={cellProps.cell.value} formatter={hostTeamName} />
|
||||
),
|
||||
},
|
||||
|
|
@ -132,14 +159,16 @@ const allHostTableHeaders: IHostDataColumn[] = [
|
|||
Header: "Status",
|
||||
disableSortBy: true,
|
||||
accessor: "status",
|
||||
Cell: (cellProps) => <StatusCell value={cellProps.cell.value} />,
|
||||
Cell: (cellProps: ICellProps) => (
|
||||
<StatusCell value={cellProps.cell.value} />
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Issues",
|
||||
Header: () => <img alt="host issues" src={IssueIcon} />,
|
||||
disableSortBy: true,
|
||||
accessor: "issues",
|
||||
Cell: (cellProps) => (
|
||||
Cell: (cellProps: ICellProps) => (
|
||||
<IssueCell
|
||||
issues={cellProps.row.original.issues}
|
||||
rowId={cellProps.row.original.id}
|
||||
|
|
@ -148,47 +177,83 @@ const allHostTableHeaders: IHostDataColumn[] = [
|
|||
},
|
||||
{
|
||||
title: "OS",
|
||||
Header: (cellProps) => (
|
||||
Header: (cellProps: IHeaderProps) => (
|
||||
<HeaderCell
|
||||
value={cellProps.column.title}
|
||||
isSortedDesc={cellProps.column.isSortedDesc}
|
||||
/>
|
||||
),
|
||||
accessor: "os_version",
|
||||
Cell: (cellProps) => <TextCell value={cellProps.cell.value} />,
|
||||
Cell: (cellProps: ICellProps) => <TextCell value={cellProps.cell.value} />,
|
||||
},
|
||||
{
|
||||
title: "Osquery",
|
||||
Header: (cellProps) => (
|
||||
Header: (cellProps: IHeaderProps) => (
|
||||
<HeaderCell
|
||||
value={cellProps.column.title}
|
||||
isSortedDesc={cellProps.column.isSortedDesc}
|
||||
/>
|
||||
),
|
||||
accessor: "osquery_version",
|
||||
Cell: (cellProps) => <TextCell value={cellProps.cell.value} />,
|
||||
Cell: (cellProps: ICellProps) => <TextCell value={cellProps.cell.value} />,
|
||||
},
|
||||
{
|
||||
title: "Used by",
|
||||
Header: "Used by",
|
||||
disableSortBy: true,
|
||||
accessor: "device_mapping",
|
||||
Cell: (cellProps: IDeviceUserCellProps): JSX.Element => {
|
||||
const numUsers = cellProps.cell.value?.length || 0;
|
||||
const users = condenseDeviceUsers(cellProps.cell.value || []);
|
||||
if (users.length) {
|
||||
const tooltipText = tooltipTextWithLineBreaks(users);
|
||||
return (
|
||||
<>
|
||||
<span
|
||||
className={`text-cell ${users.length > 1 ? "text-muted" : ""}`}
|
||||
data-tip
|
||||
data-for={`device_mapping__${cellProps.row.original.id}`}
|
||||
data-tip-disable={users.length <= 1}
|
||||
>
|
||||
{numUsers === 1 ? users[0] : `${numUsers} users`}
|
||||
</span>
|
||||
<ReactTooltip
|
||||
place="top"
|
||||
type="dark"
|
||||
effect="solid"
|
||||
backgroundColor="#3e4771"
|
||||
id={`device_mapping__${cellProps.row.original.id}`}
|
||||
data-html
|
||||
>
|
||||
<span className={`tooltip__tooltip-text`}>{tooltipText}</span>
|
||||
</ReactTooltip>
|
||||
</>
|
||||
);
|
||||
}
|
||||
return <span className="text-muted">---</span>;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "IP address",
|
||||
Header: (cellProps) => (
|
||||
Header: (cellProps: IHeaderProps) => (
|
||||
<HeaderCell
|
||||
value={cellProps.column.title}
|
||||
isSortedDesc={cellProps.column.isSortedDesc}
|
||||
/>
|
||||
),
|
||||
accessor: "primary_ip",
|
||||
Cell: (cellProps) => <TextCell value={cellProps.cell.value} />,
|
||||
Cell: (cellProps: ICellProps) => <TextCell value={cellProps.cell.value} />,
|
||||
},
|
||||
{
|
||||
title: "Last fetched",
|
||||
Header: (cellProps) => (
|
||||
Header: (cellProps: IHeaderProps) => (
|
||||
<HeaderCell
|
||||
value={cellProps.column.title}
|
||||
isSortedDesc={cellProps.column.isSortedDesc}
|
||||
/>
|
||||
),
|
||||
accessor: "detail_updated_at",
|
||||
Cell: (cellProps) => (
|
||||
Cell: (cellProps: ICellProps) => (
|
||||
<TextCell
|
||||
value={cellProps.cell.value}
|
||||
formatter={humanHostDetailUpdated}
|
||||
|
|
@ -197,38 +262,38 @@ const allHostTableHeaders: IHostDataColumn[] = [
|
|||
},
|
||||
{
|
||||
title: "Last seen",
|
||||
Header: (cellProps) => (
|
||||
Header: (cellProps: IHeaderProps) => (
|
||||
<HeaderCell
|
||||
value={cellProps.column.title}
|
||||
isSortedDesc={cellProps.column.isSortedDesc}
|
||||
/>
|
||||
),
|
||||
accessor: "seen_time",
|
||||
Cell: (cellProps) => (
|
||||
Cell: (cellProps: ICellProps) => (
|
||||
<TextCell value={cellProps.cell.value} formatter={humanHostLastSeen} />
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "UUID",
|
||||
Header: (cellProps) => (
|
||||
Header: (cellProps: IHeaderProps) => (
|
||||
<HeaderCell
|
||||
value={cellProps.column.title}
|
||||
isSortedDesc={cellProps.column.isSortedDesc}
|
||||
/>
|
||||
),
|
||||
accessor: "uuid",
|
||||
Cell: (cellProps) => <TextCell value={cellProps.cell.value} />,
|
||||
Cell: (cellProps: ICellProps) => <TextCell value={cellProps.cell.value} />,
|
||||
},
|
||||
{
|
||||
title: "Uptime",
|
||||
Header: (cellProps) => (
|
||||
Header: (cellProps: IHeaderProps) => (
|
||||
<HeaderCell
|
||||
value={cellProps.column.title}
|
||||
isSortedDesc={cellProps.column.isSortedDesc}
|
||||
/>
|
||||
),
|
||||
accessor: "uptime",
|
||||
Cell: (cellProps) => (
|
||||
Cell: (cellProps: ICellProps) => (
|
||||
<TextCell value={cellProps.cell.value} formatter={humanHostUptime} />
|
||||
),
|
||||
},
|
||||
|
|
@ -237,57 +302,58 @@ const allHostTableHeaders: IHostDataColumn[] = [
|
|||
Header: "CPU",
|
||||
disableSortBy: true,
|
||||
accessor: "cpu_type",
|
||||
Cell: (cellProps) => <TextCell value={cellProps.cell.value} />,
|
||||
Cell: (cellProps: ICellProps) => <TextCell value={cellProps.cell.value} />,
|
||||
},
|
||||
{
|
||||
title: "RAM",
|
||||
Header: (cellProps) => (
|
||||
Header: (cellProps: IHeaderProps) => (
|
||||
<HeaderCell
|
||||
value={cellProps.column.title}
|
||||
isSortedDesc={cellProps.column.isSortedDesc}
|
||||
/>
|
||||
),
|
||||
accessor: "memory",
|
||||
Cell: (cellProps) => (
|
||||
Cell: (cellProps: ICellProps) => (
|
||||
<TextCell value={cellProps.cell.value} formatter={humanHostMemory} />
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "MAC address",
|
||||
Header: (cellProps) => (
|
||||
Header: (cellProps: IHeaderProps) => (
|
||||
<HeaderCell
|
||||
value={cellProps.column.title}
|
||||
isSortedDesc={cellProps.column.isSortedDesc}
|
||||
/>
|
||||
),
|
||||
accessor: "primary_mac",
|
||||
Cell: (cellProps) => <TextCell value={cellProps.cell.value} />,
|
||||
Cell: (cellProps: ICellProps) => <TextCell value={cellProps.cell.value} />,
|
||||
},
|
||||
{
|
||||
title: "Serial number",
|
||||
Header: (cellProps) => (
|
||||
Header: (cellProps: IHeaderProps) => (
|
||||
<HeaderCell
|
||||
value={cellProps.column.title}
|
||||
isSortedDesc={cellProps.column.isSortedDesc}
|
||||
/>
|
||||
),
|
||||
accessor: "hardware_serial",
|
||||
Cell: (cellProps) => <TextCell value={cellProps.cell.value} />,
|
||||
Cell: (cellProps: ICellProps) => <TextCell value={cellProps.cell.value} />,
|
||||
},
|
||||
{
|
||||
title: "Hardware model",
|
||||
Header: (cellProps) => (
|
||||
Header: (cellProps: IHeaderProps) => (
|
||||
<HeaderCell
|
||||
value={cellProps.column.title}
|
||||
isSortedDesc={cellProps.column.isSortedDesc}
|
||||
/>
|
||||
),
|
||||
accessor: "hardware_model",
|
||||
Cell: (cellProps) => <TextCell value={cellProps.cell.value} />,
|
||||
Cell: (cellProps: ICellProps) => <TextCell value={cellProps.cell.value} />,
|
||||
},
|
||||
];
|
||||
|
||||
const defaultHiddenColumns = [
|
||||
"device_mapping",
|
||||
"primary_mac",
|
||||
"cpu_type",
|
||||
"memory",
|
||||
|
|
@ -306,9 +372,9 @@ const generateAvailableTableHeaders = (
|
|||
config: IConfig,
|
||||
currentUser: IUser,
|
||||
currentTeam: ITeamSummary | undefined
|
||||
): IHostDataColumn[] => {
|
||||
): IDataColumn[] => {
|
||||
return allHostTableHeaders.reduce(
|
||||
(columns: IHostDataColumn[], currentColumn: IHostDataColumn) => {
|
||||
(columns: Column[], currentColumn: Column) => {
|
||||
// skip over column headers that are not shown in free observer tier
|
||||
if (
|
||||
permissionUtils.isFreeTier(config) &&
|
||||
|
|
@ -356,7 +422,7 @@ const generateVisibleTableColumns = (
|
|||
config: IConfig,
|
||||
currentUser: IUser,
|
||||
currentTeam: ITeamSummary | undefined
|
||||
): IHostDataColumn[] => {
|
||||
): IDataColumn[] => {
|
||||
// remove columns set as hidden by the user.
|
||||
return generateAvailableTableHeaders(config, currentUser, currentTeam).filter(
|
||||
(column) => {
|
||||
|
|
|
|||
|
|
@ -478,7 +478,7 @@ const ManageHostsPage = ({
|
|||
if (options.sortBy) {
|
||||
delete options.sortBy;
|
||||
}
|
||||
retrieveHostCount(options);
|
||||
retrieveHostCount(omit(options, "device_mapping"));
|
||||
};
|
||||
|
||||
let teamSync = false;
|
||||
|
|
@ -519,6 +519,7 @@ const ManageHostsPage = ({
|
|||
softwareId,
|
||||
page: tableQueryData ? tableQueryData.pageIndex : 0,
|
||||
perPage: tableQueryData ? tableQueryData.pageSize : 100,
|
||||
device_mapping: true,
|
||||
};
|
||||
|
||||
if (isEqual(options, currentQueryOptions)) {
|
||||
|
|
@ -526,7 +527,7 @@ const ManageHostsPage = ({
|
|||
}
|
||||
if (teamSync) {
|
||||
retrieveHosts(options);
|
||||
retrieveHostCount(options);
|
||||
retrieveHostCount(omit(options, "device_mapping"));
|
||||
setCurrentQueryOptions(options);
|
||||
}
|
||||
}, [availableTeams, currentTeam, location, labels]);
|
||||
|
|
|
|||
|
|
@ -127,6 +127,14 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
.device_mapping__cell {
|
||||
.text-cell {
|
||||
display: inline;
|
||||
}
|
||||
.text-muted {
|
||||
color: $ui-fleet-black-50;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -389,8 +389,8 @@
|
|||
text-align: center;
|
||||
}
|
||||
|
||||
&__device-mapping {
|
||||
.device-user-tooltip {
|
||||
&__device_mapping {
|
||||
.device_mapping--tooltip {
|
||||
flex-direction: column;
|
||||
justify-content: start;
|
||||
text-align: left;
|
||||
|
|
|
|||
|
|
@ -404,8 +404,8 @@
|
|||
margin-right: $pad-small;
|
||||
}
|
||||
|
||||
&__device-mapping {
|
||||
.device-user-tooltip {
|
||||
&__device_mapping {
|
||||
.device_mapping--tooltip {
|
||||
flex-direction: column;
|
||||
justify-content: start;
|
||||
text-align: left;
|
||||
|
|
|
|||
|
|
@ -81,48 +81,44 @@ const About = ({
|
|||
};
|
||||
|
||||
const renderDeviceUser = () => {
|
||||
const numUsers = deviceMapping?.length;
|
||||
if (numUsers) {
|
||||
return (
|
||||
<div className="info-grid__block">
|
||||
<span className="info-grid__header">Used by</span>
|
||||
<span className="info-grid__data">
|
||||
{numUsers === 1 && deviceMapping ? (
|
||||
deviceMapping[0].email || "---"
|
||||
) : (
|
||||
<span className={`${baseClass}__device-mapping`}>
|
||||
<span
|
||||
className="device-user"
|
||||
data-tip
|
||||
data-for="device-user-tooltip"
|
||||
>
|
||||
{`${numUsers} users`}
|
||||
</span>
|
||||
<ReactTooltip
|
||||
place="top"
|
||||
type="dark"
|
||||
effect="solid"
|
||||
id="device-user-tooltip"
|
||||
backgroundColor="#3e4771"
|
||||
>
|
||||
<div
|
||||
className={`${baseClass}__tooltip-text device-user-tooltip`}
|
||||
>
|
||||
{deviceMapping &&
|
||||
deviceMapping.map((user: any, i: number, arr: any) => (
|
||||
<span key={user.email}>{`${user.email}${
|
||||
i < arr.length - 1 ? ", " : ""
|
||||
}`}</span>
|
||||
))}
|
||||
</div>
|
||||
</ReactTooltip>
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
if (!deviceMapping) {
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
|
||||
const numUsers = deviceMapping.length;
|
||||
const tooltipText = deviceMapping.map((d) => (
|
||||
<span key={Math.random().toString().slice(2)}>
|
||||
{d.email}
|
||||
<br />
|
||||
</span>
|
||||
));
|
||||
|
||||
return (
|
||||
<div className="info-grid__block">
|
||||
<span className="info-grid__header">Used by</span>
|
||||
<span className="info-grid__data">
|
||||
{numUsers > 1 ? (
|
||||
<>
|
||||
<span data-tip data-for={`device_mapping`}>
|
||||
{`${numUsers} users`}
|
||||
</span>
|
||||
<ReactTooltip
|
||||
place="top"
|
||||
type="dark"
|
||||
effect="solid"
|
||||
backgroundColor="#3e4771"
|
||||
id={`device_mapping`}
|
||||
data-html
|
||||
>
|
||||
<span className={`tooltip__tooltip-text`}>{tooltipText}</span>
|
||||
</ReactTooltip>
|
||||
</>
|
||||
) : (
|
||||
deviceMapping[0].email || "---"
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const renderGeolocation = () => {
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ export interface ILoadHostsOptions {
|
|||
policyId?: number;
|
||||
policyResponse?: string;
|
||||
softwareId?: number;
|
||||
device_mapping?: boolean;
|
||||
columns?: string;
|
||||
}
|
||||
|
||||
|
|
@ -127,6 +128,7 @@ export default {
|
|||
const policyId = options?.policyId || null;
|
||||
const policyResponse = options?.policyResponse || null;
|
||||
const softwareId = options?.softwareId || null;
|
||||
const device_mapping = options?.device_mapping || null;
|
||||
const columns = options?.columns || null;
|
||||
|
||||
// TODO: add this query param logic to client class
|
||||
|
|
@ -183,6 +185,10 @@ export default {
|
|||
path += `&software_id=${softwareId}`;
|
||||
}
|
||||
|
||||
if (device_mapping) {
|
||||
path += "&device_mapping=true";
|
||||
}
|
||||
|
||||
if (columns) {
|
||||
path += `&columns=${columns}`;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -834,7 +834,7 @@ func registerTLS(conf config.MysqlConfig) error {
|
|||
func generateMysqlConnectionString(conf config.MysqlConfig) string {
|
||||
tz := url.QueryEscape("'-00:00'")
|
||||
dsn := fmt.Sprintf(
|
||||
"%s:%s@%s(%s)/%s?charset=utf8mb4&parseTime=true&loc=UTC&time_zone=%s&clientFoundRows=true&allowNativePasswords=true",
|
||||
"%s:%s@%s(%s)/%s?charset=utf8mb4&parseTime=true&loc=UTC&time_zone=%s&clientFoundRows=true&allowNativePasswords=true&group_concat_max_len=4194304",
|
||||
conf.Username,
|
||||
conf.Password,
|
||||
conf.Protocol,
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/fleetdm/fleet/v4/server"
|
||||
"github.com/fleetdm/fleet/v4/server/datastore/mysql"
|
||||
"github.com/fleetdm/fleet/v4/server/fleet"
|
||||
"github.com/fleetdm/fleet/v4/server/ptr"
|
||||
|
|
@ -2189,6 +2190,35 @@ func (s *integrationTestSuite) TestHostDeviceMapping() {
|
|||
require.Len(t, listHosts.Hosts, 0)
|
||||
}
|
||||
|
||||
func (s *integrationTestSuite) TestListHostsDeviceMappingSize() {
|
||||
t := s.T()
|
||||
ctx := context.Background()
|
||||
hosts := s.createHosts(t)
|
||||
|
||||
testSize := 50
|
||||
var mappings []*fleet.HostDeviceMapping
|
||||
for i := 0; i < testSize; i++ {
|
||||
testEmail, _ := server.GenerateRandomText(14)
|
||||
mappings = append(mappings, &fleet.HostDeviceMapping{HostID: hosts[0].ID, Email: testEmail, Source: "google_chrome_profiles"})
|
||||
}
|
||||
|
||||
s.ds.ReplaceHostDeviceMapping(ctx, hosts[0].ID, mappings)
|
||||
|
||||
var listHosts listHostsResponse
|
||||
s.DoJSON("GET", "/api/latest/fleet/hosts?device_mapping=true", nil, http.StatusOK, &listHosts)
|
||||
|
||||
hostsByID := make(map[uint]HostResponse)
|
||||
for _, h := range listHosts.Hosts {
|
||||
hostsByID[h.ID] = h
|
||||
}
|
||||
require.NotNil(t, *hostsByID[hosts[0].ID].DeviceMapping)
|
||||
|
||||
var dm []*fleet.HostDeviceMapping
|
||||
err := json.Unmarshal(*hostsByID[hosts[0].ID].DeviceMapping, &dm)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, dm, testSize)
|
||||
}
|
||||
|
||||
func (s *integrationTestSuite) TestGetMacadminsData() {
|
||||
t := s.T()
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue