Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions public/locales/gsa-de.json
Original file line number Diff line number Diff line change
Expand Up @@ -806,6 +806,7 @@
"Error loading credential store: ": "Fehler beim Laden des Anmeldedaten-Stores: ",
"Error Message": "Fehlermeldung",
"Error Messages": "Fehlermeldungen",
"Error while loading Applications for Report {{reportId}}": "Fehler beim Laden der Anwendungen für Bericht {{reportId}}",
"Error while loading Container Scanning Results for Report {{reportId}}": "Fehler beim Laden der Container-Scan-Ergebnisse für Bericht {{reportId}}",
"Error while loading Errors for Report {{reportId}}": "Fehler beim Laden der Fehler für Bericht {{reportId}}",
"Error while loading Operating Systems for Report {{reportId}}": "Fehler beim Laden der Betriebssysteme für Bericht {{reportId}}",
Expand Down
1 change: 1 addition & 0 deletions public/locales/gsa-en.json
Original file line number Diff line number Diff line change
Expand Up @@ -806,6 +806,7 @@
"Error loading credential store: ": "Error loading credential store: ",
"Error Message": "Error Message",
"Error Messages": "Error Messages",
"Error while loading Applications for Report {{reportId}}": "",
"Error while loading Container Scanning Results for Report {{reportId}}": "",
"Error while loading Errors for Report {{reportId}}": "",
"Error while loading Operating Systems for Report {{reportId}}": "",
Expand Down
1 change: 1 addition & 0 deletions public/locales/gsa-zh_CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -806,6 +806,7 @@
"Error loading credential store: ": "加载凭据存储时出错:",
"Error Message": "错误消息",
"Error Messages": "错误消息",
"Error while loading Applications for Report {{reportId}}": "",
"Error while loading Container Scanning Results for Report {{reportId}}": "",
"Error while loading Errors for Report {{reportId}}": "",
"Error while loading Operating Systems for Report {{reportId}}": "",
Expand Down
1 change: 1 addition & 0 deletions public/locales/gsa-zh_TW.json
Original file line number Diff line number Diff line change
Expand Up @@ -806,6 +806,7 @@
"Error loading credential store: ": "",
"Error Message": "錯誤訊息",
"Error Messages": "錯誤訊息",
"Error while loading Applications for Report {{reportId}}": "",
"Error while loading Container Scanning Results for Report {{reportId}}": "",
"Error while loading Errors for Report {{reportId}}": "",
"Error while loading Operating Systems for Report {{reportId}}": "",
Expand Down
177 changes: 177 additions & 0 deletions src/gmp/commands/__tests__/report-applications.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
/* SPDX-FileCopyrightText: 2026 Greenbone AG
*
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

import {describe, test, expect} from '@gsa/testing';
import ReportApplicationsCommand from 'gmp/commands/report-applications';
import {createResponse, createHttp} from 'gmp/commands/testing';

describe('ReportApplicationsCommand tests', () => {
test('should return report applications', async () => {
const response = createResponse({
get_report_applications: {
get_report_applications_response: {
applications: {
count: 2,
application: [
{
name: 'cpe:/a:vendor:product-1:1.0',
hosts_count: 1,
occurrences: 3,
severity: 5.0,
threat: 'Medium',
},
{
name: 'cpe:/a:vendor:product-2:2.0',
hosts_count: 2,
occurrences: 1,
severity: 0.0,
threat: 'Log',
},
],
},
},
},
});

const fakeHttp = createHttp(response);
const cmd = new ReportApplicationsCommand(fakeHttp);
const resp = await cmd.get({report_id: 'r1'});

expect(fakeHttp.request).toHaveBeenCalledWith('get', {
args: {
cmd: 'get_report_applications',
details: 1,
report_id: 'r1',
},
});

const {data} = resp;

expect(data).toHaveLength(2);
expect(data[0].name).toBe('cpe:/a:vendor:product-1:1.0');
expect(data[0].hosts.count).toBe(1);
expect(data[0].occurrences.total).toBe(3);
expect(data[0].severity).toBe(5.0);
expect(data[1].name).toBe('cpe:/a:vendor:product-2:2.0');
});

test('should handle single application element', async () => {
const response = createResponse({
get_report_applications: {
get_report_applications_response: {
applications: {
count: 1,
application: {
name: 'cpe:/a:vendor:single-app:1.0',
hosts_count: 1,
occurrences: 1,
severity: 0.0,
threat: 'Log',
},
},
},
},
});

const fakeHttp = createHttp(response);
const cmd = new ReportApplicationsCommand(fakeHttp);
const resp = await cmd.get({report_id: 'r2'});

const {data} = resp;
expect(data).toHaveLength(1);
expect(data[0].name).toBe('cpe:/a:vendor:single-app:1.0');
});

test('should handle empty applications', async () => {
const response = createResponse({
get_report_applications: {
get_report_applications_response: {
applications: {
count: 0,
},
},
},
});

const fakeHttp = createHttp(response);
const cmd = new ReportApplicationsCommand(fakeHttp);
const resp = await cmd.get({report_id: 'r3'});

const {data} = resp;
expect(data).toHaveLength(0);
});

test('should throw error for invalid response', async () => {
const response = createResponse({});

const fakeHttp = createHttp(response);
const cmd = new ReportApplicationsCommand(fakeHttp);

await expect(cmd.get({report_id: 'r4'})).rejects.toThrow(
'Invalid response: get_report_applications not found in response',
);
});

test('should pass filter parameter', async () => {
const response = createResponse({
get_report_applications: {
get_report_applications_response: {
applications: {
count: 0,
},
},
},
});

const fakeHttp = createHttp(response);
const cmd = new ReportApplicationsCommand(fakeHttp);
await cmd.get({report_id: 'r5', filter: 'first=1 rows=100'});

expect(fakeHttp.request).toHaveBeenCalledWith('get', {
args: {
cmd: 'get_report_applications',
details: 1,
report_id: 'r5',
filter: 'first=1 rows=100',
},
});
});

test('should include filter in meta', async () => {
const response = createResponse({
get_report_applications: {
get_report_applications_response: {
applications: {
count: 1,
application: {
name: 'cpe:/a:vendor:app:1.0',
hosts_count: 1,
occurrences: 1,
severity: 0.0,
threat: 'Log',
},
},
filters: {
term: 'first=1 rows=100',
filter: {_id: ''},
keywords: {
keyword: [
{column: 'first', relation: '=', value: '1'},
{column: 'rows', relation: '=', value: '100'},
],
},
},
},
},
});

const fakeHttp = createHttp(response);
const cmd = new ReportApplicationsCommand(fakeHttp);
const resp = await cmd.get({report_id: 'r6'});

const {filter} = resp.meta;
expect(filter).toBeDefined();
});
});
108 changes: 108 additions & 0 deletions src/gmp/commands/report-applications.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/* SPDX-FileCopyrightText: 2026 Greenbone AG
*
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

import CollectionCounts from 'gmp/collection/collection-counts';
import {parseFilter} from 'gmp/collection/parser';
import type {EntitiesMeta} from 'gmp/commands/entities';
import HttpCommand, {
type HttpCommandInputParams,
type HttpCommandOptions,
} from 'gmp/commands/http';
import type Http from 'gmp/http/http';
import type Response from 'gmp/http/response';
import type {XmlResponseData} from 'gmp/http/transform/fast-xml';
import type {FilterModelElement} from 'gmp/models/filter';
import ReportApp from 'gmp/models/report/app';
import {parseSeverity} from 'gmp/parser';
import {forEach} from 'gmp/utils/array';
import {isDefined} from 'gmp/utils/identity';

export interface ApplicationElement {
name?: string;
hosts_count?: number | string;
occurrences?: number | string;
severity?: number | string;
threat?: string;
}

interface ApplicationsContainer {
_start?: number;
_max?: number;
count?: number;
application?: ApplicationElement | ApplicationElement[];
}

interface ReportApplicationsResponseData extends XmlResponseData {
get_report_applications?: {
get_report_applications_response: {
applications?: ApplicationsContainer;
filters?: FilterModelElement;
[key: string]: unknown;
};
};
}

class ReportApplicationsCommand extends HttpCommand {
constructor(http: Http) {
super(http, {cmd: 'get_report_applications'});
}

async get(
params: HttpCommandInputParams = {},
options?: HttpCommandOptions,
): Promise<Response<ReportApp[], EntitiesMeta>> {
const response = await this.httpGetWithTransform(
{details: 1, ...params},
options,
);

const root = response.data as ReportApplicationsResponseData;

if (!root.get_report_applications) {
throw new Error(
'Invalid response: get_report_applications not found in response',
);
}

const data = root.get_report_applications.get_report_applications_response;
const appContainer = data.applications;
const apps: ReportApp[] = [];

forEach(appContainer?.application, (app: ApplicationElement) => {
if (!isDefined(app.name)) {
return;
}

const reportApp = new ReportApp({
id: app.name,
name: app.name,
severity: parseSeverity(app.severity),
});

reportApp.hosts.count = Number(app.hosts_count) || 0;
reportApp.occurrences.total = Number(app.occurrences) || 0;

apps.push(reportApp);
});

const filteredCount = apps.length;
const counts = new CollectionCounts({
all: appContainer?.count ?? filteredCount,
filtered: filteredCount,
first: appContainer?._start ?? 1,
length: filteredCount,
rows: appContainer?._max ?? filteredCount,
});

const filter = parseFilter(data);

return response.set<ReportApp[], EntitiesMeta>(apps, {
filter,
counts,
});
}
}

export default ReportApplicationsCommand;
3 changes: 3 additions & 0 deletions src/gmp/gmp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ import PoliciesCommand from 'gmp/commands/policies';
import PolicyCommand from 'gmp/commands/policy';
import {PortListCommand, PortListsCommand} from 'gmp/commands/port-lists';
import ReportCommand from 'gmp/commands/report';
import ReportApplicationsCommand from 'gmp/commands/report-applications';
import ReportConfigCommand from 'gmp/commands/report-config';
import ReportConfigsCommand from 'gmp/commands/report-configs';
import ReportsErrorsCommand from 'gmp/commands/report-errors';
Expand Down Expand Up @@ -146,6 +147,7 @@ class Gmp {
public readonly report: ReportCommand;
public readonly reportconfig: ReportConfigCommand;
public readonly reportconfigs: ReportConfigsCommand;
public readonly reportapplications: ReportApplicationsCommand;
public readonly reporterrors: ReportsErrorsCommand;
public readonly reportformat: ReportFormatCommand;
public readonly reportformats: ReportFormatsCommand;
Expand Down Expand Up @@ -237,6 +239,7 @@ class Gmp {
this.portlists = new PortListsCommand(this.http);
this.report = new ReportCommand(this.http);
this.reportconfig = new ReportConfigCommand(this.http);
this.reportapplications = new ReportApplicationsCommand(this.http);
this.reportconfigs = new ReportConfigsCommand(this.http);
this.reporterrors = new ReportsErrorsCommand(this.http);
this.reportformat = new ReportFormatCommand(this.http);
Expand Down
37 changes: 37 additions & 0 deletions src/web/hooks/use-query/report-applications.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/* SPDX-FileCopyrightText: 2026 Greenbone AG
*
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

import type Filter from 'gmp/models/filter';
import useGmp from 'web/hooks/useGmp';
import useGetEntities from 'web/queries/useGetEntities';

interface UseGetReportApplicationsParams {
reportId: string;
filter?: Filter;
refetchInterval?: number | false;
}

export const useGetReportApplications = ({
reportId,
filter = undefined,
refetchInterval = undefined,
}: UseGetReportApplicationsParams) => {
const gmp = useGmp();

return useGetEntities({
gmpMethod: ({filter: reportFilter}) =>
gmp.reportapplications.get({
report_id: reportId,
filter: reportFilter,
}),
queryId: `get_report_applications_${reportId}`,
filter,
enabled: Boolean(reportId),
keepPreviousData: true,
refetchInterval,
});
};

export default useGetReportApplications;
2 changes: 0 additions & 2 deletions src/web/pages/reports/AuditReportDetailsContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,6 @@ const AuditReportDetailsContent = ({
const [_] = useTranslation();

const hosts = report?.hosts;
const operatingSystems = report?.operatingSystems;
const results = report?.results;
const timestamp = report?.timestamp;
const scan_run_status = report?.scan_run_status;
Expand Down Expand Up @@ -304,7 +303,6 @@ const AuditReportDetailsContent = ({
audit={true}
filter={effectiveReportFilter}
reportId={reportId}
reportOperatingSystems={operatingSystems?.entities}
status={status}
/>,
),
Expand Down
Loading
Loading