@nightkatana/kronosys-app 1.0.0-beta.2 → 1.0.0-beta.21
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 +28 -1
- package/app/api/action/route.ts +39 -3
- package/app/api/action-logs/route.ts +24 -0
- package/app/api/backup/route.ts +1 -1
- package/app/api/restore/route.ts +145 -0
- package/app/changelog/page.tsx +71 -4
- package/app/globals.css +127 -0
- package/app/guide/page.tsx +61 -15
- package/app/implementation/page.tsx +700 -0
- package/app/layout.tsx +14 -3
- package/app/licenses/page.tsx +99 -37
- package/app/logs/page.tsx +258 -0
- package/app/manifest.ts +5 -5
- package/app/page.tsx +784 -229
- package/app/reporting/page.tsx +1266 -474
- package/app/settings/page.tsx +252 -18
- package/bin/kronosys.mjs +140 -15
- package/components/KronosysPayloadProvider.tsx +2 -0
- package/components/RouteTransition.tsx +18 -0
- package/components/dashboard/AppShellCommandCenterPlaceholder.tsx +17 -0
- package/components/dashboard/AppShellHeaderSessionMeta.tsx +210 -0
- package/components/dashboard/AppShellHeaderWallClock.tsx +54 -0
- package/components/dashboard/AppShellLiveSessionDrawer.tsx +154 -38
- package/components/dashboard/AppShellRouteNav.tsx +323 -48
- package/components/dashboard/DashboardPauseBackdrop.tsx +50 -0
- package/components/dashboard/DashboardSimpleModal.tsx +168 -25
- package/components/dashboard/DashboardTour.tsx +115 -29
- package/components/dashboard/GlobalPauseConfirmModal.tsx +183 -0
- package/components/dashboard/KronosysDatetimePopoverField.tsx +167 -122
- package/components/dashboard/KronosysTimePopoverField.tsx +54 -12
- package/components/dashboard/NewSessionScopeModal.tsx +211 -20
- package/components/dashboard/PlannedTaskBoundaryConflictWatcher.tsx +275 -0
- package/components/dashboard/ReportingTour.tsx +87 -21
- package/components/dashboard/SavedProjectPicker.tsx +16 -3
- package/components/dashboard/SelectedSessionSidebarBlock.tsx +512 -142
- package/components/dashboard/SessionListPanel.tsx +327 -44
- package/components/dashboard/SettingsTagsProjectsSection.tsx +1073 -264
- package/components/dashboard/SettingsTaskTemplatesSection.tsx +316 -0
- package/components/dashboard/SettingsTour.tsx +86 -21
- package/components/dashboard/TagPills.tsx +14 -1
- package/components/dashboard/TaskFocusPanel.tsx +1081 -478
- package/components/dashboard/TaskSessionLiveCard.tsx +650 -135
- package/components/dashboard/TaskTimelineGanttModal.tsx +601 -0
- package/components/dashboard/taskFieldStyles.ts +20 -4
- package/components/dashboard/useReportingInteractionState.ts +80 -0
- package/lib/appShellHeaderClasses.ts +13 -0
- package/lib/businessRulesMatrix.ts +210 -0
- package/lib/copyToClipboard.ts +43 -0
- package/lib/dashboardCopy.ts +494 -84
- package/lib/dashboardQuickSearch.ts +54 -2
- package/lib/dashboardTimeZone.ts +109 -0
- package/lib/formatAppShellWallClock.ts +66 -0
- package/lib/formatSessionNameTemplate.ts +141 -0
- package/lib/generatedUserChangelog.ts +177 -6
- package/lib/globalPausePreview.ts +292 -0
- package/lib/implementationNotes.ts +1188 -0
- package/lib/kronosysApi.ts +6 -0
- package/lib/kronosysDashboardModalGates.ts +24 -0
- package/lib/plannedBoundaryAttention.ts +9 -0
- package/lib/plannedBoundaryConflict.ts +23 -0
- package/lib/reportingAggregate.ts +517 -75
- package/lib/reportingMetricHelp.ts +8 -0
- package/lib/reportingStrings.ts +37 -3
- package/lib/sessionListMerge.ts +4 -0
- package/lib/sessionTaskSidebarStats.ts +182 -21
- package/lib/settingsCopy.ts +178 -4
- package/lib/taskParsing.ts +360 -103
- package/lib/taskTemplateDraft.ts +135 -0
- package/lib/taskTimelineGantt.ts +265 -0
- package/lib/temporalDisplayPlanned.ts +71 -0
- package/lib/userGuideCopy.ts +121 -47
- package/next.config.ts +7 -0
- package/package.json +12 -24
- package/server/actionDispatch.ts +1000 -77
- package/server/actionTaskSession.ts +337 -24
- package/server/db.ts +7 -15
- package/server/dbSchema.ts +24 -0
- package/server/defaultCfg.ts +5 -0
- package/server/gitlabTokenStore.ts +0 -12
- package/server/liveHistorySync.ts +53 -0
- package/server/mainTimerHydrate.ts +38 -2
- package/server/payloadStore.ts +33 -11
- package/server/sessionWallHydrate.ts +66 -3
- package/server/userActionLog.ts +126 -0
- package/sonar-project.properties +11 -0
- package/tsconfig.json +2 -1
- package/components/dashboard/IssuePickerModal.tsx +0 -168
- package/components/dashboard/ThemeToggle.test.tsx +0 -26
- package/lib/backupCsvExport.test.ts +0 -149
- package/lib/dashboardQuickSearchQuery.test.ts +0 -63
- package/lib/dataDir.test.ts +0 -87
- package/lib/formatIsoShort.test.ts +0 -46
- package/lib/kronoFocusRhythm.test.ts +0 -130
- package/lib/kronoFocusTimerUrgency.test.ts +0 -74
- package/lib/legacyKronoFocusStorageKeys.test.ts +0 -29
- package/lib/reportingAggregate.test.ts +0 -325
- package/lib/reportingNonFinalIndicators.test.ts +0 -157
- package/lib/reportingTagWeekBreakdown.test.ts +0 -141
- package/lib/reportingWeekLayout.test.ts +0 -239
- package/lib/sessionAssiduity.test.ts +0 -25
- package/lib/sessionEndWarnings.test.ts +0 -200
- package/lib/sessionListMerge.test.ts +0 -101
- package/lib/sessionTaskSidebarStats.test.ts +0 -24
- package/lib/taskParsing.test.ts +0 -153
- package/lib/usageProfile.test.ts +0 -84
- package/server/actionDispatch.test.ts +0 -723
- package/server/actionTaskSession.test.ts +0 -713
- package/server/kronoFocusHydrate.test.ts +0 -142
- package/server/kronoFocusMigrate.test.ts +0 -53
- package/server/mainTimerHydrate.test.ts +0 -65
- package/server/payloadStore.test.ts +0 -78
- package/server/sessionWallHydrate.test.ts +0 -46
package/app/layout.tsx
CHANGED
|
@@ -7,6 +7,8 @@ import { KronosysPackageVersionProvider } from "@/components/KronosysPackageVers
|
|
|
7
7
|
import { DashboardToastProvider } from "@/components/dashboard/DashboardToastProvider";
|
|
8
8
|
import { KronosysPayloadProvider } from "@/components/KronosysPayloadProvider";
|
|
9
9
|
import { AppShellLiveSessionDrawer } from "@/components/dashboard/AppShellLiveSessionDrawer";
|
|
10
|
+
import { PlannedTaskBoundaryConflictWatcher } from "@/components/dashboard/PlannedTaskBoundaryConflictWatcher";
|
|
11
|
+
import { RouteTransition } from "@/components/RouteTransition";
|
|
10
12
|
import { readKronosysPackageVersion } from "@/lib/readKronosysPackageVersion";
|
|
11
13
|
import { getThemeBootstrapScript } from "@/lib/theme";
|
|
12
14
|
import "./globals.css";
|
|
@@ -27,7 +29,11 @@ export const metadata: Metadata = {
|
|
|
27
29
|
"Kronosys : sessions, tâches, KronoFocus — application web locale (Next.js + SQLite).",
|
|
28
30
|
applicationName: "Kronosys Dashboard",
|
|
29
31
|
icons: {
|
|
30
|
-
icon: [
|
|
32
|
+
icon: [
|
|
33
|
+
{ url: "/icon-512.png", sizes: "512x512", type: "image/png" },
|
|
34
|
+
{ url: "/icon-192.png", sizes: "192x192", type: "image/png" },
|
|
35
|
+
],
|
|
36
|
+
shortcut: [{ url: "/icon-512.png", sizes: "512x512", type: "image/png" }],
|
|
31
37
|
apple: [{ url: "/apple-icon.png", sizes: "180x180", type: "image/png" }],
|
|
32
38
|
},
|
|
33
39
|
appleWebApp: {
|
|
@@ -58,13 +64,18 @@ export default function RootLayout({
|
|
|
58
64
|
suppressHydrationWarning
|
|
59
65
|
>
|
|
60
66
|
<body className="flex min-h-full flex-col font-sans">
|
|
61
|
-
<script
|
|
67
|
+
<script
|
|
68
|
+
dangerouslySetInnerHTML={{ __html: getThemeBootstrapScript() }}
|
|
69
|
+
/>
|
|
62
70
|
<ThemeProvider>
|
|
63
71
|
<KronosysPackageVersionProvider version={kronosysPackageVersion}>
|
|
64
72
|
<DashboardToastProvider>
|
|
65
73
|
<KronosysPayloadProvider>
|
|
74
|
+
<PlannedTaskBoundaryConflictWatcher />
|
|
66
75
|
<PwaRegister />
|
|
67
|
-
<div className="flex min-h-full flex-1 flex-col">
|
|
76
|
+
<div className="flex min-h-full flex-1 flex-col">
|
|
77
|
+
<RouteTransition>{children}</RouteTransition>
|
|
78
|
+
</div>
|
|
68
79
|
<AppShellLiveSessionDrawer />
|
|
69
80
|
<SiteLegalFooter />
|
|
70
81
|
</KronosysPayloadProvider>
|
package/app/licenses/page.tsx
CHANGED
|
@@ -8,8 +8,17 @@ import { ScrollToTopFab } from "@/components/dashboard/ScrollToTopFab";
|
|
|
8
8
|
import { ThemeToggle } from "@/components/dashboard/ThemeToggle";
|
|
9
9
|
import { PageRefreshButton } from "@/components/dashboard/PageRefreshButton";
|
|
10
10
|
import { AppShellRouteNav } from "@/components/dashboard/AppShellRouteNav";
|
|
11
|
+
import { useKronosysPayload } from "@/components/KronosysPayloadProvider";
|
|
11
12
|
import { dashboardStrings, type Lang } from "@/lib/dashboardCopy";
|
|
12
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
appShellHeaderClassName,
|
|
15
|
+
appShellHeaderTitleMetaRowClassName,
|
|
16
|
+
appShellHeaderToolbarClassName,
|
|
17
|
+
} from "@/lib/appShellHeaderClasses";
|
|
18
|
+
import { AppShellCommandCenterPlaceholder } from "@/components/dashboard/AppShellCommandCenterPlaceholder";
|
|
19
|
+
import { AppShellHeaderSessionMeta } from "@/components/dashboard/AppShellHeaderSessionMeta";
|
|
20
|
+
import { AppShellHeaderWallClock } from "@/components/dashboard/AppShellHeaderWallClock";
|
|
21
|
+
import { LanguageMenu } from "@/components/dashboard/LanguageMenu";
|
|
13
22
|
import { reportingNav } from "@/lib/reportingStrings";
|
|
14
23
|
import {
|
|
15
24
|
DASHBOARD_DEV_THIRD_PARTY,
|
|
@@ -23,6 +32,7 @@ import { withDashboardSessionParam } from "@/lib/dashboardSessionNav";
|
|
|
23
32
|
function LicensesBody() {
|
|
24
33
|
const router = useRouter();
|
|
25
34
|
const searchParams = useSearchParams();
|
|
35
|
+
const { payload } = useKronosysPayload();
|
|
26
36
|
const dashboardSessionNavId = searchParams.get("session");
|
|
27
37
|
const lang: LicensesLang = searchParams.get("lang") === "en" ? "en" : "fr";
|
|
28
38
|
const c = useMemo(() => licensesCopy(lang), [lang]);
|
|
@@ -32,7 +42,7 @@ function LicensesBody() {
|
|
|
32
42
|
return (
|
|
33
43
|
<div className="min-h-screen bg-zinc-100 text-zinc-900 dark:bg-zinc-900 dark:text-zinc-100">
|
|
34
44
|
<header className={appShellHeaderClassName}>
|
|
35
|
-
<div className={
|
|
45
|
+
<div className={appShellHeaderTitleMetaRowClassName}>
|
|
36
46
|
<div className="flex min-w-0 flex-col gap-1">
|
|
37
47
|
<div className="flex min-w-0 flex-wrap items-baseline gap-x-2 gap-y-0.5">
|
|
38
48
|
<Link
|
|
@@ -42,7 +52,9 @@ function LicensesBody() {
|
|
|
42
52
|
Kronosys
|
|
43
53
|
</Link>
|
|
44
54
|
<span className="text-zinc-400 dark:text-zinc-600">/</span>
|
|
45
|
-
<span className="text-lg font-medium text-zinc-700 dark:text-zinc-300">
|
|
55
|
+
<span className="text-lg font-medium text-zinc-700 dark:text-zinc-300">
|
|
56
|
+
{c.title}
|
|
57
|
+
</span>
|
|
46
58
|
</div>
|
|
47
59
|
<p className="flex flex-wrap items-center gap-x-2 text-xs font-medium leading-snug text-zinc-500 dark:text-zinc-400">
|
|
48
60
|
<span>{dt.brandTagline}</span>
|
|
@@ -52,14 +64,20 @@ function LicensesBody() {
|
|
|
52
64
|
<AppVersionStamp ariaLabelTemplate={dt.appVersionAriaLabel} />
|
|
53
65
|
</p>
|
|
54
66
|
</div>
|
|
55
|
-
<
|
|
67
|
+
<AppShellHeaderSessionMeta payload={payload} dt={dt} />
|
|
68
|
+
</div>
|
|
69
|
+
<div className="flex w-full justify-end">
|
|
70
|
+
<div className={appShellHeaderToolbarClassName}>
|
|
71
|
+
<AppShellHeaderWallClock lang={lang as Lang} dt={dt} />
|
|
72
|
+
<AppShellCommandCenterPlaceholder />
|
|
56
73
|
<AppShellRouteNav
|
|
57
74
|
current="licenses"
|
|
58
75
|
labels={{ ...nav, licenses: c.title }}
|
|
59
76
|
navAriaLabel={dt.appShellRouteNavAria}
|
|
60
77
|
dashboardSessionId={dashboardSessionNavId}
|
|
78
|
+
reserveGlobalPauseSlot
|
|
61
79
|
/>
|
|
62
|
-
<ThemeToggle lang={lang} />
|
|
80
|
+
<ThemeToggle lang={lang as Lang} />
|
|
63
81
|
<PageRefreshButton
|
|
64
82
|
title={dt.pageRefreshTitle}
|
|
65
83
|
ariaLabel={dt.pageRefreshAriaLabel}
|
|
@@ -72,21 +90,28 @@ function LicensesBody() {
|
|
|
72
90
|
router.refresh();
|
|
73
91
|
}}
|
|
74
92
|
/>
|
|
75
|
-
<
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
93
|
+
<LanguageMenu
|
|
94
|
+
lang={lang as Lang}
|
|
95
|
+
labelEn="English"
|
|
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
|
+
}}
|
|
114
|
+
/>
|
|
90
115
|
</div>
|
|
91
116
|
</div>
|
|
92
117
|
</header>
|
|
@@ -95,13 +120,22 @@ function LicensesBody() {
|
|
|
95
120
|
<p className="text-sm leading-relaxed text-zinc-400">{c.intro}</p>
|
|
96
121
|
|
|
97
122
|
<section className="mt-10" aria-labelledby="lic-kronosys-product">
|
|
98
|
-
<h2
|
|
123
|
+
<h2
|
|
124
|
+
id="lic-kronosys-product"
|
|
125
|
+
className="text-base font-semibold text-zinc-200"
|
|
126
|
+
>
|
|
99
127
|
{c.kronosysSectionTitle}
|
|
100
128
|
</h2>
|
|
101
|
-
<p className="mt-2 text-sm leading-relaxed text-zinc-400">
|
|
102
|
-
|
|
129
|
+
<p className="mt-2 text-sm leading-relaxed text-zinc-400">
|
|
130
|
+
{c.kronosysProduct}
|
|
131
|
+
</p>
|
|
132
|
+
<p className="mt-2 text-sm font-medium text-zinc-300">
|
|
133
|
+
{c.copyrightLine}
|
|
134
|
+
</p>
|
|
103
135
|
|
|
104
|
-
<h3 className="mt-6 text-sm font-semibold uppercase tracking-wide text-zinc-500">
|
|
136
|
+
<h3 className="mt-6 text-sm font-semibold uppercase tracking-wide text-zinc-500">
|
|
137
|
+
{c.mitHeading}
|
|
138
|
+
</h3>
|
|
105
139
|
<pre className="mt-3 overflow-x-auto rounded-lg border border-zinc-800 bg-zinc-900/60 p-4 text-xs leading-relaxed whitespace-pre-wrap text-zinc-300">
|
|
106
140
|
{KRONOSYS_MIT_LICENSE_BODY}
|
|
107
141
|
</pre>
|
|
@@ -115,16 +149,27 @@ function LicensesBody() {
|
|
|
115
149
|
<table className="w-full min-w-[20rem] text-left text-sm">
|
|
116
150
|
<thead className="border-b border-zinc-800 bg-zinc-900/50 text-xs uppercase text-zinc-500">
|
|
117
151
|
<tr>
|
|
118
|
-
<th className="px-3 py-2 font-medium">
|
|
119
|
-
|
|
120
|
-
|
|
152
|
+
<th className="px-3 py-2 font-medium">
|
|
153
|
+
{c.thirdPartyColName}
|
|
154
|
+
</th>
|
|
155
|
+
<th className="px-3 py-2 font-medium">
|
|
156
|
+
{c.thirdPartyColLicense}
|
|
157
|
+
</th>
|
|
158
|
+
<th className="px-3 py-2 font-medium">
|
|
159
|
+
{c.thirdPartyColLink}
|
|
160
|
+
</th>
|
|
121
161
|
</tr>
|
|
122
162
|
</thead>
|
|
123
163
|
<tbody>
|
|
124
164
|
{DASHBOARD_THIRD_PARTY.map((row) => (
|
|
125
|
-
<tr
|
|
165
|
+
<tr
|
|
166
|
+
key={row.name}
|
|
167
|
+
className="border-b border-zinc-800/80 last:border-0"
|
|
168
|
+
>
|
|
126
169
|
<td className="px-3 py-2.5 text-zinc-200">{row.name}</td>
|
|
127
|
-
<td className="px-3 py-2.5 font-mono text-xs text-zinc-400">
|
|
170
|
+
<td className="px-3 py-2.5 font-mono text-xs text-zinc-400">
|
|
171
|
+
{row.license}
|
|
172
|
+
</td>
|
|
128
173
|
<td className="px-3 py-2.5">
|
|
129
174
|
<a
|
|
130
175
|
href={row.url}
|
|
@@ -150,16 +195,27 @@ function LicensesBody() {
|
|
|
150
195
|
<table className="w-full min-w-[20rem] text-left text-sm">
|
|
151
196
|
<thead className="border-b border-zinc-800 bg-zinc-900/50 text-xs uppercase text-zinc-500">
|
|
152
197
|
<tr>
|
|
153
|
-
<th className="px-3 py-2 font-medium">
|
|
154
|
-
|
|
155
|
-
|
|
198
|
+
<th className="px-3 py-2 font-medium">
|
|
199
|
+
{c.thirdPartyColName}
|
|
200
|
+
</th>
|
|
201
|
+
<th className="px-3 py-2 font-medium">
|
|
202
|
+
{c.thirdPartyColLicense}
|
|
203
|
+
</th>
|
|
204
|
+
<th className="px-3 py-2 font-medium">
|
|
205
|
+
{c.thirdPartyColLink}
|
|
206
|
+
</th>
|
|
156
207
|
</tr>
|
|
157
208
|
</thead>
|
|
158
209
|
<tbody>
|
|
159
210
|
{DASHBOARD_DEV_THIRD_PARTY.map((row) => (
|
|
160
|
-
<tr
|
|
211
|
+
<tr
|
|
212
|
+
key={row.name}
|
|
213
|
+
className="border-b border-zinc-800/80 last:border-0"
|
|
214
|
+
>
|
|
161
215
|
<td className="px-3 py-2.5 text-zinc-200">{row.name}</td>
|
|
162
|
-
<td className="px-3 py-2.5 font-mono text-xs text-zinc-400">
|
|
216
|
+
<td className="px-3 py-2.5 font-mono text-xs text-zinc-400">
|
|
217
|
+
{row.license}
|
|
218
|
+
</td>
|
|
163
219
|
<td className="px-3 py-2.5">
|
|
164
220
|
<a
|
|
165
221
|
href={row.url}
|
|
@@ -181,7 +237,9 @@ function LicensesBody() {
|
|
|
181
237
|
<h2 id="lic-fonts" className="text-base font-semibold text-zinc-200">
|
|
182
238
|
{c.fontsSectionTitle}
|
|
183
239
|
</h2>
|
|
184
|
-
<p className="mt-2 text-sm leading-relaxed text-zinc-400">
|
|
240
|
+
<p className="mt-2 text-sm leading-relaxed text-zinc-400">
|
|
241
|
+
{c.fontsBody}
|
|
242
|
+
</p>
|
|
185
243
|
<ul className="mt-3 list-inside list-disc text-sm text-zinc-400">
|
|
186
244
|
<li>
|
|
187
245
|
<a
|
|
@@ -220,13 +278,17 @@ function LicensesBody() {
|
|
|
220
278
|
<h2 id="lic-ext" className="text-base font-semibold text-zinc-200">
|
|
221
279
|
{c.extensionSectionTitle}
|
|
222
280
|
</h2>
|
|
223
|
-
<p className="mt-2 text-sm leading-relaxed text-zinc-400">
|
|
281
|
+
<p className="mt-2 text-sm leading-relaxed text-zinc-400">
|
|
282
|
+
{c.extensionNote}
|
|
283
|
+
</p>
|
|
224
284
|
</section>
|
|
225
285
|
|
|
226
286
|
<p className="mt-8 text-xs text-zinc-600">{c.disclaimer}</p>
|
|
227
287
|
</main>
|
|
228
288
|
|
|
229
|
-
<ScrollToTopFab
|
|
289
|
+
<ScrollToTopFab
|
|
290
|
+
ariaLabel={lang === "fr" ? "Retour en haut de la page" : "Back to top"}
|
|
291
|
+
/>
|
|
230
292
|
</div>
|
|
231
293
|
);
|
|
232
294
|
}
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Suspense, useCallback, useEffect, useMemo, useState } from "react";
|
|
4
|
+
import Link from "next/link";
|
|
5
|
+
import { useSearchParams } from "next/navigation";
|
|
6
|
+
import { Logs } from "lucide-react";
|
|
7
|
+
import { useKronosysPayload } from "@/components/KronosysPayloadProvider";
|
|
8
|
+
import { postKronosysAction } from "@/lib/kronosysApi";
|
|
9
|
+
import { AppVersionStamp } from "@/components/dashboard/AppVersionStamp";
|
|
10
|
+
import {
|
|
11
|
+
appShellHeaderClassName,
|
|
12
|
+
appShellHeaderTitleMetaRowClassName,
|
|
13
|
+
appShellHeaderToolbarClassName,
|
|
14
|
+
} from "@/lib/appShellHeaderClasses";
|
|
15
|
+
import { AppShellCommandCenterPlaceholder } from "@/components/dashboard/AppShellCommandCenterPlaceholder";
|
|
16
|
+
import { AppShellHeaderSessionMeta } from "@/components/dashboard/AppShellHeaderSessionMeta";
|
|
17
|
+
import { AppShellHeaderWallClock } from "@/components/dashboard/AppShellHeaderWallClock";
|
|
18
|
+
import { AppShellRouteNav } from "@/components/dashboard/AppShellRouteNav";
|
|
19
|
+
import { ThemeToggle } from "@/components/dashboard/ThemeToggle";
|
|
20
|
+
import { PageRefreshButton } from "@/components/dashboard/PageRefreshButton";
|
|
21
|
+
import { LanguageMenu } from "@/components/dashboard/LanguageMenu";
|
|
22
|
+
import { dashboardStrings, type Lang } from "@/lib/dashboardCopy";
|
|
23
|
+
import { withDashboardSessionParam } from "@/lib/dashboardSessionNav";
|
|
24
|
+
import { reportingNav, reportingStrings } from "@/lib/reportingStrings";
|
|
25
|
+
|
|
26
|
+
type ActionLogRow = {
|
|
27
|
+
id: number;
|
|
28
|
+
createdAt: string;
|
|
29
|
+
actionType: string;
|
|
30
|
+
ok: boolean;
|
|
31
|
+
sourceIp: string | null;
|
|
32
|
+
userAgent: string | null;
|
|
33
|
+
sessionId: string | null;
|
|
34
|
+
payloadJson: string | null;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
type LiveShape = { language?: string };
|
|
38
|
+
|
|
39
|
+
function LogsContent() {
|
|
40
|
+
const searchParams = useSearchParams();
|
|
41
|
+
const dashboardSessionNavId = searchParams.get("session");
|
|
42
|
+
const { payload, refresh } = useKronosysPayload();
|
|
43
|
+
const live = payload?.current as LiveShape | undefined;
|
|
44
|
+
const lang: Lang = live?.language === "fr" ? "fr" : "en";
|
|
45
|
+
const dt = dashboardStrings(lang);
|
|
46
|
+
const nav = useMemo(() => reportingNav(lang), [lang]);
|
|
47
|
+
const t = reportingStrings(lang);
|
|
48
|
+
|
|
49
|
+
const [logs, setLogs] = useState<ActionLogRow[]>([]);
|
|
50
|
+
const [loading, setLoading] = useState(false);
|
|
51
|
+
const [error, setError] = useState<string | null>(null);
|
|
52
|
+
|
|
53
|
+
const labels = useMemo(
|
|
54
|
+
() =>
|
|
55
|
+
lang === "fr"
|
|
56
|
+
? {
|
|
57
|
+
title: "Journal des actions",
|
|
58
|
+
subtitle: "Historique des actions utilisateur enregistrées côté serveur.",
|
|
59
|
+
refresh: "Rafraîchir",
|
|
60
|
+
empty: "Aucune action enregistrée.",
|
|
61
|
+
action: "Action",
|
|
62
|
+
status: "Statut",
|
|
63
|
+
date: "Date",
|
|
64
|
+
sourceIp: "IP source",
|
|
65
|
+
session: "Session",
|
|
66
|
+
ok: "OK",
|
|
67
|
+
failed: "Échec",
|
|
68
|
+
unknown: "—",
|
|
69
|
+
}
|
|
70
|
+
: {
|
|
71
|
+
title: "Action logs",
|
|
72
|
+
subtitle: "History of user actions recorded on the server.",
|
|
73
|
+
refresh: "Refresh",
|
|
74
|
+
empty: "No action logs available.",
|
|
75
|
+
action: "Action",
|
|
76
|
+
status: "Status",
|
|
77
|
+
date: "Date",
|
|
78
|
+
sourceIp: "Source IP",
|
|
79
|
+
session: "Session",
|
|
80
|
+
ok: "OK",
|
|
81
|
+
failed: "Failed",
|
|
82
|
+
unknown: "—",
|
|
83
|
+
},
|
|
84
|
+
[lang]
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
const loadLogs = useCallback(async () => {
|
|
88
|
+
setLoading(true);
|
|
89
|
+
setError(null);
|
|
90
|
+
try {
|
|
91
|
+
const res = await fetch(withDashboardSessionParam("/api/action-logs?limit=200", dashboardSessionNavId), {
|
|
92
|
+
cache: "no-store",
|
|
93
|
+
});
|
|
94
|
+
if (!res.ok) {
|
|
95
|
+
throw new Error(`HTTP ${res.status}`);
|
|
96
|
+
}
|
|
97
|
+
const body = (await res.json()) as { ok?: boolean; logs?: ActionLogRow[] };
|
|
98
|
+
setLogs(Array.isArray(body.logs) ? body.logs : []);
|
|
99
|
+
} catch (e) {
|
|
100
|
+
setError(e instanceof Error ? e.message : String(e));
|
|
101
|
+
} finally {
|
|
102
|
+
setLoading(false);
|
|
103
|
+
}
|
|
104
|
+
}, [dashboardSessionNavId]);
|
|
105
|
+
|
|
106
|
+
useEffect(() => {
|
|
107
|
+
void loadLogs();
|
|
108
|
+
}, [loadLogs]);
|
|
109
|
+
|
|
110
|
+
const postLang = async (next: Lang) => {
|
|
111
|
+
await postKronosysAction({ type: "setLanguage", lang: next });
|
|
112
|
+
await refresh();
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
return (
|
|
116
|
+
<div className="min-h-screen bg-zinc-100 text-zinc-900 dark:bg-zinc-900 dark:text-zinc-100">
|
|
117
|
+
<header className={appShellHeaderClassName}>
|
|
118
|
+
<div className={appShellHeaderTitleMetaRowClassName}>
|
|
119
|
+
<div className="flex min-w-0 flex-col gap-1">
|
|
120
|
+
<div className="flex min-w-0 flex-wrap items-baseline gap-x-2 gap-y-0.5">
|
|
121
|
+
<Link
|
|
122
|
+
href={withDashboardSessionParam("/", dashboardSessionNavId)}
|
|
123
|
+
className="text-xl font-semibold tracking-tight text-zinc-900 hover:text-violet-700 dark:text-zinc-100 dark:hover:text-violet-300"
|
|
124
|
+
>
|
|
125
|
+
Kronosys
|
|
126
|
+
</Link>
|
|
127
|
+
<span className="text-zinc-400 dark:text-zinc-600" aria-hidden>
|
|
128
|
+
/
|
|
129
|
+
</span>
|
|
130
|
+
<span className="text-lg font-medium text-zinc-700 dark:text-zinc-300">
|
|
131
|
+
{labels.title}
|
|
132
|
+
</span>
|
|
133
|
+
<span
|
|
134
|
+
className="inline-flex items-center text-violet-500 dark:text-violet-400"
|
|
135
|
+
aria-hidden
|
|
136
|
+
>
|
|
137
|
+
<Logs className="size-5" strokeWidth={2} />
|
|
138
|
+
</span>
|
|
139
|
+
</div>
|
|
140
|
+
<p className="flex flex-wrap items-center gap-x-2 text-xs font-medium leading-snug text-zinc-500 dark:text-zinc-400">
|
|
141
|
+
<span>{dt.brandTagline}</span>
|
|
142
|
+
<span className="text-zinc-400/70 dark:text-zinc-600" aria-hidden>
|
|
143
|
+
·
|
|
144
|
+
</span>
|
|
145
|
+
<AppVersionStamp ariaLabelTemplate={dt.appVersionAriaLabel} />
|
|
146
|
+
</p>
|
|
147
|
+
</div>
|
|
148
|
+
<AppShellHeaderSessionMeta payload={payload} dt={dt} />
|
|
149
|
+
</div>
|
|
150
|
+
<div className="flex w-full justify-end">
|
|
151
|
+
<div className={appShellHeaderToolbarClassName}>
|
|
152
|
+
<AppShellHeaderWallClock lang={lang} dt={dt} />
|
|
153
|
+
<AppShellCommandCenterPlaceholder />
|
|
154
|
+
<AppShellRouteNav
|
|
155
|
+
current="logs"
|
|
156
|
+
labels={nav}
|
|
157
|
+
navAriaLabel={dt.appShellRouteNavAria}
|
|
158
|
+
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
|
+
lang={lang}
|
|
177
|
+
labelEn="English"
|
|
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)}
|
|
184
|
+
/>
|
|
185
|
+
</div>
|
|
186
|
+
</div>
|
|
187
|
+
</header>
|
|
188
|
+
|
|
189
|
+
<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">{labels.subtitle}</p>
|
|
191
|
+
<div className="mt-4">
|
|
192
|
+
<button
|
|
193
|
+
type="button"
|
|
194
|
+
onClick={() => void loadLogs()}
|
|
195
|
+
disabled={loading}
|
|
196
|
+
className="rounded-md border border-zinc-300 bg-white px-3 py-1.5 text-sm font-medium text-zinc-700 hover:bg-zinc-50 disabled:cursor-wait disabled:opacity-60 dark:border-zinc-600 dark:bg-zinc-800 dark:text-zinc-200"
|
|
197
|
+
>
|
|
198
|
+
{loading ? t.loading : labels.refresh}
|
|
199
|
+
</button>
|
|
200
|
+
</div>
|
|
201
|
+
|
|
202
|
+
{error ? (
|
|
203
|
+
<p className="mt-4 text-sm text-red-600 dark:text-red-400">{error}</p>
|
|
204
|
+
) : null}
|
|
205
|
+
|
|
206
|
+
<div className="mt-4 overflow-x-auto rounded-xl border border-zinc-200 bg-white dark:border-zinc-700 dark:bg-zinc-800/70">
|
|
207
|
+
<table className="w-full min-w-[900px] text-left text-sm">
|
|
208
|
+
<thead className="bg-zinc-50 dark:bg-zinc-800/90">
|
|
209
|
+
<tr className="text-zinc-600 dark:text-zinc-300">
|
|
210
|
+
<th className="px-3 py-2">{labels.date}</th>
|
|
211
|
+
<th className="px-3 py-2">{labels.action}</th>
|
|
212
|
+
<th className="px-3 py-2">{labels.status}</th>
|
|
213
|
+
<th className="px-3 py-2">{labels.sourceIp}</th>
|
|
214
|
+
<th className="px-3 py-2">{labels.session}</th>
|
|
215
|
+
</tr>
|
|
216
|
+
</thead>
|
|
217
|
+
<tbody>
|
|
218
|
+
{logs.length === 0 ? (
|
|
219
|
+
<tr>
|
|
220
|
+
<td colSpan={5} className="px-3 py-4 text-zinc-500 dark:text-zinc-400">
|
|
221
|
+
{loading ? t.loading : labels.empty}
|
|
222
|
+
</td>
|
|
223
|
+
</tr>
|
|
224
|
+
) : (
|
|
225
|
+
logs.map((row) => (
|
|
226
|
+
<tr
|
|
227
|
+
key={row.id}
|
|
228
|
+
className="border-t border-zinc-200 text-zinc-700 dark:border-zinc-700 dark:text-zinc-200"
|
|
229
|
+
>
|
|
230
|
+
<td className="px-3 py-2">{new Date(row.createdAt).toLocaleString()}</td>
|
|
231
|
+
<td className="px-3 py-2 font-mono text-xs">{row.actionType}</td>
|
|
232
|
+
<td className="px-3 py-2">{row.ok ? labels.ok : labels.failed}</td>
|
|
233
|
+
<td className="px-3 py-2">{row.sourceIp ?? labels.unknown}</td>
|
|
234
|
+
<td className="px-3 py-2 font-mono text-xs">{row.sessionId ?? labels.unknown}</td>
|
|
235
|
+
</tr>
|
|
236
|
+
))
|
|
237
|
+
)}
|
|
238
|
+
</tbody>
|
|
239
|
+
</table>
|
|
240
|
+
</div>
|
|
241
|
+
</main>
|
|
242
|
+
</div>
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export default function LogsPage() {
|
|
247
|
+
return (
|
|
248
|
+
<Suspense
|
|
249
|
+
fallback={
|
|
250
|
+
<div className="min-h-screen bg-zinc-100 px-6 py-10 text-sm text-zinc-500 dark:bg-zinc-900">
|
|
251
|
+
Kronosys…
|
|
252
|
+
</div>
|
|
253
|
+
}
|
|
254
|
+
>
|
|
255
|
+
<LogsContent />
|
|
256
|
+
</Suspense>
|
|
257
|
+
);
|
|
258
|
+
}
|
package/app/manifest.ts
CHANGED
|
@@ -16,14 +16,14 @@ export default function manifest(): MetadataRoute.Manifest {
|
|
|
16
16
|
categories: ["productivity", "developer"],
|
|
17
17
|
icons: [
|
|
18
18
|
{
|
|
19
|
-
src: "/icon-
|
|
20
|
-
sizes: "
|
|
19
|
+
src: "/icon-512.png",
|
|
20
|
+
sizes: "512x512",
|
|
21
21
|
type: "image/png",
|
|
22
|
-
purpose: "
|
|
22
|
+
purpose: "maskable",
|
|
23
23
|
},
|
|
24
24
|
{
|
|
25
|
-
src: "/icon-
|
|
26
|
-
sizes: "
|
|
25
|
+
src: "/icon-192.png",
|
|
26
|
+
sizes: "192x192",
|
|
27
27
|
type: "image/png",
|
|
28
28
|
purpose: "any",
|
|
29
29
|
},
|