mirror of
https://github.com/empayre/fleet.git
synced 2024-11-06 00:45:19 +00:00
ChromeOS tables: Errors surfaced in Fleet UI (#12376)
This commit is contained in:
parent
414c2f42b3
commit
9c5d7faa58
1
changes/12383-improve-chrome-live-query-error-handling
Normal file
1
changes/12383-improve-chrome-live-query-error-handling
Normal file
@ -0,0 +1 @@
|
||||
- UI improvement: Surface chrome live query errors to Fleet UI (including errors for specific columns while maintaining successful data in results)
|
@ -12,6 +12,7 @@ interface requestArgs {
|
||||
body?: Record<string, any>;
|
||||
reenroll?: boolean;
|
||||
}
|
||||
|
||||
const request = async ({ path, body = {} }: requestArgs): Promise<any> => {
|
||||
const { fleet_url } = await chrome.storage.managed.get({
|
||||
fleet_url: FLEET_URL,
|
||||
@ -67,8 +68,8 @@ const authenticatedRequest = async ({
|
||||
};
|
||||
|
||||
const enroll = async () => {
|
||||
const os_version = await DATABASE.query("SELECT * FROM os_version");
|
||||
const system_info = await DATABASE.query("SELECT * FROM system_info");
|
||||
const os_version = (await DATABASE.query("SELECT * FROM os_version")).data;
|
||||
const system_info = (await DATABASE.query("SELECT * FROM system_info")).data;
|
||||
const host_details = {
|
||||
os_version: os_version[0],
|
||||
system_info: system_info[0],
|
||||
@ -118,7 +119,8 @@ const live_query = async () => {
|
||||
const query_discovery_sql = response.discovery[query_name];
|
||||
if (query_discovery_sql) {
|
||||
try {
|
||||
const discovery_result = await DATABASE.query(query_discovery_sql);
|
||||
const discovery_result = (await DATABASE.query(query_discovery_sql))
|
||||
.data;
|
||||
if (discovery_result.length == 0) {
|
||||
// Discovery queries that return no results mean skip running the query.
|
||||
continue;
|
||||
@ -129,6 +131,9 @@ const live_query = async () => {
|
||||
console.debug(
|
||||
`Discovery (${query_name} sql: "${query_discovery_sql}") failed: ${err}`
|
||||
);
|
||||
results[query_name] = null;
|
||||
statuses[query_name] = 1;
|
||||
messages[query_name] = err.toString();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@ -137,8 +142,12 @@ const live_query = async () => {
|
||||
const query_sql = response.queries[query_name];
|
||||
try {
|
||||
const query_result = await DATABASE.query(query_sql);
|
||||
results[query_name] = query_result;
|
||||
results[query_name] = query_result.data;
|
||||
statuses[query_name] = 0;
|
||||
if (query_result.warnings.length !== 0) {
|
||||
statuses[query_name] = 1; // Set to show warnings in errors table and campaign.ts returned host_counts to +1 failing instead of +1 successful
|
||||
messages[query_name] = query_result.warnings; // Warnings array is concatenated in Table.ts xfilter
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn(`Query (${query_name} sql: "${query_sql}") failed: ${err}`);
|
||||
results[query_name] = null;
|
||||
|
@ -15,9 +15,20 @@ import TableSystemInfo from "./tables/system_info";
|
||||
import TableSystemState from "./tables/system_state";
|
||||
import TableUsers from "./tables/users";
|
||||
|
||||
interface ChromeWarning {
|
||||
column: string;
|
||||
error_message: string;
|
||||
}
|
||||
interface ChromeResponse {
|
||||
data: Record<string, string>[];
|
||||
/** Manually add errors in catch response if table requires multiple APIs requests */
|
||||
warnings?: ChromeWarning[];
|
||||
}
|
||||
|
||||
export default class VirtualDatabase {
|
||||
sqlite3: SQLiteAPI;
|
||||
db: number;
|
||||
warnings?: ChromeWarning[];
|
||||
|
||||
private constructor(sqlite3: SQLiteAPI, db: number) {
|
||||
this.sqlite3 = sqlite3;
|
||||
@ -42,7 +53,11 @@ export default class VirtualDatabase {
|
||||
new TablePrivacyPreferences(sqlite3, db)
|
||||
);
|
||||
VirtualDatabase.register(sqlite3, db, new TableScreenLock(sqlite3, db));
|
||||
VirtualDatabase.register(sqlite3, db, new TableSystemInfo(sqlite3, db));
|
||||
VirtualDatabase.register(
|
||||
sqlite3,
|
||||
db,
|
||||
new TableSystemInfo(sqlite3, db, this.warnings)
|
||||
);
|
||||
VirtualDatabase.register(sqlite3, db, new TableSystemState(sqlite3, db));
|
||||
VirtualDatabase.register(sqlite3, db, new TableOSVersion(sqlite3, db));
|
||||
VirtualDatabase.register(sqlite3, db, new TableOsqueryInfo(sqlite3, db));
|
||||
@ -60,7 +75,7 @@ export default class VirtualDatabase {
|
||||
sqlite3.create_module(db, table.name, table);
|
||||
}
|
||||
|
||||
async query(sql: string): Promise<Record<string, string | number>[]> {
|
||||
async query(sql: string): Promise<ChromeResponse> {
|
||||
let rows = [];
|
||||
await this.sqlite3.exec(this.db, sql, (row, columns) => {
|
||||
// map each row to object
|
||||
@ -68,6 +83,6 @@ export default class VirtualDatabase {
|
||||
Object.fromEntries(columns.map((_, i) => [columns[i], row[i]]))
|
||||
);
|
||||
});
|
||||
return rows;
|
||||
return { data: rows, warnings: this.warnings };
|
||||
}
|
||||
}
|
||||
|
@ -4,29 +4,48 @@
|
||||
|
||||
import * as SQLite from "wa-sqlite";
|
||||
|
||||
/** Creates a single UI friendly string out of chrome tables that return multiple warnings */
|
||||
const CONCAT_CHROME_WARNINGS = (warnings: ChromeWarning[]): string => {
|
||||
const warningStrings = warnings.map(
|
||||
(warning) => `Column: ${warning.column} - ${warning.error_message}`
|
||||
);
|
||||
return warningStrings.join("\n");
|
||||
};
|
||||
class cursorState {
|
||||
rowIndex: number;
|
||||
rows: Record<string, string>[];
|
||||
error: any;
|
||||
}
|
||||
|
||||
interface ChromeWarning {
|
||||
column: string;
|
||||
error_message: string;
|
||||
}
|
||||
interface ChromeResponse {
|
||||
data: Record<string, string>[];
|
||||
/** Manually add errors in catch response if table requires requests to multiple APIs */
|
||||
warnings?: ChromeWarning[];
|
||||
}
|
||||
|
||||
export default abstract class Table implements SQLiteModule {
|
||||
sqlite3: SQLiteAPI;
|
||||
db: number;
|
||||
name: string;
|
||||
columns: string[];
|
||||
cursorStates: Map<number, cursorState>;
|
||||
warnings?: ChromeWarning[];
|
||||
|
||||
abstract generate(
|
||||
idxNum: number,
|
||||
idxString: string,
|
||||
values: Array<number>
|
||||
): Promise<Record<string, string>[]>;
|
||||
): Promise<ChromeResponse>;
|
||||
|
||||
constructor(sqlite3: SQLiteAPI, db: number) {
|
||||
constructor(sqlite3: SQLiteAPI, db: number, warnings?: ChromeWarning[]) {
|
||||
this.sqlite3 = sqlite3;
|
||||
this.db = db;
|
||||
this.cursorStates = new Map();
|
||||
this.warnings = warnings;
|
||||
}
|
||||
|
||||
// This is replaced by wa-sqlite when SQLite is loaded up, but missing from the SQLiteModule
|
||||
@ -91,10 +110,19 @@ export default abstract class Table implements SQLiteModule {
|
||||
const cursorState = this.cursorStates.get(pCursor);
|
||||
cursorState.rowIndex = 0;
|
||||
try {
|
||||
cursorState.rows = await this.generate(idxNum, idxStr, values);
|
||||
const tableDataReturned = await this.generate(idxNum, idxStr, values);
|
||||
|
||||
// Set warnings to this.warnings for database to surface in UI
|
||||
if (tableDataReturned.warnings) {
|
||||
globalThis.DB.warnings = []; // Reset warnings
|
||||
globalThis.DB.warnings = CONCAT_CHROME_WARNINGS(
|
||||
tableDataReturned.warnings
|
||||
);
|
||||
}
|
||||
cursorState.rows = tableDataReturned.data;
|
||||
} catch (err) {
|
||||
// Throwing here doesn't seem to work as expected in testing (the error doesn't seem to be
|
||||
// thrown in away that it can be caught appropriately), so instead we save the error and
|
||||
// thrown in a way that it can be caught appropriately), so instead we save the error and
|
||||
// throw in xEof.
|
||||
cursorState.error = err;
|
||||
}
|
||||
|
@ -36,6 +36,6 @@ export default class TableChromeExtensions extends Table {
|
||||
});
|
||||
}
|
||||
|
||||
return rows;
|
||||
return { data: rows };
|
||||
}
|
||||
}
|
||||
|
@ -1,19 +1,21 @@
|
||||
import VirtualDatabase from "../db";
|
||||
|
||||
const DISK_INFO_MOCK = [
|
||||
{
|
||||
capacity: 1234,
|
||||
id: 123,
|
||||
name: "Cell phone (internal storage",
|
||||
type: "Removable",
|
||||
},
|
||||
{
|
||||
capacity: 0,
|
||||
id: 12,
|
||||
name: "Thumbdrive",
|
||||
type: "Removable",
|
||||
},
|
||||
];
|
||||
const DISK_INFO_MOCK = {
|
||||
data: [
|
||||
{
|
||||
capacity: 1234,
|
||||
id: 123,
|
||||
name: "Cell phone (internal storage",
|
||||
type: "Removable",
|
||||
},
|
||||
{
|
||||
capacity: 0,
|
||||
id: 12,
|
||||
name: "Thumbdrive",
|
||||
type: "Removable",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
describe("disk_info", () => {
|
||||
test("success", async () => {
|
||||
|
@ -15,6 +15,6 @@ export default class TableDiskInfo extends Table {
|
||||
type: d.type,
|
||||
});
|
||||
}
|
||||
return rows;
|
||||
return { data: rows };
|
||||
}
|
||||
}
|
||||
|
@ -45,14 +45,16 @@ describe("geolocation", () => {
|
||||
})
|
||||
);
|
||||
const rows = await db.query("select * from geolocation");
|
||||
expect(rows).toEqual([
|
||||
{
|
||||
ip: "260f:1337:4a7e:e300:abcd:a98a:1234:18c",
|
||||
city: "Vancouver",
|
||||
country: "Canada",
|
||||
region: "British Columbia",
|
||||
},
|
||||
]);
|
||||
expect(rows).toEqual({
|
||||
data: [
|
||||
{
|
||||
ip: "260f:1337:4a7e:e300:abcd:a98a:1234:18c",
|
||||
city: "Vancouver",
|
||||
country: "Canada",
|
||||
region: "British Columbia",
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
test("request returns incomplete data", async () => {
|
||||
@ -66,14 +68,16 @@ describe("geolocation", () => {
|
||||
})
|
||||
);
|
||||
const rows = await db.query("select * from geolocation");
|
||||
expect(rows).toEqual([
|
||||
{
|
||||
ip: null,
|
||||
city: "Vancouver",
|
||||
country: null,
|
||||
region: null,
|
||||
},
|
||||
]);
|
||||
expect(rows).toEqual({
|
||||
data: [
|
||||
{
|
||||
ip: null,
|
||||
city: "Vancouver",
|
||||
country: null,
|
||||
region: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
test("request fails", async () => {
|
||||
|
@ -14,13 +14,15 @@ export default class TableGeolocation extends Table {
|
||||
async generate() {
|
||||
const resp = await fetch("https://ipapi.co/json");
|
||||
const json = await resp.json();
|
||||
return [
|
||||
{
|
||||
ip: this.ensureString(json.ip),
|
||||
city: this.ensureString(json.city),
|
||||
country: this.ensureString(json.country_name),
|
||||
region: this.ensureString(json.region),
|
||||
},
|
||||
];
|
||||
return {
|
||||
data: [
|
||||
{
|
||||
ip: this.ensureString(json.ip),
|
||||
city: this.ensureString(json.city),
|
||||
country: this.ensureString(json.country_name),
|
||||
region: this.ensureString(json.region),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -5,22 +5,20 @@ export default class TableNetworkInterfaces extends Table {
|
||||
columns = ["mac", "ipv4", "ipv6"];
|
||||
|
||||
async generate() {
|
||||
let ipv4: string, ipv6: string, mac: string;
|
||||
try {
|
||||
// @ts-expect-error @types/chrome doesn't yet have the getNetworkDetails Promise API.
|
||||
const networkDetails = (await chrome.enterprise.networkingAttributes.getNetworkDetails()) as chrome.enterprise.networkingAttributes.NetworkDetails;
|
||||
ipv4 = networkDetails.ipv4;
|
||||
ipv6 = networkDetails.ipv6;
|
||||
mac = networkDetails.macAddress;
|
||||
} catch (err) {
|
||||
console.warn(`get network details: ${err}`);
|
||||
}
|
||||
return [
|
||||
{
|
||||
mac,
|
||||
ipv4,
|
||||
ipv6,
|
||||
},
|
||||
];
|
||||
// @ts-expect-error @types/chrome doesn't yet have the getNetworkDetails Promise API.
|
||||
const networkDetails = (await chrome.enterprise.networkingAttributes.getNetworkDetails()) as chrome.enterprise.networkingAttributes.NetworkDetails;
|
||||
const ipv4 = networkDetails.ipv4;
|
||||
const ipv6 = networkDetails.ipv6;
|
||||
const mac = networkDetails.macAddress;
|
||||
|
||||
return {
|
||||
data: [
|
||||
{
|
||||
mac,
|
||||
ipv4,
|
||||
ipv6,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -3,18 +3,18 @@ import TableOSVersion from "./os_version";
|
||||
|
||||
describe("os_version", () => {
|
||||
describe("getName", () => {
|
||||
const sut = new TableOSVersion(null, null)
|
||||
const sut = new TableOSVersion(null, null);
|
||||
it("returns platform name properly formatted", () => {
|
||||
expect(sut.getName("Chrome OS")).toBe("ChromeOS")
|
||||
})
|
||||
})
|
||||
expect(sut.getName("Chrome OS")).toBe("ChromeOS");
|
||||
});
|
||||
});
|
||||
|
||||
describe("getCodename", () => {
|
||||
const sut = new TableOSVersion(null, null)
|
||||
const sut = new TableOSVersion(null, null);
|
||||
it("has the proper prefix", () => {
|
||||
expect(sut.getCodename("10.0.0").startsWith("ChromeOS")).toBe(true)
|
||||
})
|
||||
})
|
||||
expect(sut.getCodename("10.0.0").startsWith("ChromeOS")).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
test("success", async () => {
|
||||
// @ts-expect-error Typescript doesn't include the userAgentData API yet.
|
||||
@ -40,20 +40,22 @@ describe("os_version", () => {
|
||||
|
||||
const db = await VirtualDatabase.init();
|
||||
const res = await db.query("select * from os_version");
|
||||
expect(res).toEqual([
|
||||
{
|
||||
name: "ChromeOS",
|
||||
platform: "chrome",
|
||||
platform_like: "chrome",
|
||||
version: "110.0.5481.177",
|
||||
major: "110",
|
||||
minor: "0",
|
||||
build: "5481",
|
||||
patch: "177",
|
||||
arch: "x86-64",
|
||||
codename: "ChromeOS 13.2.1",
|
||||
},
|
||||
]);
|
||||
expect(res).toEqual({
|
||||
data: [
|
||||
{
|
||||
name: "ChromeOS",
|
||||
platform: "chrome",
|
||||
platform_like: "chrome",
|
||||
version: "110.0.5481.177",
|
||||
major: "110",
|
||||
minor: "0",
|
||||
build: "5481",
|
||||
patch: "177",
|
||||
arch: "x86-64",
|
||||
codename: "ChromeOS 13.2.1",
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
test("unexpected version string", async () => {
|
||||
@ -81,20 +83,22 @@ describe("os_version", () => {
|
||||
|
||||
const db = await VirtualDatabase.init();
|
||||
const res = await db.query("select * from os_version");
|
||||
expect(res).toEqual([
|
||||
{
|
||||
name: "ChromeOS",
|
||||
platform: "chrome",
|
||||
platform_like: "chrome",
|
||||
version: "110.weird_version",
|
||||
major: "",
|
||||
minor: "",
|
||||
build: "",
|
||||
patch: "",
|
||||
arch: "x86-64",
|
||||
codename: "ChromeOS 13.2.1",
|
||||
},
|
||||
]);
|
||||
expect(res).toEqual({
|
||||
data: [
|
||||
{
|
||||
name: "ChromeOS",
|
||||
platform: "chrome",
|
||||
platform_like: "chrome",
|
||||
version: "110.weird_version",
|
||||
major: "",
|
||||
minor: "",
|
||||
build: "",
|
||||
patch: "",
|
||||
arch: "x86-64",
|
||||
codename: "ChromeOS 13.2.1",
|
||||
},
|
||||
],
|
||||
});
|
||||
expect(console.warn).toHaveBeenCalledWith(
|
||||
expect.stringContaining("expected 4 segments")
|
||||
);
|
||||
@ -105,9 +109,11 @@ describe("os_version", () => {
|
||||
global.navigator.userAgentData = {
|
||||
getHighEntropyValues: jest.fn(() =>
|
||||
Promise.resolve({
|
||||
fullVersionList: [
|
||||
{ brand: "Not even chrome", version: "110.0.5481.177" },
|
||||
],
|
||||
data: {
|
||||
fullVersionList: [
|
||||
{ brand: "Not even chrome", version: "110.0.5481.177" },
|
||||
],
|
||||
},
|
||||
})
|
||||
),
|
||||
};
|
||||
|
@ -63,20 +63,22 @@ export default class TableOSVersion extends Table {
|
||||
const { arch } = platformInfo;
|
||||
|
||||
// Some of these values won't actually be correct on a non-chromeOS machine.
|
||||
return [
|
||||
{
|
||||
name: this.getName(data.platform),
|
||||
platform: "chrome",
|
||||
platform_like: "chrome",
|
||||
version,
|
||||
major,
|
||||
minor,
|
||||
build,
|
||||
patch,
|
||||
codename: this.getCodename(data.platformVersion),
|
||||
// https://developer.chrome.com/docs/extensions/reference/runtime/#type-PlatformArch
|
||||
arch: arch,
|
||||
},
|
||||
];
|
||||
return {
|
||||
data: [
|
||||
{
|
||||
name: this.getName(data.platform),
|
||||
platform: "chrome",
|
||||
platform_like: "chrome",
|
||||
version,
|
||||
major,
|
||||
minor,
|
||||
build,
|
||||
patch,
|
||||
codename: this.getCodename(data.platformVersion),
|
||||
// https://developer.chrome.com/docs/extensions/reference/runtime/#type-PlatformArch
|
||||
arch: arch,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -5,13 +5,15 @@ export default class TableOsqueryInfo extends Table {
|
||||
columns = ["version", "build_platform", "build_distro", "extensions"];
|
||||
|
||||
async generate() {
|
||||
return [
|
||||
{
|
||||
version: `fleetd-chrome-${chrome.runtime.getManifest().version}`,
|
||||
build_platform: "chrome",
|
||||
build_distro: "chrome",
|
||||
extensions: "inactive",
|
||||
},
|
||||
];
|
||||
return {
|
||||
data: [
|
||||
{
|
||||
version: `fleetd-chrome-${chrome.runtime.getManifest().version}`,
|
||||
build_platform: "chrome",
|
||||
build_distro: "chrome",
|
||||
extensions: "inactive",
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -88,10 +88,12 @@ export default class TablePrivacyPreferences extends Table {
|
||||
const columns = await Promise.all(results);
|
||||
errors.length > 0 &&
|
||||
console.log("Caught errors in chrome API calls: ", errors);
|
||||
return [
|
||||
columns.reduce((resultRow, column) => {
|
||||
return { ...resultRow, ...column };
|
||||
}, {}),
|
||||
];
|
||||
return {
|
||||
data: [
|
||||
columns.reduce((resultRow, column) => {
|
||||
return { ...resultRow, ...column };
|
||||
}, {}),
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -7,11 +7,13 @@ describe("screenlock", () => {
|
||||
const db = await VirtualDatabase.init();
|
||||
|
||||
const res = await db.query("select * from screenlock");
|
||||
expect(res).toEqual([
|
||||
{
|
||||
enabled: 1,
|
||||
grace_period: 600,
|
||||
},
|
||||
]);
|
||||
expect(res).toEqual({
|
||||
data: [
|
||||
{
|
||||
enabled: 1,
|
||||
grace_period: 600,
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -13,7 +13,7 @@ export default class TableScreenLock extends Table {
|
||||
const enabled = delay > 0 ? "1" : "0";
|
||||
const gracePeriod = delay > 0 ? delay.toString() : "-1";
|
||||
|
||||
return [{ enabled, grace_period: gracePeriod }];
|
||||
return { data: [{ enabled, grace_period: gracePeriod }] };
|
||||
}
|
||||
throw new Error(
|
||||
"Unexpected response from chrome.idle.getAutoLockDelay - expected number"
|
||||
|
@ -1,21 +1,23 @@
|
||||
import TableSystemInfo from "./system_info";
|
||||
|
||||
describe("system_info", () => {
|
||||
describe("getComputerName", () => {
|
||||
const sut = new TableSystemInfo(null, null)
|
||||
it("is computed from the hostname and hw serial", () => {
|
||||
const testCases: [string, string, string][] = [
|
||||
[null, null, "Chromebook"],
|
||||
[undefined, undefined, "Chromebook"],
|
||||
["", "", "Chromebook"],
|
||||
["mychromebook", "", "mychromebook"],
|
||||
["mychromebook", "123", "mychromebook"],
|
||||
["", "123", "Chromebook 123"],
|
||||
]
|
||||
describe("getComputerName", () => {
|
||||
const sut = new TableSystemInfo(null, null);
|
||||
it("is computed from the hostname and hw serial", () => {
|
||||
const testCases: [string, string, string][] = [
|
||||
[null, null, "Chromebook"],
|
||||
[undefined, undefined, "Chromebook"],
|
||||
["", "", "Chromebook"],
|
||||
["mychromebook", "", "mychromebook"],
|
||||
["mychromebook", "123", "mychromebook"],
|
||||
["", "123", "Chromebook 123"],
|
||||
];
|
||||
|
||||
for (let [hostname, hwSerial, expected] of testCases) {
|
||||
expect(sut.getComputerName(hostname, hwSerial)).toEqual(expected)
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
for (let [hostname, hwSerial, expected] of testCases) {
|
||||
expect(sut.getComputerName(hostname, hwSerial)).toEqual({
|
||||
data: expected,
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -29,6 +29,8 @@ export default class TableSystemInfo extends Table {
|
||||
}
|
||||
|
||||
async generate() {
|
||||
let warningsArray = [];
|
||||
|
||||
// @ts-expect-error @types/chrome doesn't yet have instanceID.
|
||||
const uuid = (await chrome.instanceID.getID()) as string;
|
||||
|
||||
@ -39,14 +41,22 @@ export default class TableSystemInfo extends Table {
|
||||
hostname = (await chrome.enterprise.deviceAttributes.getDeviceHostname()) as string;
|
||||
} catch (err) {
|
||||
console.warn("get hostname:", err);
|
||||
warningsArray.push({
|
||||
column: "hostname",
|
||||
error_message: err.message.toString(),
|
||||
});
|
||||
}
|
||||
|
||||
let hwSerial = "";
|
||||
try {
|
||||
// @ts-expect-error @types/chrome doesn't yet have the deviceAttributes Promise API.
|
||||
hwSerial = await chrome.enterprise.deviceAttributes.getDeviceSerialNumber();
|
||||
hwSerial = (await chrome.enterprise.deviceAttributes.getDeviceSerialNumber()) as string;
|
||||
} catch (err) {
|
||||
console.warn("get serial number:", err);
|
||||
warningsArray.push({
|
||||
column: "hardware_serial",
|
||||
error_message: err.message.toString(),
|
||||
});
|
||||
}
|
||||
|
||||
let hwVendor = "",
|
||||
@ -61,6 +71,14 @@ export default class TableSystemInfo extends Table {
|
||||
hwModel = platformInfo.model;
|
||||
} catch (err) {
|
||||
console.warn("get platform info:", err);
|
||||
warningsArray.push({
|
||||
column: "hardware_vendor",
|
||||
error_message: err.message.toString(),
|
||||
});
|
||||
warningsArray.push({
|
||||
column: "hardware_model",
|
||||
error_message: err.message.toString(),
|
||||
});
|
||||
}
|
||||
|
||||
let cpuBrand = "",
|
||||
@ -71,6 +89,14 @@ export default class TableSystemInfo extends Table {
|
||||
cpuType = cpuInfo.archName;
|
||||
} catch (err) {
|
||||
console.warn("get cpu info:", err);
|
||||
warningsArray.push({
|
||||
column: "cpu_brand",
|
||||
error_message: err.message.toString(),
|
||||
});
|
||||
warningsArray.push({
|
||||
column: "cpu_type",
|
||||
error_message: err.message.toString(),
|
||||
});
|
||||
}
|
||||
|
||||
let physicalMemory = "";
|
||||
@ -79,20 +105,27 @@ export default class TableSystemInfo extends Table {
|
||||
physicalMemory = memoryInfo.capacity.toString();
|
||||
} catch (err) {
|
||||
console.warn("get memory info:", err);
|
||||
warningsArray.push({
|
||||
column: "physical_memory",
|
||||
error_message: err.message.toString(),
|
||||
});
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
uuid,
|
||||
hostname,
|
||||
computer_name: this.getComputerName(hostname, hwSerial),
|
||||
hardware_serial: hwSerial,
|
||||
hardware_vendor: hwVendor,
|
||||
hardware_model: hwModel,
|
||||
cpu_brand: cpuBrand,
|
||||
cpu_type: cpuType,
|
||||
physical_memory: physicalMemory,
|
||||
},
|
||||
];
|
||||
return {
|
||||
data: [
|
||||
{
|
||||
uuid,
|
||||
hostname,
|
||||
computer_name: this.getComputerName(hostname, hwSerial),
|
||||
hardware_serial: hwSerial,
|
||||
hardware_vendor: hwVendor,
|
||||
hardware_model: hwModel,
|
||||
cpu_brand: cpuBrand,
|
||||
cpu_type: cpuType,
|
||||
physical_memory: physicalMemory,
|
||||
},
|
||||
],
|
||||
warnings: warningsArray,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -8,10 +8,12 @@ describe("screenlock", () => {
|
||||
const db = await VirtualDatabase.init();
|
||||
|
||||
const res = await db.query("select * from system_state");
|
||||
expect(res).toEqual([
|
||||
{
|
||||
idle_state: "active",
|
||||
},
|
||||
]);
|
||||
expect(res).toEqual({
|
||||
data: [
|
||||
{
|
||||
idle_state: "active",
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -24,6 +24,6 @@ export default class TableSystemState extends Table {
|
||||
}
|
||||
);
|
||||
|
||||
return [{ idle_state: idleState }];
|
||||
return { data: [{ idle_state: idleState }] };
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,6 @@ export default class TableUsers extends Table {
|
||||
|
||||
async generate() {
|
||||
const { email, id } = await chrome.identity.getProfileUserInfo({});
|
||||
return [{ uid: id, email, username: email }];
|
||||
return { data: [{ uid: id, email, username: email }] };
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ import { IHost } from "./host";
|
||||
export default PropTypes.shape({
|
||||
hosts_count: PropTypes.shape({
|
||||
total: PropTypes.number,
|
||||
successful: PropTypes.number,
|
||||
successful: PropTypes.number, // Does not include ChromeOS results that are partially successful
|
||||
failed: PropTypes.number,
|
||||
}),
|
||||
id: PropTypes.number,
|
||||
@ -26,7 +26,7 @@ export interface ICampaign {
|
||||
hosts: IHost[];
|
||||
hosts_count: {
|
||||
total: number;
|
||||
successful: number;
|
||||
successful: number; // Does not include ChromeOS results that are partially successful
|
||||
failed: number;
|
||||
};
|
||||
id: number;
|
||||
|
@ -213,8 +213,7 @@ const QueryResults = ({
|
||||
// TODO - clean up these conditions
|
||||
const hasNoResultsYet =
|
||||
!isQueryFinished && (!queryResults?.length || tableHeaders === null);
|
||||
const finishedWithNoResults =
|
||||
isQueryFinished && (!queryResults?.length || !hostsCount.successful);
|
||||
const finishedWithNoResults = isQueryFinished && !queryResults?.length;
|
||||
|
||||
if (hasNoResultsYet) {
|
||||
return <AwaitingResults />;
|
||||
|
@ -1,5 +1,4 @@
|
||||
.query-results {
|
||||
|
||||
&__results-cta {
|
||||
display: flex;
|
||||
}
|
||||
@ -52,4 +51,8 @@
|
||||
&__error-table-container {
|
||||
margin-top: 32px;
|
||||
}
|
||||
|
||||
.error__cell {
|
||||
white-space: pre-wrap; // Converts \n into new lines
|
||||
}
|
||||
}
|
||||
|
@ -1079,7 +1079,7 @@ func (svc *Service) ingestQueryResults(
|
||||
var err error
|
||||
switch {
|
||||
case strings.HasPrefix(query, hostDistributedQueryPrefix):
|
||||
err = svc.ingestDistributedQuery(ctx, *host, query, rows, failed, messages[query])
|
||||
err = svc.ingestDistributedQuery(ctx, *host, query, rows, messages[query])
|
||||
case strings.HasPrefix(query, hostPolicyQueryPrefix):
|
||||
err = ingestMembershipQuery(hostPolicyQueryPrefix, query, rows, policyResults, failed)
|
||||
case strings.HasPrefix(query, hostLabelQueryPrefix):
|
||||
@ -1147,7 +1147,7 @@ func (svc *Service) directIngestDetailQuery(ctx context.Context, host *fleet.Hos
|
||||
|
||||
// ingestDistributedQuery takes the results of a distributed query and modifies the
|
||||
// provided fleet.Host appropriately.
|
||||
func (svc *Service) ingestDistributedQuery(ctx context.Context, host fleet.Host, name string, rows []map[string]string, failed bool, errMsg string) error {
|
||||
func (svc *Service) ingestDistributedQuery(ctx context.Context, host fleet.Host, name string, rows []map[string]string, errMsg string) error {
|
||||
trimmedQuery := strings.TrimPrefix(name, hostDistributedQueryPrefix)
|
||||
|
||||
campaignID, err := strconv.Atoi(osquery_utils.EmptyToZero(trimmedQuery))
|
||||
@ -1165,7 +1165,7 @@ func (svc *Service) ingestDistributedQuery(ctx context.Context, host fleet.Host,
|
||||
},
|
||||
Rows: rows,
|
||||
}
|
||||
if failed {
|
||||
if errMsg != "" {
|
||||
res.Error = &errMsg
|
||||
}
|
||||
|
||||
|
@ -1762,7 +1762,7 @@ func TestIngestDistributedQueryParseIdError(t *testing.T) {
|
||||
}
|
||||
|
||||
host := fleet.Host{ID: 1}
|
||||
err := svc.ingestDistributedQuery(context.Background(), host, "bad_name", []map[string]string{}, false, "")
|
||||
err := svc.ingestDistributedQuery(context.Background(), host, "bad_name", []map[string]string{}, "")
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "unable to parse campaign")
|
||||
}
|
||||
@ -1788,7 +1788,7 @@ func TestIngestDistributedQueryOrphanedCampaignLoadError(t *testing.T) {
|
||||
|
||||
host := fleet.Host{ID: 1}
|
||||
|
||||
err := svc.ingestDistributedQuery(context.Background(), host, "fleet_distributed_query_42", []map[string]string{}, false, "")
|
||||
err := svc.ingestDistributedQuery(context.Background(), host, "fleet_distributed_query_42", []map[string]string{}, "")
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "loading orphaned campaign")
|
||||
}
|
||||
@ -1821,7 +1821,7 @@ func TestIngestDistributedQueryOrphanedCampaignWaitListener(t *testing.T) {
|
||||
|
||||
host := fleet.Host{ID: 1}
|
||||
|
||||
err := svc.ingestDistributedQuery(context.Background(), host, "fleet_distributed_query_42", []map[string]string{}, false, "")
|
||||
err := svc.ingestDistributedQuery(context.Background(), host, "fleet_distributed_query_42", []map[string]string{}, "")
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "campaignID=42 waiting for listener")
|
||||
}
|
||||
@ -1857,7 +1857,7 @@ func TestIngestDistributedQueryOrphanedCloseError(t *testing.T) {
|
||||
|
||||
host := fleet.Host{ID: 1}
|
||||
|
||||
err := svc.ingestDistributedQuery(context.Background(), host, "fleet_distributed_query_42", []map[string]string{}, false, "")
|
||||
err := svc.ingestDistributedQuery(context.Background(), host, "fleet_distributed_query_42", []map[string]string{}, "")
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "closing orphaned campaign")
|
||||
}
|
||||
@ -1894,7 +1894,7 @@ func TestIngestDistributedQueryOrphanedStopError(t *testing.T) {
|
||||
|
||||
host := fleet.Host{ID: 1}
|
||||
|
||||
err := svc.ingestDistributedQuery(context.Background(), host, "fleet_distributed_query_42", []map[string]string{}, false, "")
|
||||
err := svc.ingestDistributedQuery(context.Background(), host, "fleet_distributed_query_42", []map[string]string{}, "")
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "stopping orphaned campaign")
|
||||
}
|
||||
@ -1931,7 +1931,7 @@ func TestIngestDistributedQueryOrphanedStop(t *testing.T) {
|
||||
|
||||
host := fleet.Host{ID: 1}
|
||||
|
||||
err := svc.ingestDistributedQuery(context.Background(), host, "fleet_distributed_query_42", []map[string]string{}, false, "")
|
||||
err := svc.ingestDistributedQuery(context.Background(), host, "fleet_distributed_query_42", []map[string]string{}, "")
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "campaignID=42 stopped")
|
||||
lq.AssertExpectations(t)
|
||||
@ -1962,7 +1962,7 @@ func TestIngestDistributedQueryRecordCompletionError(t *testing.T) {
|
||||
}()
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
|
||||
err := svc.ingestDistributedQuery(context.Background(), host, "fleet_distributed_query_42", []map[string]string{}, false, "")
|
||||
err := svc.ingestDistributedQuery(context.Background(), host, "fleet_distributed_query_42", []map[string]string{}, "")
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "record query completion")
|
||||
lq.AssertExpectations(t)
|
||||
@ -1993,7 +1993,7 @@ func TestIngestDistributedQuery(t *testing.T) {
|
||||
}()
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
|
||||
err := svc.ingestDistributedQuery(context.Background(), host, "fleet_distributed_query_42", []map[string]string{}, false, "")
|
||||
err := svc.ingestDistributedQuery(context.Background(), host, "fleet_distributed_query_42", []map[string]string{}, "")
|
||||
require.NoError(t, err)
|
||||
lq.AssertExpectations(t)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user