@cryptotaxi247 / CoPilot / commits / 9f753f40

Refactor GitHub audit (#893)

* chore(gitignore): Add AI related files * chore(deps): Update dependencies * refactor(github-audit): restyle info drawer * refactor(github-audit): enhance form and list UX * refactor(github-audit): enhance audit card display * refactor(github-audit): Streamline audit card UI * refactor(github-audit): Restyle audit card details * refactor(github-audit): Simplify GitHub Audit Card layout and introduce grade label component * refactor(github-audit): Add 'View Details' button to GitHub Audit Card footer * refactor(github-audit): remove unused GitHubAuditFilters and GitHubAuditStats components * refactor(github-audit): enhance GitHub Audit Detail layout and improve score display * refactor(github-audit): replace drawer components with a simplified layout for configuration and info forms * refactor(github-audit): consolidate audit score and details into a new GitHubAuditConfigSummary component * refactor(github-audit): introduce GitHubAuditScopeFlags component for improved flag display * refactor(github-audit): improve layout and styling of GitHub Audit Report Card for better readability * refactor(github-audit): enhance GitHub Audit components with improved layout and styling for better clarity * refactor(github-audit): implement loading state and enhance report detail display in GitHub Audit components * refactor(github-audit): enhance drawer components and improve layout for GitHub Audit details * refactor(github-audit): streamline modal and button components for improved user experience in exclusion management * refactor(github-audit): improve layout and styling of GitHub Audit components for enhanced readability and responsiveness * lint * feat(shuffle): add shuffle-override styles and integrate into main SCSS file; update ShuffleAppAuth.vue to include empty state * chore(dependencies): update @shuffleio/shuffle-mcps to v0.0.32, react-router-dom to v7.16.0, and vue-router to v5.1.0 * chore(version): update CURRENT_VERSION to 0.1.67 --------- Co-authored-by: Davide Di Modica <webmaster.ddm@gmail.com>

taylor_socfortress committed May 28, 2026 at 16:14 UTC 9f753f40480a81fb07efa41f60bf11803adfa122
24 files changed +1461 -1612
.gitignore
+4 -2
@@ -68,8 +68,10 @@ frontend/tsconfig.app.tsbuildinfo
68 frontend/tsconfig.node.tsbuildinfo
69 frontend/tsconfig.vitest.tsbuildinfo
70
71 +# AI
72 +.agents/
73 AGENTS.md
72 -
74 .cursor/hooks/state/continual-learning-index.json
74 -
75 .cursor/hooks/state/continual-learning.json
76 +
77 +skills-lock.json
backend/app/version/services/version.py
+1 -1
@@ -7,7 +7,7 @@ from loguru import logger
7 from packaging.version import Version
8
9 # Current version - update this with each release
10 -CURRENT_VERSION = "0.1.66"
10 +CURRENT_VERSION = "0.1.67"
11 VERSION_CHECK_URL = "https://api.github.com/repos/socfortress/CoPilot/releases/latest"
12
13
frontend/package.json
+6 -6
@@ -50,7 +50,7 @@
50 "@microsoft/fetch-event-source": "^2.0.1",
51 "@mui/material": "^9.0.1",
52 "@shikijs/markdown-it": "^4.1.0",
53 - "@shuffleio/shuffle-mcps": "^0.0.26",
53 + "@shuffleio/shuffle-mcps": "^0.0.32",
54 "@types/codemirror": "^5.60.17",
55 "@vueuse/core": "^14.3.0",
56 "@vueuse/motion": "^3.0.3",
@@ -60,7 +60,7 @@
60 "bytes": "^3.1.2",
61 "codemirror": "~6.0.2",
62 "colord": "^2.9.3",
63 - "dayjs": "^1.11.20",
63 + "dayjs": "^1.11.21",
64 "detect-touch-device": "^1.1.6",
65 "echarts": "^6.1.0",
66 "fast-xml-parser": "^5.8.0",
@@ -78,20 +78,20 @@
78 "pinia-plugin-persistedstate": "^4.7.1",
79 "react": "^19.2.6",
80 "react-dom": "^19.2.6",
81 - "react-router-dom": "^7.15.1",
81 + "react-router-dom": "^7.16.0",
82 "react18-json-view": "^0.2.10",
83 "remark-breaks": "^4.0.0",
84 "secure-ls": "^2.0.0",
85 "shiki": "^4.1.0",
86 "thememirror": "^2.0.1",
87 "validator": "^13.15.35",
88 - "vue": "^3.5.34",
88 + "vue": "^3.5.35",
89 "vue-advanced-cropper": "^2.8.9",
90 "vue-codemirror": "^6.1.1",
91 "vue-echarts": "^8.0.1",
92 "vue-highlight-words": "^3.0.1",
93 "vue-i18n": "^11.4.4",
94 - "vue-router": "^5.0.7",
94 + "vue-router": "^5.1.0",
95 "vue-sjv": "^0.0.6",
96 "vue3-apexcharts": "^1.11.1",
97 "vue3-marquee": "^4.2.2",
@@ -135,7 +135,7 @@
135 "npm-run-all2": "^9.0.1",
136 "prettier": "^3.8.3",
137 "prettier-plugin-tailwindcss": "^0.8.0",
138 - "rolldown": "1.0.2",
138 + "rolldown": "1.0.3",
139 "sass": "^1.100.0",
140 "start-server-and-test": "^3.0.5",
141 "tailwindcss": "^4.3.0",
frontend/pnpm-lock.yaml
+296 -244
@@ -37,7 +37,7 @@ importers:
37 version: 6.43.0
38 '@f3ve/vue-markdown-it':
39 specifier: ^0.2.3
40 - version: 0.2.3(vue@3.5.34(typescript@6.0.3))
40 + version: 0.2.3(vue@3.5.35(typescript@6.0.3))
41 '@fontsource/jetbrains-mono':
42 specifier: ^5.2.8
43 version: 5.2.8
@@ -57,17 +57,17 @@ importers:
57 specifier: ^4.1.0
58 version: 4.1.0
59 '@shuffleio/shuffle-mcps':
60 - specifier: ^0.0.26
61 - version: 0.0.26(@emotion/is-prop-valid@1.4.0)(@emotion/react@11.14.0(@types/react@19.2.15)(react@19.2.6))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.15)(react@19.2.6))(@types/react@19.2.15)(react@19.2.6))(@mui/material@9.0.1(@emotion/react@11.14.0(@types/react@19.2.15)(react@19.2.6))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.15)(react@19.2.6))(@types/react@19.2.15)(react@19.2.6))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(@types/react@19.2.15)(lucide-react@1.16.0(react@19.2.6))(react-dom@19.2.6(react@19.2.6))(react-router-dom@7.15.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(react@19.2.6)
60 + specifier: ^0.0.32
61 + version: 0.0.32(@emotion/is-prop-valid@1.4.0)(@emotion/react@11.14.0(@types/react@19.2.15)(react@19.2.6))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.15)(react@19.2.6))(@types/react@19.2.15)(react@19.2.6))(@mui/material@9.0.1(@emotion/react@11.14.0(@types/react@19.2.15)(react@19.2.6))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.15)(react@19.2.6))(@types/react@19.2.15)(react@19.2.6))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(@types/react@19.2.15)(lucide-react@1.16.0(react@19.2.6))(react-dom@19.2.6(react@19.2.6))(react-router-dom@7.16.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(react@19.2.6)
62 '@types/codemirror':
63 specifier: ^5.60.17
64 version: 5.60.17
65 '@vueuse/core':
66 specifier: ^14.3.0
67 - version: 14.3.0(vue@3.5.34(typescript@6.0.3))
67 + version: 14.3.0(vue@3.5.35(typescript@6.0.3))
68 '@vueuse/motion':
69 specifier: ^3.0.3
70 - version: 3.0.3(vue@3.5.34(typescript@6.0.3))
70 + version: 3.0.3(vue@3.5.35(typescript@6.0.3))
71 algoliasearch:
72 specifier: ^5.52.1
73 version: 5.52.1
@@ -87,8 +87,8 @@ importers:
87 specifier: ^2.9.3
88 version: 2.9.3
89 dayjs:
90 - specifier: ^1.11.20
91 - version: 1.11.20
90 + specifier: ^1.11.21
91 + version: 1.11.21
92 detect-touch-device:
93 specifier: ^1.1.6
94 version: 1.1.6
@@ -118,7 +118,7 @@ importers:
118 version: 3.0.1
119 naive-ui:
120 specifier: ^2.44.1
121 - version: 2.44.1(vue@3.5.34(typescript@6.0.3))
121 + version: 2.44.1(vue@3.5.35(typescript@6.0.3))
122 nanoid:
123 specifier: ^5.1.11
124 version: 5.1.11
@@ -130,10 +130,10 @@ importers:
130 version: 5.3.0
131 pinia:
132 specifier: ^3.0.4
133 - version: 3.0.4(typescript@6.0.3)(vue@3.5.34(typescript@6.0.3))
133 + version: 3.0.4(typescript@6.0.3)(vue@3.5.35(typescript@6.0.3))
134 pinia-plugin-persistedstate:
135 specifier: ^4.7.1
136 - version: 4.7.1(@nuxt/kit@3.21.6)(pinia@3.0.4(typescript@6.0.3)(vue@3.5.34(typescript@6.0.3)))
136 + version: 4.7.1(@nuxt/kit@3.21.6)(pinia@3.0.4(typescript@6.0.3)(vue@3.5.35(typescript@6.0.3)))
137 react:
138 specifier: ^19.2.6
139 version: 19.2.6
@@ -141,8 +141,8 @@ importers:
141 specifier: ^19.2.6
142 version: 19.2.6(react@19.2.6)
143 react-router-dom:
144 - specifier: ^7.15.1
145 - version: 7.15.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
144 + specifier: ^7.16.0
145 + version: 7.16.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
146 react18-json-view:
147 specifier: ^0.2.10
148 version: 0.2.10(react@19.2.6)
@@ -162,38 +162,38 @@ importers:
162 specifier: ^13.15.35
163 version: 13.15.35
164 vue:
165 - specifier: ^3.5.34
166 - version: 3.5.34(typescript@6.0.3)
165 + specifier: ^3.5.35
166 + version: 3.5.35(typescript@6.0.3)
167 vue-advanced-cropper:
168 specifier: ^2.8.9
169 - version: 2.8.9(vue@3.5.34(typescript@6.0.3))
169 + version: 2.8.9(vue@3.5.35(typescript@6.0.3))
170 vue-codemirror:
171 specifier: ^6.1.1
172 - version: 6.1.1(codemirror@6.0.2)(vue@3.5.34(typescript@6.0.3))
172 + version: 6.1.1(codemirror@6.0.2)(vue@3.5.35(typescript@6.0.3))
173 vue-echarts:
174 specifier: ^8.0.1
175 - version: 8.0.1(echarts@6.1.0)(vue@3.5.34(typescript@6.0.3))
175 + version: 8.0.1(echarts@6.1.0)(vue@3.5.35(typescript@6.0.3))
176 vue-highlight-words:
177 specifier: ^3.0.1
178 - version: 3.0.1(vue@3.5.34(typescript@6.0.3))
178 + version: 3.0.1(vue@3.5.35(typescript@6.0.3))
179 vue-i18n:
180 specifier: ^11.4.4
181 - version: 11.4.4(vue@3.5.34(typescript@6.0.3))
181 + version: 11.4.4(vue@3.5.35(typescript@6.0.3))
182 vue-router:
183 - specifier: ^5.0.7
184 - version: 5.0.7(@vue/compiler-sfc@3.5.34)(pinia@3.0.4(typescript@6.0.3)(vue@3.5.34(typescript@6.0.3)))(vue@3.5.34(typescript@6.0.3))
183 + specifier: ^5.1.0
184 + version: 5.1.0(@vue/compiler-sfc@3.5.35)(pinia@3.0.4(typescript@6.0.3)(vue@3.5.35(typescript@6.0.3)))(vite@8.0.13(@types/node@25.9.1)(jiti@2.7.0)(sass@1.100.0)(yaml@2.9.0))(vue@3.5.35(typescript@6.0.3))
185 vue-sjv:
186 specifier: ^0.0.6
187 - version: 0.0.6(vue@3.5.34(typescript@6.0.3))
187 + version: 0.0.6(vue@3.5.35(typescript@6.0.3))
188 vue3-apexcharts:
189 specifier: ^1.11.1
190 - version: 1.11.1(apexcharts@5.13.0)(vue@3.5.34(typescript@6.0.3))
190 + version: 1.11.1(apexcharts@5.13.0)(vue@3.5.35(typescript@6.0.3))
191 vue3-marquee:
192 specifier: ^4.2.2
193 - version: 4.2.2(vue@3.5.34(typescript@6.0.3))
193 + version: 4.2.2(vue@3.5.35(typescript@6.0.3))
194 vuedraggable:
195 specifier: ^4.1.0
196 - version: 4.1.0(vue@3.5.34(typescript@6.0.3))
196 + version: 4.1.0(vue@3.5.35(typescript@6.0.3))
197 xmllint:
198 specifier: ^0.1.1
199 version: 0.1.1
@@ -203,13 +203,13 @@ importers:
203 devDependencies:
204 '@antfu/eslint-config':
205 specifier: ~9.0.0
206 - version: 9.0.0(@typescript-eslint/rule-tester@8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3))(@typescript-eslint/typescript-estree@8.60.0(typescript@6.0.3))(@typescript-eslint/utils@8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3))(@vue/compiler-sfc@3.5.34)(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3)(vitest@4.1.7(@types/node@25.9.1)(jsdom@29.1.1)(vite@8.0.13(@types/node@25.9.1)(jiti@2.7.0)(sass@1.100.0)(yaml@2.9.0)))
206 + version: 9.0.0(@typescript-eslint/rule-tester@8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3))(@typescript-eslint/typescript-estree@8.60.0(typescript@6.0.3))(@typescript-eslint/utils@8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3))(@vue/compiler-sfc@3.5.35)(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3)(vitest@4.1.7(@types/node@25.9.1)(jsdom@29.1.1)(vite@8.0.13(@types/node@25.9.1)(jiti@2.7.0)(sass@1.100.0)(yaml@2.9.0)))
207 '@clack/prompts':
208 specifier: ^1.4.0
209 version: 1.4.0
210 '@iconify/vue':
211 specifier: ^5.0.1
212 - version: 5.0.1(vue@3.5.34(typescript@6.0.3))
212 + version: 5.0.1(vue@3.5.35(typescript@6.0.3))
213 '@tailwindcss/vite':
214 specifier: ^4.3.0
215 version: 4.3.0(vite@8.0.13(@types/node@25.9.1)(jiti@2.7.0)(sass@1.100.0)(yaml@2.9.0))
@@ -251,16 +251,16 @@ importers:
251 version: 13.15.10
252 '@vitejs/plugin-vue':
253 specifier: ^6.0.7
254 - version: 6.0.7(vite@8.0.13(@types/node@25.9.1)(jiti@2.7.0)(sass@1.100.0)(yaml@2.9.0))(vue@3.5.34(typescript@6.0.3))
254 + version: 6.0.7(vite@8.0.13(@types/node@25.9.1)(jiti@2.7.0)(sass@1.100.0)(yaml@2.9.0))(vue@3.5.35(typescript@6.0.3))
255 '@vitejs/plugin-vue-jsx':
256 specifier: ^5.1.5
257 - version: 5.1.5(vite@8.0.13(@types/node@25.9.1)(jiti@2.7.0)(sass@1.100.0)(yaml@2.9.0))(vue@3.5.34(typescript@6.0.3))
257 + version: 5.1.5(vite@8.0.13(@types/node@25.9.1)(jiti@2.7.0)(sass@1.100.0)(yaml@2.9.0))(vue@3.5.35(typescript@6.0.3))
258 '@vue/test-utils':
259 specifier: ^2.4.10
260 - version: 2.4.10(@vue/compiler-dom@3.5.34)(@vue/server-renderer@3.5.34(vue@3.5.34(typescript@6.0.3)))(vue@3.5.34(typescript@6.0.3))
260 + version: 2.4.10(@vue/compiler-dom@3.5.35)(@vue/server-renderer@3.5.35(vue@3.5.35(typescript@6.0.3)))(vue@3.5.35(typescript@6.0.3))
261 '@vue/tsconfig':
262 specifier: ~0.9.1
263 - version: 0.9.1(typescript@6.0.3)(vue@3.5.34(typescript@6.0.3))
263 + version: 0.9.1(typescript@6.0.3)(vue@3.5.35(typescript@6.0.3))
264 baseline-browser-mapping:
265 specifier: ^2.10.32
266 version: 2.10.32
@@ -292,8 +292,8 @@ importers:
292 specifier: ^0.8.0
293 version: 0.8.0(prettier@3.8.3)
294 rolldown:
295 - specifier: 1.0.2
296 - version: 1.0.2
295 + specifier: 1.0.3
296 + version: 1.0.3
297 sass:
298 specifier: ^1.100.0
299 version: 1.100.0
@@ -317,16 +317,16 @@ importers:
317 version: 8.0.13(@types/node@25.9.1)(jiti@2.7.0)(sass@1.100.0)(yaml@2.9.0)
318 vite-bundle-visualizer:
319 specifier: ^1.2.1
320 - version: 1.2.1(rolldown@1.0.2)
320 + version: 1.2.1(rolldown@1.0.3)
321 vite-plugin-inspect:
322 specifier: ^11.3.3
323 version: 11.3.3(@nuxt/kit@3.21.6)(vite@8.0.13(@types/node@25.9.1)(jiti@2.7.0)(sass@1.100.0)(yaml@2.9.0))
324 vite-plugin-vue-devtools:
325 specifier: ^8.1.2
326 - version: 8.1.2(@nuxt/kit@3.21.6)(vite@8.0.13(@types/node@25.9.1)(jiti@2.7.0)(sass@1.100.0)(yaml@2.9.0))(vue@3.5.34(typescript@6.0.3))
326 + version: 8.1.2(@nuxt/kit@3.21.6)(vite@8.0.13(@types/node@25.9.1)(jiti@2.7.0)(sass@1.100.0)(yaml@2.9.0))(vue@3.5.35(typescript@6.0.3))
327 vite-svg-loader:
328 specifier: ^5.1.1
329 - version: 5.1.1(vue@3.5.34(typescript@6.0.3))
329 + version: 5.1.1(vue@3.5.35(typescript@6.0.3))
330 vitest:
331 specifier: ^4.1.7
332 version: 4.1.7(@types/node@25.9.1)(jsdom@29.1.1)(vite@8.0.13(@types/node@25.9.1)(jiti@2.7.0)(sass@1.100.0)(yaml@2.9.0))
@@ -342,7 +342,7 @@ importers:
342 version: 0.3.11
343 vueuc:
344 specifier: ^0.4.64
345 - version: 0.4.65(vue@3.5.34(typescript@6.0.3))
345 + version: 0.4.65(vue@3.5.35(typescript@6.0.3))
346
347 packages:
348
@@ -1156,8 +1156,8 @@ packages:
1156 '@oxc-project/types@0.130.0':
1157 resolution: {integrity: sha512-ibD2usx9JRu7f5pu2tMKMI4cpA4NgXJQoYRP4pQ7Pxmn1l6k/53qWtQWZayhYy3X4QZkt90Ot+mJEaeXouio6Q==}
1158
1159 - '@oxc-project/types@0.132.0':
1160 - resolution: {integrity: sha512-FESMOxil5Se014ui/Eq8fT5uHJo6nIRwH0PfJrZJXs6Gek3ZVFOrpUv3YIZT20m+extU98Hg1Ym72U58rlsxUQ==}
1159 + '@oxc-project/types@0.133.0':
1160 + resolution: {integrity: sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==}
1161
1162 '@parcel/watcher-android-arm64@2.5.6':
1163 resolution: {integrity: sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==}
@@ -1270,8 +1270,8 @@ packages:
1270 cpu: [arm64]
1271 os: [android]
1272
1273 - '@rolldown/binding-android-arm64@1.0.2':
1274 - resolution: {integrity: sha512-ZS4D1JPGn/MYQN/SYDWftIE/nVsM8j/AFOYEzAoOE2O3NktQOZru+/vYXGbR/qtdLdIfGCP0lcoJiYVzsEz+iQ==}
1273 + '@rolldown/binding-android-arm64@1.0.3':
1274 + resolution: {integrity: sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==}
1275 engines: {node: ^20.19.0 || >=22.12.0}
1276 cpu: [arm64]
1277 os: [android]
@@ -1282,8 +1282,8 @@ packages:
1282 cpu: [arm64]
1283 os: [darwin]
1284
1285 - '@rolldown/binding-darwin-arm64@1.0.2':
1286 - resolution: {integrity: sha512-vdFA9+C/rekyGce7WqHs/xoT0ioZEWaOFyZLIV1mEeNFaFDUQrPIo8Vs2GvJ6eetb3rzDUtUBgzto3ExpXJB3w==}
1285 + '@rolldown/binding-darwin-arm64@1.0.3':
1286 + resolution: {integrity: sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA==}
1287 engines: {node: ^20.19.0 || >=22.12.0}
1288 cpu: [arm64]
1289 os: [darwin]
@@ -1294,8 +1294,8 @@ packages:
1294 cpu: [x64]
1295 os: [darwin]
1296
1297 - '@rolldown/binding-darwin-x64@1.0.2':
1298 - resolution: {integrity: sha512-BewSOwTHazv77DTYiAZXSqqKZ4KP/KonFisDMVU7PImxoWfB2aepnPhd2E4SWz3zDzYgDNbs6jBmTdgNnF02GA==}
1297 + '@rolldown/binding-darwin-x64@1.0.3':
1298 + resolution: {integrity: sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg==}
1299 engines: {node: ^20.19.0 || >=22.12.0}
1300 cpu: [x64]
1301 os: [darwin]
@@ -1306,8 +1306,8 @@ packages:
1306 cpu: [x64]
1307 os: [freebsd]
1308
1309 - '@rolldown/binding-freebsd-x64@1.0.2':
1310 - resolution: {integrity: sha512-m41o7M0YWtUdqk61Tb+jnKb2rN++iRdIASlExkUoKfIAH30DOHCB8fVLzSUpbWHHU8esmEioY62PxzexE8MBuA==}
1309 + '@rolldown/binding-freebsd-x64@1.0.3':
1310 + resolution: {integrity: sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g==}
1311 engines: {node: ^20.19.0 || >=22.12.0}
1312 cpu: [x64]
1313 os: [freebsd]
@@ -1318,8 +1318,8 @@ packages:
1318 cpu: [arm]
1319 os: [linux]
1320
1321 - '@rolldown/binding-linux-arm-gnueabihf@1.0.2':
1322 - resolution: {integrity: sha512-jcojB9H7W/jS29pMKWAK1N+fU99vXodHDTatS3b3y/XSOCiHo0kkA74pL3jJmkoQtYpOCxDvaKs1fo2Ij/1X5w==}
1321 + '@rolldown/binding-linux-arm-gnueabihf@1.0.3':
1322 + resolution: {integrity: sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw==}
1323 engines: {node: ^20.19.0 || >=22.12.0}
1324 cpu: [arm]
1325 os: [linux]
@@ -1331,8 +1331,8 @@ packages:
1331 os: [linux]
1332 libc: [glibc]
1333
1334 - '@rolldown/binding-linux-arm64-gnu@1.0.2':
1335 - resolution: {integrity: sha512-1jn6qDU5iiOgFgygDzKUuKP0maTi0/f1+sBLgvij/76C77Nm3ts6ufz9Bjg5q5dduxiUIxtq86JIoBvo1xQ4Ig==}
1334 + '@rolldown/binding-linux-arm64-gnu@1.0.3':
1335 + resolution: {integrity: sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw==}
1336 engines: {node: ^20.19.0 || >=22.12.0}
1337 cpu: [arm64]
1338 os: [linux]
@@ -1345,8 +1345,8 @@ packages:
1345 os: [linux]
1346 libc: [musl]
1347
1348 - '@rolldown/binding-linux-arm64-musl@1.0.2':
1349 - resolution: {integrity: sha512-QVLO/czFMdoMFSqlX3bcswcJNm/23r+qoa/jgtmFc/qEp6/jXmIkDjF/XIo8dPfGaiwy1xfQn8o77L79GeXFgw==}
1348 + '@rolldown/binding-linux-arm64-musl@1.0.3':
1349 + resolution: {integrity: sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==}
1350 engines: {node: ^20.19.0 || >=22.12.0}
1351 cpu: [arm64]
1352 os: [linux]
@@ -1359,8 +1359,8 @@ packages:
1359 os: [linux]
1360 libc: [glibc]
1361
1362 - '@rolldown/binding-linux-ppc64-gnu@1.0.2':
1363 - resolution: {integrity: sha512-hgO5Abm0w5UL6FEa2iFnZqo2KlK7TQ5QhV5x09hujBf7t5KzHQ1VmfPuTpqRy/rNlSxua3eWH374xxiVrP+lcA==}
1362 + '@rolldown/binding-linux-ppc64-gnu@1.0.3':
1363 + resolution: {integrity: sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==}
1364 engines: {node: ^20.19.0 || >=22.12.0}
1365 cpu: [ppc64]
1366 os: [linux]
@@ -1373,8 +1373,8 @@ packages:
1373 os: [linux]
1374 libc: [glibc]
1375
1376 - '@rolldown/binding-linux-s390x-gnu@1.0.2':
1377 - resolution: {integrity: sha512-fy8rXxuYEu602abC8MUNaPjYLIFzReOaEIEMKMUa0rFEUxNpVXhs15KSSQ4qlqSaM7B6rcj9rDZgADh/IGDzLQ==}
1376 + '@rolldown/binding-linux-s390x-gnu@1.0.3':
1377 + resolution: {integrity: sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==}
1378 engines: {node: ^20.19.0 || >=22.12.0}
1379 cpu: [s390x]
1380 os: [linux]
@@ -1387,8 +1387,8 @@ packages:
1387 os: [linux]
1388 libc: [glibc]
1389
1390 - '@rolldown/binding-linux-x64-gnu@1.0.2':
1391 - resolution: {integrity: sha512-0+bOkiQ779+r1WpoHOWHqncvyySci0vKph+myNDYb+im6meJAzHQXay6oEgnkHuUGouM1LKTZwqKpBow6Kj7CQ==}
1390 + '@rolldown/binding-linux-x64-gnu@1.0.3':
1391 + resolution: {integrity: sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==}
1392 engines: {node: ^20.19.0 || >=22.12.0}
1393 cpu: [x64]
1394 os: [linux]
@@ -1401,8 +1401,8 @@ packages:
1401 os: [linux]
1402 libc: [musl]
1403
1404 - '@rolldown/binding-linux-x64-musl@1.0.2':
1405 - resolution: {integrity: sha512-mjSkrzZK5Qsl0a9d1JgILOiuZOSDTVdKENcSXBoqbzSrspLR/4/IRVDo5wd2GgZjNss/viBFJdeq+j7qH2nypw==}
1404 + '@rolldown/binding-linux-x64-musl@1.0.3':
1405 + resolution: {integrity: sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==}
1406 engines: {node: ^20.19.0 || >=22.12.0}
1407 cpu: [x64]
1408 os: [linux]
@@ -1414,8 +1414,8 @@ packages:
1414 cpu: [arm64]
1415 os: [openharmony]
1416
1417 - '@rolldown/binding-openharmony-arm64@1.0.2':
1418 - resolution: {integrity: sha512-1v5vHasdfQAZoEHakBV72LIFAC9JjnymsiKxp+GEr/ma3+NJCPSaYK+qavInOovJkgwFrs7GccX2d6IgDA3Z5w==}
1417 + '@rolldown/binding-openharmony-arm64@1.0.3':
1418 + resolution: {integrity: sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==}
1419 engines: {node: ^20.19.0 || >=22.12.0}
1420 cpu: [arm64]
1421 os: [openharmony]
@@ -1425,8 +1425,8 @@ packages:
1425 engines: {node: ^20.19.0 || >=22.12.0}
1426 cpu: [wasm32]
1427
1428 - '@rolldown/binding-wasm32-wasi@1.0.2':
1429 - resolution: {integrity: sha512-mb1VobWn6NheziTk5/WEaR6AKVbrwT5sOi6C7zk3gy/pD1qtJfU1j4PgTo2NJnOtbL9Dl3Aeei8w9jJ7qC2jZQ==}
1428 + '@rolldown/binding-wasm32-wasi@1.0.3':
1429 + resolution: {integrity: sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg==}
1430 engines: {node: ^20.19.0 || >=22.12.0}
1431 cpu: [wasm32]
1432
@@ -1436,8 +1436,8 @@ packages:
1436 cpu: [arm64]
1437 os: [win32]
1438
1439 - '@rolldown/binding-win32-arm64-msvc@1.0.2':
1440 - resolution: {integrity: sha512-SqKonF56vA/L2yHwHYcEp2P34URpOZ7d1fS635cTkpDnUtEGdUbhI6NzsPdqeSWvAAeGDrxjWjNmibDIdFf9/A==}
1439 + '@rolldown/binding-win32-arm64-msvc@1.0.3':
1440 + resolution: {integrity: sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g==}
1441 engines: {node: ^20.19.0 || >=22.12.0}
1442 cpu: [arm64]
1443 os: [win32]
@@ -1448,8 +1448,8 @@ packages:
1448 cpu: [x64]
1449 os: [win32]
1450
1451 - '@rolldown/binding-win32-x64-msvc@1.0.2':
1452 - resolution: {integrity: sha512-v7qRI7gXLRINcOGXt+7YmAZ6iFuyZVMIoXAxhd8oP+DR9dLfL9GfNIx7PLMxmhZdvq8waUJBQiWN9EKNy+TRBQ==}
1451 + '@rolldown/binding-win32-x64-msvc@1.0.3':
1452 + resolution: {integrity: sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA==}
1453 engines: {node: ^20.19.0 || >=22.12.0}
1454 cpu: [x64]
1455 os: [win32]
@@ -1503,8 +1503,8 @@ packages:
1503 '@shikijs/vscode-textmate@10.0.2':
1504 resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==}
1505
1506 - '@shuffleio/shuffle-mcps@0.0.26':
1507 - resolution: {integrity: sha512-FycWJX2I6ILNOnVxtkTJy5KcUxzgD+uuZr6EsQwns5Mm0lmCLUQwdtRlZjfM7/ZOwdTPv305KCH0+vwtxZIdIA==}
1506 + '@shuffleio/shuffle-mcps@0.0.32':
1507 + resolution: {integrity: sha512-+fa2NXqvKaByzpL+FYzc0ZC+lmHrlmQyLDinWgKIcU9fHXHZsS1uE/1JNizZyr0JFUH+4qm3MGIwycYYVDM3Cg==}
1508 peerDependencies:
1509 '@emotion/react': '>=11'
1510 '@emotion/styled': '>=11'
@@ -1926,15 +1926,27 @@ packages:
1926 '@vue/compiler-core@3.5.34':
1927 resolution: {integrity: sha512-s9cLyK5mLcvZ4Agva5QgRsQyLKvts9WbU9DB6NqiZkkGEdwmcEiylj5Jbwkp680drF/NNCV8OlAJSe+yMLxaJw==}
1928
1929 + '@vue/compiler-core@3.5.35':
1930 + resolution: {integrity: sha512-BUmHaR1J+O+CKZ9uJucdVTEr1LHsdyvv7vG3eNRhK3CczEHeMd/LtsHAuD7PbrxvI2envCY2v7HI1vC1aBRzKw==}
1931 +
1932 '@vue/compiler-dom@3.5.34':
1933 resolution: {integrity: sha512-EbF/T++k0e2MMZlJsBhzK8Sgwt0HcIPOhzn1CTB/lv6sQcyk+OWf8YeiLxZp3ro7MbbLcAfAJ6sEvjFWuNgUCw==}
1934
1935 + '@vue/compiler-dom@3.5.35':
1936 + resolution: {integrity: sha512-k+bprkXxuqhVajgTx5mUHuir7TwQzUKOWR40ng1ncAqQRPnrLngGGgqVEEhOnTMlc8btHYVKmrP8s5Qyg0hvYA==}
1937 +
1938 '@vue/compiler-sfc@3.5.34':
1939 resolution: {integrity: sha512-D/ihr6uZeIt6r+pVZf46RWT1fAsLFMbUP7k8G1VkiiWexriED9GrX3echHd4Abbt17zjlfiFJ8z7a3BxZOPNjg==}
1940
1941 + '@vue/compiler-sfc@3.5.35':
1942 + resolution: {integrity: sha512-G5VPMcXTSywXBgtFOZOnHKBxKSrwXUcvY1iaF5/hRcy7t0J6CH/d8ha9F4nzi00Fax1eLV0QHM7v4mQu68jydw==}
1943 +
1944 '@vue/compiler-ssr@3.5.34':
1945 resolution: {integrity: sha512-cDtTHKibkThKGHH1SP+WdccquNRYQDFH6rRjQCqT9G2ltFAfoR5pUftpab/z+aM5mW9HLLVQW7hfKKQe/1GBeQ==}
1946
1947 + '@vue/compiler-ssr@3.5.35':
1948 + resolution: {integrity: sha512-rGhAeXgdM7/ffTJGXT69rCCdTmjDewnFuUZfBQQHTdcEBeWdT5HCGY60y2ytLJr9/Dsu7IntUi5z/w0h6Rjnzw==}
1949 +
1950 '@vue/devtools-api@6.6.4':
1951 resolution: {integrity: sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==}
1952
@@ -1964,23 +1976,26 @@ packages:
1976 '@vue/language-core@3.3.2':
1977 resolution: {integrity: sha512-CLwjSfHlPLhjd2qhuS3tTFtnOIWHXAM5u4X1DxmzlQ8j5bmOYlKCsSusOP7jCRJnlVg0mCTQtHU3vwFvopZGoQ==}
1978
1967 - '@vue/reactivity@3.5.34':
1968 - resolution: {integrity: sha512-y9XDjCEuBp+98k+UL5dbYkh57AHU4o6cxZedOPXw3bmrZZYLQsVHguGurq7hVrPCSrQtrnz1f9dssyFr+dMXfQ==}
1979 + '@vue/reactivity@3.5.35':
1980 + resolution: {integrity: sha512-tVc+SsHConvh/Lz64qq1pP3rYArBmK42xonovEcxY74SQtvctZodG/zhq54P5dr38cVuw25d27cPNRdlMidpGQ==}
1981
1970 - '@vue/runtime-core@3.5.34':
1971 - resolution: {integrity: sha512-mKeBYvu8tcMSLhypAHBmriUFfWXKTCF/23Z4jiCoYK3UtWepkliViNLuR90V9XOyD62mUxs9p1jsrpK3CCGIzw==}
1982 + '@vue/runtime-core@3.5.35':
1983 + resolution: {integrity: sha512-A/xFNX9loIcWDygeQuNCfKuh0CoYBzxhqEMNah5TSFg9Z53DrFYEN2qi5CU9necjM1OWYegYREUTHmXTmhfXtg==}
1984
1973 - '@vue/runtime-dom@3.5.34':
1974 - resolution: {integrity: sha512-e8kZzERmCwUnBRVsgSQlAfrfU2rGoy0FFKPBXSlfEjc/O3KfA7QP0t1/2ZylrbchjmIKB4dPTd07A6WPr0eOrg==}
1985 + '@vue/runtime-dom@3.5.35':
1986 + resolution: {integrity: sha512-odrJ1C391dbGnyDRh8U+rnP7J2amIEzfmRk5vXy7xi3aZhEXofTvpi0T4HJb6jlNqQZTNPR5MPHSB3RHNkIORA==}
1987
1976 - '@vue/server-renderer@3.5.34':
1977 - resolution: {integrity: sha512-nHxmJoTrKsmrkbILRhkC9gY1G3moZbJTqCzDd7DOOzG5KH9oeJ0Unqrff5f9v0pW//jES05ZkJcNtfE8JjOIew==}
1988 + '@vue/server-renderer@3.5.35':
1989 + resolution: {integrity: sha512-NkebSOYdB97wi8OQcO3HqzZSlymJi/aWsN/7h74OSVhRTm6qGs3Jp3e0rCXynmWwSlKeRrnlIug+ilYoHBmQDA==}
1990 peerDependencies:
1979 - vue: 3.5.34
1991 + vue: 3.5.35
1992
1993 '@vue/shared@3.5.34':
1994 resolution: {integrity: sha512-24uqU4OIiX29ryC3MeWid/Xf2fa2EFRUVLb77nRhk+UrTVrh/XiGtFAFmJBAtBRbjwNdsPRP+jj/OL27Eg1NDA==}
1995
1996 + '@vue/shared@3.5.35':
1997 + resolution: {integrity: sha512-zSbjL7gRXwks2ZQLRGCajBtBXEOXW9Ddhn/HvSdrGkE2dqGnumzW8XtusRrxrE9LvqtiqDXQ+A60Hp6mvdYxfA==}
1998 +
1999 '@vue/test-utils@2.4.10':
2000 resolution: {integrity: sha512-SmoZ5EA1kYiAFs9NkYdiFFQF+cSnUwnvlYEbY+DogWQZUiqOm/Y29eSbc5T6yi75SgSF9863SBeXniIEoPajCA==}
2001 peerDependencies:
@@ -2128,8 +2143,8 @@ packages:
2143 resolution: {integrity: sha512-m1Q/RaVOnTp9JxPX+F+Zn7IcLYMzM8kZofDImfsKZd8MbR+ikdOzTeztStWqfrqIxZnYWryyI9ePm3NGjnZgGw==}
2144 engines: {node: '>=20.19.0'}
2145
2131 - ast-walker-scope@0.8.3:
2132 - resolution: {integrity: sha512-cbdCP0PGOBq0ASG+sjnKIoYkWMKhhz+F/h9pRexUdX2Hd38+WOlBkRKlqkGOSm0YQpcFMQBJeK4WspUAkwsEdg==}
2146 + ast-walker-scope@0.9.0:
2147 + resolution: {integrity: sha512-IJdzo2vLiElBxKzwS36VsCue/62d6IdWjnPB2v3nuPKeWGynp6FF/CYoLa5i/3jXH/z97ZDdsXz6abpgM6w07A==}
2148 engines: {node: '>=20.19.0'}
2149
2150 async-validator@4.2.5:
@@ -2468,8 +2483,8 @@ packages:
2483 date-fns@4.3.0:
2484 resolution: {integrity: sha512-OYcL+3N/jyWbYdFGqoMAhytDgxP9pbYPUUiRCOgn4Fewaadk9l/Wam4Avciiyp2BgkpfQyBV9B+ehnVJych+eQ==}
2485
2471 - dayjs@1.11.20:
2472 - resolution: {integrity: sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==}
2486 + dayjs@1.11.21:
2487 + resolution: {integrity: sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA==}
2488
2489 debounce@1.2.1:
2490 resolution: {integrity: sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==}
@@ -4227,15 +4242,15 @@ packages:
4242 '@types/react': '>=18'
4243 react: '>=18'
4244
4230 - react-router-dom@7.15.1:
4231 - resolution: {integrity: sha512-AzF62gjY6U9rkMq4RfP/r2EVtQ7DMfNMjyOp/flLTCrtRylLiK4wT4pSq6O8rOXZ2eXdZYJPEYe+ifomiv+Igg==}
4245 + react-router-dom@7.16.0:
4246 + resolution: {integrity: sha512-kMUAbimWB5FVbF4Bce4bJsiKJWLIUHq/mEG8+CFDnCSgltptBiG5nguducmsJeGKytlCvQud9Qhzpn49iduTlA==}
4247 engines: {node: '>=20.0.0'}
4248 peerDependencies:
4249 react: '>=18'
4250 react-dom: '>=18'
4251
4237 - react-router@7.15.1:
4238 - resolution: {integrity: sha512-R8rl9HhgikFYoPJymnUtPXWbnDb3oget6lQnfIoupbt61aT9aOhRkDsY2XRhZRyX1Z/8a5sL74fXmFNm3NRK5A==}
4252 + react-router@7.16.0:
4253 + resolution: {integrity: sha512-wArC8lVyJb3+jM9OpDyW6hLCizACWkvQR/sSGqSs+o5uEXEtGlqdZ4v8hENR3Jad6i+LRkK93q/+bQAcvl6V1A==}
4254 engines: {node: '>=20.0.0'}
4255 peerDependencies:
4256 react: '>=18'
@@ -4366,8 +4381,8 @@ packages:
4381 engines: {node: ^20.19.0 || >=22.12.0}
4382 hasBin: true
4383
4369 - rolldown@1.0.2:
4370 - resolution: {integrity: sha512-oZx5zVDtVB44AW3eaifgDml1gWRDZGvjcfdxonE4swNPG98PrrXjaO/KrnUjzlMnztCCRVlUueA1kCXhARGk6g==}
4384 + rolldown@1.0.3:
4385 + resolution: {integrity: sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==}
4386 engines: {node: ^20.19.0 || >=22.12.0}
4387 hasBin: true
4388
@@ -4978,12 +4993,13 @@ packages:
4993 peerDependencies:
4994 vue: ^3.0.0
4995
4981 - vue-router@5.0.7:
4982 - resolution: {integrity: sha512-dqfk8kvRbCutmCOCj/XLDqDEYxc1wBdAOGLuVy5M93ifYMsBd5fIjfaPN4tQAbxr5IprdBDIox1gr4wYyOx/SA==}
4996 + vue-router@5.1.0:
4997 + resolution: {integrity: sha512-HAbiLzLEHQwxPgvsbOJDAwtavszEgLwri6XfyrsPECIFez8+59xc9LofWVdc/HEaSRT822lJ8H9Ns38VVond5g==}
4998 peerDependencies:
4999 '@pinia/colada': '>=0.21.2'
5000 '@vue/compiler-sfc': ^3.5.34
5001 pinia: ^3.0.4
5002 + vite: ^7.0.0 || ^8.0.0
5003 vue: ^3.5.34
5004 peerDependenciesMeta:
5005 '@pinia/colada':
@@ -4992,6 +5008,8 @@ packages:
5008 optional: true
5009 pinia:
5010 optional: true
5011 + vite:
5012 + optional: true
5013
5014 vue-sjv@0.0.6:
5015 resolution: {integrity: sha512-mk0D/OjzTS/sAqMuEWllVbopzKIoluNdgXIe3OIfqSyl53xX71DmZbe/EAUuaxgilQ9OZiQa3Hq5LYr2+CMW/w==}
@@ -5016,8 +5034,8 @@ packages:
5034 peerDependencies:
5035 vue: ^3.2
5036
5019 - vue@3.5.34:
5020 - resolution: {integrity: sha512-WdLBG9gm02OgJIG9axd5Hpx0TFLdzVgfG2evFFu8Rur5O/IoGc5cMjnjh3tPL6GnRGsYvUhBSKVPYVcxRKpMCA==}
5037 + vue@3.5.35:
5038 + resolution: {integrity: sha512-cx89fnr+0kVGHiNFG6y6s0bdjypJRFNZn6x3WPstNdQR1bi1mbB7h4v5IBGTsPJU3nK1+0Iqj3Zf+hZWMieR4Q==}
5039 peerDependencies:
5040 typescript: '*'
5041 peerDependenciesMeta:
@@ -5263,7 +5281,7 @@ snapshots:
5281 dependencies:
5282 '@algolia/client-common': 5.52.1
5283
5266 - '@antfu/eslint-config@9.0.0(@typescript-eslint/rule-tester@8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3))(@typescript-eslint/typescript-estree@8.60.0(typescript@6.0.3))(@typescript-eslint/utils@8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3))(@vue/compiler-sfc@3.5.34)(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3)(vitest@4.1.7(@types/node@25.9.1)(jsdom@29.1.1)(vite@8.0.13(@types/node@25.9.1)(jiti@2.7.0)(sass@1.100.0)(yaml@2.9.0)))':
5284 + '@antfu/eslint-config@9.0.0(@typescript-eslint/rule-tester@8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3))(@typescript-eslint/typescript-estree@8.60.0(typescript@6.0.3))(@typescript-eslint/utils@8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3))(@vue/compiler-sfc@3.5.35)(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3)(vitest@4.1.7(@types/node@25.9.1)(jsdom@29.1.1)(vite@8.0.13(@types/node@25.9.1)(jiti@2.7.0)(sass@1.100.0)(yaml@2.9.0)))':
5285 dependencies:
5286 '@antfu/install-pkg': 1.1.0
5287 '@clack/prompts': 1.4.0
@@ -5295,7 +5313,7 @@ snapshots:
5313 eslint-plugin-unused-imports: 4.4.1(@typescript-eslint/eslint-plugin@8.60.0(@typescript-eslint/parser@8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.0(jiti@2.7.0))
5314 eslint-plugin-vue: 10.9.1(@stylistic/eslint-plugin@5.10.0(eslint@10.4.0(jiti@2.7.0)))(@typescript-eslint/parser@8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.0(jiti@2.7.0))(vue-eslint-parser@10.4.0(eslint@10.4.0(jiti@2.7.0)))
5315 eslint-plugin-yml: 3.3.2(eslint@10.4.0(jiti@2.7.0))
5298 - eslint-processor-vue-blocks: 2.0.0(@vue/compiler-sfc@3.5.34)(eslint@10.4.0(jiti@2.7.0))
5316 + eslint-processor-vue-blocks: 2.0.0(@vue/compiler-sfc@3.5.35)(eslint@10.4.0(jiti@2.7.0))
5317 globals: 17.6.0
5318 local-pkg: 1.2.1
5319 parse-gitignore: 2.0.0
@@ -5656,9 +5674,9 @@ snapshots:
5674 dependencies:
5675 css-render: 0.15.14
5676
5659 - '@css-render/vue3-ssr@0.15.14(vue@3.5.34(typescript@6.0.3))':
5677 + '@css-render/vue3-ssr@0.15.14(vue@3.5.35(typescript@6.0.3))':
5678 dependencies:
5661 - vue: 3.5.34(typescript@6.0.3)
5679 + vue: 3.5.35(typescript@6.0.3)
5680
5681 '@csstools/color-helpers@6.0.2': {}
5682
@@ -5880,10 +5898,10 @@ snapshots:
5898
5899 '@exodus/bytes@1.15.1': {}
5900
5883 - '@f3ve/vue-markdown-it@0.2.3(vue@3.5.34(typescript@6.0.3))':
5901 + '@f3ve/vue-markdown-it@0.2.3(vue@3.5.35(typescript@6.0.3))':
5902 dependencies:
5903 markdown-it: 14.2.0
5886 - vue: 3.5.34(typescript@6.0.3)
5904 + vue: 3.5.35(typescript@6.0.3)
5905
5906 '@fontsource/jetbrains-mono@5.2.8': {}
5907
@@ -5927,10 +5945,10 @@ snapshots:
5945
5946 '@iconify/types@2.0.0': {}
5947
5930 - '@iconify/vue@5.0.1(vue@3.5.34(typescript@6.0.3))':
5948 + '@iconify/vue@5.0.1(vue@3.5.35(typescript@6.0.3))':
5949 dependencies:
5950 '@iconify/types': 2.0.0
5933 - vue: 3.5.34(typescript@6.0.3)
5951 + vue: 3.5.35(typescript@6.0.3)
5952
5953 '@intlify/core-base@11.4.4':
5954 dependencies:
@@ -6173,7 +6191,7 @@ snapshots:
6191
6192 '@oxc-project/types@0.130.0': {}
6193
6176 - '@oxc-project/types@0.132.0': {}
6194 + '@oxc-project/types@0.133.0': {}
6195
6196 '@parcel/watcher-android-arm64@2.5.6':
6197 optional: true
@@ -6252,73 +6270,73 @@ snapshots:
6270 '@rolldown/binding-android-arm64@1.0.1':
6271 optional: true
6272
6255 - '@rolldown/binding-android-arm64@1.0.2':
6273 + '@rolldown/binding-android-arm64@1.0.3':
6274 optional: true
6275
6276 '@rolldown/binding-darwin-arm64@1.0.1':
6277 optional: true
6278
6261 - '@rolldown/binding-darwin-arm64@1.0.2':
6279 + '@rolldown/binding-darwin-arm64@1.0.3':
6280 optional: true
6281
6282 '@rolldown/binding-darwin-x64@1.0.1':
6283 optional: true
6284
6267 - '@rolldown/binding-darwin-x64@1.0.2':
6285 + '@rolldown/binding-darwin-x64@1.0.3':
6286 optional: true
6287
6288 '@rolldown/binding-freebsd-x64@1.0.1':
6289 optional: true
6290
6273 - '@rolldown/binding-freebsd-x64@1.0.2':
6291 + '@rolldown/binding-freebsd-x64@1.0.3':
6292 optional: true
6293
6294 '@rolldown/binding-linux-arm-gnueabihf@1.0.1':
6295 optional: true
6296
6279 - '@rolldown/binding-linux-arm-gnueabihf@1.0.2':
6297 + '@rolldown/binding-linux-arm-gnueabihf@1.0.3':
6298 optional: true
6299
6300 '@rolldown/binding-linux-arm64-gnu@1.0.1':
6301 optional: true
6302
6285 - '@rolldown/binding-linux-arm64-gnu@1.0.2':
6303 + '@rolldown/binding-linux-arm64-gnu@1.0.3':
6304 optional: true
6305
6306 '@rolldown/binding-linux-arm64-musl@1.0.1':
6307 optional: true
6308
6291 - '@rolldown/binding-linux-arm64-musl@1.0.2':
6309 + '@rolldown/binding-linux-arm64-musl@1.0.3':
6310 optional: true
6311
6312 '@rolldown/binding-linux-ppc64-gnu@1.0.1':
6313 optional: true
6314
6297 - '@rolldown/binding-linux-ppc64-gnu@1.0.2':
6315 + '@rolldown/binding-linux-ppc64-gnu@1.0.3':
6316 optional: true
6317
6318 '@rolldown/binding-linux-s390x-gnu@1.0.1':
6319 optional: true
6320
6303 - '@rolldown/binding-linux-s390x-gnu@1.0.2':
6321 + '@rolldown/binding-linux-s390x-gnu@1.0.3':
6322 optional: true
6323
6324 '@rolldown/binding-linux-x64-gnu@1.0.1':
6325 optional: true
6326
6309 - '@rolldown/binding-linux-x64-gnu@1.0.2':
6327 + '@rolldown/binding-linux-x64-gnu@1.0.3':
6328 optional: true
6329
6330 '@rolldown/binding-linux-x64-musl@1.0.1':
6331 optional: true
6332
6315 - '@rolldown/binding-linux-x64-musl@1.0.2':
6333 + '@rolldown/binding-linux-x64-musl@1.0.3':
6334 optional: true
6335
6336 '@rolldown/binding-openharmony-arm64@1.0.1':
6337 optional: true
6338
6321 - '@rolldown/binding-openharmony-arm64@1.0.2':
6339 + '@rolldown/binding-openharmony-arm64@1.0.3':
6340 optional: true
6341
6342 '@rolldown/binding-wasm32-wasi@1.0.1':
@@ -6328,7 +6346,7 @@ snapshots:
6346 '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)
6347 optional: true
6348
6331 - '@rolldown/binding-wasm32-wasi@1.0.2':
6349 + '@rolldown/binding-wasm32-wasi@1.0.3':
6350 dependencies:
6351 '@emnapi/core': 1.10.0
6352 '@emnapi/runtime': 1.10.0
@@ -6338,13 +6356,13 @@ snapshots:
6356 '@rolldown/binding-win32-arm64-msvc@1.0.1':
6357 optional: true
6358
6341 - '@rolldown/binding-win32-arm64-msvc@1.0.2':
6359 + '@rolldown/binding-win32-arm64-msvc@1.0.3':
6360 optional: true
6361
6362 '@rolldown/binding-win32-x64-msvc@1.0.1':
6363 optional: true
6364
6347 - '@rolldown/binding-win32-x64-msvc@1.0.2':
6365 + '@rolldown/binding-win32-x64-msvc@1.0.3':
6366 optional: true
6367
6368 '@rolldown/pluginutils@1.0.1': {}
@@ -6397,7 +6415,7 @@ snapshots:
6415
6416 '@shikijs/vscode-textmate@10.0.2': {}
6417
6400 - '@shuffleio/shuffle-mcps@0.0.26(@emotion/is-prop-valid@1.4.0)(@emotion/react@11.14.0(@types/react@19.2.15)(react@19.2.6))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.15)(react@19.2.6))(@types/react@19.2.15)(react@19.2.6))(@mui/material@9.0.1(@emotion/react@11.14.0(@types/react@19.2.15)(react@19.2.6))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.15)(react@19.2.6))(@types/react@19.2.15)(react@19.2.6))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(@types/react@19.2.15)(lucide-react@1.16.0(react@19.2.6))(react-dom@19.2.6(react@19.2.6))(react-router-dom@7.15.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(react@19.2.6)':
6418 + '@shuffleio/shuffle-mcps@0.0.32(@emotion/is-prop-valid@1.4.0)(@emotion/react@11.14.0(@types/react@19.2.15)(react@19.2.6))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.15)(react@19.2.6))(@types/react@19.2.15)(react@19.2.6))(@mui/material@9.0.1(@emotion/react@11.14.0(@types/react@19.2.15)(react@19.2.6))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.15)(react@19.2.6))(@types/react@19.2.15)(react@19.2.6))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(@types/react@19.2.15)(lucide-react@1.16.0(react@19.2.6))(react-dom@19.2.6(react@19.2.6))(react-router-dom@7.16.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(react@19.2.6)':
6419 dependencies:
6420 '@emotion/react': 11.14.0(@types/react@19.2.15)(react@19.2.6)
6421 '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.15)(react@19.2.6))(@types/react@19.2.15)(react@19.2.6)
@@ -6408,7 +6426,7 @@ snapshots:
6426 react: 19.2.6
6427 react-dom: 19.2.6(react@19.2.6)
6428 react-markdown: 10.1.0(@types/react@19.2.15)(react@19.2.6)
6411 - react-router-dom: 7.15.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
6429 + react-router-dom: 7.16.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
6430 react18-json-view: 0.2.10(react@19.2.6)
6431 remark-breaks: 4.0.0
6432 remark-gfm: 4.0.1
@@ -6730,7 +6748,7 @@ snapshots:
6748
6749 '@ungap/structured-clone@1.3.1': {}
6750
6733 - '@vitejs/plugin-vue-jsx@5.1.5(vite@8.0.13(@types/node@25.9.1)(jiti@2.7.0)(sass@1.100.0)(yaml@2.9.0))(vue@3.5.34(typescript@6.0.3))':
6751 + '@vitejs/plugin-vue-jsx@5.1.5(vite@8.0.13(@types/node@25.9.1)(jiti@2.7.0)(sass@1.100.0)(yaml@2.9.0))(vue@3.5.35(typescript@6.0.3))':
6752 dependencies:
6753 '@babel/core': 7.29.7
6754 '@babel/plugin-syntax-typescript': 7.29.7(@babel/core@7.29.7)
@@ -6738,15 +6756,15 @@ snapshots:
6756 '@rolldown/pluginutils': 1.0.1
6757 '@vue/babel-plugin-jsx': 2.0.1(@babel/core@7.29.7)
6758 vite: 8.0.13(@types/node@25.9.1)(jiti@2.7.0)(sass@1.100.0)(yaml@2.9.0)
6741 - vue: 3.5.34(typescript@6.0.3)
6759 + vue: 3.5.35(typescript@6.0.3)
6760 transitivePeerDependencies:
6761 - supports-color
6762
6745 - '@vitejs/plugin-vue@6.0.7(vite@8.0.13(@types/node@25.9.1)(jiti@2.7.0)(sass@1.100.0)(yaml@2.9.0))(vue@3.5.34(typescript@6.0.3))':
6763 + '@vitejs/plugin-vue@6.0.7(vite@8.0.13(@types/node@25.9.1)(jiti@2.7.0)(sass@1.100.0)(yaml@2.9.0))(vue@3.5.35(typescript@6.0.3))':
6764 dependencies:
6765 '@rolldown/pluginutils': 1.0.1
6766 vite: 8.0.13(@types/node@25.9.1)(jiti@2.7.0)(sass@1.100.0)(yaml@2.9.0)
6749 - vue: 3.5.34(typescript@6.0.3)
6767 + vue: 3.5.35(typescript@6.0.3)
6768
6769 '@vitest/eslint-plugin@1.6.18(@typescript-eslint/eslint-plugin@8.60.0(@typescript-eslint/parser@8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3)(vitest@4.1.7(@types/node@25.9.1)(jsdom@29.1.1)(vite@8.0.13(@types/node@25.9.1)(jiti@2.7.0)(sass@1.100.0)(yaml@2.9.0)))':
6770 dependencies:
@@ -6813,15 +6831,15 @@ snapshots:
6831 path-browserify: 1.0.1
6832 vscode-uri: 3.1.0
6833
6816 - '@vue-macros/common@3.1.2(vue@3.5.34(typescript@6.0.3))':
6834 + '@vue-macros/common@3.1.2(vue@3.5.35(typescript@6.0.3))':
6835 dependencies:
6818 - '@vue/compiler-sfc': 3.5.34
6836 + '@vue/compiler-sfc': 3.5.35
6837 ast-kit: 2.2.0
6838 local-pkg: 1.2.1
6839 magic-string-ast: 1.0.3
6840 unplugin-utils: 0.3.1
6841 optionalDependencies:
6824 - vue: 3.5.34(typescript@6.0.3)
6842 + vue: 3.5.35(typescript@6.0.3)
6843
6844 '@vue/babel-helper-vue-transform-on@1.5.0': {}
6845
@@ -6889,11 +6907,24 @@ snapshots:
6907 estree-walker: 2.0.2
6908 source-map-js: 1.2.1
6909
6910 + '@vue/compiler-core@3.5.35':
6911 + dependencies:
6912 + '@babel/parser': 7.29.7
6913 + '@vue/shared': 3.5.35
6914 + entities: 7.0.1
6915 + estree-walker: 2.0.2
6916 + source-map-js: 1.2.1
6917 +
6918 '@vue/compiler-dom@3.5.34':
6919 dependencies:
6920 '@vue/compiler-core': 3.5.34
6921 '@vue/shared': 3.5.34
6922
6923 + '@vue/compiler-dom@3.5.35':
6924 + dependencies:
6925 + '@vue/compiler-core': 3.5.35
6926 + '@vue/shared': 3.5.35
6927 +
6928 '@vue/compiler-sfc@3.5.34':
6929 dependencies:
6930 '@babel/parser': 7.29.7
@@ -6906,11 +6937,28 @@ snapshots:
6937 postcss: 8.5.15
6938 source-map-js: 1.2.1
6939
6940 + '@vue/compiler-sfc@3.5.35':
6941 + dependencies:
6942 + '@babel/parser': 7.29.7
6943 + '@vue/compiler-core': 3.5.35
6944 + '@vue/compiler-dom': 3.5.35
6945 + '@vue/compiler-ssr': 3.5.35
6946 + '@vue/shared': 3.5.35
6947 + estree-walker: 2.0.2
6948 + magic-string: 0.30.21
6949 + postcss: 8.5.15
6950 + source-map-js: 1.2.1
6951 +
6952 '@vue/compiler-ssr@3.5.34':
6953 dependencies:
6954 '@vue/compiler-dom': 3.5.34
6955 '@vue/shared': 3.5.34
6956
6957 + '@vue/compiler-ssr@3.5.35':
6958 + dependencies:
6959 + '@vue/compiler-dom': 3.5.35
6960 + '@vue/shared': 3.5.35
6961 +
6962 '@vue/devtools-api@6.6.4': {}
6963
6964 '@vue/devtools-api@7.7.9':
@@ -6921,11 +6969,11 @@ snapshots:
6969 dependencies:
6970 '@vue/devtools-kit': 8.1.2
6971
6924 - '@vue/devtools-core@8.1.2(vue@3.5.34(typescript@6.0.3))':
6972 + '@vue/devtools-core@8.1.2(vue@3.5.35(typescript@6.0.3))':
6973 dependencies:
6974 '@vue/devtools-kit': 8.1.2
6975 '@vue/devtools-shared': 8.1.2
6928 - vue: 3.5.34(typescript@6.0.3)
6976 + vue: 3.5.35(typescript@6.0.3)
6977
6978 '@vue/devtools-kit@7.7.9':
6979 dependencies:
@@ -6960,83 +7008,85 @@ snapshots:
7008 path-browserify: 1.0.1
7009 picomatch: 4.0.4
7010
6963 - '@vue/reactivity@3.5.34':
7011 + '@vue/reactivity@3.5.35':
7012 dependencies:
6965 - '@vue/shared': 3.5.34
7013 + '@vue/shared': 3.5.35
7014
6967 - '@vue/runtime-core@3.5.34':
7015 + '@vue/runtime-core@3.5.35':
7016 dependencies:
6969 - '@vue/reactivity': 3.5.34
6970 - '@vue/shared': 3.5.34
7017 + '@vue/reactivity': 3.5.35
7018 + '@vue/shared': 3.5.35
7019
6972 - '@vue/runtime-dom@3.5.34':
7020 + '@vue/runtime-dom@3.5.35':
7021 dependencies:
6974 - '@vue/reactivity': 3.5.34
6975 - '@vue/runtime-core': 3.5.34
6976 - '@vue/shared': 3.5.34
7022 + '@vue/reactivity': 3.5.35
7023 + '@vue/runtime-core': 3.5.35
7024 + '@vue/shared': 3.5.35
7025 csstype: 3.2.3
7026
6979 - '@vue/server-renderer@3.5.34(vue@3.5.34(typescript@6.0.3))':
7027 + '@vue/server-renderer@3.5.35(vue@3.5.35(typescript@6.0.3))':
7028 dependencies:
6981 - '@vue/compiler-ssr': 3.5.34
6982 - '@vue/shared': 3.5.34
6983 - vue: 3.5.34(typescript@6.0.3)
7029 + '@vue/compiler-ssr': 3.5.35
7030 + '@vue/shared': 3.5.35
7031 + vue: 3.5.35(typescript@6.0.3)
7032
7033 '@vue/shared@3.5.34': {}
7034
6987 - '@vue/test-utils@2.4.10(@vue/compiler-dom@3.5.34)(@vue/server-renderer@3.5.34(vue@3.5.34(typescript@6.0.3)))(vue@3.5.34(typescript@6.0.3))':
7035 + '@vue/shared@3.5.35': {}
7036 +
7037 + '@vue/test-utils@2.4.10(@vue/compiler-dom@3.5.35)(@vue/server-renderer@3.5.35(vue@3.5.35(typescript@6.0.3)))(vue@3.5.35(typescript@6.0.3))':
7038 dependencies:
6989 - '@vue/compiler-dom': 3.5.34
7039 + '@vue/compiler-dom': 3.5.35
7040 js-beautify: 1.15.4
6991 - vue: 3.5.34(typescript@6.0.3)
7041 + vue: 3.5.35(typescript@6.0.3)
7042 vue-component-type-helpers: 3.3.2
7043 optionalDependencies:
6994 - '@vue/server-renderer': 3.5.34(vue@3.5.34(typescript@6.0.3))
7044 + '@vue/server-renderer': 3.5.35(vue@3.5.35(typescript@6.0.3))
7045
6996 - '@vue/tsconfig@0.9.1(typescript@6.0.3)(vue@3.5.34(typescript@6.0.3))':
7046 + '@vue/tsconfig@0.9.1(typescript@6.0.3)(vue@3.5.35(typescript@6.0.3))':
7047 optionalDependencies:
7048 typescript: 6.0.3
6999 - vue: 3.5.34(typescript@6.0.3)
7049 + vue: 3.5.35(typescript@6.0.3)
7050
7001 - '@vueuse/core@13.9.0(vue@3.5.34(typescript@6.0.3))':
7051 + '@vueuse/core@13.9.0(vue@3.5.35(typescript@6.0.3))':
7052 dependencies:
7053 '@types/web-bluetooth': 0.0.21
7054 '@vueuse/metadata': 13.9.0
7005 - '@vueuse/shared': 13.9.0(vue@3.5.34(typescript@6.0.3))
7006 - vue: 3.5.34(typescript@6.0.3)
7055 + '@vueuse/shared': 13.9.0(vue@3.5.35(typescript@6.0.3))
7056 + vue: 3.5.35(typescript@6.0.3)
7057
7008 - '@vueuse/core@14.3.0(vue@3.5.34(typescript@6.0.3))':
7058 + '@vueuse/core@14.3.0(vue@3.5.35(typescript@6.0.3))':
7059 dependencies:
7060 '@types/web-bluetooth': 0.0.21
7061 '@vueuse/metadata': 14.3.0
7012 - '@vueuse/shared': 14.3.0(vue@3.5.34(typescript@6.0.3))
7013 - vue: 3.5.34(typescript@6.0.3)
7062 + '@vueuse/shared': 14.3.0(vue@3.5.35(typescript@6.0.3))
7063 + vue: 3.5.35(typescript@6.0.3)
7064
7065 '@vueuse/metadata@13.9.0': {}
7066
7067 '@vueuse/metadata@14.3.0': {}
7068
7019 - '@vueuse/motion@3.0.3(vue@3.5.34(typescript@6.0.3))':
7069 + '@vueuse/motion@3.0.3(vue@3.5.35(typescript@6.0.3))':
7070 dependencies:
7021 - '@vueuse/core': 13.9.0(vue@3.5.34(typescript@6.0.3))
7022 - '@vueuse/shared': 13.9.0(vue@3.5.34(typescript@6.0.3))
7071 + '@vueuse/core': 13.9.0(vue@3.5.35(typescript@6.0.3))
7072 + '@vueuse/shared': 13.9.0(vue@3.5.35(typescript@6.0.3))
7073 defu: 6.1.7
7074 framesync: 6.1.2
7075 popmotion: 11.0.5
7076 style-value-types: 5.1.2
7027 - vue: 3.5.34(typescript@6.0.3)
7077 + vue: 3.5.35(typescript@6.0.3)
7078 optionalDependencies:
7079 '@nuxt/kit': 3.21.6
7080 transitivePeerDependencies:
7081 - magicast
7082
7033 - '@vueuse/shared@13.9.0(vue@3.5.34(typescript@6.0.3))':
7083 + '@vueuse/shared@13.9.0(vue@3.5.35(typescript@6.0.3))':
7084 dependencies:
7035 - vue: 3.5.34(typescript@6.0.3)
7085 + vue: 3.5.35(typescript@6.0.3)
7086
7037 - '@vueuse/shared@14.3.0(vue@3.5.34(typescript@6.0.3))':
7087 + '@vueuse/shared@14.3.0(vue@3.5.35(typescript@6.0.3))':
7088 dependencies:
7039 - vue: 3.5.34(typescript@6.0.3)
7089 + vue: 3.5.35(typescript@6.0.3)
7090
7091 abbrev@2.0.0: {}
7092
@@ -7121,9 +7171,10 @@ snapshots:
7171 '@babel/parser': 7.29.7
7172 pathe: 2.0.3
7173
7124 - ast-walker-scope@0.8.3:
7174 + ast-walker-scope@0.9.0:
7175 dependencies:
7176 '@babel/parser': 7.29.7
7177 + '@babel/types': 7.29.7
7178 ast-kit: 2.2.0
7179
7180 async-validator@4.2.5: {}
@@ -7451,7 +7502,7 @@ snapshots:
7502
7503 date-fns@4.3.0: {}
7504
7454 - dayjs@1.11.20: {}
7505 + dayjs@1.11.21: {}
7506
7507 debounce@1.2.1: {}
7508
@@ -7827,9 +7878,9 @@ snapshots:
7878 natural-compare: 1.4.0
7879 yaml-eslint-parser: 2.0.0
7880
7830 - eslint-processor-vue-blocks@2.0.0(@vue/compiler-sfc@3.5.34)(eslint@10.4.0(jiti@2.7.0)):
7881 + eslint-processor-vue-blocks@2.0.0(@vue/compiler-sfc@3.5.35)(eslint@10.4.0(jiti@2.7.0)):
7882 dependencies:
7832 - '@vue/compiler-sfc': 3.5.34
7883 + '@vue/compiler-sfc': 3.5.35
7884 eslint: 10.4.0(jiti@2.7.0)
7885
7886 eslint-scope@9.1.2:
@@ -9082,10 +9133,10 @@ snapshots:
9133 arrify: 2.0.1
9134 minimatch: 3.1.5
9135
9085 - naive-ui@2.44.1(vue@3.5.34(typescript@6.0.3)):
9136 + naive-ui@2.44.1(vue@3.5.35(typescript@6.0.3)):
9137 dependencies:
9138 '@css-render/plugin-bem': 0.15.14(css-render@0.15.14)
9088 - '@css-render/vue3-ssr': 0.15.14(vue@3.5.34(typescript@6.0.3))
9139 + '@css-render/vue3-ssr': 0.15.14(vue@3.5.35(typescript@6.0.3))
9140 '@types/lodash': 4.17.24
9141 '@types/lodash-es': 4.17.12
9142 async-validator: 4.2.5
@@ -9099,10 +9150,10 @@ snapshots:
9150 lodash-es: 4.18.1
9151 seemly: 0.3.10
9152 treemate: 0.3.11
9102 - vdirs: 0.1.8(vue@3.5.34(typescript@6.0.3))
9103 - vooks: 0.2.12(vue@3.5.34(typescript@6.0.3))
9104 - vue: 3.5.34(typescript@6.0.3)
9105 - vueuc: 0.4.65(vue@3.5.34(typescript@6.0.3))
9153 + vdirs: 0.1.8(vue@3.5.35(typescript@6.0.3))
9154 + vooks: 0.2.12(vue@3.5.35(typescript@6.0.3))
9155 + vue: 3.5.35(typescript@6.0.3)
9156 + vueuc: 0.4.65(vue@3.5.35(typescript@6.0.3))
9157
9158 nanoid@3.3.12: {}
9159
@@ -9287,17 +9338,17 @@ snapshots:
9338
9339 pidtree@0.6.0: {}
9340
9290 - pinia-plugin-persistedstate@4.7.1(@nuxt/kit@3.21.6)(pinia@3.0.4(typescript@6.0.3)(vue@3.5.34(typescript@6.0.3))):
9341 + pinia-plugin-persistedstate@4.7.1(@nuxt/kit@3.21.6)(pinia@3.0.4(typescript@6.0.3)(vue@3.5.35(typescript@6.0.3))):
9342 dependencies:
9343 defu: 6.1.7
9344 optionalDependencies:
9345 '@nuxt/kit': 3.21.6
9295 - pinia: 3.0.4(typescript@6.0.3)(vue@3.5.34(typescript@6.0.3))
9346 + pinia: 3.0.4(typescript@6.0.3)(vue@3.5.35(typescript@6.0.3))
9347
9297 - pinia@3.0.4(typescript@6.0.3)(vue@3.5.34(typescript@6.0.3)):
9348 + pinia@3.0.4(typescript@6.0.3)(vue@3.5.35(typescript@6.0.3)):
9349 dependencies:
9350 '@vue/devtools-api': 7.7.9
9300 - vue: 3.5.34(typescript@6.0.3)
9351 + vue: 3.5.35(typescript@6.0.3)
9352 optionalDependencies:
9353 typescript: 6.0.3
9354
@@ -9480,13 +9531,13 @@ snapshots:
9531 transitivePeerDependencies:
9532 - supports-color
9533
9483 - react-router-dom@7.15.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6):
9534 + react-router-dom@7.16.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6):
9535 dependencies:
9536 react: 19.2.6
9537 react-dom: 19.2.6(react@19.2.6)
9487 - react-router: 7.15.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
9538 + react-router: 7.16.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
9539
9489 - react-router@7.15.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6):
9540 + react-router@7.16.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6):
9541 dependencies:
9542 cookie: 1.1.1
9543 react: 19.2.6
@@ -9644,35 +9695,35 @@ snapshots:
9695 '@rolldown/binding-win32-arm64-msvc': 1.0.1
9696 '@rolldown/binding-win32-x64-msvc': 1.0.1
9697
9647 - rolldown@1.0.2:
9698 + rolldown@1.0.3:
9699 dependencies:
9649 - '@oxc-project/types': 0.132.0
9700 + '@oxc-project/types': 0.133.0
9701 '@rolldown/pluginutils': 1.0.1
9702 optionalDependencies:
9652 - '@rolldown/binding-android-arm64': 1.0.2
9653 - '@rolldown/binding-darwin-arm64': 1.0.2
9654 - '@rolldown/binding-darwin-x64': 1.0.2
9655 - '@rolldown/binding-freebsd-x64': 1.0.2
9656 - '@rolldown/binding-linux-arm-gnueabihf': 1.0.2
9657 - '@rolldown/binding-linux-arm64-gnu': 1.0.2
9658 - '@rolldown/binding-linux-arm64-musl': 1.0.2
9659 - '@rolldown/binding-linux-ppc64-gnu': 1.0.2
9660 - '@rolldown/binding-linux-s390x-gnu': 1.0.2
9661 - '@rolldown/binding-linux-x64-gnu': 1.0.2
9662 - '@rolldown/binding-linux-x64-musl': 1.0.2
9663 - '@rolldown/binding-openharmony-arm64': 1.0.2
9664 - '@rolldown/binding-wasm32-wasi': 1.0.2
9665 - '@rolldown/binding-win32-arm64-msvc': 1.0.2
9666 - '@rolldown/binding-win32-x64-msvc': 1.0.2
9667 -
9668 - rollup-plugin-visualizer@5.14.0(rolldown@1.0.2):
9703 + '@rolldown/binding-android-arm64': 1.0.3
9704 + '@rolldown/binding-darwin-arm64': 1.0.3
9705 + '@rolldown/binding-darwin-x64': 1.0.3
9706 + '@rolldown/binding-freebsd-x64': 1.0.3
9707 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.3
9708 + '@rolldown/binding-linux-arm64-gnu': 1.0.3
9709 + '@rolldown/binding-linux-arm64-musl': 1.0.3
9710 + '@rolldown/binding-linux-ppc64-gnu': 1.0.3
9711 + '@rolldown/binding-linux-s390x-gnu': 1.0.3
9712 + '@rolldown/binding-linux-x64-gnu': 1.0.3
9713 + '@rolldown/binding-linux-x64-musl': 1.0.3
9714 + '@rolldown/binding-openharmony-arm64': 1.0.3
9715 + '@rolldown/binding-wasm32-wasi': 1.0.3
9716 + '@rolldown/binding-win32-arm64-msvc': 1.0.3
9717 + '@rolldown/binding-win32-x64-msvc': 1.0.3
9718 +
9719 + rollup-plugin-visualizer@5.14.0(rolldown@1.0.3):
9720 dependencies:
9721 open: 8.4.2
9722 picomatch: 4.0.4
9723 source-map: 0.7.6
9724 yargs: 17.7.2
9725 optionalDependencies:
9675 - rolldown: 1.0.2
9726 + rolldown: 1.0.3
9727
9728 run-applescript@7.1.0: {}
9729
@@ -10087,10 +10138,10 @@ snapshots:
10138
10139 validator@13.15.35: {}
10140
10090 - vdirs@0.1.8(vue@3.5.34(typescript@6.0.3)):
10141 + vdirs@0.1.8(vue@3.5.35(typescript@6.0.3)):
10142 dependencies:
10143 evtd: 0.2.4
10093 - vue: 3.5.34(typescript@6.0.3)
10144 + vue: 3.5.35(typescript@6.0.3)
10145
10146 vfile-message@4.0.3:
10147 dependencies:
@@ -10102,11 +10153,11 @@ snapshots:
10153 '@types/unist': 3.0.3
10154 vfile-message: 4.0.3
10155
10105 - vite-bundle-visualizer@1.2.1(rolldown@1.0.2):
10156 + vite-bundle-visualizer@1.2.1(rolldown@1.0.3):
10157 dependencies:
10158 cac: 6.7.14
10159 import-from-esm: 1.3.4
10109 - rollup-plugin-visualizer: 5.14.0(rolldown@1.0.2)
10160 + rollup-plugin-visualizer: 5.14.0(rolldown@1.0.3)
10161 tmp: 0.2.5
10162 transitivePeerDependencies:
10163 - rolldown
@@ -10140,9 +10191,9 @@ snapshots:
10191 transitivePeerDependencies:
10192 - supports-color
10193
10143 - vite-plugin-vue-devtools@8.1.2(@nuxt/kit@3.21.6)(vite@8.0.13(@types/node@25.9.1)(jiti@2.7.0)(sass@1.100.0)(yaml@2.9.0))(vue@3.5.34(typescript@6.0.3)):
10194 + vite-plugin-vue-devtools@8.1.2(@nuxt/kit@3.21.6)(vite@8.0.13(@types/node@25.9.1)(jiti@2.7.0)(sass@1.100.0)(yaml@2.9.0))(vue@3.5.35(typescript@6.0.3)):
10195 dependencies:
10145 - '@vue/devtools-core': 8.1.2(vue@3.5.34(typescript@6.0.3))
10196 + '@vue/devtools-core': 8.1.2(vue@3.5.35(typescript@6.0.3))
10197 '@vue/devtools-kit': 8.1.2
10198 '@vue/devtools-shared': 8.1.2
10199 sirv: 3.0.2
@@ -10169,11 +10220,11 @@ snapshots:
10220 transitivePeerDependencies:
10221 - supports-color
10222
10172 - vite-svg-loader@5.1.1(vue@3.5.34(typescript@6.0.3)):
10223 + vite-svg-loader@5.1.1(vue@3.5.35(typescript@6.0.3)):
10224 dependencies:
10225 debug: 4.4.3
10226 svgo: 3.3.3
10176 - vue: 3.5.34(typescript@6.0.3)
10227 + vue: 3.5.35(typescript@6.0.3)
10228 transitivePeerDependencies:
10229 - supports-color
10230
@@ -10221,35 +10272,35 @@ snapshots:
10272
10273 void-elements@3.1.0: {}
10274
10224 - vooks@0.2.12(vue@3.5.34(typescript@6.0.3)):
10275 + vooks@0.2.12(vue@3.5.35(typescript@6.0.3)):
10276 dependencies:
10277 evtd: 0.2.4
10227 - vue: 3.5.34(typescript@6.0.3)
10278 + vue: 3.5.35(typescript@6.0.3)
10279
10280 vscode-uri@3.1.0: {}
10281
10231 - vue-advanced-cropper@2.8.9(vue@3.5.34(typescript@6.0.3)):
10282 + vue-advanced-cropper@2.8.9(vue@3.5.35(typescript@6.0.3)):
10283 dependencies:
10284 classnames: 2.5.1
10285 debounce: 1.2.1
10286 easy-bem: 1.1.1
10236 - vue: 3.5.34(typescript@6.0.3)
10287 + vue: 3.5.35(typescript@6.0.3)
10288
10238 - vue-codemirror@6.1.1(codemirror@6.0.2)(vue@3.5.34(typescript@6.0.3)):
10289 + vue-codemirror@6.1.1(codemirror@6.0.2)(vue@3.5.35(typescript@6.0.3)):
10290 dependencies:
10291 '@codemirror/commands': 6.10.3
10292 '@codemirror/language': 6.12.3
10293 '@codemirror/state': 6.6.0
10294 '@codemirror/view': 6.43.0
10295 codemirror: 6.0.2
10245 - vue: 3.5.34(typescript@6.0.3)
10296 + vue: 3.5.35(typescript@6.0.3)
10297
10298 vue-component-type-helpers@3.3.2: {}
10299
10249 - vue-echarts@8.0.1(echarts@6.1.0)(vue@3.5.34(typescript@6.0.3)):
10300 + vue-echarts@8.0.1(echarts@6.1.0)(vue@3.5.35(typescript@6.0.3)):
10301 dependencies:
10302 echarts: 6.1.0
10252 - vue: 3.5.34(typescript@6.0.3)
10303 + vue: 3.5.35(typescript@6.0.3)
10304
10305 vue-eslint-parser@10.4.0(eslint@10.4.0(jiti@2.7.0)):
10306 dependencies:
@@ -10263,25 +10314,25 @@ snapshots:
10314 transitivePeerDependencies:
10315 - supports-color
10316
10266 - vue-highlight-words@3.0.1(vue@3.5.34(typescript@6.0.3)):
10317 + vue-highlight-words@3.0.1(vue@3.5.35(typescript@6.0.3)):
10318 dependencies:
10319 highlight-words-core: 1.2.3
10269 - vue: 3.5.34(typescript@6.0.3)
10320 + vue: 3.5.35(typescript@6.0.3)
10321
10271 - vue-i18n@11.4.4(vue@3.5.34(typescript@6.0.3)):
10322 + vue-i18n@11.4.4(vue@3.5.35(typescript@6.0.3)):
10323 dependencies:
10324 '@intlify/core-base': 11.4.4
10325 '@intlify/devtools-types': 11.4.4
10326 '@intlify/shared': 11.4.4
10327 '@vue/devtools-api': 6.6.4
10277 - vue: 3.5.34(typescript@6.0.3)
10328 + vue: 3.5.35(typescript@6.0.3)
10329
10279 - vue-router@5.0.7(@vue/compiler-sfc@3.5.34)(pinia@3.0.4(typescript@6.0.3)(vue@3.5.34(typescript@6.0.3)))(vue@3.5.34(typescript@6.0.3)):
10330 + vue-router@5.1.0(@vue/compiler-sfc@3.5.35)(pinia@3.0.4(typescript@6.0.3)(vue@3.5.35(typescript@6.0.3)))(vite@8.0.13(@types/node@25.9.1)(jiti@2.7.0)(sass@1.100.0)(yaml@2.9.0))(vue@3.5.35(typescript@6.0.3)):
10331 dependencies:
10332 '@babel/generator': 8.0.0-rc.6
10282 - '@vue-macros/common': 3.1.2(vue@3.5.34(typescript@6.0.3))
10333 + '@vue-macros/common': 3.1.2(vue@3.5.35(typescript@6.0.3))
10334 '@vue/devtools-api': 8.1.2
10284 - ast-walker-scope: 0.8.3
10335 + ast-walker-scope: 0.9.0
10336 chokidar: 5.0.0
10337 json5: 2.2.3
10338 local-pkg: 1.2.1
@@ -10294,15 +10345,16 @@ snapshots:
10345 tinyglobby: 0.2.16
10346 unplugin: 3.0.0
10347 unplugin-utils: 0.3.1
10297 - vue: 3.5.34(typescript@6.0.3)
10348 + vue: 3.5.35(typescript@6.0.3)
10349 yaml: 2.9.0
10350 optionalDependencies:
10300 - '@vue/compiler-sfc': 3.5.34
10301 - pinia: 3.0.4(typescript@6.0.3)(vue@3.5.34(typescript@6.0.3))
10351 + '@vue/compiler-sfc': 3.5.35
10352 + pinia: 3.0.4(typescript@6.0.3)(vue@3.5.35(typescript@6.0.3))
10353 + vite: 8.0.13(@types/node@25.9.1)(jiti@2.7.0)(sass@1.100.0)(yaml@2.9.0)
10354
10303 - vue-sjv@0.0.6(vue@3.5.34(typescript@6.0.3)):
10355 + vue-sjv@0.0.6(vue@3.5.35(typescript@6.0.3)):
10356 dependencies:
10305 - vue: 3.5.34(typescript@6.0.3)
10357 + vue: 3.5.35(typescript@6.0.3)
10358
10359 vue-tsc@3.3.2(typescript@6.0.3):
10360 dependencies:
@@ -10310,40 +10362,40 @@ snapshots:
10362 '@vue/language-core': 3.3.2
10363 typescript: 6.0.3
10364
10313 - vue3-apexcharts@1.11.1(apexcharts@5.13.0)(vue@3.5.34(typescript@6.0.3)):
10365 + vue3-apexcharts@1.11.1(apexcharts@5.13.0)(vue@3.5.35(typescript@6.0.3)):
10366 dependencies:
10367 apexcharts: 5.13.0
10316 - vue: 3.5.34(typescript@6.0.3)
10368 + vue: 3.5.35(typescript@6.0.3)
10369
10318 - vue3-marquee@4.2.2(vue@3.5.34(typescript@6.0.3)):
10370 + vue3-marquee@4.2.2(vue@3.5.35(typescript@6.0.3)):
10371 dependencies:
10320 - vue: 3.5.34(typescript@6.0.3)
10372 + vue: 3.5.35(typescript@6.0.3)
10373
10322 - vue@3.5.34(typescript@6.0.3):
10374 + vue@3.5.35(typescript@6.0.3):
10375 dependencies:
10324 - '@vue/compiler-dom': 3.5.34
10325 - '@vue/compiler-sfc': 3.5.34
10326 - '@vue/runtime-dom': 3.5.34
10327 - '@vue/server-renderer': 3.5.34(vue@3.5.34(typescript@6.0.3))
10328 - '@vue/shared': 3.5.34
10376 + '@vue/compiler-dom': 3.5.35
10377 + '@vue/compiler-sfc': 3.5.35
10378 + '@vue/runtime-dom': 3.5.35
10379 + '@vue/server-renderer': 3.5.35(vue@3.5.35(typescript@6.0.3))
10380 + '@vue/shared': 3.5.35
10381 optionalDependencies:
10382 typescript: 6.0.3
10383
10332 - vuedraggable@4.1.0(vue@3.5.34(typescript@6.0.3)):
10384 + vuedraggable@4.1.0(vue@3.5.35(typescript@6.0.3)):
10385 dependencies:
10386 sortablejs: 1.14.0
10335 - vue: 3.5.34(typescript@6.0.3)
10387 + vue: 3.5.35(typescript@6.0.3)
10388
10337 - vueuc@0.4.65(vue@3.5.34(typescript@6.0.3)):
10389 + vueuc@0.4.65(vue@3.5.35(typescript@6.0.3)):
10390 dependencies:
10339 - '@css-render/vue3-ssr': 0.15.14(vue@3.5.34(typescript@6.0.3))
10391 + '@css-render/vue3-ssr': 0.15.14(vue@3.5.35(typescript@6.0.3))
10392 '@juggle/resize-observer': 3.4.0
10393 css-render: 0.15.14
10394 evtd: 0.2.4
10395 seemly: 0.3.10
10344 - vdirs: 0.1.8(vue@3.5.34(typescript@6.0.3))
10345 - vooks: 0.2.12(vue@3.5.34(typescript@6.0.3))
10346 - vue: 3.5.34(typescript@6.0.3)
10396 + vdirs: 0.1.8(vue@3.5.35(typescript@6.0.3))
10397 + vooks: 0.2.12(vue@3.5.35(typescript@6.0.3))
10398 + vue: 3.5.35(typescript@6.0.3)
10399
10400 w3c-keyname@2.2.8: {}
10401
frontend/src/assets/scss/index.scss
+1
@@ -6,3 +6,4 @@
6 @include meta.load-css("overrides/naive-override");
7 @include meta.load-css("overrides/singul-override");
8 @include meta.load-css("overrides/mui-override");
9 +@include meta.load-css("overrides/shuffle-override");
frontend/src/assets/scss/overrides/shuffle-override.scss new
+31
@@ -0,0 +1,31 @@
1 +/* shuffle-mcp-theme-bridge.css */
2 +.shuffle-mcp-scope.dark,
3 +.shuffle-mcp-scope:not(.dark) {
4 + --background: inherit !important;
5 + --background-elevated: inherit !important;
6 + --background-surface: inherit !important;
7 + --foreground: inherit !important;
8 + --card: inherit !important;
9 + --card-foreground: inherit !important;
10 + --popover: inherit !important;
11 + --popover-foreground: inherit !important;
12 + --primary: inherit !important;
13 + --primary-foreground: inherit !important;
14 + --secondary: inherit !important;
15 + --secondary-foreground: inherit !important;
16 + --muted: inherit !important;
17 + --muted-foreground: inherit !important;
18 + --accent: inherit !important;
19 + --accent-foreground: inherit !important;
20 + --destructive: inherit !important;
21 + --destructive-foreground: inherit !important;
22 + --border: inherit !important;
23 + --input: inherit !important;
24 + --ring: inherit !important;
25 + --severity-info: inherit !important;
26 + --severity-low: inherit !important;
27 + --severity-medium: inherit !important;
28 + --severity-high: inherit !important;
29 + --severity-critical: inherit !important;
30 + --infra-email: inherit !important;
31 +}
frontend/src/components/activeResponse/ActiveResponseWizard.vue
+3 -3
@@ -14,7 +14,7 @@
14
15 <div class="mt-4 flex grow flex-col">
16 <Transition :name="`slide-form-${slideFormDirection}`">
17 - <div v-if="current === 1" class="flex flex-col gap-2 px-7">
17 + <div v-if="current === 1" class="flex flex-col gap-2 px-6">
18 <CardEntity embedded hoverable clickable @click.stop="setOs('linux')">
19 <div class="flex items-center gap-3">
20 <Icon :size="18" :name="iconFromOs('linux')" />
@@ -35,7 +35,7 @@
35 </CardEntity>
36 </div>
37
38 - <div v-else-if="current === 2" class="px-7">
38 + <div v-else-if="current === 2" class="px-6">
39 <n-spin :show="loadingActiveResponse">
40 <div class="flex flex-col gap-2">
41 <template v-if="activeResponseFiltered.length">
@@ -59,7 +59,7 @@
59 </div>
60 </n-spin>
61 </div>
62 - <div v-else-if="current === 3" class="flex min-h-[401px] grow flex-col px-7 pb-7">
62 + <div v-else-if="current === 3" class="flex min-h-[401px] grow flex-col px-6 pb-7">
63 <ActiveResponseInvokeForm
64 v-if="selectedActiveResponse"
65 :active-response="selectedActiveResponse"
frontend/src/components/agents/BulkDeleteModal.vue
+1 -2
@@ -112,7 +112,7 @@
112 <n-button @click="closeModal">Cancel</n-button>
113 <n-button type="error" :loading :disabled="!canDelete" @click="confirmDelete">
114 <template #icon>
115 - <n-icon><Icon :name="DeleteIcon" /></n-icon>
115 + <Icon :name="DeleteIcon" />
116 </template>
117 Delete Agents
118 </n-button>
@@ -159,7 +159,6 @@ import {
159 NDivider,
160 NForm,
161 NFormItem,
162 - NIcon,
162 NInputNumber,
163 NModal,
164 NScrollbar,
frontend/src/components/githubAudit/GitHubAuditCard.vue
+42 -84
@@ -1,81 +1,63 @@
1 <template>
2 - <n-card class="github-audit-card" hoverable @click="$emit('click', config)">
3 - <div class="flex items-start justify-between">
4 - <div class="flex-1">
5 - <div class="mb-2 flex items-center gap-2">
6 - <n-icon size="20" :color="config.enabled ? '#18a058' : '#999'">
7 - <Icon :name="GithubIcon" />
8 - </n-icon>
9 - <h3 class="m-0 text-lg font-semibold">{{ config.organization }}</h3>
10 - <n-tag v-if="!config.enabled" type="warning" size="small">Disabled</n-tag>
11 - </div>
12 -
13 - <div class="text-secondary mb-3 text-sm">
14 - <span>Customer: {{ config.customer_code }}</span>
15 - </div>
2 + <CardEntity>
3 + <template #headerMain>
4 + <div class="flex items-center gap-3">
5 + <Icon name="codicon:organization" :size="20" />
6
17 - <div class="flex gap-4 text-sm">
18 - <div v-if="config.last_audit_at" class="flex items-center gap-1">
19 - <n-icon><Icon :name="ClockIcon" /></n-icon>
20 - <span>Last audit: {{ formatDate(config.last_audit_at, dFormats.datetime) }}</span>
21 - </div>
22 - <div v-if="config.last_audit_grade" class="flex items-center gap-1">
23 - <span>Grade:</span>
24 - <GitHubAuditGradeBadge
25 - :grade="config.last_audit_grade"
26 - :score="config.last_audit_score ?? undefined"
27 - />
28 - </div>
29 - <div v-if="config.auto_audit_enabled" class="flex items-center gap-1">
30 - <n-icon color="#18a058"><Icon :name="ScheduleIcon" /></n-icon>
31 - <span>Scheduled</span>
32 - </div>
7 + <div class="text-default font-display text-lg font-semibold">
8 + {{ config.organization }}
9 </div>
10 </div>
11 + </template>
12 + <template #headerExtra>
13 + <div class="flex items-center justify-end gap-2">
14 + <n-tag v-if="config.enabled" type="success" size="small">Enabled</n-tag>
15 + <n-tag v-else type="error" size="small">Disabled</n-tag>
16
36 - <div class="flex flex-col gap-2">
37 - <n-button type="primary" size="small" :loading="running" @click.stop="runAudit">
17 + <n-tag v-if="config.auto_audit_enabled" type="success" size="small">Scheduled</n-tag>
18 + <n-tag v-else type="error" size="small">Not scheduled</n-tag>
19 + </div>
20 + </template>
21 + <template #default>
22 + <GitHubAuditConfigSummary :config meta-fields="card" />
23 + </template>
24 + <template #footerMain>
25 + <GitHubAuditScopeFlags :config />
26 + </template>
27 + <template #footerExtra>
28 + <div class="flex flex-wrap items-center justify-end gap-2">
29 + <n-button size="small" quaternary :loading="running" @click.stop="runAudit">
30 <template #icon>
39 - <n-icon><Icon :name="PlayIcon" /></n-icon>
31 + <Icon :name="PlayIcon" />
32 </template>
33 Run Audit
34 </n-button>
35 <n-button size="small" @click.stop="$emit('edit', config)">
36 <template #icon>
45 - <n-icon><Icon :name="EditIcon" /></n-icon>
37 + <Icon :name="EditIcon" />
38 </template>
39 Edit
40 </n-button>
41 + <n-button size="small" type="primary" secondary @click.stop="$emit('click', config)">
42 + <template #icon>
43 + <Icon :name="DetailIcon" />
44 + </template>
45 + View Details
46 + </n-button>
47 </div>
50 - </div>
51 -
52 - <n-divider v-if="config.last_audit_score !== null" style="margin: 12px 0" />
53 -
54 - <div v-if="config.last_audit_score !== null" class="audit-score-bar">
55 - <div class="mb-1 flex justify-between">
56 - <span class="text-sm">Security Score</span>
57 - <span class="text-sm font-semibold">{{ config.last_audit_score?.toFixed(1) }}%</span>
58 - </div>
59 - <n-progress
60 - type="line"
61 - :percentage="config.last_audit_score ?? 0"
62 - :status="scoreStatus"
63 - :show-indicator="false"
64 - />
65 - </div>
66 - </n-card>
48 + </template>
49 + </CardEntity>
50 </template>
51
52 <script setup lang="ts">
70 -// TODO-FE: refactor
53 import type { GitHubAuditConfig } from "@/types/githubAudit.d"
72 -import { NButton, NCard, NDivider, NIcon, NProgress, NTag, useMessage } from "naive-ui"
73 -import { computed, ref } from "vue"
54 +import { NButton, NTag, useMessage } from "naive-ui"
55 +import { ref } from "vue"
56 import Api from "@/api"
57 import Icon from "@/components/common/Icon.vue"
76 -import { useSettingsStore } from "@/stores/settings"
77 -import { formatDate } from "@/utils/format"
78 -import GitHubAuditGradeBadge from "./GitHubAuditGradeBadge.vue"
58 +import CardEntity from "../common/cards/CardEntity.vue"
59 +import GitHubAuditConfigSummary from "./GitHubAuditConfigSummary.vue"
60 +import GitHubAuditScopeFlags from "./GitHubAuditScopeFlags.vue"
61
62 const props = defineProps<{
63 config: GitHubAuditConfig
@@ -85,22 +67,13 @@ const emit = defineEmits<{
67 (e: "edit", config: GitHubAuditConfig): void
68 (e: "audit-complete"): void
69 }>()
88 -const GithubIcon = "carbon:logo-github"
89 -const ClockIcon = "carbon:time"
90 -const ScheduleIcon = "carbon:calendar"
91 -const PlayIcon = "carbon:play-filled"
70 +
71 +const PlayIcon = "carbon:play"
72 const EditIcon = "carbon:edit"
73 +const DetailIcon = "carbon:view"
74
75 const message = useMessage()
76 const running = ref(false)
96 -const dFormats = useSettingsStore().dateFormat
97 -
98 -const scoreStatus = computed(() => {
99 - const score = props.config.last_audit_score ?? 0
100 - if (score >= 80) return "success"
101 - if (score >= 60) return "warning"
102 - return "error"
103 -})
77
78 async function runAudit() {
79 running.value = true
@@ -115,18 +88,3 @@ async function runAudit() {
88 }
89 }
90 </script>
118 -
119 -<style scoped>
120 -.github-audit-card {
121 - cursor: pointer;
122 - transition: all 0.2s ease;
123 -}
124 -
125 -.github-audit-card:hover {
126 - transform: translateY(-2px);
127 -}
128 -
129 -.text-secondary {
130 - color: var(--text-color-3);
131 -}
132 -</style>
frontend/src/components/githubAudit/GitHubAuditConfigForm.vue
+155 -155
@@ -1,153 +1,144 @@
1 <template>
2 - <n-drawer v-model:show="showDrawer" :width="600" placement="right">
3 - <n-drawer-content :title="isEdit ? 'Edit Configuration' : 'New GitHub Audit Configuration'" closable>
4 - <n-form ref="formRef" :model="formData" :rules label-placement="top">
5 - <n-divider title-placement="left">Basic Settings</n-divider>
6 -
7 - <n-form-item label="Customer" path="customer_code">
8 - <n-select
9 - v-model:value="formData.customer_code"
10 - placeholder="Select customer"
11 - :options="customerOptions"
12 - :disabled="isEdit"
13 - filterable
14 - />
15 - </n-form-item>
16 -
17 - <n-form-item label="GitHub Organization" path="organization">
18 - <n-input v-model:value="formData.organization" placeholder="e.g., my-org" />
19 - </n-form-item>
20 -
21 - <n-form-item label="GitHub Token" path="github_token">
22 - <n-input
23 - v-model:value="formData.github_token"
24 - type="password"
25 - show-password-on="click"
26 - :placeholder="isEdit ? 'Leave blank to keep existing token' : 'ghp_xxxxxxxxxxxx'"
27 - />
28 - </n-form-item>
29 -
30 - <n-form-item label="Token Type" path="token_type">
31 - <n-radio-group v-model:value="formData.token_type">
32 - <n-radio value="pat">Personal Access Token</n-radio>
33 - <n-radio value="app">GitHub App</n-radio>
34 - </n-radio-group>
35 - </n-form-item>
36 -
37 - <n-form-item label="Enabled">
38 - <n-switch v-model:value="formData.enabled" />
39 - </n-form-item>
40 -
41 - <n-divider title-placement="left">Audit Scope</n-divider>
42 -
43 - <n-grid :cols="2" :x-gap="16">
44 - <n-gi>
45 - <n-form-item label="Include Repositories">
46 - <n-switch v-model:value="formData.include_repos" />
47 - </n-form-item>
48 - </n-gi>
49 - <n-gi>
50 - <n-form-item label="Include Workflows">
51 - <n-switch v-model:value="formData.include_workflows" />
52 - </n-form-item>
53 - </n-gi>
54 - <n-gi>
55 - <n-form-item label="Include Members">
56 - <n-switch v-model:value="formData.include_members" />
57 - </n-form-item>
58 - </n-gi>
59 - <n-gi>
60 - <n-form-item label="Include Archived Repos">
61 - <n-switch v-model:value="formData.include_archived_repos" />
62 - </n-form-item>
63 - </n-gi>
64 - </n-grid>
65 -
66 - <n-form-item label="Repository Filter Mode">
67 - <n-radio-group v-model:value="formData.repo_filter_mode">
68 - <n-radio value="all">All Repositories</n-radio>
69 - <n-radio value="include">Include Only</n-radio>
70 - <n-radio value="exclude">Exclude</n-radio>
71 - </n-radio-group>
72 - </n-form-item>
73 -
74 - <n-form-item v-if="formData.repo_filter_mode !== 'all'" label="Repository List">
75 - <n-dynamic-tags v-model:value="formData.repo_filter_list" />
76 - <template #feedback>Enter repository names to {{ formData.repo_filter_mode }}</template>
77 - </n-form-item>
78 -
79 - <n-divider title-placement="left">Schedule</n-divider>
80 -
81 - <n-form-item label="Enable Scheduled Audits">
82 - <n-switch v-model:value="formData.auto_audit_enabled" />
83 - </n-form-item>
84 -
85 - <n-form-item v-if="formData.auto_audit_enabled" label="Schedule (Cron)" path="audit_schedule_cron">
86 - <n-input v-model:value="formData.audit_schedule_cron" placeholder="0 0 * * 1 (Weekly on Monday)" />
87 - <template #feedback>
88 - <n-text depth="3">
89 - Use cron format. Example: "0 0 * * 1" for weekly on Monday at midnight
90 - </n-text>
91 - </template>
92 - </n-form-item>
93 -
94 - <n-divider title-placement="left">Notifications</n-divider>
95 -
96 - <n-grid :cols="2" :x-gap="16">
97 - <n-gi>
98 - <n-form-item label="Notify on Critical">
99 - <n-switch v-model:value="formData.notify_on_critical" />
100 - </n-form-item>
101 - </n-gi>
102 - <n-gi>
103 - <n-form-item label="Notify on High">
104 - <n-switch v-model:value="formData.notify_on_high" />
105 - </n-form-item>
106 - </n-gi>
107 - </n-grid>
108 -
109 - <n-form-item label="Notification Webhook URL">
110 - <n-input v-model:value="formData.notification_webhook_url" placeholder="https://..." />
111 - </n-form-item>
112 -
113 - <n-form-item label="Notification Email">
114 - <n-input v-model:value="formData.notification_email" placeholder="security@example.com" />
115 - </n-form-item>
116 -
117 - <n-divider title-placement="left">Thresholds</n-divider>
118 -
119 - <n-form-item label="Minimum Passing Score">
120 - <n-slider v-model:value="formData.minimum_passing_score" :min="0" :max="100" :step="5" />
121 - <n-input-number
122 - v-model:value="formData.minimum_passing_score"
123 - :min="0"
124 - :max="100"
125 - style="width: 100px; margin-left: 16px"
126 - />
127 - </n-form-item>
128 - </n-form>
129 -
130 - <template #footer>
131 - <div class="flex justify-end gap-3">
132 - <n-button @click="showDrawer = false">Cancel</n-button>
133 - <n-button type="primary" :loading="saving" @click="handleSubmit">
134 - {{ isEdit ? "Update" : "Create" }}
135 - </n-button>
136 - </div>
137 - </template>
138 - </n-drawer-content>
139 - </n-drawer>
2 + <div class="flex flex-col gap-4">
3 + <n-form ref="formRef" :model="formData" :rules label-placement="top" :disabled="saving">
4 + <n-divider title-placement="left" class="mt-2!">Basic Settings</n-divider>
5 +
6 + <n-form-item label="Customer" path="customer_code">
7 + <n-select
8 + v-model:value="formData.customer_code"
9 + placeholder="Select customer"
10 + :options="customerOptions"
11 + :disabled="isEdit"
12 + filterable
13 + />
14 + </n-form-item>
15 +
16 + <n-form-item label="GitHub Organization" path="organization">
17 + <n-input v-model:value="formData.organization" placeholder="e.g., my-org" />
18 + </n-form-item>
19 +
20 + <n-form-item label="GitHub Token" path="github_token">
21 + <n-input
22 + v-model:value="formData.github_token"
23 + type="password"
24 + show-password-on="click"
25 + :placeholder="isEdit ? 'Leave blank to keep existing token' : 'ghp_xxxxxxxxxxxx'"
26 + />
27 + </n-form-item>
28 +
29 + <n-form-item label="Token Type" path="token_type">
30 + <n-radio-group v-model:value="formData.token_type">
31 + <n-radio value="pat">Personal Access Token</n-radio>
32 + <n-radio value="app">GitHub App</n-radio>
33 + </n-radio-group>
34 + </n-form-item>
35 +
36 + <n-form-item label="Enabled" :show-feedback="false">
37 + <n-switch v-model:value="formData.enabled" />
38 + </n-form-item>
39 +
40 + <n-divider title-placement="left">Audit Scope</n-divider>
41 +
42 + <n-grid :cols="2" :x-gap="16">
43 + <n-gi>
44 + <n-form-item label="Include Repositories">
45 + <n-switch v-model:value="formData.include_repos" />
46 + </n-form-item>
47 + </n-gi>
48 + <n-gi>
49 + <n-form-item label="Include Workflows">
50 + <n-switch v-model:value="formData.include_workflows" />
51 + </n-form-item>
52 + </n-gi>
53 + <n-gi>
54 + <n-form-item label="Include Members">
55 + <n-switch v-model:value="formData.include_members" />
56 + </n-form-item>
57 + </n-gi>
58 + <n-gi>
59 + <n-form-item label="Include Archived Repos">
60 + <n-switch v-model:value="formData.include_archived_repos" />
61 + </n-form-item>
62 + </n-gi>
63 + </n-grid>
64 +
65 + <n-form-item label="Repository Filter Mode" :show-feedback="false">
66 + <n-radio-group v-model:value="formData.repo_filter_mode">
67 + <n-radio value="all">All Repositories</n-radio>
68 + <n-radio value="include">Include Only</n-radio>
69 + <n-radio value="exclude">Exclude</n-radio>
70 + </n-radio-group>
71 + </n-form-item>
72 +
73 + <n-form-item v-if="formData.repo_filter_mode !== 'all'" label="Repository List">
74 + <n-dynamic-tags v-model:value="formData.repo_filter_list" />
75 + <template #feedback>Enter repository names to {{ formData.repo_filter_mode }}</template>
76 + </n-form-item>
77 +
78 + <n-divider title-placement="left">Schedule</n-divider>
79 +
80 + <n-form-item label="Enable Scheduled Audits" :show-feedback="false">
81 + <n-switch v-model:value="formData.auto_audit_enabled" />
82 + </n-form-item>
83 +
84 + <n-form-item v-if="formData.auto_audit_enabled" label="Schedule (Cron)" path="audit_schedule_cron">
85 + <n-input v-model:value="formData.audit_schedule_cron" placeholder="0 0 * * 1 (Weekly on Monday)" />
86 + <template #feedback>
87 + <n-text depth="3">Use cron format. Example: "0 0 * * 1" for weekly on Monday at midnight</n-text>
88 + </template>
89 + </n-form-item>
90 +
91 + <n-divider title-placement="left">Notifications</n-divider>
92 +
93 + <n-grid :cols="2" :x-gap="16">
94 + <n-gi>
95 + <n-form-item label="Notify on Critical">
96 + <n-switch v-model:value="formData.notify_on_critical" />
97 + </n-form-item>
98 + </n-gi>
99 + <n-gi>
100 + <n-form-item label="Notify on High">
101 + <n-switch v-model:value="formData.notify_on_high" />
102 + </n-form-item>
103 + </n-gi>
104 + </n-grid>
105 +
106 + <n-form-item label="Notification Webhook URL">
107 + <n-input v-model:value="formData.notification_webhook_url" placeholder="https://..." />
108 + </n-form-item>
109 +
110 + <n-form-item label="Notification Email" :show-feedback="false">
111 + <n-input v-model:value="formData.notification_email" placeholder="security@example.com" />
112 + </n-form-item>
113 +
114 + <n-divider title-placement="left">Thresholds</n-divider>
115 +
116 + <n-form-item label="Minimum Passing Score" :show-feedback="false">
117 + <n-slider v-model:value="formData.minimum_passing_score" :min="0" :max="100" :step="5" />
118 + <n-input-number
119 + v-model:value="formData.minimum_passing_score"
120 + :min="0"
121 + :max="100"
122 + style="width: 100px; margin-left: 16px"
123 + />
124 + </n-form-item>
125 + </n-form>
126 +
127 + <div class="flex justify-end gap-3">
128 + <n-button @click="emit('cancel')">Cancel</n-button>
129 + <n-button type="primary" :loading="saving" @click="handleSubmit">
130 + {{ isEdit ? "Update" : "Create" }}
131 + </n-button>
132 + </div>
133 + </div>
134 </template>
135
136 <script setup lang="ts">
143 -// TODO-FE: refactor
137 import type { FormInst, FormRules } from "naive-ui"
138 import type { GitHubAuditConfig, GitHubAuditConfigCreate, GitHubAuditConfigUpdate } from "@/types/githubAudit.d"
139 import {
140 NButton,
141 NDivider,
149 - NDrawer,
150 - NDrawerContent,
142 NDynamicTags,
143 NForm,
144 NFormItem,
@@ -166,14 +157,22 @@ import {
157 import { computed, onBeforeMount, reactive, ref, watch } from "vue"
158 import Api from "@/api"
159
160 +interface GitHubAuditConfigFormData extends Omit<
161 + GitHubAuditConfigCreate,
162 + "customer_code" | "github_token" | "organization"
163 +> {
164 + customer_code: string | null
165 + github_token: string | null
166 + organization: string | null
167 +}
168 +
169 const props = defineProps<{
170 - show: boolean
170 config?: GitHubAuditConfig | null
171 }>()
172
173 const emit = defineEmits<{
175 - (e: "update:show", value: boolean): void
174 (e: "saved"): void
175 + (e: "cancel"): void
176 }>()
177
178 const message = useMessage()
@@ -181,18 +180,13 @@ const formRef = ref<FormInst | null>(null)
180 const saving = ref(false)
181 const customerOptions = ref<{ label: string; value: string }[]>([])
182
184 -const showDrawer = computed({
185 - get: () => props.show,
186 - set: value => emit("update:show", value)
187 -})
188 -
183 const isEdit = computed(() => !!props.config)
184
191 -function defaultFormData(): GitHubAuditConfigCreate {
185 +function defaultFormData(): GitHubAuditConfigFormData {
186 return {
193 - customer_code: "",
194 - github_token: "",
195 - organization: "",
187 + customer_code: null,
188 + github_token: null,
189 + organization: null,
190 token_type: "pat",
191 enabled: true,
192 auto_audit_enabled: false,
@@ -211,7 +205,7 @@ function defaultFormData(): GitHubAuditConfigCreate {
205 }
206 }
207
214 -const formData = reactive<GitHubAuditConfigCreate>(defaultFormData())
208 +const formData = reactive<GitHubAuditConfigFormData>(defaultFormData())
209
210 const rules: FormRules = {
211 customer_code: { required: true, message: "Customer is required", trigger: "blur" },
@@ -262,6 +256,7 @@ async function handleSubmit() {
256 }
257
258 saving.value = true
259 +
260 try {
261 if (isEdit.value && props.config) {
262 const updateData: GitHubAuditConfigUpdate = { ...formData }
@@ -271,11 +266,16 @@ async function handleSubmit() {
266 await Api.githubAudit.updateConfig(props.config.id, updateData)
267 message.success("Configuration updated successfully")
268 } else {
274 - await Api.githubAudit.createConfig(formData)
269 + const createData: GitHubAuditConfigCreate = {
270 + ...formData,
271 + customer_code: formData.customer_code ?? "",
272 + github_token: formData.github_token ?? "",
273 + organization: formData.organization ?? ""
274 + }
275 + await Api.githubAudit.createConfig(createData)
276 message.success("Configuration created successfully")
277 }
278 emit("saved")
278 - showDrawer.value = false
279 } catch (error: any) {
280 message.error(error.response?.data?.detail || "Failed to save configuration")
281 } finally {
frontend/src/components/githubAudit/GitHubAuditConfigSummary.vue new
+102
@@ -0,0 +1,102 @@
1 +<template>
2 + <div class="flex items-center gap-6">
3 + <n-progress
4 + v-if="showScore"
5 + type="circle"
6 + :percentage="config.last_audit_score ?? 0"
7 + :status="scoreStatus"
8 + gap-position="bottom"
9 + >
10 + <div class="flex flex-col items-center justify-between gap-1 text-center">
11 + <span class="text-xs">Security Score</span>
12 + </div>
13 + </n-progress>
14 +
15 + <div v-if="showGrade || hasMetaFields" class="flex min-w-0 flex-1 flex-col gap-3">
16 + <div v-if="showGrade" class="flex items-end justify-between gap-3">
17 + <div class="min-w-0">
18 + <p class="text-secondary mb-1 text-[10px] font-medium tracking-widest uppercase">Grade</p>
19 + <GitHubAuditGradeLabel :grade="config.last_audit_grade" class="text-4xl" />
20 + </div>
21 + <p
22 + v-if="showScore && config.last_audit_score != null"
23 + class="text-secondary shrink-0 font-mono text-xs tabular-nums"
24 + >
25 + {{ config.last_audit_score.toFixed(1) }}%
26 + </p>
27 + </div>
28 +
29 + <dl v-if="hasMetaFields" class="border-border flex flex-col gap-2" :class="{ 'border-t pt-3': showGrade }">
30 + <div v-if="showCustomer" class="flex items-baseline justify-between gap-3">
31 + <dt class="text-secondary shrink-0 text-[10px] tracking-wider uppercase">Customer</dt>
32 + <dd class="text-default min-w-0 truncate font-mono text-xs">
33 + {{ config.customer_code }}
34 + </dd>
35 + </div>
36 + <div v-if="showOrganization" class="flex items-baseline justify-between gap-3">
37 + <dt class="text-secondary shrink-0 text-[10px] tracking-wider uppercase">Organization</dt>
38 + <dd class="text-default min-w-0 truncate font-mono text-xs">
39 + {{ config.organization }}
40 + </dd>
41 + </div>
42 + <div v-if="showLastAudit" class="flex items-baseline justify-between gap-3">
43 + <dt class="text-secondary shrink-0 text-[10px] tracking-wider uppercase">Last audit</dt>
44 + <dd class="text-default min-w-0 truncate text-right font-mono text-xs tabular-nums">
45 + {{ config.last_audit_at ? formatDate(config.last_audit_at, dFormats.datetime) : "—" }}
46 + </dd>
47 + </div>
48 + <div v-if="showTokenType" class="flex items-baseline justify-between gap-3">
49 + <dt class="text-secondary shrink-0 text-[10px] tracking-wider uppercase">Token type</dt>
50 + <dd class="text-default min-w-0 truncate text-right font-mono text-xs">
51 + {{ tokenTypeLabel }}
52 + </dd>
53 + </div>
54 + </dl>
55 + </div>
56 + </div>
57 +</template>
58 +
59 +<script setup lang="ts">
60 +import type { GitHubAuditConfig } from "@/types/githubAudit.d"
61 +import { NProgress } from "naive-ui"
62 +import { computed } from "vue"
63 +import { useSettingsStore } from "@/stores/settings"
64 +import { formatDate } from "@/utils/format"
65 +import GitHubAuditGradeLabel from "./GitHubAuditGradeLabel.vue"
66 +
67 +export type GitHubAuditConfigSummaryMetaFields = "all" | "card" | "none"
68 +
69 +const props = withDefaults(
70 + defineProps<{
71 + config: GitHubAuditConfig
72 + showScore?: boolean
73 + showGrade?: boolean
74 + metaFields?: GitHubAuditConfigSummaryMetaFields
75 + }>(),
76 + {
77 + showScore: true,
78 + showGrade: true,
79 + metaFields: "all"
80 + }
81 +)
82 +
83 +const dFormats = useSettingsStore().dateFormat
84 +
85 +const showCustomer = computed(() => props.metaFields === "all" || props.metaFields === "card")
86 +const showOrganization = computed(() => props.metaFields === "all")
87 +const showLastAudit = computed(() => props.metaFields === "all" || props.metaFields === "card")
88 +const showTokenType = computed(() => props.metaFields === "all")
89 +
90 +const hasMetaFields = computed(
91 + () => showCustomer.value || showOrganization.value || showLastAudit.value || showTokenType.value
92 +)
93 +
94 +const scoreStatus = computed(() => {
95 + const score = props.config.last_audit_score ?? 0
96 + if (score >= 80) return "success"
97 + if (score >= 60) return "warning"
98 + return "error"
99 +})
100 +
101 +const tokenTypeLabel = computed(() => (props.config.token_type === "pat" ? "Personal Access Token" : "GitHub App"))
102 +</script>
frontend/src/components/githubAudit/GitHubAuditDetail.vue
+212 -231
@@ -1,183 +1,187 @@
1 <template>
2 - <n-drawer v-model:show="showDrawer" :width="800" placement="right">
3 - <n-drawer-content v-if="config" closable>
4 - <template #header>
5 - <div class="flex items-center gap-3">
6 - <n-icon size="24">
7 - <Icon :name="GithubIcon" />
8 - </n-icon>
9 - <span>{{ config.organization }}</span>
10 - <n-tag v-if="!config.enabled" type="warning" size="small">Disabled</n-tag>
2 + <n-tabs v-model:value="activeTab" type="segment" animated>
3 + <n-tab-pane name="overview" tab="Overview">
4 + <div class="flex flex-col gap-6 py-4">
5 + <GitHubAuditConfigSummary :config />
6 +
7 + <dl class="border-border bg-secondary flex flex-col gap-2 rounded-md border p-4">
8 + <div class="flex items-baseline justify-between gap-3">
9 + <dt class="text-secondary shrink-0 text-[10px] tracking-wider uppercase">Enabled</dt>
10 + <dd class="font-mono text-xs" :class="config.enabled ? 'text-success' : 'text-warning'">
11 + {{ config.enabled ? "Active" : "Disabled" }}
12 + </dd>
13 + </div>
14 + <div class="flex items-baseline justify-between gap-3">
15 + <dt class="text-secondary shrink-0 text-[10px] tracking-wider uppercase">Scheduled</dt>
16 + <dd
17 + class="font-mono text-xs"
18 + :class="config.auto_audit_enabled ? 'text-success' : 'text-tertiary'"
19 + >
20 + {{ config.auto_audit_enabled ? "Enabled" : "Disabled" }}
21 + </dd>
22 + </div>
23 + <div v-if="config.auto_audit_enabled" class="flex items-baseline justify-between gap-3">
24 + <dt class="text-secondary shrink-0 text-[10px] tracking-wider uppercase">Cron</dt>
25 + <dd class="text-default min-w-0 truncate text-right font-mono text-xs tabular-nums">
26 + {{ config.audit_schedule_cron || "—" }}
27 + </dd>
28 + </div>
29 + </dl>
30 +
31 + <div class="flex flex-col gap-2">
32 + <p class="text-secondary text-[10px] font-medium tracking-widest uppercase">Audit scope</p>
33 + <GitHubAuditScopeFlags :config size="large" class="bg-secondary" />
34 </div>
12 - </template>
35
14 - <n-tabs v-model:value="activeTab" type="line" animated>
15 - <n-tab-pane name="overview" tab="Overview">
16 - <div class="space-y-4">
17 - <n-descriptions :column="2" label-placement="top" bordered>
18 - <n-descriptions-item label="Customer">
19 - {{ config.customer_code }}
20 - </n-descriptions-item>
21 - <n-descriptions-item label="Organization">
22 - {{ config.organization }}
23 - </n-descriptions-item>
24 - <n-descriptions-item label="Token Type">
25 - {{ config.token_type === "pat" ? "Personal Access Token" : "GitHub App" }}
26 - </n-descriptions-item>
27 - <n-descriptions-item label="Enabled">
28 - <n-tag :type="config.enabled ? 'success' : 'warning'" size="small">
29 - {{ config.enabled ? "Yes" : "No" }}
30 - </n-tag>
31 - </n-descriptions-item>
32 - <n-descriptions-item label="Last Audit">
33 - {{
34 - config.last_audit_at ? formatDate(config.last_audit_at, dFormats.datetime) : "Never"
35 - }}
36 - </n-descriptions-item>
37 - <n-descriptions-item label="Last Score">
38 - <template v-if="config.last_audit_score !== null">
39 - {{ config.last_audit_score?.toFixed(1) }}%
40 - <GitHubAuditGradeBadge :grade="config.last_audit_grade || 'F'" />
41 - </template>
42 - <template v-else>N/A</template>
43 - </n-descriptions-item>
44 - <n-descriptions-item label="Scheduled Audits">
45 - <n-tag :type="config.auto_audit_enabled ? 'success' : 'default'" size="small">
46 - {{ config.auto_audit_enabled ? "Enabled" : "Disabled" }}
47 - </n-tag>
48 - <span v-if="config.auto_audit_enabled" class="ml-2 text-sm">
49 - {{ config.audit_schedule_cron }}
50 - </span>
51 - </n-descriptions-item>
52 - <n-descriptions-item label="Audit Scope">
53 - <n-space>
54 - <n-tag v-if="config.include_repos" size="small">Repos</n-tag>
55 - <n-tag v-if="config.include_workflows" size="small">Workflows</n-tag>
56 - <n-tag v-if="config.include_members" size="small">Members</n-tag>
57 - </n-space>
58 - </n-descriptions-item>
59 - </n-descriptions>
60 -
61 - <div class="flex gap-3">
62 - <n-button type="primary" :loading="running" @click="runAudit">
63 - <template #icon>
64 - <n-icon><Icon :name="PlayIcon" /></n-icon>
65 - </template>
66 - Run Audit Now
67 - </n-button>
68 - <n-button @click="handleEdit">
69 - <template #icon>
70 - <n-icon><Icon :name="EditIcon" /></n-icon>
71 - </template>
72 - Edit Configuration
73 - </n-button>
74 - <n-popconfirm @positive-click="deleteConfig">
75 - <template #trigger>
76 - <n-button type="error" ghost>
77 - <template #icon>
78 - <n-icon><Icon :name="DeleteIcon" /></n-icon>
79 - </template>
80 - Delete
81 - </n-button>
82 - </template>
83 - Are you sure you want to delete this configuration?
84 - </n-popconfirm>
85 - </div>
86 - </div>
87 - </n-tab-pane>
88 -
89 - <n-tab-pane name="reports" tab="Reports">
90 - <n-spin :show="loadingReports">
91 - <div v-if="reports.length === 0 && !loadingReports" class="py-8 text-center">
92 - <n-empty description="No reports yet">
93 - <template #extra>
94 - <n-button type="primary" @click="runAudit">Run your first audit</n-button>
95 - </template>
96 - </n-empty>
97 - </div>
98 -
99 - <div v-else class="space-y-3">
100 - <GitHubAuditReportCard
101 - v-for="report in reports"
102 - :key="report.id"
103 - :report
104 - @click="openReportDetail"
105 - />
106 -
107 - <n-pagination
108 - v-if="totalReports > pageSize"
109 - v-model:page="currentPage"
110 - :page-size
111 - :item-count="totalReports"
112 - @update:page="loadReports"
113 - />
114 - </div>
115 - </n-spin>
116 - </n-tab-pane>
117 -
118 - <n-tab-pane name="exclusions" tab="Exclusions">
119 - <div class="mb-4">
120 - <n-button type="primary" size="small" @click="showExclusionForm = true">
36 + <div class="border-border flex flex-wrap justify-between gap-3 border-t pt-6">
37 + <div class="flex flex-wrap gap-3">
38 + <n-button @click="handleEdit">
39 + <template #icon>
40 + <Icon :name="EditIcon" />
41 + </template>
42 + Edit Configuration
43 + </n-button>
44 + <n-button quaternary :loading="running" @click="runAudit">
45 <template #icon>
122 - <n-icon><Icon :name="AddIcon" /></n-icon>
46 + <Icon :name="PlayIcon" />
47 </template>
124 - Add Exclusion
48 + Run Audit Now
49 </n-button>
50 </div>
51 + <n-popconfirm @positive-click="deleteConfig">
52 + <template #trigger>
53 + <n-button type="error" ghost>
54 + <template #icon>
55 + <Icon :name="DeleteIcon" />
56 + </template>
57 + Delete
58 + </n-button>
59 + </template>
60 + Are you sure you want to delete this configuration?
61 + </n-popconfirm>
62 + </div>
63 + </div>
64 + </n-tab-pane>
65 +
66 + <n-tab-pane name="reports" tab="Reports">
67 + <n-spin :show="loadingReports">
68 + <div v-if="reports.length === 0 && !loadingReports" class="py-8 text-center">
69 + <n-empty description="No reports yet">
70 + <template #extra>
71 + <n-button type="primary" @click="runAudit">Run your first audit</n-button>
72 + </template>
73 + </n-empty>
74 + </div>
75
128 - <n-spin :show="loadingExclusions">
129 - <div v-if="exclusions.length === 0 && !loadingExclusions" class="py-8 text-center">
130 - <n-empty description="No exclusions configured" />
131 - </div>
132 -
133 - <n-table v-else :bordered="false" :single-line="false">
134 - <thead>
135 - <tr>
136 - <th>Check</th>
137 - <th>Resource</th>
138 - <th>Reason</th>
139 - <th>Expires</th>
140 - <th>Actions</th>
141 - </tr>
142 - </thead>
143 - <tbody>
144 - <tr v-for="exclusion in exclusions" :key="exclusion.id">
145 - <td>{{ exclusion.check_id }}</td>
146 - <td>{{ exclusion.resource_name || "All" }}</td>
147 - <td>{{ exclusion.reason }}</td>
148 - <td>
149 - {{
150 - exclusion.expires_at
151 - ? formatDate(exclusion.expires_at, dFormats.datetime)
152 - : "Never"
153 - }}
154 - </td>
155 - <td>
156 - <n-button text type="error" @click="deleteExclusion(exclusion.id)">
157 - <n-icon><Icon :name="DeleteIcon" /></n-icon>
76 + <div v-else class="flex flex-col gap-3">
77 + <GitHubAuditReportCard
78 + v-for="report in reports"
79 + :key="report.id"
80 + :report
81 + :loading="loadingReportId === report.id"
82 + @click="openReportDetail"
83 + />
84 +
85 + <n-pagination
86 + v-if="totalReports > pageSize"
87 + v-model:page="currentPage"
88 + :page-size
89 + :item-count="totalReports"
90 + @update:page="loadReports"
91 + />
92 + </div>
93 + </n-spin>
94 + </n-tab-pane>
95 +
96 + <n-tab-pane name="exclusions" tab="Exclusions">
97 + <div class="mb-4">
98 + <n-button type="primary" size="small" @click="showExclusionForm = true">
99 + <template #icon>
100 + <Icon :name="AddIcon" />
101 + </template>
102 + Add Exclusion
103 + </n-button>
104 + </div>
105 +
106 + <n-spin :show="loadingExclusions">
107 + <div v-if="exclusions.length === 0 && !loadingExclusions" class="py-8 text-center">
108 + <n-empty description="No exclusions configured" />
109 + </div>
110 +
111 + <div v-else class="scrollbar-styled overflow-x-auto">
112 + <n-table :single-line="false" class="min-w-150">
113 + <thead>
114 + <tr>
115 + <th>Check</th>
116 + <th>Resource</th>
117 + <th>Reason</th>
118 + <th>Expires</th>
119 + <th class="text-right!">Actions</th>
120 + </tr>
121 + </thead>
122 + <tbody>
123 + <tr v-for="exclusion in exclusions" :key="exclusion.id">
124 + <td>{{ exclusion.check_id }}</td>
125 + <td>{{ exclusion.resource_name || "All" }}</td>
126 + <td>{{ exclusion.reason }}</td>
127 + <td>
128 + {{
129 + exclusion.expires_at
130 + ? formatDate(exclusion.expires_at, dFormats.datetime)
131 + : "Never"
132 + }}
133 + </td>
134 + <td>
135 + <div class="flex items-center justify-end gap-2">
136 + <n-button
137 + size="small"
138 + ghost
139 + type="error"
140 + @click="deleteExclusion(exclusion.id)"
141 + >
142 + <template #icon>
143 + <Icon :name="DeleteIcon" />
144 + </template>
145 + Delete
146 </n-button>
159 - </td>
160 - </tr>
161 - </tbody>
162 - </n-table>
163 - </n-spin>
164 - </n-tab-pane>
165 - </n-tabs>
166 -
167 - <GitHubAuditExclusionForm
168 - v-if="showExclusionForm"
169 - v-model:show="showExclusionForm"
170 - :config-id="config.id"
171 - @saved="loadExclusions"
172 - />
147 + </div>
148 + </td>
149 + </tr>
150 + </tbody>
151 + </n-table>
152 + </div>
153 + </n-spin>
154 + </n-tab-pane>
155 + </n-tabs>
156 +
157 + <n-modal v-model:show="showExclusionForm" preset="dialog" title="Add Exclusion" style="width: 500px">
158 + <GitHubAuditExclusionForm
159 + v-if="showExclusionForm"
160 + :config-id="config.id"
161 + @saved="onExclusionSaved"
162 + @cancel="showExclusionForm = false"
163 + />
164 + </n-modal>
165 +
166 + <n-drawer v-model:show="showReportDetail" :width="900" placement="right" class="max-w-[98vw]">
167 + <n-drawer-content v-if="selectedReport" closable :native-scrollbar="false">
168 + <template #header>
169 + <div class="flex items-center gap-3">
170 + <span>{{ selectedReport.report_name }}</span>
171 + <n-tag :type="reportStatusType" size="small">{{ selectedReport.status }}</n-tag>
172 + </div>
173 + </template>
174
174 - <GitHubAuditReportDetail v-if="showReportDetail" v-model:show="showReportDetail" :report="selectedReport" />
175 + <GitHubAuditReportDetail
176 + :report="selectedReport"
177 + @close="showReportDetail = false"
178 + @deleted="onReportDeleted"
179 + />
180 </n-drawer-content>
181 </n-drawer>
182 </template>
183
184 <script setup lang="ts">
180 -// TODO-FE: refactor
185 import type {
186 GitHubAuditCheckExclusion,
187 GitHubAuditConfig,
@@ -186,15 +190,12 @@ import type {
190 } from "@/types/githubAudit.d"
191 import {
192 NButton,
189 - NDescriptions,
190 - NDescriptionsItem,
193 NDrawer,
194 NDrawerContent,
195 NEmpty,
194 - NIcon,
196 + NModal,
197 NPagination,
198 NPopconfirm,
197 - NSpace,
199 NSpin,
200 NTable,
201 NTabPane,
@@ -207,30 +208,30 @@ import Api from "@/api"
208 import Icon from "@/components/common/Icon.vue"
209 import { useSettingsStore } from "@/stores/settings"
210 import { formatDate } from "@/utils/format"
211 +import GitHubAuditConfigSummary from "./GitHubAuditConfigSummary.vue"
212 import GitHubAuditExclusionForm from "./GitHubAuditExclusionForm.vue"
211 -import GitHubAuditGradeBadge from "./GitHubAuditGradeBadge.vue"
213 import GitHubAuditReportCard from "./GitHubAuditReportCard.vue"
214 import GitHubAuditReportDetail from "./GitHubAuditReportDetail.vue"
215 +import GitHubAuditScopeFlags from "./GitHubAuditScopeFlags.vue"
216
217 const props = defineProps<{
216 - show: boolean
217 - config: GitHubAuditConfig | null
218 + config: GitHubAuditConfig
219 }>()
220
221 const emit = defineEmits<{
221 - (e: "update:show", value: boolean): void
222 (e: "updated"): void
223 (e: "edit", config: GitHubAuditConfig): void
224 + (e: "close"): void
225 }>()
226
226 -const GithubIcon = "mdi:github"
227 -const PlayIcon = "ion:play"
228 -const EditIcon = "ion:create-outline"
227 +const PlayIcon = "carbon:play"
228 +const EditIcon = "carbon:edit"
229 const DeleteIcon = "ion:trash-outline"
230 const AddIcon = "ion:add"
231
232 const message = useMessage()
233 const dFormats = useSettingsStore().dateFormat
234 +
235 const activeTab = ref("overview")
236 const running = ref(false)
237
@@ -242,54 +243,38 @@ const currentPage = ref(1)
243 const pageSize = 10
244 const showReportDetail = ref(false)
245 const selectedReport = ref<GitHubAuditReport | null>(null)
246 +const loadingReportId = ref<number | null>(null)
247
248 // Exclusions
249 const loadingExclusions = ref(false)
250 const exclusions = ref<GitHubAuditCheckExclusion[]>([])
251 const showExclusionForm = ref(false)
252
251 -const showDrawer = computed({
252 - get: () => props.show,
253 - set: value => emit("update:show", value)
253 +const reportStatusType = computed(() => {
254 + switch (selectedReport.value?.status) {
255 + case "completed":
256 + return "success"
257 + case "running":
258 + return "info"
259 + case "failed":
260 + return "error"
261 + default:
262 + return "default"
263 + }
264 })
265
256 -// Watch for drawer opening
257 -watch(
258 - () => props.show,
259 - show => {
260 - if (show && props.config) {
261 - activeTab.value = "overview"
262 - // Reset state
263 - reports.value = []
264 - exclusions.value = []
265 - currentPage.value = 1
266 - // Load data
267 - loadReports()
268 - loadExclusions()
269 - }
270 - },
271 - { immediate: true }
272 -)
273 -
274 -// Watch for config changes while drawer is open
275 -watch(
276 - () => props.config?.id,
277 - (newId, oldId) => {
278 - if (newId && newId !== oldId && props.show) {
279 - reports.value = []
280 - exclusions.value = []
281 - currentPage.value = 1
282 - loadReports()
283 - loadExclusions()
284 - }
285 - }
286 -)
266 +function resetAndLoad() {
267 + activeTab.value = "overview"
268 + reports.value = []
269 + exclusions.value = []
270 + currentPage.value = 1
271 + loadReports()
272 + loadExclusions()
273 +}
274 +
275 +watch(() => props.config.id, resetAndLoad, { immediate: true })
276
277 async function loadReports() {
289 - if (!props.config) {
290 - console.warn("loadReports called but config is null")
291 - return
292 - }
278 if (loadingReports.value) {
279 return
280 }
@@ -312,7 +297,7 @@ async function loadReports() {
297 }
298
299 async function loadExclusions() {
315 - if (!props.config || loadingExclusions.value) return
300 + if (loadingExclusions.value) return
301
302 loadingExclusions.value = true
303 try {
@@ -327,8 +312,6 @@ async function loadExclusions() {
312 }
313
314 async function runAudit() {
330 - if (!props.config) return
331 -
315 running.value = true
316 try {
317 await Api.githubAudit.runAuditFromConfig(props.config.id)
@@ -344,12 +327,10 @@ async function runAudit() {
327 }
328
329 async function deleteConfig() {
347 - if (!props.config) return
348 -
330 try {
331 await Api.githubAudit.deleteConfig(props.config.id)
332 message.success("Configuration deleted")
352 - showDrawer.value = false
333 + emit("close")
334 emit("updated")
335 } catch (error: any) {
336 message.error(error.response?.data?.detail || "Failed to delete configuration")
@@ -367,13 +348,11 @@ async function deleteExclusion(exclusionId: number) {
348 }
349
350 function handleEdit() {
370 - if (props.config) {
371 - showDrawer.value = false
372 - emit("edit", props.config)
373 - }
351 + emit("edit", props.config)
352 }
353
354 async function openReportDetail(report: GitHubAuditReportSummary) {
355 + loadingReportId.value = report.id
356 try {
357 const response = await Api.githubAudit.getReport(report.id)
358 if (response.data.report) {
@@ -382,16 +361,18 @@ async function openReportDetail(report: GitHubAuditReportSummary) {
361 }
362 } catch {
363 message.error("Failed to load report details")
364 + } finally {
365 + loadingReportId.value = null
366 }
367 }
387 -</script>
368
389 -<style scoped>
390 -.space-y-4 > * + * {
391 - margin-top: 1rem;
369 +function onReportDeleted() {
370 + showReportDetail.value = false
371 + loadReports()
372 }
373
394 -.space-y-3 > * + * {
395 - margin-top: 0.75rem;
374 +function onExclusionSaved() {
375 + showExclusionForm.value = false
376 + loadExclusions()
377 }
397 -</style>
378 +</script>
frontend/src/components/githubAudit/GitHubAuditExclusionForm.vue
+23 -23
@@ -1,5 +1,5 @@
1 <template>
2 - <n-modal v-model:show="showModal" preset="dialog" title="Add Exclusion" style="width: 500px">
2 + <div class="flex flex-col gap-4">
3 <n-form ref="formRef" :model="formData" :rules label-placement="top">
4 <n-form-item label="Check to Exclude" path="check_id">
5 <n-select
@@ -35,49 +35,48 @@
35 </n-form-item>
36 </n-form>
37
38 - <template #action>
39 - <n-button @click="showModal = false">Cancel</n-button>
38 + <div class="flex justify-end gap-3">
39 + <n-button @click="emit('cancel')">Cancel</n-button>
40 <n-button type="primary" :loading="saving" @click="handleSubmit">Create</n-button>
41 - </template>
42 - </n-modal>
41 + </div>
42 + </div>
43 </template>
44
45 <script setup lang="ts">
46 -// TODO-FE: refactor
46 import type { FormInst, FormRules } from "naive-ui"
47 import type { GitHubAuditExclusionCreate } from "@/types/githubAudit.d"
49 -import { NButton, NDatePicker, NForm, NFormItem, NInput, NModal, NSelect, useMessage } from "naive-ui"
50 -import { computed, onBeforeMount, reactive, ref } from "vue"
48 +import { NButton, NDatePicker, NForm, NFormItem, NInput, NSelect, useMessage } from "naive-ui"
49 +import { onBeforeMount, reactive, ref } from "vue"
50 import Api from "@/api"
51 +import { useAuthStore } from "@/stores/auth"
52 +
53 +type ExclusionFormData = Omit<GitHubAuditExclusionCreate, "created_by" | "check_id" | "reason"> & {
54 + check_id: string | null
55 + reason: string | null
56 +}
57
58 const props = defineProps<{
54 - show: boolean
59 configId: number
60 }>()
61
62 const emit = defineEmits<{
59 - (e: "update:show", value: boolean): void
63 (e: "saved"): void
64 + (e: "cancel"): void
65 }>()
66
67 const message = useMessage()
68 +const authStore = useAuthStore()
69 const formRef = ref<FormInst | null>(null)
70 const saving = ref(false)
71 const checkOptions = ref<{ label: string; value: string }[]>([])
72 const expiresAtTimestamp = ref<number | null>(null)
73
69 -const showModal = computed({
70 - get: () => props.show,
71 - set: value => emit("update:show", value)
72 -})
73 -
74 -const formData = reactive<GitHubAuditExclusionCreate>({
75 - check_id: "",
74 +const formData = reactive<ExclusionFormData>({
75 + check_id: null,
76 resource_name: null,
77 - reason: "",
77 + reason: null,
78 approved_by: null,
79 - expires_at: null,
80 - created_by: "current_user" // TODO-FE: Get from auth context
79 + expires_at: null
80 })
81
82 const rules: FormRules = {
@@ -94,16 +93,17 @@ async function handleSubmit() {
93
94 saving.value = true
95 try {
97 - const data = {
96 + const data: GitHubAuditExclusionCreate = {
97 ...formData,
98 + check_id: formData.check_id ?? "",
99 + reason: formData.reason ?? "",
100 + created_by: authStore.userName,
101 expires_at: expiresAtTimestamp.value ? new Date(expiresAtTimestamp.value).toISOString() : null
102 }
103 await Api.githubAudit.createExclusion(props.configId, data)
104 message.success("Exclusion created successfully")
105 emit("saved")
104 - showModal.value = false
106
106 - // Reset form
107 formData.check_id = ""
108 formData.resource_name = null
109 formData.reason = ""
frontend/src/components/githubAudit/GitHubAuditFilters.vue deleted
-177
@@ -1,177 +0,0 @@
1 -<template>
2 - <div class="github-audit-list">
3 - <n-card>
4 - <div class="mb-4 flex items-center justify-between">
5 - <h2 class="m-0 text-xl font-semibold">GitHub Audit Configurations</h2>
6 - <n-button type="primary" @click="openCreateForm">
7 - <template #icon>
8 - <Icon :name="AddIcon" :size="16" />
9 - </template>
10 - New Configuration
11 - </n-button>
12 - </div>
13 -
14 - <div class="github-audit-filters mb-4 flex flex-wrap items-center gap-4">
15 - <n-select
16 - v-model:value="filterCustomerCode"
17 - placeholder="Filter by Customer"
18 - clearable
19 - :options="customerOptions"
20 - :loading="loadingCustomers"
21 - style="min-width: 200px"
22 - @update:value="loadConfigs"
23 - />
24 - <n-select
25 - v-model:value="filterStatus"
26 - placeholder="Filter by Status"
27 - clearable
28 - :options="statusOptions"
29 - style="min-width: 150px"
30 - @update:value="loadConfigs"
31 - />
32 - <n-input
33 - v-model:value="filterOrganization"
34 - placeholder="Search organization..."
35 - clearable
36 - style="min-width: 200px"
37 - @keyup.enter="loadConfigs"
38 - @clear="loadConfigs"
39 - >
40 - <template #prefix>
41 - <Icon :name="SearchIcon" :size="16" />
42 - </template>
43 - </n-input>
44 - </div>
45 -
46 - <n-spin :show="loading">
47 - <div v-if="configs.length === 0 && !loading" class="py-8 text-center">
48 - <n-empty description="No configurations found">
49 - <template #extra>
50 - <n-button type="primary" @click="openCreateForm">Create your first configuration</n-button>
51 - </template>
52 - </n-empty>
53 - </div>
54 -
55 - <n-grid v-else :cols="2" :x-gap="16" :y-gap="16">
56 - <n-gi v-for="config in configs" :key="config.id">
57 - <GitHubAuditCard
58 - :config
59 - @click="openDetail(config)"
60 - @edit="openEditForm"
61 - @audit-complete="loadConfigs"
62 - />
63 - </n-gi>
64 - </n-grid>
65 - </n-spin>
66 - </n-card>
67 -
68 - <GitHubAuditConfigForm
69 - v-if="showForm"
70 - v-model:show="showForm"
71 - :config="selectedConfig"
72 - @saved="onConfigSaved"
73 - />
74 -
75 - <GitHubAuditDetail
76 - v-if="showDetail"
77 - v-model:show="showDetail"
78 - :config="selectedConfig"
79 - @updated="loadConfigs"
80 - @edit="openEditForm"
81 - />
82 - </div>
83 -</template>
84 -
85 -<script setup lang="ts">
86 -// TODO-FE: refactor
87 -import type { GitHubAuditConfig } from "@/types/githubAudit.d"
88 -import { NButton, NCard, NEmpty, NGi, NGrid, NInput, NSelect, NSpin, useMessage } from "naive-ui"
89 -import { onBeforeMount, ref } from "vue"
90 -import Api from "@/api"
91 -import Icon from "@/components/common/Icon.vue"
92 -import GitHubAuditCard from "./GitHubAuditCard.vue"
93 -import GitHubAuditConfigForm from "./GitHubAuditConfigForm.vue"
94 -import GitHubAuditDetail from "./GitHubAuditDetail.vue"
95 -
96 -const AddIcon = "ion:add"
97 -const SearchIcon = "ion:search-outline"
98 -
99 -const message = useMessage()
100 -const loading = ref(false)
101 -const configs = ref<GitHubAuditConfig[]>([])
102 -const showForm = ref(false)
103 -const showDetail = ref(false)
104 -const selectedConfig = ref<GitHubAuditConfig | null>(null)
105 -
106 -// Filters - inline instead of separate component
107 -const filterCustomerCode = ref<string | null>(null)
108 -const filterStatus = ref<string | null>(null)
109 -const filterOrganization = ref<string | null>(null)
110 -const customerOptions = ref<{ label: string; value: string }[]>([])
111 -const loadingCustomers = ref(false)
112 -
113 -const statusOptions = [
114 - { label: "Enabled", value: "enabled" },
115 - { label: "Disabled", value: "disabled" }
116 -]
117 -
118 -async function loadCustomers() {
119 - if (loadingCustomers.value) return
120 -
121 - loadingCustomers.value = true
122 - try {
123 - const response = await Api.customers.getCustomers()
124 - if (response.data.customers) {
125 - customerOptions.value = response.data.customers.map((c: any) => ({
126 - label: `${c.customer_name} (${c.customer_code})`,
127 - value: c.customer_code
128 - }))
129 - }
130 - } catch (error) {
131 - console.error("Failed to load customers:", error)
132 - } finally {
133 - loadingCustomers.value = false
134 - }
135 -}
136 -
137 -async function loadConfigs() {
138 - if (loading.value) return
139 -
140 - loading.value = true
141 - try {
142 - const response = await Api.githubAudit.getConfigs(filterCustomerCode.value || undefined)
143 - configs.value = response.data.configs || []
144 - } catch (error: any) {
145 - message.error(error.response?.data?.detail || "Failed to load configurations")
146 - configs.value = []
147 - } finally {
148 - loading.value = false
149 - }
150 -}
151 -
152 -function openCreateForm() {
153 - selectedConfig.value = null
154 - showForm.value = true
155 -}
156 -
157 -function openEditForm(config: GitHubAuditConfig) {
158 - selectedConfig.value = config
159 - showDetail.value = false
160 - showForm.value = true
161 -}
162 -
163 -function openDetail(config: GitHubAuditConfig) {
164 - selectedConfig.value = config
165 - showDetail.value = true
166 -}
167 -
168 -function onConfigSaved() {
169 - showForm.value = false
170 - loadConfigs()
171 -}
172 -
173 -onBeforeMount(() => {
174 - loadCustomers()
175 - loadConfigs()
176 -})
177 -</script>
frontend/src/components/githubAudit/GitHubAuditGradeBadge.vue
+3 -2
@@ -1,16 +1,17 @@
1 <template>
2 - <n-tag :type="gradeType" :bordered="false" round size="small">
2 + <n-tag :type="gradeType" :bordered="false" round :size>
3 {{ grade }}
4 </n-tag>
5 </template>
6
7 <script setup lang="ts">
8 -// TODO-FE: refactor
8 +import type { TagSize } from "naive-ui"
9 import { NTag } from "naive-ui"
10 import { computed } from "vue"
11
12 const props = defineProps<{
13 grade: string
14 + size?: TagSize
15 score?: number
16 }>()
17
frontend/src/components/githubAudit/GitHubAuditGradeLabel.vue new
+26
@@ -0,0 +1,26 @@
1 +<template>
2 + <div>
3 + <div v-if="grade" class="font-mono leading-none font-bold tracking-tight" :class="gradeTextClass">
4 + {{ grade }}
5 + </div>
6 + <div v-else class="text-tertiary font-mono leading-none font-semibold">—</div>
7 + </div>
8 +</template>
9 +
10 +<script setup lang="ts">
11 +import { computed } from "vue"
12 +
13 +const props = defineProps<{
14 + grade?: string | null
15 +}>()
16 +
17 +const gradeTextClass = computed(() => {
18 + const value = props.grade
19 + if (!value) return ""
20 + if (value === "A" || value === "A+") return "text-success"
21 + if (value.startsWith("B")) return "text-info"
22 + if (value.startsWith("C")) return "text-warning"
23 + if (value === "D" || value === "D+" || value === "F") return "text-error"
24 + return "text-tertiary"
25 +})
26 +</script>
frontend/src/components/githubAudit/GitHubAuditInfo.vue
+204 -244
@@ -1,255 +1,230 @@
1 <template>
2 - <n-drawer v-model:show="showDrawer" :width="700" placement="right">
3 - <n-drawer-content closable>
4 - <template #header>
5 - <div class="flex items-center gap-3">
6 - <n-icon size="24">
7 - <Icon :name="InfoIcon" />
8 - </n-icon>
9 - <span>GitHub Audit Reference Guide</span>
2 + <div class="@container flex flex-col gap-6">
3 + <!-- Status Legend -->
4 + <n-card size="small" title="Interpreting Results" embedded>
5 + <div class="grid gap-3 @md:grid-cols-2">
6 + <div class="flex items-center gap-2">
7 + <n-tag type="success" size="small">PASS</n-tag>
8 + <span class="text-sm">Control meets baseline</span>
9 </div>
11 - </template>
12 -
13 - <div class="space-y-6">
14 - <!-- Status Legend -->
15 - <n-card size="small" title="Interpreting Results">
16 - <div class="grid grid-cols-2 gap-3">
17 - <div class="flex items-center gap-2">
18 - <n-tag type="success" size="small">PASS</n-tag>
19 - <span class="text-sm">Control meets baseline</span>
20 - </div>
21 - <div class="flex items-center gap-2">
22 - <n-tag type="error" size="small">FAIL</n-tag>
23 - <span class="text-sm">Remediation recommended</span>
24 - </div>
25 - <div class="flex items-center gap-2">
26 - <n-tag type="warning" size="small">WARN</n-tag>
27 - <span class="text-sm">Attention required</span>
28 - </div>
29 - <div class="flex items-center gap-2">
30 - <n-tag type="default" size="small">SKIP</n-tag>
31 - <span class="text-sm">Cannot evaluate</span>
32 - </div>
10 + <div class="flex items-center gap-2">
11 + <n-tag type="error" size="small">FAIL</n-tag>
12 + <span class="text-sm">Remediation recommended</span>
13 + </div>
14 + <div class="flex items-center gap-2">
15 + <n-tag type="warning" size="small">WARN</n-tag>
16 + <span class="text-sm">Attention required</span>
17 + </div>
18 + <div class="flex items-center gap-2">
19 + <n-tag type="default" size="small">SKIP</n-tag>
20 + <span class="text-sm">Cannot evaluate</span>
21 + </div>
22 + </div>
23 + <n-divider />
24 + <div class="flex flex-col gap-2 text-sm">
25 + <strong>Skip Reasons:</strong>
26 + <div class="flex flex-col gap-1.5">
27 + <div>
28 + <code>not_authorized</code>
29 + <span class="text-secondary">— Token/user missing permission</span>
30 + </div>
31 + <div>
32 + <code>not_supported</code>
33 + <span class="text-secondary">— Plan/feature not available</span>
34 </div>
34 - <n-divider />
35 - <div class="text-sm text-gray-500">
36 - <p class="mb-2"><strong>Skip Reasons:</strong></p>
37 - <ul class="list-inside list-disc space-y-1">
38 - <li>
39 - <code>not_authorized</code>
40 - — Token/user missing permission
41 - </li>
42 - <li>
43 - <code>not_supported</code>
44 - — Plan/feature not available
45 - </li>
46 - <li>
47 - <code>error</code>
48 - — Transient/API error; retry or inspect details
49 - </li>
50 - </ul>
35 + <div>
36 + <code>error</code>
37 + <span class="text-secondary">— Transient/API error; retry or inspect details</span>
38 </div>
52 - </n-card>
39 + </div>
40 + </div>
41 + </n-card>
42
54 - <!-- Controls Coverage -->
55 - <n-collapse>
56 - <n-collapse-item title="Controls Coverage" name="controls">
57 - <template #header-extra>
58 - <n-tag size="small" type="info">What We Check</n-tag>
59 - </template>
43 + <!-- Controls Coverage -->
44 + <n-collapse>
45 + <n-collapse-item title="Controls Coverage" name="controls">
46 + <template #header-extra>
47 + <n-tag size="small" type="info">What We Check</n-tag>
48 + </template>
49
61 - <n-card size="small" title="Organization-Level (Governance)" class="mb-3">
62 - <n-list>
63 - <n-list-item v-for="control in orgControls" :key="control.id">
64 - <template #prefix>
65 - <n-icon :color="control.critical ? '#e88080' : '#63e2b7'">
66 - <Icon
67 - :name="control.critical ? 'ion:alert-circle' : 'ion:checkmark-circle'"
68 - />
69 - </n-icon>
70 - </template>
71 - <div>
72 - <div class="font-medium">{{ control.name }}</div>
73 - <div class="text-sm text-gray-500">{{ control.description }}</div>
74 - </div>
75 - </n-list-item>
76 - </n-list>
77 - </n-card>
50 + <div class="flex flex-col gap-3">
51 + <n-card size="small" title="Organization-Level (Governance)" embedded>
52 + <div class="divide-border flex flex-col divide-y">
53 + <div
54 + v-for="control in orgControls"
55 + :key="control.id"
56 + class="flex items-start gap-2 py-2 first:pt-0 last:pb-0"
57 + >
58 + <Icon
59 + class="mt-0.5"
60 + :size="18"
61 + :name="control.critical ? 'ion:alert-circle' : 'ion:checkmark-circle'"
62 + :class="control.critical ? 'text-error' : 'text-success'"
63 + />
64
79 - <n-card size="small" title="Repository-Level (Posture)">
80 - <n-list>
81 - <n-list-item v-for="control in repoControls" :key="control.id">
82 - <template #prefix>
83 - <n-icon :color="control.critical ? '#e88080' : '#63e2b7'">
84 - <Icon
85 - :name="control.critical ? 'ion:alert-circle' : 'ion:checkmark-circle'"
86 - />
87 - </n-icon>
88 - </template>
89 - <div>
90 - <div class="font-medium">{{ control.name }}</div>
91 - <div class="text-sm text-gray-500">{{ control.description }}</div>
92 - </div>
93 - </n-list-item>
94 - </n-list>
95 - </n-card>
96 - </n-collapse-item>
65 + <div class="flex flex-col gap-0">
66 + <div class="text-sm font-medium">{{ control.name }}</div>
67 + <div class="text-secondary text-xs">{{ control.description }}</div>
68 + </div>
69 + </div>
70 + </div>
71 + </n-card>
72
98 - <!-- API Permissions -->
99 - <n-collapse-item title="Required API Permissions" name="permissions">
100 - <template #header-extra>
101 - <n-tag size="small" type="warning">Read-Only</n-tag>
102 - </template>
73 + <n-card size="small" title="Repository-Level (Posture)" embedded>
74 + <div class="divide-border flex flex-col divide-y">
75 + <div
76 + v-for="control in repoControls"
77 + :key="control.id"
78 + class="flex items-start gap-2 py-2 first:pt-0 last:pb-0"
79 + >
80 + <Icon
81 + class="mt-0.5"
82 + :size="18"
83 + :name="control.critical ? 'ion:alert-circle' : 'ion:checkmark-circle'"
84 + :class="control.critical ? 'text-error' : 'text-success'"
85 + />
86
104 - <n-alert type="info" class="mb-4">
105 - This audit is intentionally
106 - <strong>read-only</strong>
107 - . No write or admin scopes are required.
108 - </n-alert>
87 + <div class="flex flex-col gap-0">
88 + <div class="text-sm font-medium">{{ control.name }}</div>
89 + <div class="text-secondary text-xs">{{ control.description }}</div>
90 + </div>
91 + </div>
92 + </div>
93 + </n-card>
94 + </div>
95 + </n-collapse-item>
96
110 - <n-tabs type="segment" animated>
111 - <n-tab-pane name="fine-grained" tab="Fine-Grained PAT (Recommended)">
112 - <div class="space-y-4">
113 - <p class="text-sm">
114 - Create a fine-grained PAT restricted to only the target organization and repos
115 - you intend to audit.
116 - </p>
97 + <!-- API Permissions -->
98 + <n-collapse-item title="Required API Permissions" name="permissions">
99 + <template #header-extra>
100 + <n-tag size="small" type="warning">Read-Only</n-tag>
101 + </template>
102
118 - <n-card size="small" title="Organization Permissions (READ)">
119 - <n-list>
120 - <n-list-item v-for="perm in fineGrainedOrgPerms" :key="perm.name">
121 - <template #prefix>
122 - <n-tag :type="perm.required ? 'error' : 'default'" size="small">
123 - {{ perm.required ? "Required" : "Optional" }}
124 - </n-tag>
125 - </template>
126 - <div>
127 - <div class="font-medium">{{ perm.name }}</div>
128 - <div class="text-sm text-gray-500">{{ perm.description }}</div>
129 - </div>
130 - </n-list-item>
131 - </n-list>
132 - </n-card>
103 + <div class="flex flex-col gap-2">
104 + <p class="*: flex items-center gap-2 text-sm">
105 + <Icon name="carbon:information" />
106 + This audit is intentionally
107 + <strong>read-only</strong>
108 + . No write or admin scopes are required.
109 + </p>
110
134 - <n-card size="small" title="Repository Permissions (READ)">
135 - <n-list>
136 - <n-list-item v-for="perm in fineGrainedRepoPerms" :key="perm.name">
137 - <template #prefix>
138 - <n-tag :type="perm.required ? 'error' : 'default'" size="small">
139 - {{ perm.required ? "Required" : "Optional" }}
140 - </n-tag>
141 - </template>
142 - <div>
143 - <div class="font-medium">{{ perm.name }}</div>
144 - <div class="text-sm text-gray-500">{{ perm.description }}</div>
145 - </div>
146 - </n-list-item>
147 - </n-list>
148 - </n-card>
149 - </div>
150 - </n-tab-pane>
111 + <n-tabs type="segment" animated>
112 + <n-tab-pane name="fine-grained" tab="Fine-Grained PAT (Recommended)">
113 + <div class="flex flex-col gap-3">
114 + <p class="text-warning flex items-center gap-2 text-sm">
115 + <Icon name="carbon:warning" class="text-warning" />
116 + Create a fine-grained PAT restricted to only the target organization and repos you
117 + intend to audit.
118 + </p>
119 +
120 + <n-card size="small" title="Organization Permissions (READ)" embedded>
121 + <n-list class="bg-transparent!">
122 + <n-list-item v-for="perm in fineGrainedOrgPerms" :key="perm.name">
123 + <template #prefix>
124 + <n-tag :type="perm.required ? 'error' : 'default'" size="small">
125 + {{ perm.required ? "Required" : "Optional" }}
126 + </n-tag>
127 + </template>
128 + <div>
129 + <div class="text-sm font-medium">{{ perm.name }}</div>
130 + <div class="text-secondary text-xs">{{ perm.description }}</div>
131 + </div>
132 + </n-list-item>
133 + </n-list>
134 + </n-card>
135 +
136 + <n-card size="small" title="Repository Permissions (READ)" embedded>
137 + <n-list class="bg-transparent!">
138 + <n-list-item v-for="perm in fineGrainedRepoPerms" :key="perm.name">
139 + <template #prefix>
140 + <n-tag :type="perm.required ? 'error' : 'default'" size="small">
141 + {{ perm.required ? "Required" : "Optional" }}
142 + </n-tag>
143 + </template>
144 + <div>
145 + <div class="text-sm font-medium">{{ perm.name }}</div>
146 + <div class="text-secondary text-xs">{{ perm.description }}</div>
147 + </div>
148 + </n-list-item>
149 + </n-list>
150 + </n-card>
151 + </div>
152 + </n-tab-pane>
153
152 - <n-tab-pane name="classic" tab="Classic PAT (Fallback)">
153 - <div class="space-y-4">
154 - <n-alert type="warning" class="mb-4">
155 - Classic PATs have broader scope. Use fine-grained PATs when possible.
156 - </n-alert>
154 + <n-tab-pane name="classic" tab="Classic PAT (Fallback)">
155 + <div class="flex flex-col gap-3">
156 + <p class="text-warning flex items-center gap-2 text-sm">
157 + <Icon name="carbon:warning" class="text-warning" />
158 + Classic PATs have broader scope. Use fine-grained PATs when possible.
159 + </p>
160
158 - <n-card size="small" title="Required Scopes">
159 - <n-list>
160 - <n-list-item v-for="scope in classicScopes" :key="scope.name">
161 - <template #prefix>
162 - <n-tag :type="scope.required ? 'error' : 'default'" size="small">
163 - {{ scope.required ? "Required" : "Optional" }}
164 - </n-tag>
165 - </template>
166 - <div>
167 - <div class="font-mono font-medium">{{ scope.name }}</div>
168 - <div class="text-sm text-gray-500">{{ scope.description }}</div>
161 + <n-card size="small" title="Required Scopes" embedded>
162 + <n-list class="bg-transparent!">
163 + <n-list-item v-for="scope in classicScopes" :key="scope.name">
164 + <template #prefix>
165 + <n-tag :type="scope.required ? 'error' : 'default'" size="small">
166 + {{ scope.required ? "Required" : "Optional" }}
167 + </n-tag>
168 + </template>
169 + <div>
170 + <div class="text-sm font-medium">{{ scope.name }}</div>
171 + <div class="text-secondary text-xs">
172 + {{ scope.description }}
173 </div>
170 - </n-list-item>
171 - </n-list>
172 - </n-card>
173 - </div>
174 - </n-tab-pane>
175 - </n-tabs>
176 - </n-collapse-item>
174 + </div>
175 + </n-list-item>
176 + </n-list>
177 + </n-card>
178 + </div>
179 + </n-tab-pane>
180 + </n-tabs>
181 + </div>
182 + </n-collapse-item>
183
178 - <!-- API Endpoints -->
179 - <n-collapse-item title="API Endpoints Used" name="endpoints">
180 - <template #header-extra>
181 - <n-tag size="small">GET Only</n-tag>
182 - </template>
184 + <!-- API Endpoints -->
185 + <n-collapse-item title="API Endpoints Used" name="endpoints">
186 + <template #header-extra>
187 + <n-tag size="small">GET Only</n-tag>
188 + </template>
189
184 - <n-card size="small" title="Organization Endpoints" class="mb-3">
185 - <n-list>
186 - <n-list-item v-for="endpoint in orgEndpoints" :key="endpoint.path">
187 - <div class="font-mono text-sm">
188 - <span class="text-green-500">GET</span>
189 - {{ endpoint.path }}
190 - </div>
191 - <div v-if="endpoint.note" class="mt-1 text-xs text-gray-500">
192 - {{ endpoint.note }}
193 - </div>
194 - </n-list-item>
195 - </n-list>
196 - </n-card>
190 + <div class="flex flex-col gap-3">
191 + <n-card size="small" title="Organization Endpoints" embedded>
192 + <n-list class="bg-transparent!">
193 + <n-list-item v-for="endpoint in orgEndpoints" :key="endpoint.path">
194 + <div class="font-mono text-sm">
195 + <span class="text-green-500">GET</span>
196 + {{ endpoint.path }}
197 + </div>
198 + <div v-if="endpoint.note" class="text-secondary mt-1 text-xs">
199 + {{ endpoint.note }}
200 + </div>
201 + </n-list-item>
202 + </n-list>
203 + </n-card>
204
198 - <n-card size="small" title="Repository Endpoints">
199 - <n-list>
200 - <n-list-item v-for="endpoint in repoEndpoints" :key="endpoint.path">
201 - <div class="font-mono text-sm">
202 - <span class="text-green-500">GET</span>
203 - {{ endpoint.path }}
204 - </div>
205 - <div v-if="endpoint.note" class="mt-1 text-xs text-gray-500">
206 - {{ endpoint.note }}
207 - </div>
208 - </n-list-item>
209 - </n-list>
210 - </n-card>
211 - </n-collapse-item>
212 - </n-collapse>
213 - </div>
214 - </n-drawer-content>
215 - </n-drawer>
205 + <n-card size="small" title="Repository Endpoints" embedded>
206 + <n-list class="bg-transparent!">
207 + <n-list-item v-for="endpoint in repoEndpoints" :key="endpoint.path">
208 + <div class="font-mono text-sm">
209 + <span class="text-green-500">GET</span>
210 + {{ endpoint.path }}
211 + </div>
212 + <div v-if="endpoint.note" class="text-secondary mt-1 text-xs">
213 + {{ endpoint.note }}
214 + </div>
215 + </n-list-item>
216 + </n-list>
217 + </n-card>
218 + </div>
219 + </n-collapse-item>
220 + </n-collapse>
221 + </div>
222 </template>
223
224 <script setup lang="ts">
219 -// TODO-FE: refactor
220 -import {
221 - NAlert,
222 - NCard,
223 - NCollapse,
224 - NCollapseItem,
225 - NDivider,
226 - NDrawer,
227 - NDrawerContent,
228 - NIcon,
229 - NList,
230 - NListItem,
231 - NTabPane,
232 - NTabs,
233 - NTag
234 -} from "naive-ui"
235 -import { computed } from "vue"
225 +import { NCard, NCollapse, NCollapseItem, NDivider, NList, NListItem, NTabPane, NTabs, NTag } from "naive-ui"
226 import Icon from "@/components/common/Icon.vue"
227
238 -const props = defineProps<{
239 - show: boolean
240 -}>()
241 -
242 -const emit = defineEmits<{
243 - (e: "update:show", value: boolean): void
244 -}>()
245 -
246 -const InfoIcon = "ion:information-circle-outline"
247 -
248 -const showDrawer = computed({
249 - get: () => props.show,
250 - set: value => emit("update:show", value)
251 -})
252 -
228 // Controls data
229 const orgControls = [
230 {
@@ -408,18 +383,3 @@ const repoEndpoints = [
383 { path: "/repos/{org}/{repo}/environments", note: null }
384 ]
385 </script>
411 -
412 -<style scoped>
413 -.space-y-6 > * + * {
414 - margin-top: 1.5rem;
415 -}
416 -
417 -.space-y-4 > * + * {
418 - margin-top: 1rem;
419 -}
420 -
421 -.grid-cols-2 {
422 - display: grid;
423 - grid-template-columns: repeat(2, 1fr);
424 -}
425 -</style>
frontend/src/components/githubAudit/GitHubAuditList.vue
+82 -53
@@ -1,32 +1,32 @@
1 <template>
2 - <div class="github-audit-list">
3 - <n-card>
4 - <div class="mb-4 flex items-center justify-between">
5 - <h2 class="m-0 text-xl font-semibold">GitHub Audit Configurations</h2>
6 - <n-space>
7 - <n-button quaternary @click="showInfo = true">
2 + <div class="@container flex flex-col gap-8">
3 + <div class="flex w-full flex-col gap-4">
4 + <div class="flex flex-col justify-between gap-4 @2xl:flex-row @2xl:items-center">
5 + <h2 class="text-xl font-semibold">GitHub Audit Configurations</h2>
6 + <div class="flex flex-wrap items-center gap-x-6 gap-y-2">
7 + <n-button text @click="showInfo = true">
8 <template #icon>
9 - <n-icon><Icon :name="InfoIcon" /></n-icon>
9 + <Icon :name="InfoIcon" />
10 </template>
11 Reference Guide
12 </n-button>
13 <n-button type="primary" @click="openCreateForm">
14 <template #icon>
15 - <n-icon><Icon :name="AddIcon" /></n-icon>
15 + <Icon :name="AddIcon" />
16 </template>
17 New Configuration
18 </n-button>
19 - </n-space>
19 + </div>
20 </div>
21
22 - <div class="github-audit-filters mb-4 flex flex-wrap items-center gap-4">
22 + <div class="flex flex-wrap items-center gap-2">
23 <n-select
24 v-model:value="filterCustomerCode"
25 placeholder="Filter by Customer"
26 clearable
27 :options="customerOptions"
28 :loading="loadingCustomers"
29 - style="min-width: 200px"
29 + class="min-w-50 flex-1"
30 @update:value="loadConfigs"
31 />
32 <n-select
@@ -34,68 +34,97 @@
34 placeholder="Filter by Status"
35 clearable
36 :options="statusOptions"
37 - style="min-width: 150px"
37 + class="min-w-50 flex-1"
38 @update:value="loadConfigs"
39 />
40 <n-input
41 v-model:value="filterOrganization"
42 placeholder="Search organization..."
43 clearable
44 - style="min-width: 200px"
44 + class="min-w-80 flex-1"
45 @keyup.enter="loadConfigs"
46 @clear="loadConfigs"
47 >
48 <template #prefix>
49 - <n-icon><Icon :name="SearchIcon" /></n-icon>
49 + <Icon :name="SearchIcon" />
50 </template>
51 </n-input>
52 </div>
53 + </div>
54
54 - <n-spin :show="loading">
55 - <div v-if="configs.length === 0 && !loading" class="py-8 text-center">
56 - <n-empty description="No configurations found">
57 - <template #extra>
58 - <n-button type="primary" @click="openCreateForm">Create your first configuration</n-button>
59 - </template>
60 - </n-empty>
61 - </div>
55 + <n-spin :show="loading">
56 + <div v-if="configs.length === 0 && !loading" class="py-8 text-center">
57 + <n-empty description="No configurations found">
58 + <template #extra>
59 + <n-button type="primary" @click="openCreateForm">Create your first configuration</n-button>
60 + </template>
61 + </n-empty>
62 + </div>
63
63 - <n-grid v-else :cols="2" :x-gap="16" :y-gap="16">
64 - <n-gi v-for="config in configs" :key="config.id">
65 - <GitHubAuditCard
66 - :config
67 - @click="openDetail(config)"
68 - @edit="openEditForm"
69 - @audit-complete="loadConfigs"
70 - />
71 - </n-gi>
72 - </n-grid>
73 - </n-spin>
74 - </n-card>
75 -
76 - <GitHubAuditConfigForm
77 - v-if="showForm"
78 - v-model:show="showForm"
79 - :config="selectedConfig"
80 - @saved="onConfigSaved"
81 - />
82 -
83 - <GitHubAuditDetail
84 - v-if="showDetail"
85 - v-model:show="showDetail"
86 - :config="selectedConfig"
87 - @updated="loadConfigs"
88 - @edit="openEditForm"
89 - />
90 -
91 - <GitHubAuditInfo v-model:show="showInfo" />
64 + <div v-else class="grid grid-cols-1 gap-4 @4xl:grid-cols-2">
65 + <GitHubAuditCard
66 + v-for="config in configs"
67 + :key="config.id"
68 + :config
69 + @click="openDetail(config)"
70 + @edit="openEditForm"
71 + @audit-complete="loadConfigs"
72 + />
73 + </div>
74 + </n-spin>
75 +
76 + <n-drawer v-model:show="showForm" :width="600" placement="right" class="max-w-[98vw]">
77 + <n-drawer-content
78 + :title="selectedConfig ? 'Edit Configuration' : 'New GitHub Audit Configuration'"
79 + closable
80 + :native-scrollbar="false"
81 + >
82 + <GitHubAuditConfigForm
83 + v-if="showForm"
84 + :config="selectedConfig"
85 + @saved="onConfigSaved"
86 + @cancel="showForm = false"
87 + />
88 + </n-drawer-content>
89 + </n-drawer>
90 +
91 + <n-drawer v-model:show="showDetail" :width="800" placement="right" class="max-w-[98vw]">
92 + <n-drawer-content v-if="selectedConfig" closable :native-scrollbar="false">
93 + <template #header>
94 + <div class="flex items-center gap-3">
95 + <Icon name="codicon:organization" :size="24" />
96 + <span>{{ selectedConfig.organization }}</span>
97 + <n-tag v-if="!selectedConfig.enabled" type="warning" size="small">Disabled</n-tag>
98 + </div>
99 + </template>
100 +
101 + <GitHubAuditDetail
102 + :config="selectedConfig"
103 + @updated="loadConfigs"
104 + @edit="openEditForm"
105 + @close="showDetail = false"
106 + />
107 + </n-drawer-content>
108 + </n-drawer>
109 +
110 + <n-drawer v-model:show="showInfo" :width="700" placement="right" class="max-w-[98vw]">
111 + <n-drawer-content closable :native-scrollbar="false">
112 + <template #header>
113 + <div class="flex items-center gap-3">
114 + <Icon :name="InfoIcon" :size="24" />
115 + <span>GitHub Audit Reference Guide</span>
116 + </div>
117 + </template>
118 +
119 + <GitHubAuditInfo />
120 + </n-drawer-content>
121 + </n-drawer>
122 </div>
123 </template>
124
125 <script setup lang="ts">
96 -// TODO-FE: refactor
126 import type { GitHubAuditConfig } from "@/types/githubAudit.d"
98 -import { NButton, NCard, NEmpty, NGi, NGrid, NIcon, NInput, NSelect, NSpace, NSpin, useMessage } from "naive-ui"
127 +import { NButton, NDrawer, NDrawerContent, NEmpty, NInput, NSelect, NSpin, NTag, useMessage } from "naive-ui"
128 import { onBeforeMount, ref } from "vue"
129 import Api from "@/api"
130 import Icon from "@/components/common/Icon.vue"
frontend/src/components/githubAudit/GitHubAuditReportCard.vue
+51 -55
@@ -1,46 +1,69 @@
1 <template>
2 - <n-card class="github-audit-report-card" hoverable size="small" @click="$emit('click', report)">
3 - <div class="flex items-center justify-between">
4 - <div class="flex-1">
2 + <CardEntity clickable hoverable :loading @click="$emit('click', report)">
3 + <div class="flex flex-wrap items-start justify-between gap-4">
4 + <div class="min-w-0">
5 <div class="mb-1 flex items-center gap-2">
6 - <span class="font-medium">{{ report.report_name }}</span>
7 - <n-tag :type="statusType" size="small">{{ report.status }}</n-tag>
6 + <span class="text-default truncate font-mono text-sm font-medium">
7 + {{ report.report_name }}
8 + </span>
9 + <span class="shrink-0 font-mono text-[10px] tracking-wider uppercase" :class="statusTextClass">
10 + {{ report.status }}
11 + </span>
12 </div>
9 - <div class="text-secondary text-xs">
13 + <p class="text-secondary font-mono text-xs tabular-nums">
14 {{ formatDate(report.audit_started_at, dFormats.datetime) }}
11 - <span v-if="report.audit_duration_seconds">• {{ report.audit_duration_seconds.toFixed(1) }}s</span>
12 - </div>
15 + <template v-if="report.audit_duration_seconds != null">
16 + <span class="text-tertiary mx-1.5">·</span>
17 + {{ report.audit_duration_seconds.toFixed(1) }}s
18 + </template>
19 + </p>
20 </div>
21
15 - <div class="flex items-center gap-4">
16 - <div class="text-center">
17 - <div class="text-2xl font-bold" :class="scoreClass">{{ report.score.toFixed(0) }}%</div>
18 - <GitHubAuditGradeBadge :grade="report.grade" />
22 + <div class="flex shrink-0 items-end gap-4 text-right">
23 + <div>
24 + <p class="text-secondary mb-0.5 text-[10px] tracking-wider uppercase">Score</p>
25 + <p class="font-mono text-2xl leading-none font-bold tabular-nums" :class="scoreClass">
26 + {{ report.score.toFixed(0) }}%
27 + </p>
28 </div>
20 -
21 - <div class="findings-summary text-xs">
22 - <div v-if="report.critical_findings" class="text-error">
23 - {{ report.critical_findings }} Critical
24 - </div>
25 - <div v-if="report.high_findings" class="text-warning">{{ report.high_findings }} High</div>
26 - <div class="text-secondary">{{ report.passed_checks }}/{{ report.total_checks }} Passed</div>
29 + <div>
30 + <p class="text-secondary mb-0.5 text-[10px] tracking-wider uppercase">Grade</p>
31 + <GitHubAuditGradeLabel :grade="report.grade" class="text-2xl" />
32 </div>
33 </div>
34 </div>
30 - </n-card>
35 +
36 + <template #footer>
37 + <dl class="flex flex-wrap items-baseline gap-x-4 gap-y-1">
38 + <Badge v-if="report.critical_findings" color="danger" type="splitted" size="small">
39 + <template #label>Critical</template>
40 + <template #value>{{ report.critical_findings }}</template>
41 + </Badge>
42 + <Badge v-if="report.high_findings" color="warning" type="splitted" size="small">
43 + <template #label>High</template>
44 + <template #value>{{ report.high_findings }}</template>
45 + </Badge>
46 + <Badge type="splitted" size="small">
47 + <template #label>Passed</template>
48 + <template #value>{{ report.passed_checks }}/{{ report.total_checks }}</template>
49 + </Badge>
50 + </dl>
51 + </template>
52 + </CardEntity>
53 </template>
54
55 <script setup lang="ts">
34 -// TODO-FE: refactor
56 import type { GitHubAuditReportSummary } from "@/types/githubAudit.d"
36 -import { NCard, NTag } from "naive-ui"
57 import { computed } from "vue"
58 +import Badge from "@/components/common/Badge.vue"
59 +import CardEntity from "@/components/common/cards/CardEntity.vue"
60 import { useSettingsStore } from "@/stores/settings"
61 import { formatDate } from "@/utils/format"
40 -import GitHubAuditGradeBadge from "./GitHubAuditGradeBadge.vue"
62 +import GitHubAuditGradeLabel from "./GitHubAuditGradeLabel.vue"
63
64 const props = defineProps<{
65 report: GitHubAuditReportSummary
66 + loading?: boolean
67 }>()
68
69 defineEmits<{
@@ -49,16 +72,16 @@ defineEmits<{
72
73 const dFormats = useSettingsStore().dateFormat
74
52 -const statusType = computed(() => {
75 +const statusTextClass = computed(() => {
76 switch (props.report.status) {
77 case "completed":
55 - return "success"
78 + return "text-success"
79 case "running":
57 - return "info"
80 + return "text-info"
81 case "failed":
59 - return "error"
82 + return "text-error"
83 default:
61 - return "default"
84 + return "text-tertiary"
85 }
86 })
87
@@ -68,30 +91,3 @@ const scoreClass = computed(() => {
91 return "text-error"
92 })
93 </script>
71 -
72 -<style scoped>
73 -.github-audit-report-card {
74 - cursor: pointer;
75 - transition: all 0.2s ease;
76 -}
77 -
78 -.github-audit-report-card:hover {
79 - transform: translateY(-1px);
80 -}
81 -
82 -.text-secondary {
83 - color: var(--text-color-3);
84 -}
85 -
86 -.text-success {
87 - color: var(--success-color);
88 -}
89 -
90 -.text-warning {
91 - color: var(--warning-color);
92 -}
93 -
94 -.text-error {
95 - color: var(--error-color);
96 -}
97 -</style>
frontend/src/components/githubAudit/GitHubAuditReportDetail.vue
+162 -272
@@ -1,187 +1,161 @@
1 <template>
2 - <n-drawer v-model:show="showDrawer" :width="900" placement="right">
3 - <n-drawer-content v-if="report" closable>
4 - <template #header>
5 - <div class="flex items-center gap-3">
6 - <span>{{ report.report_name }}</span>
7 - <n-tag :type="statusType" size="small">{{ report.status }}</n-tag>
8 - </div>
9 - </template>
10 -
11 - <div class="space-y-6">
12 - <!-- Summary Section -->
13 - <n-card title="Summary" size="small">
14 - <n-grid :cols="4" :x-gap="16" :y-gap="16">
15 - <n-gi>
16 - <n-statistic label="Score">
17 - <template #default>
18 - <span :class="scoreClass">{{ report.score.toFixed(1) }}%</span>
19 - </template>
20 - <template #suffix>
21 - <GitHubAuditGradeBadge :grade="report.grade" />
22 - </template>
23 - </n-statistic>
24 - </n-gi>
25 - <n-gi>
26 - <n-statistic label="Repos Audited" :value="report.total_repos_audited" />
27 - </n-gi>
28 - <n-gi>
29 - <n-statistic label="Checks Passed">
30 - <template #default>{{ report.passed_checks }} / {{ report.total_checks }}</template>
31 - </n-statistic>
32 - </n-gi>
33 - <n-gi>
34 - <n-statistic label="Duration">
35 - <template #default>{{ report.audit_duration_seconds?.toFixed(1) || "N/A" }}s</template>
36 - </n-statistic>
37 - </n-gi>
38 - </n-grid>
39 - </n-card>
40 -
41 - <!-- Findings Summary -->
42 - <n-card title="Findings by Severity" size="small">
43 - <n-grid :cols="4" :x-gap="16">
44 - <n-gi>
45 - <div class="finding-stat critical">
46 - <div class="number">{{ report.critical_findings }}</div>
47 - <div class="label">Critical</div>
48 - </div>
49 - </n-gi>
50 - <n-gi>
51 - <div class="finding-stat high">
52 - <div class="number">{{ report.high_findings }}</div>
53 - <div class="label">High</div>
54 - </div>
55 - </n-gi>
56 - <n-gi>
57 - <div class="finding-stat medium">
58 - <div class="number">{{ report.medium_findings }}</div>
59 - <div class="label">Medium</div>
60 - </div>
61 - </n-gi>
62 - <n-gi>
63 - <div class="finding-stat low">
64 - <div class="number">{{ report.low_findings }}</div>
65 - <div class="label">Low</div>
66 - </div>
67 - </n-gi>
68 - </n-grid>
69 - </n-card>
70 -
71 - <!-- Top Findings -->
72 - <n-card v-if="report.top_findings && report.top_findings.length > 0" title="Top Findings" size="small">
73 - <n-table :bordered="false" :single-line="false" size="small">
74 - <thead>
75 - <tr>
76 - <th>Severity</th>
77 - <th>Check</th>
78 - <th>Resource</th>
79 - <th>Description</th>
80 - </tr>
81 - </thead>
82 - <tbody>
83 - <tr v-for="(finding, index) in report.top_findings" :key="index">
84 - <td>
85 - <n-tag :type="getSeverityType(finding.severity)" size="small">
86 - {{ finding.severity }}
87 - </n-tag>
88 - </td>
89 - <td>{{ finding.check_name }}</td>
90 - <td>{{ finding.resource_name || "N/A" }}</td>
91 - <td>{{ finding.description }}</td>
92 - </tr>
93 - </tbody>
94 - </n-table>
95 - </n-card>
96 -
97 - <!-- Organization Results -->
98 - <n-card v-if="report.full_report?.organization_results" title="Organization Settings" size="small">
99 - <n-table :bordered="false" :single-line="false" size="small">
100 - <thead>
101 - <tr>
102 - <th>Status</th>
103 - <th>Check</th>
104 - <th>Description</th>
105 - </tr>
106 - </thead>
107 - <tbody>
108 - <tr v-for="check in report.full_report.organization_results.checks" :key="check.check_id">
109 - <td>
110 - <n-tag :type="getStatusType(check.status)" size="small">
111 - {{ check.status }}
112 - </n-tag>
113 - </td>
114 - <td>{{ check.check_name }}</td>
115 - <td>{{ check.description }}</td>
116 - </tr>
117 - </tbody>
118 - </n-table>
119 - </n-card>
120 -
121 - <!-- Repository Results -->
122 - <n-card v-if="report.full_report?.repository_results?.length" title="Repository Results" size="small">
123 - <n-collapse>
124 - <n-collapse-item
125 - v-for="repo in report.full_report.repository_results"
126 - :key="repo.repo_name"
127 - :title="repo.repo_name"
128 - >
129 - <template #header-extra>
130 - <n-space>
131 - <n-tag type="success" size="small">{{ repo.passed_count }} passed</n-tag>
132 - <n-tag v-if="repo.failed_count > 0" type="error" size="small">
133 - {{ repo.failed_count }} failed
134 - </n-tag>
135 - </n-space>
136 - </template>
137 -
138 - <n-table :bordered="false" :single-line="false" size="small">
139 - <thead>
140 - <tr>
141 - <th>Status</th>
142 - <th>Check</th>
143 - <th>Description</th>
144 - </tr>
145 - </thead>
146 - <tbody>
147 - <tr v-for="check in repo.checks" :key="check.check_id">
148 - <td>
149 - <n-tag :type="getStatusType(check.status)" size="small">
150 - {{ check.status }}
151 - </n-tag>
152 - </td>
153 - <td>{{ check.check_name }}</td>
154 - <td>{{ check.description }}</td>
155 - </tr>
156 - </tbody>
157 - </n-table>
158 - </n-collapse-item>
159 - </n-collapse>
160 - </n-card>
161 -
162 - <!-- Error Message -->
163 - <n-alert v-if="report.error_message" title="Error" type="error">
164 - {{ report.error_message }}
165 - </n-alert>
2 + <div class="@container flex flex-col gap-6">
3 + <!-- Summary Section -->
4 + <n-card title="Summary" size="small">
5 + <div class="grid gap-4 @sm:grid-cols-2 @xl:grid-cols-4">
6 + <n-statistic label="Score">
7 + <template #default>
8 + <span :class="scoreClass">{{ report.score.toFixed(1) }}%</span>
9 + </template>
10 + <template #suffix>
11 + <GitHubAuditGradeBadge :grade="report.grade" class="relative -top-0.5" />
12 + </template>
13 + </n-statistic>
14 +
15 + <n-statistic label="Repos Audited" :value="report.total_repos_audited" />
16 +
17 + <n-statistic label="Checks Passed">
18 + <template #default>{{ report.passed_checks }} / {{ report.total_checks }}</template>
19 + </n-statistic>
20 +
21 + <n-statistic label="Duration">
22 + <template #default>{{ report.audit_duration_seconds?.toFixed(1) || "N/A" }}s</template>
23 + </n-statistic>
24 </div>
167 -
168 - <template #footer>
169 - <div class="flex justify-between">
170 - <n-popconfirm @positive-click="deleteReport">
171 - <template #trigger>
172 - <n-button type="error" ghost>Delete Report</n-button>
173 - </template>
174 - Are you sure you want to delete this report?
175 - </n-popconfirm>
176 - <n-button @click="showDrawer = false">Close</n-button>
25 + </n-card>
26 +
27 + <!-- Findings Summary -->
28 + <n-card title="Findings by Severity" size="small">
29 + <div class="border-border divide-border grid grid-cols-4 divide-x rounded-md border">
30 + <div
31 + v-for="stat in severityStats"
32 + :key="stat.key"
33 + class="flex flex-col items-center gap-1 px-3 py-4"
34 + :class="stat.bgClass"
35 + >
36 + <p class="text-secondary text-[10px] tracking-wider uppercase">{{ stat.label }}</p>
37 + <p class="font-mono text-2xl leading-none font-bold tabular-nums" :class="stat.numberClass">
38 + {{ report[stat.key] }}
39 + </p>
40 </div>
178 - </template>
179 - </n-drawer-content>
180 - </n-drawer>
41 + </div>
42 + </n-card>
43 +
44 + <!-- Top Findings -->
45 + <n-card v-if="report.top_findings && report.top_findings.length > 0" title="Top Findings" size="small">
46 + <div class="scrollbar-styled overflow-x-auto">
47 + <n-table :single-line="false" size="small" class="min-w-150">
48 + <thead>
49 + <tr>
50 + <th>Severity</th>
51 + <th>Check</th>
52 + <th>Resource</th>
53 + <th>Description</th>
54 + </tr>
55 + </thead>
56 + <tbody>
57 + <tr v-for="(finding, index) in report.top_findings" :key="index">
58 + <td>
59 + <n-tag :type="getSeverityType(finding.severity)" size="small">
60 + {{ finding.severity }}
61 + </n-tag>
62 + </td>
63 + <td>{{ finding.check_name }}</td>
64 + <td>{{ finding.resource_name || "N/A" }}</td>
65 + <td>{{ finding.description }}</td>
66 + </tr>
67 + </tbody>
68 + </n-table>
69 + </div>
70 + </n-card>
71 +
72 + <!-- Organization Results -->
73 + <n-card v-if="report.full_report?.organization_results" title="Organization Settings" size="small">
74 + <div class="scrollbar-styled overflow-x-auto">
75 + <n-table :single-line="false" size="small" class="min-w-150">
76 + <thead>
77 + <tr>
78 + <th>Status</th>
79 + <th>Check</th>
80 + <th>Description</th>
81 + </tr>
82 + </thead>
83 + <tbody>
84 + <tr v-for="check in report.full_report.organization_results.checks" :key="check.check_id">
85 + <td>
86 + <n-tag :type="getStatusType(check.status)" size="small">
87 + {{ check.status }}
88 + </n-tag>
89 + </td>
90 + <td>{{ check.check_name }}</td>
91 + <td>{{ check.description }}</td>
92 + </tr>
93 + </tbody>
94 + </n-table>
95 + </div>
96 + </n-card>
97 +
98 + <!-- Repository Results -->
99 + <n-card v-if="report.full_report?.repository_results?.length" title="Repository Results" size="small">
100 + <n-collapse>
101 + <n-collapse-item
102 + v-for="repo in report.full_report.repository_results"
103 + :key="repo.repo_name"
104 + :title="repo.repo_name"
105 + >
106 + <template #header-extra>
107 + <n-space>
108 + <n-tag type="success" size="small">{{ repo.passed_count }} passed</n-tag>
109 + <n-tag v-if="repo.failed_count > 0" type="error" size="small">
110 + {{ repo.failed_count }} failed
111 + </n-tag>
112 + </n-space>
113 + </template>
114 +
115 + <div class="scrollbar-styled overflow-x-auto">
116 + <n-table :single-line="false" size="small" class="min-w-150">
117 + <thead>
118 + <tr>
119 + <th>Status</th>
120 + <th>Check</th>
121 + <th>Description</th>
122 + </tr>
123 + </thead>
124 + <tbody>
125 + <tr v-for="check in repo.checks" :key="check.check_id">
126 + <td>
127 + <n-tag :type="getStatusType(check.status)" size="small">
128 + {{ check.status }}
129 + </n-tag>
130 + </td>
131 + <td>{{ check.check_name }}</td>
132 + <td>{{ check.description }}</td>
133 + </tr>
134 + </tbody>
135 + </n-table>
136 + </div>
137 + </n-collapse-item>
138 + </n-collapse>
139 + </n-card>
140 +
141 + <!-- Error Message -->
142 + <n-alert v-if="report.error_message" title="Error" type="error">
143 + {{ report.error_message }}
144 + </n-alert>
145 +
146 + <div class="border-border flex justify-between gap-3 border-t pt-4">
147 + <n-popconfirm @positive-click="deleteReport">
148 + <template #trigger>
149 + <n-button type="error" ghost>Delete Report</n-button>
150 + </template>
151 + Are you sure you want to delete this report?
152 + </n-popconfirm>
153 + <n-button @click="emit('close')">Close</n-button>
154 + </div>
155 + </div>
156 </template>
157
158 <script setup lang="ts">
184 -// TODO-FE: refactor
159 import type { GitHubAuditReport } from "@/types/githubAudit.d"
160 import {
161 NAlert,
@@ -189,10 +163,6 @@ import {
163 NCard,
164 NCollapse,
165 NCollapseItem,
192 - NDrawer,
193 - NDrawerContent,
194 - NGi,
195 - NGrid,
166 NPopconfirm,
167 NSpace,
168 NStatistic,
@@ -206,37 +176,25 @@ import { AuditStatus, SeverityLevel } from "@/types/githubAudit.d"
176 import GitHubAuditGradeBadge from "./GitHubAuditGradeBadge.vue"
177
178 const props = defineProps<{
209 - show: boolean
210 - report: GitHubAuditReport | null
179 + report: GitHubAuditReport
180 }>()
181
182 const emit = defineEmits<{
214 - (e: "update:show", value: boolean): void
183 + (e: "close"): void
184 (e: "deleted"): void
185 }>()
186
187 const message = useMessage()
188
220 -const showDrawer = computed({
221 - get: () => props.show,
222 - set: value => emit("update:show", value)
223 -})
224 -
225 -const statusType = computed(() => {
226 - switch (props.report?.status) {
227 - case "completed":
228 - return "success"
229 - case "running":
230 - return "info"
231 - case "failed":
232 - return "error"
233 - default:
234 - return "default"
235 - }
236 -})
189 +const severityStats = [
190 + { key: "critical_findings" as const, label: "Critical", numberClass: "text-error", bgClass: "bg-error/5" },
191 + { key: "high_findings" as const, label: "High", numberClass: "text-warning", bgClass: "bg-warning/5" },
192 + { key: "medium_findings" as const, label: "Medium", numberClass: "text-info", bgClass: "bg-info/5" },
193 + { key: "low_findings" as const, label: "Low", numberClass: "text-success", bgClass: "bg-success/5" }
194 +]
195
196 const scoreClass = computed(() => {
239 - const score = props.report?.score ?? 0
197 + const score = props.report.score ?? 0
198 if (score >= 80) return "text-success"
199 if (score >= 60) return "text-warning"
200 return "text-error"
@@ -275,81 +233,13 @@ function getSeverityType(severity: SeverityLevel | string) {
233 }
234
235 async function deleteReport() {
278 - if (!props.report) return
279 -
236 try {
237 await Api.githubAudit.deleteReport(props.report.id)
238 message.success("Report deleted")
283 - showDrawer.value = false
239 emit("deleted")
240 + emit("close")
241 } catch {
242 message.error("Failed to delete report")
243 }
244 }
245 </script>
290 -
291 -<style scoped>
292 -.space-y-6 > * + * {
293 - margin-top: 1.5rem;
294 -}
295 -
296 -.finding-stat {
297 - text-align: center;
298 - padding: 16px;
299 - border-radius: 8px;
300 -}
301 -
302 -.finding-stat .number {
303 - font-size: 2rem;
304 - font-weight: bold;
305 -}
306 -
307 -.finding-stat .label {
308 - font-size: 0.875rem;
309 - color: var(--text-color-3);
310 -}
311 -
312 -.finding-stat.critical {
313 - background: rgba(208, 48, 80, 0.1);
314 -}
315 -
316 -.finding-stat.critical .number {
317 - color: #d03050;
318 -}
319 -
320 -.finding-stat.high {
321 - background: rgba(240, 160, 32, 0.1);
322 -}
323 -
324 -.finding-stat.high .number {
325 - color: #f0a020;
326 -}
327 -
328 -.finding-stat.medium {
329 - background: rgba(32, 128, 240, 0.1);
330 -}
331 -
332 -.finding-stat.medium .number {
333 - color: #2080f0;
334 -}
335 -
336 -.finding-stat.low {
337 - background: rgba(24, 160, 88, 0.1);
338 -}
339 -
340 -.finding-stat.low .number {
341 - color: #18a058;
342 -}
343 -
344 -.text-success {
345 - color: var(--success-color);
346 -}
347 -
348 -.text-warning {
349 - color: var(--warning-color);
350 -}
351 -
352 -.text-error {
353 - color: var(--error-color);
354 -}
355 -</style>
frontend/src/components/githubAudit/GitHubAuditScopeFlags.vue new
+46
@@ -0,0 +1,46 @@
1 +<template>
2 + <div class="border-border divide-border grid grid-cols-4 divide-x rounded-md border">
3 + <n-tooltip v-for="flag in includeFlags" :key="flag.key" class="px-2! py-1!">
4 + <template #trigger>
5 + <div
6 + class="flex flex-col items-center justify-center"
7 + :class="size === 'large' ? 'gap-1 px-2 py-2.5' : 'gap-0 px-1 py-0.5'"
8 + >
9 + <span :class="size === 'large' ? 'text-sm' : 'text-xs'">
10 + {{ flag.shortLabel }}
11 + </span>
12 + <Icon
13 + :name="config[flag.key] ? 'carbon:checkmark' : 'carbon:close'"
14 + :class="config[flag.key] ? 'text-success' : 'text-error'"
15 + />
16 + </div>
17 + </template>
18 + <span :class="size === 'large' ? 'text-sm' : 'text-xs'">{{ flag.label }}</span>
19 + </n-tooltip>
20 + </div>
21 +</template>
22 +
23 +<script setup lang="ts">
24 +import type { GitHubAuditConfig } from "@/types/githubAudit.d"
25 +import { NTooltip } from "naive-ui"
26 +import Icon from "@/components/common/Icon.vue"
27 +
28 +type IncludeFlagKey = "include_archived_repos" | "include_members" | "include_repos" | "include_workflows"
29 +
30 +type FlagSize = "small" | "large"
31 +
32 +withDefaults(
33 + defineProps<{
34 + config: GitHubAuditConfig
35 + size?: FlagSize
36 + }>(),
37 + { size: "small" }
38 +)
39 +
40 +const includeFlags: { key: IncludeFlagKey; shortLabel: string; label: string }[] = [
41 + { key: "include_archived_repos", shortLabel: "A", label: "Include archived repos" },
42 + { key: "include_members", shortLabel: "M", label: "Include members" },
43 + { key: "include_repos", shortLabel: "R", label: "Include repos" },
44 + { key: "include_workflows", shortLabel: "W", label: "Include workflows" }
45 +]
46 +</script>
frontend/src/components/githubAudit/GitHubAuditStats.vue deleted
-55
@@ -1,55 +0,0 @@
1 -<template>
2 - <div class="github-audit-stats">
3 - <n-grid :cols="4" :x-gap="16" :y-gap="16">
4 - <n-gi>
5 - <n-statistic label="Total Configs" :value="stats.totalConfigs" />
6 - </n-gi>
7 - <n-gi>
8 - <n-statistic label="Active Configs" :value="stats.activeConfigs" />
9 - </n-gi>
10 - <n-gi>
11 - <n-statistic label="Total Reports" :value="stats.totalReports" />
12 - </n-gi>
13 - <n-gi>
14 - <n-statistic label="Avg Score">
15 - <template #default>
16 - <span :class="scoreClass">{{ stats.avgScore.toFixed(1) }}%</span>
17 - </template>
18 - </n-statistic>
19 - </n-gi>
20 - </n-grid>
21 - </div>
22 -</template>
23 -
24 -<script setup lang="ts">
25 -// TODO-FE: refactor
26 -import { NGi, NGrid, NStatistic } from "naive-ui"
27 -import { computed } from "vue"
28 -
29 -const props = defineProps<{
30 - stats: {
31 - totalConfigs: number
32 - activeConfigs: number
33 - totalReports: number
34 - avgScore: number
35 - }
36 -}>()
37 -
38 -const scoreClass = computed(() => {
39 - if (props.stats.avgScore >= 80) return "text-success"
40 - if (props.stats.avgScore >= 60) return "text-warning"
41 - return "text-error"
42 -})
43 -</script>
44 -
45 -<style scoped>
46 -.text-success {
47 - color: var(--success-color);
48 -}
49 -.text-warning {
50 - color: var(--warning-color);
51 -}
52 -.text-error {
53 - color: var(--error-color);
54 -}
55 -</style>
frontend/src/components/githubAudit/index.ts
+3 -2
@@ -1,11 +1,12 @@
1 export { default as GitHubAuditButton } from "./GitHubAuditButton.vue"
2 export { default as GitHubAuditCard } from "./GitHubAuditCard.vue"
3 export { default as GitHubAuditConfigForm } from "./GitHubAuditConfigForm.vue"
4 +export { default as GitHubAuditConfigSummary } from "./GitHubAuditConfigSummary.vue"
5 export { default as GitHubAuditDetail } from "./GitHubAuditDetail.vue"
6 export { default as GitHubAuditExclusionForm } from "./GitHubAuditExclusionForm.vue"
6 -export { default as GitHubAuditFilters } from "./GitHubAuditFilters.vue"
7 export { default as GitHubAuditGradeBadge } from "./GitHubAuditGradeBadge.vue"
8 +export { default as GitHubAuditGradeLabel } from "./GitHubAuditGradeLabel.vue"
9 export { default as GitHubAuditList } from "./GitHubAuditList.vue"
10 export { default as GitHubAuditReportCard } from "./GitHubAuditReportCard.vue"
11 export { default as GitHubAuditReportDetail } from "./GitHubAuditReportDetail.vue"
11 -export { default as GitHubAuditStats } from "./GitHubAuditStats.vue"
12 +export { default as GitHubAuditScopeFlags } from "./GitHubAuditScopeFlags.vue"
frontend/src/views/externalServices/ShuffleAppAuth.vue
+7 -1
@@ -23,12 +23,18 @@
23 class="h-full"
24 />
25 </n-spin>
26 +
27 + <n-empty
28 + v-else
29 + description="Select an organization to manage app authentication"
30 + class="grow justify-center"
31 + />
32 </div>
33 </template>
34
35 <script setup lang="ts">
36 import type { Organization } from "@/types/shuffle.d"
31 -import { NSelect, NSpin, useMessage } from "naive-ui"
37 +import { NEmpty, NSelect, NSpin, useMessage } from "naive-ui"
38 import { computed, onBeforeMount, ref, watch } from "vue"
39 import Api from "@/api"
40 import ShuffleMCPEmbed from "@/components/shuffle/ShuffleMCPEmbed.vue"