This is an automated email from the ASF dual-hosted git repository. piotr pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/iggy.git
The following commit(s) were added to refs/heads/master by this push: new 8b1d8dd3 feat(web): enhance ui interactions and improve code quality (#2078) 8b1d8dd3 is described below commit 8b1d8dd3e34a5998be47cc3eea5f4f25c315e997 Author: Piotr Ziółko <50527113+piotrzio...@users.noreply.github.com> AuthorDate: Mon Aug 11 09:30:52 2025 +0200 feat(web): enhance ui interactions and improve code quality (#2078) This PR enhances the web interface with improved keyboard navigation and modal interactions, strengthens type safety by migrating from Svelte's on:click to onclick handlers, fixes message count calculation in partition view, adds the missing max_topic_size field to topic schema, and removes unused imports for cleaner code organization. --- web/package-lock.json | 527 +++++++++++++++++++++ web/src/lib/actions/tooltip.ts | 2 +- web/src/lib/api/ApiSchema.ts | 1 + web/src/lib/components/Button.svelte | 49 +- web/src/lib/components/Checkbox.svelte | 5 +- web/src/lib/components/Combobox.svelte | 3 +- .../components/DeleteButtonWithConfirmation.svelte | 4 +- .../components/DropdownMenu/DropdownMenu.svelte | 2 +- web/src/lib/components/Input.svelte | 62 --- .../lib/components/Layouts/SettingsLayout.svelte | 2 +- .../MessageDecoder/MessageDecoder.svelte | 6 +- web/src/lib/components/ModalConfirmation.svelte | 18 +- .../components/Modals/AddPartitionsModal.svelte | 4 +- .../lib/components/Modals/AddStreamModal.svelte | 5 +- web/src/lib/components/Modals/AddTopicModal.svelte | 5 +- web/src/lib/components/Modals/AddUserModal.svelte | 4 +- web/src/lib/components/Modals/AppModals.svelte | 24 +- .../components/Modals/DeletePartitionsModal.svelte | 8 +- .../lib/components/Modals/DeleteUserModal.svelte | 2 +- web/src/lib/components/Modals/EditUserModal.svelte | 2 +- .../Modals/EditUserPermissionsModal.svelte | 2 +- web/src/lib/components/Modals/ModalBase.svelte | 3 +- .../components/Modals/StreamSettingsModal.svelte | 10 +- .../components/Modals/TopicSettingsModal.svelte | 23 +- web/src/lib/components/Paginator.svelte | 10 +- web/src/lib/components/PasswordInput.svelte | 5 +- web/src/lib/components/PeriodicInvalidator.svelte | 15 +- web/src/lib/components/PermissionsManager.svelte | 2 +- web/src/lib/components/RangeInput.svelte | 4 - .../RouteComponents/Settings/UsersTab.svelte | 8 +- .../Settings/UsersTabActions.svelte | 7 +- web/src/lib/components/Select.svelte | 2 +- web/src/lib/components/StopPropagation.svelte | 11 + web/src/lib/components/ThemeController.svelte | 4 +- web/src/lib/components/Toggler.svelte | 3 +- web/src/lib/domain/Partition.ts | 2 +- web/src/lib/domain/Permissions.ts | 1 - web/src/lib/domain/Stream.ts | 2 +- web/src/lib/queries.ts | 5 - .../lib/utils/{dataHas.ts => constants/keys.ts} | 83 ++-- web/src/lib/utils/dataHas.ts | 2 - web/src/lib/utils/formatters/bytesFormatter.ts | 2 - web/src/lib/utils/formatters/durationFormatter.ts | 1 - web/src/routes/+error.svelte | 2 - web/src/routes/auth/sign-in/+page.svelte | 7 +- web/src/routes/dashboard/overview/+page.svelte | 2 +- .../routes/dashboard/settings/server/+page.svelte | 4 - .../routes/dashboard/settings/users/+page.svelte | 10 +- .../routes/dashboard/settings/webUI/+page.svelte | 2 +- web/src/routes/dashboard/streams/+layout.svelte | 7 +- .../dashboard/streams/[streamId=i32]/+page.svelte | 167 +++---- .../topics/[topicId=i32]/+page.svelte | 176 +++---- .../[partitionId=i32]/messages/+page.server.ts | 6 +- .../[partitionId=i32]/messages/+page.svelte | 228 ++++----- 54 files changed, 1029 insertions(+), 524 deletions(-) diff --git a/web/package-lock.json b/web/package-lock.json index 6548368d..042db8e2 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -101,6 +101,474 @@ "node": ">=6.9.0" } }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.8.tgz", + "integrity": "sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.8.tgz", + "integrity": "sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.8.tgz", + "integrity": "sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.8.tgz", + "integrity": "sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.8.tgz", + "integrity": "sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.8.tgz", + "integrity": "sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.8.tgz", + "integrity": "sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.8.tgz", + "integrity": "sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.8.tgz", + "integrity": "sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.8.tgz", + "integrity": "sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.8.tgz", + "integrity": "sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.8.tgz", + "integrity": "sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.8.tgz", + "integrity": "sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.8.tgz", + "integrity": "sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.8.tgz", + "integrity": "sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.8.tgz", + "integrity": "sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.8.tgz", + "integrity": "sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.8.tgz", + "integrity": "sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.8.tgz", + "integrity": "sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.8.tgz", + "integrity": "sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.8.tgz", + "integrity": "sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.8.tgz", + "integrity": "sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.8.tgz", + "integrity": "sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.8.tgz", + "integrity": "sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.8.tgz", + "integrity": "sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.8.tgz", + "integrity": "sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", @@ -1801,6 +2269,50 @@ "dev": true, "license": "MIT" }, + "node_modules/esbuild": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.8.tgz", + "integrity": "sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "peer": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.8", + "@esbuild/android-arm": "0.25.8", + "@esbuild/android-arm64": "0.25.8", + "@esbuild/android-x64": "0.25.8", + "@esbuild/darwin-arm64": "0.25.8", + "@esbuild/darwin-x64": "0.25.8", + "@esbuild/freebsd-arm64": "0.25.8", + "@esbuild/freebsd-x64": "0.25.8", + "@esbuild/linux-arm": "0.25.8", + "@esbuild/linux-arm64": "0.25.8", + "@esbuild/linux-ia32": "0.25.8", + "@esbuild/linux-loong64": "0.25.8", + "@esbuild/linux-mips64el": "0.25.8", + "@esbuild/linux-ppc64": "0.25.8", + "@esbuild/linux-riscv64": "0.25.8", + "@esbuild/linux-s390x": "0.25.8", + "@esbuild/linux-x64": "0.25.8", + "@esbuild/netbsd-arm64": "0.25.8", + "@esbuild/netbsd-x64": "0.25.8", + "@esbuild/openbsd-arm64": "0.25.8", + "@esbuild/openbsd-x64": "0.25.8", + "@esbuild/openharmony-arm64": "0.25.8", + "@esbuild/sunos-x64": "0.25.8", + "@esbuild/win32-arm64": "0.25.8", + "@esbuild/win32-ia32": "0.25.8", + "@esbuild/win32-x64": "0.25.8" + } + }, "node_modules/esbuild-runner": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/esbuild-runner/-/esbuild-runner-2.2.2.tgz", @@ -4041,6 +4553,21 @@ } } }, + "node_modules/svelte-check/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/svelte-eslint-parser": { "version": "0.43.0", "resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-0.43.0.tgz", diff --git a/web/src/lib/actions/tooltip.ts b/web/src/lib/actions/tooltip.ts index a10b590d..1c180679 100644 --- a/web/src/lib/actions/tooltip.ts +++ b/web/src/lib/actions/tooltip.ts @@ -51,7 +51,7 @@ export function tooltip( let cleanup: VoidFunction | undefined; const unsub = openId.subscribe((val) => (val === id ? showTooltip() : hideTooltip())); - const openTooltip = () => openId.set(id); + const closeTooltip = () => openId.set(null); const toggleOpen = () => openId.update((val) => (val === id ? null : id)); diff --git a/web/src/lib/api/ApiSchema.ts b/web/src/lib/api/ApiSchema.ts index 0c041803..b553a77f 100644 --- a/web/src/lib/api/ApiSchema.ts +++ b/web/src/lib/api/ApiSchema.ts @@ -137,6 +137,7 @@ type Topics = name: string; message_expiry: number; compression_algorithm: number; + max_topic_size: number; }; } | { diff --git a/web/src/lib/components/Button.svelte b/web/src/lib/components/Button.svelte index b6be20b5..ba20233a 100644 --- a/web/src/lib/components/Button.svelte +++ b/web/src/lib/components/Button.svelte @@ -1,6 +1,3 @@ -<!-- @migration-task Error while migrating Svelte code: This migration would change the name of a slot making the component unusable --> -<!-- @migration-task Error while migrating Svelte code: This migration would change the name of a slot making the component unusable --> -<!-- // TODO: check it - Error while migrating Svelte code: This migration would change the name of a slot making the component unusable --> <script lang="ts"> import { tooltip } from '$lib/actions/tooltip'; import type { Placement } from '@floating-ui/dom'; @@ -33,49 +30,39 @@ xl: 'px-7 py-4 text-lg' }; - interface $$Props extends HTMLButtonAttributes { + interface Props extends HTMLButtonAttributes { variant: keyof typeof variants; tooltipPlacement?: Placement; size?: keyof typeof sizes; class?: string; + children?: import('svelte').Snippet; + tooltip?: import('svelte').Snippet; } - export let variant: keyof typeof variants; - export let tooltipPlacement: Placement = 'right'; - export let size: keyof typeof sizes = 'md'; - let className = ''; - export { className as class }; + let { + variant, + tooltipPlacement = 'right', + size = 'md', + class: className = '', + children, + tooltip: tooltipSnippet, + onclick, + ...restProps + }: Props = $props(); </script> -<!-- <div use:tooltip={{ placement: tooltipPlacement }} class="w-full"> - <button - on:click - data-trigger - class={twMerge(baseClasses, disabledClasses, variants[variant], sizes[size], className)} - {...$$restProps} - > - <slot /> - </button> - - {#if $$slots.tooltip} - <div role="tooltip" class="tooltip"> - <slot name="tooltip" /> - </div> - {/if} -</div> --> - <button - on:click + {onclick} data-trigger use:tooltip={{ placement: tooltipPlacement, isTrigger: true }} class={twMerge(baseClasses, variants[variant], sizes[size], disabledClasses, className, ' ')} - {...$$restProps} + {...restProps} > - <slot /> + {@render children?.()} - {#if $$slots.tooltip} + {#if tooltipSnippet} <div role="tooltip" class="tooltip ring-bl"> - <slot name="tooltip" /> + {@render tooltipSnippet()} </div> {/if} </button> diff --git a/web/src/lib/components/Checkbox.svelte b/web/src/lib/components/Checkbox.svelte index cd74c48b..8d8410c8 100644 --- a/web/src/lib/components/Checkbox.svelte +++ b/web/src/lib/components/Checkbox.svelte @@ -12,6 +12,7 @@ name?: string | undefined; bindGroup?: string[] | undefined; disabled?: boolean; + onclick?: (e: Event) => void; } let { @@ -20,7 +21,8 @@ id = '', name = undefined, bindGroup = $bindable(undefined), - disabled = false + disabled = false, + onclick }: Props = $props(); function onChange(e: Event) { @@ -47,6 +49,7 @@ {id} {disabled} onchange={handlers(onChange, bubble('change'))} + onclick={onclick} onmousedown={() => (isMouseDown = true)} onmouseup={() => (isMouseDown = false)} onmouseleave={() => (isMouseDown = false)} diff --git a/web/src/lib/components/Combobox.svelte b/web/src/lib/components/Combobox.svelte index 1f7c66de..dcc43d43 100644 --- a/web/src/lib/components/Combobox.svelte +++ b/web/src/lib/components/Combobox.svelte @@ -67,7 +67,7 @@ <div class="flex flex-col gap-2"> {#if label} - <label class="text-sm ml-1 text-color"> + <label for="combobox-input" class="text-sm ml-1 text-color"> {label} </label> {/if} @@ -77,6 +77,7 @@ class="rounded-md dark:bg-shadeD400 ring-1 text-color ring-gray-300 dark:ring-gray-500 flex items-center h-[40px] text-color relative focus-within:ring-2 focus-within:ring-gray-400 transition group" > <input + id="combobox-input" use:combobox.input onselect={(e) => { selectedValue = noTypeCheck(e).detail.selected; diff --git a/web/src/lib/components/DeleteButtonWithConfirmation.svelte b/web/src/lib/components/DeleteButtonWithConfirmation.svelte index 24dc237e..82ad7cc0 100644 --- a/web/src/lib/components/DeleteButtonWithConfirmation.svelte +++ b/web/src/lib/components/DeleteButtonWithConfirmation.svelte @@ -31,13 +31,13 @@ </script> <div bind:this={wrapperRef} use:tooltip={{ placement: 'top' }}> - <Button variant="containedRed" on:click={() => (isTooltipOpen = true)}>Delete</Button> + <Button variant="containedRed" onclick={() => (isTooltipOpen = true)}>Delete</Button> <div class="tooltip"> <div class="flex flex-col gap-4 items-center justify-center p-2"> <span>Are you sure? </span> <div class="flex flex-row gap-2"> - <Button variant="text" type="button" on:click={() => (isTooltipOpen = false)} size="sm" + <Button variant="text" type="button" onclick={() => (isTooltipOpen = false)} size="sm" >No</Button > <Button diff --git a/web/src/lib/components/DropdownMenu/DropdownMenu.svelte b/web/src/lib/components/DropdownMenu/DropdownMenu.svelte index f2688f87..456c337c 100644 --- a/web/src/lib/components/DropdownMenu/DropdownMenu.svelte +++ b/web/src/lib/components/DropdownMenu/DropdownMenu.svelte @@ -36,7 +36,7 @@ <div class="px-1 py-1"> {#each group as { action, icon, className, label }} <button - on:click={() => { + onclick={() => { if (action) { action(() => tooltipRef.dispatchEvent(new Event('closeTooltip'))); } diff --git a/web/src/lib/components/Input.svelte b/web/src/lib/components/Input.svelte index b8cbb303..cf484e6b 100644 --- a/web/src/lib/components/Input.svelte +++ b/web/src/lib/components/Input.svelte @@ -1,65 +1,3 @@ -<!-- <script lang="ts"> - import type { HTMLInputAttributes, HTMLInputTypeAttribute } from 'svelte/elements'; - import { twMerge } from 'tailwind-merge'; - import type { iconType } from './Icon.svelte'; - import Icon from './Icon.svelte'; - - interface $$Props extends HTMLInputAttributes { - name: string; - id?: string; - label?: string; - value: string | number; - errorMessage?: string; - type?: HTMLInputTypeAttribute; - } - - export let name: string; - export let id: string = crypto.randomUUID(); - export let leadingIcon: iconType | undefined = undefined; - export let label: string | undefined = undefined; - export let value: string | number; - export let type: HTMLInputTypeAttribute = 'text'; -</script> - -<div class="flex flex-col gap-1"> - {#if label} - <label for={id} class="text-sm ml-1 text-color"> - {label} - </label> - {/if} - - <div - class={twMerge( - 'rounded-md border border-gray-300 dark:border-none outline-none dark:bg-shadeD400 text-color px-3 w-full relative', - error && 'border-red-600 text-red-600 border-none outline-red-600', - leadingIcon && 'pl-8', - $$restProps.class - )} - > - {#if leadingIcon} - <Icon - name={leadingIcon} - className="absolute text-gray-400 left-2 top-1/2 -translate-y-1/2 w-[18px]" - /> - {/if} - - <input - bind:value - {...$$restProps} - autocomplete="off" - {id} - {name} - class="w-full h-full bg-transparent py-2 outline-none" - /> - </div> - - {#if errorMe} - <span class="text-red-600 text-xs ml-1 mt-1"> - {error} - </span> - {/if} -</div> --> - <script lang="ts"> import { createBubbler } from 'svelte/legacy'; diff --git a/web/src/lib/components/Layouts/SettingsLayout.svelte b/web/src/lib/components/Layouts/SettingsLayout.svelte index af7b6c31..d5cd5b44 100644 --- a/web/src/lib/components/Layouts/SettingsLayout.svelte +++ b/web/src/lib/components/Layouts/SettingsLayout.svelte @@ -51,7 +51,7 @@ </div> <div class="flex gap-12 border-b px-10"> - {#each tabs as { icon, name, tab, href }, idx (idx)} + {#each tabs as { icon, name, href }, idx (idx)} {@const isActive = activeTab === href.split('/').slice(-1)[0]} <a {href} diff --git a/web/src/lib/components/MessageDecoder/MessageDecoder.svelte b/web/src/lib/components/MessageDecoder/MessageDecoder.svelte index a1d26c65..4754c70f 100644 --- a/web/src/lib/components/MessageDecoder/MessageDecoder.svelte +++ b/web/src/lib/components/MessageDecoder/MessageDecoder.svelte @@ -80,7 +80,7 @@ /> </div> {/if} - <Button variant="contained" on:click={decode}>Decode</Button> + <Button variant="contained" onclick={decode}>Decode</Button> {:else} {#if decodeState.status === 'error'} <div class="bg-red-100 dark:bg-red-900 p-4 rounded-md w-full"> @@ -92,7 +92,7 @@ {#if decodeState.status === 'success'} <div class="flex items-center gap-3"> <p class="font-bold">Codec: {selectedDecoder}</p> - <Button variant="text" on:click={() => (decodeState = { status: 'idle', forceChangeDecoder: true })}>Change decoder</Button> + <Button variant="text" onclick={() => (decodeState = { status: 'idle', forceChangeDecoder: true })}>Change decoder</Button> </div> <div class="w-full mt-2"> <pre class="bg-gray-100 dark:bg-gray-800 p-1 rounded-md overflow-auto"><code @@ -102,7 +102,7 @@ {/if} <div class="mt-4 mb-2"> - <Button variant="text" on:click={() => (showRaw = !showRaw)}> + <Button variant="text" onclick={() => (showRaw = !showRaw)}> {showRaw ? 'Hide' : 'Show'} raw payload (base64) </Button> </div> diff --git a/web/src/lib/components/ModalConfirmation.svelte b/web/src/lib/components/ModalConfirmation.svelte index 05a8d97b..8eeb06b9 100644 --- a/web/src/lib/components/ModalConfirmation.svelte +++ b/web/src/lib/components/ModalConfirmation.svelte @@ -4,6 +4,7 @@ import { createEventDispatcher } from 'svelte'; import Input from './Input.svelte'; import Icon from './Icon.svelte'; + import { Keys } from '$lib/utils/constants/keys'; interface Props { open: boolean; @@ -32,6 +33,15 @@ <div transition:fade={{ duration: 100 }} onclick={() => dispatch('result', false)} + onkeydown={(e) => { + if (e.key === Keys.ENTER || e.key === Keys.SPACE) { + e.preventDefault(); + dispatch('result', false); + } + }} + role="button" + tabindex="0" + aria-label="Close confirmation dialog" class="absolute z-40 backdrop-blur-xs rounded-2xl inset-3" ></div> <div @@ -41,7 +51,7 @@ <div class="p-5 pt-10 flex flex-col items-center border-b relative text-color"> <Button variant="rounded" - on:click={() => dispatch('result', false)} + onclick={() => dispatch('result', false)} class="absolute top-3 right-3 p-2" > <Icon name="close" strokeWidth={2.3} /> @@ -63,7 +73,7 @@ <div class="w-full"> <Button variant="containedRed" - on:click={() => dispatch('result', true)} + onclick={() => dispatch('result', true)} disabled={retypedText !== retypeText} class="w-full">{deleteButtonTitle}</Button > @@ -73,8 +83,8 @@ {/if} <!-- <div class="flex gap-2 p-5 py-10 w-full"> - <Button variant="containedRed" class="flex-1" on:click={() => dispatch('result', true)} + <Button variant="containedRed" class="flex-1" onclick={() => dispatch('result', true)} >Yes</Button > - <Button variant="text" class="flex-1" on:click={() => dispatch('result', false)}>No</Button> + <Button variant="text" class="flex-1" onclick={() => dispatch('result', false)}>No</Button> </div> --> diff --git a/web/src/lib/components/Modals/AddPartitionsModal.svelte b/web/src/lib/components/Modals/AddPartitionsModal.svelte index a887cccb..fd470d78 100644 --- a/web/src/lib/components/Modals/AddPartitionsModal.svelte +++ b/web/src/lib/components/Modals/AddPartitionsModal.svelte @@ -9,7 +9,6 @@ import { fetchRouteApi } from '$lib/api/fetchRouteApi'; import { page } from '$app/state'; import { dataHas } from '$lib/utils/dataHas'; - import { invalidateAll } from '$app/navigation'; import { showToast } from '../AppToasts.svelte'; import Button from '../Button.svelte'; import { customInvalidateAll } from '../PeriodicInvalidator.svelte'; @@ -31,6 +30,7 @@ taintedMessage: false, async onUpdate({ form }) { if (!form.valid) return; + if (!page.params.streamId || !page.params.topicId) return; const { data, ok } = await fetchRouteApi({ method: 'POST', @@ -72,7 +72,7 @@ /> <div class="flex justify-end gap-3 mt-auto"> - <Button variant="text" type="button" class="w-2/5" on:click={() => closeModal()} + <Button variant="text" type="button" class="w-2/5" onclick={() => closeModal()} >Cancel</Button > <Button type="submit" variant="contained" class="w-2/5">Create</Button> diff --git a/web/src/lib/components/Modals/AddStreamModal.svelte b/web/src/lib/components/Modals/AddStreamModal.svelte index 709d68f3..3c82ed5a 100644 --- a/web/src/lib/components/Modals/AddStreamModal.svelte +++ b/web/src/lib/components/Modals/AddStreamModal.svelte @@ -9,7 +9,6 @@ import { showToast } from '../AppToasts.svelte'; import { fetchRouteApi } from '$lib/api/fetchRouteApi'; import { dataHas } from '$lib/utils/dataHas'; - import { invalidateAll } from '$app/navigation'; import { numberSizes } from '$lib/utils/constants/numberSizes'; import { customInvalidateAll } from '../PeriodicInvalidator.svelte'; @@ -28,7 +27,7 @@ .max(255, 'Name must not exceed 255 characters') }); - const { form, errors, enhance, constraints, submitting, reset } = superForm( + const { form, errors, enhance, constraints, submitting } = superForm( defaults(zod(schema)), { SPA: true, @@ -84,7 +83,7 @@ /> <div class="flex justify-end gap-3 mt-auto w-full"> - <Button type="button" variant="text" class="w-2/5" on:click={() => closeModal()} + <Button type="button" variant="text" class="w-2/5" onclick={() => closeModal()} >Cancel</Button > <Button type="submit" variant="contained" class="w-2/5" disabled={$submitting}>Create</Button> diff --git a/web/src/lib/components/Modals/AddTopicModal.svelte b/web/src/lib/components/Modals/AddTopicModal.svelte index 0717d32e..30cf4220 100644 --- a/web/src/lib/components/Modals/AddTopicModal.svelte +++ b/web/src/lib/components/Modals/AddTopicModal.svelte @@ -9,11 +9,9 @@ import type { CloseModalFn } from '$lib/types/utilTypes'; import type { StreamDetails } from '$lib/domain/StreamDetails'; import { fetchRouteApi } from '$lib/api/fetchRouteApi'; - import { intervalToDuration } from 'date-fns'; import { durationFormatter } from '$lib/utils/formatters/durationFormatter'; import { numberSizes } from '$lib/utils/constants/numberSizes'; import { dataHas } from '$lib/utils/dataHas'; - import { invalidateAll } from '$app/navigation'; import { showToast } from '../AppToasts.svelte'; import { customInvalidateAll } from '../PeriodicInvalidator.svelte'; @@ -48,6 +46,7 @@ method: 'POST', path: `/streams/${streamDetails.id}/topics`, body: { + stream_id: streamDetails.id, topic_id: form.data.topic_id, name: form.data.name, partitions_count: form.data.partitions_count, @@ -140,7 +139,7 @@ /> <div class="flex justify-end gap-3 mt-auto"> - <Button variant="text" type="button" class="w-2/5" on:click={() => closeModal()} + <Button variant="text" type="button" class="w-2/5" onclick={() => closeModal()} >Cancel</Button > <Button type="submit" variant="contained" class="w-2/5">Create</Button> diff --git a/web/src/lib/components/Modals/AddUserModal.svelte b/web/src/lib/components/Modals/AddUserModal.svelte index 8c37d711..ed3725b0 100644 --- a/web/src/lib/components/Modals/AddUserModal.svelte +++ b/web/src/lib/components/Modals/AddUserModal.svelte @@ -92,7 +92,7 @@ { name: 'Inactive', value: 'inactive' } ]} selectedValue={$form.status} - on:selectedValue={(e) => $form.status = e.detail} + on:selectedValue={(e) => $form.status = e.detail as 'active' | 'inactive'} /> </div> @@ -102,7 +102,7 @@ /> <div class="flex justify-end gap-3 mt-16 w-[350px] ml-auto"> - <Button variant="text" type="button" class="w-2/5" on:click={() => closeModal()}> + <Button variant="text" type="button" class="w-2/5" onclick={() => closeModal()}> Cancel </Button> <Button type="submit" variant="contained" class="w-2/5">Add</Button> diff --git a/web/src/lib/components/Modals/AppModals.svelte b/web/src/lib/components/Modals/AppModals.svelte index 043e97d9..c40e3549 100644 --- a/web/src/lib/components/Modals/AppModals.svelte +++ b/web/src/lib/components/Modals/AppModals.svelte @@ -1,5 +1,5 @@ <script lang="ts" module> - import type { ComponentProps, ComponentType, SvelteComponent } from 'svelte'; + import type { ComponentProps } from 'svelte'; import AddPartitionsModal from './AddPartitionsModal.svelte'; import AddStreamModal from './AddStreamModal.svelte'; import AddTopicModal from './AddTopicModal.svelte'; @@ -14,6 +14,7 @@ import { fade } from 'svelte/transition'; import { noTypeCheck } from '$lib/utils/noTypeCheck'; import { writable } from 'svelte/store'; + import { Keys } from '$lib/utils/constants/keys'; const modals = { AddPartitionsModal, @@ -31,7 +32,7 @@ type DistributiveOmit<T, K extends string> = T extends T ? Omit<T, K> : never; type AllModals = keyof typeof modals; - type ModalProps<T extends AllModals> = ComponentProps<InstanceType<(typeof modals)[T]>>; + type ModalProps<T extends AllModals> = ComponentProps<(typeof modals)[T]>; type ExtraProps<T extends AllModals> = DistributiveOmit<ModalProps<T>, 'closeModal'>; const openedModal = writable< @@ -68,14 +69,27 @@ <svelte:window onkeydown={(e) => { - if (e.key === 'Escape' && $openedModal) { - $openedModal.props.closeModal(); + if (e.key === Keys.ESCAPE && $openedModal) { + $openedModal?.props.closeModal(); } }} /> {#if $openedModal} - <div transition:fade={{ duration: 100 }} class="fixed inset-0 bg-black/40 z-[500]" onclick={$openedModal.props.closeModal} role="button" tabindex={1}></div> + <div + transition:fade={{ duration: 100 }} + class="fixed inset-0 bg-black/40 z-[500]" + onclick={() => $openedModal.props.closeModal()} + onkeydown={(e) => { + if (e.key === Keys.ENTER || e.key === Keys.SPACE) { + e.preventDefault(); + $openedModal?.props.closeModal(); + } + }} + role="button" + tabindex="0" + aria-label="Close modal" + ></div> {@const SvelteComponent_1 = noTypeCheck(modals[$openedModal.modal])} <SvelteComponent_1 {...$openedModal.props} diff --git a/web/src/lib/components/Modals/DeletePartitionsModal.svelte b/web/src/lib/components/Modals/DeletePartitionsModal.svelte index 484a1ca6..0d976ccb 100644 --- a/web/src/lib/components/Modals/DeletePartitionsModal.svelte +++ b/web/src/lib/components/Modals/DeletePartitionsModal.svelte @@ -11,7 +11,6 @@ import ModalConfirmation from '../ModalConfirmation.svelte'; import { fetchRouteApi } from '$lib/api/fetchRouteApi'; import { page } from '$app/state'; - import { invalidateAll } from '$app/navigation'; import { showToast } from '../AppToasts.svelte'; import { dataHas } from '$lib/utils/dataHas'; import { customInvalidateAll } from '../PeriodicInvalidator.svelte'; @@ -41,7 +40,7 @@ partitions_count: z.coerce.number().min(1).max(topic.partitionsCount).default(1) }); - const { form, errors, enhance, constraints, submitting, validateForm } = superForm( + const { form, errors, enhance, constraints, validateForm } = superForm( defaults(zod(schema)), { SPA: true, @@ -49,6 +48,7 @@ async onUpdate({ form }) { if (!form.valid) return; + if (!page.params.streamId || !page.params.topicId) return; const { data, ok } = await fetchRouteApi({ method: 'DELETE', @@ -111,14 +111,14 @@ /> <div class="flex justify-end gap-3 mt-auto"> - <Button type="button" variant="text" class="w-2/5" on:click={() => closeModal()} + <Button type="button" variant="text" class="w-2/5" onclick={() => closeModal()} >Cancel</Button > <Button type="button" variant="containedRed" class="w-2/5" - on:click={async () => { + onclick={async () => { const result = await validateForm(); if (result.valid) confirmationOpen = true; }}>Delete</Button diff --git a/web/src/lib/components/Modals/DeleteUserModal.svelte b/web/src/lib/components/Modals/DeleteUserModal.svelte index ec6c5dc1..0ed1a5f5 100644 --- a/web/src/lib/components/Modals/DeleteUserModal.svelte +++ b/web/src/lib/components/Modals/DeleteUserModal.svelte @@ -13,7 +13,7 @@ <ModalBase {closeModal} title="Delete user"> <div class="h-[300px] flex flex-col"> <!-- <div class="flex justify-end gap-3 mt-auto"> - <Button variant="text" class="w-2/5" on:click={closeModal}>Cancel</Button> + <Button variant="text" class="w-2/5" onclick={closeModal}>Cancel</Button> <Button type="submit" variant="contained" class="w-2/5">Save</Button> </div> --> </div> diff --git a/web/src/lib/components/Modals/EditUserModal.svelte b/web/src/lib/components/Modals/EditUserModal.svelte index 17fe4d56..4af756fb 100644 --- a/web/src/lib/components/Modals/EditUserModal.svelte +++ b/web/src/lib/components/Modals/EditUserModal.svelte @@ -13,7 +13,7 @@ <ModalBase {closeModal} title="Edit user"> <div class="h-[300px] flex flex-col"> <!-- <div class="flex justify-end gap-3 mt-auto"> - <Button variant="text" class="w-2/5" on:click={closeModal}>Cancel</Button> + <Button variant="text" class="w-2/5" onclick={closeModal}>Cancel</Button> <Button type="submit" variant="contained" class="w-2/5">Save</Button> </div> --> </div> diff --git a/web/src/lib/components/Modals/EditUserPermissionsModal.svelte b/web/src/lib/components/Modals/EditUserPermissionsModal.svelte index 7d511fb8..da437642 100644 --- a/web/src/lib/components/Modals/EditUserPermissionsModal.svelte +++ b/web/src/lib/components/Modals/EditUserPermissionsModal.svelte @@ -13,7 +13,7 @@ <ModalBase {closeModal} title="Edit user permissions"> <div class="h-[300px] flex flex-col"> <!-- <div class="flex justify-end gap-3 mt-auto"> - <Button variant="text" class="w-2/5" on:click={closeModal}>Cancel</Button> + <Button variant="text" class="w-2/5" onclick={closeModal}>Cancel</Button> <Button type="submit" variant="contained" class="w-2/5">Save</Button> </div> --> </div> diff --git a/web/src/lib/components/Modals/ModalBase.svelte b/web/src/lib/components/Modals/ModalBase.svelte index b07a845b..ef302b82 100644 --- a/web/src/lib/components/Modals/ModalBase.svelte +++ b/web/src/lib/components/Modals/ModalBase.svelte @@ -1,5 +1,4 @@ <script lang="ts"> - import type { HTMLAttributes } from 'svelte/elements'; import { twMerge } from 'tailwind-merge'; import Icon from '../Icon.svelte'; import type { TransitionConfig } from 'svelte/transition'; @@ -54,7 +53,7 @@ )} > <div class="h-[15%]"> - <Button variant="rounded" on:click={() => closeModal()} class="absolute p-2 top-5 right-5"> + <Button variant="rounded" onclick={() => closeModal()} class="absolute p-2 top-5 right-5"> <Icon name="close" strokeWidth={2.3} /> </Button> diff --git a/web/src/lib/components/Modals/StreamSettingsModal.svelte b/web/src/lib/components/Modals/StreamSettingsModal.svelte index 26da1276..4d620df8 100644 --- a/web/src/lib/components/Modals/StreamSettingsModal.svelte +++ b/web/src/lib/components/Modals/StreamSettingsModal.svelte @@ -11,7 +11,7 @@ import { zod } from 'sveltekit-superforms/adapters'; import { fetchRouteApi } from '$lib/api/fetchRouteApi'; import { dataHas } from '$lib/utils/dataHas'; - import { goto, invalidateAll } from '$app/navigation'; + import { goto } from '$app/navigation'; import { showToast } from '../AppToasts.svelte'; import ModalConfirmation from '../ModalConfirmation.svelte'; import { typedRoute } from '$lib/types/appRoutes'; @@ -36,7 +36,7 @@ .default(stream.name) }); - const { form, errors, enhance, constraints, submitting, reset, tainted } = superForm( + const { form, errors, enhance, submitting, tainted } = superForm( defaults(zod(schema)), { SPA: true, @@ -77,7 +77,7 @@ confirmationOpen = false; if (result) { - const { data, ok } = await fetchRouteApi({ + const { ok } = await fetchRouteApi({ method: 'DELETE', path: `/streams/${stream.id}` }); @@ -129,7 +129,7 @@ /> <div class="flex justify-end gap-3 w-full mt-auto"> - <Button type="button" variant="text" class="w-2/5" on:click={() => closeModal()} + <Button type="button" variant="text" class="w-2/5" onclick={() => closeModal()} >Cancel</Button > @@ -150,7 +150,7 @@ <Button variant="containedRed" class="max-h-[36px]" - on:click={() => (confirmationOpen = true)} + onclick={() => (confirmationOpen = true)} > <Icon name="trash" class="w-[20px] -ml-1" /> Delete</Button diff --git a/web/src/lib/components/Modals/TopicSettingsModal.svelte b/web/src/lib/components/Modals/TopicSettingsModal.svelte index 69248580..3172416c 100644 --- a/web/src/lib/components/Modals/TopicSettingsModal.svelte +++ b/web/src/lib/components/Modals/TopicSettingsModal.svelte @@ -1,6 +1,6 @@ <script lang="ts"> - import type { StreamDetails } from '$lib/domain/StreamDetails'; import type { CloseModalFn } from '$lib/types/utilTypes'; + import type { TopicDetails } from '$lib/domain/TopicDetails'; import { z } from 'zod'; import Button from '../Button.svelte'; @@ -11,15 +11,13 @@ import { zod } from 'sveltekit-superforms/adapters'; import { fetchRouteApi } from '$lib/api/fetchRouteApi'; import { dataHas } from '$lib/utils/dataHas'; - import { goto, invalidateAll } from '$app/navigation'; + import { goto } from '$app/navigation'; import { showToast } from '../AppToasts.svelte'; import ModalConfirmation from '../ModalConfirmation.svelte'; - import { typedRoute } from '$lib/types/appRoutes'; import { browser } from '$app/environment'; - import type { TopicDetails } from '$lib/domain/TopicDetails'; + import { customInvalidateAll } from '../PeriodicInvalidator.svelte'; import { numberSizes } from '$lib/utils/constants/numberSizes'; import { page } from '$app/state'; - import { customInvalidateAll } from '../PeriodicInvalidator.svelte'; import { durationFormatter } from '$lib/utils/formatters/durationFormatter'; interface Props { @@ -41,7 +39,7 @@ message_expiry: z.number().min(0).max(numberSizes.max.u32).default(topic.messageExpiry) }); - const { form, errors, enhance, constraints, submitting, reset, tainted } = superForm( + const { form, errors, enhance, constraints, submitting, tainted } = superForm( defaults(zod(schema)), { SPA: true, @@ -50,13 +48,16 @@ taintedMessage: false, async onUpdate({ form }) { if (!form.valid) return; + if (!page.params.streamId) return; const { data, ok } = await fetchRouteApi({ method: 'PUT', path: `/streams/${+page.params.streamId}/topics/${topic.id}`, body: { name: form.data.name, - message_expiry: form.data.message_expiry + message_expiry: form.data.message_expiry, + compression_algorithm: topic.compressionAlgorithm, + max_topic_size: 0 } }); @@ -83,9 +84,9 @@ confirmationOpen = false; if (result) { - const { data, ok } = await fetchRouteApi({ + const { ok } = await fetchRouteApi({ method: 'DELETE', - path: `/streams/${+page.params.streamId}/topics/${topic.id}` + path: `/streams/${+(page.params.streamId || '')}/topics/${topic.id}` }); if (ok) { @@ -149,7 +150,7 @@ </span> <div class="flex justify-end gap-3 w-full mt-auto"> - <Button type="button" variant="text" class="w-2/5" on:click={() => closeModal()} + <Button type="button" variant="text" class="w-2/5" onclick={() => closeModal()} >Cancel</Button > @@ -170,7 +171,7 @@ <Button variant="containedRed" class="max-h-[36px]" - on:click={() => (confirmationOpen = true)} + onclick={() => (confirmationOpen = true)} > <Icon name="trash" class="w-[20px] -ml-1" /> Delete</Button diff --git a/web/src/lib/components/Paginator.svelte b/web/src/lib/components/Paginator.svelte index 6faf6f90..92d7fce7 100644 --- a/web/src/lib/components/Paginator.svelte +++ b/web/src/lib/components/Paginator.svelte @@ -48,14 +48,14 @@ <div class="flex justify-center items-center space-x-2"> <Button variant="text" - on:click={() => emitPageChange(currentPage - 1)} + onclick={() => emitPageChange(currentPage - 1)} disabled={currentPage === 1} > <Icon name="arrowLeft" /> </Button> {#if visiblePages[0] > 1} - <Button variant="text" on:click={() => emitPageChange(1)}>1</Button> + <Button variant="text" onclick={() => emitPageChange(1)}>1</Button> {#if visiblePages[0] > 2} <span class="px-2">...</span> {/if} @@ -64,7 +64,7 @@ {#each visiblePages as page} <Button variant={currentPage === page ? 'contained' : 'text'} - on:click={() => emitPageChange(page)} + onclick={() => emitPageChange(page)} > {page} </Button> @@ -74,12 +74,12 @@ {#if visiblePages[visiblePages.length - 1] < totalPages - 1} <span class="px-2">...</span> {/if} - <Button variant="text" on:click={() => emitPageChange(totalPages)}>{totalPages}</Button> + <Button variant="text" onclick={() => emitPageChange(totalPages)}>{totalPages}</Button> {/if} <Button variant="text" - on:click={() => emitPageChange(currentPage + 1)} + onclick={() => emitPageChange(currentPage + 1)} disabled={currentPage === totalPages} > <Icon name="arrowRight" /> diff --git a/web/src/lib/components/PasswordInput.svelte b/web/src/lib/components/PasswordInput.svelte index e4832fa5..b1fae866 100644 --- a/web/src/lib/components/PasswordInput.svelte +++ b/web/src/lib/components/PasswordInput.svelte @@ -1,11 +1,8 @@ <script lang="ts"> - import type { HTMLInputAttributes } from 'svelte/elements'; import Input from '$lib/components/Input.svelte'; import Icon from './Icon.svelte'; import Button from './Button.svelte'; - - interface Props { label: string; id?: string; @@ -41,7 +38,7 @@ <Button variant="rounded" class="w-[33px] h-[33px] p-0 flex items-center justify-center" - on:click={(e) => { + onclick={(e) => { isVisible = !isVisible; e.preventDefault(); e.stopPropagation(); diff --git a/web/src/lib/components/PeriodicInvalidator.svelte b/web/src/lib/components/PeriodicInvalidator.svelte index 7daf8880..23cb2efb 100644 --- a/web/src/lib/components/PeriodicInvalidator.svelte +++ b/web/src/lib/components/PeriodicInvalidator.svelte @@ -50,12 +50,17 @@ }); </script> -<Button variant="rounded" on:click={customInvalidateAll}> - <div class={twMerge($isInvalidatingClampMin && 'spin')}> - <Icon name="refresh" class=" dark:stroke-white" /> - </div> +<Button + variant="rounded" + onclick={customInvalidateAll} +> + {#snippet children()} + <div class={twMerge($isInvalidatingClampMin && 'spin')}> + <Icon name="refresh" class="dark:text-white" /> + </div> + {/snippet} {#snippet tooltip()} - <div >Refresh</div> + <div>Refresh</div> {/snippet} </Button> diff --git a/web/src/lib/components/PermissionsManager.svelte b/web/src/lib/components/PermissionsManager.svelte index 6d228f3b..a1757eb1 100644 --- a/web/src/lib/components/PermissionsManager.svelte +++ b/web/src/lib/components/PermissionsManager.svelte @@ -349,7 +349,7 @@ value={globalPerms[key].name} id={`global-permissions-${key}`} name={globalPerms[key].name} - on:change={(e) => onGlobalPermChanged(key, noTypeCheck(e).target.checked)} + onclick={(e) => onGlobalPermChanged(key, noTypeCheck(e).target.checked)} /> <span class="text-sm">{globalPerms[key].name}</span> </label> diff --git a/web/src/lib/components/RangeInput.svelte b/web/src/lib/components/RangeInput.svelte index 8c1c3a26..3c8a4395 100644 --- a/web/src/lib/components/RangeInput.svelte +++ b/web/src/lib/components/RangeInput.svelte @@ -8,8 +8,6 @@ }; interface Props { - min: number; - max: number; initValue: number; className?: string; size?: keyof typeof sizes; @@ -17,8 +15,6 @@ } let { - min, - max, initValue, className = '', size = 'medium', diff --git a/web/src/lib/components/RouteComponents/Settings/UsersTab.svelte b/web/src/lib/components/RouteComponents/Settings/UsersTab.svelte index ab8d6f2f..3505299f 100644 --- a/web/src/lib/components/RouteComponents/Settings/UsersTab.svelte +++ b/web/src/lib/components/RouteComponents/Settings/UsersTab.svelte @@ -32,21 +32,21 @@ label: 'Edit', icon: 'editPen', action: () => { - openModal('EditUserModal', {}); + openModal('EditUserModal'); } }, { label: 'Permissions', icon: 'shieldLock', action: () => { - openModal('EditUserPermissionsModal', {}); + openModal('EditUserPermissionsModal'); } }, { label: 'Delete', icon: 'trash', action: () => { - openModal('DeleteUserModal', {}); + openModal('DeleteUserModal'); } } ] satisfies { label: string; icon: iconType; action: VoidFunction }[]; @@ -99,7 +99,7 @@ > {#snippet header()} <div class="flex items-center justify-center" > - <Checkbox value="all" checked={allChecked} on:change={toggleAllChecked} /> + <Checkbox value="all" checked={allChecked} onclick={toggleAllChecked} /> </div> {/snippet} diff --git a/web/src/lib/components/RouteComponents/Settings/UsersTabActions.svelte b/web/src/lib/components/RouteComponents/Settings/UsersTabActions.svelte index 7482d15a..be745bd9 100644 --- a/web/src/lib/components/RouteComponents/Settings/UsersTabActions.svelte +++ b/web/src/lib/components/RouteComponents/Settings/UsersTabActions.svelte @@ -1,10 +1,7 @@ <script lang="ts"> - import Button from '$lib/components/Button.svelte'; import Icon from '$lib/components/Icon.svelte'; import Input from '$lib/components/Input.svelte'; - import { openModal } from '$lib/components/Modals/AppModals.svelte'; - import { fade } from 'svelte/transition'; - import { searchQuery, selectedUsersId, usersCount } from './UsersTab.svelte'; + import { searchQuery, usersCount } from './UsersTab.svelte'; </script> <div class="flex flex-col-reverse lg:flex-row gap-3 lg:gap-5 items-center"> @@ -34,7 +31,7 @@ </Input> </div> <!-- - <Button variant="contained" on:click={() => openModal('AddUserModal', {streams: })}> + <Button variant="contained" onclick={() => openModal('AddUserModal', {streams: })}> <Icon name="plus" /> Add user </Button> --> diff --git a/web/src/lib/components/Select.svelte b/web/src/lib/components/Select.svelte index 28c83139..7457a837 100644 --- a/web/src/lib/components/Select.svelte +++ b/web/src/lib/components/Select.svelte @@ -2,7 +2,7 @@ import { createBubbler } from 'svelte/legacy'; const bubble = createBubbler(); - import type { HTMLInputAttributes, HTMLInputTypeAttribute } from 'svelte/elements'; + import type { HTMLInputTypeAttribute } from 'svelte/elements'; import { twMerge } from 'tailwind-merge'; diff --git a/web/src/lib/components/StopPropagation.svelte b/web/src/lib/components/StopPropagation.svelte index 85c02b21..a4ccd985 100644 --- a/web/src/lib/components/StopPropagation.svelte +++ b/web/src/lib/components/StopPropagation.svelte @@ -1,4 +1,6 @@ <script lang="ts"> + import { Keys } from '$lib/utils/constants/keys'; + interface Props { class?: string | undefined; children?: import('svelte').Snippet; @@ -13,6 +15,15 @@ onclick={(e) => { e.stopPropagation(); }} + onkeydown={(e) => { + if (e.key === Keys.ENTER || e.key === Keys.SPACE) { + e.preventDefault(); + e.stopPropagation(); + } + }} + role="button" + tabindex="0" + aria-label="Stop propagation" > {@render children?.()} </div> diff --git a/web/src/lib/components/ThemeController.svelte b/web/src/lib/components/ThemeController.svelte index e30f8985..99c193e5 100644 --- a/web/src/lib/components/ThemeController.svelte +++ b/web/src/lib/components/ThemeController.svelte @@ -21,13 +21,13 @@ export const setDarkMode = () => { document.body.classList.add('transitions-disabled'); document.documentElement.classList.add('dark'); - const _ = window.getComputedStyle(document.body).opacity; + void window.getComputedStyle(document.body).opacity; document.body.classList.remove('transitions-disabled'); }; export const setLightMode = () => { document.body.classList.add('transitions-disabled'); document.documentElement.classList.remove('dark'); - const _ = window.getComputedStyle(document.body).opacity; + void window.getComputedStyle(document.body).opacity; document.body.classList.remove('transitions-disabled'); }; diff --git a/web/src/lib/components/Toggler.svelte b/web/src/lib/components/Toggler.svelte index 72cb6b06..480c4628 100644 --- a/web/src/lib/components/Toggler.svelte +++ b/web/src/lib/components/Toggler.svelte @@ -3,10 +3,9 @@ interface Props { checked?: boolean; - value?: string; } - let { checked = $bindable(false), value = '1' }: Props = $props(); + let { checked = $bindable(false) }: Props = $props(); </script> <label diff --git a/web/src/lib/domain/Partition.ts b/web/src/lib/domain/Partition.ts index c57b1bd6..e69e4eb2 100644 --- a/web/src/lib/domain/Partition.ts +++ b/web/src/lib/domain/Partition.ts @@ -17,7 +17,7 @@ * under the License. */ -import { bytesFormatter } from '$lib/utils/formatters/bytesFormatter'; + import { formatDate } from '$lib/utils/formatters/dateFormatter'; export type Partition = { diff --git a/web/src/lib/domain/Permissions.ts b/web/src/lib/domain/Permissions.ts index 4ed41596..17a13b77 100644 --- a/web/src/lib/domain/Permissions.ts +++ b/web/src/lib/domain/Permissions.ts @@ -17,7 +17,6 @@ * under the License. */ -import type { KeysToSnakeCase } from '$lib/utils/utilTypes'; export type GlobalPermissions = { manageServers: boolean; diff --git a/web/src/lib/domain/Stream.ts b/web/src/lib/domain/Stream.ts index 92b52f5e..49d70a88 100644 --- a/web/src/lib/domain/Stream.ts +++ b/web/src/lib/domain/Stream.ts @@ -17,7 +17,7 @@ * under the License. */ -import { bytesFormatter } from '$lib/utils/formatters/bytesFormatter'; + export type Stream = { id: number; diff --git a/web/src/lib/queries.ts b/web/src/lib/queries.ts index 958f0515..2b88086b 100644 --- a/web/src/lib/queries.ts +++ b/web/src/lib/queries.ts @@ -17,13 +17,8 @@ * under the License. */ -import { statsMapper } from '$lib/domain/Stats'; -import { streamDetailsMapper } from '$lib/domain/StreamDetails'; // import { apiClient } from '$lib/utils/apiClient'; -import { streamMapper, type Stream } from './domain/Stream'; -import { topicDetailsMapper } from './domain/TopicDetails'; - const apiClient = async () => {}; // export function getStatsQuery() { diff --git a/web/src/lib/utils/dataHas.ts b/web/src/lib/utils/constants/keys.ts similarity index 56% copy from web/src/lib/utils/dataHas.ts copy to web/src/lib/utils/constants/keys.ts index 262ead38..059113db 100644 --- a/web/src/lib/utils/dataHas.ts +++ b/web/src/lib/utils/constants/keys.ts @@ -1,28 +1,55 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { setError } from 'sveltekit-superforms/client'; - -export const dataHas = (data: unknown, ...args: string[]) => { - if (data && typeof data === 'object') { - return args.every((arg) => arg in data); - } - - return false; -}; +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export const Keys = { + ENTER: 'Enter', + ESCAPE: 'Escape', + SPACE: ' ', + TAB: 'Tab', + BACKSPACE: 'Backspace', + DELETE: 'Delete', + + ARROW_UP: 'ArrowUp', + ARROW_DOWN: 'ArrowDown', + ARROW_LEFT: 'ArrowLeft', + ARROW_RIGHT: 'ArrowRight', + + PAGE_UP: 'PageUp', + PAGE_DOWN: 'PageDown', + HOME: 'Home', + END: 'End', + + SHIFT: 'Shift', + CONTROL: 'Control', + ALT: 'Alt', + META: 'Meta', + + F1: 'F1', + F2: 'F2', + F3: 'F3', + F4: 'F4', + F5: 'F5', + F6: 'F6', + F7: 'F7', + F8: 'F8', + F9: 'F9', + F10: 'F10', + F11: 'F11', + F12: 'F12', +} as const; diff --git a/web/src/lib/utils/dataHas.ts b/web/src/lib/utils/dataHas.ts index 262ead38..013dfda7 100644 --- a/web/src/lib/utils/dataHas.ts +++ b/web/src/lib/utils/dataHas.ts @@ -17,8 +17,6 @@ * under the License. */ -import { setError } from 'sveltekit-superforms/client'; - export const dataHas = (data: unknown, ...args: string[]) => { if (data && typeof data === 'object') { return args.every((arg) => arg in data); diff --git a/web/src/lib/utils/formatters/bytesFormatter.ts b/web/src/lib/utils/formatters/bytesFormatter.ts index 885dc00f..3f61325a 100644 --- a/web/src/lib/utils/formatters/bytesFormatter.ts +++ b/web/src/lib/utils/formatters/bytesFormatter.ts @@ -17,8 +17,6 @@ * under the License. */ -import prettyBytes from 'pretty-bytes'; - export function bytesFormatter(bytes: any) { // The bytes size string is already formatted by the API return bytes; diff --git a/web/src/lib/utils/formatters/durationFormatter.ts b/web/src/lib/utils/formatters/durationFormatter.ts index 04ce7fa1..685507fe 100644 --- a/web/src/lib/utils/formatters/durationFormatter.ts +++ b/web/src/lib/utils/formatters/durationFormatter.ts @@ -18,7 +18,6 @@ */ import { formatDuration, intervalToDuration, isValid } from 'date-fns'; -import { number } from 'zod'; export const durationFormatter = (seconds: number) => { if (seconds <= 0 || seconds.toString().length > 11 || !isValid(seconds)) return ''; diff --git a/web/src/routes/+error.svelte b/web/src/routes/+error.svelte index ec5d1a05..acd1f9b2 100644 --- a/web/src/routes/+error.svelte +++ b/web/src/routes/+error.svelte @@ -1,7 +1,5 @@ <script> - import { goto, invalidate, invalidateAll } from '$app/navigation'; import { page } from '$app/state'; - import { typedRoute } from '$lib/types/appRoutes'; </script> {#if page.error && page.error.message === 'Not Found'} diff --git a/web/src/routes/auth/sign-in/+page.svelte b/web/src/routes/auth/sign-in/+page.svelte index 79fa60b9..a12466c5 100644 --- a/web/src/routes/auth/sign-in/+page.svelte +++ b/web/src/routes/auth/sign-in/+page.svelte @@ -1,5 +1,4 @@ <script lang="ts"> - import { updated } from '$app/state'; import Button from '$lib/components/Button.svelte'; import Checkbox from '$lib/components/Checkbox.svelte'; import Icon from '$lib/components/Icon.svelte'; @@ -14,7 +13,7 @@ } let { data }: Props = $props(); - const { form, constraints, errors, message, reset } = superForm(data.form, {}); + const { form, constraints, errors, message } = superForm(data.form, {}); const remember = persistedStore('rememberMe', { rememberMe: true, username: '', password: '' }); @@ -44,7 +43,7 @@ <Input label="Username" name="username" - errorMessage={$errors?.username?.join(',')} + errorMessage={Array.isArray($errors?.username) ? $errors.username.join(',') : String($errors?.username || '')} bind:value={$form.username} {...$constraints.username} /> @@ -52,7 +51,7 @@ <PasswordInput label="Password" name="password" - errorMessage={$errors.password?.join(',')} + errorMessage={Array.isArray($errors?.password) ? $errors.password.join(',') : String($errors?.password || '')} bind:value={$form.password} {...$constraints.password} /> diff --git a/web/src/routes/dashboard/overview/+page.svelte b/web/src/routes/dashboard/overview/+page.svelte index 45b7bc46..09ba1b38 100644 --- a/web/src/routes/dashboard/overview/+page.svelte +++ b/web/src/routes/dashboard/overview/+page.svelte @@ -5,7 +5,7 @@ let { data }: Props = $props(); - let statsValues = $derived(Object.values(data.stats).map((val) => val)); + let statsValues = $derived(Object.values(data.stats).map((val) => val as { name: string; value: string })); </script> <div class="h-full overflow-auto p-10"> diff --git a/web/src/routes/dashboard/settings/server/+page.svelte b/web/src/routes/dashboard/settings/server/+page.svelte index 4cb818b8..1979ba57 100644 --- a/web/src/routes/dashboard/settings/server/+page.svelte +++ b/web/src/routes/dashboard/settings/server/+page.svelte @@ -1,11 +1,9 @@ <script lang="ts"> import Button from '$lib/components/Button.svelte'; import SettingsLayout from '$lib/components/Layouts/SettingsLayout.svelte'; - import Loader from '$lib/components/Loader.svelte'; import RangeInput from '$lib/components/RangeInput.svelte'; import Toggler from '$lib/components/Toggler.svelte'; - import type { Stats } from '$lib/domain/Stats'; interface Props { data: any; @@ -38,8 +36,6 @@ <RangeInput className="w-[270px] h-[9px]" size="big" - min={0} - max={100} initValue={50} bind:value={cacheValue} /> diff --git a/web/src/routes/dashboard/settings/users/+page.svelte b/web/src/routes/dashboard/settings/users/+page.svelte index fe5e9ed1..337857f7 100644 --- a/web/src/routes/dashboard/settings/users/+page.svelte +++ b/web/src/routes/dashboard/settings/users/+page.svelte @@ -34,21 +34,21 @@ label: 'Edit', icon: 'editPen', action: () => { - openModal('EditUserModal', {}); + openModal('EditUserModal'); } }, { label: 'Permissions', icon: 'shieldLock', action: () => { - openModal('EditUserPermissionsModal', {}); + openModal('EditUserPermissionsModal'); } }, { label: 'Delete', icon: 'trash', action: () => { - openModal('DeleteUserModal', {}); + openModal('DeleteUserModal'); } } ] satisfies { label: string; icon: iconType; action: VoidFunction }[]; @@ -97,7 +97,7 @@ <Button variant="contained" - on:click={() => + onclick={() => openModal('AddUserModal', { streams: data.streams })} @@ -145,7 +145,7 @@ > {#snippet header()} <div class="flex items-center justify-center" > - <Checkbox value="all" checked={allChecked} on:change={toggleAllChecked} /> + <Checkbox value="all" checked={allChecked} onclick={toggleAllChecked} /> </div> {/snippet} diff --git a/web/src/routes/dashboard/settings/webUI/+page.svelte b/web/src/routes/dashboard/settings/webUI/+page.svelte index ca744c79..c9f83f3a 100644 --- a/web/src/routes/dashboard/settings/webUI/+page.svelte +++ b/web/src/routes/dashboard/settings/webUI/+page.svelte @@ -36,7 +36,7 @@ </span> <Button disabled={saveDisabled} - on:click={() => { + onclick={() => { $invalidateIntervalDuration = intervalValue; }} variant="contained">Save</Button diff --git a/web/src/routes/dashboard/streams/+layout.svelte b/web/src/routes/dashboard/streams/+layout.svelte index cceb5035..8172d85f 100644 --- a/web/src/routes/dashboard/streams/+layout.svelte +++ b/web/src/routes/dashboard/streams/+layout.svelte @@ -1,6 +1,6 @@ <script lang="ts"> import Icon from '$lib/components/Icon.svelte'; - import { goto, invalidateAll, onNavigate } from '$app/navigation'; + import { goto } from '$app/navigation'; import { twMerge } from 'tailwind-merge'; import { page } from '$app/state'; import { openModal } from '$lib/components/Modals/AppModals.svelte'; @@ -9,9 +9,6 @@ import { typedRoute } from '$lib/types/appRoutes'; import { arrayMax } from '$lib/utils/arrayMax'; import { onMount } from 'svelte'; - import { noTypeCheck } from '$lib/utils/noTypeCheck.js'; - import { slide } from 'svelte/transition'; - import { bytesFormatter } from '$lib/utils/formatters/bytesFormatter'; interface Props { data: any; @@ -88,7 +85,7 @@ <Button variant="outlined" class="w-full" - on:click={() => + onclick={() => openModal('AddStreamModal', { nextStreamId: arrayMax(data.streams.map((s) => s.id)) + 1 })} diff --git a/web/src/routes/dashboard/streams/[streamId=i32]/+page.svelte b/web/src/routes/dashboard/streams/[streamId=i32]/+page.svelte index 44ac6615..c23d4b5f 100644 --- a/web/src/routes/dashboard/streams/[streamId=i32]/+page.svelte +++ b/web/src/routes/dashboard/streams/[streamId=i32]/+page.svelte @@ -1,82 +1,85 @@ -<script lang="ts"> - import { page } from '$app/state'; - import Button from '$lib/components/Button.svelte'; - import Icon from '$lib/components/Icon.svelte'; - import { openModal } from '$lib/components/Modals/AppModals.svelte'; - import SortableList from '$lib/components/SortableList.svelte'; - import { typedRoute } from '$lib/types/appRoutes'; - import { arrayMax } from '$lib/utils/arrayMax'; - import type { StreamDetails } from '$lib/domain/StreamDetails'; - - interface Props { - data: { - streamDetails: StreamDetails - }; - } - - let { data }: Props = $props(); - let stream = $derived(data.streamDetails); -</script> - -<div class="h-[80px] flex flex-row text-xs items-center px-7"> - <h1 class="font-semibold text-xl dark:text-white max-w-[380px] break-words line-clamp-3"> - Stream {stream.name} - </h1> - - <Button - variant="rounded" - class="ml-3" - on:click={() => openModal('StreamSettingsModal', { stream })} - > - <Icon name="settings" class="dark:text-white" /> - {#snippet tooltip()} - <div >Settings</div> - {/snippet} - </Button> - - <div class="flex gap-3 ml-7"> - <div class="chip"> - <span>Id: {stream.id}</span> - </div> - <div class="chip"> - <span>Size: {stream.sizeFormatted}</span> - </div> - <div class="chip"> - <span>Messages: {stream.messagesCount}</span> - </div> - <div class="chip"> - <span>Topics: {stream.topicsCount}</span> - </div> - </div> - - <Button - variant="contained" - class="ml-auto" - on:click={() => - openModal('AddTopicModal', { - streamDetails: stream, - nextTopicId: arrayMax(data.streamDetails.topics.map((t) => t.id)) + 1 - })} - > - <Icon name="plus" class="w-[16px] h-[16px]" strokeWidth={2} /> - Add Topic - </Button> -</div> - -<SortableList - emptyDataMessage="This stream has no topics." - rowClass="grid grid-cols-[150px_3fr_2fr_2fr_2fr_2fr_3fr]" - data={stream.topics} - hrefBuilder={(topic) => - typedRoute(`/dashboard/streams/${+page.params.streamId}/topics/${topic.id}`)} - colNames={{ - id: 'ID', - name: 'Name', - messagesCount: 'Messages', - partitionsCount: 'Partitions', - messageExpiryFormatted: 'Message expiry', - sizeFormatted: 'Size', - createdAt: 'Created', - sizeBytes: undefined - }} -/> +<script lang="ts"> + import { page } from '$app/state'; + import Button from '$lib/components/Button.svelte'; + import Icon from '$lib/components/Icon.svelte'; + import { openModal } from '$lib/components/Modals/AppModals.svelte'; + import SortableList from '$lib/components/SortableList.svelte'; + import { typedRoute } from '$lib/types/appRoutes'; + import { arrayMax } from '$lib/utils/arrayMax'; + import type { StreamDetails } from '$lib/domain/StreamDetails'; + + interface Props { + data: { + streamDetails: StreamDetails + }; + } + + let { data }: Props = $props(); + let stream = $derived(data.streamDetails); +</script> + +<div class="h-[80px] flex flex-row text-xs items-center px-7"> + <h1 class="font-semibold text-xl dark:text-white max-w-[380px] break-words line-clamp-3"> + Stream {stream.name} + </h1> + + <Button + variant="rounded" + class="ml-3" + onclick={() => openModal('StreamSettingsModal', { stream })} + > + {#snippet children()} + <Icon name="settings" class="dark:text-white" /> + {/snippet} + {#snippet tooltip()} + <div>Settings</div> + {/snippet} + </Button> + + <div class="flex gap-3 ml-7"> + <div class="chip"> + <span>Id: {stream.id}</span> + </div> + <div class="chip"> + <span>Size: {stream.sizeFormatted}</span> + </div> + <div class="chip"> + <span>Messages: {stream.messagesCount}</span> + </div> + <div class="chip"> + <span>Topics: {stream.topicsCount}</span> + </div> + </div> + + <Button + variant="contained" + class="ml-auto" + onclick={() => + openModal('AddTopicModal', { + streamDetails: stream, + nextTopicId: arrayMax(data.streamDetails.topics.map((t) => t.id)) + 1 + })} + > + <Icon name="plus" class="w-[16px] h-[16px]" strokeWidth={2} /> + Add Topic + </Button> +</div> + +<SortableList + emptyDataMessage="This stream has no topics." + rowClass="grid grid-cols-[150px_3fr_2fr_2fr_2fr_2fr_3fr]" + data={stream.topics} + hrefBuilder={(topic) => + typedRoute(`/dashboard/streams/${+(page.params.streamId || '')}/topics/${topic.id}`)} + colNames={{ + id: 'ID', + name: 'Name', + messagesCount: 'Messages', + partitionsCount: 'Partitions', + messageExpiryFormatted: 'Message expiry', + sizeFormatted: 'Size', + createdAt: 'Created', + sizeBytes: undefined + }} +/> + diff --git a/web/src/routes/dashboard/streams/[streamId=i32]/topics/[topicId=i32]/+page.svelte b/web/src/routes/dashboard/streams/[streamId=i32]/topics/[topicId=i32]/+page.svelte index 84e9b923..b748bdce 100644 --- a/web/src/routes/dashboard/streams/[streamId=i32]/topics/[topicId=i32]/+page.svelte +++ b/web/src/routes/dashboard/streams/[streamId=i32]/topics/[topicId=i32]/+page.svelte @@ -1,87 +1,89 @@ -<script lang="ts"> - import { page } from '$app/state'; - import Button from '$lib/components/Button.svelte'; - import Icon from '$lib/components/Icon.svelte'; - import { goto } from '$app/navigation'; - import { typedRoute } from '$lib/types/appRoutes'; - import { openModal } from '$lib/components/Modals/AppModals.svelte'; - import SortableList from '$lib/components/SortableList.svelte'; - import type { Topic } from '$lib/domain/Topic'; - import type { Partition } from '$lib/domain/Partition'; - - interface Props { - data: { - topic: Topic & { - partitions: Partition[]; - }; - } - } - - let { data }: Props = $props(); - let topic = $derived(data.topic); - let prevPage = $derived(page.url.pathname.split('/').slice(0, 4).join('/') + '/'); -</script> - -<div class="h-[80px] flex text-xs items-center pl-2 pr-5"> - <Button variant="rounded" class="mr-5" on:click={() => goto(prevPage)}> - <Icon name="arrowLeft" class="h-[40px] w-[30px]" /> - </Button> - - <h1 class="text-xl font-semibold text-color">Topic {topic.name}</h1> - - <Button - variant="rounded" - class="ml-3" - on:click={() => openModal('TopicSettingsModal', { topic, onDeleteRedirectPath: prevPage })} - > - <Icon name="settings" /> - {#snippet tooltip()} - <div >Settings</div> - {/snippet} - </Button> - - <div class="flex gap-3 ml-7"> - <div class="chip"> - <span>Id: {topic.id}</span> - </div> - <div class="chip"> - <span>Size: {topic.sizeFormatted}</span> - </div> - <div class="chip"> - <span>Messages: {topic.messagesCount}</span> - </div> - <div class="chip"> - <span>Partitions: {topic.partitionsCount}</span> - </div> - </div> - - <div class="flex gap-2 ml-auto"> - {#if topic.partitions.length > 0} - <Button variant="outlinedRed" on:click={() => openModal('DeletePartitionsModal', { topic })} - >Delete partitions</Button - > - {/if} - <Button variant="contained" on:click={() => openModal('AddPartitionsModal', {})} - >Add partitions</Button - > - </div> -</div> - -<SortableList - emptyDataMessage="No partitions found." - rowClass="grid grid-cols-[150px_1fr_1fr_1fr_1fr_1fr]" - data={topic.partitions} - hrefBuilder={(partition) => - typedRoute( - `/dashboard/streams/${+page.params.streamId}/topics/${topic.id}/partitions/${partition.id}/messages` - )} - colNames={{ - id: 'ID', - currentOffset: 'Offset', - segmentsCount: 'Segments', - messagesCount: 'Messages', - sizeFormatted: 'Size', - createdAt: 'Created', - sizeBytes: undefined - }} -/> +<script lang="ts"> + import { page } from '$app/state'; + import Button from '$lib/components/Button.svelte'; + import Icon from '$lib/components/Icon.svelte'; + import { goto } from '$app/navigation'; + import { typedRoute } from '$lib/types/appRoutes'; + import { openModal } from '$lib/components/Modals/AppModals.svelte'; + import SortableList from '$lib/components/SortableList.svelte'; + import type { Topic } from '$lib/domain/Topic'; + import type { Partition } from '$lib/domain/Partition'; + + interface Props { + data: { + topic: Topic & { + partitions: Partition[]; + }; + } + } + + let { data }: Props = $props(); + let topic = $derived(data.topic); + let prevPage = $derived(page.url.pathname.split('/').slice(0, 4).join('/') + '/'); +</script> + +<div class="h-[80px] flex text-xs items-center pl-2 pr-5"> + <Button variant="rounded" class="mr-5" onclick={() => goto(prevPage)}> + <Icon name="arrowLeft" class="h-[40px] w-[30px]" /> + </Button> + + <h1 class="text-xl font-semibold text-color">Topic {topic.name}</h1> + + <Button + variant="rounded" + class="ml-3" + onclick={() => openModal('TopicSettingsModal', { topic, onDeleteRedirectPath: prevPage })} + > + {#snippet children()} + <Icon name="settings" class="dark:text-white" /> + {/snippet} + {#snippet tooltip()} + <div>Settings</div> + {/snippet} + </Button> + + <div class="flex gap-3 ml-7"> + <div class="chip"> + <span>Id: {topic.id}</span> + </div> + <div class="chip"> + <span>Size: {topic.sizeFormatted}</span> + </div> + <div class="chip"> + <span>Messages: {topic.messagesCount}</span> + </div> + <div class="chip"> + <span>Partitions: {topic.partitionsCount}</span> + </div> + </div> + + <div class="flex gap-2 ml-auto"> + {#if topic.partitions.length > 0} + <Button variant="outlinedRed" onclick={() => openModal('DeletePartitionsModal', { topic })} + >Delete partitions</Button + > + {/if} + <Button variant="contained" onclick={() => openModal('AddPartitionsModal')} + >Add partitions</Button + > + </div> +</div> + +<SortableList + emptyDataMessage="No partitions found." + rowClass="grid grid-cols-[150px_1fr_1fr_1fr_1fr_1fr]" + data={topic.partitions} + hrefBuilder={(partition) => + typedRoute( + `/dashboard/streams/${+(page.params.streamId || '')}/topics/${topic.id}/partitions/${partition.id}/messages` + )} + colNames={{ + id: 'ID', + currentOffset: 'Offset', + segmentsCount: 'Segments', + messagesCount: 'Messages', + sizeFormatted: 'Size', + createdAt: 'Created', + sizeBytes: undefined + }} +/> diff --git a/web/src/routes/dashboard/streams/[streamId=i32]/topics/[topicId=i32]/partitions/[partitionId=i32]/messages/+page.server.ts b/web/src/routes/dashboard/streams/[streamId=i32]/topics/[topicId=i32]/partitions/[partitionId=i32]/messages/+page.server.ts index f368ee88..7bf5ba31 100644 --- a/web/src/routes/dashboard/streams/[streamId=i32]/topics/[topicId=i32]/partitions/[partitionId=i32]/messages/+page.server.ts +++ b/web/src/routes/dashboard/streams/[streamId=i32]/topics/[topicId=i32]/partitions/[partitionId=i32]/messages/+page.server.ts @@ -45,10 +45,10 @@ export const load = async ({ params, cookies, url }) => { const { data: initialData } = await handleFetchErrors(initialResult, cookies); const initialMessages = partitionMessagesDetailsMapper(initialData as any); - const totalMessages = initialMessages.currentOffset; + const totalMessages = initialMessages.currentOffset + 1; const offset = url.searchParams.get('offset') ?? - (direction === 'desc' ? Math.max(0, totalMessages - MESSAGES_PER_PAGE) : '0'); + (direction === 'desc' ? Math.max(0, totalMessages - MESSAGES_PER_PAGE).toString() : '0'); const result = await fetchIggyApi({ method: 'GET', @@ -84,7 +84,7 @@ export const load = async ({ params, cookies, url }) => { partitionMessages, topic, pagination: { - offset, + offset: parseInt(offset.toString()), count: MESSAGES_PER_PAGE } }; diff --git a/web/src/routes/dashboard/streams/[streamId=i32]/topics/[topicId=i32]/partitions/[partitionId=i32]/messages/+page.svelte b/web/src/routes/dashboard/streams/[streamId=i32]/topics/[topicId=i32]/partitions/[partitionId=i32]/messages/+page.svelte index a99fac53..30e8dce9 100644 --- a/web/src/routes/dashboard/streams/[streamId=i32]/topics/[topicId=i32]/partitions/[partitionId=i32]/messages/+page.svelte +++ b/web/src/routes/dashboard/streams/[streamId=i32]/topics/[topicId=i32]/partitions/[partitionId=i32]/messages/+page.svelte @@ -1,109 +1,119 @@ -<script lang="ts"> - import { page } from '$app/state'; - import Button from '$lib/components/Button.svelte'; - import Icon from '$lib/components/Icon.svelte'; - import { goto } from '$app/navigation'; - import { openModal } from '$lib/components/Modals/AppModals.svelte'; - import SortableList from '$lib/components/SortableList.svelte'; - import Paginator from '$lib/components/Paginator.svelte'; - - interface Props { - data: any; - } - - let { data }: Props = $props(); - let topic = $derived(data.topic); - let partitionMessages = $derived(data.partitionMessages); - let prevPage = $derived(page.url.pathname.split('/').slice(0, 6).join('/') + '/'); - - let direction = $state(page.url.searchParams.get('direction') || 'desc'); - let currentPage = $state(1); - let totalPages = $derived( - Math.ceil((partitionMessages.currentOffset + 1) / data.pagination.count) - ); - - async function loadPage(page: number) { - const messagesPerPage = data.pagination.count; - const totalMessages = partitionMessages.currentOffset + 1; - - let offset: number; - if (direction === 'desc') { - offset = Math.max(0, totalMessages - page * messagesPerPage); - } else { - offset = (page - 1) * messagesPerPage; - } - - const url = new URL(window.location.href); - url.searchParams.set('offset', offset.toString()); - url.searchParams.set('direction', direction); - await goto(url, { keepFocus: true, noScroll: true }); - currentPage = page; - } - - function onPageChange(event: CustomEvent<number>) { - const page = event.detail; - loadPage(page); - } - - function toggleDirection() { - direction = direction === 'desc' ? 'asc' : 'desc'; - loadPage(1); - } -</script> - -<div class="h-[80px] flex text-xs items-center pl-2 pr-5"> - <Button variant="rounded" class="mr-5" on:click={() => goto(prevPage)}> - <Icon name="arrowLeft" class="h-[40px] w-[30px]" /> - </Button> - - <h1 class="text-xl font-semibold text-color"> - Messages for {topic.name}, partition {partitionMessages.partitionId} - </h1> - - <div class="flex gap-3 ml-7"> - <div class="chip"> - <span>Messages: {partitionMessages.currentOffset}</span> - </div> - </div> - - <div class="flex gap-2 ml-auto"> - <Button variant="contained" on:click={toggleDirection}> - <Icon name={direction === 'desc' ? 'arrowDown' : 'arrowUp'} class="h-5 w-5 mr-2" /> - {direction === 'desc' ? 'Newest first' : 'Oldest first'} - </Button> - </div> -</div> - -<div class="flex-1 overflow-auto"> - {#if partitionMessages.messages.length === 0} - <div class="flex items-center justify-center h-full text-gray-400 text-xl"> - <em>No messages found.</em> - </div> - {:else} - <SortableList - emptyDataMessage="No messages found." - rowClass="grid grid-cols-[150px_2fr_1fr_1fr_1fr]" - data={direction === 'desc' - ? [...partitionMessages.messages].reverse() - : partitionMessages.messages} - onclickAction={(index) => - openModal('InspectMessage', { - message: - partitionMessages.messages[ - direction === 'desc' ? partitionMessages.messages.length - 1 - index : index - ] - })} - ariaRoleDescription="Display message details" - colNames={{ - offset: 'Offset', - truncatedPayload: 'Payload', - formattedTimestamp: 'Timestamp', - checksum: 'Checksum' - }} - /> - {/if} -</div> - -<div class="mt-2 mb-2"> - <Paginator {currentPage} {totalPages} maxVisiblePages={5} on:pageChange={onPageChange} /> -</div> +<script lang="ts"> + import { page } from '$app/state'; + import Button from '$lib/components/Button.svelte'; + import Icon from '$lib/components/Icon.svelte'; + import { goto } from '$app/navigation'; + import { openModal } from '$lib/components/Modals/AppModals.svelte'; + import SortableList from '$lib/components/SortableList.svelte'; + import Paginator from '$lib/components/Paginator.svelte'; + import type { MessagePartition } from '$lib/domain/Message'; + import type { TopicDetails } from '$lib/domain/TopicDetails'; + + interface Props { + data: { + partitionMessages: MessagePartition; + topic: TopicDetails; + pagination: { + offset: number; + count: number; + }; + }; + } + + let { data }: Props = $props(); + let topic = $derived(data.topic); + let partitionMessages = $derived(data.partitionMessages); + let prevPage = $derived(page.url.pathname.split('/').slice(0, 6).join('/') + '/'); + + let direction = $state(page.url.searchParams.get('direction') || 'desc'); + let currentPage = $state(1); + let totalPages = $derived( + Math.ceil((partitionMessages.currentOffset + 1) / data.pagination.count) + ); + + async function loadPage(page: number) { + const messagesPerPage = data.pagination.count; + const totalMessages = partitionMessages.currentOffset + 1; + + let offset: number; + if (direction === 'desc') { + offset = Math.max(0, totalMessages - page * messagesPerPage); + } else { + offset = (page - 1) * messagesPerPage; + } + + const url = new URL(window.location.href); + url.searchParams.set('offset', offset.toString()); + url.searchParams.set('direction', direction); + await goto(url, { keepFocus: true, noScroll: true }); + currentPage = page; + } + + function onPageChange(event: CustomEvent<number>) { + const page = event.detail; + loadPage(page); + } + + function toggleDirection() { + direction = direction === 'desc' ? 'asc' : 'desc'; + loadPage(1); + } +</script> + +<div class="h-[80px] flex text-xs items-center pl-2 pr-5"> + <Button variant="rounded" class="mr-5" onclick={() => goto(prevPage)}> + <Icon name="arrowLeft" class="h-[40px] w-[30px]" /> + </Button> + + <h1 class="text-xl font-semibold text-color"> + Messages for {topic.name}, partition {partitionMessages.partitionId} + </h1> + + <div class="flex gap-3 ml-7"> + <div class="chip"> + <span>Messages: {partitionMessages.messages.length > 0 ? partitionMessages.currentOffset + 1 : 0}</span> + </div> + </div> + + <div class="flex gap-2 ml-auto"> + <Button variant="contained" onclick={toggleDirection}> + <Icon name={direction === 'desc' ? 'arrowDown' : 'arrowUp'} class="h-5 w-5 mr-2" /> + {direction === 'desc' ? 'Newest first' : 'Oldest first'} + </Button> + </div> +</div> + +<div class="flex-1 overflow-auto"> + {#if partitionMessages.messages.length === 0} + <div class="flex items-center justify-center h-full text-gray-400 text-xl"> + <em>No messages found.</em> + </div> + {:else} + <SortableList + emptyDataMessage="No messages found." + rowClass="grid grid-cols-[150px_2fr_1fr_1fr_1fr]" + data={direction === 'desc' + ? [...partitionMessages.messages].reverse() + : partitionMessages.messages} + onclickAction={(index) => + openModal('InspectMessage', { + message: + partitionMessages.messages[ + direction === 'desc' ? partitionMessages.messages.length - 1 - index : index + ] + })} + ariaRoleDescription="Display message details" + colNames={{ + offset: 'Offset', + truncatedPayload: 'Payload', + formattedTimestamp: 'Timestamp', + checksum: 'Checksum' + }} + /> + {/if} +</div> + +<div class="mt-2 mb-2"> + <Paginator {currentPage} {totalPages} maxVisiblePages={5} on:pageChange={onPageChange} /> +</div> +