refactor(web): replace @headlessui/react with shadcn components

Swap every headlessui usage for the shadcn/base-ui components that are
already bundled in the app, then drop the dependency:

- menus (8): Menu/MenuButton/MenuItems/MenuItem + Transition -> DropdownMenu;
  role checks that guarded the original clicks are preserved as
  DropdownMenuItem `disabled`
- dialogs (14): Dialog/DialogPanel/DialogTitle + Transition* -> Dialog,
  DialogContent, DialogTitle, DialogDescription, DialogFooter and Button
- listboxes (6): Listbox -> Select; the multiple-selection one becomes a
  DropdownMenu with DropdownMenuCheckboxItem
- comboboxes (9): Combobox -> Popover + Command, wiring the existing search
  state into CommandInput and keeping shouldFilter={false} for server-side
  search
- remove @headlessui/react from package.json/bun.lock

Also included:
- drop imports that became unused (Table/Button in Builder/RunnerList,
  Separator, useSidebar, dayjs, Link/useParams, Check/ChevronsUpDown and the
  leftover shadcn imports in the webhook pages)
- replace Tailwind v4 arbitrary sizes that have spacing-scale equivalents
  (w-[350px] -> w-87.5, h-[36px] -> h-9, after:top-[2px] -> after:top-0.5, ...);
  h-[18.4px] is intentionally kept since 4.6 is not a multiple of 0.25
- fix the webhook log delete-confirm label (was "Resend")
- add oxlint with `lint` and `lint:fix` scripts

Verification: web: `tsc --noEmit`; `vite build`; `oxlint` (0 errors,
72 pre-existing warnings); scripted scan reports 0 unused imports and no
remaining @headlessui imports.
This commit is contained in:
Tosone committed 2026-09-17 15:31:21 +08:00
1 parent 959b2f2ee6
commit 3ca56e7b67
24 files changed
+2214 -3438

No files matched your search

+41 -29
View File
@@ -9,7 +9,6 @@
"@bytemd/plugin-gfm": "^1.22.0",
"@bytemd/react": "^1.22.0",
"@fontsource-variable/geist": "^5.3.0",
"@headlessui/react": "^2.2.10",
"@heroicons/react": "^2.2.0",
"@shikijs/core": "^4.4.3",
"@shikijs/engine-javascript": "^4.4.3",
@@ -52,6 +51,7 @@
"@types/react-dom": "^19.3.0",
"autoprefixer": "^10.5.6",
"esbuild": "^0.28.1",
"oxlint": "^1.83.0",
"postcss": "^8.5.28",
"postcss-import": "^16.2.0",
"postcss-nesting": "^13.0.2",
@@ -197,26 +197,16 @@
"@floating-ui/dom": ["@floating-ui/dom@1.8.0", "", { "dependencies": { "@floating-ui/core": "^1.8.0", "@floating-ui/utils": "^0.2.12" } }, "sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg=="],
"@floating-ui/react": ["@floating-ui/react@0.26.28", "", { "dependencies": { "@floating-ui/react-dom": "^2.1.2", "@floating-ui/utils": "^0.2.8", "tabbable": "^6.0.0" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-yORQuuAtVpiRjpMhdc0wJj06b9JFjrYF4qp96j++v2NBpbi6SEGF7donUJ3TMieerQ6qVkAv1tgr7L4r5roTqw=="],
"@floating-ui/react-dom": ["@floating-ui/react-dom@2.1.9", "", { "dependencies": { "@floating-ui/dom": "^1.8.0" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-JDjEFGCpImxDCA7JJKviA0M9+RtmJdj0m/NVU5IMgBK+AmZouAQQ7/+2GLH0GXXY0YMw9oXPB8hKdbPYg5QLYg=="],
"@floating-ui/utils": ["@floating-ui/utils@0.2.12", "", {}, "sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww=="],
"@fontsource-variable/geist": ["@fontsource-variable/geist@5.3.0", "", {}, "sha512-j0m+vLQuG5XAYoHtGCVu0spvlGreR3EzpECUVzkFmI1mTVnAO38l/NEPDCFgZ177JxzYJCLSmTQibIiYPilGrA=="],
"@headlessui/react": ["@headlessui/react@2.2.10", "", { "dependencies": { "@floating-ui/react": "^0.26.16", "@react-aria/focus": "^3.20.2", "@react-aria/interactions": "^3.25.0", "@tanstack/react-virtual": "^3.13.9", "use-sync-external-store": "^1.5.0" }, "peerDependencies": { "react": "^18 || ^19 || ^19.0.0-rc", "react-dom": "^18 || ^19 || ^19.0.0-rc" } }, "sha512-5pVLNK9wlpxTUTy9GpgbX/SdcRh+HBnPktjM2wbiLTH4p+2EPHBO1aoSryUCuKUIItdDWO9ITlhUL8UnUN/oIA=="],
"@heroicons/react": ["@heroicons/react@2.2.0", "", { "peerDependencies": { "react": ">= 16 || ^19.0.0-rc" } }, "sha512-LMcepvRaS9LYHJGsF0zzmgKCUim/X3N/DQKc4jepAXJ7l8QxJ1PmxJzqplF2Z3FE4PqBAIGyJAQ/w4B5dsqbtQ=="],
"@hono/node-server": ["@hono/node-server@2.1.1", "", { "peerDependencies": { "hono": "^4" } }, "sha512-ELuehkj5VCBdgEw9zs+ivkKwyzzUCSQuE96YmiPvn1ECBoZCczbFXJLeEGMTYjphP6gydh4pHMqEYPVMYUVgQg=="],
"@internationalized/date": ["@internationalized/date@3.12.4", "", { "dependencies": { "@swc/helpers": "^0.5.0" } }, "sha512-M1dEn4c1U1HsSlaVR8upZtSqvXrTkHDfv18H01uCSJyjVLDxnBR38v/fMxecmlwXKR4i9HeZcmgQAPE6A+aGJQ=="],
"@internationalized/number": ["@internationalized/number@3.6.8", "", { "dependencies": { "@swc/helpers": "^0.5.0" } }, "sha512-8UmMFia46DUt+k97zKd9fKWXcWHR+k8ae3eYzILETuT2KbIvLyOfac7zesw+sJdRAAZ7Q9pM1Mk22aXp2LD0Ig=="],
"@internationalized/string": ["@internationalized/string@3.2.10", "", { "dependencies": { "@swc/helpers": "^0.5.0" } }, "sha512-PDx6//vHSpRnHfxqMqto11zQvhsaU74O3mKv2F/0eicGZcl9NLjQmGlbHz/LsJh5tLKp4A4L7ZVTzN1/MmMTvA=="],
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
"@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="],
@@ -237,6 +227,44 @@
"@oxc-project/types": ["@oxc-project/types@0.149.0", "", {}, "sha512-Efcc+iF0j3Bf67YjEqIqWXbX5XddXoK/Mw4K1/JuXwRCZ8N16VR7iT23nlCc9XrveFVh/E5Rqs2StT0V8v9LdA=="],
"@oxlint/binding-android-arm-eabi": ["@oxlint/binding-android-arm-eabi@1.83.0", "", { "os": "android", "cpu": "arm" }, "sha512-0yGY24EwsLk5YDe6F+VkmZyRHSwJDALa3nIrPpq7FXmp2lV2d0TzvBCGeZk+wgiULRGr5blhyr4QMp5KCXJUqA=="],
"@oxlint/binding-android-arm64": ["@oxlint/binding-android-arm64@1.83.0", "", { "os": "android", "cpu": "arm64" }, "sha512-hHfJ0vc17A4iUjH5p9BsTUPYbYRNxGpvD2lbu1aBRk54bzNIx9o5TtYF39QPZcV95DagZd+4DEAw2RH3G2ZsMg=="],
"@oxlint/binding-darwin-arm64": ["@oxlint/binding-darwin-arm64@1.83.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-hsOjYjszLb/3zym/TkzUMPAoQlTJcuzSyEPOAyA+skXJIX9M0o+4JfOtqopX/Vf4hSLrJ98j0nvFo23gzk8auQ=="],
"@oxlint/binding-darwin-x64": ["@oxlint/binding-darwin-x64@1.83.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-mjh5oH2EA+wl5yRJYT9K9G61O2zFlpuv+yf2JwZOi0+dq2FnTUtm1h8i+5Ik0fXPWIu/k84I1psZR9aQsLAnyA=="],
"@oxlint/binding-freebsd-x64": ["@oxlint/binding-freebsd-x64@1.83.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-fNHr64/YaO8YssuoDVC8+F4Uk5enR86q5uxfHkQrjAPs1dbAILOrD2uaud+J7MO8Fx774g44ERLD0IGIvZE48w=="],
"@oxlint/binding-linux-arm-gnueabihf": ["@oxlint/binding-linux-arm-gnueabihf@1.83.0", "", { "os": "linux", "cpu": "arm" }, "sha512-Qpwy3zzAwMj+8/lyYItHmkSMwbkprFNWTK7jPYDOxSyxEhaSLOWYUTCMkjF334J8/WD0nznCCsoBbIH6hpsuIw=="],
"@oxlint/binding-linux-arm-musleabihf": ["@oxlint/binding-linux-arm-musleabihf@1.83.0", "", { "os": "linux", "cpu": "arm" }, "sha512-s+BirYLFq7JL2k9sP0XI3ZXJ9dYvJ8sX3jLCLoag7tt+zrSHpZxP0jqznfL+Gdgwu7ay0dYgGYJXrQvq3iWloA=="],
"@oxlint/binding-linux-arm64-gnu": ["@oxlint/binding-linux-arm64-gnu@1.83.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-7lihXt3vKr+GIyapNbHrnFHm/biiW30le6Zv/DExbAFPF6YwCQXVFlONPFehxs0CpGO4CBfYPM9rdDT+XMoIlg=="],
"@oxlint/binding-linux-arm64-musl": ["@oxlint/binding-linux-arm64-musl@1.83.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-q63JalLYVkZiZvls1z3PPUnpmQluOMXp0khqQMznCeAPLGydfNY8JhvuA4WlK57JfrvikU8wB5lPVveqpIXvew=="],
"@oxlint/binding-linux-ppc64-gnu": ["@oxlint/binding-linux-ppc64-gnu@1.83.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-krQmDF+dRbxvdqVPV88ZuOoPPu8X5BuqDA8Hd+qcS4YMRQCb+nexA57DazgGsc/rGdKBe3QmV0mnv0bdpW/p5g=="],
"@oxlint/binding-linux-riscv64-gnu": ["@oxlint/binding-linux-riscv64-gnu@1.83.0", "", { "os": "linux", "cpu": "none" }, "sha512-MmOl8Y6txEAXZU1RG8Rr264jQ6D7VPmqFsU/45x/FeWsGe32hklTqGrLE6UxHzp5Rjt0wP+20tY8YXKgSFB3mw=="],
"@oxlint/binding-linux-riscv64-musl": ["@oxlint/binding-linux-riscv64-musl@1.83.0", "", { "os": "linux", "cpu": "none" }, "sha512-u1rMymh0W3JZkq370kzQsYPULGWqhE09pZRqnZvUSoYaI9pVO5yVX+iYIslmWuEgwuzH9YAaOsScJiobWCHoOw=="],
"@oxlint/binding-linux-s390x-gnu": ["@oxlint/binding-linux-s390x-gnu@1.83.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-y0zK3HNwGysu7rqtE+BQG/d0bx5gh/KwlOtghN8oWeK1KcWzeaLqtZrbm8owqdma1lFyrce/hTO5ismuNu+INQ=="],
"@oxlint/binding-linux-x64-gnu": ["@oxlint/binding-linux-x64-gnu@1.83.0", "", { "os": "linux", "cpu": "x64" }, "sha512-rS5gM0NgD7ngmuJmbIehsidtrOwKkLFwCQbKEeb9KuyQrrWNq5Zkn0uV6AYdXOMJ0grrWEiLwBuvMxt8w5vsNw=="],
"@oxlint/binding-linux-x64-musl": ["@oxlint/binding-linux-x64-musl@1.83.0", "", { "os": "linux", "cpu": "x64" }, "sha512-W2IH4EtpcPaWcvNGCA95YoDg4vxqE/ZiPCi3arrxEEpsK7+JQN9WYwrlYFx9pcdP6KPXqRqkv3zdQPHcx7b6YQ=="],
"@oxlint/binding-openharmony-arm64": ["@oxlint/binding-openharmony-arm64@1.83.0", "", { "os": "none", "cpu": "arm64" }, "sha512-6LyKkUyoajssTPLlZmDbZIbu4IZ5B4bGuRUnBgCGpEvHP3FQMaYITncHA/unPUo7q+Z+pIu2HhdkQ+8d1SG7iA=="],
"@oxlint/binding-win32-arm64-msvc": ["@oxlint/binding-win32-arm64-msvc@1.83.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-Uz/fObEtF0jmNJQJ8CGRBKfefYstS0/wjD3s6IGzP8nUwsJykHQJBiN3npHwKiGRGn/vvBEgNr4B3cCzmmatvg=="],
"@oxlint/binding-win32-ia32-msvc": ["@oxlint/binding-win32-ia32-msvc@1.83.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-u7XcvPW6Bk58tY5iWs2ESb0vJjoE/kuSpHxopbwp/p3ZtWVQXZ6wor5w3ssVTHOqd/v8b+QdhSFWQ4grEUNWpA=="],
"@oxlint/binding-win32-x64-msvc": ["@oxlint/binding-win32-x64-msvc@1.83.0", "", { "os": "win32", "cpu": "x64" }, "sha512-LZRubd7ph13QmAg4fFecTYVZkiYbROR2Htaxh/ufWRkDhPOm2wrwaEYR89e0YpPFD3dqBrPoxS7myBw5hmYA7Q=="],
"@popperjs/core": ["@popperjs/core@2.11.8", "", {}, "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A=="],
"@radix-ui/primitive": ["@radix-ui/primitive@1.1.7", "", {}, "sha512-rqWnm76nYT8HoNNqEjpgJ7Pw/DrBj5iBTrmEPo6HTX5+VJyBNOqTdv4g89G63HuR5g0AaENoAcH7Is5fF2kZ8Q=="],
@@ -271,12 +299,6 @@
"@radix-ui/react-use-layout-effect": ["@radix-ui/react-use-layout-effect@1.1.4", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-K20DkRkUwDnxEYMBPcg3Y6voLkEy5p5QQmszZgLngKKiC7dzBR/aEuK3w1qlx2JWDUNH6FluahYdgR3BP+QbYw=="],
"@react-aria/focus": ["@react-aria/focus@3.22.1", "", { "dependencies": { "@swc/helpers": "^0.5.0", "react-aria": "^3.48.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "sha512-CPxtkyrBi/HYY5P3lE/57sQ6qfa0lN8E55TOm89H0kNGv0lKt+/0zP7lWERzBjRr5IxBVrQX4gFEowBN52LPaA=="],
"@react-aria/interactions": ["@react-aria/interactions@3.28.1", "", { "dependencies": { "@react-types/shared": "^3.34.0", "@swc/helpers": "^0.5.0", "react-aria": "^3.48.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "sha512-Bqb+HrD5I5MHS2SKBhISYqo2SW8Y2dfzgF/Y1lIJq7xqLxheo9vzxPGEHhz+XzkgGfoqEJx8A6a3C7uiqS3HWA=="],
"@react-types/shared": ["@react-types/shared@3.36.1", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "sha512-AzsuD9OfxTOZMMvTRhlN3oHBwOmFN7tDh27LzqmHt4+uOgPhJT7ZM7/kVs/8/o0WxayMUIk3hBmCFRHv1FUoag=="],
"@remix-run/router": ["@remix-run/router@1.23.4", "", {}, "sha512-q7j5geK7xs3UJSdm9/iytUNclBnLmYx1EnSeCFXHPeutdqgIMeFeHtUZgS3EhlKxdBEAu8OwtJCwmLrEzpSs7Q=="],
"@rolldown/binding-android-arm-eabi": ["@rolldown/binding-android-arm-eabi@1.2.8", "", { "os": "android", "cpu": "arm" }, "sha512-tN5aztYkKCte4i5SIrrz5yK/HMjEuCqCSCJa418jOV8tZ1cBY3YF2otxB1ktPxzsLA1BeTqwapK0bfjxNvHJVw=="],
@@ -329,8 +351,6 @@
"@sindresorhus/merge-streams": ["@sindresorhus/merge-streams@4.0.0", "", {}, "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ=="],
"@swc/helpers": ["@swc/helpers@0.5.23", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw=="],
"@tailwindcss/node": ["@tailwindcss/node@4.3.3", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.24.1", "jiti": "^2.7.0", "lightningcss": "1.32.0", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.3.3" } }, "sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg=="],
"@tailwindcss/oxide": ["@tailwindcss/oxide@4.3.3", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.3.3", "@tailwindcss/oxide-darwin-arm64": "4.3.3", "@tailwindcss/oxide-darwin-x64": "4.3.3", "@tailwindcss/oxide-freebsd-x64": "4.3.3", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.3", "@tailwindcss/oxide-linux-arm64-gnu": "4.3.3", "@tailwindcss/oxide-linux-arm64-musl": "4.3.3", "@tailwindcss/oxide-linux-x64-gnu": "4.3.3", "@tailwindcss/oxide-linux-x64-musl": "4.3.3", "@tailwindcss/oxide-wasm32-wasi": "4.3.3", "@tailwindcss/oxide-win32-arm64-msvc": "4.3.3", "@tailwindcss/oxide-win32-x64-msvc": "4.3.3" } }, "sha512-krXjAikiaFSPaK/FkAQT5UTx3VormQaiZ5hBFlJZ9UFQGB/rwg1MZIhHAG9smMQRTdyJxP6Qt5MwMtdyU5FWrA=="],
@@ -365,10 +385,6 @@
"@tanstack/react-query": ["@tanstack/react-query@5.102.8", "", { "dependencies": { "@tanstack/query-core": "5.102.8" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-TYBea4OuXWD7MhaSHq069TWbFe7rcwWN6kzT7JF0OKi1K6c1gTv2IzD6A6ExJsCMozdkqBWeuIUZmu4KQg0O5A=="],
"@tanstack/react-virtual": ["@tanstack/react-virtual@3.14.12", "", { "dependencies": { "@tanstack/virtual-core": "3.17.10" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-EOJkBss/FRqzFrCpX8M96lIh+ZMc0jPt0LILhxWQ3tOHQ3XdVvuE0jceS2yZXpDtayNfpjeg2EnuYPm90CxIlA=="],
"@tanstack/virtual-core": ["@tanstack/virtual-core@3.17.10", "", {}, "sha512-eMorKWIi2nekElu+8DgSMJR4iR+sTDeh+W8VjKiMH79yUfEDJ3W9aL/96/wwbxNjyV6EfN39HKszSxjd90zAqQ=="],
"@ts-morph/common": ["@ts-morph/common@0.27.0", "", { "dependencies": { "fast-glob": "^3.3.3", "minimatch": "^10.0.1", "path-browserify": "^1.0.1" } }, "sha512-Wf29UqxWDpc+i61k3oIOzcUfQt79PIT9y/MWfAGlrkjg6lBC1hwDECLXPVJAhWjiGbfBCxZd65F/LIZF3+jeJQ=="],
"@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="],
@@ -1025,6 +1041,8 @@
"ora": ["ora@8.2.0", "", { "dependencies": { "chalk": "^5.3.0", "cli-cursor": "^5.0.0", "cli-spinners": "^2.9.2", "is-interactive": "^2.0.0", "is-unicode-supported": "^2.0.0", "log-symbols": "^6.0.0", "stdin-discarder": "^0.2.2", "string-width": "^7.2.0", "strip-ansi": "^7.1.0" } }, "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw=="],
"oxlint": ["oxlint@1.83.0", "", { "optionalDependencies": { "@oxlint/binding-android-arm-eabi": "1.83.0", "@oxlint/binding-android-arm64": "1.83.0", "@oxlint/binding-darwin-arm64": "1.83.0", "@oxlint/binding-darwin-x64": "1.83.0", "@oxlint/binding-freebsd-x64": "1.83.0", "@oxlint/binding-linux-arm-gnueabihf": "1.83.0", "@oxlint/binding-linux-arm-musleabihf": "1.83.0", "@oxlint/binding-linux-arm64-gnu": "1.83.0", "@oxlint/binding-linux-arm64-musl": "1.83.0", "@oxlint/binding-linux-ppc64-gnu": "1.83.0", "@oxlint/binding-linux-riscv64-gnu": "1.83.0", "@oxlint/binding-linux-riscv64-musl": "1.83.0", "@oxlint/binding-linux-s390x-gnu": "1.83.0", "@oxlint/binding-linux-x64-gnu": "1.83.0", "@oxlint/binding-linux-x64-musl": "1.83.0", "@oxlint/binding-openharmony-arm64": "1.83.0", "@oxlint/binding-win32-arm64-msvc": "1.83.0", "@oxlint/binding-win32-ia32-msvc": "1.83.0", "@oxlint/binding-win32-x64-msvc": "1.83.0" }, "peerDependencies": { "oxlint-tsgolint": ">=7.0.2001", "vite-plus": "*" }, "optionalPeers": ["oxlint-tsgolint", "vite-plus"], "bin": { "oxlint": "bin/oxlint" } }, "sha512-cyDzSzaw3uzP0TeCeq3lLRPPoaUxkbB4ZOXj+kn+5r+BX9V+4bNVGk9lxer+WrgcpebH4JxLlJ3KQjveVztOLQ=="],
"p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="],
"p-locate": ["p-locate@3.0.0", "", { "dependencies": { "p-limit": "^2.0.0" } }, "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ=="],
@@ -1091,8 +1109,6 @@
"react": ["react@19.3.0", "", {}, "sha512-E8LUcbtBWt20bbl2YoHfx4ZDBdxVTfOKtCZn9cDSJ4l6/nuoApcpIBcj47t2wZoVX8g2ZHuMHbiShgCR1T5Sog=="],
"react-aria": ["react-aria@3.52.1", "", { "dependencies": { "@internationalized/date": "^3.12.4", "@internationalized/number": "^3.6.8", "@internationalized/string": "^3.2.10", "@react-types/shared": "^3.36.1", "@swc/helpers": "^0.5.0", "aria-hidden": "^1.2.3", "clsx": "^2.0.0", "react-stately": "3.50.0", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "sha512-fdZZruC9/x/joCg0mhKGs5aHpwrXLCSZ4GOJmhhYyiE0ffyEsk9MLFt9LCOCF9tn8ErTD+UDQP4oDUSlZkmpCg=="],
"react-dom": ["react-dom@19.3.0", "", { "dependencies": { "scheduler": "^0.28.0" }, "peerDependencies": { "react": "^19.3.0" } }, "sha512-JDk8dgif51OjFoDE70+OT9ICyYr+69HlmihNwp1+Nsfbna3t5sIiCa9ZJktDmQ4/1b/rn26hIAR2uYXDMr5r0Q=="],
"react-fast-compare": ["react-fast-compare@3.2.2", "", {}, "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ=="],
@@ -1111,8 +1127,6 @@
"react-router-dom": ["react-router-dom@6.30.6", "", { "dependencies": { "@remix-run/router": "1.23.4", "react-router": "6.30.6" }, "peerDependencies": { "react": ">=16.8", "react-dom": ">=16.8" } }, "sha512-0RHKZz7wwffvkU+2MFVT2NnjK44ssLEV+m0CAJaS2Ksmorrwj7WxH00jO0SOCW26/tINUnJHToXblDs33I38YQ=="],
"react-stately": ["react-stately@3.50.0", "", { "dependencies": { "@internationalized/date": "^3.12.4", "@internationalized/number": "^3.6.8", "@internationalized/string": "^3.2.10", "@react-types/shared": "^3.36.1", "@swc/helpers": "^0.5.0", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "sha512-TnckvpDQGU0672wEaLZqdzvhuSeXWjgW6vijMWxiKOxc8CosQUfPGISdcZyfHqjX3XMjEtRxj4w9HumvX4h4mw=="],
"react-style-singleton": ["react-style-singleton@2.2.3", "", { "dependencies": { "get-nonce": "^1.0.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ=="],
"react-universal-interface": ["react-universal-interface@0.6.2", "", { "peerDependencies": { "react": "*", "tslib": "*" } }, "sha512-dg8yXdcQmvgR13RIlZbTRQOoUrDciFVoSBZILwjE2LFISxZZ8loVJKAkuzswl5js8BHda79bIb2b84ehU8IjXw=="],
@@ -1247,8 +1261,6 @@
"systeminformation": ["systeminformation@5.33.10", "", { "os": "!aix", "bin": { "systeminformation": "lib/cli.js" } }, "sha512-/NXbMVASt2UbSVgmWto4aBTIAeuYY7zOELpZybmNxqpsrbo5uYHpuEPf5nkyrPng0uG5YOb+Yv6s0FyKY4mDQg=="],
"tabbable": ["tabbable@6.5.0", "", {}, "sha512-wieBHXygIm7OyQOu5hQlkk62/WyCFYGlWg7L6/ZCUZwx0o398Zkn4pVmMyfYhfMG8kGrj/Krt8eIk6UKC6VzwA=="],
"tailwind-merge": ["tailwind-merge@3.6.0", "", {}, "sha512-uxL7qAVQriqRQPAyK3pj66VqskWqoZ37PW94jwOTwNfq/z9oyu1V+eqrZqtR2+fCiXdYOZe/Modt8GtvqNzu+w=="],
"tailwindcss": ["tailwindcss@4.3.3", "", {}, "sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ=="],
+4 -2
View File
@@ -6,14 +6,15 @@
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview"
"preview": "vite preview",
"lint": "oxlint",
"lint:fix": "oxlint --fix"
},
"dependencies": {
"@base-ui/react": "^1.8.0",
"@bytemd/plugin-gfm": "^1.22.0",
"@bytemd/react": "^1.22.0",
"@fontsource-variable/geist": "^5.3.0",
"@headlessui/react": "^2.2.10",
"@heroicons/react": "^2.2.0",
"@shikijs/core": "^4.4.3",
"@shikijs/engine-javascript": "^4.4.3",
@@ -56,6 +57,7 @@
"@types/react-dom": "^19.3.0",
"autoprefixer": "^10.5.6",
"esbuild": "^0.28.1",
"oxlint": "^1.83.0",
"postcss": "^8.5.28",
"postcss-import": "^16.2.0",
"postcss-nesting": "^13.0.2",
-1
View File
@@ -15,7 +15,6 @@
*/
import { ReactNode } from 'react';
import { Separator } from "@/components/ui/separator";
export default function Header({ title, breadcrumb, props }: { title: ReactNode, breadcrumb?: ReactNode, props?: ReactNode }) {
return (
-1
View File
@@ -38,7 +38,6 @@ import {
SidebarMenuButton,
SidebarMenuItem,
SidebarProvider,
useSidebar,
} from "@/components/ui/sidebar";
import {
DropdownMenu,
+1 -1
View File
@@ -5,7 +5,7 @@ import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const badgeVariants = cva(
"group/badge inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-4xl border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3!",
"group/badge inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-4xl border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-all focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3!",
{
variants: {
variant: {
+1 -1
View File
@@ -133,7 +133,7 @@ function DropdownMenuSubContent({
return (
<DropdownMenuContent
data-slot="dropdown-menu-sub-content"
className={cn("w-auto min-w-[96px] rounded-lg bg-popover p-1 text-popover-foreground shadow-lg ring-1 ring-foreground/10 duration-100 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95", className )}
className={cn("w-auto min-w-24 rounded-lg bg-popover p-1 text-popover-foreground shadow-lg ring-1 ring-foreground/10 duration-100 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95", className )}
align={align}
alignOffset={alignOffset}
side={side}
+1 -1
View File
@@ -16,7 +16,7 @@ function ScrollArea({
>
<ScrollAreaPrimitive.Viewport
data-slot="scroll-area-viewport"
className="size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1"
className="size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-3 focus-visible:ring-ring/50 focus-visible:outline-1"
>
{children}
</ScrollAreaPrimitive.Viewport>
+1 -1
View File
@@ -289,7 +289,7 @@ function SidebarRail({ className, ...props }: React.ComponentProps<"button">) {
onClick={toggleSidebar}
title="Toggle Sidebar"
className={cn(
"absolute inset-y-0 z-20 hidden w-4 transition-all ease-linear group-data-[side=left]:-right-4 group-data-[side=right]:left-0 after:absolute after:inset-y-0 after:start-1/2 after:w-[2px] hover:after:bg-sidebar-border sm:flex ltr:-translate-x-1/2 rtl:-translate-x-1/2",
"absolute inset-y-0 z-20 hidden w-4 transition-all ease-linear group-data-[side=left]:-right-4 group-data-[side=right]:left-0 after:absolute after:inset-y-0 after:start-1/2 after:w-0.5 hover:after:bg-sidebar-border sm:flex ltr:-translate-x-1/2 rtl:-translate-x-1/2",
"in-data-[side=left]:cursor-w-resize in-data-[side=right]:cursor-e-resize",
"[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize",
"group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full hover:group-data-[collapsible=offcanvas]:bg-sidebar",
+1 -1
View File
@@ -16,7 +16,7 @@ function Switch({
data-slot="switch"
data-size={size}
className={cn(
"peer group/switch relative inline-flex shrink-0 items-center rounded-full border border-transparent transition-all outline-none group-has-[:focus-visible]/field-label:border-transparent group-has-[:focus-visible]/field-label:ring-0 after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-[size=default]:h-[18.4px] data-[size=default]:w-[32px] data-[size=sm]:h-[14px] data-[size=sm]:w-[24px] dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 data-checked:bg-primary data-unchecked:bg-input dark:data-unchecked:bg-input/80 data-disabled:cursor-not-allowed data-disabled:opacity-50",
"peer group/switch relative inline-flex shrink-0 items-center rounded-full border border-transparent transition-all outline-none group-has-[:focus-visible]/field-label:border-transparent group-has-[:focus-visible]/field-label:ring-0 after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-[size=default]:h-[18.4px] data-[size=default]:w-8 data-[size=sm]:h-3.5 data-[size=sm]:w-6 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 data-checked:bg-primary data-unchecked:bg-input dark:data-unchecked:bg-input/80 data-disabled:cursor-not-allowed data-disabled:opacity-50",
className
)}
{...props}
+2 -2
View File
@@ -22,7 +22,7 @@ function Tabs({
}
const tabsListVariants = cva(
"group/tabs-list inline-flex w-fit items-center justify-center rounded-lg p-[3px] text-muted-foreground group-data-horizontal/tabs:h-9 group-data-vertical/tabs:h-fit group-data-vertical/tabs:flex-col data-[variant=line]:rounded-none",
"group/tabs-list inline-flex w-fit items-center justify-center rounded-lg p-0.75 text-muted-foreground group-data-horizontal/tabs:h-9 group-data-vertical/tabs:h-fit group-data-vertical/tabs:flex-col data-[variant=line]:rounded-none",
{
variants: {
variant: {
@@ -56,7 +56,7 @@ function TabsTrigger({ className, ...props }: TabsPrimitive.Tab.Props) {
<TabsPrimitive.Tab
data-slot="tabs-trigger"
className={cn(
"relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-1.5 py-0.5 text-sm font-medium whitespace-nowrap text-foreground/60 transition-all group-data-vertical/tabs:w-full group-data-vertical/tabs:justify-start hover:text-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1 focus-visible:outline-ring disabled:pointer-events-none disabled:opacity-50 has-data-[icon=inline-end]:pr-1 has-data-[icon=inline-start]:pl-1 aria-disabled:pointer-events-none aria-disabled:opacity-50 dark:text-muted-foreground dark:hover:text-foreground group-data-[variant=default]/tabs-list:data-active:shadow-sm group-data-[variant=line]/tabs-list:data-active:shadow-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
"relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-1.5 py-0.5 text-sm font-medium whitespace-nowrap text-foreground/60 transition-all group-data-vertical/tabs:w-full group-data-vertical/tabs:justify-start hover:text-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 focus-visible:outline-1 focus-visible:outline-ring disabled:pointer-events-none disabled:opacity-50 has-data-[icon=inline-end]:pr-1 has-data-[icon=inline-start]:pl-1 aria-disabled:pointer-events-none aria-disabled:opacity-50 dark:text-muted-foreground dark:hover:text-foreground group-data-[variant=default]/tabs-list:data-active:shadow-sm group-data-[variant=line]/tabs-list:data-active:shadow-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
"group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-active:bg-transparent dark:group-data-[variant=line]/tabs-list:data-active:border-transparent dark:group-data-[variant=line]/tabs-list:data-active:bg-transparent",
"data-active:bg-background data-active:text-foreground dark:data-active:border-input dark:data-active:bg-input/30 dark:data-active:text-foreground",
"after:absolute after:bg-foreground after:opacity-0 after:transition-opacity group-data-horizontal/tabs:after:inset-x-0 group-data-horizontal/tabs:after:bottom-[-5px] group-data-horizontal/tabs:after:h-0.5 group-data-vertical/tabs:after:inset-y-0 group-data-vertical/tabs:after:-right-1 group-data-vertical/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-active:after:opacity-100",
+1 -1
View File
@@ -6,7 +6,7 @@ import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const toggleVariants = cva(
"group/toggle inline-flex items-center justify-center gap-1 rounded-lg text-sm font-medium whitespace-nowrap transition-all outline-none hover:bg-muted hover:text-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 aria-pressed:bg-muted data-[state=on]:bg-muted dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
"group/toggle inline-flex items-center justify-center gap-1 rounded-lg text-sm font-medium whitespace-nowrap transition-all outline-none hover:bg-muted hover:text-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 aria-pressed:bg-muted data-[state=on]:bg-muted dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
{
variants: {
variant: {
+128 -167
View File
@@ -16,10 +16,9 @@
import axios from "axios";
import dayjs from "dayjs";
import { Dialog, Transition } from "@headlessui/react";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
import { Button } from "@/components/ui/button";
import { Fragment, useEffect, useState } from "react";
import { Dialog, DialogContent, DialogFooter } from "@/components/ui/dialog";
import { useEffect, useState } from "react";
import { Helmet, HelmetProvider } from "react-helmet-async";
import { Link, useParams, useSearchParams } from "react-router-dom";
import { Tooltip } from '../../utils';
@@ -322,171 +321,133 @@ export default function ({ localServer }: { localServer: string }) {
</div>
</div >
<Transition.Root show={createRunnerModal} as={Fragment}>
<Dialog as="div" className="relative z-10" onClose={setCreateRunnerModal}>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-10 overflow-y-auto">
<div className="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform overflow-hidden rounded-lg bg-white px-4 pt-5 pb-4 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg sm:p-6">
<label htmlFor="tagTemplate" className="block text-sm font-medium leading-6 text-gray-900">
<span className="text-red-600">*</span>Tag
</label>
<div className="relative mt-2 rounded-md shadow-sm">
<input
type="text"
id="tagTemplate"
name="tagTemplate"
placeholder="tag template"
className={(tagTemplateTextValid ? "block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6" : "block w-full rounded-md border-0 py-1.5 pr-10 text-red-900 ring-1 ring-inset ring-red-300 placeholder:text-red-300 focus:ring-2 focus:ring-inset focus:ring-red-500 sm:text-sm sm:leading-6")}
value={tagTemplateText}
onChange={e => {
setTagTemplateText(e.target.value);
}}
/>
{
tagTemplateTextValid ? (
<div></div>
) : (
<div className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-3">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="h-5 w-5 text-red-500">
<path strokeLinecap="round" strokeLinejoin="round" d="M12 9v3.75m9-.75a9 9 0 11-18 0 9 9 0 0118 0zm-9 3.75h.008v.008H12v-.008z" />
</svg>
</div>
)
}
</div>
<p className="mt-1 text-xs text-red-600">
{
tagTemplateTextValid ? (
<span></span>
) : (
<span>
Not a valid tag template, you can try 'main', '&#123;&#123;.ScmRef&#125;&#125;', '&#123;&#123;.ScmBrach&#125;&#125;'.
</span>
)
}
</p>
{
builderObj?.source !== "Dockerfile" ? (
<>
<label htmlFor="first-name" className="block text-sm font-medium leading-6 text-gray-900">
<span className="text-red-600">*</span>Branch
</label>
<div className="relative mt-2 rounded-md shadow-sm">
<input
type="text"
name="namespace"
placeholder="1-64 characters"
className={(branchTextValid ? "block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6" : "block w-full rounded-md border-0 py-1.5 pr-10 text-red-900 ring-1 ring-inset ring-red-300 placeholder:text-red-300 focus:ring-2 focus:ring-inset focus:ring-red-500 sm:text-sm sm:leading-6")}
value={branchText}
onChange={e => {
setBranchText(e.target.value);
}}
/>
{
branchTextValid ? (
<div></div>
) : (
<div className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-3">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="h-5 w-5 text-red-500">
<path strokeLinecap="round" strokeLinejoin="round" d="M12 9v3.75m9-.75a9 9 0 11-18 0 9 9 0 0118 0zm-9 3.75h.008v.008H12v-.008z" />
</svg>
</div>
)
}
</div>
<p className="mt-1 text-xs text-red-600">
{
branchTextValid ? (
<span></span>
) : (
<span>
Not a valid branch, you can try 'main', 'master', 'dev'.
</span>
)
}
</p>
</>
) : null
}
<label htmlFor="first-name" className="block text-sm font-medium text-gray-700">
Description
</label>
<div className="relative mt-2 rounded-md shadow-sm">
<textarea
name="description"
placeholder="30 characters"
className={(descriptionTextValid ? "block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6" : "block w-full rounded-md border-0 py-1.5 pr-10 text-red-900 ring-1 ring-inset ring-red-300 placeholder:text-red-300 focus:ring-2 focus:ring-inset focus:ring-red-500 sm:text-sm sm:leading-6")}
value={descriptionText}
onChange={e => setDescriptionText(e.target.value)}
/>
{
descriptionTextValid ? (
<div></div>
) : (
<div className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-3">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="h-5 w-5 text-red-500">
<path strokeLinecap="round" strokeLinejoin="round" d="M12 9v3.75m9-.75a9 9 0 11-18 0 9 9 0 0118 0zm-9 3.75h.008v.008H12v-.008z" />
</svg>
</div>
)
}
</div>
<p className="mt-1 text-xs text-red-600">
{
descriptionTextValid ? (
<span></span>
) : (
<span>
Not a valid description, max 50 characters.
</span>
)
}
</p>
<div className="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
<button
type="button"
className="inline-flex w-full justify-center rounded-md border border-transparent bg-indigo-500 px-4 py-2 text-base font-medium text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:bg-indigo-500 focus:ring-offset-2 sm:ml-3 sm:w-auto sm:text-sm"
onClick={() => createRunner()}
>
Create
</button>
<button
type="button"
className="mt-3 inline-flex w-full justify-center rounded-md border border-gray-300 bg-white px-4 py-2 text-base font-medium text-gray-700 shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 sm:mt-0 sm:w-auto sm:text-sm"
onClick={() => setCreateRunnerModal(false)}
>
Cancel
</button>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
<Dialog open={createRunnerModal} onOpenChange={setCreateRunnerModal}>
<DialogContent className="sm:max-w-lg" aria-label="Create runner">
<label htmlFor="tagTemplate" className="block text-sm font-medium leading-6 text-gray-900">
<span className="text-red-600">*</span>Tag
</label>
<div className="relative mt-2 rounded-md shadow-sm">
<input
type="text"
id="tagTemplate"
name="tagTemplate"
placeholder="tag template"
className={(tagTemplateTextValid ? "block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6" : "block w-full rounded-md border-0 py-1.5 pr-10 text-red-900 ring-1 ring-inset ring-red-300 placeholder:text-red-300 focus:ring-2 focus:ring-inset focus:ring-red-500 sm:text-sm sm:leading-6")}
value={tagTemplateText}
onChange={e => {
setTagTemplateText(e.target.value);
}}
/>
{
tagTemplateTextValid ? (
<div></div>
) : (
<div className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-3">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="h-5 w-5 text-red-500">
<path strokeLinecap="round" strokeLinejoin="round" d="M12 9v3.75m9-.75a9 9 0 11-18 0 9 9 0 0118 0zm-9 3.75h.008v.008H12v-.008z" />
</svg>
</div>
)
}
</div>
</Dialog>
</Transition.Root>
<p className="mt-1 text-xs text-red-600">
{
tagTemplateTextValid ? (
<span></span>
) : (
<span>
Not a valid tag template, you can try 'main', '&#123;&#123;.ScmRef&#125;&#125;', '&#123;&#123;.ScmBrach&#125;&#125;'.
</span>
)
}
</p>
{
builderObj?.source !== "Dockerfile" ? (
<>
<label htmlFor="first-name" className="block text-sm font-medium leading-6 text-gray-900">
<span className="text-red-600">*</span>Branch
</label>
<div className="relative mt-2 rounded-md shadow-sm">
<input
type="text"
name="namespace"
placeholder="1-64 characters"
className={(branchTextValid ? "block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6" : "block w-full rounded-md border-0 py-1.5 pr-10 text-red-900 ring-1 ring-inset ring-red-300 placeholder:text-red-300 focus:ring-2 focus:ring-inset focus:ring-red-500 sm:text-sm sm:leading-6")}
value={branchText}
onChange={e => {
setBranchText(e.target.value);
}}
/>
{
branchTextValid ? (
<div></div>
) : (
<div className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-3">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="h-5 w-5 text-red-500">
<path strokeLinecap="round" strokeLinejoin="round" d="M12 9v3.75m9-.75a9 9 0 11-18 0 9 9 0 0118 0zm-9 3.75h.008v.008H12v-.008z" />
</svg>
</div>
)
}
</div>
<p className="mt-1 text-xs text-red-600">
{
branchTextValid ? (
<span></span>
) : (
<span>
Not a valid branch, you can try 'main', 'master', 'dev'.
</span>
)
}
</p>
</>
) : null
}
<label htmlFor="first-name" className="block text-sm font-medium text-gray-700">
Description
</label>
<div className="relative mt-2 rounded-md shadow-sm">
<textarea
name="description"
placeholder="30 characters"
className={(descriptionTextValid ? "block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6" : "block w-full rounded-md border-0 py-1.5 pr-10 text-red-900 ring-1 ring-inset ring-red-300 placeholder:text-red-300 focus:ring-2 focus:ring-inset focus:ring-red-500 sm:text-sm sm:leading-6")}
value={descriptionText}
onChange={e => setDescriptionText(e.target.value)}
/>
{
descriptionTextValid ? (
<div></div>
) : (
<div className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-3">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="h-5 w-5 text-red-500">
<path strokeLinecap="round" strokeLinejoin="round" d="M12 9v3.75m9-.75a9 9 0 11-18 0 9 9 0 0118 0zm-9 3.75h.008v.008H12v-.008z" />
</svg>
</div>
)
}
</div>
<p className="mt-1 text-xs text-red-600">
{
descriptionTextValid ? (
<span></span>
) : (
<span>
Not a valid description, max 50 characters.
</span>
)
}
</p>
<DialogFooter>
<Button variant="outline" onClick={() => setCreateRunnerModal(false)}>
Cancel
</Button>
<Button onClick={() => createRunner()}>Create</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</>
)
File diff suppressed because it is too large. Load diff
+1 -2
View File
@@ -22,7 +22,6 @@ import {
ICodeRepositoryOwnerItem,
ICodeRepositoryOwnerList,
ICodeRepositoryUser3rdParty,
IHTTPError,
IOrder
} from '../../interfaces';
import { useNavigate, useParams } from 'react-router-dom';
@@ -134,7 +133,7 @@ export default function ({ localServer }: { localServer: string }) {
<HelmetProvider>
<Helmet><title>{t("common.codeRepository")}</title></Helmet>
</HelmetProvider>
<div className="min-h-screen flex overflow-hidden bg-background min-w-[1600px]">
<div className="min-h-screen flex overflow-hidden bg-background min-w-400">
<HeaderMenu localServer={localServer} item="coderepos" />
<div className="flex flex-col flex-1 max-h-screen">
<main className="relative focus:outline-none" tabIndex={0}>
+1 -1
View File
@@ -115,7 +115,7 @@ export default function ({ localServer }: { localServer: string }) {
<title>{t("common.codeRepository")}</title>
</Helmet>
</HelmetProvider>
<div className="min-h-screen flex overflow-hidden bg-white dark:bg-gray-950 min-w-[1600px]">
<div className="min-h-screen flex overflow-hidden bg-white dark:bg-gray-950 min-w-400">
<Menu localServer={localServer} item="coderepos" />
<div className="flex flex-col flex-1 max-h-screen">
<main className="relative z-0 focus:outline-none" tabIndex={0}>
-1
View File
@@ -15,7 +15,6 @@
*/
import axios from "axios";
import dayjs from 'dayjs';
import { Fragment, useEffect, useState } from "react";
import { Helmet, HelmetProvider } from 'react-helmet-async';
import { Link, useLocation, useParams, useSearchParams } from 'react-router-dom';
-1
View File
@@ -16,7 +16,6 @@
import Toast from 'react-hot-toast';
import axios from "axios";
import dayjs from 'dayjs';
import { Fragment, useEffect, useState } from "react";
import { Helmet, HelmetProvider } from 'react-helmet-async';
import { Link, useLocation, useNavigate, useParams, useSearchParams } from 'react-router-dom';
File diff suppressed because it is too large. Load diff
+245 -457
View File
@@ -19,13 +19,37 @@ import Toast from 'react-hot-toast';
import { Fragment, useEffect, useState } from "react";
import { Helmet, HelmetProvider } from 'react-helmet-async';
import { Link, useParams, useSearchParams } from 'react-router-dom';
import { CheckIcon, ChevronUpDownIcon, EllipsisVerticalIcon } from "@heroicons/react/20/solid";
import { ChevronUpDownIcon, EllipsisVerticalIcon } from "@heroicons/react/20/solid";
import { Button } from "@/components/ui/button";
import {
Combobox, ComboboxButton, ComboboxInput, ComboboxOption,
ComboboxOptions, Dialog, DialogPanel, DialogTitle, Listbox,
ListboxButton, ListboxOption, ListboxOptions, Menu, MenuItem,
MenuItems, Transition, TransitionChild
} from "@headlessui/react";
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from "@/components/ui/command";
import {
Dialog,
DialogContent,
DialogFooter,
DialogTitle,
} from "@/components/ui/dialog";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import Header from "../../components/Header";
import IMenu from "../../components/Menu";
@@ -200,7 +224,7 @@ export default function Member({ localServer }: { localServer: string }) {
)
}
/>
<div className="pt-1 pb-1 flex justify-between items-center min-h-[60px]">
<div className="pt-1 pb-1 flex justify-between items-center min-h-15">
<div className="px-4">
<div className="flex gap-4">
<div className="relative mt-2 flex items-center">
@@ -272,216 +296,107 @@ export default function Member({ localServer }: { localServer: string }) {
<Pagination limit={Settings.PageSize} page={page} setPage={setPage} total={total} />
</div>
</div>
<Transition show={createUserNamespaceModal} as={Fragment}>
<Dialog as="div" className="relative z-10" onClose={setCreateUserNamespaceModal}>
<TransitionChild
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" />
</TransitionChild>
<div className="fixed inset-0 z-10 overflow-y-auto">
<div className="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
<TransitionChild
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<DialogPanel className="relative transform rounded-lg bg-white px-6 pb-4 text-left shadow-xl transition-all">
<DialogTitle
as="h3"
className="text-lg font-medium leading-6 text-gray-900 border-b pt-4 pb-4"
>
Add member
</DialogTitle>
<div className="flex flex-col gap-0 mt-4">
<div className="grid grid-cols-6 gap-4">
<div className="col-span-2 flex flex-row">
<label htmlFor="usernameText" className="block text-sm font-medium leading-6 text-gray-900 my-auto">
<div className="flex">
<span className="text-red-600">*</span>
<span className="leading-6 ">User</span>
<span>:</span>
</div>
</label>
</div>
<div className="col-span-4">
<Combobox value={userSelected}
onChange={(user: IUserItem | null) => {
if (!user) {
return;
}
setUserSelected(user);
}}>
<div className="relative mt-1">
<div className="w-full relative cursor-default overflow-hidden rounded-lg bg-white text-left shadow focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75 focus-visible:ring-offset-2 focus-visible:ring-offset-teal-300 sm:text-sm">
<ComboboxInput
id="namespace"
className="w-full border-none py-2 pl-3 pr-10 text-sm leading-5 text-gray-900 focus:ring-0"
displayValue={(user: IUserItem) => user.username}
onChange={event => {
setUserSearch(event.target.value);
}}
/>
<ComboboxButton className="absolute inset-y-0 right-0 flex items-center pr-2">
<ChevronUpDownIcon
className="h-5 w-5 text-gray-400"
aria-hidden="true"
/>
</ComboboxButton>
</div>
<Transition
as={Fragment}
leave="transition ease-in duration-100"
leaveFrom="opacity-100"
leaveTo="opacity-0"
afterLeave={() => setUserSearch('')}
>
<ComboboxOptions className="absolute mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm z-10">
{
userList?.length === 0 ? (
<div className="relative cursor-default select-none py-2 px-4 text-gray-700">
Nothing found.
</div>
) : (
userList?.map(user => (
<ComboboxOption
key={user.id}
className={`relative cursor-pointer select-none`}
value={user}
>
{({ focus, selected }) => (
<span className={`block truncate font-normal py-2 px-4 ${focus ? 'bg-gray-200 text-gray-800' : 'text-gray-900'}`}
>
{user.username}
</span>
)}
</ComboboxOption>
))
)
}
</ComboboxOptions>
</Transition>
</div>
</Combobox>
</div>
</div>
<div className="grid grid-cols-6 gap-4">
<div className="col-span-2"></div>
<div className="col-span-4">
{
userSelectedValid ? null : (
<p className="mt-1 text-xs text-red-600">
<span>
Please select a user.
</span>
</p>
)
}
</div>
</div>
<div className="grid grid-cols-6 gap-4 mt-4">
<div className="col-span-2 flex flex-row">
<label htmlFor="usernameText" className="block text-sm font-medium leading-6 text-gray-900 my-auto">
<div className="flex">
<span className="text-red-600">*</span>
<span className="leading-6 ">Role</span>
<span>:</span>
</div>
</label>
</div>
<div className="col-span-4">
<Listbox
value={addNamespaceRoleRole}
onChange={(source: string) => {
setAddNamespaceRoleRole(source);
}}>
<div className="relative w-full">
<ListboxButton
className={() => {
let cursor = ''
if ((searchParams.get('code_repository_stick') || '') === 'true') {
cursor = 'cursor-not-allowed ';
} else {
cursor = 'cursor-pointer ';
}
return cursor + "relative w-full rounded-lg bg-white py-2 pl-3 pr-10 text-left shadow focus:outline-none focus-visible:border-indigo-500 focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75 focus-visible:ring-offset-2 focus-visible:ring-offset-orange-300 sm:text-sm"
}}
>
<span className="block truncate">{addNamespaceRoleRole}</span>
<span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
<ChevronUpDownIcon
className="h-5 w-5 text-gray-400"
aria-hidden="true"
/>
</span>
</ListboxButton>
<ListboxOptions
className="absolute mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm z-10"
>
{
namespaceRoles.map(source => (
<ListboxOption key={source.name} value={source.name} className={({ active }) =>
`relative cursor-pointer select-none py-2 pl-10 pr-4 ${active ? 'bg-gray-100 text-gray-900' : 'text-gray-900'
}`
}>
{({ selected }) => (
<>
<span
className={`block truncate ${selected ? 'font-medium' : 'font-normal'
}`}
>
{source.name}
</span>
{
selected ? (
<span className="absolute inset-y-0 left-0 flex items-center pl-3 text-gray-600">
<CheckIcon className="h-5 w-5" aria-hidden="true" />
</span>
) : null
}
</>
)}
</ListboxOption>
))
}
</ListboxOptions>
</div>
</Listbox>
</div>
</div>
<div className="flex flex-row-reverse mt-5">
<button
type="button"
className="inline-flex w-full justify-center rounded-md border border-transparent bg-indigo-500 px-4 py-2 text-base font-medium text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:bg-indigo-500 focus:ring-offset-2 sm:ml-3 sm:w-auto sm:text-sm"
onClick={e => addMember()}
>
Add
</button>
<button
type="button"
className="mt-3 inline-flex w-full justify-center rounded-md border border-gray-300 bg-white px-4 py-2 text-base font-medium text-gray-700 shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 sm:mt-0 sm:w-auto sm:text-sm"
onClick={e => { setCreateUserNamespaceModal(false) }}
>
Cancel
</button>
</div>
<Dialog open={createUserNamespaceModal} onOpenChange={setCreateUserNamespaceModal}>
<DialogContent className="sm:max-w-lg">
<DialogTitle className="border-b pb-4">Add member</DialogTitle>
<div className="flex flex-col gap-0 mt-4">
<div className="grid grid-cols-6 gap-4">
<div className="col-span-2 flex flex-row">
<label htmlFor="usernameText" className="block text-sm font-medium leading-6 text-gray-900 my-auto">
<div className="flex">
<span className="text-red-600">*</span>
<span className="leading-6 ">User</span>
<span>:</span>
</div>
</DialogPanel>
</TransitionChild>
</label>
</div>
<div className="col-span-4">
<Popover>
<PopoverTrigger
render={
<Button variant="outline" className="w-full justify-between font-normal">
{userSelected?.username || "Select user"}
<ChevronUpDownIcon className="size-4 text-muted-foreground" aria-hidden="true" />
</Button>
}
/>
<PopoverContent align="start" className="w-(--anchor-width) p-0">
<Command shouldFilter={false}>
<CommandInput
placeholder="Search user"
value={userSearch}
onValueChange={setUserSearch}
/>
<CommandList>
<CommandEmpty>Nothing found.</CommandEmpty>
<CommandGroup>
{userList?.map(user => (
<CommandItem
key={user.id}
value={user.username}
onSelect={() => setUserSelected(user)}
>
{user.username}
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
</div>
</div>
<div className="grid grid-cols-6 gap-4">
<div className="col-span-2"></div>
<div className="col-span-4">
{
userSelectedValid ? null : (
<p className="mt-1 text-xs text-red-600">
<span>
Please select a user.
</span>
</p>
)
}
</div>
</div>
<div className="grid grid-cols-6 gap-4 mt-4">
<div className="col-span-2 flex flex-row">
<label htmlFor="usernameText" className="block text-sm font-medium leading-6 text-gray-900 my-auto">
<div className="flex">
<span className="text-red-600">*</span>
<span className="leading-6 ">Role</span>
<span>:</span>
</div>
</label>
</div>
<div className="col-span-4">
<Select
value={addNamespaceRoleRole}
onValueChange={(source) => { if (source) setAddNamespaceRoleRole(source); }}
>
<SelectTrigger className="w-full">
<SelectValue />
</SelectTrigger>
<SelectContent>
{namespaceRoles.map(source => (
<SelectItem key={source.name} value={source.name}>
{source.name}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
</div>
</Dialog>
</Transition>
<DialogFooter>
<Button variant="outline" onClick={() => setCreateUserNamespaceModal(false)}>
Cancel
</Button>
<Button onClick={() => addMember()}>Add</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</Fragment >
);
}
@@ -547,254 +462,127 @@ function TableItem({ localServer, index, namespace, userSelectedArg, member, set
<td className="pr-3 whitespace-nowrap" onClick={e => {
e.stopPropagation();
}}>
<Menu as="div" className="relative flex-none" onClick={e => {
e.stopPropagation();
}}>
<Menu.Button className="mx-auto -m-2.5 block p-2.5 text-gray-500 hover:text-gray-900 margin">
<span className="sr-only">Open options</span>
<EllipsisVerticalIcon className="h-5 w-5" aria-hidden="true" />
</Menu.Button>
<Transition
as={Fragment}
enter="transition ease-out duration-100"
enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<MenuItems className={(index > 10 ? "menu-action-top" : "mt-2") + " text-left absolute right-0 z-10 w-20 origin-top-right rounded-md bg-white py-2 shadow-lg ring-1 ring-gray-900/5 focus:outline-none"} >
<MenuItem>
{({ active }) => (
<div
className={
(active ? 'bg-gray-100' : '') +
' block px-3 py-1 text-sm leading-6 text-gray-900 cursor-pointer'
}
onClick={e => setUpdateUserNamespaceModal(true)}
>
Update
</div>
)}
</MenuItem>
<MenuItem>
{({ active }) => (
<div
className={
(active ? 'bg-gray-50' : '') + ' block px-3 py-1 text-sm leading-6 text-gray-900 hover:text-white hover:bg-red-600 cursor-pointer'
}
onClick={e => deleteMember()}
>
Delete
</div>
)}
</MenuItem>
</MenuItems>
</Transition>
</Menu>
<DropdownMenu>
<DropdownMenuTrigger
render={
<Button variant="ghost" size="icon-sm" className="text-gray-500">
<span className="sr-only">Open options</span>
<EllipsisVerticalIcon className="size-5" aria-hidden="true" />
</Button>
}
/>
<DropdownMenuContent align="end" className="w-28">
<DropdownMenuItem onClick={() => setUpdateUserNamespaceModal(true)}>
Update
</DropdownMenuItem>
<DropdownMenuItem variant="destructive" onClick={() => deleteMember()}>
Delete
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</td>
<td className="absolute hidden" onClick={e => { e.preventDefault() }}>
<Transition show={updateUserNamespaceModal} as={Fragment}>
<Dialog as="div" className="relative z-10" onClose={setUpdateUserNamespaceModal}>
<TransitionChild
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" />
</TransitionChild>
<div className="fixed inset-0 z-10 overflow-y-auto">
<div className="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
<TransitionChild
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<DialogPanel className="relative transform rounded-lg bg-white px-6 pb-4 text-left shadow-xl transition-all">
<DialogTitle
as="h3"
className="text-lg font-medium leading-6 text-gray-900 border-b pt-4 pb-4"
>
Update member
</DialogTitle>
<div className="flex flex-col gap-0 mt-4">
{/* <div className="grid grid-cols-6 gap-4">
<div className="col-span-2 flex flex-row">
<label htmlFor="usernameText" className="block text-sm font-medium leading-6 text-gray-900 my-auto">
<div className="flex">
<span className="text-red-600">*</span>
<span className="leading-6 ">User</span>
<span>:</span>
</div>
</label>
</div>
<div className="col-span-4">
<Combobox value={userSelected}
onChange={(user: IUserItem) => {
setUserSelected(user);
}}>
<div className="relative mt-1">
<div className="w-full relative cursor-default overflow-hidden rounded-lg bg-white text-left shadow focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75 focus-visible:ring-offset-2 focus-visible:ring-offset-teal-300 sm:text-sm">
<Combobox.Input
id="namespace"
className="w-full border-none py-2 pl-3 pr-10 text-sm leading-5 text-gray-900 focus:ring-0"
displayValue={(user: IUserItem) => user.username}
onChange={event => {
setUserSearch(event.target.value);
}}
/>
<Combobox.Button className="absolute inset-y-0 right-0 flex items-center pr-2">
<ChevronUpDownIcon
className="h-5 w-5 text-gray-400"
aria-hidden="true"
/>
</Combobox.Button>
</div>
<Transition
as={Fragment}
leave="transition ease-in duration-100"
leaveFrom="opacity-100"
leaveTo="opacity-0"
afterLeave={() => setUserSearch('')}
>
<Combobox.Options className="absolute mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm z-10">
{
userList.length === 0 ? (
<div className="relative cursor-default select-none py-2 px-4 text-gray-700">
Nothing found.
</div>
) : (
userList.map(user => (
<Combobox.Option
key={user.id}
className={({ active }) =>
`relative cursor-pointer select-none py-2 pl-4 pr-4 ${active ? 'bg-gray-200 text-gray-800' : 'text-gray-900'
}`
}
value={user}
>
<span className={`block truncate font-normal`}>
{user.username}
</span>
</Combobox.Option>
))
)
}
</Combobox.Options>
</Transition>
</div>
</Combobox>
</div>
</div> */}
{/* <div className="grid grid-cols-6 gap-4">
<div className="col-span-2"></div>
<div className="col-span-4">
{
userSelectedValid ? null : (
<p className="mt-1 text-xs text-red-600">
<span>
Please select a user.
</span>
</p>
)
}
</div>
</div> */}
<div className="grid grid-cols-6 gap-4">
<div className="col-span-2 flex flex-row">
<label htmlFor="usernameText" className="block text-sm font-medium leading-6 text-gray-900 my-auto">
<div className="flex">
<span className="text-red-600">*</span>
<span className="leading-6 ">Role</span>
<span>:</span>
</div>
</label>
</div>
<div className="col-span-4">
<Listbox
value={addNamespaceRoleRole}
onChange={(source: string) => {
setAddNamespaceRoleRole(source);
}}>
<div className="relative w-full">
<ListboxButton
className={() => {
let cursor = 'cursor-pointer '
return cursor + "relative w-full rounded-lg bg-white py-2 pl-3 pr-10 text-left shadow focus:outline-none focus-visible:border-indigo-500 focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75 focus-visible:ring-offset-2 focus-visible:ring-offset-orange-300 sm:text-sm min-w-[200px]"
}}
>
<span className="block truncate">{addNamespaceRoleRole}</span>
<span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
<ChevronUpDownIcon
className="h-5 w-5 text-gray-400"
aria-hidden="true"
/>
</span>
</ListboxButton>
<ListboxOptions className="absolute mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm z-10">
{
namespaceRoles.map(source => (
<ListboxOption key={source.name} value={source.name} className={({ active }) =>
`relative cursor-pointer select-none py-2 pl-10 pr-4 ${active ? 'bg-gray-100 text-gray-900' : 'text-gray-900'
}`
}>
{({ selected }) => (
<>
<span
className={`block truncate ${selected ? 'font-medium' : 'font-normal'
}`}
>
{source.name}
</span>
{
selected ? (
<span className="absolute inset-y-0 left-0 flex items-center pl-3 text-gray-600">
<CheckIcon className="h-5 w-5" aria-hidden="true" />
</span>
) : null
}
</>
)}
</ListboxOption>
))
}
</ListboxOptions>
</div>
</Listbox>
</div>
</div>
<div className="flex flex-row-reverse mt-5">
<button
type="button"
className="inline-flex w-full justify-center rounded-md border border-transparent bg-indigo-500 px-4 py-2 text-base font-medium text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:bg-indigo-500 focus:ring-offset-2 sm:ml-3 sm:w-auto sm:text-sm"
onClick={e => updateMember()}
>
Update
</button>
<button
type="button"
className="mt-3 inline-flex w-full justify-center rounded-md border border-gray-300 bg-white px-4 py-2 text-base font-medium text-gray-700 shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 sm:mt-0 sm:w-auto sm:text-sm"
onClick={e => { setUpdateUserNamespaceModal(false) }}
>
Cancel
</button>
</div>
<Dialog open={updateUserNamespaceModal} onOpenChange={setUpdateUserNamespaceModal}>
<DialogContent className="sm:max-w-lg">
<DialogTitle className="border-b pb-4">Update member</DialogTitle>
<div className="flex flex-col gap-0 mt-4">
{/* <div className="grid grid-cols-6 gap-4">
<div className="col-span-2 flex flex-row">
<label htmlFor="usernameText" className="block text-sm font-medium leading-6 text-gray-900 my-auto">
<div className="flex">
<span className="text-red-600">*</span>
<span className="leading-6 ">User</span>
<span>:</span>
</div>
</DialogPanel>
</TransitionChild>
</label>
</div>
<div className="col-span-4">
<Popover>
<PopoverTrigger
render={
<Button variant="outline" className="w-full justify-between font-normal">
{userSelected?.username || "Select user"}
<ChevronUpDownIcon className="size-4 text-muted-foreground" aria-hidden="true" />
</Button>
}
/>
<PopoverContent align="start" className="w-(--anchor-width) p-0">
<Command shouldFilter={false}>
<CommandInput
placeholder="Search user"
value={userSearch}
onValueChange={setUserSearch}
/>
<CommandList>
<CommandEmpty>Nothing found.</CommandEmpty>
<CommandGroup>
{userList?.map(user => (
<CommandItem
key={user.id}
value={user.username}
onSelect={() => setUserSelected(user)}
>
{user.username}
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
</div>
</div> */}
{/* <div className="grid grid-cols-6 gap-4">
<div className="col-span-2"></div>
<div className="col-span-4">
{
userSelectedValid ? null : (
<p className="mt-1 text-xs text-red-600">
<span>
Please select a user.
</span>
</p>
)
}
</div>
</div> */}
<div className="grid grid-cols-6 gap-4">
<div className="col-span-2 flex flex-row">
<label htmlFor="usernameText" className="block text-sm font-medium leading-6 text-gray-900 my-auto">
<div className="flex">
<span className="text-red-600">*</span>
<span className="leading-6 ">Role</span>
<span>:</span>
</div>
</label>
</div>
<div className="col-span-4">
<Select
value={addNamespaceRoleRole}
onValueChange={(source) => { if (source) setAddNamespaceRoleRole(source); }}
>
<SelectTrigger className="w-full">
<SelectValue />
</SelectTrigger>
<SelectContent>
{namespaceRoles.map(source => (
<SelectItem key={source.name} value={source.name}>
{source.name}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
</div>
</Dialog>
</Transition>
<DialogFooter>
<Button variant="outline" onClick={() => setUpdateUserNamespaceModal(false)}>
Cancel
</Button>
<Button onClick={() => updateMember()}>Update</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</td>
</tr>
)
+1 -1
View File
@@ -199,7 +199,7 @@ export default function ({ localServer }: { localServer: string }) {
</Tabs>
}
/>
<div className="pt-3 pb-3 flex justify-between items-center min-h-[60px]">
<div className="pt-3 pb-3 flex justify-between items-center min-h-15">
<div className="px-4">
<div className="relative flex items-center">
<Label htmlFor="repositorySearch" className="absolute -top-2 left-2 inline-block bg-background px-1 text-xs font-medium text-foreground z-10">
-1
View File
@@ -18,7 +18,6 @@ import axios from "axios";
import dayjs from "dayjs";
import { Fragment, useEffect, useState } from "react";
import { Helmet, HelmetProvider } from 'react-helmet-async';
import { Link, useParams } from 'react-router-dom';
import Header from "../../components/Header";
import Menu from "../../components/Menu";
+73 -109
View File
@@ -1,4 +1,3 @@
import { Dialog, DialogTitle, Menu, MenuButton, MenuItem, MenuItems, Transition } from "@headlessui/react";
/**
* Copyright 2023 sigma
*
@@ -33,6 +32,21 @@ import Settings from "../../Settings";
import Toast from "../../components/Notification";
import distros, { distroName } from '../../utils/distros';
import { useTranslation } from "../../i18n/useTranslation";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogTitle,
} from "@/components/ui/dialog";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import {
IArtifact,
IEndpoint,
@@ -46,7 +60,6 @@ import {
IUserSelf,
IVuln
} from "../../interfaces";
import { NamespaceRole, UserRole } from "../../interfaces/enums";
import { Tooltip, trimHTTP } from "../../utils";
export default function Tag({ localServer }: { localServer: string }) {
@@ -258,69 +271,36 @@ export default function Tag({ localServer }: { localServer: string }) {
</div>
</main>
<div className="flex-1 overflow-y-auto">
<Transition.Root show={deleteTagModal} as={Fragment}>
<Dialog as="div" className="relative z-10" onClose={setDeleteTagModal}>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-10 overflow-y-auto">
<div className="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform overflow-hidden rounded-lg bg-white px-4 pb-4 pt-5 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg sm:p-6">
<div className="sm:flex sm:items-start">
<div className="mx-auto flex h-12 w-12 shrink-0 items-center justify-center rounded-full bg-red-100 sm:mx-0 sm:h-10 sm:w-10">
<ExclamationTriangleIcon className="h-6 w-6 text-red-600" aria-hidden="true" />
</div>
<div className="mt-3 text-center sm:ml-4 sm:mt-0 sm:text-left">
<DialogTitle as="h3" className="text-base font-semibold leading-6 text-gray-900">
Delete tag
</DialogTitle>
<div className="mt-2">
<p className="text-sm text-gray-500">
Are you sure you want to delete the tag <span className="text-black font-medium">{deleteTagModalTag.name}</span>
</p>
</div>
</div>
</div>
<div className="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
<button
type="button"
className="inline-flex w-full justify-center rounded-md bg-red-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-red-500 sm:ml-3 sm:w-auto"
onClick={e => { setDeleteTagModal(false); deleteTag(deleteTagModalTag.id); }}
>
Delete
</button>
<button
type="button"
className="mt-3 inline-flex w-full justify-center rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 sm:mt-0 sm:w-auto"
onClick={() => setDeleteTagModal(false)}
>
Cancel
</button>
</div>
</Dialog.Panel>
</Transition.Child>
<Dialog open={deleteTagModal} onOpenChange={setDeleteTagModal}>
<DialogContent className="sm:max-w-lg">
<div className="flex items-start gap-4">
<div className="mx-auto flex size-10 shrink-0 items-center justify-center rounded-full bg-red-100 sm:mx-0">
<ExclamationTriangleIcon className="size-6 text-red-600" aria-hidden="true" />
</div>
<div className="text-center sm:text-left">
<DialogTitle>Delete tag</DialogTitle>
<DialogDescription className="mt-2">
Are you sure you want to delete the tag{' '}
<span className="text-foreground font-medium">{deleteTagModalTag.name}</span>
</DialogDescription>
</div>
</div>
</Dialog>
</Transition.Root>
<DialogFooter>
<Button
variant="outline"
onClick={() => setDeleteTagModal(false)}
>
Cancel
</Button>
<Button
variant="destructive"
onClick={() => { setDeleteTagModal(false); deleteTag(deleteTagModalTag.id); }}
>
Delete
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
<div className="flex flex-col border-b border-gray-200">
{
tagList.items?.map((tag, index) => {
@@ -369,43 +349,27 @@ export default function Tag({ localServer }: { localServer: string }) {
</code>
</div>
<div className="flex flex-col justify-center">
<Menu as="div" className="relative flex-none" onClick={e => {
e.stopPropagation();
}}>
<MenuButton className="mx-auto my-auto block p-1 text-gray-500 hover:text-gray-900 margin">
<span className="sr-only">Open options</span>
<EllipsisVerticalIcon className="h-5 w-5" aria-hidden="true" />
</MenuButton>
<Transition
as={Fragment}
enter="transition ease-out duration-100"
enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<MenuItems className={(index > 10 ? "menu-action-top" : "mt-2") + " text-left absolute right-0 z-10 w-20 origin-top-right rounded-md bg-white py-2 shadow-lg ring-1 ring-gray-900/5 focus:outline-none"} >
<MenuItem>
{({ active }) => (
<div
className={
(active ? 'bg-gray-50' : '') +
(((userObj.role == UserRole.Admin || userObj.role == UserRole.Root || (namespaceObj.role != undefined && (namespaceObj.role == NamespaceRole.Admin || namespaceObj.role == NamespaceRole.Manager)))) ? ' cursor-pointer' : ' cursor-not-allowed') +
' block px-3 py-1 text-sm leading-6 text-gray-900 hover:text-white hover:bg-red-600 cursor-pointer'
}
onClick={e => {
setDeleteTagModal(true);
setDeleteTagModalTag(tag);
}}
>
Delete
</div>
)}
</MenuItem>
</MenuItems>
</Transition>
</Menu>
<DropdownMenu>
<DropdownMenuTrigger
render={
<Button variant="ghost" size="icon-sm" className="text-gray-500">
<span className="sr-only">Open options</span>
<EllipsisVerticalIcon className="size-5" aria-hidden="true" />
</Button>
}
/>
<DropdownMenuContent align="end" className="w-20">
<DropdownMenuItem
variant="destructive"
onClick={() => {
setDeleteTagModal(true);
setDeleteTagModalTag(tag);
}}
>
Delete
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
{/* first row end */}
@@ -520,15 +484,15 @@ function DetailItem({ artifact }: { artifact: IArtifact }) {
let imageConfigObj = JSON.parse(artifact.config_raw) as IImageConfig;
return (
<tr className="hover:bg-gray-50 cursor-pointer">
<td className="text-left w-[180px]">
<td className="text-left w-45">
<code className="text-xs underline underline-offset-1 text-blue-600 hover:text-blue-500">
{cutDigest(artifact.digest)}
</code>
</td>
<td className="text-left text-xs w-[180px] capitalize">
<td className="text-left text-xs w-45 capitalize">
{artifact.type}
</td>
<td className="text-left text-xs w-[180px]">
<td className="text-left text-xs w-45">
<div className='flex gap-1'>
{distros(sbomObj.distro?.name) === "" ? "" : (
<img src={"/distros/" + distros(sbomObj.distro.name)} alt={sbomObj.distro.name} className="w-4 h-4 inline relative" />
@@ -538,7 +502,7 @@ function DetailItem({ artifact }: { artifact: IArtifact }) {
</div>
</div>
</td>
<td className="text-left text-xs w-[180px]">
<td className="text-left text-xs w-45">
{
imageConfigObj.os === undefined ||
imageConfigObj.architecture === undefined ||
@@ -548,22 +512,22 @@ function DetailItem({ artifact }: { artifact: IArtifact }) {
)
}
</td>
<td className="text-left text-xs w-[180px]">
<td className="text-left text-xs w-45">
Verified
</td>
<td className="text-left text-xs w-[180px]">
<td className="text-left text-xs w-45">
{(artifact.pull_times || 0) > 0 ? dayjs().to(dayjs(artifact.last_pull)) : "Never pulled"}
</td>
{/* <td className="text-right text-xs w-[180px]">
{/* <td className="text-right text-xs w-45">
{artifact.pull_times}
</td> */}
<td className="text-right text-xs w-[220px]">
<td className="text-right text-xs w-55">
<span className="bg-red-800 text-white text-xs font-medium mr-1 px-2 py-0.5 dark:bg-red-900 dark:text-red-300"><span>{vulnerabilityObj.critical || 0}</span> C</span>
<span className="bg-red-300 text-gray-800 text-xs font-medium mr-1 px-2 py-0.5 dark:bg-red-900 dark:text-red-300">{vulnerabilityObj.high || 0} H</span>
<span className="bg-amber-400 text-gray-800 text-xs font-medium mr-1 px-2 py-0.5 dark:bg-red-900 dark:text-red-300">{vulnerabilityObj.medium || 0} M</span>
<span className="bg-amber-200 text-gray-800 text-xs font-medium px-2 py-0.5 dark:bg-red-900 dark:text-red-300">{vulnerabilityObj.low || 0} L</span>
</td>
<td className="text-right text-xs w-[180px]">
<td className="text-right text-xs w-45">
{humanFormat(artifact.blob_size || 0)}
</td>
</tr>
File diff suppressed because it is too large. Load diff
+89 -241
View File
@@ -1,4 +1,3 @@
import { Dialog, Menu, Transition } from "@headlessui/react";
/**
* Copyright 2024 sigma
*
@@ -19,7 +18,8 @@ import axios from "axios";
import dayjs from "dayjs";
import { Fragment, useEffect, useState } from "react";
import { Helmet, HelmetProvider } from 'react-helmet-async';
import { Dialog as ShadDialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogTitle } from "@/components/ui/dialog";
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
import { Link, useParams, useSearchParams, useLocation } from 'react-router-dom';
@@ -30,7 +30,6 @@ import Pagination from "../../components/Pagination";
import Settings from "../../Settings";
import { IHTTPError, INamespaceItem, IOrder, IUserSelf, IWebhookLogItem, IWebhookLogList } from "../../interfaces";
import OrderHeader from "../../components/OrderHeader";
import { EllipsisVertical, TriangleAlert } from "lucide-react";
import { EllipsisVerticalIcon, ExclamationTriangleIcon } from "@heroicons/react/24/outline";
import { NamespaceRole, UserRole } from "../../interfaces/enums";
@@ -157,7 +156,7 @@ export default function ({ localServer }: { localServer: string }) {
<div
id="tooltip-webhook-retry-duration"
role="tooltip"
className="absolute z-50 invisible inline-block px-3 py-2 text-sm font-medium text-white transition-opacity duration-300 bg-gray-900 rounded-lg shadow-sm opacity-0 tooltip dark:bg-gray-700 w-[220px]">
className="absolute z-50 invisible inline-block px-3 py-2 text-sm font-medium text-white transition-opacity duration-300 bg-gray-900 rounded-lg shadow-sm opacity-0 tooltip dark:bg-gray-700 w-55">
Less than 10, unit is second.
</div>
<div className="min-h-screen flex overflow-hidden bg-white">
@@ -200,7 +199,7 @@ export default function ({ localServer }: { localServer: string }) {
</div>
)
} />
<div className="pt-1 pb-1 flex justify-between items-center min-h-[60px]">
<div className="pt-1 pb-1 flex justify-between items-center min-h-15">
<div className="px-4">
<div className="flex gap-4">
<div className="relative mt-2 flex items-center">
@@ -264,74 +263,35 @@ export default function ({ localServer }: { localServer: string }) {
<Pagination limit={Settings.PageSize} page={page} setPage={setPage} total={total} />
</div>
</div>
<Transition.Root show={webhookPingModal} as={Fragment}>
<Dialog as="div" className="relative z-10" onClose={setWebhookPingModal}>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-10 overflow-y-auto">
<div className="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform overflow-hidden rounded-lg bg-white px-4 pb-4 pt-5 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg sm:p-6 min-w-[600px]">
<div className="sm:flex sm:items-start">
<div className="mx-auto flex h-12 w-12 shrink-0 items-center justify-center rounded-full bg-red-100 sm:mx-0 sm:h-10 sm:w-10">
<ExclamationTriangleIcon className="h-6 w-6 text-red-600" aria-hidden="true" />
</div>
<div className="mt-3 text-center sm:ml-4 sm:mt-0 sm:text-left">
<Dialog.Title as="h3" className="text-base font-semibold leading-6 text-gray-900">
Send webhook ping event
</Dialog.Title>
<div className="mt-2">
<p className="text-sm text-gray-500">
Are you sure you want to send the webhook ping event?
</p>
</div>
</div>
</div>
<div className="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
<button
type="button"
className="inline-flex w-full justify-center rounded-md bg-red-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-red-500 sm:ml-3 sm:w-auto"
onClick={e => { setWebhookPingModal(false); webhookPing(); }}
>
Send
</button>
<button
type="button"
className="mt-3 inline-flex w-full justify-center rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 sm:mt-0 sm:w-auto"
onClick={() => setWebhookPingModal(false)}
>
Cancel
</button>
</div>
</Dialog.Panel>
</Transition.Child>
<Dialog open={webhookPingModal} onOpenChange={setWebhookPingModal}>
<DialogContent className="sm:max-w-lg">
<div className="flex items-start gap-4">
<div className="mx-auto flex size-10 shrink-0 items-center justify-center rounded-full bg-red-100 sm:mx-0">
<ExclamationTriangleIcon className="size-6 text-red-600" aria-hidden="true" />
</div>
<div className="text-center sm:text-left">
<DialogTitle>Send webhook ping event</DialogTitle>
<DialogDescription className="mt-2">
Are you sure you want to send the webhook ping event?
</DialogDescription>
</div>
</div>
</Dialog>
</Transition.Root>
<DialogFooter>
<Button variant="outline" onClick={() => setWebhookPingModal(false)}>
Cancel
</Button>
<Button onClick={() => { setWebhookPingModal(false); webhookPing(); }}>
Send
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</Fragment >
);
}
function TableItem({ localServer, index, userObj, namespaceObj, webhookLogObj, setRefresh }: { localServer: string, index: number, userObj: IUserSelf, namespaceObj: INamespaceItem, webhookLogObj: IWebhookLogItem, setRefresh: (param: any) => void }) {
const canManageWebhookLog = userObj.role == UserRole.Admin || userObj.role == UserRole.Root || (namespaceObj.role != undefined && (namespaceObj.role == NamespaceRole.Admin || namespaceObj.role == NamespaceRole.Manager));
const [webhookLogResendModal, setWebhookLogResendModal] = useState(false);
const [webhookLogDeleteModal, setWebhookLogDeleteModal] = useState(false);
@@ -395,192 +355,80 @@ function TableItem({ localServer, index, userObj, namespaceObj, webhookLogObj, s
<td className="pr-3 whitespace-nowrap text-center" onClick={e => {
e.stopPropagation();
}}>
<Menu as="div" className="relative flex-none" onClick={e => {
e.stopPropagation();
}}>
<Menu.Button className="mx-auto -m-2.5 block p-2.5 text-gray-500 hover:text-gray-900 margin">
<span className="sr-only">Open options</span>
<EllipsisVerticalIcon className="h-5 w-5" aria-hidden="true" />
</Menu.Button>
<Transition
as={Fragment}
enter="transition ease-out duration-100"
enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<Menu.Items className={(index > 10 ? "menu-action-top" : "mt-2") + " text-left absolute right-0 z-10 w-20 origin-top-right rounded-md bg-white py-2 shadow-lg ring-1 ring-gray-900/5 focus:outline-none"} >
<Menu.Item>
{({ active }) => (
<div
className={
(active ? 'bg-gray-100' : '') +
(((userObj.role == UserRole.Admin || userObj.role == UserRole.Root || (namespaceObj.role != undefined && (namespaceObj.role == NamespaceRole.Admin || namespaceObj.role == NamespaceRole.Manager)))) ? ' cursor-pointer' : ' cursor-not-allowed') +
' block px-3 py-1 text-sm leading-6 text-gray-900'
}
onClick={e => {
((userObj.role == UserRole.Admin || userObj.role == UserRole.Root || (namespaceObj.role != undefined && (namespaceObj.role == NamespaceRole.Admin || namespaceObj.role == NamespaceRole.Manager)))) && setWebhookLogResendModal(true);
}}
>
Resend
</div>
)}
</Menu.Item>
<Menu.Item>
{({ active }) => (
<div
className={
(active ? 'bg-gray-50' : '') +
(((userObj.role == UserRole.Admin || userObj.role == UserRole.Root || (namespaceObj.role != undefined && (namespaceObj.role == NamespaceRole.Admin || namespaceObj.role == NamespaceRole.Manager)))) ? ' cursor-pointer' : ' cursor-not-allowed') +
' block px-3 py-1 text-sm leading-6 text-gray-900 hover:text-white hover:bg-red-600 cursor-pointer'
}
onClick={e => {
((userObj.role == UserRole.Admin || userObj.role == UserRole.Root || (namespaceObj.role != undefined && (namespaceObj.role == NamespaceRole.Admin || namespaceObj.role == NamespaceRole.Manager)))) && setWebhookLogDeleteModal(true);
}}
>
Delete
</div>
)}
</Menu.Item>
</Menu.Items>
</Transition>
</Menu>
<DropdownMenu>
<DropdownMenuTrigger
render={
<Button variant="ghost" size="icon-sm" className="text-gray-500">
<span className="sr-only">Open options</span>
<EllipsisVerticalIcon className="size-5" aria-hidden="true" />
</Button>
}
/>
<DropdownMenuContent align="end" className="w-28">
<DropdownMenuItem disabled={!canManageWebhookLog} onClick={() => setWebhookLogResendModal(true)}>
Resend
</DropdownMenuItem>
<DropdownMenuItem variant="destructive" disabled={!canManageWebhookLog} onClick={() => setWebhookLogDeleteModal(true)}>
Delete
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</td>
<td className="absolute hidden" onClick={e => { e.preventDefault() }}>
<Transition.Root show={webhookLogResendModal} as={Fragment}>
<Dialog as="div" className="relative z-10" onClose={setWebhookLogResendModal}>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-10 overflow-y-auto">
<div className="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform overflow-hidden rounded-lg bg-white px-4 pb-4 pt-5 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg sm:p-6">
<div className="sm:flex sm:items-start">
<div className="mx-auto flex h-12 w-12 shrink-0 items-center justify-center rounded-full bg-red-100 sm:mx-0 sm:h-10 sm:w-10">
<ExclamationTriangleIcon className="h-6 w-6 text-red-600" aria-hidden="true" />
</div>
<div className="mt-3 text-center sm:ml-4 sm:mt-0 sm:text-left">
<Dialog.Title as="h3" className="text-base font-semibold leading-6 text-gray-900">
Resend webhook event
</Dialog.Title>
<div className="mt-2">
<p className="text-sm text-gray-500">
Are you sure you want to resend the webhook event <span className="text-black font-medium capitalize">{webhookLogObj.resource_type}</span>
</p>
</div>
</div>
</div>
<div className="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
<button
type="button"
className="inline-flex w-full justify-center rounded-md bg-red-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-red-500 sm:ml-3 sm:w-auto"
onClick={e => { setWebhookLogResendModal(false); webhookLogResend() }}
>
Resend
</button>
<button
type="button"
className="mt-3 inline-flex w-full justify-center rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 sm:mt-0 sm:w-auto"
onClick={() => setWebhookLogResendModal(false)}
>
Cancel
</button>
</div>
</Dialog.Panel>
</Transition.Child>
<Dialog open={webhookLogResendModal} onOpenChange={setWebhookLogResendModal}>
<DialogContent className="sm:max-w-lg">
<div className="flex items-start gap-4">
<div className="mx-auto flex size-10 shrink-0 items-center justify-center rounded-full bg-red-100 sm:mx-0">
<ExclamationTriangleIcon className="size-6 text-red-600" aria-hidden="true" />
</div>
<div className="text-center sm:text-left">
<DialogTitle>Resend webhook event</DialogTitle>
<DialogDescription className="mt-2">
Are you sure you want to resend the webhook event{" "}
<span className="text-foreground font-medium capitalize">{webhookLogObj.resource_type}</span>
</DialogDescription>
</div>
</div>
</Dialog>
</Transition.Root>
<DialogFooter>
<Button variant="outline" onClick={() => setWebhookLogResendModal(false)}>
Cancel
</Button>
<Button variant="destructive" onClick={() => { setWebhookLogResendModal(false); webhookLogResend() }}>
Resend
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</td>
<td className="absolute hidden" onClick={e => { e.preventDefault() }}>
<Transition.Root show={webhookLogDeleteModal} as={Fragment}>
<Dialog as="div" className="relative z-10" onClose={setWebhookLogDeleteModal}>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-10 overflow-y-auto">
<div className="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform overflow-hidden rounded-lg bg-white px-4 pb-4 pt-5 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg sm:p-6">
<div className="sm:flex sm:items-start">
<div className="mx-auto flex h-12 w-12 shrink-0 items-center justify-center rounded-full bg-red-100 sm:mx-0 sm:h-10 sm:w-10">
<ExclamationTriangleIcon className="h-6 w-6 text-red-600" aria-hidden="true" />
</div>
<div className="mt-3 text-center sm:ml-4 sm:mt-0 sm:text-left">
<Dialog.Title as="h3" className="text-base font-semibold leading-6 text-gray-900">
Delete webhook log
</Dialog.Title>
<div className="mt-2">
<p className="text-sm text-gray-500">
Are you sure you want to delete the webhook event <span className="text-black font-medium capitalize">{webhookLogObj.resource_type}</span>
</p>
</div>
</div>
</div>
<div className="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
<button
type="button"
className="inline-flex w-full justify-center rounded-md bg-red-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-red-500 sm:ml-3 sm:w-auto"
onClick={e => { setWebhookLogDeleteModal(false); webhookLogDelete() }}
>
Resend
</button>
<button
type="button"
className="mt-3 inline-flex w-full justify-center rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 sm:mt-0 sm:w-auto"
onClick={() => setWebhookLogDeleteModal(false)}
>
Cancel
</button>
</div>
</Dialog.Panel>
</Transition.Child>
<Dialog open={webhookLogDeleteModal} onOpenChange={setWebhookLogDeleteModal}>
<DialogContent className="sm:max-w-lg">
<div className="flex items-start gap-4">
<div className="mx-auto flex size-10 shrink-0 items-center justify-center rounded-full bg-red-100 sm:mx-0">
<ExclamationTriangleIcon className="size-6 text-red-600" aria-hidden="true" />
</div>
<div className="text-center sm:text-left">
<DialogTitle>Delete webhook log</DialogTitle>
<DialogDescription className="mt-2">
Are you sure you want to delete the webhook event{" "}
<span className="text-foreground font-medium capitalize">{webhookLogObj.resource_type}</span>
</DialogDescription>
</div>
</div>
</Dialog>
</Transition.Root>
<DialogFooter>
<Button variant="outline" onClick={() => setWebhookLogDeleteModal(false)}>
Cancel
</Button>
<Button variant="destructive" onClick={() => { setWebhookLogDeleteModal(false); webhookLogDelete() }}>
Delete
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</td>
<td className="absolute" onClick={e => { e.preventDefault() }}>
<div className={`fixed inset-0 z-30 bg-black/20 transition-opacity ${drawerShow ? "opacity-100" : "pointer-events-none opacity-0"}`} onClick={() => setDrawerShow(false)} />
<div id={`drawer-${index}`} className={`fixed top-0 right-0 z-40 h-screen p-4 overflow-y-auto bg-white w-[800px] shadow-xl transition-transform dark:bg-gray-800 ${drawerShow ? "translate-x-0" : "translate-x-full"}`} aria-labelledby="drawer-right-label">
<div id={`drawer-${index}`} className={`fixed top-0 right-0 z-40 h-screen p-4 overflow-y-auto bg-white w-200 shadow-xl transition-transform dark:bg-gray-800 ${drawerShow ? "translate-x-0" : "translate-x-full"}`} aria-labelledby="drawer-right-label">
<button
type="button"
className="absolute right-4 top-4 rounded-md text-gray-400 hover:text-gray-600"