diff --git a/knip.ts b/knip.ts new file mode 100644 index 00000000..72ef7672 --- /dev/null +++ b/knip.ts @@ -0,0 +1,10 @@ +import type { KnipConfig } from "knip"; + +const config: KnipConfig = { + entry: ["src/main.tsx"], + ignore: ["src/api/generated/**/*"], + ignoreDependencies: ["husky"], + project: ["src/**/*.{js,jsx,ts,tsx}"], +}; + +export default config; diff --git a/package-lock.json b/package-lock.json index 8b580694..816cf032 100644 --- a/package-lock.json +++ b/package-lock.json @@ -58,6 +58,7 @@ "globals": "^15.12.0", "husky": "^9.1.6", "jsdom": "^25.0.1", + "knip": "^5.43.6", "lint-staged": "^15.3.0", "msw": "^2.7.0", "postcss": "^8.4.49", @@ -3897,6 +3898,34 @@ "win32" ] }, + "node_modules/@snyk/github-codeowners": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@snyk/github-codeowners/-/github-codeowners-1.1.0.tgz", + "integrity": "sha512-lGFf08pbkEac0NYgVf4hdANpAgApRjNByLXB+WBip3qj1iendOIyAwP2GKkKbQMNVy2r1xxDf0ssfWscoiC+Vw==", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "^4.1.1", + "ignore": "^5.1.8", + "p-map": "^4.0.0" + }, + "bin": { + "github-codeowners": "dist/cli.js" + }, + "engines": { + "node": ">=8.10" + } + }, + "node_modules/@snyk/github-codeowners/node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, "node_modules/@stacklok/ui-kit": { "version": "1.0.1-0", "resolved": "https://registry.npmjs.org/@stacklok/ui-kit/-/ui-kit-1.0.1-0.tgz", @@ -5058,6 +5087,20 @@ "node": ">= 14" } }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -5580,6 +5623,16 @@ "consola": "^3.2.3" } }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/cli-cursor": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", @@ -5717,6 +5770,17 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.8" + } + }, "node_modules/clsx": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", @@ -6079,6 +6143,20 @@ "dev": true, "license": "MIT" }, + "node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/defu": { "version": "6.1.4", "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", @@ -6179,6 +6257,29 @@ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "license": "MIT" }, + "node_modules/easy-table": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/easy-table/-/easy-table-1.2.0.tgz", + "integrity": "sha512-OFzVOv03YpvtcWGe5AayU5G2hgybsg3iqA6drU8UaoZyB9jLGMTrz9+asnLp/E+6qPh88yEI1gvyZFZ41dmgww==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "optionalDependencies": { + "wcwidth": "^1.0.1" + } + }, + "node_modules/easy-table/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/electron-to-chromium": { "version": "1.5.76", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.76.tgz", @@ -6193,6 +6294,20 @@ "dev": true, "license": "MIT" }, + "node_modules/enhanced-resolve": { + "version": "5.18.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.0.tgz", + "integrity": "sha512-0/r0MySGYG8YqlayBZ6MuCfECmHFdJ5qyPh8s8wa5Hnm6SaFLSK1VYCbj+NKp090Nm1caZhD+QTnmxO7esYGyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/entities": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", @@ -6565,16 +6680,16 @@ } }, "node_modules/fast-glob": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", - "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", "license": "MIT", "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", - "micromatch": "^4.0.4" + "micromatch": "^4.0.8" }, "engines": { "node": ">=8.6.0" @@ -6958,6 +7073,13 @@ "dev": true, "license": "MIT" }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, "node_modules/graphemer": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", @@ -7724,6 +7846,130 @@ "json-buffer": "3.0.1" } }, + "node_modules/knip": { + "version": "5.43.6", + "resolved": "https://registry.npmjs.org/knip/-/knip-5.43.6.tgz", + "integrity": "sha512-bUCFlg44imdV5vayYxu0pIAB373S8Ufjda0qaI9oRZDH6ltJFwUoAO2j7nafxDmo5G0ZeP4IiLAHqlc3wYIONQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/webpro" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/knip" + }, + { + "type": "polar", + "url": "https://polar.sh/webpro-nl" + } + ], + "license": "ISC", + "dependencies": { + "@nodelib/fs.walk": "3.0.1", + "@snyk/github-codeowners": "1.1.0", + "easy-table": "1.2.0", + "enhanced-resolve": "^5.18.0", + "fast-glob": "^3.3.3", + "jiti": "^2.4.2", + "js-yaml": "^4.1.0", + "minimist": "^1.2.8", + "picocolors": "^1.1.0", + "picomatch": "^4.0.1", + "pretty-ms": "^9.0.0", + "smol-toml": "^1.3.1", + "strip-json-comments": "5.0.1", + "summary": "2.1.0", + "zod": "^3.22.4", + "zod-validation-error": "^3.0.3" + }, + "bin": { + "knip": "bin/knip.js", + "knip-bun": "bin/knip-bun.js" + }, + "engines": { + "node": ">=18.18.0" + }, + "peerDependencies": { + "@types/node": ">=18", + "typescript": ">=5.0.4" + } + }, + "node_modules/knip/node_modules/@nodelib/fs.scandir": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-4.0.1.tgz", + "integrity": "sha512-vAkI715yhnmiPupY+dq+xenu5Tdf2TBQ66jLvBIcCddtz+5Q8LbMKaf9CIJJreez8fQ8fgaY+RaywQx8RJIWpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "4.0.0", + "run-parallel": "^1.2.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/knip/node_modules/@nodelib/fs.stat": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-4.0.0.tgz", + "integrity": "sha512-ctr6bByzksKRCV0bavi8WoQevU6plSp2IkllIsEqaiKe2mwNNnaluhnRhcsgGZHrrHk57B3lf95MkLMO3STYcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/knip/node_modules/@nodelib/fs.walk": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-3.0.1.tgz", + "integrity": "sha512-nIh/M6Kh3ZtOmlY00DaUYB4xeeV6F3/ts1l29iwl3/cfyY/OuCfUx+v08zgx8TKPTifXRcjjqVQ4KB2zOYSbyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "4.0.1", + "fastq": "^1.15.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/knip/node_modules/jiti": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz", + "integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/knip/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/knip/node_modules/strip-json-comments": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.1.tgz", + "integrity": "sha512-0fk9zBqO67Nq5M/m45qHCJxylV/DhBlIOVExqgOMiCCrzrhU6tCibRXNqE3jwJLftzE9SNuZtYbpzcO+i9FiKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -9392,6 +9638,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/package-json-from-dist": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", @@ -9436,6 +9698,19 @@ "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", "license": "MIT" }, + "node_modules/parse-ms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz", + "integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/parse5": { "version": "7.2.1", "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz", @@ -9815,6 +10090,22 @@ "dev": true, "license": "MIT" }, + "node_modules/pretty-ms": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.2.0.tgz", + "integrity": "sha512-4yf0QO/sllf/1zbZWYnvWw3NxCQwLXKzIj0G849LSufP15BXKM0rbD2Z3wVnkMfjdn/CB0Dpp444gYAACdsplg==", + "dev": true, + "license": "MIT", + "dependencies": { + "parse-ms": "^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/prismjs": { "version": "1.29.0", "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz", @@ -10815,6 +11106,19 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/smol-toml": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.3.1.tgz", + "integrity": "sha512-tEYNll18pPKHroYSmLLrksq233j021G0giwW7P3D24jC54pQ5W5BXMsQ/Mvw1OJCmEYDgY+lrzT+3nNUtoNfXQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 18" + }, + "funding": { + "url": "https://github.com/sponsors/cyyynthia" + } + }, "node_modules/sonner": { "version": "1.7.2", "resolved": "https://registry.npmjs.org/sonner/-/sonner-1.7.2.tgz", @@ -11100,6 +11404,13 @@ "node": ">= 6" } }, + "node_modules/summary": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/summary/-/summary-2.1.0.tgz", + "integrity": "sha512-nMIjMrd5Z2nuB2RZCKJfFMjgS3fygbeyGk9PxPPaJR1RIcyN9yn4A63Isovzm3ZtQuEkLBVgMdPup8UeLH7aQw==", + "dev": true, + "license": "MIT" + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -11226,6 +11537,16 @@ "node": ">=4" } }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/tar": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", @@ -12182,6 +12503,17 @@ "node": ">=18" } }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "defaults": "^1.0.3" + } + }, "node_modules/webidl-conversions": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", @@ -12564,6 +12896,29 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/zod": { + "version": "3.24.1", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.1.tgz", + "integrity": "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-validation-error": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-3.4.0.tgz", + "integrity": "sha512-ZOPR9SVY6Pb2qqO5XHt+MkkTRxGXb4EVtnjc9JpXUOtUB1T9Ru7mZOT361AN3MsetVe7R0a1KZshJDZdgp9miQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "zod": "^3.18.0" + } + }, "node_modules/zustand": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.3.tgz", diff --git a/package.json b/package.json index b4cf0642..6ff223ce 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "generate-client": "npm run fetch-openapi-schema && npm run openapi-ts", "fetch-openapi-schema": "curl https://raw.githubusercontent.com/stacklok/codegate/refs/heads/main/api/openapi.json > src/api/openapi.json", "openapi-ts": "openapi-ts", + "knip": "knip --production", "lint": "eslint .", "lint:fix": "eslint --fix", "prepare": "husky", @@ -69,6 +70,7 @@ "globals": "^15.12.0", "husky": "^9.1.6", "jsdom": "^25.0.1", + "knip": "^5.43.6", "lint-staged": "^15.3.0", "msw": "^2.7.0", "postcss": "^8.4.49", diff --git a/src/App.tsx b/src/App.tsx index 14201770..e9f9922b 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,4 +1,4 @@ -import { Header } from "./components/Header"; +import { Header } from "./features/header/components/header"; import { PromptList } from "./components/PromptList"; import { usePromptsData } from "./hooks/usePromptsData"; import { Sidebar } from "./components/Sidebar"; diff --git a/src/components/Error.tsx b/src/components/Error.tsx index 500e7f42..a53a563c 100644 --- a/src/components/Error.tsx +++ b/src/components/Error.tsx @@ -1,5 +1,5 @@ import { CircleAlert } from "lucide-react"; -import { Header } from "./Header"; +import { Header } from "../features/header/components/header"; import { Card } from "@stacklok/ui-kit"; export function Error() { diff --git a/src/components/Header.tsx b/src/components/Header.tsx deleted file mode 100644 index ae056a5b..00000000 --- a/src/components/Header.tsx +++ /dev/null @@ -1,114 +0,0 @@ -import { Link } from "react-router-dom"; -import { SidebarTrigger } from "./ui/sidebar"; -import { DropdownMenu } from "./HoverPopover"; -import { Separator, ButtonDarkMode, OptionsSchema } from "@stacklok/ui-kit"; -import { WorkspacesSelection } from "@/features/workspace/components/workspaces-selection"; -import { BookOpenText, Download, ShieldCheck } from "lucide-react"; -import { Continue, Copilot, Discord, Github, Youtube } from "./icons"; - -const CERTIFICATE_MENU_ITEMS: OptionsSchema<"menu">[] = [ - { - icon: , - id: "about-certificate-security", - href: "/certificates/security", - textValue: "About certificate security", - }, - { - icon: , - id: "download-certificates", - href: "/certificates", - textValue: "Download certificates", - }, -]; - -const HELP_MENU_ITEMS: OptionsSchema<"menu">[] = [ - { - textValue: "Getting Starting", - id: "setup", - items: [ - { - icon: , - id: "continue-setup", - href: "https://docs.codegate.ai/how-to/use-with-continue", - textValue: "Use with Continue", - target: "_blank", - }, - { - icon: , - id: "copilot-setup", - href: "https://docs.codegate.ai/how-to/use-with-copilot", - textValue: "Use with Copilot", - target: "_blank", - }, - { - icon: , - id: "documentation", - href: "https://docs.codegate.ai/", - textValue: "Documentation", - target: "_blank", - }, - ], - }, - { - textValue: "Resources", - id: "resources", - items: [ - { - icon: , - id: "discord", - href: "https://discord.gg/stacklok", - textValue: "Discord", - target: "_blank", - }, - { - icon: , - id: "github", - href: "https://github.com/stacklok/codegate", - textValue: "GitHub", - target: "_blank", - }, - { - icon: , - id: "youtube", - href: "https://www.youtube.com/@Stacklok", - textValue: "YouTube", - target: "_blank", - }, - ], - }, -]; - -export function Header({ hasError }: { hasError?: boolean }) { - return ( - - - {!hasError && ( - <> - - - > - )} - - - - - CodeGate Dashboard - - - - - - - - - - - - - - - ); -} diff --git a/src/components/HoverPopover.tsx b/src/components/HoverPopover.tsx index 3b80650f..80a83440 100644 --- a/src/components/HoverPopover.tsx +++ b/src/components/HoverPopover.tsx @@ -29,7 +29,7 @@ export function DropdownMenu({ {title} - + diff --git a/src/features/dashboard-codegate-status/components/__tests__/codegate-status.test.tsx b/src/features/dashboard-codegate-status/components/__tests__/codegate-status.test.tsx deleted file mode 100644 index d3e14ee7..00000000 --- a/src/features/dashboard-codegate-status/components/__tests__/codegate-status.test.tsx +++ /dev/null @@ -1,134 +0,0 @@ -import { server } from "@/mocks/msw/node"; -import { http, HttpResponse } from "msw"; -import { expect } from "vitest"; -import { CodegateStatus } from "../codegate-status"; -import { render, waitFor } from "@/lib/test-utils"; - -const renderComponent = () => render(); - -describe("CardCodegateStatus", () => { - test("renders 'healthy' state", async () => { - server.use( - http.get("*/health", () => HttpResponse.json({ status: "healthy" })), - ); - - const { getByText } = renderComponent(); - - await waitFor( - () => { - expect(getByText(/healthy/i)).toBeVisible(); - }, - { timeout: 10_000 }, - ); - }); - - test("renders 'unhealthy' state", async () => { - server.use(http.get("*/health", () => HttpResponse.json({ status: null }))); - - const { getByText } = renderComponent(); - - await waitFor( - () => { - expect(getByText(/unhealthy/i)).toBeVisible(); - }, - { timeout: 10_000 }, - ); - }); - - test("renders 'error' state when health check request fails", async () => { - server.use(http.get("*/health", () => HttpResponse.error())); - - const { getByText } = renderComponent(); - - await waitFor( - () => { - expect(getByText(/an error occurred/i)).toBeVisible(); - }, - { timeout: 10_000 }, - ); - }); - - test("renders 'error' state when version check request fails", async () => { - server.use(http.get("*/api/v1/version", () => HttpResponse.error())); - - const { getByText } = renderComponent(); - - await waitFor( - () => { - expect(getByText(/an error occurred/i)).toBeVisible(); - }, - { timeout: 10_000 }, - ); - }); - - test("renders 'latest version' state", async () => { - server.use( - http.get("*/api/v1/version", () => - HttpResponse.json({ - current_version: "foo", - latest_version: "foo", - is_latest: true, - error: null, - }), - ), - ); - - const { getByText } = renderComponent(); - - await waitFor( - () => { - expect(getByText(/latest/i)).toBeVisible(); - }, - { timeout: 10_000 }, - ); - }); - - test("renders 'update available' state", async () => { - server.use( - http.get("*/api/v1/version", () => - HttpResponse.json({ - current_version: "foo", - latest_version: "bar", - is_latest: false, - error: null, - }), - ), - ); - - const { getByRole } = renderComponent(); - - await waitFor( - () => { - const role = getByRole("link", { name: /update available/i }); - expect(role).toBeVisible(); - expect(role).toHaveAttribute( - "href", - "https://docs.codegate.ai/how-to/install#upgrade-codegate", - ); - }, - { timeout: 10_000 }, - ); - }); - - test("renders 'version check error' state", async () => { - server.use( - http.get("*/api/v1/version", () => - HttpResponse.json({ - current_version: "foo", - latest_version: "bar", - is_latest: false, - error: "foo", - }), - ), - ); - - const { getByText } = renderComponent(); - - await waitFor( - () => { - expect(getByText(/error checking version/i)).toBeVisible(); - }, - { timeout: 10_000 }, - ); - }); -}); diff --git a/src/features/dashboard-codegate-status/components/codegate-status-error-ui.tsx b/src/features/dashboard-codegate-status/components/codegate-status-error-ui.tsx deleted file mode 100644 index bad6810a..00000000 --- a/src/features/dashboard-codegate-status/components/codegate-status-error-ui.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { XCircle } from "lucide-react"; - -export function CodegateStatusErrorUI() { - return ( - - - - An error occurred - - - If this issue persists, please reach out to us on{" "} - - Discord - {" "} - or open a new{" "} - - Github issue - - - - ); -} diff --git a/src/features/dashboard-codegate-status/components/codegate-status-health.tsx b/src/features/dashboard-codegate-status/components/codegate-status-health.tsx deleted file mode 100644 index f545cfc1..00000000 --- a/src/features/dashboard-codegate-status/components/codegate-status-health.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { LoaderCircle, CheckCircle2, XCircle } from "lucide-react"; -import { HealthStatus } from "../types"; - -export const CodegateStatusHealth = ({ - data: data, - isPending, -}: { - data: HealthStatus | null; - isPending: boolean; -}) => { - if (isPending || data === null) { - return ( - - Checking - - ); - } - - switch (data) { - case HealthStatus.HEALTHY: - return ( - - {HealthStatus.HEALTHY} - - ); - case HealthStatus.UNHEALTHY: - return ( - - {HealthStatus.UNHEALTHY} - - ); - default: { - data satisfies never; - } - } -}; diff --git a/src/features/dashboard-codegate-status/components/codegate-status-polling-control.tsx b/src/features/dashboard-codegate-status/components/codegate-status-polling-control.tsx deleted file mode 100644 index 5b24d8d9..00000000 --- a/src/features/dashboard-codegate-status/components/codegate-status-polling-control.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import { Dispatch, SetStateAction } from "react"; -import { Label, Select, SelectButton, OptionsSchema } from "@stacklok/ui-kit"; - -// NOTE: We don't poll more than once per minute, as the server depends on -// Github's public API, which is rate limited to 60reqs per hour. -export const POLLING_INTERVAl = { - "1_MIN": { value: 60_000, name: "1 minute" }, - "5_MIN": { value: 300_000, name: "5 minutes" }, - "10_MIN": { value: 600_000, name: "10 minutes" }, -} as const; - -export const INTERVAL_SELECT_ITEMS: OptionsSchema<"listbox">[] = Object.entries( - POLLING_INTERVAl, -).map(([key, { name }]) => { - return { textValue: name, id: key }; -}); - -export const DEFAULT_INTERVAL: PollingInterval = "5_MIN"; - -export type PollingInterval = keyof typeof POLLING_INTERVAl; - -export function PollIntervalControl({ - className, - pollingInterval, - setPollingInterval, -}: { - className?: string; - pollingInterval: PollingInterval; - setPollingInterval: Dispatch>; -}) { - return ( - - setPollingInterval(v.toString() as PollingInterval) - } - items={INTERVAL_SELECT_ITEMS} - defaultSelectedKey={pollingInterval} - > - - Check for updates - - - - ); -} diff --git a/src/features/dashboard-codegate-status/components/codegate-status-refresh-button.tsx b/src/features/dashboard-codegate-status/components/codegate-status-refresh-button.tsx deleted file mode 100644 index ceb73746..00000000 --- a/src/features/dashboard-codegate-status/components/codegate-status-refresh-button.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import { useQueryClient } from "@tanstack/react-query"; -import { PollingInterval } from "./codegate-status-polling-control"; -import { getQueryOptionsCodeGateStatus } from "../hooks/use-codegate-status"; -import { useCallback, useEffect, useState } from "react"; -import { Button } from "@stacklok/ui-kit"; -import { RefreshCcw } from "lucide-react"; -import { twMerge } from "tailwind-merge"; - -export function CodeGateStatusRefreshButton({ - pollingInterval, - className, -}: { - pollingInterval: PollingInterval; - className?: string; -}) { - const queryClient = useQueryClient(); - const { queryKey } = getQueryOptionsCodeGateStatus(pollingInterval); - - const [refreshed, setRefreshed] = useState(false); - - useEffect(() => { - const id = setTimeout(() => setRefreshed(false), 500); - return () => clearTimeout(id); - }, [refreshed]); - - const handleRefresh = useCallback(() => { - setRefreshed(true); - return queryClient.invalidateQueries({ queryKey, refetchType: "all" }); - }, [queryClient, queryKey]); - - return ( - - - - ); -} diff --git a/src/features/dashboard-codegate-status/components/codegate-status-version.tsx b/src/features/dashboard-codegate-status/components/codegate-status-version.tsx deleted file mode 100644 index 96a763e5..00000000 --- a/src/features/dashboard-codegate-status/components/codegate-status-version.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import { LoaderCircle, CheckCircle2, CircleAlert, XCircle } from "lucide-react"; -import { Link, Tooltip, TooltipTrigger } from "@stacklok/ui-kit"; -import { VersionResponse } from "../types"; - -export const CodegateStatusVersion = ({ - data, - isPending, -}: { - data: VersionResponse | null; - isPending: boolean; -}) => { - if (isPending || data === null) { - return ( - - Checking - - ); - } - - const { current_version, is_latest, latest_version, error } = data || {}; - - if (error !== null || is_latest === null) { - return ( - - Error checking version - - ); - } - - switch (is_latest) { - case true: - return ( - - Latest - - ); - case false: - return ( - - - - Update available - - - Current version: {current_version} - Latest version: {latest_version} - - - - ); - default: { - is_latest satisfies never; - } - } -}; diff --git a/src/features/dashboard-codegate-status/components/codegate-status.tsx b/src/features/dashboard-codegate-status/components/codegate-status.tsx deleted file mode 100644 index 4464db95..00000000 --- a/src/features/dashboard-codegate-status/components/codegate-status.tsx +++ /dev/null @@ -1,112 +0,0 @@ -import { - Card, - CardBody, - CardFooter, - CardHeader, - CardTitle, - Cell, - Column, - Row, - Table, - TableBody, - TableHeader, -} from "@stacklok/ui-kit"; - -import { format } from "date-fns"; -import { useState } from "react"; -import { useCodeGateStatus } from "../hooks/use-codegate-status"; -import { CodegateStatusErrorUI } from "./codegate-status-error-ui"; -import { - DEFAULT_INTERVAL, - PollingInterval, - PollIntervalControl, -} from "./codegate-status-polling-control"; -import { CodegateStatusHealth } from "./codegate-status-health"; -import { CodegateStatusVersion } from "./codegate-status-version"; -import { CodeGateStatusRefreshButton } from "./codegate-status-refresh-button"; - -export function InnerContent({ - isError, - isPending, - data, -}: Pick< - ReturnType, - "data" | "isPending" | "isError" ->) { - if (!isPending && isError) { - return ; - } - - const { health, version } = data || {}; - - return ( - - - Name - Value - - - - CodeGate server - - - - - - - CodeGate version - - - - - - - ); -} - -export function CodegateStatus() { - const [pollingInterval, setPollingInterval] = useState( - () => DEFAULT_INTERVAL, - ); - const { data, dataUpdatedAt, isPending, isError } = - useCodeGateStatus(pollingInterval); - - return ( - - - - CodeGate Status - - - - - - - - - - - - - Last checked - - - {format(new Date(dataUpdatedAt), "pp")} - - - - - - - ); -} diff --git a/src/features/header/components/__tests__/header-status-menu.test.tsx b/src/features/header/components/__tests__/header-status-menu.test.tsx new file mode 100644 index 00000000..154109e0 --- /dev/null +++ b/src/features/header/components/__tests__/header-status-menu.test.tsx @@ -0,0 +1,142 @@ +import { server } from "@/mocks/msw/node"; +import { http, HttpResponse } from "msw"; +import { expect } from "vitest"; + +import { render, waitFor } from "@/lib/test-utils"; +import { HeaderStatusMenu } from "../header-status-menu"; +import userEvent from "@testing-library/user-event"; + +const renderComponent = () => render(); + +describe("CardCodegateStatus", () => { + test("renders 'healthy' state", async () => { + server.use( + http.get("*/health", () => HttpResponse.json({ status: "healthy" })), + ); + + const { getByRole } = renderComponent(); + + await waitFor(() => { + expect(getByRole("button", { name: /service healthy/i })).toBeVisible(); + }); + }); + + test("renders 'unhealthy' state", async () => { + server.use(http.get("*/health", () => HttpResponse.json({ status: null }))); + + const { getByRole } = renderComponent(); + + await waitFor(() => { + expect(getByRole("button", { name: /service unhealthy/i })).toBeVisible(); + }); + }); + + test("renders 'error' state when health check request fails", async () => { + server.use(http.get("*/health", () => HttpResponse.error())); + + const { getByRole } = renderComponent(); + + await waitFor(() => { + expect(getByRole("button", { name: /error/i })).toBeVisible(); + }); + }); + + test("renders 'error' state when version check request fails", async () => { + server.use( + http.get("*/health", () => HttpResponse.json({ status: "healthy" })), + http.get("*/api/v1/version", () => HttpResponse.error()), + ); + + const { getByRole } = renderComponent(); + + await waitFor(() => { + expect(getByRole("button", { name: /error/i })).toBeVisible(); + }); + }); + + test("renders 'up to date' state", async () => { + server.use( + http.get("*/health", () => HttpResponse.json({ status: "healthy" })), + http.get("*/api/v1/version", () => + HttpResponse.json({ + current_version: "foo", + latest_version: "foo", + is_latest: true, + error: null, + }), + ), + ); + + const { getByRole, getByText } = renderComponent(); + + await waitFor(() => { + expect(getByRole("button", { name: /service healthy/i })).toBeVisible(); + }); + + await userEvent.click(getByRole("button", { name: /service healthy/i })); + + await waitFor(() => { + expect(getByRole("dialog", { name: /codegate status/i })).toBeVisible(); + expect(getByText(/up to date/i)).toBeVisible(); + }); + }); + + test("renders 'update available' state", async () => { + server.use( + http.get("*/health", () => HttpResponse.json({ status: "healthy" })), + http.get("*/api/v1/version", () => + HttpResponse.json({ + current_version: "foo", + latest_version: "bar", + is_latest: false, + error: null, + }), + ), + ); + + const { getByRole } = renderComponent(); + + await waitFor(() => { + expect(getByRole("button", { name: /update available/i })).toBeVisible(); + }); + + await userEvent.click(getByRole("button", { name: /update available/i })); + + await waitFor(() => { + expect(getByRole("dialog", { name: /codegate status/i })).toBeVisible(); + const role = getByRole("link", { name: /update available/i }); + expect(role).toBeVisible(); + expect(role).toHaveAttribute( + "href", + "https://docs.codegate.ai/how-to/install#upgrade-codegate", + ); + }); + }); + + test("renders 'version check error' state", async () => { + server.use( + http.get("*/health", () => HttpResponse.json({ status: "healthy" })), + http.get("*/api/v1/version", () => + HttpResponse.json({ + current_version: "foo", + latest_version: "bar", + is_latest: false, + error: "foo", + }), + ), + ); + + const { getByRole, getByText } = renderComponent(); + + await waitFor(() => { + expect(getByRole("button", { name: /service healthy/i })).toBeVisible(); + }); + + await userEvent.click(getByRole("button", { name: /service healthy/i })); + + await waitFor(() => { + expect(getByRole("dialog", { name: /codegate status/i })).toBeVisible(); + expect(getByText(/error/i)).toBeVisible(); + }); + }); +}); diff --git a/src/features/header/components/header-status-menu.tsx b/src/features/header/components/header-status-menu.tsx new file mode 100644 index 00000000..3eb70efa --- /dev/null +++ b/src/features/header/components/header-status-menu.tsx @@ -0,0 +1,309 @@ +import { + AlertCircle, + CheckCircle2, + ShieldCheck, + ShieldX, + ShieldAlert, +} from "lucide-react"; +import { useCodeGateStatus } from "../hooks/use-codegate-status"; +import { HealthStatus } from "../types"; +import { + Button, + DialogTrigger, + Loader, + Link, + Popover, + Tooltip, + TooltipTrigger, +} from "@stacklok/ui-kit"; +import { Dialog } from "react-aria-components"; + +import { ReactNode } from "react"; + +type CodeGateStatus = + | "healthy" + | "update_available" + | "unhealthy" + | "loading" + | "error_checking_status"; + +type CodeGateVersionStatus = + | "up_to_date" + | "update_available" + | "loading" + | "error_checking_version"; + +type CodeGateHealthCheckStatus = + | "healthy" + | "unhealthy" + | "loading" + | "error_checking_health"; + +function deriveOverallStatus( + data: ReturnType["data"], + isPending: boolean, + isError: boolean, +): CodeGateStatus { + if (isPending) return "loading"; + if (isError) return "error_checking_status"; + + if ( + data?.health === HealthStatus.HEALTHY && + data.version?.error === null && + data.version?.is_latest === false + ) + return "update_available"; + + if (data?.health === HealthStatus.HEALTHY) return "healthy"; + + return "unhealthy"; +} + +function deriveVersionStatus( + data: ReturnType["data"], + isPending: boolean, + isError: boolean, +): CodeGateVersionStatus { + if (isPending) return "loading"; + if (isError || data?.version?.error) return "error_checking_version"; + + if (data?.version?.is_latest === false) return "update_available"; + return "up_to_date"; +} + +function deriveHealthCheckStatus( + data: ReturnType["data"], + isPending: boolean, + isError: boolean, +): CodeGateHealthCheckStatus { + if (isPending) return "loading"; + if (isError) return "error_checking_health"; + + if (data?.health == HealthStatus.HEALTHY) return "healthy"; + return "unhealthy"; +} + +function getButtonText(status: CodeGateStatus): string { + switch (status) { + case "error_checking_status": + return "Error"; + case "healthy": + return "Service healthy"; + case "loading": + return "Loading"; + case "unhealthy": + return "Service unhealthy"; + case "update_available": + return "Update available"; + default: + return status satisfies never; + } +} + +function getVersionText( + status: CodeGateVersionStatus, + data: ReturnType["data"], +): ReactNode { + switch (status) { + case "error_checking_version": + return "Error"; + case "loading": + return "Loading"; + case "up_to_date": + return "Up to date"; + case "update_available": + return ( + + + Update available + + + + Current version: {data?.version?.current_version} + + + Latest version: {data?.version?.latest_version} + + + + ); + default: + return status satisfies never; + } +} + +function getHealthCheckText(status: CodeGateHealthCheckStatus): string { + switch (status) { + case "healthy": + return "Healthy"; + case "loading": + return "Loading"; + case "error_checking_health": + return "Error"; + case "unhealthy": + return "Unhealthy"; + default: + return status satisfies never; + } +} + +function ButtonIcon({ + status, + className, +}: { + status: CodeGateStatus; + className?: string; +}) { + switch (status) { + case "error_checking_status": + return ; + case "healthy": + return ; + case "loading": + return ; + case "unhealthy": + return ; + case "update_available": + return ; + default: + return status satisfies never; + } +} + +function HealthCheckIcon({ + healthCheckStatus, + className, +}: { + healthCheckStatus: CodeGateHealthCheckStatus; + className?: string; +}): ReactNode { + switch (healthCheckStatus) { + case "error_checking_health": + case "unhealthy": + return ; + case "healthy": + return ; + case "loading": + return ; + default: + return healthCheckStatus satisfies never; + } +} + +function VersionIcon({ + versionStatus: versionStatus, + className, +}: { + versionStatus: CodeGateVersionStatus; + className?: string; +}) { + switch (versionStatus) { + case "error_checking_version": + return ; + case "update_available": + return ; + case "up_to_date": + return ; + case "loading": + return ; + default: + return versionStatus satisfies never; + } +} + +function StatusMenuTrigger({ + status, + isPending, +}: { + status: CodeGateStatus; + isPending: boolean; +}) { + return ( + + {getButtonText(status)}{" "} + {isPending ? ( + + ) : ( + + )} + + ); +} + +function Row({ + title, + value, + icon, +}: { + title: string; + value: ReactNode; + icon: ReactNode; +}) { + return ( + + {title} + + {value} {icon} + + + ); +} + +function StatusPopover({ + versionStatus, + healthCheckStatus, + data, +}: { + versionStatus: CodeGateVersionStatus; + healthCheckStatus: CodeGateHealthCheckStatus; + data: ReturnType["data"]; +}) { + return ( + + + + } + /> + + } + /> + + + ); +} + +export function HeaderStatusMenu() { + const { data, isPending, isError } = useCodeGateStatus(); + + const status = deriveOverallStatus(data, isPending, isError); + const versionStatus = deriveVersionStatus(data, isPending, isError); + const healthCheckStatus = deriveHealthCheckStatus(data, isPending, isError); + + return ( + + + + + ); +} diff --git a/src/features/header/components/header.tsx b/src/features/header/components/header.tsx new file mode 100644 index 00000000..8006ad62 --- /dev/null +++ b/src/features/header/components/header.tsx @@ -0,0 +1,49 @@ +import { Link } from "react-router-dom"; +import { SidebarTrigger } from "../../../components/ui/sidebar"; +import { DropdownMenu } from "../../../components/HoverPopover"; +import { Separator, ButtonDarkMode } from "@stacklok/ui-kit"; +import { WorkspacesSelection } from "@/features/workspace/components/workspaces-selection"; +import { CERTIFICATE_MENU_ITEMS } from "../constants/certificate-menu-items"; +import { HELP_MENU_ITEMS } from "../constants/help-menu-items"; +import { HeaderStatusMenu } from "./header-status-menu"; + +function HomeLink() { + return ( + + + CodeGate Dashboard + + + ); +} + +export function Header({ hasError }: { hasError?: boolean }) { + return ( + + + {!hasError && ( + <> + + + > + )} + + + + + + + + + + + + + + + + ); +} diff --git a/src/features/header/constants/certificate-menu-items.tsx b/src/features/header/constants/certificate-menu-items.tsx new file mode 100644 index 00000000..d05399b9 --- /dev/null +++ b/src/features/header/constants/certificate-menu-items.tsx @@ -0,0 +1,17 @@ +import { OptionsSchema } from "@stacklok/ui-kit"; +import { Download, ShieldCheck } from "lucide-react"; + +export const CERTIFICATE_MENU_ITEMS = [ + { + icon: , + id: "about-certificate-security", + href: "/certificates/security", + textValue: "About certificate security", + }, + { + icon: , + id: "download-certificates", + href: "/certificates", + textValue: "Download certificates", + }, +] as const satisfies OptionsSchema<"menu">[]; diff --git a/src/features/header/constants/help-menu-items.tsx b/src/features/header/constants/help-menu-items.tsx new file mode 100644 index 00000000..c36b0925 --- /dev/null +++ b/src/features/header/constants/help-menu-items.tsx @@ -0,0 +1,66 @@ +import { + Continue, + Copilot, + Discord, + Github, + Youtube, +} from "@/components/icons"; +import { OptionsSchema } from "@stacklok/ui-kit"; +import { BookOpenText } from "lucide-react"; + +export const HELP_MENU_ITEMS = [ + { + textValue: "Getting started", + id: "setup", + items: [ + { + icon: , + id: "continue-setup", + href: "https://docs.codegate.ai/how-to/use-with-continue", + textValue: "Use with Continue", + target: "_blank", + }, + { + icon: , + id: "copilot-setup", + href: "https://docs.codegate.ai/how-to/use-with-copilot", + textValue: "Use with Copilot", + target: "_blank", + }, + { + icon: , + id: "documentation", + href: "https://docs.codegate.ai/", + textValue: "Documentation", + target: "_blank", + }, + ], + }, + { + textValue: "Resources", + id: "resources", + items: [ + { + icon: , + id: "discord", + href: "https://discord.gg/stacklok", + textValue: "Discord", + target: "_blank", + }, + { + icon: , + id: "github", + href: "https://github.com/stacklok/codegate", + textValue: "GitHub", + target: "_blank", + }, + { + icon: , + id: "youtube", + href: "https://www.youtube.com/@Stacklok", + textValue: "YouTube", + target: "_blank", + }, + ], + }, +] as const satisfies OptionsSchema<"menu">[]; diff --git a/src/features/dashboard-codegate-status/hooks/use-codegate-status.ts b/src/features/header/hooks/use-codegate-status.ts similarity index 73% rename from src/features/dashboard-codegate-status/hooks/use-codegate-status.ts rename to src/features/header/hooks/use-codegate-status.ts index 93c041ad..ca1d4161 100644 --- a/src/features/dashboard-codegate-status/hooks/use-codegate-status.ts +++ b/src/features/header/hooks/use-codegate-status.ts @@ -1,9 +1,5 @@ import { queryOptions, useQuery } from "@tanstack/react-query"; -import { - PollingInterval, - POLLING_INTERVAl, -} from "../components/codegate-status-polling-control"; import { healthCheckHealthGet, v1VersionCheck } from "@/api/generated"; import { HealthStatus, VersionResponse } from "../types"; @@ -24,9 +20,7 @@ const getVersion = async (): Promise => { return ((await v1VersionCheck()).data as VersionResponse) ?? null; }; -export function getQueryOptionsCodeGateStatus( - pollingInterval: PollingInterval, -) { +export function getQueryOptionsCodeGateStatus() { return queryOptions({ queryFn: async () => { const health = await getCodeGateHealth(); @@ -37,8 +31,8 @@ export function getQueryOptionsCodeGateStatus( version: version as VersionResponse | null, }; }, - queryKey: ["useHealthCheck", { pollingInterval }], - refetchInterval: POLLING_INTERVAl[pollingInterval].value, + queryKey: ["useHealthCheck"], + refetchInterval: 5_000, staleTime: Infinity, gcTime: Infinity, refetchIntervalInBackground: true, @@ -49,7 +43,7 @@ export function getQueryOptionsCodeGateStatus( }); } -export const useCodeGateStatus = (pollingInterval: PollingInterval) => +export const useCodeGateStatus = () => useQuery({ - ...getQueryOptionsCodeGateStatus(pollingInterval), + ...getQueryOptionsCodeGateStatus(), }); diff --git a/src/features/dashboard-codegate-status/types.ts b/src/features/header/types.ts similarity index 100% rename from src/features/dashboard-codegate-status/types.ts rename to src/features/header/types.ts diff --git a/src/routes/route-dashboard.tsx b/src/routes/route-dashboard.tsx index b7e4dbd2..76a368dc 100644 --- a/src/routes/route-dashboard.tsx +++ b/src/routes/route-dashboard.tsx @@ -4,7 +4,6 @@ import { BarChart } from "@/viz/BarChart"; import { LineChart } from "@/viz/LineChart"; import { PieChart } from "@/viz/PieChart"; import { useSearchParams } from "react-router-dom"; -import { CodegateStatus } from "@/features/dashboard-codegate-status/components/codegate-status"; import { useAlertsData, useMaliciousPackagesChartData, @@ -34,8 +33,7 @@ export function RouteDashboard() { return ( - - +