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