rl-core-front 0.18.2 → 0.18.3
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/package.json +1 -1
- package/src/features/logs/hooks/use-request-logs.ts +16 -11
- package/src/features/logs/panels/request-logs-panel.tsx +7 -1
- package/src/features/queues/hooks/use-queue-jobs.ts +19 -2
- package/src/features/rbac/panels/definitions-panel.tsx +10 -1
- package/src/hooks/use-tab-state.ts +21 -11
- package/src/index.ts +1 -1
package/package.json
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
} from "react";
|
|
11
11
|
|
|
12
12
|
import type { FilterValues } from "#core/_utils/filter";
|
|
13
|
+
import { safeStorage } from "#core/_utils/storage";
|
|
13
14
|
import { RequestOutcome } from "#core/features/logs/enums/request-outcome.enum";
|
|
14
15
|
import { logsService, RequestLog } from "#core/features/logs/services/logs.service";
|
|
15
16
|
import type { FiltersState } from "#core/hooks/use-filters";
|
|
@@ -17,15 +18,22 @@ import { useLatestRequest } from "#core/hooks/use-latest-request";
|
|
|
17
18
|
import { useListQuery } from "#core/hooks/use-list-query";
|
|
18
19
|
import { useRequest } from "#core/hooks/use-request";
|
|
19
20
|
|
|
21
|
+
/** Onde o navegador guarda o último desfecho escolhido na barra. */
|
|
22
|
+
export const LOGS_OUTCOME_STORAGE_KEY = "tab:logs-outcome";
|
|
23
|
+
|
|
20
24
|
/**
|
|
21
|
-
* A tela abre no sucesso,
|
|
25
|
+
* A tela abre no último desfecho escolhido — e no sucesso, na primeira vez.
|
|
22
26
|
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
27
|
+
* O desfecho é filtro (§11) e por isso vive na URL, que o F5 e o link já
|
|
28
|
+
* carregam. O que a URL não cobre é voltar pelo menu: aí entra o navegador,
|
|
29
|
+
* como nas abas. Lido uma vez por montagem porque é dependência do efeito de
|
|
30
|
+
* hidratação do filtro, e um literal novo por render o refaria sem parar.
|
|
25
31
|
*/
|
|
26
|
-
const
|
|
27
|
-
outcome: {
|
|
28
|
-
|
|
32
|
+
const defaultFilter = (): FilterValues => ({
|
|
33
|
+
outcome: {
|
|
34
|
+
values: [safeStorage.get(LOGS_OUTCOME_STORAGE_KEY) ?? String(RequestOutcome.SUCCESS)],
|
|
35
|
+
},
|
|
36
|
+
});
|
|
29
37
|
|
|
30
38
|
export interface UseRequestLogsResult {
|
|
31
39
|
rows: RequestLog[];
|
|
@@ -46,11 +54,8 @@ export function useRequestLogs(): UseRequestLogsResult {
|
|
|
46
54
|
const { run, loading } = useRequest();
|
|
47
55
|
|
|
48
56
|
const latest = useLatestRequest();
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
20,
|
|
52
|
-
DEFAULT_FILTER,
|
|
53
|
-
);
|
|
57
|
+
const [initialFilter] = useState(defaultFilter);
|
|
58
|
+
const list = useListQuery("/audit/requests/filter-schema", 20, initialFilter);
|
|
54
59
|
|
|
55
60
|
const [rows, setRows] = useState<RequestLog[]>([]);
|
|
56
61
|
const [total, setTotal] = useState(0);
|
|
@@ -10,6 +10,7 @@ import { Eye } from "lucide-react";
|
|
|
10
10
|
import type { JSX } from "react";
|
|
11
11
|
import { useMemo, useState } from "react";
|
|
12
12
|
|
|
13
|
+
import { safeStorage } from "#core/_utils/storage";
|
|
13
14
|
import {
|
|
14
15
|
Badge,
|
|
15
16
|
Button,
|
|
@@ -29,7 +30,10 @@ import {
|
|
|
29
30
|
ErrorTypeLabelKey,
|
|
30
31
|
} from "#core/features/logs/enums/error-type.enum";
|
|
31
32
|
import { RequestOutcomeLabelKey } from "#core/features/logs/enums/request-outcome.enum";
|
|
32
|
-
import {
|
|
33
|
+
import {
|
|
34
|
+
LOGS_OUTCOME_STORAGE_KEY,
|
|
35
|
+
useRequestLogs,
|
|
36
|
+
} from "#core/features/logs/hooks/use-request-logs";
|
|
33
37
|
import { RequestLog } from "#core/features/logs/services/logs.service";
|
|
34
38
|
|
|
35
39
|
function statusVariant(
|
|
@@ -72,6 +76,8 @@ export function RequestLogsPanel(): JSX.Element {
|
|
|
72
76
|
outcome: { values: [value] },
|
|
73
77
|
});
|
|
74
78
|
logs.setPage(0);
|
|
79
|
+
// O navegador lembra para a próxima visita pelo menu; a URL já leva o resto.
|
|
80
|
+
safeStorage.set(LOGS_OUTCOME_STORAGE_KEY, value);
|
|
75
81
|
};
|
|
76
82
|
|
|
77
83
|
const outcomeOptions = useMemo<SegmentedOption[]>(
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
} from "#core/features/queues/services/queues.service";
|
|
13
13
|
import { useLatestRequest } from "#core/hooks/use-latest-request";
|
|
14
14
|
import { useRequest } from "#core/hooks/use-request";
|
|
15
|
+
import { useTabState } from "#core/hooks/use-tab-state";
|
|
15
16
|
|
|
16
17
|
const EMPTY_COUNTS: QueueCounts = {
|
|
17
18
|
waiting: 0,
|
|
@@ -47,6 +48,9 @@ export interface UseQueueJobsResult {
|
|
|
47
48
|
* Abre em `failed` de propósito: quem entra aqui está atrás do que deu errado.
|
|
48
49
|
* O que está andando bem não precisa de tela.
|
|
49
50
|
*/
|
|
51
|
+
const QUEUE_JOB_STATES = Object.values(QueueJobState);
|
|
52
|
+
const QUEUE_NAMES = Object.values(QueueName);
|
|
53
|
+
|
|
50
54
|
export function useQueueJobs(): UseQueueJobsResult {
|
|
51
55
|
const { run, loading } = useRequest();
|
|
52
56
|
const latest = useLatestRequest();
|
|
@@ -56,8 +60,21 @@ export function useQueueJobs(): UseQueueJobsResult {
|
|
|
56
60
|
const [total, setTotal] = useState(0);
|
|
57
61
|
const [page, setPage] = useState(0);
|
|
58
62
|
const [pageSize, setPageSize] = useState(20);
|
|
59
|
-
|
|
60
|
-
|
|
63
|
+
// Os dois seletores ficam salvos — na URL para o F5 e o link, no navegador
|
|
64
|
+
// para o menu — cada um com o próprio parâmetro, que na mesma tela não
|
|
65
|
+
// podem dividir o `?tab=`.
|
|
66
|
+
const { tab: state, setTab: setStateValue } = useTabState(
|
|
67
|
+
"queues-state",
|
|
68
|
+
QUEUE_JOB_STATES,
|
|
69
|
+
QueueJobState.FAILED,
|
|
70
|
+
{ param: "state" },
|
|
71
|
+
);
|
|
72
|
+
const { tab: queue, setTab: setQueueValue } = useTabState(
|
|
73
|
+
"queues-queue",
|
|
74
|
+
QUEUE_NAMES,
|
|
75
|
+
QueueName.JOBS,
|
|
76
|
+
{ param: "queue" },
|
|
77
|
+
);
|
|
61
78
|
// Sem ordem escolhida, a lista sai na ordem do Redis — que é a mais recente
|
|
62
79
|
// primeiro e não custa varredura nenhuma no backend.
|
|
63
80
|
const [sorting, setSorting] = useState<SortingState>([]);
|
|
@@ -8,6 +8,7 @@ import { ActionsPanel } from "#core/features/rbac/panels/actions-panel";
|
|
|
8
8
|
import { PermissionsPanel } from "#core/features/rbac/panels/permissions-panel";
|
|
9
9
|
import { ResourcesPanel } from "#core/features/rbac/panels/resources-panel";
|
|
10
10
|
import { ScopesPanel } from "#core/features/rbac/panels/scopes-panel";
|
|
11
|
+
import { useTabState } from "#core/hooks/use-tab-state";
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* A mecânica do RBAC — as peças de que um código `resource:action:scope` é
|
|
@@ -49,6 +50,14 @@ export function DefinitionsPanel(): JSX.Element {
|
|
|
49
50
|
},
|
|
50
51
|
].filter((tab) => hasPermission(tab.perm));
|
|
51
52
|
|
|
53
|
+
// `sub`, e não `tab`: este está dentro da aba Definições do RBAC, que já usa `?tab=`.
|
|
54
|
+
const { tab, setTab } = useTabState(
|
|
55
|
+
"rbac-definitions",
|
|
56
|
+
tabs.map((item) => item.value),
|
|
57
|
+
undefined,
|
|
58
|
+
{ param: "sub" },
|
|
59
|
+
);
|
|
60
|
+
|
|
52
61
|
if (tabs.length === 0) {
|
|
53
62
|
return <></>;
|
|
54
63
|
}
|
|
@@ -56,7 +65,7 @@ export function DefinitionsPanel(): JSX.Element {
|
|
|
56
65
|
return (
|
|
57
66
|
<div className="space-y-4">
|
|
58
67
|
<p className="text-sm text-muted-foreground">{t("rbac.definitionsHint")}</p>
|
|
59
|
-
<Tabs
|
|
68
|
+
<Tabs value={tab} onValueChange={setTab}>
|
|
60
69
|
<TabsList>
|
|
61
70
|
{tabs.map((tab) => (
|
|
62
71
|
<TabsTrigger key={tab.value} value={tab.value}>
|
|
@@ -5,7 +5,16 @@ import { useCallback, useLayoutEffect, useRef, useState } from "react";
|
|
|
5
5
|
import { safeStorage } from "#core/_utils/storage";
|
|
6
6
|
|
|
7
7
|
const STORAGE_PREFIX = "tab:";
|
|
8
|
-
const
|
|
8
|
+
const DEFAULT_PARAM = "tab";
|
|
9
|
+
|
|
10
|
+
export interface UseTabStateOptions {
|
|
11
|
+
/**
|
|
12
|
+
* O nome do parâmetro na URL. `tab` por padrão; muda quando a tela tem mais
|
|
13
|
+
* de um seletor (a fila e a situação, em Filas) ou abas dentro de abas
|
|
14
|
+
* (Definições dentro do RBAC) — dois com o mesmo nome se sobrescreveriam.
|
|
15
|
+
*/
|
|
16
|
+
param?: string;
|
|
17
|
+
}
|
|
9
18
|
|
|
10
19
|
export interface UseTabStateResult<T extends string> {
|
|
11
20
|
tab: T;
|
|
@@ -16,13 +25,13 @@ export interface UseTabStateResult<T extends string> {
|
|
|
16
25
|
setTab: (next: string) => void;
|
|
17
26
|
}
|
|
18
27
|
|
|
19
|
-
const readFromUrl = (): string | null =>
|
|
20
|
-
new URLSearchParams(window.location.search).get(
|
|
28
|
+
const readFromUrl = (param: string): string | null =>
|
|
29
|
+
new URLSearchParams(window.location.search).get(param);
|
|
21
30
|
|
|
22
|
-
/** Troca só o
|
|
23
|
-
const writeToUrl = (value: string): void => {
|
|
31
|
+
/** Troca só o próprio parâmetro, preservando o resto — o `?filter=` da listagem mora ao lado. */
|
|
32
|
+
const writeToUrl = (param: string, value: string): void => {
|
|
24
33
|
const params = new URLSearchParams(window.location.search);
|
|
25
|
-
params.set(
|
|
34
|
+
params.set(param, value);
|
|
26
35
|
window.history.replaceState(null, "", `${window.location.pathname}?${params.toString()}`);
|
|
27
36
|
};
|
|
28
37
|
|
|
@@ -50,6 +59,7 @@ export function useTabState<T extends string>(
|
|
|
50
59
|
storageKey: string,
|
|
51
60
|
tabs: readonly T[],
|
|
52
61
|
defaultTab: T = tabs[0],
|
|
62
|
+
{ param = DEFAULT_PARAM }: UseTabStateOptions = {},
|
|
53
63
|
): UseTabStateResult<T> {
|
|
54
64
|
const [tab, setStoredTab] = useState<T>(defaultTab);
|
|
55
65
|
|
|
@@ -74,7 +84,7 @@ export function useTabState<T extends string>(
|
|
|
74
84
|
// O de layout corre antes de o navegador desenhar — a primeira imagem já é
|
|
75
85
|
// a aba salva. No servidor o React o ignora, que é o que se quer.
|
|
76
86
|
useLayoutEffect(() => {
|
|
77
|
-
const fromUrl = readFromUrl();
|
|
87
|
+
const fromUrl = readFromUrl(param);
|
|
78
88
|
const fromStorage = safeStorage.get(`${STORAGE_PREFIX}${storageKey}`);
|
|
79
89
|
const resolved = isTab(fromUrl) ? fromUrl : isTab(fromStorage) ? fromStorage : defaultTab;
|
|
80
90
|
|
|
@@ -84,9 +94,9 @@ export function useTabState<T extends string>(
|
|
|
84
94
|
// isso, copiar o link depois de cair na aba do storage levaria à padrão.
|
|
85
95
|
// Lista vazia (nenhuma aba permitida) não tem o que dizer.
|
|
86
96
|
if (resolved !== undefined && fromUrl !== resolved) {
|
|
87
|
-
writeToUrl(resolved);
|
|
97
|
+
writeToUrl(param, resolved);
|
|
88
98
|
}
|
|
89
|
-
}, [storageKey, defaultTab, isTab]);
|
|
99
|
+
}, [storageKey, defaultTab, isTab, param]);
|
|
90
100
|
|
|
91
101
|
const setTab = useCallback(
|
|
92
102
|
(next: string): void => {
|
|
@@ -95,10 +105,10 @@ export function useTabState<T extends string>(
|
|
|
95
105
|
}
|
|
96
106
|
|
|
97
107
|
setStoredTab(next);
|
|
98
|
-
writeToUrl(next);
|
|
108
|
+
writeToUrl(param, next);
|
|
99
109
|
safeStorage.set(`${STORAGE_PREFIX}${storageKey}`, next);
|
|
100
110
|
},
|
|
101
|
-
[storageKey, isTab],
|
|
111
|
+
[storageKey, isTab, param],
|
|
102
112
|
);
|
|
103
113
|
|
|
104
114
|
return { tab, setTab };
|
package/src/index.ts
CHANGED
|
@@ -121,7 +121,7 @@ export {
|
|
|
121
121
|
TAB_SHORTCUT_DIGITS,
|
|
122
122
|
useShortcut,
|
|
123
123
|
} from "#core/hooks/use-shortcut";
|
|
124
|
-
export type { UseTabStateResult } from "#core/hooks/use-tab-state";
|
|
124
|
+
export type { UseTabStateOptions, UseTabStateResult } from "#core/hooks/use-tab-state";
|
|
125
125
|
export { useTabState } from "#core/hooks/use-tab-state";
|
|
126
126
|
export type { TableView, UseTableViewResult } from "#core/hooks/use-table-view";
|
|
127
127
|
export { useTableView } from "#core/hooks/use-table-view";
|