@epcare/esm-report-builder 0.1.1-pre.76 → 0.1.1-pre.78
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/__mocks__/react-i18next.js +12 -0
- package/dist/729.js +1 -1
- package/dist/760.js +1 -0
- package/dist/760.js.map +1 -0
- package/dist/main.js +1 -1
- package/dist/openmrs-esm-report-builder.js.buildmanifest.json +26 -26
- package/dist/routes.json +1 -1
- package/package.json +1 -1
- package/routes.json +3 -2
- package/src/components/admin/admin-page.component.tsx +53 -24
- package/src/components/admin/etl-browser/etl-browser.component.scss +10 -3
- package/src/components/admin/etl-browser/preview-grid.component.tsx +1 -1
- package/src/components/admin/etl-sources-page.component.tsx +30 -19
- package/src/components/admin/report-categories-page.component.tsx +32 -21
- package/src/components/admin/report-library-page.component.tsx +30 -19
- package/src/components/app-shell/report-builder-shell.component.tsx +194 -162
- package/src/components/app-shell/report-builder-shell.scss +4 -1
- package/src/components/dashboards/dashboards-page.component.tsx +13 -6
- package/src/components/dashboards/dashboards-table.component.tsx +24 -17
- package/src/components/data-themes/data-themes-page.component.tsx +11 -4
- package/src/components/data-themes/data-themes-table.component.tsx +10 -3
- package/src/components/data-visualizer/data-visualizer.resource.ts +49 -37
- package/src/components/etl-monitor/builder/etl-monitor-builder-page.component.tsx +6 -1
- package/src/components/etl-monitor/etl-monitors-page.component.tsx +35 -24
- package/src/components/import-export/import-export-page.component.tsx +38 -22
- package/src/components/indicators/indicators-page.component.tsx +41 -6
- package/src/components/indicators/indicators-table.component.tsx +24 -4
- package/src/components/landing/report-builder-landing-page.component.tsx +34 -1
- package/src/components/linelist/definition/linelist-definition-editor.component.tsx +14 -2
- package/src/components/linelist/linelist-dashboard.page.component.tsx +20 -13
- package/src/components/report/report-dashboard.page.component.tsx +14 -8
- package/src/components/report/report-editor.page.component.tsx +6 -1
- package/src/components/report-sections/report-sections-page.component.tsx +17 -5
- package/src/components/security/access-denied-page.component.tsx +69 -0
- package/src/components/security/access-denied-page.scss +39 -0
- package/src/components/security/privilege-route.component.test.tsx +79 -0
- package/src/components/security/privilege-route.component.tsx +39 -0
- package/src/constants/privileges.test.ts +53 -0
- package/src/constants/privileges.ts +118 -0
- package/src/hooks/use-report-builder-privileges.test.tsx +98 -0
- package/src/hooks/use-report-builder-privileges.ts +58 -0
- package/src/resources/etl-monitor/etl-monitor-test.api.ts +5 -2
- package/src/resources/etl-task/etl-task.api.ts +21 -13
- package/src/resources/legacy-reports/legacy-reports.api.ts +10 -22
- package/src/resources/openmrs-api.ts +30 -14
- package/src/resources/report-section/section-preview.api.ts +10 -6
- package/src/routes/report-builder.routes.tsx +93 -28
- package/src/routes.json +3 -2
- package/src/utils/api-error.utils.test.ts +151 -0
- package/src/utils/api-error.utils.ts +131 -0
- package/dist/359.js +0 -1
- package/dist/359.js.map +0 -1
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { createRoot } from 'react-dom/client';
|
|
3
|
+
import { act } from 'react-dom/test-utils';
|
|
4
|
+
|
|
5
|
+
import { useSession } from '@openmrs/esm-framework';
|
|
6
|
+
import { useReportBuilderPrivileges } from './use-report-builder-privileges';
|
|
7
|
+
import { RB } from '../constants/privileges';
|
|
8
|
+
|
|
9
|
+
jest.mock('@openmrs/esm-framework', () => ({
|
|
10
|
+
useSession: jest.fn(),
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
const mockUseSession = useSession as unknown as jest.Mock;
|
|
14
|
+
|
|
15
|
+
(globalThis as any).IS_REACT_ACT_ENVIRONMENT = true;
|
|
16
|
+
|
|
17
|
+
const priv = (name: string) => ({ uuid: name, name, display: name });
|
|
18
|
+
|
|
19
|
+
function renderHook<T>(hook: () => T): { current: T | undefined; rerender: () => void; unmount: () => void } {
|
|
20
|
+
const container = document.createElement('div');
|
|
21
|
+
const root = createRoot(container);
|
|
22
|
+
let current: T | undefined;
|
|
23
|
+
const Probe = () => {
|
|
24
|
+
current = hook();
|
|
25
|
+
return null;
|
|
26
|
+
};
|
|
27
|
+
const render = () => act(() => {
|
|
28
|
+
root.render(React.createElement(Probe));
|
|
29
|
+
});
|
|
30
|
+
render();
|
|
31
|
+
return {
|
|
32
|
+
get current() {
|
|
33
|
+
return current;
|
|
34
|
+
},
|
|
35
|
+
rerender: render,
|
|
36
|
+
unmount: () => act(() => root.unmount()),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function sessionWith(privileges: string[], roles: string[] = [], authenticated = true) {
|
|
41
|
+
return {
|
|
42
|
+
authenticated,
|
|
43
|
+
user: {
|
|
44
|
+
uuid: 'u-1',
|
|
45
|
+
display: 'User',
|
|
46
|
+
privileges: privileges.map(priv),
|
|
47
|
+
roles: roles.map((r) => ({ uuid: r, display: r })),
|
|
48
|
+
},
|
|
49
|
+
} as any;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
describe('useReportBuilderPrivileges', () => {
|
|
53
|
+
it('matches held privileges by name', () => {
|
|
54
|
+
mockUseSession.mockReturnValue(sessionWith([RB.REPORT_VIEW]));
|
|
55
|
+
const { current, unmount } = renderHook(() => useReportBuilderPrivileges());
|
|
56
|
+
expect(current!.has(RB.REPORT_VIEW)).toBe(true);
|
|
57
|
+
expect(current!.has(RB.REPORT_EDIT)).toBe(false);
|
|
58
|
+
expect(current!.isAuthenticated).toBe(true);
|
|
59
|
+
unmount();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('uses OR semantics like the backend @Authorized({a, b})', () => {
|
|
63
|
+
mockUseSession.mockReturnValue(sessionWith([RB.REPORT_ADD]));
|
|
64
|
+
const { current, unmount } = renderHook(() => useReportBuilderPrivileges());
|
|
65
|
+
expect(current!.has(RB.REPORT_ADD, RB.REPORT_EDIT)).toBe(true);
|
|
66
|
+
expect(current!.hasAll(RB.REPORT_ADD, RB.REPORT_EDIT)).toBe(false);
|
|
67
|
+
unmount();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('treats the System Developer role as superuser', () => {
|
|
71
|
+
mockUseSession.mockReturnValue(sessionWith([], ['System Developer']));
|
|
72
|
+
const { current, unmount } = renderHook(() => useReportBuilderPrivileges());
|
|
73
|
+
expect(current!.isSuperUser).toBe(true);
|
|
74
|
+
expect(current!.has(RB.ETLMONITOR_PURGE)).toBe(true);
|
|
75
|
+
expect(current!.hasAll(RB.PACKAGE_IMPORT, RB.PACKAGE_EXPORT)).toBe(true);
|
|
76
|
+
unmount();
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('exposes convenience flags', () => {
|
|
80
|
+
mockUseSession.mockReturnValue(
|
|
81
|
+
sessionWith([RB.REPORT_VIEW, RB.REPORT_RUN, RB.PACKAGE_EXPORT]),
|
|
82
|
+
);
|
|
83
|
+
const { current, unmount } = renderHook(() => useReportBuilderPrivileges());
|
|
84
|
+
expect(current!.canAuthor).toBe(false);
|
|
85
|
+
expect(current!.canRun).toBe(true);
|
|
86
|
+
expect(current!.canPublish).toBe(true);
|
|
87
|
+
expect(current!.canManageEtl).toBe(false);
|
|
88
|
+
unmount();
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('is false on everything for an unauthenticated session', () => {
|
|
92
|
+
mockUseSession.mockReturnValue({ authenticated: false, user: undefined });
|
|
93
|
+
const { current, unmount } = renderHook(() => useReportBuilderPrivileges());
|
|
94
|
+
expect(current!.isAuthenticated).toBe(false);
|
|
95
|
+
expect(current!.has(RB.REPORT_VIEW)).toBe(false);
|
|
96
|
+
unmount();
|
|
97
|
+
});
|
|
98
|
+
});
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { useCallback, useMemo } from 'react';
|
|
2
|
+
import { useSession } from '@openmrs/esm-framework';
|
|
3
|
+
import { RB } from '../constants/privileges';
|
|
4
|
+
|
|
5
|
+
const SUPERUSER_ROLE = 'System Developer';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Privileges of the signed-in user, as resolved by the backend session endpoint
|
|
9
|
+
* (inherited roles already expanded). Backed by the framework session store —
|
|
10
|
+
* the same fetch the SPA shell and headers already use — so there is no extra
|
|
11
|
+
* network call and values refresh automatically on re-login.
|
|
12
|
+
*
|
|
13
|
+
* `has` uses OR semantics: has(RB.REPORT_ADD, RB.REPORT_EDIT) is true when the
|
|
14
|
+
* user holds either, mirroring the backend's @Authorized({a, b}) behaviour.
|
|
15
|
+
*/
|
|
16
|
+
export function useReportBuilderPrivileges() {
|
|
17
|
+
const session = useSession();
|
|
18
|
+
const user = session?.user;
|
|
19
|
+
|
|
20
|
+
const held = useMemo(() => {
|
|
21
|
+
const names = new Set<string>();
|
|
22
|
+
for (const privilege of user?.privileges ?? []) {
|
|
23
|
+
names.add(privilege.name);
|
|
24
|
+
names.add(privilege.display);
|
|
25
|
+
}
|
|
26
|
+
return names;
|
|
27
|
+
}, [user]);
|
|
28
|
+
|
|
29
|
+
const isSuperUser = useMemo(
|
|
30
|
+
() => !!user?.roles?.some((role) => role.display === SUPERUSER_ROLE),
|
|
31
|
+
[user],
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
const has = useCallback(
|
|
35
|
+
(...needed: string[]) =>
|
|
36
|
+
needed.length === 0 || isSuperUser || needed.some((privilege) => held.has(privilege)),
|
|
37
|
+
[held, isSuperUser],
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
const hasAll = useCallback(
|
|
41
|
+
(...needed: string[]) =>
|
|
42
|
+
needed.length === 0 || isSuperUser || needed.every((privilege) => held.has(privilege)),
|
|
43
|
+
[held, isSuperUser],
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
/** has(RB.X, RB.Y) — true when the user holds ANY of the privileges. */
|
|
48
|
+
has,
|
|
49
|
+
/** hasAll(RB.X, RB.Y) — true only when the user holds ALL of the privileges. */
|
|
50
|
+
hasAll,
|
|
51
|
+
isSuperUser,
|
|
52
|
+
isAuthenticated: !!session?.authenticated,
|
|
53
|
+
canAuthor: has(RB.REPORT_ADD, RB.REPORT_EDIT),
|
|
54
|
+
canRun: has(RB.REPORT_RUN),
|
|
55
|
+
canPublish: has(RB.PACKAGE_IMPORT, RB.PACKAGE_EXPORT),
|
|
56
|
+
canManageEtl: has(RB.ETLSOURCE_ADD, RB.ETLSOURCE_EDIT, RB.ETLMONITOR_ADD, RB.ETLMONITOR_EDIT),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
@@ -221,9 +221,12 @@ export async function testEndpointConnection(
|
|
|
221
221
|
|
|
222
222
|
if (error?.response?.status === 403) {
|
|
223
223
|
return {
|
|
224
|
-
|
|
225
|
-
error:
|
|
224
|
+
// PrivilegeError (from the omrs* helpers) already carries a user-friendly message.
|
|
225
|
+
error:
|
|
226
|
+
error?.message ??
|
|
227
|
+
'Permission denied. You may not have the required privileges to test endpoints.',
|
|
226
228
|
errorCode: 'FORBIDDEN',
|
|
229
|
+
success: false,
|
|
227
230
|
durationMs,
|
|
228
231
|
testedAt: new Date().toISOString(),
|
|
229
232
|
};
|
|
@@ -6,14 +6,20 @@
|
|
|
6
6
|
import { openmrsFetch } from '@openmrs/esm-framework';
|
|
7
7
|
import type { OpenMRSTaskDefinition, ETLTaskExecutionResponse } from './etl-task.types';
|
|
8
8
|
|
|
9
|
+
// These are OpenMRS core endpoints (scheduler), not reportbuilder ones - a 403 here
|
|
10
|
+
// carries a core privilege name, which the error normalizer passes through verbatim.
|
|
11
|
+
import { guardRejection } from '../../utils/api-error.utils';
|
|
12
|
+
|
|
9
13
|
/**
|
|
10
14
|
* Fetch all available OpenMRS task definitions (with scheduler metadata)
|
|
11
15
|
*/
|
|
12
16
|
export async function fetchAllTaskDefinitions(signal?: AbortSignal): Promise<OpenMRSTaskDefinition[]> {
|
|
13
17
|
try {
|
|
14
|
-
const response = await
|
|
15
|
-
|
|
16
|
-
|
|
18
|
+
const response = await guardRejection(
|
|
19
|
+
openmrsFetch(
|
|
20
|
+
'/ws/rest/v1/taskdefinition?v=custom:(uuid,name,description,taskClass,started,lastExecutionTime,repeatInterval)',
|
|
21
|
+
{ signal },
|
|
22
|
+
),
|
|
17
23
|
);
|
|
18
24
|
const data = await response.json();
|
|
19
25
|
return data.results || [];
|
|
@@ -62,16 +68,18 @@ export async function fetchConfiguredTasks(
|
|
|
62
68
|
*/
|
|
63
69
|
export async function executeETLTask(taskName: string): Promise<ETLTaskExecutionResponse> {
|
|
64
70
|
try {
|
|
65
|
-
const response = await
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
71
|
+
const response = await guardRejection(
|
|
72
|
+
openmrsFetch('/ws/rest/v1/taskaction', {
|
|
73
|
+
method: 'POST',
|
|
74
|
+
headers: {
|
|
75
|
+
'Content-Type': 'application/json',
|
|
76
|
+
},
|
|
77
|
+
body: {
|
|
78
|
+
action: 'runtask',
|
|
79
|
+
tasks: [taskName],
|
|
80
|
+
},
|
|
81
|
+
}),
|
|
82
|
+
);
|
|
75
83
|
|
|
76
84
|
if (response.status !== 201) {
|
|
77
85
|
throw new Error(`Task execution failed with status ${response.status}`);
|
|
@@ -1,23 +1,9 @@
|
|
|
1
1
|
import { openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
async function omrsGet<T>(path: string, signal?: AbortSignal): Promise<T> {
|
|
6
|
-
const url = `${restBaseUrl}${path.startsWith('/') ? path : `/${path}`}`;
|
|
7
|
-
const res = await openmrsFetch<T>(url, { signal });
|
|
8
|
-
return res.data;
|
|
9
|
-
}
|
|
3
|
+
import { guardRejection } from '../../utils/api-error.utils';
|
|
4
|
+
import { omrsGet, omrsPost } from '../openmrs-api';
|
|
10
5
|
|
|
11
|
-
|
|
12
|
-
const url = `${restBaseUrl}${path.startsWith('/') ? path : `/${path}`}`;
|
|
13
|
-
const res = await openmrsFetch<T>(url, {
|
|
14
|
-
method: 'POST',
|
|
15
|
-
headers: { 'Content-Type': 'application/json' },
|
|
16
|
-
body,
|
|
17
|
-
signal,
|
|
18
|
-
});
|
|
19
|
-
return res.data;
|
|
20
|
-
}
|
|
6
|
+
const RESOURCE = '/reportbuilder/legacy';
|
|
21
7
|
|
|
22
8
|
export type LegacyReportDto = {
|
|
23
9
|
uuid: string;
|
|
@@ -122,11 +108,13 @@ export async function uploadLegacyReport(
|
|
|
122
108
|
|
|
123
109
|
const url = `${restBaseUrl}${RESOURCE}/upload`;
|
|
124
110
|
|
|
125
|
-
const response = await
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
111
|
+
const response = await guardRejection(
|
|
112
|
+
openmrsFetch(url, {
|
|
113
|
+
method: 'POST',
|
|
114
|
+
body: formData,
|
|
115
|
+
signal,
|
|
116
|
+
}),
|
|
117
|
+
);
|
|
130
118
|
|
|
131
119
|
if (!response.ok) {
|
|
132
120
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
@@ -1,24 +1,40 @@
|
|
|
1
1
|
import { openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
|
|
2
2
|
|
|
3
|
+
import { normalizeApiError } from '../utils/api-error.utils';
|
|
4
|
+
|
|
5
|
+
async function run<T>(request: () => Promise<T>): Promise<T> {
|
|
6
|
+
try {
|
|
7
|
+
return await request();
|
|
8
|
+
} catch (error) {
|
|
9
|
+
throw normalizeApiError(error);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
3
13
|
export async function omrsGet<T>(path: string, signal?: AbortSignal): Promise<T> {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
14
|
+
return run(async () => {
|
|
15
|
+
const url = `${restBaseUrl}${path.startsWith('/') ? path : `/${path}`}`;
|
|
16
|
+
const res = await openmrsFetch<T>(url, { signal });
|
|
17
|
+
return res.data;
|
|
18
|
+
});
|
|
7
19
|
}
|
|
8
20
|
|
|
9
21
|
export async function omrsPost<T>(path: string, body: any, signal?: AbortSignal): Promise<T> {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
22
|
+
return run(async () => {
|
|
23
|
+
const url = `${restBaseUrl}${path.startsWith('/') ? path : `/${path}`}`;
|
|
24
|
+
const res = await openmrsFetch<T>(url, {
|
|
25
|
+
method: 'POST',
|
|
26
|
+
headers: { 'Content-Type': 'application/json' },
|
|
27
|
+
body,
|
|
28
|
+
signal,
|
|
29
|
+
});
|
|
30
|
+
return res.data;
|
|
16
31
|
});
|
|
17
|
-
return res.data;
|
|
18
32
|
}
|
|
19
33
|
|
|
20
34
|
export async function omrsDelete<T>(path: string, signal?: AbortSignal): Promise<T> {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
35
|
+
return run(async () => {
|
|
36
|
+
const url = `${restBaseUrl}${path.startsWith('/') ? path : `/${path}`}`;
|
|
37
|
+
const res = await openmrsFetch<T>(url, { method: 'DELETE', signal });
|
|
38
|
+
return res.data;
|
|
39
|
+
});
|
|
40
|
+
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { openmrsFetch } from '@openmrs/esm-framework';
|
|
2
2
|
|
|
3
|
+
import { guardRejection } from '../../utils/api-error.utils';
|
|
4
|
+
|
|
3
5
|
export type SectionPreviewRequest = {
|
|
4
6
|
sectionUuid: string;
|
|
5
7
|
indicatorUuid?: string;
|
|
@@ -10,12 +12,14 @@ export type SectionPreviewRequest = {
|
|
|
10
12
|
};
|
|
11
13
|
|
|
12
14
|
export async function previewSection(body: SectionPreviewRequest, signal?: AbortSignal) {
|
|
13
|
-
const { data } = await
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
const { data } = await guardRejection(
|
|
16
|
+
openmrsFetch('/ws/rest/v1/reportbuilder/sectionpreview', {
|
|
17
|
+
method: 'POST',
|
|
18
|
+
headers: { 'Content-Type': 'application/json' },
|
|
19
|
+
body,
|
|
20
|
+
signal,
|
|
21
|
+
}),
|
|
22
|
+
);
|
|
19
23
|
|
|
20
24
|
return data;
|
|
21
25
|
}
|
|
@@ -31,55 +31,120 @@ import LinelistRunReport from '../components/linelist/linelist-run-report.page.c
|
|
|
31
31
|
|
|
32
32
|
import { ImportExportPage } from '../components/import-export';
|
|
33
33
|
|
|
34
|
+
import PrivilegeRoute from '../components/security/privilege-route.component';
|
|
35
|
+
import { ROUTE_PRIVILEGES } from '../constants/privileges';
|
|
36
|
+
|
|
37
|
+
/** Wraps a route element with its required privileges (see ROUTE_PRIVILEGES). */
|
|
38
|
+
const guard = (route: keyof typeof ROUTE_PRIVILEGES, element: React.ReactNode) => (
|
|
39
|
+
<PrivilegeRoute required={ROUTE_PRIVILEGES[route]}>{element}</PrivilegeRoute>
|
|
40
|
+
);
|
|
41
|
+
|
|
34
42
|
const ReportBuilderRoutes: React.FC = () => {
|
|
35
43
|
return (
|
|
36
44
|
<Routes>
|
|
37
45
|
<Route element={<ReportBuilderShell />}>
|
|
38
46
|
{/* Home / landing */}
|
|
39
|
-
<Route path="/" element={<ReportBuilderLandingPage />} />
|
|
47
|
+
<Route path="/" element={guard('/', <ReportBuilderLandingPage />)} />
|
|
40
48
|
|
|
41
49
|
{/* Reports */}
|
|
42
|
-
<Route path="/reports" element={<ReportDashboardPage />} />
|
|
50
|
+
<Route path="/reports" element={guard('/reports', <ReportDashboardPage />)} />
|
|
43
51
|
|
|
44
52
|
{/* Create / edit report */}
|
|
45
|
-
<Route path="/new" element={<ReportEditorPage />} />
|
|
46
|
-
<Route
|
|
53
|
+
<Route path="/new" element={guard('/new', <ReportEditorPage />)} />
|
|
54
|
+
<Route
|
|
55
|
+
path="/edit/:reportId"
|
|
56
|
+
element={guard('/edit/:reportId', <ReportEditorPage />)}
|
|
57
|
+
/>
|
|
47
58
|
|
|
48
59
|
{/* Indicators / sections */}
|
|
49
|
-
<Route path="/indicators" element={<IndicatorsPage />} />
|
|
50
|
-
<Route path="/sections" element={<SectionsPage />} />
|
|
60
|
+
<Route path="/indicators" element={guard('/indicators', <IndicatorsPage />)} />
|
|
61
|
+
<Route path="/sections" element={guard('/sections', <SectionsPage />)} />
|
|
51
62
|
|
|
52
63
|
{/* Run */}
|
|
53
|
-
<Route path="/run" element={<ReportVisualizerPage />} />
|
|
54
|
-
<Route
|
|
64
|
+
<Route path="/run" element={guard('/run', <ReportVisualizerPage />)} />
|
|
65
|
+
<Route
|
|
66
|
+
path="/run-reports"
|
|
67
|
+
element={guard('/run-reports', <ReportVisualizerPage />)}
|
|
68
|
+
/>
|
|
55
69
|
|
|
56
70
|
{/* Admin */}
|
|
57
|
-
<Route path="/admin" element={<AdminPage />} />
|
|
58
|
-
<Route
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
<Route
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
<Route
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
<Route
|
|
71
|
+
<Route path="/admin" element={guard('/admin', <AdminPage />)} />
|
|
72
|
+
<Route
|
|
73
|
+
path="/admin/report-categories"
|
|
74
|
+
element={guard('/admin/report-categories', <ReportCategoriesPage />)}
|
|
75
|
+
/>
|
|
76
|
+
<Route
|
|
77
|
+
path="/admin/age-categories"
|
|
78
|
+
element={guard('/admin/age-categories', <AgeCategoriesPage />)}
|
|
79
|
+
/>
|
|
80
|
+
<Route
|
|
81
|
+
path="/admin/age-groups"
|
|
82
|
+
element={guard('/admin/age-groups', <AgeGroupsPage />)}
|
|
83
|
+
/>
|
|
84
|
+
<Route
|
|
85
|
+
path="/admin/report-library"
|
|
86
|
+
element={guard('/admin/report-library', <ReportLibraryPage />)}
|
|
87
|
+
/>
|
|
88
|
+
<Route
|
|
89
|
+
path="/admin/etl-sources"
|
|
90
|
+
element={guard('/admin/etl-sources', <ETLSourcesPage />)}
|
|
91
|
+
/>
|
|
92
|
+
<Route
|
|
93
|
+
path="/admin/etl-tasks"
|
|
94
|
+
element={guard('/admin/etl-tasks', <ETLTasksPage />)}
|
|
95
|
+
/>
|
|
96
|
+
<Route
|
|
97
|
+
path="/admin/etl-monitors"
|
|
98
|
+
element={guard('/admin/etl-monitors', <ETLMonitorsPage />)}
|
|
99
|
+
/>
|
|
100
|
+
<Route
|
|
101
|
+
path="/admin/etl-monitors/builder"
|
|
102
|
+
element={guard('/admin/etl-monitors/builder', <EtlMonitorBuilderPage />)}
|
|
103
|
+
/>
|
|
104
|
+
<Route
|
|
105
|
+
path="/admin/etl-browser"
|
|
106
|
+
element={guard('/admin/etl-browser', <EtlBrowserPage />)}
|
|
107
|
+
/>
|
|
108
|
+
<Route
|
|
109
|
+
path="/admin/dashboards"
|
|
110
|
+
element={guard('/admin/dashboards', <DashboardsAdminPage />)}
|
|
111
|
+
/>
|
|
112
|
+
<Route path="/dashboards" element={guard('/dashboards', <DashboardsPage />)} />
|
|
113
|
+
<Route
|
|
114
|
+
path="/dashboards/:code"
|
|
115
|
+
element={guard('/dashboards/:code', <DashboardRendererPage />)}
|
|
116
|
+
/>
|
|
117
|
+
<Route
|
|
118
|
+
path="/etl-dashboard"
|
|
119
|
+
element={guard('/etl-dashboard', <EtlDashboardPage />)}
|
|
120
|
+
/>
|
|
71
121
|
|
|
72
122
|
{/* Data themes now accessed under Admin */}
|
|
73
|
-
<Route
|
|
123
|
+
<Route
|
|
124
|
+
path="/admin/themes"
|
|
125
|
+
element={guard('/admin/themes', <DataThemesPage />)}
|
|
126
|
+
/>
|
|
74
127
|
|
|
75
128
|
{/* Linelist Reports */}
|
|
76
|
-
<Route path="/linelist" element={<LinelistReportsPage />} />
|
|
77
|
-
<Route
|
|
78
|
-
|
|
79
|
-
|
|
129
|
+
<Route path="/linelist" element={guard('/linelist', <LinelistReportsPage />)} />
|
|
130
|
+
<Route
|
|
131
|
+
path="/linelist/new"
|
|
132
|
+
element={guard('/linelist/new', <LinelistBuilderWorkspace />)}
|
|
133
|
+
/>
|
|
134
|
+
<Route
|
|
135
|
+
path="/linelist/edit/:reportId"
|
|
136
|
+
element={guard('/linelist/edit/:reportId', <LinelistBuilderWorkspace />)}
|
|
137
|
+
/>
|
|
138
|
+
<Route
|
|
139
|
+
path="/linelist/run/:reportId"
|
|
140
|
+
element={guard('/linelist/run/:reportId', <LinelistRunReport />)}
|
|
141
|
+
/>
|
|
80
142
|
|
|
81
143
|
{/* Import / Export */}
|
|
82
|
-
<Route
|
|
144
|
+
<Route
|
|
145
|
+
path="/import-export"
|
|
146
|
+
element={guard('/import-export', <ImportExportPage />)}
|
|
147
|
+
/>
|
|
83
148
|
|
|
84
149
|
{/* Optional backward compatibility */}
|
|
85
150
|
<Route path="/themes" element={<Navigate to="/admin/themes" replace />} />
|
package/src/routes.json
CHANGED
|
@@ -15,7 +15,8 @@
|
|
|
15
15
|
"slot": "system-admin-page-card-link-slot",
|
|
16
16
|
"component": "reportBuilderAdminLink",
|
|
17
17
|
"online": true,
|
|
18
|
-
"offline": false
|
|
18
|
+
"offline": false,
|
|
19
|
+
"privileges": "Task: reportbuilder.report.view"
|
|
19
20
|
},
|
|
20
21
|
{
|
|
21
22
|
"component": "confirmModal",
|
|
@@ -24,4 +25,4 @@
|
|
|
24
25
|
"offline": true
|
|
25
26
|
}
|
|
26
27
|
]
|
|
27
|
-
}
|
|
28
|
+
}
|