generated from kevisual/vite-react-template
feat: enhance repository management UI and functionality
- Added navigation to repository details in RepoCard component. - Implemented a new BuildConfig component for managing build configurations. - Integrated build configuration initialization and saving logic in the store. - Updated RepoInfoCard to include workspace management features. - Improved repository editing dialog with better state handling. - Enhanced repository list fetching with search capabilities. - Added support for creating and managing development configurations. - Refactored code for better readability and maintainability.
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@kevisual/cnb-center",
|
"name": "@kevisual/cnb-center",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.5",
|
"version": "0.0.6",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"basename": "/root/cnb-center",
|
"basename": "/root/cnb-center",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"ui": "pnpm dlx shadcn@latest add ",
|
"ui": "pnpm dlx shadcn@latest add ",
|
||||||
"pub": "envision deploy ./dist -k cnb-center -v 0.0.5 -y y -u"
|
"pub": "envision deploy ./dist -k cnb-center -v 0.0.6 -y y -u"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist"
|
"dist"
|
||||||
@@ -28,6 +28,7 @@
|
|||||||
"@kevisual/kv-login": "^0.1.15",
|
"@kevisual/kv-login": "^0.1.15",
|
||||||
"@kevisual/router": "0.0.80",
|
"@kevisual/router": "0.0.80",
|
||||||
"@tanstack/react-router": "^1.161.1",
|
"@tanstack/react-router": "^1.161.1",
|
||||||
|
"@uiw/react-codemirror": "^4.25.5",
|
||||||
"ai": "^6.0.91",
|
"ai": "^6.0.91",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
@@ -49,6 +50,7 @@
|
|||||||
"access": "public"
|
"access": "public"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@codemirror/lang-yaml": "^6.1.2",
|
||||||
"@kevisual/gitea": "^0.0.6",
|
"@kevisual/gitea": "^0.0.6",
|
||||||
"@kevisual/query": "0.0.49",
|
"@kevisual/query": "0.0.49",
|
||||||
"@kevisual/types": "^0.0.12",
|
"@kevisual/types": "^0.0.12",
|
||||||
|
|||||||
206
pnpm-lock.yaml
generated
206
pnpm-lock.yaml
generated
@@ -41,6 +41,9 @@ importers:
|
|||||||
'@tanstack/react-router':
|
'@tanstack/react-router':
|
||||||
specifier: ^1.161.1
|
specifier: ^1.161.1
|
||||||
version: 1.162.9(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
version: 1.162.9(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||||
|
'@uiw/react-codemirror':
|
||||||
|
specifier: ^4.25.5
|
||||||
|
version: 4.25.5(@babel/runtime@7.28.6)(@codemirror/autocomplete@6.20.0)(@codemirror/language@6.12.2)(@codemirror/lint@6.9.4)(@codemirror/search@6.6.0)(@codemirror/state@6.5.4)(@codemirror/theme-one-dark@6.1.3)(@codemirror/view@6.39.15)(codemirror@6.0.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||||
ai:
|
ai:
|
||||||
specifier: ^6.0.91
|
specifier: ^6.0.91
|
||||||
version: 6.0.97(zod@4.3.6)
|
version: 6.0.97(zod@4.3.6)
|
||||||
@@ -90,6 +93,9 @@ importers:
|
|||||||
specifier: ^5.0.11
|
specifier: ^5.0.11
|
||||||
version: 5.0.11(@types/react@19.2.14)(react@19.2.4)(use-sync-external-store@1.6.0(react@19.2.4))
|
version: 5.0.11(@types/react@19.2.14)(react@19.2.4)(use-sync-external-store@1.6.0(react@19.2.4))
|
||||||
devDependencies:
|
devDependencies:
|
||||||
|
'@codemirror/lang-yaml':
|
||||||
|
specifier: ^6.1.2
|
||||||
|
version: 6.1.2
|
||||||
'@kevisual/gitea':
|
'@kevisual/gitea':
|
||||||
specifier: ^0.0.6
|
specifier: ^0.0.6
|
||||||
version: 0.0.6
|
version: 0.0.6
|
||||||
@@ -295,6 +301,33 @@ packages:
|
|||||||
'@types/react':
|
'@types/react':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@codemirror/autocomplete@6.20.0':
|
||||||
|
resolution: {integrity: sha512-bOwvTOIJcG5FVo5gUUupiwYh8MioPLQ4UcqbcRf7UQ98X90tCa9E1kZ3Z7tqwpZxYyOvh1YTYbmZE9RTfTp5hg==}
|
||||||
|
|
||||||
|
'@codemirror/commands@6.10.2':
|
||||||
|
resolution: {integrity: sha512-vvX1fsih9HledO1c9zdotZYUZnE4xV0m6i3m25s5DIfXofuprk6cRcLUZvSk3CASUbwjQX21tOGbkY2BH8TpnQ==}
|
||||||
|
|
||||||
|
'@codemirror/lang-yaml@6.1.2':
|
||||||
|
resolution: {integrity: sha512-dxrfG8w5Ce/QbT7YID7mWZFKhdhsaTNOYjOkSIMt1qmC4VQnXSDSYVHHHn8k6kJUfIhtLo8t1JJgltlxWdsITw==}
|
||||||
|
|
||||||
|
'@codemirror/language@6.12.2':
|
||||||
|
resolution: {integrity: sha512-jEPmz2nGGDxhRTg3lTpzmIyGKxz3Gp3SJES4b0nAuE5SWQoKdT5GoQ69cwMmFd+wvFUhYirtDTr0/DRHpQAyWg==}
|
||||||
|
|
||||||
|
'@codemirror/lint@6.9.4':
|
||||||
|
resolution: {integrity: sha512-ABc9vJ8DEmvOWuH26P3i8FpMWPQkduD9Rvba5iwb6O3hxASgclm3T3krGo8NASXkHCidz6b++LWlzWIUfEPSWw==}
|
||||||
|
|
||||||
|
'@codemirror/search@6.6.0':
|
||||||
|
resolution: {integrity: sha512-koFuNXcDvyyotWcgOnZGmY7LZqEOXZaaxD/j6n18TCLx2/9HieZJ5H6hs1g8FiRxBD0DNfs0nXn17g872RmYdw==}
|
||||||
|
|
||||||
|
'@codemirror/state@6.5.4':
|
||||||
|
resolution: {integrity: sha512-8y7xqG/hpB53l25CIoit9/ngxdfoG+fx+V3SHBrinnhOtLvKHRyAJJuHzkWrR4YXXLX8eXBsejgAAxHUOdW1yw==}
|
||||||
|
|
||||||
|
'@codemirror/theme-one-dark@6.1.3':
|
||||||
|
resolution: {integrity: sha512-NzBdIvEJmx6fjeremiGp3t/okrLPYT0d9orIc7AFun8oZcRk58aejkqhv6spnz4MLAevrKNPMQYXEWMg4s+sKA==}
|
||||||
|
|
||||||
|
'@codemirror/view@6.39.15':
|
||||||
|
resolution: {integrity: sha512-aCWjgweIIXLBHh7bY6cACvXuyrZ0xGafjQ2VInjp4RM4gMfscK5uESiNdrH0pE+e1lZr2B4ONGsjchl2KsKZzg==}
|
||||||
|
|
||||||
'@emnapi/core@1.8.1':
|
'@emnapi/core@1.8.1':
|
||||||
resolution: {integrity: sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==}
|
resolution: {integrity: sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==}
|
||||||
|
|
||||||
@@ -545,6 +578,21 @@ packages:
|
|||||||
resolution: {integrity: sha512-jLsL80wBBKkrJZrfk3SQpJ9JA/zREdlUROj7eCkmzqduAWKSI0wVcXuCKf+mLFCHB0Q0Tkh2rgzjSlurt3JQgw==}
|
resolution: {integrity: sha512-jLsL80wBBKkrJZrfk3SQpJ9JA/zREdlUROj7eCkmzqduAWKSI0wVcXuCKf+mLFCHB0Q0Tkh2rgzjSlurt3JQgw==}
|
||||||
engines: {node: '>=10.0.0'}
|
engines: {node: '>=10.0.0'}
|
||||||
|
|
||||||
|
'@lezer/common@1.5.1':
|
||||||
|
resolution: {integrity: sha512-6YRVG9vBkaY7p1IVxL4s44n5nUnaNnGM2/AckNgYOnxTG2kWh1vR8BMxPseWPjRNpb5VtXnMpeYAEAADoRV1Iw==}
|
||||||
|
|
||||||
|
'@lezer/highlight@1.2.3':
|
||||||
|
resolution: {integrity: sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==}
|
||||||
|
|
||||||
|
'@lezer/lr@1.4.8':
|
||||||
|
resolution: {integrity: sha512-bPWa0Pgx69ylNlMlPvBPryqeLYQjyJjqPx+Aupm5zydLIF3NE+6MMLT8Yi23Bd9cif9VS00aUebn+6fDIGBcDA==}
|
||||||
|
|
||||||
|
'@lezer/yaml@1.0.4':
|
||||||
|
resolution: {integrity: sha512-2lrrHqxalACEbxIbsjhqGpSW8kWpUKuY6RHgnSAFZa6qK62wvnPxA8hGOwOoDbwHcOFs5M4o27mjGu+P7TvBmw==}
|
||||||
|
|
||||||
|
'@marijn/find-cluster-break@1.0.2':
|
||||||
|
resolution: {integrity: sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==}
|
||||||
|
|
||||||
'@napi-rs/wasm-runtime@1.1.1':
|
'@napi-rs/wasm-runtime@1.1.1':
|
||||||
resolution: {integrity: sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==}
|
resolution: {integrity: sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==}
|
||||||
|
|
||||||
@@ -856,6 +904,28 @@ packages:
|
|||||||
'@types/react@19.2.14':
|
'@types/react@19.2.14':
|
||||||
resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==}
|
resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==}
|
||||||
|
|
||||||
|
'@uiw/codemirror-extensions-basic-setup@4.25.5':
|
||||||
|
resolution: {integrity: sha512-2KWS4NqrS9SQzlPs/3sxFhuArvjB3JF6WpsrZqBtGHM5/smCNTULX3lUGeRH+f3mkfMt0k6DR+q0xCW9k+Up5w==}
|
||||||
|
peerDependencies:
|
||||||
|
'@codemirror/autocomplete': '>=6.0.0'
|
||||||
|
'@codemirror/commands': '>=6.0.0'
|
||||||
|
'@codemirror/language': '>=6.0.0'
|
||||||
|
'@codemirror/lint': '>=6.0.0'
|
||||||
|
'@codemirror/search': '>=6.0.0'
|
||||||
|
'@codemirror/state': '>=6.0.0'
|
||||||
|
'@codemirror/view': '>=6.0.0'
|
||||||
|
|
||||||
|
'@uiw/react-codemirror@4.25.5':
|
||||||
|
resolution: {integrity: sha512-WUMBGwfstufdbnaiMzQzmOf+6Mzf0IbiOoleexC9ItWcDTJybidLtEi20aP2N58Wn/AQxsd5Otebydaimh7Opw==}
|
||||||
|
peerDependencies:
|
||||||
|
'@babel/runtime': '>=7.11.0'
|
||||||
|
'@codemirror/state': '>=6.0.0'
|
||||||
|
'@codemirror/theme-one-dark': '>=6.0.0'
|
||||||
|
'@codemirror/view': '>=6.0.0'
|
||||||
|
codemirror: '>=6.0.0'
|
||||||
|
react: '>=17.0.0'
|
||||||
|
react-dom: '>=17.0.0'
|
||||||
|
|
||||||
'@vercel/oidc@3.1.0':
|
'@vercel/oidc@3.1.0':
|
||||||
resolution: {integrity: sha512-Fw28YZpRnA3cAHHDlkt7xQHiJ0fcL+NRcIqsocZQUSmbzeIKRpwttJjik5ZGanXP+vlA4SbTg+AbA3bP363l+w==}
|
resolution: {integrity: sha512-Fw28YZpRnA3cAHHDlkt7xQHiJ0fcL+NRcIqsocZQUSmbzeIKRpwttJjik5ZGanXP+vlA4SbTg+AbA3bP363l+w==}
|
||||||
engines: {node: '>= 20'}
|
engines: {node: '>= 20'}
|
||||||
@@ -931,6 +1001,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
|
resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
|
codemirror@6.0.2:
|
||||||
|
resolution: {integrity: sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw==}
|
||||||
|
|
||||||
convert-source-map@2.0.0:
|
convert-source-map@2.0.0:
|
||||||
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
|
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
|
||||||
|
|
||||||
@@ -940,6 +1013,9 @@ packages:
|
|||||||
cookie-es@2.0.0:
|
cookie-es@2.0.0:
|
||||||
resolution: {integrity: sha512-RAj4E421UYRgqokKUmotqAwuplYw15qtdXfY+hGzgCJ/MBjCVZcSoHK/kH9kocfjRjcDME7IiDWR/1WX1TM2Pg==}
|
resolution: {integrity: sha512-RAj4E421UYRgqokKUmotqAwuplYw15qtdXfY+hGzgCJ/MBjCVZcSoHK/kH9kocfjRjcDME7IiDWR/1WX1TM2Pg==}
|
||||||
|
|
||||||
|
crelt@1.0.6:
|
||||||
|
resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==}
|
||||||
|
|
||||||
crossws@0.3.5:
|
crossws@0.3.5:
|
||||||
resolution: {integrity: sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA==}
|
resolution: {integrity: sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA==}
|
||||||
|
|
||||||
@@ -1333,6 +1409,9 @@ packages:
|
|||||||
spark-md5@3.0.2:
|
spark-md5@3.0.2:
|
||||||
resolution: {integrity: sha512-wcFzz9cDfbuqe0FZzfi2or1sgyIrsDwmPwfZC4hiNidPdPINjeUwNfv5kldczoEAcjl9Y1L3SM7Uz2PUEQzxQw==}
|
resolution: {integrity: sha512-wcFzz9cDfbuqe0FZzfi2or1sgyIrsDwmPwfZC4hiNidPdPINjeUwNfv5kldczoEAcjl9Y1L3SM7Uz2PUEQzxQw==}
|
||||||
|
|
||||||
|
style-mod@4.1.3:
|
||||||
|
resolution: {integrity: sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==}
|
||||||
|
|
||||||
tabbable@6.4.0:
|
tabbable@6.4.0:
|
||||||
resolution: {integrity: sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==}
|
resolution: {integrity: sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==}
|
||||||
|
|
||||||
@@ -1505,6 +1584,9 @@ packages:
|
|||||||
yaml:
|
yaml:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
w3c-keyname@2.2.8:
|
||||||
|
resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==}
|
||||||
|
|
||||||
webpack-virtual-modules@0.6.2:
|
webpack-virtual-modules@0.6.2:
|
||||||
resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==}
|
resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==}
|
||||||
|
|
||||||
@@ -1721,6 +1803,69 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/react': 19.2.14
|
'@types/react': 19.2.14
|
||||||
|
|
||||||
|
'@codemirror/autocomplete@6.20.0':
|
||||||
|
dependencies:
|
||||||
|
'@codemirror/language': 6.12.2
|
||||||
|
'@codemirror/state': 6.5.4
|
||||||
|
'@codemirror/view': 6.39.15
|
||||||
|
'@lezer/common': 1.5.1
|
||||||
|
|
||||||
|
'@codemirror/commands@6.10.2':
|
||||||
|
dependencies:
|
||||||
|
'@codemirror/language': 6.12.2
|
||||||
|
'@codemirror/state': 6.5.4
|
||||||
|
'@codemirror/view': 6.39.15
|
||||||
|
'@lezer/common': 1.5.1
|
||||||
|
|
||||||
|
'@codemirror/lang-yaml@6.1.2':
|
||||||
|
dependencies:
|
||||||
|
'@codemirror/autocomplete': 6.20.0
|
||||||
|
'@codemirror/language': 6.12.2
|
||||||
|
'@codemirror/state': 6.5.4
|
||||||
|
'@lezer/common': 1.5.1
|
||||||
|
'@lezer/highlight': 1.2.3
|
||||||
|
'@lezer/lr': 1.4.8
|
||||||
|
'@lezer/yaml': 1.0.4
|
||||||
|
|
||||||
|
'@codemirror/language@6.12.2':
|
||||||
|
dependencies:
|
||||||
|
'@codemirror/state': 6.5.4
|
||||||
|
'@codemirror/view': 6.39.15
|
||||||
|
'@lezer/common': 1.5.1
|
||||||
|
'@lezer/highlight': 1.2.3
|
||||||
|
'@lezer/lr': 1.4.8
|
||||||
|
style-mod: 4.1.3
|
||||||
|
|
||||||
|
'@codemirror/lint@6.9.4':
|
||||||
|
dependencies:
|
||||||
|
'@codemirror/state': 6.5.4
|
||||||
|
'@codemirror/view': 6.39.15
|
||||||
|
crelt: 1.0.6
|
||||||
|
|
||||||
|
'@codemirror/search@6.6.0':
|
||||||
|
dependencies:
|
||||||
|
'@codemirror/state': 6.5.4
|
||||||
|
'@codemirror/view': 6.39.15
|
||||||
|
crelt: 1.0.6
|
||||||
|
|
||||||
|
'@codemirror/state@6.5.4':
|
||||||
|
dependencies:
|
||||||
|
'@marijn/find-cluster-break': 1.0.2
|
||||||
|
|
||||||
|
'@codemirror/theme-one-dark@6.1.3':
|
||||||
|
dependencies:
|
||||||
|
'@codemirror/language': 6.12.2
|
||||||
|
'@codemirror/state': 6.5.4
|
||||||
|
'@codemirror/view': 6.39.15
|
||||||
|
'@lezer/highlight': 1.2.3
|
||||||
|
|
||||||
|
'@codemirror/view@6.39.15':
|
||||||
|
dependencies:
|
||||||
|
'@codemirror/state': 6.5.4
|
||||||
|
crelt: 1.0.6
|
||||||
|
style-mod: 4.1.3
|
||||||
|
w3c-keyname: 2.2.8
|
||||||
|
|
||||||
'@emnapi/core@1.8.1':
|
'@emnapi/core@1.8.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@emnapi/wasi-threads': 1.1.0
|
'@emnapi/wasi-threads': 1.1.0
|
||||||
@@ -2002,6 +2147,24 @@ snapshots:
|
|||||||
|
|
||||||
'@kevisual/ws@8.19.0': {}
|
'@kevisual/ws@8.19.0': {}
|
||||||
|
|
||||||
|
'@lezer/common@1.5.1': {}
|
||||||
|
|
||||||
|
'@lezer/highlight@1.2.3':
|
||||||
|
dependencies:
|
||||||
|
'@lezer/common': 1.5.1
|
||||||
|
|
||||||
|
'@lezer/lr@1.4.8':
|
||||||
|
dependencies:
|
||||||
|
'@lezer/common': 1.5.1
|
||||||
|
|
||||||
|
'@lezer/yaml@1.0.4':
|
||||||
|
dependencies:
|
||||||
|
'@lezer/common': 1.5.1
|
||||||
|
'@lezer/highlight': 1.2.3
|
||||||
|
'@lezer/lr': 1.4.8
|
||||||
|
|
||||||
|
'@marijn/find-cluster-break@1.0.2': {}
|
||||||
|
|
||||||
'@napi-rs/wasm-runtime@1.1.1':
|
'@napi-rs/wasm-runtime@1.1.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@emnapi/core': 1.8.1
|
'@emnapi/core': 1.8.1
|
||||||
@@ -2278,6 +2441,33 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
csstype: 3.2.3
|
csstype: 3.2.3
|
||||||
|
|
||||||
|
'@uiw/codemirror-extensions-basic-setup@4.25.5(@codemirror/autocomplete@6.20.0)(@codemirror/commands@6.10.2)(@codemirror/language@6.12.2)(@codemirror/lint@6.9.4)(@codemirror/search@6.6.0)(@codemirror/state@6.5.4)(@codemirror/view@6.39.15)':
|
||||||
|
dependencies:
|
||||||
|
'@codemirror/autocomplete': 6.20.0
|
||||||
|
'@codemirror/commands': 6.10.2
|
||||||
|
'@codemirror/language': 6.12.2
|
||||||
|
'@codemirror/lint': 6.9.4
|
||||||
|
'@codemirror/search': 6.6.0
|
||||||
|
'@codemirror/state': 6.5.4
|
||||||
|
'@codemirror/view': 6.39.15
|
||||||
|
|
||||||
|
'@uiw/react-codemirror@4.25.5(@babel/runtime@7.28.6)(@codemirror/autocomplete@6.20.0)(@codemirror/language@6.12.2)(@codemirror/lint@6.9.4)(@codemirror/search@6.6.0)(@codemirror/state@6.5.4)(@codemirror/theme-one-dark@6.1.3)(@codemirror/view@6.39.15)(codemirror@6.0.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.28.6
|
||||||
|
'@codemirror/commands': 6.10.2
|
||||||
|
'@codemirror/state': 6.5.4
|
||||||
|
'@codemirror/theme-one-dark': 6.1.3
|
||||||
|
'@codemirror/view': 6.39.15
|
||||||
|
'@uiw/codemirror-extensions-basic-setup': 4.25.5(@codemirror/autocomplete@6.20.0)(@codemirror/commands@6.10.2)(@codemirror/language@6.12.2)(@codemirror/lint@6.9.4)(@codemirror/search@6.6.0)(@codemirror/state@6.5.4)(@codemirror/view@6.39.15)
|
||||||
|
codemirror: 6.0.2
|
||||||
|
react: 19.2.4
|
||||||
|
react-dom: 19.2.4(react@19.2.4)
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- '@codemirror/autocomplete'
|
||||||
|
- '@codemirror/language'
|
||||||
|
- '@codemirror/lint'
|
||||||
|
- '@codemirror/search'
|
||||||
|
|
||||||
'@vercel/oidc@3.1.0': {}
|
'@vercel/oidc@3.1.0': {}
|
||||||
|
|
||||||
'@vitejs/plugin-react@5.1.4(vite@8.0.0-beta.15(@types/node@25.3.0)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0))':
|
'@vitejs/plugin-react@5.1.4(vite@8.0.0-beta.15(@types/node@25.3.0)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0))':
|
||||||
@@ -2364,12 +2554,24 @@ snapshots:
|
|||||||
|
|
||||||
clsx@2.1.1: {}
|
clsx@2.1.1: {}
|
||||||
|
|
||||||
|
codemirror@6.0.2:
|
||||||
|
dependencies:
|
||||||
|
'@codemirror/autocomplete': 6.20.0
|
||||||
|
'@codemirror/commands': 6.10.2
|
||||||
|
'@codemirror/language': 6.12.2
|
||||||
|
'@codemirror/lint': 6.9.4
|
||||||
|
'@codemirror/search': 6.6.0
|
||||||
|
'@codemirror/state': 6.5.4
|
||||||
|
'@codemirror/view': 6.39.15
|
||||||
|
|
||||||
convert-source-map@2.0.0: {}
|
convert-source-map@2.0.0: {}
|
||||||
|
|
||||||
cookie-es@1.2.2: {}
|
cookie-es@1.2.2: {}
|
||||||
|
|
||||||
cookie-es@2.0.0: {}
|
cookie-es@2.0.0: {}
|
||||||
|
|
||||||
|
crelt@1.0.6: {}
|
||||||
|
|
||||||
crossws@0.3.5:
|
crossws@0.3.5:
|
||||||
dependencies:
|
dependencies:
|
||||||
uncrypto: 0.1.3
|
uncrypto: 0.1.3
|
||||||
@@ -2690,6 +2892,8 @@ snapshots:
|
|||||||
|
|
||||||
spark-md5@3.0.2: {}
|
spark-md5@3.0.2: {}
|
||||||
|
|
||||||
|
style-mod@4.1.3: {}
|
||||||
|
|
||||||
tabbable@6.4.0: {}
|
tabbable@6.4.0: {}
|
||||||
|
|
||||||
tailwind-merge@3.5.0: {}
|
tailwind-merge@3.5.0: {}
|
||||||
@@ -2775,6 +2979,8 @@ snapshots:
|
|||||||
jiti: 2.6.1
|
jiti: 2.6.1
|
||||||
tsx: 4.21.0
|
tsx: 4.21.0
|
||||||
|
|
||||||
|
w3c-keyname@2.2.8: {}
|
||||||
|
|
||||||
webpack-virtual-modules@0.6.2: {}
|
webpack-virtual-modules@0.6.2: {}
|
||||||
|
|
||||||
yallist@3.1.1: {}
|
yallist@3.1.1: {}
|
||||||
|
|||||||
130
src/pages/repos/components/BuildConfig.tsx
Normal file
130
src/pages/repos/components/BuildConfig.tsx
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { useRepoStore } from "../store";
|
||||||
|
import { useShallow } from "zustand/shallow";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import CodeMirror from "@uiw/react-codemirror";
|
||||||
|
import { yaml } from "@codemirror/lang-yaml";
|
||||||
|
import { useLayoutStore } from "@/pages/auth/store";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||||
|
import { Workflow } from "lucide-react";
|
||||||
|
|
||||||
|
export const BuildConfig = () => {
|
||||||
|
const repoStore = useRepoStore(useShallow((state) => ({
|
||||||
|
getItem: state.getItem,
|
||||||
|
editRepo: state.editRepo,
|
||||||
|
buildConfig: state.buildConfig,
|
||||||
|
setBuildConfig: state.setBuildConfig,
|
||||||
|
initBuildConfig: state.initBuildConfig,
|
||||||
|
deleteBuildConfig: state.deleteBuildConfig,
|
||||||
|
loading: state.loading,
|
||||||
|
buildWorkspace: state.buildWorkspace,
|
||||||
|
})));
|
||||||
|
const repo = repoStore.editRepo!;
|
||||||
|
const me = useLayoutStore((state) => state.me);
|
||||||
|
const [localConfig, setLocalConfig] = useState(repoStore.buildConfig?.config || "");
|
||||||
|
|
||||||
|
// 同步 buildConfig 变化时的状态
|
||||||
|
useEffect(() => {
|
||||||
|
setLocalConfig(repoStore.buildConfig?.config || "");
|
||||||
|
}, [repoStore.buildConfig]);
|
||||||
|
useEffect(() => {
|
||||||
|
if (repo) {
|
||||||
|
repoStore.initBuildConfig({ repo: repo, user: me });
|
||||||
|
}
|
||||||
|
}, [repo, me])
|
||||||
|
|
||||||
|
const handleSave = () => {
|
||||||
|
if (repoStore.buildConfig) {
|
||||||
|
repoStore.setBuildConfig({
|
||||||
|
...repoStore.buildConfig,
|
||||||
|
config: localConfig,
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFieldChange = (field: string, value: string | null) => {
|
||||||
|
if (repoStore.buildConfig && value !== null) {
|
||||||
|
repoStore.setBuildConfig({
|
||||||
|
...repoStore.buildConfig,
|
||||||
|
[field]: value,
|
||||||
|
}, false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (repoStore.loading) {
|
||||||
|
return <div>Loading...</div>
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className="flex gap-4 h-full overflow-hidden">
|
||||||
|
{/* 左侧边栏 - 配置信息 */}
|
||||||
|
<div className="w-64 shrink-0 space-y-4">
|
||||||
|
<div className="text-xl font-bold border-b pb-2 mb-4 flex">
|
||||||
|
<span className="text-lg font-semibold">构建配置</span>
|
||||||
|
<button
|
||||||
|
onClick={repoStore.buildWorkspace}
|
||||||
|
className="ml-auto p-2 text-sm cursor-pointer bg-gray-500 text-white rounded hover:bg-gray-600 flex items-center"
|
||||||
|
title="构建工作空间"
|
||||||
|
>
|
||||||
|
<Workflow className="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<label className="text-sm text-neutral-500">仓库</label>
|
||||||
|
<Input
|
||||||
|
value={repoStore.buildConfig?.repo || ""}
|
||||||
|
onChange={(e) => handleFieldChange("repo", e.target.value)}
|
||||||
|
placeholder="仓库名称"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<label className="text-sm text-neutral-500">分支</label>
|
||||||
|
<Input
|
||||||
|
value={repoStore.buildConfig?.branch || ""}
|
||||||
|
onChange={(e) => handleFieldChange("branch", e.target.value)}
|
||||||
|
placeholder="分支名称"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<label className="text-sm text-neutral-500">事件</label>
|
||||||
|
<Input
|
||||||
|
value={repoStore.buildConfig?.event || ""}
|
||||||
|
onChange={(e) => handleFieldChange("event", e.target.value)}
|
||||||
|
placeholder="事件名称"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 右侧 - 编辑器 */}
|
||||||
|
<div className="flex-1 flex flex-col h-full ">
|
||||||
|
<div className="flex items-center justify-between mb-2">
|
||||||
|
<span className="text-sm font-medium">配置文件</span>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<button
|
||||||
|
onClick={handleSave}
|
||||||
|
className="px-3 cursor-pointer py-1 text-sm bg-primary text-white rounded hover:bg-primary/90"
|
||||||
|
>
|
||||||
|
保存
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => repoStore.deleteBuildConfig({ repo: repo, user: me })}
|
||||||
|
className="px-3 cursor-pointer py-1 text-sm bg-red-500 text-white rounded hover:bg-red-600"
|
||||||
|
>
|
||||||
|
删除
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="border rounded-md flex-1 h-[calc(100%-40px)] overflow-auto scrollbar">
|
||||||
|
<CodeMirror
|
||||||
|
value={localConfig}
|
||||||
|
height="100%"
|
||||||
|
extensions={[yaml()]}
|
||||||
|
onChange={(value) => setLocalConfig(value)}
|
||||||
|
theme="light"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default BuildConfig;
|
||||||
@@ -13,13 +13,14 @@ import {
|
|||||||
PopoverContent,
|
PopoverContent,
|
||||||
PopoverTrigger,
|
PopoverTrigger,
|
||||||
} from '@/components/ui/popover'
|
} from '@/components/ui/popover'
|
||||||
import { Star, GitFork, FileText, Edit, FolderGit2, MoreVertical, FileText as IssueIcon, Settings, Play, Trash2, RefreshCw, BookOpen, Copy, Clock, Info, Eye, Square } from 'lucide-react'
|
import { Star, GitFork, FileText, Edit, FolderGit2, MoreVertical, FileText as IssueIcon, Settings, Play, Trash2, RefreshCw, BookOpen, Copy, Clock, Info, Eye, Square, LinkIcon, ExternalLink } from 'lucide-react'
|
||||||
import { useRepoStore } from '../store'
|
import { useRepoStore } from '../store'
|
||||||
import { useMemo, useState } from 'react'
|
import { useMemo, useState } from 'react'
|
||||||
import { useShallow } from 'zustand/shallow'
|
import { useShallow } from 'zustand/shallow'
|
||||||
import { myOrgs } from '../store/build'
|
import { myOrgs } from '../store/build'
|
||||||
import { app, cnb } from '@/agents/app'
|
import { app, cnb } from '@/agents/app'
|
||||||
import { toast } from 'sonner'
|
import { toast } from 'sonner'
|
||||||
|
import { useNavigate } from '@tanstack/react-router'
|
||||||
|
|
||||||
interface RepoCardProps {
|
interface RepoCardProps {
|
||||||
repo: any
|
repo: any
|
||||||
@@ -46,13 +47,13 @@ export function RepoCard({ repo, onStartWorkspace, onEdit, onIssue, onSettings,
|
|||||||
const isWorkspaceActive = !!workspace
|
const isWorkspaceActive = !!workspace
|
||||||
const owner = repo.path.split('/')[0]
|
const owner = repo.path.split('/')[0]
|
||||||
const isMine = myOrgs.includes(owner)
|
const isMine = myOrgs.includes(owner)
|
||||||
|
const navigate = useNavigate();
|
||||||
const isKnowledge = repo?.flags === "KnowledgeBase"
|
const isKnowledge = repo?.flags === "KnowledgeBase"
|
||||||
const createKnow = async () => {
|
const createKnow = async () => {
|
||||||
const res = await app.run({ path: 'cnb', key: 'build-knowledge-base', payload: { repo: repo.path } })
|
const res = await app.run({ path: 'cnb', key: 'build-knowledge-base', payload: { repo: repo.path } })
|
||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
toast.success("知识库创建中")
|
toast.success("知识库创建中")
|
||||||
getList(true)
|
getList({}, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const onClone = async () => {
|
const onClone = async () => {
|
||||||
@@ -72,6 +73,14 @@ export function RepoCard({ repo, onStartWorkspace, onEdit, onIssue, onSettings,
|
|||||||
<div className="p-6 space-y-4">
|
<div className="p-6 space-y-4">
|
||||||
<div className="flex items-start justify-between gap-3">
|
<div className="flex items-start justify-between gap-3">
|
||||||
<div className="flex items-center gap-2 flex-1 min-w-0">
|
<div className="flex items-center gap-2 flex-1 min-w-0">
|
||||||
|
<div
|
||||||
|
className="text-lg font-bold text-neutral-900 hover:text-neutral-600 transition-colors line-clamp-1 group-hover:underline"
|
||||||
|
onClick={() => {
|
||||||
|
navigate({ to: `/repo?repo=${repo.path}` })
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{repo.path}
|
||||||
|
</div>
|
||||||
{isKnowledge && (
|
{isKnowledge && (
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
@@ -88,14 +97,6 @@ export function RepoCard({ repo, onStartWorkspace, onEdit, onIssue, onSettings,
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
)}
|
)}
|
||||||
<a
|
|
||||||
href={repo.web_url}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="text-lg font-bold text-neutral-900 hover:text-neutral-600 transition-colors line-clamp-1 group-hover:underline"
|
|
||||||
>
|
|
||||||
{repo.path}
|
|
||||||
</a>
|
|
||||||
{isWorkspaceActive && (
|
{isWorkspaceActive && (
|
||||||
<span className="relative flex h-2.5 w-2.5 shrink-0">
|
<span className="relative flex h-2.5 w-2.5 shrink-0">
|
||||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75"></span>
|
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75"></span>
|
||||||
@@ -195,13 +196,19 @@ export function RepoCard({ repo, onStartWorkspace, onEdit, onIssue, onSettings,
|
|||||||
<Copy className="w-4 h-4 mr-2" />
|
<Copy className="w-4 h-4 mr-2" />
|
||||||
Clone URL
|
Clone URL
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem onClick={() => {
|
||||||
|
window.open(repo.web_url, '_blank')
|
||||||
|
}} className="cursor-pointer">
|
||||||
|
<ExternalLink className="w-4 h-4 mr-2" />
|
||||||
|
访问仓库
|
||||||
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem onClick={() => onIssue(repo)} className="cursor-pointer">
|
<DropdownMenuItem onClick={() => onIssue(repo)} className="cursor-pointer">
|
||||||
<IssueIcon className="w-4 h-4 mr-2" />
|
<IssueIcon className="w-4 h-4 mr-2" />
|
||||||
Issue
|
访问问题
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem onClick={() => onSettings(repo)} className="cursor-pointer">
|
<DropdownMenuItem onClick={() => onSettings(repo)} className="cursor-pointer">
|
||||||
<Settings className="w-4 h-4 mr-2" />
|
<Settings className="w-4 h-4 mr-2" />
|
||||||
设置
|
访问设置
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
@@ -268,22 +275,25 @@ export function RepoCard({ repo, onStartWorkspace, onEdit, onIssue, onSettings,
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{repo.site && (
|
{repo.site && (
|
||||||
<a
|
<div
|
||||||
href={repo.site}
|
className="text-xs text-neutral-500 hover:text-neutral-900 hover:underline flex transition-colors"
|
||||||
target="_blank"
|
onClick={() => {
|
||||||
rel="noopener noreferrer"
|
window.open(repo.site, '_blank')
|
||||||
className="text-xs text-neutral-500 hover:text-neutral-900 hover:underline block truncate transition-colors"
|
}}
|
||||||
>
|
>
|
||||||
🔗 {repo.site}
|
<LinkIcon className="w-4 h-4 shrink-0 mr-2" />
|
||||||
</a>
|
<div className='truncate grow'>
|
||||||
|
{repo.site}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{repo.description && (
|
{repo.description && (
|
||||||
<p className="text-sm text-neutral-600 line-clamp-2 min-h-10">
|
<p className="ml-2 text-sm text-neutral-600 line-clamp-2 min-h-10 grow">
|
||||||
{repo.description}
|
{repo.description}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
||||||
<div className="absolute bottom-0 left-0 right-0 flex items-center gap-4 text-xs text-neutral-500 px-6 py-3 border-t border-neutral-100 bg-neutral-50">
|
<div className="absolute bottom-0 left-0 right-0 flex items-center gap-4 text-xs text-neutral-500 px-6 py-3 border-t border-neutral-100 bg-neutral-50">
|
||||||
<span className="flex items-center gap-1.5 hover:text-neutral-900 transition-colors">
|
<span className="flex items-center gap-1.5 hover:text-neutral-900 transition-colors">
|
||||||
<Star className="w-3.5 h-3.5" />
|
<Star className="w-3.5 h-3.5" />
|
||||||
|
|||||||
@@ -1,16 +1,35 @@
|
|||||||
|
import { useNavigate } from "@tanstack/react-router";
|
||||||
|
import { useMemo } from "react";
|
||||||
import { useRepoStore } from "../store";
|
import { useRepoStore } from "../store";
|
||||||
import { useShallow } from "zustand/shallow";
|
import { useShallow } from "zustand/shallow";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { Card } from "@/components/ui/card";
|
import { Card } from "@/components/ui/card";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Star, GitFork, FileText, ExternalLink, Calendar, User, Copy } from "lucide-react";
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
|
||||||
|
import { Star, GitFork, FileText, ExternalLink, Calendar, User, Copy, ArrowLeft, Play, Square, Eye, BookOpen, RefreshCw } from "lucide-react";
|
||||||
|
import { myOrgs } from "../store/build";
|
||||||
|
|
||||||
export const RepoInfoCard = () => {
|
export const RepoInfoCard = () => {
|
||||||
const repoStore = useRepoStore(useShallow((state) => ({
|
const navigate = useNavigate();
|
||||||
getItem: state.getItem,
|
const { workspaceList, getWorkspaceDetail, stopWorkspace, editRepo, setSelectedSyncRepo, setSyncDialogOpen } = useRepoStore(useShallow((state) => ({
|
||||||
|
workspaceList: state.workspaceList,
|
||||||
|
getWorkspaceDetail: state.getWorkspaceDetail,
|
||||||
|
stopWorkspace: state.stopWorkspace,
|
||||||
editRepo: state.editRepo,
|
editRepo: state.editRepo,
|
||||||
|
setSelectedSyncRepo: state.setSelectedSyncRepo,
|
||||||
|
setSyncDialogOpen: state.setSyncDialogOpen,
|
||||||
})));
|
})));
|
||||||
const repo = repoStore.editRepo!;
|
const repo = editRepo!;
|
||||||
|
|
||||||
|
const workspace = useMemo(() => {
|
||||||
|
return workspaceList.find(ws => ws.slug === repo.path)
|
||||||
|
}, [workspaceList, repo.path])
|
||||||
|
const isWorkspaceActive = !!workspace
|
||||||
|
const owner = repo.path.split('/')[0]
|
||||||
|
const isMine = myOrgs.includes(owner)
|
||||||
|
const isKnowledge = repo?.flags === "KnowledgeBase"
|
||||||
|
|
||||||
const onClone = () => {
|
const onClone = () => {
|
||||||
const url = `git clone https://cnb.cool/${repo.path}`
|
const url = `git clone https://cnb.cool/${repo.path}`
|
||||||
navigator.clipboard.writeText(url).then(() => {
|
navigator.clipboard.writeText(url).then(() => {
|
||||||
@@ -30,6 +49,13 @@ export const RepoInfoCard = () => {
|
|||||||
{/* 标题行 */}
|
{/* 标题行 */}
|
||||||
<div className="flex items-start justify-between gap-4">
|
<div className="flex items-start justify-between gap-4">
|
||||||
<div className="flex items-center gap-3 flex-1 min-w-0">
|
<div className="flex items-center gap-3 flex-1 min-w-0">
|
||||||
|
<button
|
||||||
|
onClick={() => navigate({ to: '/' })}
|
||||||
|
className="cursor-pointer flex items-center justify-center w-8 h-8 rounded-md hover:bg-neutral-100 transition-colors"
|
||||||
|
>
|
||||||
|
<ArrowLeft className="w-4 h-4 text-neutral-600" />
|
||||||
|
</button>
|
||||||
|
|
||||||
<span className="text-sm text-neutral-500 font-mono">
|
<span className="text-sm text-neutral-500 font-mono">
|
||||||
{repo.path}
|
{repo.path}
|
||||||
</span>
|
</span>
|
||||||
@@ -43,37 +69,147 @@ export const RepoInfoCard = () => {
|
|||||||
<Badge variant="outline" className="shrink-0">
|
<Badge variant="outline" className="shrink-0">
|
||||||
{repo.visibility_level === 'Public' ? '公开' : repo.visibility_level === 'Private' ? '私有' : repo.visibility_level}
|
{repo.visibility_level === 'Public' ? '公开' : repo.visibility_level === 'Private' ? '私有' : repo.visibility_level}
|
||||||
</Badge>
|
</Badge>
|
||||||
|
{isKnowledge && (
|
||||||
|
<TooltipProvider>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger
|
||||||
|
render={
|
||||||
|
<div className="shrink-0">
|
||||||
|
<BookOpen className="w-5 h-5 text-neutral-700" />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<TooltipContent>
|
||||||
|
<p>知识库</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
|
)}
|
||||||
|
{isWorkspaceActive && (
|
||||||
|
<span className="relative flex h-2.5 w-2.5 shrink-0">
|
||||||
|
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75"></span>
|
||||||
|
<span className="relative inline-flex rounded-full h-2.5 w-2.5 bg-green-500"></span>
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2 shrink-0">
|
||||||
|
{isWorkspaceActive && (
|
||||||
|
<TooltipProvider>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger
|
||||||
|
render={
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => {
|
||||||
|
stopWorkspace(workspace)
|
||||||
|
}}
|
||||||
|
className="h-8 w-8 p-0 border-neutral-200 hover:border-red-600 hover:bg-red-600 hover:text-white transition-all cursor-pointer"
|
||||||
|
>
|
||||||
|
<Square className="w-4 h-4" />
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<TooltipContent>
|
||||||
|
<p>停止工作区</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
|
)}
|
||||||
|
<TooltipProvider>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger
|
||||||
|
render={
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => {
|
||||||
|
if (!isWorkspaceActive) {
|
||||||
|
// TODO: 启动工作区
|
||||||
|
} else {
|
||||||
|
getWorkspaceDetail(workspace)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className="h-8 w-8 p-0 border-neutral-200 hover:border-neutral-900 hover:bg-neutral-900 hover:text-white transition-all cursor-pointer"
|
||||||
|
>
|
||||||
|
{isWorkspaceActive ? <Eye className="w-4 h-4" /> : <Play className="w-4 h-4" />}
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<TooltipContent>
|
||||||
|
<p>{isWorkspaceActive ? '查看工作区' : '启动工作区'}</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
|
<a
|
||||||
|
href={repo.web_url}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="flex items-center gap-1.5 text-sm text-neutral-600 hover:text-neutral-900 transition-colors shrink-0"
|
||||||
|
>
|
||||||
|
<ExternalLink className="w-4 h-4" />
|
||||||
|
CNB
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<a
|
|
||||||
href={repo.web_url}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="flex items-center gap-1.5 text-sm text-neutral-600 hover:text-neutral-900 transition-colors shrink-0"
|
|
||||||
>
|
|
||||||
<ExternalLink className="w-4 h-4" />
|
|
||||||
在 CNB 上查看
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 描述 */}
|
{/* 描述 */}
|
||||||
{repo.description && (
|
{repo.description && (
|
||||||
<p className="text-sm text-neutral-600 max-h-[4.5em] overflow-hidden truncate">
|
<p className="text-sm text-neutral-600 h-12 overflow-hidden truncate">
|
||||||
{repo.description}
|
{repo.description}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* 主题标签 */}
|
{/* 主题标签和知识库 */}
|
||||||
{repo.topics && (
|
<div className="flex items-center gap-2">
|
||||||
<div className="flex flex-wrap gap-2">
|
{/* 主题标签 */}
|
||||||
{repo.topics.split(',').map((topic: string, idx: number) => (
|
{repo.topics && (
|
||||||
<Badge key={idx} variant="outline" className="text-xs border-neutral-300 text-neutral-700">
|
<div className="flex flex-wrap gap-2">
|
||||||
{topic.trim()}
|
{repo.topics.split(',').map((topic: string, idx: number) => (
|
||||||
</Badge>
|
<Badge key={idx} variant="outline" className="text-xs border-neutral-300 text-neutral-700">
|
||||||
))}
|
{topic.trim()}
|
||||||
</div>
|
</Badge>
|
||||||
)}
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* 语言和更新时间 */}
|
</div>
|
||||||
|
|
||||||
|
{/* 统计信息 */}
|
||||||
|
<div className="flex items-center gap-6 text-xs text-neutral-500">
|
||||||
|
<span className="flex items-center gap-1.5 hover:text-neutral-900 transition-colors">
|
||||||
|
<Star className="w-3.5 h-3.5" />
|
||||||
|
<span className="font-medium">{repo.star_count}</span>
|
||||||
|
</span>
|
||||||
|
<span className="flex items-center gap-1.5 hover:text-neutral-900 transition-colors">
|
||||||
|
<GitFork className="w-3.5 h-3.5" />
|
||||||
|
<span className="font-medium">{repo.fork_count}</span>
|
||||||
|
</span>
|
||||||
|
<span className="flex items-center gap-1.5 hover:text-neutral-900 transition-colors">
|
||||||
|
<FileText className="w-3.5 h-3.5" />
|
||||||
|
<span className="font-medium">{repo.open_issue_count}</span>
|
||||||
|
</span>
|
||||||
|
{isWorkspaceActive && (
|
||||||
|
<span className="flex items-center gap-1.5 hover:text-neutral-900 transition-colors cursor-pointer">
|
||||||
|
<Play className="w-3.5 h-3.5" />
|
||||||
|
<span className="font-medium">运行中</span>
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{isMine && (
|
||||||
|
<span
|
||||||
|
className="flex items-center gap-1.5 hover:text-neutral-900 transition-colors cursor-pointer"
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedSyncRepo(repo)
|
||||||
|
setSyncDialogOpen(true)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<RefreshCw className="w-3.5 h-3.5" />
|
||||||
|
<span className="font-medium">同步</span>
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 更新信息 */}
|
||||||
<div className="flex items-center gap-6 text-xs text-neutral-500">
|
<div className="flex items-center gap-6 text-xs text-neutral-500">
|
||||||
{repo.last_update_nickname && (
|
{repo.last_update_nickname && (
|
||||||
<span className="flex items-center gap-1">
|
<span className="flex items-center gap-1">
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ export function EditRepoDialog({ open, onOpenChange, repo }: EditRepoDialogProps
|
|||||||
license: data.license?.trim() || '',
|
license: data.license?.trim() || '',
|
||||||
})
|
})
|
||||||
|
|
||||||
await getList(true)
|
await getList({}, true)
|
||||||
onOpenChange(false)
|
onOpenChange(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -165,6 +165,21 @@ export const App = () => {
|
|||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
|
<CommonRepoDialog />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CommonRepoDialog = () => {
|
||||||
|
const { editRepo, showEditDialog, setShowEditDialog, showCreateDialog, setShowCreateDialog, } = useRepoStore(useShallow((state) => ({
|
||||||
|
editRepo: state.editRepo,
|
||||||
|
showEditDialog: state.showEditDialog,
|
||||||
|
setShowEditDialog: state.setShowEditDialog,
|
||||||
|
showCreateDialog: state.showCreateDialog,
|
||||||
|
setShowCreateDialog: state.setShowCreateDialog,
|
||||||
|
})))
|
||||||
|
return (
|
||||||
|
<>
|
||||||
<EditRepoDialog
|
<EditRepoDialog
|
||||||
open={showEditDialog}
|
open={showEditDialog}
|
||||||
onOpenChange={setShowEditDialog}
|
onOpenChange={setShowEditDialog}
|
||||||
@@ -176,8 +191,7 @@ export const App = () => {
|
|||||||
/>
|
/>
|
||||||
<WorkspaceDetailDialog />
|
<WorkspaceDetailDialog />
|
||||||
<SyncRepoDialog />
|
<SyncRepoDialog />
|
||||||
</div>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
@@ -1,18 +1,27 @@
|
|||||||
import { useSearch } from "@tanstack/react-router";
|
import { useSearch } from "@tanstack/react-router";
|
||||||
import { useRepoStore } from "../store";
|
import { useRepoStore } from "../store";
|
||||||
import { useEffect } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useShallow } from "zustand/shallow";
|
import { useShallow } from "zustand/shallow";
|
||||||
import { RepoInfoCard } from "../components/RepoInfoCard";
|
import { RepoInfoCard } from "../components/RepoInfoCard";
|
||||||
|
import BuildConfig from "../components/BuildConfig";
|
||||||
|
import { CommonRepoDialog } from "../page";
|
||||||
|
|
||||||
export const App = () => {
|
export const App = () => {
|
||||||
const params = useSearch({ strict: false }) as { repo?: string };
|
const params = useSearch({ strict: false }) as { repo?: string };
|
||||||
const repoStore = useRepoStore(useShallow((state) => ({
|
const repoStore = useRepoStore(useShallow((state) => ({
|
||||||
getItem: state.getItem,
|
getItem: state.getItem,
|
||||||
editRepo: state.editRepo,
|
editRepo: state.editRepo,
|
||||||
|
refresH: state.refresh,
|
||||||
})));
|
})));
|
||||||
|
const [activeTab, setActiveTab] = useState("build");
|
||||||
|
const tabs = [
|
||||||
|
{ key: "build", label: "构建配置" },
|
||||||
|
{ key: "info", label: "基本信息" },
|
||||||
|
]
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (params.repo) {
|
if (params.repo) {
|
||||||
repoStore.getItem(params.repo);
|
repoStore.getItem(params.repo);
|
||||||
|
repoStore.refresH({ search: params.repo, showTips: false });
|
||||||
} else {
|
} else {
|
||||||
console.log('no repo param')
|
console.log('no repo param')
|
||||||
}
|
}
|
||||||
@@ -21,10 +30,30 @@ export const App = () => {
|
|||||||
return <div>Loading...</div>
|
return <div>Loading...</div>
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div className="p-2">
|
<div className="p-2 flex-col flex gap-2 h-full">
|
||||||
<div className="px-4">
|
<div className="px-4">
|
||||||
<RepoInfoCard />
|
<RepoInfoCard />
|
||||||
</div>
|
</div>
|
||||||
|
<div className="px-4 h-[calc(100%-200px)] scrollbar flex-col flex gap-4 overflow-hidden">
|
||||||
|
<div className="flex border-b mb-4">
|
||||||
|
{tabs.map(tab => (
|
||||||
|
<div
|
||||||
|
key={tab.key}
|
||||||
|
className={`px-4 py-2 cursor-pointer ${activeTab === tab.key ? 'border-b-2 border-gray-500' : ''}`}
|
||||||
|
onClick={() => setActiveTab(tab.key)}
|
||||||
|
>
|
||||||
|
{tab.label}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
{activeTab === 'build' && <BuildConfig />}
|
||||||
|
{activeTab === 'info' && (
|
||||||
|
<div className="p-4 border rounded bg-white h-full overflow-auto scrollbar">
|
||||||
|
<pre className="whitespace-pre-wrap break-all">{JSON.stringify(repoStore.editRepo, null, 2)}</pre>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<CommonRepoDialog />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,3 +50,57 @@ export const createCommitBlankConfig = (params: { repo?: string, event: 'api_tri
|
|||||||
git push
|
git push
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const createDevConfig = (params: { repo?: string, event?: string }) => {
|
||||||
|
const event = params?.event || 'api_trigger_event';
|
||||||
|
return `##### 配置开始,保留注释 #####
|
||||||
|
.common_env: &common_env
|
||||||
|
env:
|
||||||
|
# 使用环境变量管理密钥,推荐使用密钥仓库管理密钥, 详情见 readme.md
|
||||||
|
# 使用仓库密钥时,注释
|
||||||
|
## 可选 API-Key 配置(按需取消注释)
|
||||||
|
# MINIMAX_API_KEY: '' # Minimax 模型
|
||||||
|
# ZHIPU_API_KEY: '' # 智谱 AI
|
||||||
|
# BAILIAN_CODE_API_KEY: '' # 阿里云百炼
|
||||||
|
# VOLCENGINE_API_KEY: '' # 火山引擎
|
||||||
|
# CNB_API_KEY: '' # CNB API
|
||||||
|
|
||||||
|
# 可选应用配置
|
||||||
|
# FEISHU_APP_ID: '' # 飞书应用 ID
|
||||||
|
# FEISHU_APP_SECRET: '' # 飞书应用密钥
|
||||||
|
|
||||||
|
# CNB_COOKIE: '' # 可选配置,用cnb.cool的cookie
|
||||||
|
USERNAME: root
|
||||||
|
ASSISTANT_CONFIG_DIR: /workspace/kevisual # ASSISTANT_CONFIG_DIR 环境变量指定了配置文件所在的目录
|
||||||
|
# CNB_KEVISUAL_ORG: kevisual # 私密仓库使用环境配置(默认即可,默认为当前用户组CNB_GROUP_SLUG)
|
||||||
|
# CNB_KEVISUAL_APP: assistant-app # 可选配置(默认即可)
|
||||||
|
# CNB_OPENCLAW: openclaw # 仓库名(默认即可)
|
||||||
|
# CNB_OPENWEBUI: open-webui # 仓库名(默认即可)
|
||||||
|
|
||||||
|
##### 配置结束 #####
|
||||||
|
|
||||||
|
main:
|
||||||
|
${event}:
|
||||||
|
- docker:
|
||||||
|
image: docker.cnb.cool/kevisual/dev-env:latest
|
||||||
|
services:
|
||||||
|
- vscode
|
||||||
|
- docker
|
||||||
|
runner:
|
||||||
|
cpus: 16
|
||||||
|
imports:
|
||||||
|
- https://cnb.cool/kevisual/env/-/blob/main/.env.development
|
||||||
|
env: !reference [.common_env, env]
|
||||||
|
stages:
|
||||||
|
- name: 环境变量
|
||||||
|
script: printenv > ~/.env.development
|
||||||
|
- name: 启动nginx
|
||||||
|
script: nginx
|
||||||
|
- name: 初始化开发机
|
||||||
|
script: zsh /workspace/scripts/init.sh
|
||||||
|
# endStages:
|
||||||
|
# - name: 结束阶段
|
||||||
|
# script: bun /workspace/scripts/end.ts
|
||||||
|
|
||||||
|
`
|
||||||
|
}
|
||||||
@@ -3,7 +3,8 @@ import { query } from '@/modules/query';
|
|||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { cnb } from '@/agents/app'
|
import { cnb } from '@/agents/app'
|
||||||
import { WorkspaceInfo } from '@kevisual/cnb'
|
import { WorkspaceInfo } from '@kevisual/cnb'
|
||||||
import { createBuildConfig, createCommitBlankConfig } from './build';
|
import { createBuildConfig, createCommitBlankConfig, createDevConfig } from './build';
|
||||||
|
import { useLayoutStore } from '@/pages/auth/store';
|
||||||
interface DisplayModule {
|
interface DisplayModule {
|
||||||
activity: boolean;
|
activity: boolean;
|
||||||
contributors: boolean;
|
contributors: boolean;
|
||||||
@@ -52,6 +53,12 @@ interface Data {
|
|||||||
|
|
||||||
type WorkspaceTabType = 'dev' | 'work'
|
type WorkspaceTabType = 'dev' | 'work'
|
||||||
|
|
||||||
|
type BuildConfig = {
|
||||||
|
repo: string;
|
||||||
|
branch: string;
|
||||||
|
event: string;
|
||||||
|
config: string;
|
||||||
|
}
|
||||||
type State = {
|
type State = {
|
||||||
formData: Record<string, any>;
|
formData: Record<string, any>;
|
||||||
setFormData: (data: Record<string, any>) => void;
|
setFormData: (data: Record<string, any>) => void;
|
||||||
@@ -68,13 +75,13 @@ type State = {
|
|||||||
setShowEditDialog: (show: boolean) => void;
|
setShowEditDialog: (show: boolean) => void;
|
||||||
showCreateDialog: boolean;
|
showCreateDialog: boolean;
|
||||||
setShowCreateDialog: (show: boolean) => void;
|
setShowCreateDialog: (show: boolean) => void;
|
||||||
getList: (silent?: boolean) => Promise<any>;
|
getList: (params?: { search?: string }, silent?: boolean) => Promise<any>;
|
||||||
updateRepoInfo: (data: Partial<Data>) => Promise<any>;
|
updateRepoInfo: (data: Partial<Data>) => Promise<any>;
|
||||||
createRepo: (data: { visibility: any, path: string, description: string, license: string }) => Promise<any>;
|
createRepo: (data: { visibility: any, path: string, description: string, license: string }) => Promise<any>;
|
||||||
deleteItem: (repo: string) => Promise<any>;
|
deleteItem: (repo: string) => Promise<any>;
|
||||||
workspaceList: WorkspaceInfo[];
|
workspaceList: WorkspaceInfo[];
|
||||||
getWorkspaceList: () => Promise<any>;
|
getWorkspaceList: () => Promise<any>;
|
||||||
refresh: (opts?: { message?: string, showTips?: boolean }) => Promise<any>;
|
refresh: (opts?: { message?: string, showTips?: boolean, search?: string }) => Promise<any>;
|
||||||
startWorkspace: (data: Partial<Data>, params?: { open?: boolean, branch?: string }) => Promise<any>;
|
startWorkspace: (data: Partial<Data>, params?: { open?: boolean, branch?: string }) => Promise<any>;
|
||||||
stopWorkspace: (workspace?: WorkspaceInfo) => Promise<any>;
|
stopWorkspace: (workspace?: WorkspaceInfo) => Promise<any>;
|
||||||
getWorkspaceDetail: (data: WorkspaceInfo) => Promise<any>;
|
getWorkspaceDetail: (data: WorkspaceInfo) => Promise<any>;
|
||||||
@@ -89,6 +96,11 @@ type State = {
|
|||||||
buildSync: (data: Partial<Data>, params: { toRepo?: string, fromRepo?: string }) => Promise<any>;
|
buildSync: (data: Partial<Data>, params: { toRepo?: string, fromRepo?: string }) => Promise<any>;
|
||||||
buildUpdate: (data: Partial<Data>, params?: any) => Promise<any>;
|
buildUpdate: (data: Partial<Data>, params?: any) => Promise<any>;
|
||||||
getItem: (repo: string) => Promise<any>;
|
getItem: (repo: string) => Promise<any>;
|
||||||
|
buildConfig: BuildConfig | null;
|
||||||
|
setBuildConfig: (config: BuildConfig | null, save?: boolean) => Promise<any>;
|
||||||
|
deleteBuildConfig: (params: { repo: Data, user?: any }) => Promise<any>;
|
||||||
|
initBuildConfig: (params: { repo: Data, user?: any }) => Promise<any>;
|
||||||
|
buildWorkspace: () => Promise<any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useRepoStore = create<State>((set, get) => {
|
export const useRepoStore = create<State>((set, get) => {
|
||||||
@@ -114,6 +126,127 @@ export const useRepoStore = create<State>((set, get) => {
|
|||||||
setSyncDialogOpen: (open) => set({ syncDialogOpen: open }),
|
setSyncDialogOpen: (open) => set({ syncDialogOpen: open }),
|
||||||
selectedSyncRepo: null,
|
selectedSyncRepo: null,
|
||||||
setSelectedSyncRepo: (repo) => set({ selectedSyncRepo: repo }),
|
setSelectedSyncRepo: (repo) => set({ selectedSyncRepo: repo }),
|
||||||
|
buildConfig: null,
|
||||||
|
setBuildConfig: async (config, save = true) => {
|
||||||
|
const me = useLayoutStore.getState().me;
|
||||||
|
if (config && config!.repo && save) {
|
||||||
|
let path: string = config.repo || '';
|
||||||
|
path = path.replace(/\//g, '__');
|
||||||
|
const key = `buildConfig_${path}.json`;
|
||||||
|
if (config && me) {
|
||||||
|
const res = await query.post({
|
||||||
|
path: 'config',
|
||||||
|
key: 'update',
|
||||||
|
data: {
|
||||||
|
key: key,
|
||||||
|
data: config,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (res.code === 200) {
|
||||||
|
toast.success('配置已保存')
|
||||||
|
} else {
|
||||||
|
toast.error(res.message || '配置保存失败')
|
||||||
|
}
|
||||||
|
} else if (config) {
|
||||||
|
localStorage.setItem(key, JSON.stringify(config));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
set({ buildConfig: config })
|
||||||
|
},
|
||||||
|
deleteBuildConfig: async (params: { repo: Data, user?: any }) => {
|
||||||
|
const repo = params.repo;
|
||||||
|
let path: string = repo.path || '';
|
||||||
|
path = path.replace(/\//g, '__');
|
||||||
|
const key = `buildConfig_${path}.json`;
|
||||||
|
if (params?.user) {
|
||||||
|
const res = await query.post({
|
||||||
|
path: 'config',
|
||||||
|
key: 'delete',
|
||||||
|
data: {
|
||||||
|
key: key,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (res.code === 200) {
|
||||||
|
toast.success('配置已删除')
|
||||||
|
} else {
|
||||||
|
toast.error(res.message || '配置删除失败')
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
localStorage.removeItem(key);
|
||||||
|
toast.success('配置已删除')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
initBuildConfig: async (params) => {
|
||||||
|
const repo = params.repo;
|
||||||
|
if (!repo) {
|
||||||
|
toast.error('仓库数据异常');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
set({ loading: true })
|
||||||
|
try {
|
||||||
|
console.log('初始化构建配置', params)
|
||||||
|
let path: string = repo.path || '';
|
||||||
|
path = path.replace(/\//g, '__');
|
||||||
|
const key = `buildConfig_${path}.json`;
|
||||||
|
if (params?.user) {
|
||||||
|
const res = await query.post({
|
||||||
|
path: 'config',
|
||||||
|
key: 'get',
|
||||||
|
data: {
|
||||||
|
key: key,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (res.code === 200 && res.data?.data) {
|
||||||
|
set({ buildConfig: res.data.data })
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const localConfig = localStorage.getItem(key);
|
||||||
|
if (localConfig) {
|
||||||
|
try {
|
||||||
|
const config = JSON.parse(localConfig);
|
||||||
|
set({ buildConfig: config })
|
||||||
|
return;
|
||||||
|
} catch (e) {
|
||||||
|
console.error('本地配置解析失败', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const config: BuildConfig = {
|
||||||
|
repo: repo.path,
|
||||||
|
branch: 'main',
|
||||||
|
event: 'api_trigger_event',
|
||||||
|
config: createDevConfig({ repo: repo.path, event: 'api_trigger_event' }),
|
||||||
|
}
|
||||||
|
set({ buildConfig: config })
|
||||||
|
} catch (e) {
|
||||||
|
toast.error('配置加载失败');
|
||||||
|
console.error('配置加载失败', e);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
set({ loading: false })
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
buildWorkspace: async () => {
|
||||||
|
const config = get().buildConfig;
|
||||||
|
if (!config) {
|
||||||
|
toast.error('请先保存构建配置');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const res = await cnb.build.startBuild(config.repo, {
|
||||||
|
branch: config.branch,
|
||||||
|
env: {},
|
||||||
|
event: config.event,
|
||||||
|
config: config.config,
|
||||||
|
})
|
||||||
|
if (res.code === 200) {
|
||||||
|
toast.success('构建已触发')
|
||||||
|
} else {
|
||||||
|
toast.error(res.message || '构建触发失败')
|
||||||
|
}
|
||||||
|
},
|
||||||
getItem: async (repo: string) => {
|
getItem: async (repo: string) => {
|
||||||
const { setLoading } = get();
|
const { setLoading } = get();
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
@@ -129,13 +262,19 @@ export const useRepoStore = create<State>((set, get) => {
|
|||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getList: async (silent = false) => {
|
getList: async (params?: { search?: string }, silent = false) => {
|
||||||
const { setLoading } = get();
|
const { setLoading } = get();
|
||||||
if (!silent) {
|
if (!silent) {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const res = await cnb.repo.getRepoList({})
|
let opts = {}
|
||||||
|
if (params?.search) {
|
||||||
|
opts = {
|
||||||
|
search: params.search
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const res = await cnb.repo.getRepoList(opts)
|
||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
const list = res.data! || []
|
const list = res.data! || []
|
||||||
set({ list });
|
set({ list });
|
||||||
@@ -171,8 +310,8 @@ export const useRepoStore = create<State>((set, get) => {
|
|||||||
toast.error(res.message || '更新失败');
|
toast.error(res.message || '更新失败');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
refresh: async (opts?: { message?: string, showTips?: boolean }) => {
|
refresh: async (opts?: { message?: string, showTips?: boolean, search?: string }) => {
|
||||||
const getList = get().getList();
|
const getList = get().getList({ search: opts?.search }, true);
|
||||||
const getWorkspaceList = get().getWorkspaceList();
|
const getWorkspaceList = get().getWorkspaceList();
|
||||||
await Promise.all([getList, getWorkspaceList]);
|
await Promise.all([getList, getWorkspaceList]);
|
||||||
if (opts?.showTips !== false) {
|
if (opts?.showTips !== false) {
|
||||||
@@ -207,7 +346,7 @@ export const useRepoStore = create<State>((set, get) => {
|
|||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
toast.success('删除成功');
|
toast.success('删除成功');
|
||||||
// 刷新列表
|
// 刷新列表
|
||||||
await get().getList(true);
|
await get().getList({}, true);
|
||||||
} else {
|
} else {
|
||||||
toast.error(res.message || '删除失败');
|
toast.error(res.message || '删除失败');
|
||||||
}
|
}
|
||||||
@@ -216,7 +355,7 @@ export const useRepoStore = create<State>((set, get) => {
|
|||||||
if (e.message?.includes('JSON') || e.message?.includes('json')) {
|
if (e.message?.includes('JSON') || e.message?.includes('json')) {
|
||||||
toast.success('删除成功');
|
toast.success('删除成功');
|
||||||
// 刷新列表
|
// 刷新列表
|
||||||
await get().getList(true);
|
await get().getList({}, true);
|
||||||
} else {
|
} else {
|
||||||
toast.error('删除失败');
|
toast.error('删除失败');
|
||||||
console.error('删除错误:', e);
|
console.error('删除错误:', e);
|
||||||
|
|||||||
Reference in New Issue
Block a user