Merge pull request 'feat: add user management functionality with CRUD operations in database management section' (#1) from feature/generate-usuarios into development
Reviewed-on: #1
This commit is contained in:
750
package-lock.json
generated
750
package-lock.json
generated
@@ -313,6 +313,380 @@
|
||||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
|
||||
"integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"aix"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
|
||||
"integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
|
||||
"integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-x64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
|
||||
"integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-arm64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
|
||||
"integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-x64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
|
||||
"integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-arm64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
|
||||
"integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-x64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
|
||||
"integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
|
||||
"integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
|
||||
"integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ia32": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
|
||||
"integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-loong64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
|
||||
"integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-mips64el": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
|
||||
"integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
|
||||
"cpu": [
|
||||
"mips64el"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ppc64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
|
||||
"integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-riscv64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
|
||||
"integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-s390x": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
|
||||
"integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-x64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
|
||||
"integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-x64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
|
||||
"integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-x64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
|
||||
"integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/sunos-x64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
|
||||
"integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"sunos"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-arm64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
|
||||
"integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-ia32": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
|
||||
"integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-x64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
|
||||
@@ -592,6 +966,367 @@
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||
"version": "4.57.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz",
|
||||
"integrity": "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm64": {
|
||||
"version": "4.57.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz",
|
||||
"integrity": "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-arm64": {
|
||||
"version": "4.57.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz",
|
||||
"integrity": "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-x64": {
|
||||
"version": "4.57.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz",
|
||||
"integrity": "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-freebsd-arm64": {
|
||||
"version": "4.57.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz",
|
||||
"integrity": "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-freebsd-x64": {
|
||||
"version": "4.57.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz",
|
||||
"integrity": "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
||||
"version": "4.57.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz",
|
||||
"integrity": "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
||||
"version": "4.57.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz",
|
||||
"integrity": "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
||||
"version": "4.57.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz",
|
||||
"integrity": "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
||||
"version": "4.57.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz",
|
||||
"integrity": "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-loong64-gnu": {
|
||||
"version": "4.57.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz",
|
||||
"integrity": "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-loong64-musl": {
|
||||
"version": "4.57.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz",
|
||||
"integrity": "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
|
||||
"version": "4.57.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz",
|
||||
"integrity": "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-ppc64-musl": {
|
||||
"version": "4.57.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz",
|
||||
"integrity": "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
||||
"version": "4.57.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz",
|
||||
"integrity": "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-riscv64-musl": {
|
||||
"version": "4.57.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz",
|
||||
"integrity": "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
||||
"version": "4.57.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz",
|
||||
"integrity": "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
||||
"version": "4.57.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz",
|
||||
"integrity": "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-musl": {
|
||||
"version": "4.57.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz",
|
||||
"integrity": "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-openbsd-x64": {
|
||||
"version": "4.57.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz",
|
||||
"integrity": "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-openharmony-arm64": {
|
||||
"version": "4.57.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz",
|
||||
"integrity": "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openharmony"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
||||
"version": "4.57.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz",
|
||||
"integrity": "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
||||
"version": "4.57.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz",
|
||||
"integrity": "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-x64-gnu": {
|
||||
"version": "4.57.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz",
|
||||
@@ -2245,6 +2980,21 @@
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/fsevents": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/function-bind": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||
|
||||
@@ -2,6 +2,7 @@ import { db } from '$lib/server/db';
|
||||
import { env } from '$env/dynamic/private';
|
||||
import fs from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
import crypto from 'node:crypto';
|
||||
import { redirect } from '@sveltejs/kit';
|
||||
import type { PageServerLoad, Actions } from './$types';
|
||||
import { verifyToken } from '$lib/server/auth';
|
||||
@@ -50,6 +51,7 @@ export const load: PageServerLoad = async ({ cookies }) => {
|
||||
let clientsData: any[] = [];
|
||||
let alertsData: any[] = [];
|
||||
let basesDeDatosList: any[] = [];
|
||||
let usuariosList: any[] = [];
|
||||
let restoreHistory: Record<string, { restore_date: Date }[]> = {};
|
||||
let effectivenessByDb: Record<string, { month: string; effectiveness: number }[]> = {};
|
||||
|
||||
@@ -245,6 +247,11 @@ export const load: PageServerLoad = async ({ cookies }) => {
|
||||
const resBases = await azure.request().query(basesQueries);
|
||||
basesDeDatosList = resBases.recordset;
|
||||
|
||||
// Usuarios de la BD MSSQL (tabla CONTROLDESK.dbo.Usuarios)
|
||||
const usuariosQuery = `SELECT ID, IDNodoSubNodo, ClienteAutoridad, Nombre, Usuario, BD_Shelter FROM [CONTROLDESK].[dbo].[Usuarios] ORDER BY ID`;
|
||||
const resUsuarios = await azure.request().query(usuariosQuery);
|
||||
usuariosList = resUsuarios.recordset;
|
||||
|
||||
} catch (e: any) {
|
||||
console.error("Error loading Azure/ControlDesk data:", e);
|
||||
errors.azure = `Error conectando a Azure/ControlDesk: ${e.message}`;
|
||||
@@ -254,7 +261,7 @@ export const load: PageServerLoad = async ({ cookies }) => {
|
||||
try {
|
||||
let files: string[] = [];
|
||||
try {
|
||||
files = await fs.readdir(env.BACKUP_PATH);
|
||||
files = await fs.readdir(env.BACKUP_PATH as string);
|
||||
} catch (e) {
|
||||
files = [];
|
||||
errors.backups = "No se pudo acceder a la carpeta de respaldos.";
|
||||
@@ -263,7 +270,7 @@ export const load: PageServerLoad = async ({ cookies }) => {
|
||||
for (const file of files) {
|
||||
if (file === '.' || file === '..') continue;
|
||||
|
||||
const filePath = path.join(env.BACKUP_PATH, file);
|
||||
const filePath = path.join(env.BACKUP_PATH as string, file);
|
||||
let stats;
|
||||
try {
|
||||
stats = await fs.stat(filePath);
|
||||
@@ -441,9 +448,10 @@ export const load: PageServerLoad = async ({ cookies }) => {
|
||||
backupFiles,
|
||||
clientsData,
|
||||
alertsData,
|
||||
basesDeDatosList,
|
||||
restoreHistory,
|
||||
effectivenessByDb,
|
||||
basesDeDatosList,
|
||||
usuariosList,
|
||||
restoreHistory,
|
||||
effectivenessByDb,
|
||||
|
||||
errors, // Return the collected errors
|
||||
currentUser // Añadir usuario actual para la UI
|
||||
@@ -630,5 +638,137 @@ export const actions: Actions = {
|
||||
console.error('Error deleting database:', e);
|
||||
return { success: false, message: e.message };
|
||||
}
|
||||
},
|
||||
|
||||
// ---- Acciones para CONTROLDESK.dbo.Usuarios ----
|
||||
|
||||
createUsuario: async ({ request }) => {
|
||||
try {
|
||||
const formData = await request.formData();
|
||||
const idNodoSubNodo = Number(formData.get('IDNodoSubNodo'));
|
||||
const clienteAutoridad = Number(formData.get('ClienteAutoridad'));
|
||||
const nombre = String(formData.get('Nombre') || '').trim();
|
||||
const usuario = String(formData.get('Usuario') || '').trim();
|
||||
const password = String(formData.get('Password') || '');
|
||||
const bdShelter = String(formData.get('BD_Shelter') || '').trim() || null;
|
||||
|
||||
if (!idNodoSubNodo || !clienteAutoridad || !nombre || !usuario || !password) {
|
||||
return { success: false, message: 'Todos los campos obligatorios deben completarse' };
|
||||
}
|
||||
|
||||
// Hash WinDev-compatible: SHA-512(password + "soluciones"), hex UPPERCASE
|
||||
const passwordHash = crypto
|
||||
.createHash('sha512')
|
||||
.update(password + 'soluciones', 'utf8')
|
||||
.digest('hex')
|
||||
.toUpperCase();
|
||||
|
||||
const azure = await db.getAzure();
|
||||
const req = azure.request();
|
||||
req.input('idNodo', idNodoSubNodo);
|
||||
req.input('clienteAut', clienteAutoridad);
|
||||
req.input('nombre', nombre);
|
||||
req.input('usuario', usuario);
|
||||
req.input('passwordHash', passwordHash);
|
||||
req.input('bdShelter', bdShelter);
|
||||
|
||||
await req.query(`
|
||||
INSERT INTO [CONTROLDESK].[dbo].[Usuarios]
|
||||
(IDNodoSubNodo, ClienteAutoridad, Nombre, Usuario, [Password], BD_Shelter)
|
||||
VALUES
|
||||
(@idNodo, @clienteAut, @nombre, @usuario, @passwordHash, @bdShelter)
|
||||
`);
|
||||
|
||||
return { success: true };
|
||||
} catch (e: any) {
|
||||
console.error('Error creating usuario:', e);
|
||||
return { success: false, message: e.message };
|
||||
}
|
||||
},
|
||||
|
||||
updateUsuario: async ({ request }) => {
|
||||
try {
|
||||
const formData = await request.formData();
|
||||
const id = Number(formData.get('ID'));
|
||||
const idNodoSubNodo = Number(formData.get('IDNodoSubNodo'));
|
||||
const clienteAutoridad = Number(formData.get('ClienteAutoridad'));
|
||||
const nombre = String(formData.get('Nombre') || '').trim();
|
||||
const usuario = String(formData.get('Usuario') || '').trim();
|
||||
const password = String(formData.get('Password') || '');
|
||||
const bdShelter = String(formData.get('BD_Shelter') || '').trim() || null;
|
||||
|
||||
if (!id || !idNodoSubNodo || !clienteAutoridad || !nombre || !usuario) {
|
||||
return { success: false, message: 'Todos los campos obligatorios deben completarse' };
|
||||
}
|
||||
|
||||
const azure = await db.getAzure();
|
||||
const req = azure.request();
|
||||
req.input('id', id);
|
||||
req.input('idNodo', idNodoSubNodo);
|
||||
req.input('clienteAut', clienteAutoridad);
|
||||
req.input('nombre', nombre);
|
||||
req.input('usuario', usuario);
|
||||
req.input('bdShelter', bdShelter);
|
||||
|
||||
if (password) {
|
||||
// Hash WinDev-compatible: SHA-512(password + "soluciones"), hex UPPERCASE
|
||||
const passwordHash = crypto
|
||||
.createHash('sha512')
|
||||
.update(password + 'soluciones', 'utf8')
|
||||
.digest('hex')
|
||||
.toUpperCase();
|
||||
req.input('passwordHash', passwordHash);
|
||||
await req.query(`
|
||||
UPDATE [CONTROLDESK].[dbo].[Usuarios]
|
||||
SET IDNodoSubNodo = @idNodo,
|
||||
ClienteAutoridad = @clienteAut,
|
||||
Nombre = @nombre,
|
||||
Usuario = @usuario,
|
||||
[Password] = @passwordHash,
|
||||
BD_Shelter = @bdShelter
|
||||
WHERE ID = @id
|
||||
`);
|
||||
} else {
|
||||
await req.query(`
|
||||
UPDATE [CONTROLDESK].[dbo].[Usuarios]
|
||||
SET IDNodoSubNodo = @idNodo,
|
||||
ClienteAutoridad = @clienteAut,
|
||||
Nombre = @nombre,
|
||||
Usuario = @usuario,
|
||||
BD_Shelter = @bdShelter
|
||||
WHERE ID = @id
|
||||
`);
|
||||
}
|
||||
|
||||
return { success: true };
|
||||
} catch (e: any) {
|
||||
console.error('Error updating usuario:', e);
|
||||
return { success: false, message: e.message };
|
||||
}
|
||||
},
|
||||
|
||||
deleteUsuario: async ({ request }) => {
|
||||
try {
|
||||
const formData = await request.formData();
|
||||
const id = Number(formData.get('ID'));
|
||||
|
||||
if (!id) {
|
||||
return { success: false, message: 'ID requerido' };
|
||||
}
|
||||
|
||||
const azure = await db.getAzure();
|
||||
const req = azure.request();
|
||||
req.input('id', id);
|
||||
|
||||
await req.query(`
|
||||
DELETE FROM [CONTROLDESK].[dbo].[Usuarios]
|
||||
WHERE ID = @id
|
||||
`);
|
||||
|
||||
return { success: true };
|
||||
} catch (e: any) {
|
||||
console.error('Error deleting usuario:', e);
|
||||
return { success: false, message: e.message };
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,33 +1,33 @@
|
||||
<script lang="ts">
|
||||
import { untrack } from 'svelte';
|
||||
import aduanasoftLogo from '$lib/assets/logo.png';
|
||||
let { data } = $props();
|
||||
|
||||
const {
|
||||
summaryMain,
|
||||
databaseRows,
|
||||
restoredCount,
|
||||
notRestoredCount,
|
||||
backupFiles,
|
||||
clientsData,
|
||||
alertsData,
|
||||
basesDeDatosList,
|
||||
restoreHistory,
|
||||
effectivenessByDb
|
||||
} = data;
|
||||
const summaryMain = $derived(data.summaryMain);
|
||||
const databaseRows = $derived(data.databaseRows);
|
||||
const restoredCount = $derived(data.restoredCount);
|
||||
const notRestoredCount = $derived(data.notRestoredCount);
|
||||
const backupFiles = $derived(data.backupFiles);
|
||||
const clientsData = $derived(data.clientsData);
|
||||
const alertsData = $derived(data.alertsData);
|
||||
const basesDeDatosList = $derived(data.basesDeDatosList);
|
||||
const usuariosListData = $derived(data.usuariosList);
|
||||
const restoreHistory = $derived(data.restoreHistory);
|
||||
const effectivenessByDb = $derived(data.effectivenessByDb);
|
||||
|
||||
// Copias reactivas para permitir auto-refresh en cliente
|
||||
let databaseRowsLive = $state(databaseRows ?? []);
|
||||
let summaryMainLive = $state(summaryMain ?? null);
|
||||
let alertsDataLive = $state(alertsData ?? []);
|
||||
let databaseRowsLive = $state(untrack(() => data.databaseRows ?? []));
|
||||
let summaryMainLive = $state(untrack(() => data.summaryMain ?? null));
|
||||
let alertsDataLive = $state(untrack(() => data.alertsData ?? []));
|
||||
|
||||
// Métricas simples para las tarjetas del dashboard (derivadas de los datos "live")
|
||||
let totalDatabases = (databaseRowsLive ?? []).length;
|
||||
let totalSizeGB = (() => {
|
||||
let totalDatabases = $state((databaseRowsLive ?? []).length);
|
||||
let totalSizeGB = $state((() => {
|
||||
const raw = summaryMainLive?.total_size_gb ?? summaryMainLive?.TOTAL_SIZE_GB ?? 0;
|
||||
const val = Number(raw);
|
||||
if (!Number.isFinite(val)) return '0.00';
|
||||
return val.toFixed(2);
|
||||
})();
|
||||
})());
|
||||
|
||||
// Estado de vista activa y paginación
|
||||
let activeView = $state<'dashboard' | 'backups' | 'clients' | 'alerts' | 'databases'>('dashboard');
|
||||
@@ -66,7 +66,7 @@
|
||||
let selectedClient = $state<any | null>(null);
|
||||
let showClientModal = $state(false);
|
||||
// Copia editable del catálogo de clientes para poder actualizar Activo en UI
|
||||
let clientRows = $state((clientsData ?? []) as any[]);
|
||||
let clientRows = $state(untrack(() => (data.clientsData ?? []) as any[]));
|
||||
|
||||
// Modal de detalle de base de datos seleccionada
|
||||
let selectedDb = $state<any | null>(null);
|
||||
@@ -79,7 +79,128 @@
|
||||
|
||||
// Búsqueda en Gestión de Bases de Datos
|
||||
let databasesSearch = $state('');
|
||||
|
||||
|
||||
// Sub-tab dentro de "Gestión de Bases de Datos"
|
||||
let databasesSubTab = $state<'bases' | 'usuarios'>('bases');
|
||||
|
||||
// ---- Gestión de Usuarios (tabla MSSQL CONTROLDESK.dbo.Usuarios) ----
|
||||
let usuariosList = $state<any[]>(untrack(() => (data.usuariosList ?? []) as any[]));
|
||||
let usuariosSearch = $state('');
|
||||
let pageUsuarios = $state(1);
|
||||
|
||||
let showUsuarioModal = $state(false);
|
||||
let isEditingUsuario = $state(false);
|
||||
let formUsuario = $state<any>({
|
||||
ID: null,
|
||||
IDNodoSubNodo: '',
|
||||
ClienteAutoridad: '',
|
||||
Nombre: '',
|
||||
Usuario: '',
|
||||
Password: '',
|
||||
BD_Shelter: ''
|
||||
});
|
||||
let usuarioFormError = $state('');
|
||||
|
||||
const openCreateUsuarioModal = () => {
|
||||
isEditingUsuario = false;
|
||||
formUsuario = { ID: null, IDNodoSubNodo: '', ClienteAutoridad: '', Nombre: '', Usuario: '', Password: '', BD_Shelter: '' };
|
||||
usuarioFormError = '';
|
||||
showUsuarioModal = true;
|
||||
};
|
||||
|
||||
const openEditUsuarioModal = (u: any) => {
|
||||
isEditingUsuario = true;
|
||||
formUsuario = {
|
||||
ID: u.ID,
|
||||
IDNodoSubNodo: u.IDNodoSubNodo ?? '',
|
||||
ClienteAutoridad: u.ClienteAutoridad ?? '',
|
||||
Nombre: u.Nombre ?? '',
|
||||
Usuario: u.Usuario ?? '',
|
||||
Password: '', // No pre-llenar, vacío = no cambiar
|
||||
BD_Shelter: u.BD_Shelter ?? ''
|
||||
};
|
||||
usuarioFormError = '';
|
||||
showUsuarioModal = true;
|
||||
};
|
||||
|
||||
const closeUsuarioModal = () => {
|
||||
showUsuarioModal = false;
|
||||
};
|
||||
|
||||
const submitUsuarioForm = async () => {
|
||||
usuarioFormError = '';
|
||||
const action = isEditingUsuario ? 'updateUsuario' : 'createUsuario';
|
||||
const form = new FormData();
|
||||
|
||||
if (isEditingUsuario && formUsuario.ID != null) form.append('ID', String(formUsuario.ID));
|
||||
form.append('IDNodoSubNodo', String(formUsuario.IDNodoSubNodo));
|
||||
form.append('ClienteAutoridad', String(formUsuario.ClienteAutoridad));
|
||||
form.append('Nombre', formUsuario.Nombre);
|
||||
form.append('Usuario', formUsuario.Usuario);
|
||||
form.append('Password', formUsuario.Password);
|
||||
form.append('BD_Shelter', formUsuario.BD_Shelter ?? '');
|
||||
|
||||
try {
|
||||
const res = await fetch(`?/${action}`, { method: 'POST', body: form });
|
||||
const result = await res.json().catch(() => ({}));
|
||||
if (!res.ok || result.success === false) {
|
||||
usuarioFormError = result.message ?? 'Error al guardar usuario';
|
||||
return;
|
||||
}
|
||||
if (isEditingUsuario) {
|
||||
const idx = usuariosList.findIndex((x) => x.ID === formUsuario.ID);
|
||||
if (idx !== -1) {
|
||||
usuariosList[idx] = {
|
||||
...usuariosList[idx],
|
||||
IDNodoSubNodo: formUsuario.IDNodoSubNodo,
|
||||
ClienteAutoridad: formUsuario.ClienteAutoridad,
|
||||
Nombre: formUsuario.Nombre,
|
||||
Usuario: formUsuario.Usuario,
|
||||
BD_Shelter: formUsuario.BD_Shelter
|
||||
};
|
||||
}
|
||||
} else {
|
||||
window.location.reload();
|
||||
return;
|
||||
}
|
||||
closeUsuarioModal();
|
||||
} catch (e) {
|
||||
usuarioFormError = 'Error de red al guardar usuario';
|
||||
}
|
||||
};
|
||||
|
||||
const deleteUsuario = async (u: any) => {
|
||||
if (!confirm(`¿Eliminar el usuario "${u.Usuario}" (ID ${u.ID})?`)) return;
|
||||
const form = new FormData();
|
||||
form.append('ID', String(u.ID));
|
||||
try {
|
||||
const res = await fetch('?/deleteUsuario', { method: 'POST', body: form });
|
||||
const result = await res.json().catch(() => ({}));
|
||||
if (!res.ok || result.success === false) {
|
||||
alert(result.message ?? 'Error al eliminar usuario');
|
||||
return;
|
||||
}
|
||||
const idx = usuariosList.findIndex((x) => x.ID === u.ID);
|
||||
if (idx !== -1) usuariosList.splice(idx, 1);
|
||||
} catch (e) {
|
||||
alert('Error de red al eliminar usuario');
|
||||
}
|
||||
};
|
||||
|
||||
const filterUsuarios = () => {
|
||||
const term = usuariosSearch.trim().toLowerCase();
|
||||
const rows = usuariosList ?? [];
|
||||
if (!term) return rows;
|
||||
return rows.filter((u) => {
|
||||
return (
|
||||
String(u.ID ?? '').toLowerCase().includes(term) ||
|
||||
String(u.Nombre ?? '').toLowerCase().includes(term) ||
|
||||
String(u.Usuario ?? '').toLowerCase().includes(term) ||
|
||||
String(u.BD_Shelter ?? '').toLowerCase().includes(term)
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
// Estado del sidebar colapsado
|
||||
let sidebarCollapsed = $state(false);
|
||||
|
||||
@@ -567,6 +688,89 @@
|
||||
pageDatabases = 1;
|
||||
}
|
||||
});
|
||||
|
||||
// Historial de restauraciones para la base seleccionada
|
||||
const getSelectedHistory = (): { restore_date: Date }[] => {
|
||||
if (!selectedDb) return [];
|
||||
const key = String(selectedDb.visible_name ?? '').toLowerCase();
|
||||
const hist = (restoreHistory as Record<string, { restore_date: Date }[]>);
|
||||
return hist?.[key] ?? [];
|
||||
};
|
||||
|
||||
// Buckets de histograma para la semana en curso
|
||||
const getHistogramBuckets = () => {
|
||||
const entries = getSelectedHistory();
|
||||
const now = new Date();
|
||||
const buckets: { day: string; count: number }[] = [];
|
||||
for (let i = 6; i >= 0; i--) {
|
||||
const d = new Date(now);
|
||||
d.setDate(d.getDate() - i);
|
||||
const dayStr = d.toISOString().slice(0, 10);
|
||||
const count = entries.filter((e) => {
|
||||
const eDay = new Date(e.restore_date).toISOString().slice(0, 10);
|
||||
return eDay === dayStr;
|
||||
}).length;
|
||||
buckets.push({ day: dayStr, count });
|
||||
}
|
||||
return buckets;
|
||||
};
|
||||
|
||||
// Exportar historial del modal de detalle a CSV por rango de fechas
|
||||
const exportHistoryCsv = () => {
|
||||
if (!selectedDb) return;
|
||||
const entries = getSelectedHistory();
|
||||
const from = historyRangeFrom ? new Date(historyRangeFrom) : null;
|
||||
const to = historyRangeTo ? new Date(historyRangeTo + 'T23:59:59') : null;
|
||||
const filtered = entries.filter((e) => {
|
||||
const d = new Date(e.restore_date);
|
||||
if (from && d < from) return false;
|
||||
if (to && d > to) return false;
|
||||
return true;
|
||||
});
|
||||
if (!filtered.length) return;
|
||||
const header = ['Base de datos', 'Fecha de restauración'];
|
||||
const rows = filtered.map((e) =>
|
||||
[`"${selectedDb.visible_name}"`, `"${new Date(e.restore_date).toLocaleString()}"`].join(',')
|
||||
);
|
||||
const csv = [header.join(','), ...rows].join('\n');
|
||||
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = `historial_${selectedDb.visible_name}_${new Date().toISOString().slice(0, 10)}.csv`;
|
||||
link.click();
|
||||
URL.revokeObjectURL(url);
|
||||
};
|
||||
|
||||
// Formatear etiqueta de mes (YYYY-MM → Ene 2025)
|
||||
const formatMonthLabel = (month: string): string => {
|
||||
if (!month) return '';
|
||||
const [year, mon] = month.split('-');
|
||||
const d = new Date(Number(year), Number(mon) - 1, 1);
|
||||
return d.toLocaleDateString('es-MX', { month: 'short', year: 'numeric' });
|
||||
};
|
||||
|
||||
// Generar PDF de efectividad para la base seleccionada
|
||||
const generateEffectivenessPdf = (db: any) => {
|
||||
if (!db) return;
|
||||
const key = String(db.visible_name ?? '').toLowerCase();
|
||||
const rows = (effectivenessByDb as Record<string, { month: string; effectiveness: number }[]>)?.[key] ?? [];
|
||||
const printWindow = window.open('', '_blank', 'width=700,height=600');
|
||||
if (!printWindow) return;
|
||||
const generatedAt = new Date().toLocaleString();
|
||||
const tableRows = rows
|
||||
.map(
|
||||
(r) =>
|
||||
`<tr><td style="padding:4px 8px;border-bottom:1px solid #e2e8f0;">${formatMonthLabel(r.month)}</td><td style="padding:4px 8px;border-bottom:1px solid #e2e8f0;text-align:right;">${r.effectiveness}%</td></tr>`
|
||||
)
|
||||
.join('');
|
||||
const html = `<html><head><meta charset="utf-8"><title>Efectividad ${db.visible_name}</title></head><body style="margin:24px;font-family:system-ui,-apple-system,sans-serif;"><h2 style="font-size:14px;color:#0f172a;">Efectividad mensual · ${db.visible_name}</h2><p style="font-size:11px;color:#64748b;">Generado: ${generatedAt}</p><table style="border-collapse:collapse;width:100%;font-size:12px;"><thead><tr style="background:#f1f5f9;"><th style="padding:4px 8px;text-align:left;">Mes</th><th style="padding:4px 8px;text-align:right;">Efectividad</th></tr></thead><tbody>${tableRows}</tbody></table></body></html>`;
|
||||
printWindow.document.open();
|
||||
printWindow.document.write(html);
|
||||
printWindow.document.close();
|
||||
printWindow.focus();
|
||||
printWindow.print();
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class="flex min-h-screen bg-slate-50">
|
||||
@@ -738,7 +942,7 @@
|
||||
<span class="material-icons-outlined text-sm text-sky-700">person</span>
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<p class="text-xs font-medium text-slate-900 truncate">{data.currentUser?.nombre_completo || data.currentUser?.nombre_usuario || 'Usuario'}</p>
|
||||
<p class="text-xs font-medium text-slate-900 truncate">{data.currentUser?.nombre_completo || data.currentUser?.username || 'Usuario'}</p>
|
||||
<p class="text-[0.65rem] text-slate-500 truncate">{data.currentUser?.email || ''}</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -754,7 +958,7 @@
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex flex-col items-center gap-2">
|
||||
<div class="flex h-8 w-8 items-center justify-center rounded-full bg-sky-100 ring-2 ring-sky-200/60" title={data.currentUser?.nombre_usuario || 'Usuario'}>
|
||||
<div class="flex h-8 w-8 items-center justify-center rounded-full bg-sky-100 ring-2 ring-sky-200/60" title={data.currentUser?.username || 'Usuario'}>
|
||||
<span class="material-icons-outlined text-sm text-sky-700">person</span>
|
||||
</div>
|
||||
<form method="POST" action="/logout">
|
||||
@@ -1548,137 +1752,264 @@
|
||||
<section class="space-y-4">
|
||||
<header>
|
||||
<h2 class="text-base font-semibold text-slate-900">Gestión de Bases de Datos</h2>
|
||||
<p class="text-xs text-slate-500">Vista administrativa de las bases de datos registradas en el sistema.</p>
|
||||
<div class="mt-2">
|
||||
<button
|
||||
type="button"
|
||||
class="inline-flex items-center rounded-lg bg-sky-600 px-3 py-1.5 text-[0.7rem] font-medium text-white hover:bg-sky-700"
|
||||
onclick={openCreateDbModal}
|
||||
>
|
||||
Nueva base
|
||||
</button>
|
||||
</div>
|
||||
<p class="text-xs text-slate-500">Vista administrativa de las bases de datos y usuarios registrados en el sistema.</p>
|
||||
</header>
|
||||
|
||||
<div class="rounded-xl bg-white p-4 shadow-sm ring-1 ring-slate-200">
|
||||
<div class="mb-3 flex flex-col gap-2 text-xs text-slate-500 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div class="flex items-center gap-2">
|
||||
<span>Mostrando {PAGE_SIZE} por página</span>
|
||||
<span class="h-3 w-px bg-slate-200" aria-hidden="true"></span>
|
||||
<span>Total: {filterDatabases().length}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<input
|
||||
type="search"
|
||||
placeholder="Buscar por ID, nodo, nombre o servidor..."
|
||||
bind:value={databasesSearch}
|
||||
class="h-8 w-full max-w-xs rounded-full border border-slate-200 bg-slate-50 px-3 text-[0.7rem] text-slate-700 placeholder:text-slate-400 focus:border-sky-300 focus:outline-none focus:ring-1 focus:ring-sky-300"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="overflow-x-auto rounded-lg border border-slate-200">
|
||||
<table class="min-w-full table-fixed text-xs">
|
||||
<thead class="bg-slate-50 text-[0.65rem] uppercase tracking-wide text-slate-500">
|
||||
<tr>
|
||||
<th class="px-3 py-2 text-left">ID</th>
|
||||
<th class="px-3 py-2 text-left">Nodo</th>
|
||||
<th class="px-3 py-2 text-left">Nombre</th>
|
||||
<th class="px-3 py-2 text-left">Servidor</th>
|
||||
<th class="px-3 py-2 text-right">Acciones</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-slate-100">
|
||||
{#each getPaged(filterDatabases(), pageDatabases) as bd}
|
||||
<tr class="hover:bg-slate-50/80">
|
||||
<td class="px-3 py-2">{bd.ID}</td>
|
||||
<td class="px-3 py-2 truncate">{bd.NodoSubNodo}</td>
|
||||
<td class="px-3 py-2 truncate">
|
||||
{#if editingDbId === bd.ID}
|
||||
<input
|
||||
class="w-full rounded border border-slate-300 px-2 py-1 text-[0.7rem] text-slate-700 focus:border-sky-400 focus:outline-none focus:ring-1 focus:ring-sky-300"
|
||||
bind:value={editingDbName}
|
||||
/>
|
||||
{:else}
|
||||
<button
|
||||
type="button"
|
||||
class="w-full text-left text-slate-700 hover:text-sky-700 hover:underline"
|
||||
onclick={() => startEditDb(bd)}
|
||||
>
|
||||
{bd.Nombre}
|
||||
</button>
|
||||
{/if}
|
||||
</td>
|
||||
<td class="px-3 py-2 truncate">{bd.ServerName}</td>
|
||||
<td class="px-3 py-2 text-right">
|
||||
{#if editingDbId === bd.ID}
|
||||
<div class="inline-flex gap-1">
|
||||
<button
|
||||
type="button"
|
||||
class="rounded border border-emerald-500 px-2 py-0.5 text-[0.7rem] text-emerald-700 hover:bg-emerald-50"
|
||||
onclick={saveEditDb}
|
||||
>
|
||||
Guardar
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="rounded border border-slate-300 px-2 py-0.5 text-[0.7rem] text-slate-600 hover:bg-slate-50"
|
||||
onclick={cancelEditDb}
|
||||
>
|
||||
Cancelar
|
||||
</button>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="inline-flex gap-1">
|
||||
<button
|
||||
type="button"
|
||||
class="rounded border border-sky-500 px-2 py-0.5 text-[0.7rem] text-sky-700 hover:bg-sky-50"
|
||||
onclick={() => openEditDbModal(bd)}
|
||||
title="Editar base de datos"
|
||||
>
|
||||
Editar
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="rounded border border-rose-500 px-2 py-0.5 text-[0.7rem] text-rose-700 hover:bg-rose-50"
|
||||
onclick={() => deleteDb(bd)}
|
||||
title="Eliminar base de datos"
|
||||
>
|
||||
Eliminar
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
</td>
|
||||
</tr>
|
||||
{:else}
|
||||
<tr>
|
||||
<td colspan="4" class="px-3 py-6 text-center text-slate-500">
|
||||
No hay registros de gestión disponibles.
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="mt-3 flex items-center justify-between text-xs text-slate-600">
|
||||
<button
|
||||
class="inline-flex items-center rounded-lg border bg-white px-2.5 py-1 disabled:opacity-40"
|
||||
onclick={() => pageDatabases > 1 && pageDatabases--}
|
||||
disabled={pageDatabases === 1}
|
||||
>
|
||||
Anterior
|
||||
</button>
|
||||
<span>Página {pageDatabases}</span>
|
||||
<button
|
||||
class="inline-flex items-center rounded-lg border bg-white px-2.5 py-1 disabled:opacity-40"
|
||||
onclick={() =>
|
||||
getPaged(filterDatabases(), pageDatabases + 1).length > 0 && pageDatabases++}
|
||||
disabled={getPaged(filterDatabases(), pageDatabases + 1).length === 0}
|
||||
>
|
||||
Siguiente
|
||||
</button>
|
||||
</div>
|
||||
<!-- Sub-tabs -->
|
||||
<div class="flex gap-1 border-b border-slate-200">
|
||||
<button
|
||||
type="button"
|
||||
class={`px-4 py-2 text-xs font-medium transition-colors border-b-2 -mb-px ${databasesSubTab === 'bases' ? 'border-sky-600 text-sky-700' : 'border-transparent text-slate-500 hover:text-slate-700'}`}
|
||||
onclick={() => { databasesSubTab = 'bases'; pageDatabases = 1; }}
|
||||
>
|
||||
<span class="material-icons-outlined text-sm align-middle mr-1">dns</span>
|
||||
Bases de Datos
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class={`px-4 py-2 text-xs font-medium transition-colors border-b-2 -mb-px ${databasesSubTab === 'usuarios' ? 'border-sky-600 text-sky-700' : 'border-transparent text-slate-500 hover:text-slate-700'}`}
|
||||
onclick={() => { databasesSubTab = 'usuarios'; pageUsuarios = 1; }}
|
||||
>
|
||||
<span class="material-icons-outlined text-sm align-middle mr-1">people</span>
|
||||
Usuarios
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Sub-tab: Bases de Datos -->
|
||||
{#if databasesSubTab === 'bases'}
|
||||
<div>
|
||||
<div class="mb-3 flex justify-end">
|
||||
<button
|
||||
type="button"
|
||||
class="inline-flex items-center gap-1.5 rounded-lg bg-sky-600 px-3 py-1.5 text-[0.7rem] font-medium text-white hover:bg-sky-700"
|
||||
onclick={openCreateDbModal}
|
||||
>
|
||||
<span class="material-icons-outlined text-sm">add</span>
|
||||
Nueva base
|
||||
</button>
|
||||
</div>
|
||||
<div class="rounded-xl bg-white p-4 shadow-sm ring-1 ring-slate-200">
|
||||
<div class="mb-3 flex flex-col gap-2 text-xs text-slate-500 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div class="flex items-center gap-2">
|
||||
<span>Mostrando {PAGE_SIZE} por página</span>
|
||||
<span class="h-3 w-px bg-slate-200" aria-hidden="true"></span>
|
||||
<span>Total: {filterDatabases().length}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<input
|
||||
type="search"
|
||||
placeholder="Buscar por ID, nodo, nombre o servidor..."
|
||||
bind:value={databasesSearch}
|
||||
class="h-8 w-full max-w-xs rounded-full border border-slate-200 bg-slate-50 px-3 text-[0.7rem] text-slate-700 placeholder:text-slate-400 focus:border-sky-300 focus:outline-none focus:ring-1 focus:ring-sky-300"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="overflow-x-auto rounded-lg border border-slate-200">
|
||||
<table class="min-w-full table-fixed text-xs">
|
||||
<thead class="bg-slate-50 text-[0.65rem] uppercase tracking-wide text-slate-500">
|
||||
<tr>
|
||||
<th class="px-3 py-2 text-left">ID</th>
|
||||
<th class="px-3 py-2 text-left">Nodo</th>
|
||||
<th class="px-3 py-2 text-left">Nombre</th>
|
||||
<th class="px-3 py-2 text-left">Servidor</th>
|
||||
<th class="px-3 py-2 text-right">Acciones</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-slate-100">
|
||||
{#each getPaged(filterDatabases(), pageDatabases) as bd}
|
||||
<tr class="hover:bg-slate-50/80">
|
||||
<td class="px-3 py-2">{bd.ID}</td>
|
||||
<td class="px-3 py-2 truncate">{bd.NodoSubNodo}</td>
|
||||
<td class="px-3 py-2 truncate">
|
||||
{#if editingDbId === bd.ID}
|
||||
<input
|
||||
class="w-full rounded border border-slate-300 px-2 py-1 text-[0.7rem] text-slate-700 focus:border-sky-400 focus:outline-none focus:ring-1 focus:ring-sky-300"
|
||||
bind:value={editingDbName}
|
||||
/>
|
||||
{:else}
|
||||
<button
|
||||
type="button"
|
||||
class="w-full text-left text-slate-700 hover:text-sky-700 hover:underline"
|
||||
onclick={() => startEditDb(bd)}
|
||||
>
|
||||
{bd.Nombre}
|
||||
</button>
|
||||
{/if}
|
||||
</td>
|
||||
<td class="px-3 py-2 truncate">{bd.ServerName}</td>
|
||||
<td class="px-3 py-2 text-right">
|
||||
{#if editingDbId === bd.ID}
|
||||
<div class="inline-flex gap-1">
|
||||
<button
|
||||
type="button"
|
||||
class="rounded border border-emerald-500 px-2 py-0.5 text-[0.7rem] text-emerald-700 hover:bg-emerald-50"
|
||||
onclick={saveEditDb}
|
||||
>
|
||||
Guardar
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="rounded border border-slate-300 px-2 py-0.5 text-[0.7rem] text-slate-600 hover:bg-slate-50"
|
||||
onclick={cancelEditDb}
|
||||
>
|
||||
Cancelar
|
||||
</button>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="inline-flex gap-1">
|
||||
<button
|
||||
type="button"
|
||||
class="rounded border border-sky-500 px-2 py-0.5 text-[0.7rem] text-sky-700 hover:bg-sky-50"
|
||||
onclick={() => openEditDbModal(bd)}
|
||||
title="Editar base de datos"
|
||||
>
|
||||
Editar
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="rounded border border-rose-500 px-2 py-0.5 text-[0.7rem] text-rose-700 hover:bg-rose-50"
|
||||
onclick={() => deleteDb(bd)}
|
||||
title="Eliminar base de datos"
|
||||
>
|
||||
Eliminar
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
</td>
|
||||
</tr>
|
||||
{:else}
|
||||
<tr>
|
||||
<td colspan="5" class="px-3 py-6 text-center text-slate-500">
|
||||
No hay registros de gestión disponibles.
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="mt-3 flex items-center justify-between text-xs text-slate-600">
|
||||
<button
|
||||
class="inline-flex items-center rounded-lg border bg-white px-2.5 py-1 disabled:opacity-40"
|
||||
onclick={() => pageDatabases > 1 && pageDatabases--}
|
||||
disabled={pageDatabases === 1}
|
||||
>
|
||||
Anterior
|
||||
</button>
|
||||
<span>Página {pageDatabases}</span>
|
||||
<button
|
||||
class="inline-flex items-center rounded-lg border bg-white px-2.5 py-1 disabled:opacity-40"
|
||||
onclick={() =>
|
||||
getPaged(filterDatabases(), pageDatabases + 1).length > 0 && pageDatabases++}
|
||||
disabled={getPaged(filterDatabases(), pageDatabases + 1).length === 0}
|
||||
>
|
||||
Siguiente
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Sub-tab: Usuarios -->
|
||||
{#if databasesSubTab === 'usuarios'}
|
||||
<div>
|
||||
<div class="mb-3 flex justify-end">
|
||||
<button
|
||||
type="button"
|
||||
class="inline-flex items-center gap-1.5 rounded-lg bg-sky-600 px-3 py-1.5 text-[0.7rem] font-medium text-white hover:bg-sky-700"
|
||||
onclick={openCreateUsuarioModal}
|
||||
>
|
||||
<span class="material-icons-outlined text-sm">person_add</span>
|
||||
Nuevo usuario
|
||||
</button>
|
||||
</div>
|
||||
<div class="rounded-xl bg-white p-4 shadow-sm ring-1 ring-slate-200">
|
||||
<div class="mb-3 flex flex-col gap-2 text-xs text-slate-500 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div class="flex items-center gap-2">
|
||||
<span>Mostrando {PAGE_SIZE} por página</span>
|
||||
<span class="h-3 w-px bg-slate-200" aria-hidden="true"></span>
|
||||
<span>Total: {filterUsuarios().length}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<input
|
||||
type="search"
|
||||
placeholder="Buscar por nombre, usuario o BD..."
|
||||
bind:value={usuariosSearch}
|
||||
class="h-8 w-full max-w-xs rounded-full border border-slate-200 bg-slate-50 px-3 text-[0.7rem] text-slate-700 placeholder:text-slate-400 focus:border-sky-300 focus:outline-none focus:ring-1 focus:ring-sky-300"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="overflow-x-auto rounded-lg border border-slate-200">
|
||||
<table class="min-w-full table-fixed text-xs">
|
||||
<thead class="bg-slate-50 text-[0.65rem] uppercase tracking-wide text-slate-500">
|
||||
<tr>
|
||||
<th class="w-12 px-3 py-2 text-left">ID</th>
|
||||
<th class="px-3 py-2 text-left">IDNodo</th>
|
||||
<th class="px-3 py-2 text-left">Nombre</th>
|
||||
<th class="px-3 py-2 text-left">Usuario</th>
|
||||
<th class="px-3 py-2 text-left">BD Shelter</th>
|
||||
<th class="px-3 py-2 text-right">Acciones</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-slate-100">
|
||||
{#each getPaged(filterUsuarios(), pageUsuarios) as u}
|
||||
<tr class="hover:bg-slate-50/80">
|
||||
<td class="px-3 py-2">{u.ID}</td>
|
||||
<td class="px-3 py-2">{u.IDNodoSubNodo}</td>
|
||||
<td class="px-3 py-2 truncate">{u.Nombre}</td>
|
||||
<td class="px-3 py-2 truncate font-mono">{u.Usuario}</td>
|
||||
<td class="px-3 py-2 truncate">{u.BD_Shelter ?? '—'}</td>
|
||||
<td class="px-3 py-2 text-right">
|
||||
<div class="inline-flex gap-1">
|
||||
<button
|
||||
type="button"
|
||||
class="rounded border border-sky-500 px-2 py-0.5 text-[0.7rem] text-sky-700 hover:bg-sky-50"
|
||||
onclick={() => openEditUsuarioModal(u)}
|
||||
>
|
||||
Editar
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="rounded border border-rose-500 px-2 py-0.5 text-[0.7rem] text-rose-700 hover:bg-rose-50"
|
||||
onclick={() => deleteUsuario(u)}
|
||||
>
|
||||
Eliminar
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{:else}
|
||||
<tr>
|
||||
<td colspan="6" class="px-3 py-6 text-center text-slate-500">
|
||||
No hay usuarios registrados.
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="mt-3 flex items-center justify-between text-xs text-slate-600">
|
||||
<button
|
||||
class="inline-flex items-center rounded-lg border bg-white px-2.5 py-1 disabled:opacity-40"
|
||||
onclick={() => pageUsuarios > 1 && pageUsuarios--}
|
||||
disabled={pageUsuarios === 1}
|
||||
>
|
||||
Anterior
|
||||
</button>
|
||||
<span>Página {pageUsuarios}</span>
|
||||
<button
|
||||
class="inline-flex items-center rounded-lg border bg-white px-2.5 py-1 disabled:opacity-40"
|
||||
onclick={() =>
|
||||
getPaged(filterUsuarios(), pageUsuarios + 1).length > 0 && pageUsuarios++}
|
||||
disabled={getPaged(filterUsuarios(), pageUsuarios + 1).length === 0}
|
||||
>
|
||||
Siguiente
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</section>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -1689,10 +2020,12 @@
|
||||
<!-- Modal CRUD para Bases de Datos -->
|
||||
{#if showDbCrudModal}
|
||||
<div
|
||||
role="none"
|
||||
class="fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4"
|
||||
onclick={(e) => {
|
||||
if (e.target === e.currentTarget) closeDbCrudModal();
|
||||
}}
|
||||
onkeydown={(e) => { if (e.key === 'Escape') closeDbCrudModal(); }}
|
||||
>
|
||||
<div class="w-full max-w-3xl rounded-xl bg-white shadow-xl">
|
||||
<div class="flex items-center justify-between border-b border-slate-200 px-6 py-4">
|
||||
@@ -1701,6 +2034,7 @@
|
||||
</h3>
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Cerrar"
|
||||
class="rounded-lg p-1 text-slate-400 hover:bg-slate-100 hover:text-slate-600"
|
||||
onclick={closeDbCrudModal}
|
||||
>
|
||||
@@ -1714,10 +2048,11 @@
|
||||
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<!-- Nodo/SubNodo -->
|
||||
<div>
|
||||
<label class="mb-1 block text-sm font-medium text-slate-700">
|
||||
<label for="db-nodo" class="mb-1 block text-sm font-medium text-slate-700">
|
||||
Nodo/SubNodo <span class="text-rose-500">*</span>
|
||||
</label>
|
||||
<input
|
||||
id="db-nodo"
|
||||
type="text"
|
||||
bind:value={formDb.NodoSubNodo}
|
||||
required
|
||||
@@ -1728,10 +2063,11 @@
|
||||
|
||||
<!-- RFC -->
|
||||
<div>
|
||||
<label class="mb-1 block text-sm font-medium text-slate-700">
|
||||
<label for="db-rfc" class="mb-1 block text-sm font-medium text-slate-700">
|
||||
RFC <span class="text-rose-500">*</span>
|
||||
</label>
|
||||
<input
|
||||
id="db-rfc"
|
||||
type="text"
|
||||
bind:value={formDb.RFC}
|
||||
required
|
||||
@@ -1743,10 +2079,11 @@
|
||||
|
||||
<!-- Nombre -->
|
||||
<div>
|
||||
<label class="mb-1 block text-sm font-medium text-slate-700">
|
||||
<label for="db-nombre" class="mb-1 block text-sm font-medium text-slate-700">
|
||||
Nombre <span class="text-rose-500">*</span>
|
||||
</label>
|
||||
<input
|
||||
id="db-nombre"
|
||||
type="text"
|
||||
bind:value={formDb.Nombre}
|
||||
required
|
||||
@@ -1758,10 +2095,11 @@
|
||||
|
||||
<!-- Sucursal -->
|
||||
<div>
|
||||
<label class="mb-1 block text-sm font-medium text-slate-700">
|
||||
<label for="db-sucursal" class="mb-1 block text-sm font-medium text-slate-700">
|
||||
Sucursal <span class="text-rose-500">*</span>
|
||||
</label>
|
||||
<input
|
||||
id="db-sucursal"
|
||||
type="text"
|
||||
bind:value={formDb.Sucursal}
|
||||
required
|
||||
@@ -1773,10 +2111,11 @@
|
||||
|
||||
<!-- Correo de Notificación -->
|
||||
<div>
|
||||
<label class="mb-1 block text-sm font-medium text-slate-700">
|
||||
<label for="db-correo" class="mb-1 block text-sm font-medium text-slate-700">
|
||||
Correo de Notificación <span class="text-rose-500">*</span>
|
||||
</label>
|
||||
<input
|
||||
id="db-correo"
|
||||
type="email"
|
||||
bind:value={formDb.CorreoNotificacion}
|
||||
required
|
||||
@@ -1788,10 +2127,11 @@
|
||||
|
||||
<!-- Server Name -->
|
||||
<div>
|
||||
<label class="mb-1 block text-sm font-medium text-slate-700">
|
||||
<label for="db-server" class="mb-1 block text-sm font-medium text-slate-700">
|
||||
Servidor <span class="text-rose-500">*</span>
|
||||
</label>
|
||||
<input
|
||||
id="db-server"
|
||||
type="text"
|
||||
bind:value={formDb.ServerName}
|
||||
required
|
||||
@@ -1803,10 +2143,11 @@
|
||||
|
||||
<!-- BD Name -->
|
||||
<div>
|
||||
<label class="mb-1 block text-sm font-medium text-slate-700">
|
||||
<label for="db-bdname" class="mb-1 block text-sm font-medium text-slate-700">
|
||||
Nombre de BD <span class="text-rose-500">*</span>
|
||||
</label>
|
||||
<input
|
||||
id="db-bdname"
|
||||
type="text"
|
||||
bind:value={formDb.BDName}
|
||||
required
|
||||
@@ -1849,3 +2190,152 @@
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Modal CRUD para Usuarios (CONTROLDESK.dbo.Usuarios) -->
|
||||
{#if showUsuarioModal}
|
||||
<div
|
||||
role="none"
|
||||
class="fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4"
|
||||
onclick={(e) => { if (e.target === e.currentTarget) closeUsuarioModal(); }}
|
||||
onkeydown={(e) => { if (e.key === 'Escape') closeUsuarioModal(); }}
|
||||
>
|
||||
<div class="w-full max-w-lg rounded-xl bg-white shadow-xl">
|
||||
<div class="flex items-center justify-between border-b border-slate-200 px-6 py-4">
|
||||
<h3 class="text-base font-semibold text-slate-900">
|
||||
{isEditingUsuario ? 'Editar Usuario' : 'Nuevo Usuario'}
|
||||
</h3>
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Cerrar"
|
||||
class="rounded-lg p-1 text-slate-400 hover:bg-slate-100 hover:text-slate-600"
|
||||
onclick={closeUsuarioModal}
|
||||
>
|
||||
<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<form class="p-6" onsubmit={(e) => { e.preventDefault(); submitUsuarioForm(); }}>
|
||||
{#if usuarioFormError}
|
||||
<div class="mb-4 rounded-lg border border-rose-200 bg-rose-50 px-3 py-2 text-xs text-rose-700">
|
||||
{usuarioFormError}
|
||||
</div>
|
||||
{/if}
|
||||
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<!-- IDNodoSubNodo -->
|
||||
<div>
|
||||
<label for="u-nodo" class="mb-1 block text-sm font-medium text-slate-700">
|
||||
ID Nodo/SubNodo <span class="text-rose-500">*</span>
|
||||
</label>
|
||||
<input
|
||||
id="u-nodo"
|
||||
type="number"
|
||||
bind:value={formUsuario.IDNodoSubNodo}
|
||||
required
|
||||
class="w-full rounded-lg border border-slate-300 px-3 py-2 text-sm focus:border-sky-400 focus:outline-none focus:ring-1 focus:ring-sky-300"
|
||||
placeholder="ID numérico del nodo"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- ClienteAutoridad -->
|
||||
<div>
|
||||
<label for="u-cliente" class="mb-1 block text-sm font-medium text-slate-700">
|
||||
Cliente Autoridad <span class="text-rose-500">*</span>
|
||||
</label>
|
||||
<input
|
||||
id="u-cliente"
|
||||
type="number"
|
||||
bind:value={formUsuario.ClienteAutoridad}
|
||||
required
|
||||
class="w-full rounded-lg border border-slate-300 px-3 py-2 text-sm focus:border-sky-400 focus:outline-none focus:ring-1 focus:ring-sky-300"
|
||||
placeholder="ID numérico del cliente"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Nombre -->
|
||||
<div class="sm:col-span-2">
|
||||
<label for="u-nombre" class="mb-1 block text-sm font-medium text-slate-700">
|
||||
Nombre <span class="text-rose-500">*</span>
|
||||
</label>
|
||||
<input
|
||||
id="u-nombre"
|
||||
type="text"
|
||||
bind:value={formUsuario.Nombre}
|
||||
required
|
||||
maxlength="100"
|
||||
class="w-full rounded-lg border border-slate-300 px-3 py-2 text-sm focus:border-sky-400 focus:outline-none focus:ring-1 focus:ring-sky-300"
|
||||
placeholder="Nombre completo del usuario"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Usuario -->
|
||||
<div>
|
||||
<label for="u-usuario" class="mb-1 block text-sm font-medium text-slate-700">
|
||||
Usuario <span class="text-rose-500">*</span>
|
||||
</label>
|
||||
<input
|
||||
id="u-usuario"
|
||||
type="text"
|
||||
bind:value={formUsuario.Usuario}
|
||||
required
|
||||
maxlength="50"
|
||||
class="w-full rounded-lg border border-slate-300 px-3 py-2 text-sm font-mono focus:border-sky-400 focus:outline-none focus:ring-1 focus:ring-sky-300"
|
||||
placeholder="Nombre de usuario"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Contraseña -->
|
||||
<div>
|
||||
<label for="u-password" class="mb-1 block text-sm font-medium text-slate-700">
|
||||
Contraseña {#if !isEditingUsuario}<span class="text-rose-500">*</span>{/if}
|
||||
</label>
|
||||
<input
|
||||
id="u-password"
|
||||
type="password"
|
||||
bind:value={formUsuario.Password}
|
||||
required={!isEditingUsuario}
|
||||
maxlength="200"
|
||||
class="w-full rounded-lg border border-slate-300 px-3 py-2 text-sm focus:border-sky-400 focus:outline-none focus:ring-1 focus:ring-sky-300"
|
||||
placeholder={isEditingUsuario ? 'Dejar vacío para no cambiar' : 'Contraseña'}
|
||||
/>
|
||||
<p class="mt-1 text-[0.65rem] text-slate-400">
|
||||
Se almacena como SHA-512(contraseña + "soluciones") en mayúsculas.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- BD_Shelter -->
|
||||
<div class="sm:col-span-2">
|
||||
<label for="u-bdshelter" class="mb-1 block text-sm font-medium text-slate-700">
|
||||
BD Shelter
|
||||
</label>
|
||||
<input
|
||||
id="u-bdshelter"
|
||||
type="text"
|
||||
bind:value={formUsuario.BD_Shelter}
|
||||
maxlength="100"
|
||||
class="w-full rounded-lg border border-slate-300 px-3 py-2 text-sm focus:border-sky-400 focus:outline-none focus:ring-1 focus:ring-sky-300"
|
||||
placeholder="Nombre de la base de datos shelter (opcional)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 flex justify-end gap-2">
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-lg border border-slate-300 px-4 py-2 text-sm font-medium text-slate-700 hover:bg-slate-50"
|
||||
onclick={closeUsuarioModal}
|
||||
>
|
||||
Cancelar
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
class="rounded-lg bg-sky-600 px-4 py-2 text-sm font-medium text-white hover:bg-sky-700"
|
||||
>
|
||||
{isEditingUsuario ? 'Guardar Cambios' : 'Crear'}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
Reference in New Issue
Block a user