(
+ Header: (cellProps: IHeaderProps) => (
),
accessor: "seen_time",
- Cell: (cellProps) => (
+ Cell: (cellProps: ICellProps) => (
),
},
{
title: "UUID",
- Header: (cellProps) => (
+ Header: (cellProps: IHeaderProps) => (
),
accessor: "uuid",
- Cell: (cellProps) => ,
+ Cell: (cellProps: ICellProps) => ,
},
{
title: "Uptime",
- Header: (cellProps) => (
+ Header: (cellProps: IHeaderProps) => (
),
accessor: "uptime",
- Cell: (cellProps) => (
+ Cell: (cellProps: ICellProps) => (
),
},
@@ -237,57 +302,58 @@ const allHostTableHeaders: IHostDataColumn[] = [
Header: "CPU",
disableSortBy: true,
accessor: "cpu_type",
- Cell: (cellProps) => ,
+ Cell: (cellProps: ICellProps) => ,
},
{
title: "RAM",
- Header: (cellProps) => (
+ Header: (cellProps: IHeaderProps) => (
),
accessor: "memory",
- Cell: (cellProps) => (
+ Cell: (cellProps: ICellProps) => (
),
},
{
title: "MAC address",
- Header: (cellProps) => (
+ Header: (cellProps: IHeaderProps) => (
),
accessor: "primary_mac",
- Cell: (cellProps) => ,
+ Cell: (cellProps: ICellProps) => ,
},
{
title: "Serial number",
- Header: (cellProps) => (
+ Header: (cellProps: IHeaderProps) => (
),
accessor: "hardware_serial",
- Cell: (cellProps) => ,
+ Cell: (cellProps: ICellProps) => ,
},
{
title: "Hardware model",
- Header: (cellProps) => (
+ Header: (cellProps: IHeaderProps) => (
),
accessor: "hardware_model",
- Cell: (cellProps) => ,
+ Cell: (cellProps: ICellProps) => ,
},
];
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) => {
diff --git a/frontend/pages/hosts/ManageHostsPage/ManageHostsPage.tsx b/frontend/pages/hosts/ManageHostsPage/ManageHostsPage.tsx
index dd3a67168a..f5d95773f5 100644
--- a/frontend/pages/hosts/ManageHostsPage/ManageHostsPage.tsx
+++ b/frontend/pages/hosts/ManageHostsPage/ManageHostsPage.tsx
@@ -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]);
diff --git a/frontend/pages/hosts/ManageHostsPage/_styles.scss b/frontend/pages/hosts/ManageHostsPage/_styles.scss
index cdb820cf18..8aea4f0fd7 100644
--- a/frontend/pages/hosts/ManageHostsPage/_styles.scss
+++ b/frontend/pages/hosts/ManageHostsPage/_styles.scss
@@ -127,6 +127,14 @@
}
}
}
+ .device_mapping__cell {
+ .text-cell {
+ display: inline;
+ }
+ .text-muted {
+ color: $ui-fleet-black-50;
+ }
+ }
}
}
}
diff --git a/frontend/pages/hosts/details/DeviceUserPage/_styles.scss b/frontend/pages/hosts/details/DeviceUserPage/_styles.scss
index faccc83c96..bfb3de9bf8 100644
--- a/frontend/pages/hosts/details/DeviceUserPage/_styles.scss
+++ b/frontend/pages/hosts/details/DeviceUserPage/_styles.scss
@@ -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;
diff --git a/frontend/pages/hosts/details/HostDetailsPage/_styles.scss b/frontend/pages/hosts/details/HostDetailsPage/_styles.scss
index a6e053aed1..c6ec52c953 100644
--- a/frontend/pages/hosts/details/HostDetailsPage/_styles.scss
+++ b/frontend/pages/hosts/details/HostDetailsPage/_styles.scss
@@ -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;
diff --git a/frontend/pages/hosts/details/cards/About/About.tsx b/frontend/pages/hosts/details/cards/About/About.tsx
index 1313240897..f0fd621a44 100644
--- a/frontend/pages/hosts/details/cards/About/About.tsx
+++ b/frontend/pages/hosts/details/cards/About/About.tsx
@@ -81,48 +81,44 @@ const About = ({
};
const renderDeviceUser = () => {
- const numUsers = deviceMapping?.length;
- if (numUsers) {
- return (
-
-
Used by
-
- {numUsers === 1 && deviceMapping ? (
- deviceMapping[0].email || "---"
- ) : (
-
-
- {`${numUsers} users`}
-
-
-
- {deviceMapping &&
- deviceMapping.map((user: any, i: number, arr: any) => (
- {`${user.email}${
- i < arr.length - 1 ? ", " : ""
- }`}
- ))}
-
-
-
- )}
-
-
- );
+ if (!deviceMapping) {
+ return null;
}
- return null;
+
+ const numUsers = deviceMapping.length;
+ const tooltipText = deviceMapping.map((d) => (
+
+ {d.email}
+
+
+ ));
+
+ return (
+
+ Used by
+
+ {numUsers > 1 ? (
+ <>
+
+ {`${numUsers} users`}
+
+
+ {tooltipText}
+
+ >
+ ) : (
+ deviceMapping[0].email || "---"
+ )}
+
+
+ );
};
const renderGeolocation = () => {
diff --git a/frontend/services/entities/hosts.ts b/frontend/services/entities/hosts.ts
index c40f0024cf..22316d931a 100644
--- a/frontend/services/entities/hosts.ts
+++ b/frontend/services/entities/hosts.ts
@@ -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}`;
}
diff --git a/server/datastore/mysql/mysql.go b/server/datastore/mysql/mysql.go
index 098836aad0..98b4852db8 100644
--- a/server/datastore/mysql/mysql.go
+++ b/server/datastore/mysql/mysql.go
@@ -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,
diff --git a/server/service/integration_core_test.go b/server/service/integration_core_test.go
index d5ca300dd3..28c1c65dab 100644
--- a/server/service/integration_core_test.go
+++ b/server/service/integration_core_test.go
@@ -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()