mirror of
https://github.com/garethgeorge/backrest.git
synced 2026-05-04 03:50:30 +00:00
fix: refine backrest SFTP UI (#1193)
Release Please / release-please (push) Has been cancelled
Release Preview / call-reusable-release (push) Has been cancelled
Test / test-nix (push) Has been cancelled
Test / test-win (push) Has been cancelled
Update Restic / update-restic-version (push) Has been cancelled
Release Please / release-please (push) Has been cancelled
Release Preview / call-reusable-release (push) Has been cancelled
Test / test-nix (push) Has been cancelled
Test / test-win (push) Has been cancelled
Update Restic / update-restic-version (push) Has been cancelled
This commit is contained in:
@@ -280,53 +280,29 @@ func (s *BackrestHandler) SetupSftp(ctx context.Context, req *connect.Request[v1
|
||||
if port == "" {
|
||||
port = "22"
|
||||
}
|
||||
user := req.Msg.Username
|
||||
password := req.Msg.Password // Optional
|
||||
|
||||
if runtime.GOOS == "windows" {
|
||||
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("automated SFTP setup is not supported on Windows"))
|
||||
}
|
||||
|
||||
// 1. Host Key Verification/Addition
|
||||
if err := sftputil.AddHostKey(host, port, env.SSHDir()); err != nil {
|
||||
return connect.NewResponse(&v1.SetupSftpResponse{
|
||||
Error: fmt.Sprintf("Failed to add host key: %v", err),
|
||||
}), nil
|
||||
}
|
||||
|
||||
// 2. Generate Key
|
||||
// 1. Generate key pair (local, always succeeds)
|
||||
_, pubBytes, keyPath, err := sftputil.GenerateKey(host, env.SSHDir())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to generate key: %w", err)
|
||||
}
|
||||
|
||||
pubKeyStr := string(pubBytes)
|
||||
|
||||
// 3. Install if password provided
|
||||
if password != nil {
|
||||
if err := sftputil.InstallKey(host, port, user, *password, pubBytes); err != nil {
|
||||
return connect.NewResponse(&v1.SetupSftpResponse{
|
||||
Error: fmt.Sprintf("Failed to install key: %v", err),
|
||||
}), nil
|
||||
}
|
||||
|
||||
// Verify
|
||||
privPEM, err := os.ReadFile(keyPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read generated private key for verification: %w", err)
|
||||
}
|
||||
|
||||
if err := sftputil.VerifyConnection(host, port, user, privPEM); err != nil {
|
||||
return connect.NewResponse(&v1.SetupSftpResponse{
|
||||
Error: fmt.Sprintf("Key installed but verification failed: %v", err),
|
||||
}), nil
|
||||
}
|
||||
// 2. Scan remote host key into known_hosts (network, non-fatal)
|
||||
var hostKeyWarning string
|
||||
if err := sftputil.AddHostKey(host, port, env.SSHDir()); err != nil {
|
||||
zap.S().Warnf("SFTP host key scan failed for %s: %v", host, err)
|
||||
hostKeyWarning = fmt.Sprintf("Could not scan host key (%v). Add the host key to known_hosts manually or ensure the host is reachable.", err)
|
||||
}
|
||||
|
||||
return connect.NewResponse(&v1.SetupSftpResponse{
|
||||
PublicKey: pubKeyStr,
|
||||
PublicKey: string(pubBytes),
|
||||
KeyPath: keyPath,
|
||||
KnownHostsPath: filepath.Join(env.SSHDir(), "known_hosts"),
|
||||
Error: hostKeyWarning,
|
||||
}), nil
|
||||
}
|
||||
|
||||
|
||||
Generated
+67
-25
@@ -63,6 +63,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@ant-design/colors/-/colors-8.0.0.tgz",
|
||||
"integrity": "sha512-6YzkKCw30EI/E9kHOIXsQDHmMvTllT8STzjMb4K2qzit33RW2pqCJP0sk+hidBntXxE+Vz4n1+RvCTfBw6OErw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@ant-design/fast-color": "^3.0.0"
|
||||
}
|
||||
@@ -72,6 +73,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@ant-design/cssinjs/-/cssinjs-2.0.1.tgz",
|
||||
"integrity": "sha512-Lw1Z4cUQxdMmTNir67gU0HCpTl5TtkKCJPZ6UBvCqzcOTl/QmMFB6qAEoj8qFl0CuZDX9qQYa3m9+rEKfaBSbA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.11.1",
|
||||
"@emotion/hash": "^0.8.0",
|
||||
@@ -91,6 +93,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@ant-design/cssinjs-utils/-/cssinjs-utils-2.0.2.tgz",
|
||||
"integrity": "sha512-Mq3Hm6fJuQeFNKSp3+yT4bjuhVbdrsyXE2RyfpJFL0xiYNZdaJ6oFaE3zFrzmHbmvTd2Wp3HCbRtkD4fU+v2ZA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@ant-design/cssinjs": "^2.0.1",
|
||||
"@babel/runtime": "^7.23.2",
|
||||
@@ -105,25 +108,29 @@
|
||||
"version": "0.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz",
|
||||
"integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==",
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@ant-design/cssinjs/node_modules/@emotion/unitless": {
|
||||
"version": "0.7.5",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz",
|
||||
"integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==",
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@ant-design/cssinjs/node_modules/stylis": {
|
||||
"version": "4.3.6",
|
||||
"resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.6.tgz",
|
||||
"integrity": "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==",
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@ant-design/fast-color": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@ant-design/fast-color/-/fast-color-3.0.0.tgz",
|
||||
"integrity": "sha512-eqvpP7xEDm2S7dUzl5srEQCBTXZMmY3ekf97zI+M2DHOYyKdJGH0qua0JACHTqbkRnD/KHFQP9J1uMJ/XWVzzA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=8.x"
|
||||
}
|
||||
@@ -133,6 +140,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-6.1.0.tgz",
|
||||
"integrity": "sha512-KrWMu1fIg3w/1F2zfn+JlfNDU8dDqILfA5Tg85iqs1lf8ooyGlbkA+TkwfOKKgqpUmAiRY1PTFpuOU2DAIgSUg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@ant-design/colors": "^8.0.0",
|
||||
"@ant-design/icons-svg": "^4.4.0",
|
||||
@@ -151,13 +159,15 @@
|
||||
"version": "4.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@ant-design/icons-svg/-/icons-svg-4.4.2.tgz",
|
||||
"integrity": "sha512-vHbT+zJEVzllwP+CM+ul7reTEfBR0vgxFe7+lREAsAA7YGsYpboiq2sQNeQeRvh09GfQgs/GyFEvZpJ9cLXpXA==",
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@ant-design/react-slick": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@ant-design/react-slick/-/react-slick-2.0.0.tgz",
|
||||
"integrity": "sha512-HMS9sRoEmZey8LsE/Yo6+klhlzU12PisjrVcydW3So7RdklyEd2qehyU6a7Yp+OYN72mgsYs3NFCyP2lCPFVqg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.28.4",
|
||||
"clsx": "^2.1.1",
|
||||
@@ -275,7 +285,6 @@
|
||||
"integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.27.1",
|
||||
"@babel/generator": "^7.28.5",
|
||||
@@ -538,8 +547,7 @@
|
||||
"version": "2.10.2",
|
||||
"resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.10.2.tgz",
|
||||
"integrity": "sha512-uFsRXwIGyu+r6AMdz+XijIIZJYpoWeYzILt5yZ2d3mCjQrWUTVpVD9WL/jZAbvp+Ed04rOhrsk7FiTcEDseB5A==",
|
||||
"license": "(Apache-2.0 AND BSD-3-Clause)",
|
||||
"peer": true
|
||||
"license": "(Apache-2.0 AND BSD-3-Clause)"
|
||||
},
|
||||
"node_modules/@chakra-ui/react": {
|
||||
"version": "3.30.0",
|
||||
@@ -566,7 +574,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@connectrpc/connect/-/connect-2.1.1.tgz",
|
||||
"integrity": "sha512-JzhkaTvM73m2K1URT6tv53k2RwngSmCXLZJgK580qNQOXRzZRR/BCMfZw3h+90JpnG6XksP5bYT+cz0rpUzUWQ==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"peerDependencies": {
|
||||
"@bufbuild/protobuf": "^2.7.0"
|
||||
}
|
||||
@@ -598,7 +605,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz",
|
||||
"integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@dnd-kit/accessibility": "^3.1.1",
|
||||
"@dnd-kit/utilities": "^3.2.2",
|
||||
@@ -693,7 +699,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz",
|
||||
"integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.18.3",
|
||||
"@emotion/babel-plugin": "^11.13.5",
|
||||
@@ -1352,7 +1357,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.10.0.tgz",
|
||||
"integrity": "sha512-oxDR/NTEJ1k+UFVQElaNIk65E/Z83HK1z1WI3lQyhTtnNg4R5oVXaPzK3jcpKG8UHKDVuDQHzn+wsxSz8RP3aw==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@swc/helpers": "^0.5.0"
|
||||
}
|
||||
@@ -1702,7 +1706,6 @@
|
||||
"integrity": "sha512-/g2d4sW9nUDJOMz3mabVQvOGhVa4e/BN/Um7yca9Bb2XTzPPnfTWHWQg+IsEYO7M3Vx+EXvaM/I2pJWIMun1bg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@octokit/auth-token": "^4.0.0",
|
||||
"@octokit/graphql": "^7.1.0",
|
||||
@@ -2404,6 +2407,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@rc-component/async-validator/-/async-validator-5.0.4.tgz",
|
||||
"integrity": "sha512-qgGdcVIF604M9EqjNF0hbUTz42bz/RDtxWdWuU5EQe3hi7M8ob54B6B35rOsvX5eSvIHIzT9iH1R3n+hk3CGfg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.24.4"
|
||||
},
|
||||
@@ -2416,6 +2420,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@rc-component/cascader/-/cascader-1.9.0.tgz",
|
||||
"integrity": "sha512-2jbthe1QZrMBgtCvNKkJFjZYC3uKl4N/aYm5SsMvO3T+F+qRT1CGsSM9bXnh1rLj7jDk/GK0natShWF/jinhWQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@rc-component/select": "~1.3.0",
|
||||
"@rc-component/tree": "~1.1.0",
|
||||
@@ -2432,6 +2437,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@rc-component/checkbox/-/checkbox-1.0.1.tgz",
|
||||
"integrity": "sha512-08yTH8m+bSm8TOqbybbJ9KiAuIATti6bDs2mVeSfu4QfEnyeF6X0enHVvD1NEAyuBWEAo56QtLe++MYs2D9XiQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@rc-component/util": "^1.3.0",
|
||||
"clsx": "^2.1.1"
|
||||
@@ -2446,6 +2452,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@rc-component/collapse/-/collapse-1.1.2.tgz",
|
||||
"integrity": "sha512-ilBYk1dLLJHu5Q74dF28vwtKUYQ42ZXIIDmqTuVy4rD8JQVvkXOs+KixVNbweyuIEtJYJ7+t+9GVD9dPc6N02w==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.10.1",
|
||||
"@rc-component/motion": "^1.1.4",
|
||||
@@ -2462,6 +2469,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@rc-component/color-picker/-/color-picker-3.0.3.tgz",
|
||||
"integrity": "sha512-V7gFF9O7o5XwIWafdbOtqI4BUUkEUkgdBwp6favy3xajMX/2dDqytFaiXlcwrpq6aRyPLp5dKLAG5RFKLXMeGA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@ant-design/fast-color": "^3.0.0",
|
||||
"@rc-component/util": "^1.3.0",
|
||||
@@ -2477,6 +2485,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@rc-component/context/-/context-2.0.1.tgz",
|
||||
"integrity": "sha512-HyZbYm47s/YqtP6pKXNMjPEMaukyg7P0qVfgMLzr7YiFNMHbK2fKTAGzms9ykfGHSfyf75nBbgWw+hHkp+VImw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@rc-component/util": "^1.3.0"
|
||||
},
|
||||
@@ -2490,6 +2499,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@rc-component/dialog/-/dialog-1.5.1.tgz",
|
||||
"integrity": "sha512-by4Sf/a3azcb89WayWuwG19/Y312xtu8N81HoVQQtnsBDylfs+dog98fTAvLinnpeoWG52m/M7QLRW6fXR3l1g==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@rc-component/motion": "^1.1.3",
|
||||
"@rc-component/portal": "^2.0.0",
|
||||
@@ -2506,6 +2516,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@rc-component/drawer/-/drawer-1.3.0.tgz",
|
||||
"integrity": "sha512-rE+sdXEmv2W25VBQ9daGbnb4J4hBIEKmdbj0b3xpY+K7TUmLXDIlSnoXraIbFZdGyek9WxxGKK887uRnFgI+pQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@rc-component/motion": "^1.1.4",
|
||||
"@rc-component/portal": "^2.0.0",
|
||||
@@ -2522,6 +2533,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@rc-component/dropdown/-/dropdown-1.0.2.tgz",
|
||||
"integrity": "sha512-6PY2ecUSYhDPhkNHHb4wfeAya04WhpmUSKzdR60G+kMNVUCX2vjT/AgTS0Lz0I/K6xrPMJ3enQbwVpeN3sHCgg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@rc-component/trigger": "^3.0.0",
|
||||
"@rc-component/util": "^1.2.1",
|
||||
@@ -2537,6 +2549,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@rc-component/form/-/form-1.6.0.tgz",
|
||||
"integrity": "sha512-A7vrN8kExtw4sW06mrsgCb1rowhvBFFvQU6Bk/NL0Fj6Wet/5GF0QnGCxBu/sG3JI9FEhsJWES0D44BW2d0hzg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@rc-component/async-validator": "^5.0.3",
|
||||
"@rc-component/util": "^1.5.0",
|
||||
@@ -2555,6 +2568,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@rc-component/image/-/image-1.5.3.tgz",
|
||||
"integrity": "sha512-/NR7QW9uCN8Ugar+xsHZOPvzPySfEhcW2/vLcr7VPRM+THZMrllMRv7LAUgW7ikR+Z67Ab67cgPp5K5YftpJsQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@rc-component/motion": "^1.0.0",
|
||||
"@rc-component/portal": "^2.0.0",
|
||||
@@ -2571,6 +2585,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@rc-component/input/-/input-1.1.2.tgz",
|
||||
"integrity": "sha512-Q61IMR47piUBudgixJ30CciKIy9b1H95qe7GgEKOmSJVJXvFRWJllJfQry9tif+MX2cWFXWJf/RXz4kaCeq/Fg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@rc-component/util": "^1.4.0",
|
||||
"clsx": "^2.1.1"
|
||||
@@ -2585,6 +2600,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@rc-component/input-number/-/input-number-1.6.2.tgz",
|
||||
"integrity": "sha512-Gjcq7meZlCOiWN1t1xCC+7/s85humHVokTBI7PJgTfoyw5OWF74y3e6P8PHX104g9+b54jsodFIzyaj6p8LI9w==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@rc-component/mini-decimal": "^1.0.1",
|
||||
"@rc-component/util": "^1.4.0",
|
||||
@@ -2600,6 +2616,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@rc-component/mentions/-/mentions-1.6.0.tgz",
|
||||
"integrity": "sha512-KIkQNP6habNuTsLhUv0UGEOwG67tlmE7KNIJoQZZNggEZl5lQJTytFDb69sl5CK3TDdISCTjKP3nGEBKgT61CQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@rc-component/input": "~1.1.0",
|
||||
"@rc-component/menu": "~1.2.0",
|
||||
@@ -2618,6 +2635,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@rc-component/menu/-/menu-1.2.0.tgz",
|
||||
"integrity": "sha512-VWwDuhvYHSnTGj4n6bV3ISrLACcPAzdPOq3d0BzkeiM5cve8BEYfvkEhNoM0PLzv51jpcejeyrLXeMVIJ+QJlg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@rc-component/motion": "^1.1.4",
|
||||
"@rc-component/overflow": "^1.0.0",
|
||||
@@ -2635,6 +2653,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@rc-component/mini-decimal/-/mini-decimal-1.1.0.tgz",
|
||||
"integrity": "sha512-jS4E7T9Li2GuYwI6PyiVXmxTiM6b07rlD9Ge8uGZSCz3WlzcG5ZK7g5bbuKNeZ9pgUuPK/5guV781ujdVpm4HQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.18.0"
|
||||
},
|
||||
@@ -2647,6 +2666,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@rc-component/motion/-/motion-1.1.6.tgz",
|
||||
"integrity": "sha512-aEQobs/YA0kqRvHIPjQvOytdtdRVyhf/uXAal4chBjxDu6odHckExJzjn2D+Ju1aKK6hx3pAs6BXdV9+86xkgQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@rc-component/util": "^1.2.0",
|
||||
"clsx": "^2.1.1"
|
||||
@@ -2661,6 +2681,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@rc-component/mutate-observer/-/mutate-observer-2.0.1.tgz",
|
||||
"integrity": "sha512-AyarjoLU5YlxuValRi+w8JRH2Z84TBbFO2RoGWz9d8bSu0FqT8DtugH3xC3BV7mUwlmROFauyWuXFuq4IFbH+w==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@rc-component/util": "^1.2.0"
|
||||
},
|
||||
@@ -2677,6 +2698,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@rc-component/notification/-/notification-1.2.0.tgz",
|
||||
"integrity": "sha512-OX3J+zVU7rvoJCikjrfW7qOUp7zlDeFBK2eA3SFbGSkDqo63Sl4Ss8A04kFP+fxHSxMDIS9jYVEZtU1FNCFuBA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@rc-component/motion": "^1.1.4",
|
||||
"@rc-component/util": "^1.2.1",
|
||||
@@ -2695,6 +2717,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@rc-component/overflow/-/overflow-1.0.0.tgz",
|
||||
"integrity": "sha512-GSlBeoE0XTBi5cf3zl8Qh7Uqhn7v8RrlJ8ajeVpEkNe94HWy5l5BQ0Mwn2TVUq9gdgbfEMUmTX7tJFAg7mz0Rw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.11.1",
|
||||
"@rc-component/resize-observer": "^1.0.1",
|
||||
@@ -2711,6 +2734,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@rc-component/pagination/-/pagination-1.2.0.tgz",
|
||||
"integrity": "sha512-YcpUFE8dMLfSo6OARJlK6DbHHvrxz7pMGPGmC/caZSJJz6HRKHC1RPP001PRHCvG9Z/veD039uOQmazVuLJzlw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@rc-component/util": "^1.3.0",
|
||||
"clsx": "^2.1.1"
|
||||
@@ -2725,6 +2749,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@rc-component/picker/-/picker-1.9.0.tgz",
|
||||
"integrity": "sha512-OLisdk8AWVCG9goBU1dWzuH5QlBQk8jktmQ6p0/IyBFwdKGwyIZOSjnBYo8hooHiTdl0lU+wGf/OfMtVBw02KQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@rc-component/overflow": "^1.0.0",
|
||||
"@rc-component/resize-observer": "^1.0.0",
|
||||
@@ -2763,6 +2788,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@rc-component/portal/-/portal-2.1.0.tgz",
|
||||
"integrity": "sha512-P25IXWkzvBbyEtrAHRfqSNRkILXgAjDfuk0s4daPfHHO0XzVk3D3KJY3Lh069xwuBGtsTZpg+mP4WBLYl9GNaA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@rc-component/util": "^1.2.1",
|
||||
"clsx": "^2.1.1"
|
||||
@@ -2780,6 +2806,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@rc-component/progress/-/progress-1.0.2.tgz",
|
||||
"integrity": "sha512-WZUnH9eGxH1+xodZKqdrHke59uyGZSWgj5HBM5Kwk5BrTMuAORO7VJ2IP5Qbm9aH3n9x3IcesqHHR0NWPBC7fQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@rc-component/util": "^1.2.1",
|
||||
"clsx": "^2.1.1"
|
||||
@@ -2794,6 +2821,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@rc-component/qrcode/-/qrcode-1.1.1.tgz",
|
||||
"integrity": "sha512-LfLGNymzKdUPjXUbRP+xOhIWY4jQ+YMj5MmWAcgcAq1Ij8XP7tRmAXqyuv96XvLUBE/5cA8hLFl9eO1JQMujrA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.24.7"
|
||||
},
|
||||
@@ -2810,6 +2838,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@rc-component/rate/-/rate-1.0.1.tgz",
|
||||
"integrity": "sha512-bkXxeBqDpl5IOC7yL7GcSYjQx9G8H+6kLYQnNZWeBYq2OYIv1MONd6mqKTjnnJYpV0cQIU2z3atdW0j1kttpTw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@rc-component/util": "^1.3.0",
|
||||
"clsx": "^2.1.1"
|
||||
@@ -2827,6 +2856,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@rc-component/resize-observer/-/resize-observer-1.0.1.tgz",
|
||||
"integrity": "sha512-r+w+Mz1EiueGk1IgjB3ptNXLYSLZ5vnEfKHH+gfgj7JMupftyzvUUl3fRcMZe5uMM04x0n8+G2o/c6nlO2+Wag==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@rc-component/util": "^1.2.0"
|
||||
},
|
||||
@@ -2840,6 +2870,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@rc-component/segmented/-/segmented-1.3.0.tgz",
|
||||
"integrity": "sha512-5J/bJ01mbDnoA6P/FW8SxUvKn+OgUSTZJPzCNnTBntG50tzoP7DydGhqxp7ggZXZls7me3mc2EQDXakU3iTVFg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.11.1",
|
||||
"@rc-component/motion": "^1.1.4",
|
||||
@@ -2856,6 +2887,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@rc-component/select/-/select-1.3.6.tgz",
|
||||
"integrity": "sha512-CzbJ9TwmWcF5asvTMZ9BMiTE9CkkrigeOGRPpzCNmeZP7KBwwmYrmOIiKh9tMG7d6DyGAEAQ75LBxzPx+pGTHA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@rc-component/overflow": "^1.0.0",
|
||||
"@rc-component/trigger": "^3.0.0",
|
||||
@@ -2876,6 +2908,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@rc-component/slider/-/slider-1.0.1.tgz",
|
||||
"integrity": "sha512-uDhEPU1z3WDfCJhaL9jfd2ha/Eqpdfxsn0Zb0Xcq1NGQAman0TWaR37OWp2vVXEOdV2y0njSILTMpTfPV1454g==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@rc-component/util": "^1.3.0",
|
||||
"clsx": "^2.1.1"
|
||||
@@ -2893,6 +2926,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@rc-component/steps/-/steps-1.2.2.tgz",
|
||||
"integrity": "sha512-/yVIZ00gDYYPHSY0JP+M+s3ZvuXLu2f9rEjQqiUDs7EcYsUYrpJ/1bLj9aI9R7MBR3fu/NGh6RM9u2qGfqp+Nw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@rc-component/util": "^1.2.1",
|
||||
"clsx": "^2.1.1"
|
||||
@@ -2910,6 +2944,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@rc-component/switch/-/switch-1.0.3.tgz",
|
||||
"integrity": "sha512-Jgi+EbOBquje/XNdofr7xbJQZPYJP+BlPfR0h+WN4zFkdtB2EWqEfvkXJWeipflwjWip0/17rNbxEAqs8hVHfw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@rc-component/util": "^1.3.0",
|
||||
"clsx": "^2.1.1"
|
||||
@@ -2924,6 +2959,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@rc-component/table/-/table-1.9.0.tgz",
|
||||
"integrity": "sha512-cq3P9FkD+F3eglkFYhBuNlHclg+r4jY8+ZIgK7zbEFo6IwpnA77YL/Gq4ensLw9oua3zFCTA6JDu6YgBei0TxA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@rc-component/context": "^2.0.1",
|
||||
"@rc-component/resize-observer": "^1.0.0",
|
||||
@@ -2944,6 +2980,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@rc-component/tabs/-/tabs-1.7.0.tgz",
|
||||
"integrity": "sha512-J48cs2iBi7Ho3nptBxxIqizEliUC+ExE23faspUQKGQ550vaBlv3aGF8Epv/UB1vFWeoJDTW/dNzgIU0Qj5i/w==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@rc-component/dropdown": "~1.0.0",
|
||||
"@rc-component/menu": "~1.2.0",
|
||||
@@ -2965,6 +3002,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@rc-component/textarea/-/textarea-1.1.2.tgz",
|
||||
"integrity": "sha512-9rMUEODWZDMovfScIEHXWlVZuPljZ2pd1LKNjslJVitn4SldEzq5vO1CL3yy3Dnib6zZal2r2DPtjy84VVpF6A==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@rc-component/input": "~1.1.0",
|
||||
"@rc-component/resize-observer": "^1.0.0",
|
||||
@@ -2981,6 +3019,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@rc-component/tooltip/-/tooltip-1.4.0.tgz",
|
||||
"integrity": "sha512-8Rx5DCctIlLI4raR0I0xHjVTf1aF48+gKCNeAAo5bmF5VoR5YED+A/XEqzXv9KKqrJDRcd3Wndpxh2hyzrTtSg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@rc-component/trigger": "^3.7.1",
|
||||
"@rc-component/util": "^1.3.0",
|
||||
@@ -2996,6 +3035,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@rc-component/tour/-/tour-2.2.1.tgz",
|
||||
"integrity": "sha512-BUCrVikGJsXli38qlJ+h2WyDD6dYxzDA9dV3o0ij6gYhAq6ooT08SUMWOikva9v4KZ2BEuluGl5bPcsjrSoBgQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@rc-component/portal": "^2.0.0",
|
||||
"@rc-component/trigger": "^3.0.0",
|
||||
@@ -3015,6 +3055,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@rc-component/tree/-/tree-1.1.0.tgz",
|
||||
"integrity": "sha512-HZs3aOlvFgQdgrmURRc/f4IujiNBf4DdEeXUlkS0lPoLlx9RoqsZcF0caXIAMVb+NaWqKtGQDnrH8hqLCN5zlA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@rc-component/motion": "^1.0.0",
|
||||
"@rc-component/util": "^1.2.1",
|
||||
@@ -3034,6 +3075,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@rc-component/tree-select/-/tree-select-1.4.0.tgz",
|
||||
"integrity": "sha512-I3UAlO2hNqy9CSKc8EBaESgnmKk2QaRzuZ2XHZGFCgsSMkGl06mdF97sVfROM02YIb64ocgLKefsjE0Ch4ocwQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@rc-component/select": "~1.3.0",
|
||||
"@rc-component/tree": "~1.1.0",
|
||||
@@ -3050,6 +3092,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@rc-component/trigger/-/trigger-3.7.2.tgz",
|
||||
"integrity": "sha512-25x+D2k9SAkaK/MNMNmv2Nlv8FH1D9RtmjoMoLEw1Cid+sMV4pAAT5k49ku59UeXaOA1qwLUVrBUMq4A6gUSsQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@rc-component/motion": "^1.1.4",
|
||||
"@rc-component/portal": "^2.0.0",
|
||||
@@ -3070,6 +3113,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@rc-component/upload/-/upload-1.1.0.tgz",
|
||||
"integrity": "sha512-LIBV90mAnUE6VK5N4QvForoxZc4XqEYZimcp7fk+lkE4XwHHyJWxpIXQQwMU8hJM+YwBbsoZkGksL1sISWHQxw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@rc-component/util": "^1.3.0",
|
||||
"clsx": "^2.1.1"
|
||||
@@ -3084,6 +3128,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@rc-component/util/-/util-1.6.0.tgz",
|
||||
"integrity": "sha512-YbjuIVAm8InCnXVoA4n6G+uh31yESTxQ6fSY2frZ2/oMSvktoB+bumFUfNN7RKh7YeOkZgOvN2suGtEDhJSX0A==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"is-mobile": "^5.0.0",
|
||||
"react-is": "^18.2.0"
|
||||
@@ -3098,6 +3143,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@rc-component/virtual-list/-/virtual-list-1.0.2.tgz",
|
||||
"integrity": "sha512-uvTol/mH74FYsn5loDGJxo+7kjkO4i+y4j87Re1pxJBs0FaeuMuLRzQRGaXwnMcV1CxpZLi2Z56Rerj2M00fjQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.0",
|
||||
"@rc-component/resize-observer": "^1.0.1",
|
||||
@@ -3645,7 +3691,6 @@
|
||||
"integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/prop-types": "*",
|
||||
"csstype": "^3.2.2"
|
||||
@@ -4841,7 +4886,6 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"baseline-browser-mapping": "^2.9.0",
|
||||
"caniuse-lite": "^1.0.30001759",
|
||||
@@ -5117,7 +5161,8 @@
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-3.1.1.tgz",
|
||||
"integrity": "sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw==",
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/consola": {
|
||||
"version": "3.4.0",
|
||||
@@ -5993,7 +6038,8 @@
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-mobile/-/is-mobile-5.0.0.tgz",
|
||||
"integrity": "sha512-Tz/yndySvLAEXh+Uk8liFCxOwVH6YutuR74utvOcu7I9Di+DwM0mtdPVZNaVvvBUM2OXxne/NhOs1zAO7riusQ==",
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/is-number": {
|
||||
"version": "7.0.0",
|
||||
@@ -6077,6 +6123,7 @@
|
||||
"resolved": "https://registry.npmjs.org/json2mq/-/json2mq-0.2.0.tgz",
|
||||
"integrity": "sha512-SzoRg7ux5DWTII9J2qkrZrqV1gt+rTaoufMxEzXbS26Uid0NwaJd123HcoB80TgubEppxxIGdNxCx50fEoEWQA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"string-convert": "^0.2.0"
|
||||
}
|
||||
@@ -6172,7 +6219,6 @@
|
||||
"integrity": "sha512-FIyV/64EkKhJmjgC0g2hygpBv5RNWVPyNCqSAD7eTCv6eFWNIi4PN1UvdSJGicN/o35bnevgis4Y0UDC0qi8jQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
@@ -6708,7 +6754,6 @@
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
|
||||
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0"
|
||||
},
|
||||
@@ -6721,7 +6766,6 @@
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
|
||||
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0",
|
||||
"scheduler": "^0.23.2"
|
||||
@@ -6979,7 +7023,6 @@
|
||||
"integrity": "sha512-3nk8Y3a9Ea8szgKhinMlGMhGMw89mqule3KWczxhIzqudyHdCIOHw8WJlj/r329fACjKLEh13ZSk7oE22kyeIw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/estree": "1.0.8"
|
||||
},
|
||||
@@ -7084,7 +7127,6 @@
|
||||
"integrity": "sha512-uf6HoO8fy6ClsrShvMgaKUn14f2EHQLQRtpsZZLeU/Mv0Q1K5P0+x2uvH6Cub39TVVbWNSrraUhDAoFph6vh0A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"chokidar": "^4.0.0",
|
||||
"immutable": "^5.0.2",
|
||||
@@ -7114,6 +7156,7 @@
|
||||
"resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-3.1.0.tgz",
|
||||
"integrity": "sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"compute-scroll-into-view": "^3.0.2"
|
||||
}
|
||||
@@ -7211,7 +7254,8 @@
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/string-convert/-/string-convert-0.2.1.tgz",
|
||||
"integrity": "sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A==",
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/string-width": {
|
||||
"version": "5.1.2",
|
||||
@@ -7353,6 +7397,7 @@
|
||||
"resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-5.0.2.tgz",
|
||||
"integrity": "sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12.22"
|
||||
}
|
||||
@@ -7427,7 +7472,6 @@
|
||||
"integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"esbuild": "~0.27.0",
|
||||
"get-tsconfig": "^4.7.5"
|
||||
@@ -7448,7 +7492,6 @@
|
||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
@@ -7594,7 +7637,6 @@
|
||||
"integrity": "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.27.0",
|
||||
"fdir": "^6.5.0",
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
AccordionItemTrigger,
|
||||
AccordionRoot,
|
||||
} from "../../components/ui/accordion";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { useShowModal } from "../../components/common/ModalManager";
|
||||
import {
|
||||
CommandPrefix_CPUNiceLevel,
|
||||
@@ -108,6 +108,7 @@ interface SftpConfigSectionProps {
|
||||
onChangeIdentityFile: (path: string) => void;
|
||||
port: number | null;
|
||||
onChangePort: (port: number | null) => void;
|
||||
knownHostsPath: string;
|
||||
onChangeKnownHostsPath: (path: string) => void;
|
||||
isWindows: boolean;
|
||||
}
|
||||
@@ -118,70 +119,48 @@ const SftpConfigSection = ({
|
||||
onChangeIdentityFile,
|
||||
port,
|
||||
onChangePort,
|
||||
knownHostsPath,
|
||||
onChangeKnownHostsPath,
|
||||
isWindows,
|
||||
}: SftpConfigSectionProps) => {
|
||||
// Setup Keys state
|
||||
const [sftpUsername, setSftpUsername] = useState("");
|
||||
const [sftpPassword, setSftpPassword] = useState("");
|
||||
const [setupLoading, setSetupLoading] = useState(false);
|
||||
const [generatedPublicKey, setGeneratedPublicKey] = useState<string | null>(
|
||||
null,
|
||||
);
|
||||
const [generatedPublicKey, setGeneratedPublicKey] = useState<string | null>(null);
|
||||
const [hostKeyWarning, setHostKeyWarning] = useState<string | null>(null);
|
||||
const [keyCopied, setKeyCopied] = useState(false);
|
||||
|
||||
if (isWindows) return null;
|
||||
|
||||
const handleSetupKeys = async () => {
|
||||
const handleGenerateKey = async () => {
|
||||
setSetupLoading(true);
|
||||
setGeneratedPublicKey(null);
|
||||
setHostKeyWarning(null);
|
||||
try {
|
||||
if (!uri) return;
|
||||
// Simple parse of URI for host/port if not fully robust
|
||||
let host = "";
|
||||
|
||||
// Parse host and port from the SFTP URI
|
||||
const authority = uri.replace("sftp:", "").split("/")[0];
|
||||
const hostPart = authority.includes("@") ? authority.split("@")[1] : authority;
|
||||
let host = hostPart;
|
||||
let defaultPort = "22";
|
||||
const uriParts = uri.replace("sftp:", "").split("/");
|
||||
const authority = uriParts[0];
|
||||
let hostPart = authority;
|
||||
if (authority.includes("@")) {
|
||||
setSftpUsername(authority.split("@")[0]);
|
||||
hostPart = authority.split("@")[1];
|
||||
}
|
||||
|
||||
if (hostPart.includes(":")) {
|
||||
host = hostPart.split(":")[0];
|
||||
defaultPort = hostPart.split(":")[1];
|
||||
} else {
|
||||
host = hostPart;
|
||||
[host, defaultPort] = hostPart.split(":");
|
||||
}
|
||||
|
||||
// Override from manual input if username is set there
|
||||
const username = sftpUsername || uri.match(/([^@]+)@/)?.[1] || "";
|
||||
|
||||
const res = await backrestService.setupSftp({
|
||||
host: host,
|
||||
host,
|
||||
port: port ? port.toString() : defaultPort,
|
||||
username: username,
|
||||
password: sftpPassword || undefined,
|
||||
username: "",
|
||||
});
|
||||
|
||||
if (res.error) {
|
||||
throw new Error(res.error);
|
||||
}
|
||||
|
||||
onChangeIdentityFile(res.keyPath);
|
||||
onChangeKnownHostsPath(res.knownHostsPath);
|
||||
if (res.publicKey) {
|
||||
setGeneratedPublicKey(res.publicKey);
|
||||
}
|
||||
alerts.success(
|
||||
"Created SSH keypair at " +
|
||||
res.keyPath +
|
||||
" and updated known hosts file at " +
|
||||
res.knownHostsPath,
|
||||
);
|
||||
alerts.success(
|
||||
"Updated restic flags to use the SSH keypair and known hosts file.",
|
||||
);
|
||||
if (res.error) {
|
||||
setHostKeyWarning(res.error);
|
||||
}
|
||||
alerts.success("Generated SSH keypair at " + res.keyPath);
|
||||
} catch (e: any) {
|
||||
alerts.error(formatErrorAlert(e, "SFTP Setup Failed"));
|
||||
} finally {
|
||||
@@ -195,34 +174,22 @@ const SftpConfigSection = ({
|
||||
<AccordionRoot collapsible variant="enclosed">
|
||||
<AccordionItem value="bootstrap">
|
||||
<AccordionItemTrigger>
|
||||
Bootstrap SSH Key (Optional)
|
||||
Setup SSH Key (Optional)
|
||||
</AccordionItemTrigger>
|
||||
<AccordionItemContent>
|
||||
<Stack gap={3} p={2}>
|
||||
<CText fontSize="sm">
|
||||
Enter your SSH credentials here. When you click "Setup Keys",
|
||||
backrest will generate an SSH key pair.
|
||||
Click "Generate Key" to create an SSH key pair for this host.
|
||||
Backrest will attempt to scan the host key into known_hosts automatically.
|
||||
You will then need to add the generated public key to{" "}
|
||||
<Code>~/.ssh/authorized_keys</Code> on the remote server.
|
||||
</CText>
|
||||
<Field label="SSH Username">
|
||||
<Input
|
||||
placeholder="user"
|
||||
value={sftpUsername}
|
||||
onChange={(e) => setSftpUsername(e.target.value)}
|
||||
/>
|
||||
</Field>
|
||||
<Field label="SSH Password">
|
||||
<PasswordInput
|
||||
placeholder="password (optional)"
|
||||
value={sftpPassword}
|
||||
onChange={(e) => setSftpPassword(e.target.value)}
|
||||
/>
|
||||
</Field>
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={handleSetupKeys}
|
||||
onClick={handleGenerateKey}
|
||||
loading={setupLoading}
|
||||
>
|
||||
Setup Keys
|
||||
Generate Key
|
||||
</Button>
|
||||
</Stack>
|
||||
</AccordionItemContent>
|
||||
@@ -237,8 +204,7 @@ const SftpConfigSection = ({
|
||||
Key Generated Successfully!
|
||||
</CText>
|
||||
<CText fontSize="sm">
|
||||
Please add the following public key to your server's{" "}
|
||||
<Code>~/.ssh/authorized_keys</Code> file:
|
||||
Add the following public key to <Code>~/.ssh/authorized_keys</Code> on the remote server:
|
||||
</CText>
|
||||
<Box position="relative">
|
||||
<Code
|
||||
@@ -254,13 +220,22 @@ const SftpConfigSection = ({
|
||||
size="xs"
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(generatedPublicKey || "");
|
||||
alerts.success("Key copied to clipboard");
|
||||
setKeyCopied(true);
|
||||
setTimeout(() => setKeyCopied(false), 2000);
|
||||
}}
|
||||
colorPalette={keyCopied ? "green" : undefined}
|
||||
>
|
||||
Copy
|
||||
{keyCopied ? "Copied!" : "Copy"}
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
{hostKeyWarning && (
|
||||
<Box p={3} borderWidth={1} borderRadius="md" borderColor="yellow.400" bg="yellow.subtle">
|
||||
<CText fontSize="sm" color="yellow.700">
|
||||
<strong>Host key scan failed:</strong> {hostKeyWarning}
|
||||
</CText>
|
||||
</Box>
|
||||
)}
|
||||
</Stack>
|
||||
</Box>
|
||||
)}
|
||||
@@ -288,6 +263,17 @@ const SftpConfigSection = ({
|
||||
defaultValue={"22"}
|
||||
/>
|
||||
</Field>
|
||||
|
||||
<Field
|
||||
label="Known Hosts File"
|
||||
helperText="Optional: Path to a known_hosts file for host key verification. Populated automatically by Setup Keys."
|
||||
>
|
||||
<Input
|
||||
placeholder="/home/user/.ssh/known_hosts"
|
||||
value={knownHostsPath}
|
||||
onChange={(e) => onChangeKnownHostsPath(e.target.value)}
|
||||
/>
|
||||
</Field>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
@@ -304,12 +290,14 @@ export const AddRepoModal = ({ template }: { template: Repo | null }) => {
|
||||
: toJson(RepoSchema, repoDefaults, { alwaysEmitImplicit: true }),
|
||||
);
|
||||
|
||||
// SFTP specific state
|
||||
// SFTP specific state
|
||||
const [sftpIdentityFile, setSftpIdentityFile] = useState("");
|
||||
const [sftpPort, setSftpPort] = useState<number | null>(null);
|
||||
const [sftpKnownHostsPath, setSftpKnownHostsPath] = useState("");
|
||||
|
||||
// Ref to read current flags without making them a useEffect dependency
|
||||
const flagsRef = useRef<string[]>([]);
|
||||
|
||||
const [confirmation, setConfirmation] = useState<ConfirmationState>({
|
||||
open: false,
|
||||
title: "",
|
||||
@@ -323,11 +311,30 @@ export const AddRepoModal = ({ template }: { template: Repo | null }) => {
|
||||
? toJson(RepoSchema, template, { alwaysEmitImplicit: true })
|
||||
: toJson(RepoSchema, repoDefaults, { alwaysEmitImplicit: true }),
|
||||
);
|
||||
// Reset SFTP fields when template changes (or is null)
|
||||
if (!template) {
|
||||
setSftpIdentityFile("");
|
||||
setSftpPort(null);
|
||||
setSftpKnownHostsPath("");
|
||||
|
||||
setSftpIdentityFile("");
|
||||
setSftpPort(null);
|
||||
setSftpKnownHostsPath("");
|
||||
|
||||
if (template?.uri?.startsWith("sftp:")) {
|
||||
// Populate SFTP fields by parsing the existing sftp.args flag
|
||||
const sftpArgsFlag = (template.flags || []).find(
|
||||
(f) => f.includes("sftp.args") || f.includes("sftp.command"),
|
||||
);
|
||||
if (sftpArgsFlag) {
|
||||
const argsMatch = sftpArgsFlag.match(/sftp\.args=['"]?(.+?)['"]?\s*$/);
|
||||
if (argsMatch) {
|
||||
const argsStr = argsMatch[1].replace(/^'|'$/g, "");
|
||||
const identityMatch = argsStr.match(/-i\s+["']?([^\s"']+)["']?/);
|
||||
if (identityMatch) setSftpIdentityFile(identityMatch[1]);
|
||||
const portMatch = argsStr.match(/-p\s+(\d+)/);
|
||||
if (portMatch) setSftpPort(parseInt(portMatch[1], 10));
|
||||
const knownHostsMatch = argsStr.match(
|
||||
/-oUserKnownHostsFile=["']?([^\s"']+)["']?/,
|
||||
);
|
||||
if (knownHostsMatch) setSftpKnownHostsPath(knownHostsMatch[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [template]);
|
||||
|
||||
@@ -353,49 +360,45 @@ export const AddRepoModal = ({ template }: { template: Repo | null }) => {
|
||||
return curr;
|
||||
};
|
||||
|
||||
// Logic to update flags based on SFTP inputs
|
||||
useEffect(() => {
|
||||
// If we are editing, we don't touch the flags. The user can edit them manually.
|
||||
if (template) {
|
||||
return;
|
||||
}
|
||||
// Keep flagsRef in sync with latest formData.flags so the SFTP effect can
|
||||
// read the current value without flags being a reactive dependency.
|
||||
flagsRef.current = (formData.flags as string[]) || [];
|
||||
|
||||
// Keep sftp.args flag in sync with the SFTP config fields.
|
||||
useEffect(() => {
|
||||
const uri = getField(["uri"]);
|
||||
if (!uri?.startsWith("sftp:")) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentFlags = getField(["flags"]) || [];
|
||||
// Read flags via ref so this effect does not re-run whenever the user
|
||||
// edits the flags list (which would immediately erase empty rows).
|
||||
const currentFlags = flagsRef.current;
|
||||
const newFlags = currentFlags.filter(
|
||||
(f: string) =>
|
||||
f && !f.includes("sftp.args") && !f.includes("sftp.command"),
|
||||
);
|
||||
|
||||
// Always include -oBatchMode=yes; quote paths to handle spaces.
|
||||
let sftpArgs = "-oBatchMode=yes";
|
||||
let argsChanged = false;
|
||||
|
||||
if (sftpIdentityFile) {
|
||||
let cleanPath = sftpIdentityFile;
|
||||
if (cleanPath.startsWith("@")) {
|
||||
cleanPath = cleanPath.substring(1);
|
||||
}
|
||||
sftpArgs += ` -i ${cleanPath}`;
|
||||
argsChanged = true;
|
||||
sftpArgs += ` -i "${cleanPath}"`;
|
||||
}
|
||||
|
||||
if (sftpPort && sftpPort !== 0 && sftpPort !== 22) {
|
||||
sftpArgs += ` -p ${sftpPort}`;
|
||||
argsChanged = true;
|
||||
}
|
||||
|
||||
if (sftpKnownHostsPath) {
|
||||
sftpArgs += ` -oUserKnownHostsFile=${sftpKnownHostsPath}`;
|
||||
argsChanged = true;
|
||||
sftpArgs += ` -oUserKnownHostsFile="${sftpKnownHostsPath}"`;
|
||||
}
|
||||
|
||||
if (argsChanged) {
|
||||
newFlags.push(`--option=sftp.args='${sftpArgs}'`);
|
||||
}
|
||||
newFlags.push(`--option=sftp.args='${sftpArgs}'`);
|
||||
|
||||
const sortedCurrent = [...currentFlags].sort();
|
||||
const sortedNew = [...newFlags].sort();
|
||||
@@ -407,8 +410,9 @@ export const AddRepoModal = ({ template }: { template: Repo | null }) => {
|
||||
getField(["uri"]),
|
||||
sftpIdentityFile,
|
||||
sftpPort,
|
||||
template,
|
||||
getField(["flags"]),
|
||||
sftpKnownHostsPath,
|
||||
// flags intentionally omitted: flagsRef avoids a circular dep where any
|
||||
// user edit to flags would re-trigger the effect and erase empty rows.
|
||||
]);
|
||||
|
||||
if (!config) return null;
|
||||
@@ -784,13 +788,14 @@ export const AddRepoModal = ({ template }: { template: Repo | null }) => {
|
||||
</Field>
|
||||
|
||||
{/* SFTP Specific Fields */}
|
||||
{getField(["uri"])?.startsWith("sftp:") && !template && (
|
||||
{getField(["uri"])?.startsWith("sftp:") && (
|
||||
<SftpConfigSection
|
||||
uri={getField(["uri"])}
|
||||
identityFile={sftpIdentityFile}
|
||||
onChangeIdentityFile={setSftpIdentityFile}
|
||||
port={sftpPort}
|
||||
onChangePort={setSftpPort}
|
||||
knownHostsPath={sftpKnownHostsPath}
|
||||
onChangeKnownHostsPath={setSftpKnownHostsPath}
|
||||
isWindows={isWindows}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user