@nightkatana/kronosys-app 1.0.0-beta.21 → 1.0.0-beta.22
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/README.md +1 -1
- package/app/changelog/page.tsx +87 -19
- package/app/globals.css +10 -8
- package/app/guide/page.tsx +71 -34
- package/app/implementation/page.tsx +70 -60
- package/app/licenses/page.tsx +79 -47
- package/app/logs/page.tsx +103 -47
- package/app/page.tsx +104 -169
- package/app/reporting/page.tsx +1918 -1436
- package/app/settings/page.tsx +66 -44
- package/components/KronosysPayloadProvider.tsx +19 -5
- package/components/dashboard/AppShellHeaderKronoFocus.tsx +78 -0
- package/components/dashboard/AppShellHeaderToolbarLayout.tsx +36 -0
- package/components/dashboard/AppShellHeaderUtilityRibbon.tsx +19 -0
- package/components/dashboard/AppShellHeaderWallClock.tsx +23 -17
- package/components/dashboard/AppShellRouteNav.tsx +336 -209
- package/components/dashboard/AppShellToolbarCommandCenter.tsx +225 -0
- package/components/dashboard/AppShellToolbarRouteNav.tsx +204 -0
- package/components/dashboard/DashboardCommandCenter.tsx +119 -30
- package/components/dashboard/KronoFocusPanel.tsx +287 -260
- package/components/dashboard/LanguageMenu.tsx +23 -7
- package/components/dashboard/PageRefreshButton.tsx +42 -16
- package/components/dashboard/ReportingTour.tsx +20 -2
- package/components/dashboard/SessionListPanel.tsx +4 -4
- package/components/dashboard/ThemeToggle.tsx +4 -3
- package/components/dashboard/useAnchoredFloatingPortalStyle.ts +9 -2
- package/components/dashboard/useKronoFocusLiveSeconds.ts +4 -2
- package/lib/appShellHeaderClasses.ts +22 -3
- package/lib/appShellToolbarChrome.ts +112 -0
- package/lib/appShellToolbarDeferredIntents.ts +112 -0
- package/lib/appShellToolbarSessionSlices.ts +67 -0
- package/lib/dashboardCopy.ts +78 -29
- package/lib/dashboardQuickSearch.ts +37 -6
- package/lib/dashboardUrlSession.ts +36 -0
- package/lib/generatedUserChangelog.ts +14 -0
- package/lib/implementationNotes.ts +18 -14
- package/lib/reportingAggregate.ts +68 -9
- package/lib/reportingMetricHelp.ts +8 -8
- package/lib/reportingStrings.ts +118 -9
- package/lib/reportingTagWeekBreakdown.ts +55 -13
- package/lib/settingsCopy.ts +6 -7
- package/lib/userGuideCopy.ts +29 -26
- package/package.json +7 -5
- package/server/db.ts +6 -4
- package/server/dbSchema.ts +2 -2
- package/components/dashboard/AppShellCommandCenterPlaceholder.tsx +0 -17
package/app/licenses/page.tsx
CHANGED
|
@@ -1,23 +1,26 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { Suspense, useMemo } from "react";
|
|
3
|
+
import { Suspense, useCallback, useMemo } from "react";
|
|
4
4
|
import Link from "next/link";
|
|
5
5
|
import { useRouter, useSearchParams } from "next/navigation";
|
|
6
6
|
import { AppVersionStamp } from "@/components/dashboard/AppVersionStamp";
|
|
7
7
|
import { ScrollToTopFab } from "@/components/dashboard/ScrollToTopFab";
|
|
8
8
|
import { ThemeToggle } from "@/components/dashboard/ThemeToggle";
|
|
9
9
|
import { PageRefreshButton } from "@/components/dashboard/PageRefreshButton";
|
|
10
|
-
import {
|
|
10
|
+
import { AppShellToolbarRouteNav } from "@/components/dashboard/AppShellToolbarRouteNav";
|
|
11
11
|
import { useKronosysPayload } from "@/components/KronosysPayloadProvider";
|
|
12
|
+
import { postKronosysAction } from "@/lib/kronosysApi";
|
|
12
13
|
import { dashboardStrings, type Lang } from "@/lib/dashboardCopy";
|
|
13
14
|
import {
|
|
14
15
|
appShellHeaderClassName,
|
|
15
16
|
appShellHeaderTitleMetaRowClassName,
|
|
16
|
-
appShellHeaderToolbarClassName,
|
|
17
17
|
} from "@/lib/appShellHeaderClasses";
|
|
18
|
-
import {
|
|
18
|
+
import { AppShellHeaderToolbarLayout } from "@/components/dashboard/AppShellHeaderToolbarLayout";
|
|
19
|
+
import { AppShellToolbarCommandCenter } from "@/components/dashboard/AppShellToolbarCommandCenter";
|
|
19
20
|
import { AppShellHeaderSessionMeta } from "@/components/dashboard/AppShellHeaderSessionMeta";
|
|
20
21
|
import { AppShellHeaderWallClock } from "@/components/dashboard/AppShellHeaderWallClock";
|
|
22
|
+
import { AppShellHeaderKronoFocus } from "@/components/dashboard/AppShellHeaderKronoFocus";
|
|
23
|
+
import { AppShellHeaderUtilityRibbon } from "@/components/dashboard/AppShellHeaderUtilityRibbon";
|
|
21
24
|
import { LanguageMenu } from "@/components/dashboard/LanguageMenu";
|
|
22
25
|
import { reportingNav } from "@/lib/reportingStrings";
|
|
23
26
|
import {
|
|
@@ -32,13 +35,21 @@ import { withDashboardSessionParam } from "@/lib/dashboardSessionNav";
|
|
|
32
35
|
function LicensesBody() {
|
|
33
36
|
const router = useRouter();
|
|
34
37
|
const searchParams = useSearchParams();
|
|
35
|
-
const { payload } = useKronosysPayload();
|
|
38
|
+
const { payload, refresh } = useKronosysPayload();
|
|
36
39
|
const dashboardSessionNavId = searchParams.get("session");
|
|
37
40
|
const lang: LicensesLang = searchParams.get("lang") === "en" ? "en" : "fr";
|
|
38
41
|
const c = useMemo(() => licensesCopy(lang), [lang]);
|
|
39
42
|
const nav = reportingNav(lang as Lang);
|
|
40
43
|
const dt = dashboardStrings(lang as Lang);
|
|
41
44
|
|
|
45
|
+
const postHeaderAction = useCallback(
|
|
46
|
+
async (body: Record<string, unknown>) => {
|
|
47
|
+
await postKronosysAction(body);
|
|
48
|
+
await refresh({ routerInvalidate: true });
|
|
49
|
+
},
|
|
50
|
+
[refresh],
|
|
51
|
+
);
|
|
52
|
+
|
|
42
53
|
return (
|
|
43
54
|
<div className="min-h-screen bg-zinc-100 text-zinc-900 dark:bg-zinc-900 dark:text-zinc-100">
|
|
44
55
|
<header className={appShellHeaderClassName}>
|
|
@@ -66,54 +77,75 @@ function LicensesBody() {
|
|
|
66
77
|
</div>
|
|
67
78
|
<AppShellHeaderSessionMeta payload={payload} dt={dt} />
|
|
68
79
|
</div>
|
|
69
|
-
<
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
80
|
+
<AppShellHeaderToolbarLayout
|
|
81
|
+
leading={
|
|
82
|
+
<>
|
|
83
|
+
<AppShellHeaderWallClock lang={lang as Lang} dt={dt} />
|
|
84
|
+
<AppShellHeaderKronoFocus
|
|
85
|
+
payload={payload}
|
|
86
|
+
dt={dt}
|
|
87
|
+
post={postHeaderAction}
|
|
88
|
+
/>
|
|
89
|
+
<AppShellToolbarCommandCenter
|
|
90
|
+
dt={dt}
|
|
91
|
+
lang={lang as Lang}
|
|
92
|
+
dashboardSessionNavId={dashboardSessionNavId}
|
|
93
|
+
onManualRefresh={() => void refresh({ routerInvalidate: true })}
|
|
94
|
+
/>
|
|
95
|
+
</>
|
|
96
|
+
}
|
|
97
|
+
nav={
|
|
98
|
+
<AppShellToolbarRouteNav
|
|
74
99
|
current="licenses"
|
|
75
100
|
labels={{ ...nav, licenses: c.title }}
|
|
76
101
|
navAriaLabel={dt.appShellRouteNavAria}
|
|
77
102
|
dashboardSessionId={dashboardSessionNavId}
|
|
78
|
-
reserveGlobalPauseSlot
|
|
79
|
-
/>
|
|
80
|
-
<ThemeToggle lang={lang as Lang} />
|
|
81
|
-
<PageRefreshButton
|
|
82
|
-
title={dt.pageRefreshTitle}
|
|
83
|
-
ariaLabel={dt.pageRefreshAriaLabel}
|
|
84
|
-
inlineMessages={{
|
|
85
|
-
loading: dt.pageRefreshProgressLabel,
|
|
86
|
-
success: dt.pageRefreshDoneToast,
|
|
87
|
-
error: dt.pageRefreshFailedToast,
|
|
88
|
-
}}
|
|
89
|
-
onRefresh={() => {
|
|
90
|
-
router.refresh();
|
|
91
|
-
}}
|
|
92
|
-
/>
|
|
93
|
-
<LanguageMenu
|
|
94
103
|
lang={lang as Lang}
|
|
95
|
-
|
|
96
|
-
labelFr="Français"
|
|
97
|
-
menuHeading={lang === "fr" ? "Langue" : "Language"}
|
|
98
|
-
triggerAriaLabel={
|
|
99
|
-
lang === "fr" ? "Langue de l’interface" : "Interface language"
|
|
100
|
-
}
|
|
101
|
-
onSelect={(next) => {
|
|
102
|
-
const path =
|
|
103
|
-
next === "en"
|
|
104
|
-
? withDashboardSessionParam(
|
|
105
|
-
"/licenses?lang=en",
|
|
106
|
-
dashboardSessionNavId,
|
|
107
|
-
)
|
|
108
|
-
: withDashboardSessionParam(
|
|
109
|
-
"/licenses",
|
|
110
|
-
dashboardSessionNavId,
|
|
111
|
-
);
|
|
112
|
-
router.push(path);
|
|
113
|
-
}}
|
|
104
|
+
dt={dt}
|
|
114
105
|
/>
|
|
115
|
-
|
|
116
|
-
|
|
106
|
+
}
|
|
107
|
+
trailing={
|
|
108
|
+
<AppShellHeaderUtilityRibbon
|
|
109
|
+
ariaLabel={dt.appShellUtilityToolbarGroupAria}
|
|
110
|
+
>
|
|
111
|
+
<ThemeToggle lang={lang as Lang} />
|
|
112
|
+
<PageRefreshButton
|
|
113
|
+
title={dt.pageRefreshTitle}
|
|
114
|
+
ariaLabel={dt.pageRefreshAriaLabel}
|
|
115
|
+
inlineMessages={{
|
|
116
|
+
loading: dt.pageRefreshProgressLabel,
|
|
117
|
+
success: dt.pageRefreshDoneToast,
|
|
118
|
+
error: dt.pageRefreshFailedToast,
|
|
119
|
+
}}
|
|
120
|
+
onRefresh={() => {
|
|
121
|
+
router.refresh();
|
|
122
|
+
}}
|
|
123
|
+
/>
|
|
124
|
+
<LanguageMenu
|
|
125
|
+
lang={lang as Lang}
|
|
126
|
+
labelEn="English"
|
|
127
|
+
labelFr="Français"
|
|
128
|
+
menuHeading={lang === "fr" ? "Langue" : "Language"}
|
|
129
|
+
triggerAriaLabel={
|
|
130
|
+
lang === "fr" ? "Langue de l’interface" : "Interface language"
|
|
131
|
+
}
|
|
132
|
+
onSelect={(next) => {
|
|
133
|
+
const path =
|
|
134
|
+
next === "en"
|
|
135
|
+
? withDashboardSessionParam(
|
|
136
|
+
"/licenses?lang=en",
|
|
137
|
+
dashboardSessionNavId,
|
|
138
|
+
)
|
|
139
|
+
: withDashboardSessionParam(
|
|
140
|
+
"/licenses",
|
|
141
|
+
dashboardSessionNavId,
|
|
142
|
+
);
|
|
143
|
+
router.push(path);
|
|
144
|
+
}}
|
|
145
|
+
/>
|
|
146
|
+
</AppShellHeaderUtilityRibbon>
|
|
147
|
+
}
|
|
148
|
+
/>
|
|
117
149
|
</header>
|
|
118
150
|
|
|
119
151
|
<main className="mx-auto max-w-3xl px-5 py-8 pb-16 sm:px-8 lg:px-10">
|
package/app/logs/page.tsx
CHANGED
|
@@ -10,12 +10,14 @@ import { AppVersionStamp } from "@/components/dashboard/AppVersionStamp";
|
|
|
10
10
|
import {
|
|
11
11
|
appShellHeaderClassName,
|
|
12
12
|
appShellHeaderTitleMetaRowClassName,
|
|
13
|
-
appShellHeaderToolbarClassName,
|
|
14
13
|
} from "@/lib/appShellHeaderClasses";
|
|
15
|
-
import {
|
|
14
|
+
import { AppShellHeaderToolbarLayout } from "@/components/dashboard/AppShellHeaderToolbarLayout";
|
|
15
|
+
import { AppShellToolbarCommandCenter } from "@/components/dashboard/AppShellToolbarCommandCenter";
|
|
16
16
|
import { AppShellHeaderSessionMeta } from "@/components/dashboard/AppShellHeaderSessionMeta";
|
|
17
17
|
import { AppShellHeaderWallClock } from "@/components/dashboard/AppShellHeaderWallClock";
|
|
18
|
-
import {
|
|
18
|
+
import { AppShellHeaderKronoFocus } from "@/components/dashboard/AppShellHeaderKronoFocus";
|
|
19
|
+
import { AppShellHeaderUtilityRibbon } from "@/components/dashboard/AppShellHeaderUtilityRibbon";
|
|
20
|
+
import { AppShellToolbarRouteNav } from "@/components/dashboard/AppShellToolbarRouteNav";
|
|
19
21
|
import { ThemeToggle } from "@/components/dashboard/ThemeToggle";
|
|
20
22
|
import { PageRefreshButton } from "@/components/dashboard/PageRefreshButton";
|
|
21
23
|
import { LanguageMenu } from "@/components/dashboard/LanguageMenu";
|
|
@@ -55,7 +57,8 @@ function LogsContent() {
|
|
|
55
57
|
lang === "fr"
|
|
56
58
|
? {
|
|
57
59
|
title: "Journal des actions",
|
|
58
|
-
subtitle:
|
|
60
|
+
subtitle:
|
|
61
|
+
"Historique des actions utilisateur enregistrées côté serveur.",
|
|
59
62
|
refresh: "Rafraîchir",
|
|
60
63
|
empty: "Aucune action enregistrée.",
|
|
61
64
|
action: "Action",
|
|
@@ -81,20 +84,37 @@ function LogsContent() {
|
|
|
81
84
|
failed: "Failed",
|
|
82
85
|
unknown: "—",
|
|
83
86
|
},
|
|
84
|
-
[lang]
|
|
87
|
+
[lang],
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
const postHeaderAction = useCallback(
|
|
91
|
+
async (body: Record<string, unknown>) => {
|
|
92
|
+
await postKronosysAction(body);
|
|
93
|
+
await refresh({ routerInvalidate: true });
|
|
94
|
+
},
|
|
95
|
+
[refresh],
|
|
85
96
|
);
|
|
86
97
|
|
|
87
98
|
const loadLogs = useCallback(async () => {
|
|
88
99
|
setLoading(true);
|
|
89
100
|
setError(null);
|
|
90
101
|
try {
|
|
91
|
-
const res = await fetch(
|
|
92
|
-
|
|
93
|
-
|
|
102
|
+
const res = await fetch(
|
|
103
|
+
withDashboardSessionParam(
|
|
104
|
+
"/api/action-logs?limit=200",
|
|
105
|
+
dashboardSessionNavId,
|
|
106
|
+
),
|
|
107
|
+
{
|
|
108
|
+
cache: "no-store",
|
|
109
|
+
},
|
|
110
|
+
);
|
|
94
111
|
if (!res.ok) {
|
|
95
112
|
throw new Error(`HTTP ${res.status}`);
|
|
96
113
|
}
|
|
97
|
-
const body = (await res.json()) as {
|
|
114
|
+
const body = (await res.json()) as {
|
|
115
|
+
ok?: boolean;
|
|
116
|
+
logs?: ActionLogRow[];
|
|
117
|
+
};
|
|
98
118
|
setLogs(Array.isArray(body.logs) ? body.logs : []);
|
|
99
119
|
} catch (e) {
|
|
100
120
|
setError(e instanceof Error ? e.message : String(e));
|
|
@@ -147,47 +167,70 @@ function LogsContent() {
|
|
|
147
167
|
</div>
|
|
148
168
|
<AppShellHeaderSessionMeta payload={payload} dt={dt} />
|
|
149
169
|
</div>
|
|
150
|
-
<
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
170
|
+
<AppShellHeaderToolbarLayout
|
|
171
|
+
leading={
|
|
172
|
+
<>
|
|
173
|
+
<AppShellHeaderWallClock lang={lang} dt={dt} />
|
|
174
|
+
<AppShellHeaderKronoFocus
|
|
175
|
+
payload={payload}
|
|
176
|
+
dt={dt}
|
|
177
|
+
post={postHeaderAction}
|
|
178
|
+
/>
|
|
179
|
+
<AppShellToolbarCommandCenter
|
|
180
|
+
dt={dt}
|
|
181
|
+
lang={lang}
|
|
182
|
+
dashboardSessionNavId={dashboardSessionNavId}
|
|
183
|
+
onManualRefresh={() => refresh({ routerInvalidate: true })}
|
|
184
|
+
/>
|
|
185
|
+
</>
|
|
186
|
+
}
|
|
187
|
+
nav={
|
|
188
|
+
<AppShellToolbarRouteNav
|
|
155
189
|
current="logs"
|
|
156
190
|
labels={nav}
|
|
157
191
|
navAriaLabel={dt.appShellRouteNavAria}
|
|
158
192
|
dashboardSessionId={dashboardSessionNavId}
|
|
159
|
-
reserveGlobalPauseSlot
|
|
160
|
-
/>
|
|
161
|
-
<ThemeToggle lang={lang} />
|
|
162
|
-
<PageRefreshButton
|
|
163
|
-
title={dt.pageRefreshTitle}
|
|
164
|
-
ariaLabel={dt.pageRefreshAriaLabel}
|
|
165
|
-
inlineMessages={{
|
|
166
|
-
loading: dt.pageRefreshProgressLabel,
|
|
167
|
-
success: dt.pageRefreshDoneToast,
|
|
168
|
-
error: dt.pageRefreshFailedToast,
|
|
169
|
-
}}
|
|
170
|
-
onRefresh={async () => {
|
|
171
|
-
await loadLogs();
|
|
172
|
-
return await refresh({ routerInvalidate: true });
|
|
173
|
-
}}
|
|
174
|
-
/>
|
|
175
|
-
<LanguageMenu
|
|
176
193
|
lang={lang}
|
|
177
|
-
|
|
178
|
-
labelFr="Français"
|
|
179
|
-
menuHeading={lang === "fr" ? "Langue" : "Language"}
|
|
180
|
-
triggerAriaLabel={
|
|
181
|
-
lang === "fr" ? "Langue de l’interface" : "Interface language"
|
|
182
|
-
}
|
|
183
|
-
onSelect={(next) => void postLang(next)}
|
|
194
|
+
dt={dt}
|
|
184
195
|
/>
|
|
185
|
-
|
|
186
|
-
|
|
196
|
+
}
|
|
197
|
+
trailing={
|
|
198
|
+
<AppShellHeaderUtilityRibbon
|
|
199
|
+
ariaLabel={dt.appShellUtilityToolbarGroupAria}
|
|
200
|
+
>
|
|
201
|
+
<ThemeToggle lang={lang} />
|
|
202
|
+
<PageRefreshButton
|
|
203
|
+
title={dt.pageRefreshTitle}
|
|
204
|
+
ariaLabel={dt.pageRefreshAriaLabel}
|
|
205
|
+
inlineMessages={{
|
|
206
|
+
loading: dt.pageRefreshProgressLabel,
|
|
207
|
+
success: dt.pageRefreshDoneToast,
|
|
208
|
+
error: dt.pageRefreshFailedToast,
|
|
209
|
+
}}
|
|
210
|
+
onRefresh={async () => {
|
|
211
|
+
await loadLogs();
|
|
212
|
+
return await refresh({ routerInvalidate: true });
|
|
213
|
+
}}
|
|
214
|
+
/>
|
|
215
|
+
<LanguageMenu
|
|
216
|
+
lang={lang}
|
|
217
|
+
labelEn="English"
|
|
218
|
+
labelFr="Français"
|
|
219
|
+
menuHeading={lang === "fr" ? "Langue" : "Language"}
|
|
220
|
+
triggerAriaLabel={
|
|
221
|
+
lang === "fr" ? "Langue de l’interface" : "Interface language"
|
|
222
|
+
}
|
|
223
|
+
onSelect={(next) => void postLang(next)}
|
|
224
|
+
/>
|
|
225
|
+
</AppShellHeaderUtilityRibbon>
|
|
226
|
+
}
|
|
227
|
+
/>
|
|
187
228
|
</header>
|
|
188
229
|
|
|
189
230
|
<main className="mx-auto w-full max-w-[1200px] px-5 pb-16 pt-6 sm:px-8 lg:px-10">
|
|
190
|
-
<p className="text-sm text-zinc-600 dark:text-zinc-400">
|
|
231
|
+
<p className="text-sm text-zinc-600 dark:text-zinc-400">
|
|
232
|
+
{labels.subtitle}
|
|
233
|
+
</p>
|
|
191
234
|
<div className="mt-4">
|
|
192
235
|
<button
|
|
193
236
|
type="button"
|
|
@@ -217,7 +260,10 @@ function LogsContent() {
|
|
|
217
260
|
<tbody>
|
|
218
261
|
{logs.length === 0 ? (
|
|
219
262
|
<tr>
|
|
220
|
-
<td
|
|
263
|
+
<td
|
|
264
|
+
colSpan={5}
|
|
265
|
+
className="px-3 py-4 text-zinc-500 dark:text-zinc-400"
|
|
266
|
+
>
|
|
221
267
|
{loading ? t.loading : labels.empty}
|
|
222
268
|
</td>
|
|
223
269
|
</tr>
|
|
@@ -227,11 +273,21 @@ function LogsContent() {
|
|
|
227
273
|
key={row.id}
|
|
228
274
|
className="border-t border-zinc-200 text-zinc-700 dark:border-zinc-700 dark:text-zinc-200"
|
|
229
275
|
>
|
|
230
|
-
<td className="px-3 py-2">
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
<td className="px-3 py-2
|
|
234
|
-
|
|
276
|
+
<td className="px-3 py-2">
|
|
277
|
+
{new Date(row.createdAt).toLocaleString()}
|
|
278
|
+
</td>
|
|
279
|
+
<td className="px-3 py-2 font-mono text-xs">
|
|
280
|
+
{row.actionType}
|
|
281
|
+
</td>
|
|
282
|
+
<td className="px-3 py-2">
|
|
283
|
+
{row.ok ? labels.ok : labels.failed}
|
|
284
|
+
</td>
|
|
285
|
+
<td className="px-3 py-2">
|
|
286
|
+
{row.sourceIp ?? labels.unknown}
|
|
287
|
+
</td>
|
|
288
|
+
<td className="px-3 py-2 font-mono text-xs">
|
|
289
|
+
{row.sessionId ?? labels.unknown}
|
|
290
|
+
</td>
|
|
235
291
|
</tr>
|
|
236
292
|
))
|
|
237
293
|
)}
|