diff --git a/changes/default-columns-agent b/changes/default-columns-agent
new file mode 100644
index 0000000000..b793ded528
--- /dev/null
+++ b/changes/default-columns-agent
@@ -0,0 +1,2 @@
+* Replaced the "Osquery" column with a richer "Agent" column on the Hosts page that shows Orbit version with a tooltip displaying osquery, Orbit, and Fleet Desktop versions.
+* Hidden "Issues" and "Private IP address" columns by default for new Fleet instances.
diff --git a/frontend/pages/hosts/ManageHostsPage/HostTableConfig.tsx b/frontend/pages/hosts/ManageHostsPage/HostTableConfig.tsx
index dfe84cc9b2..e8c624e2a6 100644
--- a/frontend/pages/hosts/ManageHostsPage/HostTableConfig.tsx
+++ b/frontend/pages/hosts/ManageHostsPage/HostTableConfig.tsx
@@ -582,22 +582,61 @@ const allHostTableHeaders = (teamId?: number): IHostTableColumnConfig[] => [
);
},
},
- // Osquery
+ // Agent
{
- title: "Osquery",
+ title: "Agent",
Header: (cellProps: IHostTableHeaderProps) => (
-
+
),
- accessor: "osquery_version",
- id: "osquery_version",
+ accessor: (row) => row.orbit_version || row.osquery_version,
+ id: "agent",
Cell: (cellProps: IHostTableStringCellProps) => {
- if (isMobilePlatform(cellProps.row.original.platform)) {
+ const {
+ platform,
+ orbit_version,
+ osquery_version,
+ fleet_desktop_version,
+ } = cellProps.row.original;
+
+ if (isMobilePlatform(platform)) {
return NotSupported;
}
- return ;
+
+ // Match the Host details Vitals card: treat a missing/empty orbit version
+ // (including the normalized "---" placeholder) as a vanilla osquery host.
+ const isChromeOrVanillaOsquery =
+ platform === "chrome" ||
+ !orbit_version ||
+ orbit_version === DEFAULT_EMPTY_CELL_VALUE;
+
+ if (isChromeOrVanillaOsquery) {
+ return ;
+ }
+
+ return (
+
+ osquery: {osquery_version}
+
+ Orbit: {orbit_version}
+ {fleet_desktop_version &&
+ fleet_desktop_version !== DEFAULT_EMPTY_CELL_VALUE && (
+ <>
+
+ Fleet Desktop: {fleet_desktop_version}
+ >
+ )}
+ >
+ }
+ >
+ {orbit_version}
+
+ }
+ />
+ );
},
},
// Last seen
@@ -672,6 +711,8 @@ const defaultHiddenColumns = [
"device_mapping",
"primary_mac",
"public_ip",
+ "primary_ip",
+ "issues",
"cpu_type",
// TODO: should those be mdm.?
"mdm.server_url",
diff --git a/server/datastore/mysql/hosts.go b/server/datastore/mysql/hosts.go
index 0d8026dc3a..e81be56d41 100644
--- a/server/datastore/mysql/hosts.go
+++ b/server/datastore/mysql/hosts.go
@@ -72,6 +72,7 @@ var hostAllowedOrderKeys = common_mysql.OrderKeyAllowlist{
"public_ip": "h.public_ip",
"last_enrolled_at": "h.last_enrolled_at",
"last_restarted_at": "h.last_restarted_at",
+ "agent": "COALESCE(NULLIF(hoi.version, ''), h.osquery_version)",
"orbit_version": "hoi.version",
"fleet_desktop_version": "hoi.desktop_version",
"issues": "host_issues.total_issues_count",
@@ -1130,7 +1131,9 @@ func (ds *Datastore) ListHosts(ctx context.Context, filter fleet.TeamFilter, opt
t.name AS team_name,
COALESCE(hu.software_updated_at, h.created_at) AS software_updated_at,
h.last_restarted_at,
- h.timezone
+ h.timezone,
+ hoi.version AS orbit_version,
+ hoi.desktop_version AS fleet_desktop_version
`
sql += hostMDMSelect
@@ -1442,6 +1445,7 @@ func (ds *Datastore) applyHostFilters(
LEFT JOIN host_updates hu ON (h.id = hu.host_id)
LEFT JOIN teams t ON (h.team_id = t.id)
LEFT JOIN host_disks hd ON hd.host_id = h.id
+ LEFT JOIN host_orbit_info hoi ON hoi.host_id = h.id
%s
%s
%s
diff --git a/server/datastore/mysql/hosts_test.go b/server/datastore/mysql/hosts_test.go
index d14f8282d7..84691bc5b8 100644
--- a/server/datastore/mysql/hosts_test.go
+++ b/server/datastore/mysql/hosts_test.go
@@ -10132,6 +10132,61 @@ func testHostOrder(t *testing.T, ds *Datastore) {
)
require.NoError(t, err)
chk(hosts, "0003", "0004", "0001")
+
+ // Test sorting by "agent". The agent order key is
+ // COALESCE(NULLIF(hoi.version, ''), h.osquery_version): orbit-enrolled hosts
+ // sort by their orbit version, while hosts with no orbit row OR an empty
+ // orbit version fall back to their osquery version (host_orbit_info.version
+ // is NOT NULL, so absent orbit info is stored as '' for some hosts).
+ // hostIDs[0] ("0001"): osquery 9.0.0, no orbit row -> effective 9.0.0
+ // hostIDs[1] ("0004"): osquery 9.9.9, orbit 1.0.0 -> effective 1.0.0 (orbit wins)
+ // hostIDs[2] ("0003"): osquery 5.0.0, orbit '' -> effective 5.0.0 (NULLIF fallback)
+ _, err = ds.writer(ctx).Exec(`UPDATE hosts SET osquery_version = '9.0.0' WHERE id = ?`, hostIDs[0])
+ require.NoError(t, err)
+ _, err = ds.writer(ctx).Exec(`UPDATE hosts SET osquery_version = '9.9.9' WHERE id = ?`, hostIDs[1])
+ require.NoError(t, err)
+ _, err = ds.writer(ctx).Exec(`UPDATE hosts SET osquery_version = '5.0.0' WHERE id = ?`, hostIDs[2])
+ require.NoError(t, err)
+ err = ds.SetOrUpdateHostOrbitInfo(
+ ctx, hostIDs[1], "1.0.0", sql.NullString{String: "1.0.0", Valid: true}, sql.NullBool{Bool: true, Valid: true},
+ )
+ require.NoError(t, err)
+ // Empty orbit version must still fall back to osquery_version (guards NULLIF);
+ // plain COALESCE would sort this host as '' and place it first.
+ err = ds.SetOrUpdateHostOrbitInfo(
+ ctx, hostIDs[2], "", sql.NullString{Valid: false}, sql.NullBool{Valid: false},
+ )
+ require.NoError(t, err)
+
+ hosts, err = ds.ListHosts(
+ ctx, fleet.TeamFilter{User: test.UserAdmin}, fleet.HostListOptions{
+ ListOptions: fleet.ListOptions{
+ OrderKey: "agent",
+ OrderDirection: fleet.OrderAscending,
+ },
+ },
+ )
+ require.NoError(t, err)
+ chk(hosts, "0004", "0003", "0001")
+
+ // ListHosts must also populate the orbit/desktop version fields the Agent
+ // column tooltip relies on (these were previously only loaded by ds.Host).
+ var orbitHost, vanillaHost *fleet.Host
+ for _, h := range hosts {
+ switch h.DisplayName() {
+ case "0004":
+ orbitHost = h
+ case "0001":
+ vanillaHost = h
+ }
+ }
+ require.NotNil(t, orbitHost)
+ require.NotNil(t, vanillaHost)
+ assert.Equal(t, new("1.0.0"), orbitHost.OrbitVersion)
+ assert.Equal(t, new("1.0.0"), orbitHost.DesktopVersion)
+ // Vanilla osquery host: no orbit info loaded.
+ assert.Nil(t, vanillaHost.OrbitVersion)
+ assert.Nil(t, vanillaHost.DesktopVersion)
}
func testHostIDsByOSID(t *testing.T, ds *Datastore) {