Compare commits

...

400 Commits

Author SHA1 Message Date
Nicolas Arenas 1fbec200ab Updates control
oggui-debian-package/pipeline/head This commit looks good Details
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-04-29 15:57:49 +02:00
Nicolas Arenas 253af06ad5 Updtes control description
testing/ogGui-multibranch/pipeline/head Something is wrong with the build of this commit Details
oggui-debian-package/pipeline/head Something is wrong with the build of this commit Details
2025-04-29 15:53:05 +02:00
Nicolas Arenas 5dc1677851 Fix typo
testing/ogGui-multibranch/pipeline/head This commit looks good Details
oggui-debian-package/pipeline/tag This commit looks good Details
oggui-debian-package/pipeline/head Something is wrong with the build of this commit Details
2025-04-22 12:13:42 +02:00
Nicolas Arenas 73a69f79c9 Preserve config files
oggui-debian-package/pipeline/head This commit looks good Details
oggui-debian-package/pipeline/tag This commit looks good Details
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-04-22 11:54:28 +02:00
Manuel Aranda Rosales 858a204036 Merge pull request 'develop' (#21) from develop into main
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
oggui-debian-package/pipeline/tag This commit looks good Details
oggui-debian-package/pipeline/head This commit looks good Details
Reviewed-on: #21
2025-04-16 14:36:15 +02:00
Manuel Aranda Rosales 844d3dc0f0 Merge branch 'develop' of ssh://ognproject.evlt.uma.es:21987/opengnsys/oggui into develop
testing/ogGui-multibranch/pipeline/head This commit looks good Details
testing/ogGui-multibranch/pipeline/pr-main Build started... Details
2025-04-16 14:33:12 +02:00
Manuel Aranda Rosales 23bf2b51ea refs #1924. Refresh status card view 2025-04-16 14:33:03 +02:00
Manuel Aranda Rosales e4e6a8907e Merge branch 'main' into develop 2025-04-16 14:32:39 +02:00
Manuel Aranda Rosales 0096daca42 refs #1924. Refresh status card view 2025-04-16 14:32:17 +02:00
Lucas Lara García de56a23a2b refs #1920 use all clients by default in execute command
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-04-16 11:34:47 +02:00
Manuel Aranda Rosales 3bae27d88e Merge pull request 'develop' (#20) from develop into main
testing/ogGui-multibranch/pipeline/head This commit looks good Details
Reviewed-on: #20
2025-04-16 11:29:37 +02:00
Manuel Aranda Rosales 265b4888c3 Merge branch 'main' into develop
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
testing/ogGui-multibranch/pipeline/pr-main There was a failure building this commit Details
2025-04-16 11:27:54 +02:00
Manuel Aranda Rosales ce1a06d51b Added changelog
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-04-16 11:23:11 +02:00
Manuel Aranda Rosales a0d833726d refs #1922. Show new fields in clients table
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-04-16 11:17:11 +02:00
Manuel Aranda Rosales c7919cf412 refs #1921. Crreate image version bug in selector
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-04-16 10:52:02 +02:00
Manuel Aranda Rosales cbe15bba4d refs #1907. Updated global status sync endpoints
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-04-16 10:50:27 +02:00
Manuel Aranda Rosales b6e8134810 Merge branch 'develop' of ssh://ognproject.evlt.uma.es:21987/opengnsys/oggui into develop 2025-04-15 18:37:50 +02:00
Manuel Aranda Rosales 6205f3ad2f refs #1919. Fixed bug when removed partition 2025-04-15 18:37:36 +02:00
Lucas Lara García 436267cfb9 refs #1883 Move 'Execute commands' to tree node actions
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-04-15 15:17:03 +02:00
Nicolas Arenas 2b3f7b6e34 Updated pre and post files
testing/ogGui-multibranch/pipeline/head Something is wrong with the build of this commit Details
oggui-debian-package/pipeline/tag This commit looks good Details
oggui-debian-package/pipeline/head This commit looks good Details
2025-04-11 13:44:29 +02:00
Lucas Lara García e9a00119aa Fix incorrect path for loading config.json in ConfigService
testing/ogGui-multibranch/pipeline/head Something is wrong with the build of this commit Details
2025-04-11 13:20:53 +02:00
Manuel Aranda Rosales b2d34a6880 Merge pull request 'develop' (#19) from develop into main
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
oggui-debian-package/pipeline/head This commit looks good Details
Reviewed-on: #19
2025-04-11 12:03:38 +02:00
Manuel Aranda Rosales cf5f2754c6 Merge branch 'develop' of ssh://ognproject.evlt.uma.es:21987/opengnsys/oggui into develop
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
testing/ogGui-multibranch/pipeline/pr-main Build started... Details
2025-04-11 11:59:01 +02:00
Manuel Aranda Rosales 80d9f5cb0f Solve conflics 2025-04-11 11:58:57 +02:00
Lucas Lara García 321a31eecb refs #1899 Refactor dialog close handling in ManageClient and ManageOrganizationalUnit components to return success status; update GroupsComponent to close menus accordingly.
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-04-11 11:56:48 +02:00
Manuel Aranda Rosales 636a66b93b Updated changelog
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-04-11 11:53:44 +02:00
Manuel Aranda Rosales 6a06e0a477 UX general improvements
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-04-11 11:45:32 +02:00
Manuel Aranda Rosales 411491091b refs #1864. Added ssh_port and user in imageRepository in UX. 2025-04-11 11:45:15 +02:00
Manuel Aranda Rosales 9e9d7b9873 refs #1902. Multi select checkbox logic
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-04-11 11:44:23 +02:00
Manuel Aranda Rosales dad3635f4f refs #1857. Rename image.
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-04-11 11:15:43 +02:00
Manuel Aranda Rosales 2958e05c98 refs #1901. Fixed manage-ou component. Added spinner
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-04-11 11:14:04 +02:00
Manuel Aranda Rosales c365ef2a14 refs #1794. Improvements, and new UX
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-04-11 10:46:49 +02:00
Manuel Aranda Rosales 1f7101c7a0 refs #1857. Rename image.
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-04-11 10:40:27 +02:00
Lucas Lara García 824e55102e refs #1900 Set selected node and fetch clients on menu click in GroupsComponent
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-04-11 09:44:39 +02:00
Nicolas Arenas 4758190b6d Publish main in nightly repo
oggui-debian-package/pipeline/head This commit looks good Details
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-04-10 16:51:29 +02:00
Nicolas Arenas 64fa13f36f Publish main in nightly repo
oggui-debian-package/pipeline/head Something is wrong with the build of this commit Details
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-04-10 16:50:26 +02:00
Lucas Lara García c7d6e41874 refs #1869 Add ShowGitImages component and integrate it into RepositoriesComponent
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-04-10 14:16:35 +02:00
Lucas Lara García 02fbf57384 refs #1867 Add MatDialog to LoginComponent for displaying GlobalStatusComponent on successful login
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-04-09 13:31:10 +02:00
Lucas Lara García bd0135b796 Fix component references in ShowMonoliticImagesComponent tests
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-04-09 12:31:12 +02:00
Lucas Lara García 390bc54213 Add ShowGitImages component and refactor show-images for show-monolitic-images
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-04-09 12:27:55 +02:00
Lucas Lara García 7659c09cd6 Fixed tests
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-04-09 10:08:59 +02:00
Manuel Aranda Rosales 380cf50080 refs #1858. Added description into deployImage selector
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-04-08 15:57:42 +02:00
Manuel Aranda Rosales eac4b0a948 refs #1858. Added description and change in repository component
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-04-08 15:57:03 +02:00
Manuel Aranda Rosales ebd448ce71 refs #1852. Updated mercure event and added new client status DISCONNECTED
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-04-08 15:55:35 +02:00
Lucas Lara García 672f0eade4 refs #1805 Enhance clients table layout and styling for improved readability and responsiveness
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-04-08 10:05:09 +02:00
Lucas Lara García 2b0d70dd58 refs #1799 Enhance responsiveness and layout for small screens in groups component
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-04-07 09:56:46 +02:00
Manuel Aranda Rosales 4f2bf0ec05 refs #1740. Run assistant updates. Save command
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-04-03 08:40:26 +02:00
Manuel Aranda Rosales 1c417e5f35 Merge branch 'develop' of ssh://ognproject.evlt.uma.es:21987/opengnsys/oggui into develop
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-04-03 06:55:55 +02:00
Manuel Aranda Rosales 4fed92505e refs #1797. Rename image. Crete image 2025-04-03 06:55:48 +02:00
Lucas Lara García 74f5f79206 refs #1798 Enhance header component for better small screen support and responsiveness
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-04-02 16:35:57 +02:00
Lucas Lara García b5a6bb0559 refs #1791 Refactor layout components for improved sidebar behavior and responsiveness
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-04-02 14:06:03 +02:00
Lucas Lara García 5baf4d8e3d Improve subnet synchronization with loading state and error handling
testing/ogGui-multibranch/pipeline/head Build queued... Details
2025-04-02 14:05:53 +02:00
Lucas Lara García 4d32540784 Fixed tests
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-04-02 09:51:24 +02:00
Manuel Aranda Rosales 9ef61500cb refs #1797. Rename image. Crete image
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-04-01 19:45:47 +02:00
Manuel Aranda Rosales 41f9521d4a refs #1793. Updted assistants UX
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-04-01 10:50:45 +02:00
Manuel Aranda Rosales 673fe5e7fd refs #1739. Rin script assistant 2025-04-01 10:49:16 +02:00
Manuel Aranda Rosales 945ae8ca0b Merge branch 'develop' of ssh://ognproject.evlt.uma.es:21987/opengnsys/oggui into develop 2025-03-31 14:37:52 +02:00
Lucas Lara García 11a4773570 Refactor error handling in Global Status component to prevent duplicate entries in errorRepositories
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-03-31 14:32:00 +02:00
Nicolas Arenas 49671ed686 Using shared libraries
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
oggui-debian-package/pipeline/head This commit looks good Details
oggui-debian-package/pipeline/tag This commit looks good Details
2025-03-27 08:35:57 +01:00
Manuel Aranda Rosales 34bf065de9 Merge branch 'main' of ssh://ognproject.evlt.uma.es:21987/opengnsys/oggui
oggui-debian-package/pipeline/tag This commit looks good Details
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-03-27 07:54:13 +01:00
Manuel Aranda Rosales 23d2b591f8 Updated changelog 2025-03-27 07:54:02 +01:00
Nicolas Arenas 6f16d07537 Updated Jenkinsfile to avoid colissions
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
oggui-debian-package/pipeline/tag This commit looks good Details
2025-03-27 07:36:18 +01:00
Manuel Aranda Rosales e33726bf6a Merge branch 'main' of ssh://ognproject.evlt.uma.es:21987/opengnsys/oggui
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
oggui-debian-package/pipeline/tag There was a failure building this commit Details
2025-03-26 21:49:53 +01:00
Manuel Aranda Rosales edfab0be94 Added constraint create client form (Mac). Updated groups tree UX 2025-03-26 21:49:42 +01:00
Nicolas Arenas da9fbc1fdb Update Jenkinsfile
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-03-26 13:24:26 +01:00
Nicolas Arenas c568a555a2 Improves oggui package
oggui-debian-package/pipeline/head This commit looks good Details
testing/ogGui-multibranch/pipeline/head This commit looks good Details
oggui-debian-package/pipeline/tag This commit looks good Details
2025-03-26 13:19:32 +01:00
Manuel Aranda Rosales 7bff91aa42 Merge pull request 'develop' (#18) from develop into main
testing/ogGui-multibranch/pipeline/head This commit looks good Details
oggui-debian-package/pipeline/tag This commit looks good Details
Reviewed-on: #18
2025-03-25 16:25:10 +01:00
Manuel Aranda Rosales 081da1efc6 Merge main, and updated improvements
testing/ogGui-multibranch/pipeline/pr-main There was a failure building this commit Details
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-03-25 16:23:30 +01:00
Manuel Aranda Rosales 7dc1f662e6 Merge main, and updated improvements 2025-03-25 15:53:41 +01:00
Manuel Aranda Rosales fd0778d096 Merge branch 'main' into develop 2025-03-25 15:40:43 +01:00
Manuel Aranda Rosales d430091d54 Merge branch 'develop' of ssh://ognproject.evlt.uma.es:21987/opengnsys/oggui into develop 2025-03-25 15:31:13 +01:00
Lucas Lara García 1d28e443a3 Refactor Global Status component to support individual error handling for repositories
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-03-25 15:30:26 +01:00
Manuel Aranda Rosales 8fdee4fc9b Merge branch 'develop' of ssh://ognproject.evlt.uma.es:21987/opengnsys/oggui into develop 2025-03-25 15:03:52 +01:00
Lucas Lara García 22d775e793 Refactor Global Status component to handle multiple error states for OgBoot, Dhcp, and Repositories
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-03-25 15:02:23 +01:00
Manuel Aranda Rosales 3cd61cfc8f Merge branch 'develop' of ssh://ognproject.evlt.uma.es:21987/opengnsys/oggui into develop 2025-03-25 14:21:29 +01:00
Lucas Lara García 1fbf494061 refs #1726 Add error handling and display for data loading in Global Status component 2025-03-25 13:49:52 +01:00
Lucas Lara García 4998463ba1 refs #1726 Add translation support for disk and RAM usage labels in Global Status component 2025-03-25 12:42:37 +01:00
Manuel Aranda Rosales 1f2c953509 Merge branch 'develop' of ssh://ognproject.evlt.uma.es:21987/opengnsys/oggui into develop 2025-03-25 12:21:56 +01:00
Lucas Lara García 2f47b2ec66 Refactor Global Status component to handle repository loading asynchronously and enhance CSS for RAM and process status display 2025-03-25 12:13:46 +01:00
Manuel Aranda Rosales 97b1ce15ab Merge branch 'develop' of ssh://ognproject.evlt.uma.es:21987/opengnsys/oggui into develop 2025-03-25 10:57:31 +01:00
Lucas Lara García 04ed52754c refs #1726 Add translation support for RAM and CPU usage labels in Global Status component 2025-03-25 10:56:36 +01:00
Manuel Aranda Rosales 3a5c4efecd Merge branch 'develop' of ssh://ognproject.evlt.uma.es:21987/opengnsys/oggui into develop 2025-03-24 13:46:05 +01:00
Lucas Lara García fdf33addc1 Fix repository status handling by using UUID instead of ID in Global Status component 2025-03-24 13:27:11 +01:00
Manuel Aranda Rosales b5510ffa13 Merge branch 'develop' of ssh://ognproject.evlt.uma.es:21987/opengnsys/oggui into develop 2025-03-24 13:07:47 +01:00
Lucas Lara García a0b3f0a4f7 Add repository status display with RAM, CPU usage, and process details in Global Status component 2025-03-24 13:05:21 +01:00
Manuel Aranda Rosales 5d54cf78ec Merge branch 'develop' of ssh://ognproject.evlt.uma.es:21987/opengnsys/oggui into develop 2025-03-24 12:38:13 +01:00
Lucas Lara García 3599a40ede Enhance Global Status component to display detailed repository status including RAM and CPU usage 2025-03-24 12:37:44 +01:00
Manuel Aranda Rosales da8451d405 Merge branch 'develop' of ssh://ognproject.evlt.uma.es:21987/opengnsys/oggui into develop 2025-03-24 12:28:46 +01:00
Lucas Lara García dcf9390870 Add repository loading functionality to Global Status component 2025-03-24 12:27:09 +01:00
Nicolas Arenas 84863cb0ac Updated oggui debian install
oggui-debian-package/pipeline/tag This commit looks good Details
2025-03-21 14:51:15 +01:00
Manuel Aranda Rosales 4e7c823094 Merge branch 'develop' of ssh://ognproject.evlt.uma.es:21987/opengnsys/oggui into develop 2025-03-21 12:30:03 +01:00
Lucas Lara García ebe14e0125 Add loading spinner to Global Status component during data fetch 2025-03-21 10:42:11 +01:00
Manuel Aranda Rosales 063ed4c310 Merge branch 'develop' of ssh://ognproject.evlt.uma.es:21987/opengnsys/oggui into develop 2025-03-20 14:18:24 +01:00
Lucas Lara García 44c4c60297 Enhance Global Status component layout with loading state handling and improved dialog dimensions
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-03-20 14:17:49 +01:00
Manuel Aranda Rosales c6b3deea41 Merge branch 'develop' of ssh://ognproject.evlt.uma.es:21987/opengnsys/oggui into develop 2025-03-20 14:17:37 +01:00
Lucas Lara García b1af49c641 Refactor Installed OgLives section in Global Status component for improved clarity
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-03-20 11:20:46 +01:00
Manuel Aranda Rosales b4bf4909fa Merge branch 'develop' of ssh://ognproject.evlt.uma.es:21987/opengnsys/oggui into develop 2025-03-20 11:12:00 +01:00
Manuel Aranda Rosales 1bf77166d6 Updated client view styles 2025-03-20 11:11:51 +01:00
Lucas Lara García 3d62161aaa refs #1725 Add support for DHCP and subnets in Global Status component
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-03-20 11:07:38 +01:00
Nicolas Arenas 2f968499f6 jenkins_upload_packages (#17)
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
oggui-debian-package/pipeline/tag This commit looks good Details
Reviewed-on: #17
2025-03-20 10:46:35 +01:00
Lucas Lara García 081f9a9846 Enhance unit tests for ConvertImageToVirtualComponent with improved mock services and dependencies
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-03-20 09:46:52 +01:00
Manuel Aranda Rosales 09d3420387 refs #1580. Fixed add multiple clients regexp
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-03-20 09:42:56 +01:00
Manuel Aranda Rosales 523b4bfc60 refs #1731. New UX integration. Convert image to virtual
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-03-19 15:40:52 +01:00
Lucas Lara García 7e133f2b2b Fix installedOgLives in global status components
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-03-19 14:02:04 +01:00
Lucas Lara García 07acbc5f87 Refactor Global Status component to improve disk usage data handling and ensure proper chart data structure
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-03-19 13:19:13 +01:00
Lucas Lara García 6c8ad465ea refs #1725 Use tab status component for dchp status in global component
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-03-19 13:02:25 +01:00
Lucas Lara García 50755bd1d5 refs #1727 Add StatusTab component to Global Status for enhanced service and disk usage display
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-03-19 13:01:23 +01:00
Nicolas Arenas d4eec6e5ff jenkins_upload_packages (#16)
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
Uploads packages

Reviewed-on: #16
refers #1313
2025-03-19 12:28:22 +01:00
Lucas Lara García 9f9d73644b Refactor tests for ManageRepository and ShowImages components to improve structure and add necessary imports
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-03-19 08:55:14 +01:00
Manuel Aranda Rosales 335f4683fc Refactor Images/Repositories modules
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-03-18 17:09:25 +01:00
Lucas Lara García a9dd983f53 Refactor Global Status component to remove unnecessary console logs and clean up disk usage chart data handling
testing/ogGui-multibranch/pipeline/head Something is wrong with the build of this commit Details
2025-03-18 14:31:38 +01:00
Lucas Lara García bb31acb4cc Fix service status assignment and update disk usage chart data in Global Status component
testing/ogGui-multibranch/pipeline/head Something is wrong with the build of this commit Details
2025-03-18 14:27:41 +01:00
Lucas Lara García f7dcafbd52 refs #1724 OgBoot status added to global status component
testing/ogGui-multibranch/pipeline/head Something is wrong with the build of this commit Details
2025-03-18 13:55:40 +01:00
Nicolas Arenas 06f969f43f jenkins-deb (#15)
testing/ogGui-multibranch/pipeline/head This commit looks good Details
oggui-debian-package/pipeline/head This commit looks good Details
Include Jenkinsfile in main branch

Reviewed-on: #15

refs #1715
2025-03-18 10:56:19 +01:00
Lucas Lara García f004de1ebd Add check for ogBootServicesStatus in getServices method to prevent errors
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-03-18 10:22:50 +01:00
Lucas Lara García a125252be9 Fix loading state handling in Global Status component template
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-03-18 10:20:00 +01:00
Lucas Lara García e8e68649cd refs #1705 Enhance Global Status component to reload OgBoot status on tab change
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-03-18 10:13:30 +01:00
Lucas Lara García 984e4fe4db refs #1705 Add Global Status component with styling and integration in header
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-03-18 09:56:36 +01:00
Manuel Aranda Rosales d1af610e93 Refactor transferImages
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-03-18 09:26:43 +01:00
Manuel Aranda Rosales fd612b1a66 Test refactor transferImage
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-03-17 09:05:40 +01:00
Nicolas Arenas 251708e21e reates debian folder to create debian packages , refs #1708
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
Reviewed-on: #14
2025-03-13 17:04:07 +01:00
Manuel Aranda Rosales 82eea78c30 Merge branch 'develop' of ssh://ognproject.evlt.uma.es:21987/opengnsys/oggui into develop
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-03-13 14:47:29 +01:00
Manuel Aranda Rosales 083b46a94d refs #1702. Updated ogLive sync. Deleted wrong or uninstalled oglives 2025-03-13 14:44:11 +01:00
Lucas Lara García 8312132e1f refs #1684 Remove localization support and update translations for software management
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-03-13 14:25:59 +01:00
Lucas Lara García e230b3b41d refs #1700 Add mock ConfigService to component tests for improved isolation
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-03-13 12:00:08 +01:00
Manuel Aranda Rosales 44199881cc refs #1671. Udpated deploy method type. Added udpcast-direct'
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-03-13 11:14:33 +01:00
Manuel Aranda Rosales a5617ad012 Merge branch 'develop' of ssh://ognproject.evlt.uma.es:21987/opengnsys/oggui into develop
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-03-12 17:35:48 +01:00
Manuel Aranda Rosales bd14cbcfd0 refs #1693. Convert Image. Webhook updated. 2025-03-12 17:35:40 +01:00
Lucas Lara García dc99c2d2a7 refs #1690 Add mercureUrl to config and refactor services to use ConfigService for API URLs
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-03-12 14:23:47 +01:00
Lucas Lara García 40385bc73c refs #1690 Add ConfigService integration to EnvVars and Roles components and fixed tests.
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-03-12 10:29:54 +01:00
Manuel Aranda Rosales fda7d9b154 Updated changelog
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-03-12 09:54:44 +01:00
Manuel Aranda Rosales 294e85508b Merge branch 'develop' of ssh://ognproject.evlt.uma.es:21987/opengnsys/oggui into develop
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-03-11 17:10:11 +01:00
Manuel Aranda Rosales 16c367e770 refs #1693. Convert Image 2025-03-11 17:10:04 +01:00
Lucas Lara García 68d5f7f006 refs #1688 Add ConfigService for loading application configuration at startup
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-03-11 13:46:56 +01:00
Manuel Aranda Rosales d2b3c8f772 Merge branch 'develop' of ssh://ognproject.evlt.uma.es:21987/opengnsys/oggui into develop
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-03-11 11:12:31 +01:00
Manuel Aranda Rosales 769d55a624 Merge branch 'main' into develop 2025-03-11 11:12:12 +01:00
Manuel Aranda Rosales 7a7c3e8e0d Added Mercure_URL var env
testing/ogGui-multibranch/pipeline/head This commit looks good Details
testing/ogGui-multibranch/pipeline/tag This commit looks good Details
2025-03-11 11:10:37 +01:00
Lucas Lara García a6806c5fa7 Use ngx-translate exclusively instead of i18n
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-03-10 17:56:11 +01:00
Manuel Aranda Rosales 70319d718f Merge branch 'develop' of ssh://ognproject.evlt.uma.es:21987/opengnsys/oggui into develop
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-03-10 16:21:43 +01:00
Manuel Aranda Rosales adc11df008 Improvements repository UX 2025-03-10 16:21:03 +01:00
Lucas Lara García 8e10d135e1 refs #1638. Refactor client details layout and improve sync status display
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-03-07 14:41:11 +01:00
Lucas Lara García b0d24b4799 Update client name styling for improved readability and layout
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-03-07 10:35:06 +01:00
Lucas Lara García 9ab68cc6e2 refs #1654. Refactor create image component for improved UX and loading state management
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-03-07 09:27:37 +01:00
Manuel Aranda Rosales 67ebc5b926 Merge pull request 'develop' (#13) from develop into main
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
testing/ogGui-multibranch/pipeline/tag This commit looks good Details
Reviewed-on: #13
2025-03-06 08:12:20 +01:00
Manuel Aranda Rosales 83c3b3caed Added changeLol 0.9.0
testing/ogGui-multibranch/pipeline/pr-main There was a failure building this commit Details
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-03-06 08:09:09 +01:00
Manuel Aranda Rosales 754dc8ed15 UX and CSS improvements
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-03-05 17:33:11 +01:00
Manuel Aranda Rosales 2b69ef3bd6 Added getImage api call
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-03-05 17:32:45 +01:00
Manuel Aranda Rosales b23d1727e8 refs #1645. Cancel deploy image button
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-03-05 17:31:56 +01:00
Lucas Lara García c568d5a8e7 refs #1641. Implement debounced client filtering in groups component
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-03-05 15:45:23 +01:00
Lucas Lara García 5907404f77 refs #1639. Enhance user modal for add/edit functionality and improve password change dialog
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-03-05 13:25:22 +01:00
Manuel Aranda Rosales 9b67d6ef43 Merge branch 'develop' of ssh://ognproject.evlt.uma.es:21987/opengnsys/oggui into develop
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-03-05 09:02:17 +01:00
Manuel Aranda Rosales 9c004441a8 Chagned filter groups and user improvements 2025-03-05 09:02:07 +01:00
Lucas Lara García bc8ba38f01 refs #1637 refactor: remove unused client edit and create components; add manage client component
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-03-03 16:15:55 +01:00
Lucas Lara García 858db45e0d refs #1619. Style: enhance cards view layout and paginator integration in groups component
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-03-03 11:53:20 +01:00
Lucas Lara García 42b10c63c1 refactor: update paginator settings and improve page change handling in groups component
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-02-28 13:58:55 +01:00
Lucas Lara García 1d78965c92 Merge branch 'develop' of ssh://ognproject.evlt.uma.es:21987/opengnsys/oggui into develop
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-02-28 11:26:23 +01:00
Lucas Lara García b7e0595867 style: clean up and optimize CSS for groups component; enhance HTML structure and improve responsiveness 2025-02-28 11:25:33 +01:00
Manuel Aranda Rosales a6356e1457 Updated groups paginator
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-02-28 10:58:33 +01:00
Manuel Aranda Rosales d1bf12dd6a Merge branch 'develop' of ssh://ognproject.evlt.uma.es:21987/opengnsys/oggui into develop
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-02-28 10:57:21 +01:00
Manuel Aranda Rosales a296706757 refs #1567. New subnet field: 'dns' 2025-02-28 09:21:08 +01:00
Lucas Lara García 7d2c0e4c29 test: update mock HTTP response in login component tests
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-02-28 09:15:39 +01:00
Manuel Aranda Rosales 20452c83eb refs #1563. New field groupsView in user
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-02-27 16:40:05 +01:00
Lucas Lara García 4ef99a7c98 Refactor: improve test setup by adding mock services for dialog and HTTP client
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-02-27 16:15:32 +01:00
Lucas Lara García 6fd741e7b4 refs #1619.Refactor: update layout dimensions and styles for groups component
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-02-27 16:12:13 +01:00
Manuel Aranda Rosales b29a3f58f1 refs #1558. Add client/subnet new UX modal
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-02-27 11:05:26 +01:00
Lucas Lara García bae2069661 refs #1619. In Progress. Refactor: update layout dimensions and styles for groups component
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-02-27 10:35:20 +01:00
Lucas Lara García 1ae55c3254 Refactor: simplify client main view by removing unused buttons and updating layout
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-02-26 13:14:32 +01:00
Lucas Lara García 7e06e60598 refs #1617. Fix: handle undefined networkSettings in ClientViewComponent
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-02-26 12:20:21 +01:00
Lucas Lara García 900cb423b3 Remove unused CreatePxeBootFileComponent from app module
testing/ogGui-multibranch/pipeline/head Something is wrong with the build of this commit Details
2025-02-26 11:45:17 +01:00
Lucas Lara García dddfcd4e60 Remove AcctionsModal component and associated files
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-02-26 11:33:21 +01:00
Lucas Lara García c441edead4 Tests fixed
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-02-26 10:05:34 +01:00
Lucas Lara García 114d919141 Merge branch 'develop' of ssh://ognproject.evlt.uma.es:21987/opengnsys/oggui into develop
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-02-26 09:51:32 +01:00
Lucas Lara García 1962beaf8a refs #1608 and #1561 Fix selection in both views. Add multi-selection in cards view. 2025-02-26 09:34:29 +01:00
Manuel Aranda Rosales 0d888dec62 refs #1558. Add client/subnet new UX modal
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-02-26 08:43:12 +01:00
Manuel Aranda Rosales a0bc697edb Merge branch 'develop' of ssh://ognproject.evlt.uma.es:21987/opengnsys/oggui into develop
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-02-26 08:42:20 +01:00
Manuel Aranda Rosales 9c121a3027 refs #1559. Refactor ogdhcp UX 2025-02-26 08:42:13 +01:00
Nicolas Arenas f0e3a34f00 Update angular.json
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-02-25 13:24:32 +01:00
root e7d99be6c2 First version of debian packge for oggui 2025-02-25 13:24:32 +01:00
Nicolas Arenas 27bdcbf98b Update angular.json to fix locale issue 2025-02-25 13:24:32 +01:00
Lucas Lara García 1c4343bb48 refs #1549. Add loading state management to ManageOrganizationalUnitComponent for improved user experience during data fetching
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-02-24 16:13:29 +01:00
Lucas Lara García f1ddf20d0c refs #1550. Improve error handling in ManageOrganizationalUnitComponent by parsing and cleaning error messages for better user feedback
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-02-24 12:50:27 +01:00
Nicolas Arenas 56966fd767 Increase maximun size error
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-02-21 11:46:13 +01:00
Manuel Aranda Rosales 2d9ccd01b4 refs #1575. Mercure and notifications
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-02-21 11:22:01 +01:00
Manuel Aranda Rosales 09f83f6af7 refs #1604. Backup image UX
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-02-21 10:57:24 +01:00
Lucas Lara García 0350e0110c refs #1551 Apply global button styles to all components 2025-02-19 20:25:03 +01:00
Lucas Lara García 37aff33b11 refs #1551 Apply global button styles to all components 2025-02-19 16:26:12 +01:00
Lucas Lara García b4a389e5bd refs #1529. Refactor task logs component: enhance client fetching logic with forkJoin for better performance 2025-02-18 14:16:52 +01:00
Lucas Lara García 8ed2d62a81 Add MatCheckboxModule to ManageOrganizationalUnitComponent tests
testing/ogGui-multibranch/pipeline/head This commit looks good Details
testing/ogGui-multibranch/pipeline/tag This commit looks good Details
2025-02-17 15:06:38 +01:00
Manuel Aranda Rosales b0ede36d3b refs #1523. Hierarchy networkSettings. Resolve conflicts
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
testing/ogGui-multibranch/pipeline/tag There was a failure building this commit Details
2025-02-17 14:42:49 +01:00
Lucas Lara García 024914d993 refs #1520 and #1524. Unify edit and create organizational unit into one single component. Refactor form structure.
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-02-17 14:05:58 +01:00
Manuel Aranda Rosales 952938a253 Some improvements. Added new filters
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-02-17 09:16:43 +01:00
Lucas Lara García 7698be2bca refs #1518. Add progress bar to task logs component and update styles for better UX
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-02-14 12:47:06 +01:00
Lucas Lara García ee37287f9e refs #1477. Refactor repository view styles and structure for improved layout and readability
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-02-14 10:08:09 +01:00
Lucas Lara García 3c9458d15a Remove unnecessary tooltip binding from user welcome message in sidebar
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-02-14 10:06:52 +01:00
Manuel Aranda Rosales c6a81a8152 Merge branch 'develop' of ssh://ognproject.evlt.uma.es:21987/opengnsys/oggui into develop
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-02-13 15:43:55 +01:00
Manuel Aranda Rosales 8d6e4615c7 refs #1516. Changed form and global import 2025-02-13 15:43:47 +01:00
Lucas Lara García 9af35975f5 Enhance ShowOrganizationalUnit component to support 'clients-group' type and add type translations
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-02-13 11:22:40 +01:00
Manuel Aranda Rosales 12f7bad764 refs #1472. Changes in images and imageRepo
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-02-13 08:36:56 +01:00
Manuel Aranda Rosales 97556caa95 refs #1472. Changes in images and imageRepo
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-02-13 07:56:00 +01:00
Manuel Aranda Rosales 805c0026bc refs #1472. Changes in images and imageRepo
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-02-13 07:45:39 +01:00
Manuel Aranda Rosales 9669202dbd refs #1472. Changes in images and imageRepo
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-02-12 17:13:25 +01:00
Lucas Lara García c8c1bdd0bd refs #1505. Refactor Groups component to use arrayClients for filtering
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-02-12 14:10:00 +01:00
Lucas Lara García bada1762aa refs #1485 Refactor components to replace mat-spinner with loading component for improved loading indication
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-02-11 13:07:01 +01:00
Lucas Lara García 218816d1f1 refs #1477 Refactor Task-logs component for improved layout and styling
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-02-11 12:40:56 +01:00
Lucas Lara García 9e071814e9 refs #1477 Refactor Env-Vars, Roles and Users components for improved layout and styling
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-02-11 12:32:14 +01:00
Lucas Lara García 4437acdac2 refs #1477 Refactor Images, Menus, Repositories, Software and OperativeSystem components for improved layout and styling
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-02-11 10:32:03 +01:00
Lucas Lara García 7b3e1534eb refs #1477. Refactor Calendar component for improved layout and replace mat-spinner with loading component
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-02-10 17:02:12 +01:00
Lucas Lara García 828f711549 refs #1477 Refactor Ogboot component for improved layout and styling
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-02-10 16:49:11 +01:00
Manuel Aranda Rosales 75357b82c8 refs #1472. Changes in images and imageRepo
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-02-10 13:59:14 +01:00
Lucas Lara García 3e64ae03ba refs #1477 Refactor CSS styles for improved layout and consistency in OgDhcp component
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-02-10 13:15:24 +01:00
Lucas Lara García 7490456b71 refs #1477 Refactor Command component for improved layout and styling
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-02-10 12:52:26 +01:00
Lucas Lara García ecd3980f61 refs #1475. Update delete function. Add actions menu to cards view.
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-02-07 14:03:18 +01:00
Manuel Aranda Rosales 2b21a621ae refs #1480. Fixed test
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-02-07 13:58:07 +01:00
Manuel Aranda Rosales e2056524cf refs #1480. Fixed bug in deploy form
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-02-07 12:48:04 +01:00
Manuel Aranda Rosales 9401448d0c refs #1479. Changed delete-permanent/delete-trash order
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-02-07 12:17:47 +01:00
Manuel Aranda Rosales 112eb23195 Merge branch 'develop' of ssh://ognproject.evlt.uma.es:21987/opengnsys/oggui into develop
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-02-07 12:02:45 +01:00
Manuel Aranda Rosales c7d33ff502 refs #1473. Changes in ogRepo. Import 2025-02-07 12:02:40 +01:00
Lucas Lara García 3c1663aeb1 refs #1457 Pass node ID to refreshData after client update
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-02-06 11:31:43 +01:00
Manuel Aranda Rosales 7e174c9617 refs #1456. Added checkbox in card view
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-02-05 13:19:09 +01:00
Manuel Aranda Rosales 17c22c8714 Merge branch 'develop' of ssh://ognproject.evlt.uma.es:21987/opengnsys/oggui into develop 2025-02-05 13:05:04 +01:00
Lucas Lara García c139bd6b05 Fix GroupsComponent: Set initialLoading to false when no node is selected
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-02-05 13:03:05 +01:00
Manuel Aranda Rosales 1299a0607f Merge branch 'main' into develop 2025-02-05 12:54:00 +01:00
Lucas Lara García 3bd923cbbb refs #1446 Refactor GroupsComponent: Improve loading state management
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-02-05 12:24:06 +01:00
Manuel Aranda Rosales 4d757c4379 Merge pull request 'develop' (#12) from develop into main
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
testing/ogGui-multibranch/pipeline/tag This commit looks good Details
Reviewed-on: #12
2025-02-04 09:53:49 +01:00
Manuel Aranda Rosales d9f3fd6203 Merge branch 'main' into develop
testing/ogGui-multibranch/pipeline/pr-main Build started... Details
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-02-04 09:52:40 +01:00
Manuel Aranda Rosales 0b790543b9 Fixed test
testing/ogGui-multibranch/pipeline/head This commit looks good Details
testing/ogGui-multibranch/pipeline/pr-main Build queued... Details
2025-02-04 09:18:00 +01:00
Manuel Aranda Rosales 24d81ac533 Fixed test
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-02-04 08:12:32 +01:00
Manuel Aranda Rosales 22ed91551e Fixed test
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-02-04 08:03:05 +01:00
Manuel Aranda Rosales ab5a4448dc Added global loading
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-02-04 07:57:45 +01:00
Manuel Aranda Rosales fe72fcb5fb Added isGlobal property in Image
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-02-03 13:10:39 +01:00
Lucas Lara García fe89e6dc37 refs #1438 Increase paginator options to include 50 items per page in groups component
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-02-03 12:42:08 +01:00
Lucas Lara García 6e739270de refs #1436 Fix create-multiple-client's toast
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-02-03 11:48:50 +01:00
Manuel Aranda Rosales 45bf13e63a Updated repo/images UX
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-02-03 11:14:19 +01:00
Lucas Lara García b81db79237 refs #1419 and #1420. Fix OU's path in clients' and OUs' form. Remove stepper from forms.
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-01-31 14:09:14 +01:00
Lucas Lara García f092754464 refs #1423 Allow network settings step for both classroom and clients-group types in edit organizational unit form
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-01-31 10:31:20 +01:00
Lucas Lara García 1494ed50c8 refs #1423 Show network settings step only for classroom type in edit organizational unit form
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-01-31 10:18:38 +01:00
Manuel Aranda Rosales bdbb16d3fd ogRepo new endpoints. Export/Import image
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-01-31 09:10:02 +01:00
Lucas Lara García b2bf6b8c96 refs #1416 Add translations for organizational unit types in edit form
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-01-30 12:16:08 +01:00
Lucas Lara García e3e00f3765 refs #1417 Change submit button from add to edit when editting OU.
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-01-30 12:09:14 +01:00
Manuel Aranda Rosales b85ed5ad5c Changes in torrent p2p
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-01-29 17:40:32 +01:00
Lucas Lara García 4e45f1b552 Add tooltip displaying client path in client list view
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-01-29 14:19:01 +01:00
Lucas Lara García e119ce867d Fix selected node identification in tree view component
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-01-28 16:18:36 +01:00
Lucas Lara García ab5cf647a1 Add clear search functionality for tree and client filters
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-01-28 14:26:26 +01:00
Manuel Aranda Rosales 22ee53e9db Fixed test
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-01-28 07:43:34 +01:00
Manuel Aranda Rosales ab2309c958 refs #1354. Added multiple commands logic. Updated endpoints
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-01-27 16:50:45 +01:00
Manuel Aranda Rosales b516103008 refs #1354. Added multiple commands logic. Updated endpoints
testing/ogGui-multibranch/pipeline/head Something is wrong with the build of this commit Details
2025-01-27 15:57:32 +01:00
Lucas Lara García 3a7bff9e74 refs #1373 Add search functionality for organizational units and update translations and tests
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-01-27 14:23:28 +01:00
Lucas Lara García 37460cb01d refs #1376 Extend adding multiple client option to OU's tree
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-01-24 14:24:48 +01:00
Lucas Lara García c063e0f50f refs #1375 Remove TreeViewComponent and associated files from groups component.
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-01-23 14:27:44 +01:00
Lucas Lara García 8d66494458 refs #1373 Refactor styles and filters in groups component.
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-01-23 13:49:43 +01:00
Lucas Lara García 46dd71889d Implement data refresh functionality for organizational units and clients
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-01-22 10:35:29 +01:00
Lucas Lara García 6f226d6da6 Update padding and margin in group component styles; adjust icon placement in no clients message
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-01-20 13:51:09 +01:00
Lucas Lara García 6e83a9f827 Refactor group component styles and update success messages for unit creation
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-01-16 13:02:52 +01:00
Manuel Aranda Rosales 898444f1af Merge pull request 'Improvements' (#11) from develop into main
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
testing/ogGui-multibranch/pipeline/tag This commit looks good Details
Reviewed-on: #11
2025-01-15 15:54:24 +01:00
Manuel Aranda Rosales cf2a693281 Improvements
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
testing/ogGui-multibranch/pipeline/pr-main Build started... Details
2025-01-15 15:51:40 +01:00
Manuel Aranda Rosales bed68408d8 Merge pull request 'develop' (#10) from develop into main
testing/ogGui-multibranch/pipeline/head This commit looks good Details
testing/ogGui-multibranch/pipeline/tag This commit looks good Details
Reviewed-on: #10
2025-01-14 09:48:54 +01:00
Manuel Aranda Rosales 1949186e2a refs #1314. Fixed tree groups bugs.
testing/ogGui-multibranch/pipeline/head This commit looks good Details
testing/ogGui-multibranch/pipeline/pr-main Build queued... Details
2025-01-14 09:19:31 +01:00
Manuel Aranda Rosales c13db7f36e refs #1314. Fixed tree groups bugs.
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-01-13 15:08:00 +01:00
Lucas Lara García 5661ec68ca Add MatTreeModule to GroupsComponent tests
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-01-13 13:31:31 +01:00
Lucas Lara García b16670939e Add custom theme and update menu references in groups component
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-01-13 11:48:33 +01:00
Lucas Lara García 634b7794a0 Merge branch 'develop' of ssh://ognproject.evlt.uma.es:21987/opengnsys/oggui into develop
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-01-13 11:33:01 +01:00
Lucas Lara García 87f436624c refs #1321 Unit's view discarded. Tree view becomes main view. 2025-01-13 11:29:16 +01:00
Manuel Aranda Rosales ed23c4bd23 Fixed test
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-01-10 11:42:38 +01:00
Manuel Aranda Rosales b74f129dbe refs #1320. Splice create client logic.
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-01-10 08:26:52 +01:00
Lucas Lara García d689583b56 refs #1318 Add back button in client details view
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-01-08 13:15:51 +01:00
Lucas Lara García d469b93a96 refs #1318 No clients view added and minor upgrades.
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-01-08 12:32:59 +01:00
Manuel Aranda Rosales e9baede635 refs #1319. Default menu logic
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-01-07 17:52:04 +01:00
Lucas Lara García fd5f4b5f41 refs #1314 Selected node title updates correctly. Groups title styles improved.
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2024-12-26 11:50:30 +01:00
Manuel Aranda Rosales 5b18c24115 Merge branch 'develop' of ssh://ognproject.evlt.uma.es:21987/opengnsys/oggui into develop
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2024-12-20 12:03:40 +01:00
Manuel Aranda Rosales 013536bc9e refs #1282. Adapted ogCore filter. Refactor unused and wrong code 2024-12-20 12:03:30 +01:00
Alvaro Puente Mella 71db1ad9b6 Merge branch 'develop' of ssh://ognproject.evlt.uma.es:21987/opengnsys/oggui into develop
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2024-12-20 11:48:01 +01:00
Alvaro Puente Mella 98491bc3c8 Refactor group component and remove unused code 2024-12-20 11:47:58 +01:00
Manuel Aranda Rosales 369e09ee86 refs #1288. Added Menu CRUD
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2024-12-20 08:02:10 +01:00
Alvaro Puente Mella 4c45e51493 refs #1283 Fix mat-tree close on object refresh
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2024-12-17 14:04:19 +01:00
Alvaro Puente Mella f15920a016 Fix create menu and menus test
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2024-12-16 16:19:12 +01:00
Manuel Aranda Rosales 1c791df45e refs #1288. Added Menu CRUD
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2024-12-16 15:25:33 +01:00
Manuel Aranda Rosales f51b255209 refs #1288. Added Menu CRUD
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2024-12-16 11:02:21 +01:00
Manuel Aranda Rosales d9d8eddc1d Groups improvements
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2024-12-16 08:25:27 +01:00
Alvaro Puente Mella 101f5dd5e4 refs #1234 añadido repositorio en crear cliente unico
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2024-12-12 09:28:36 +01:00
Alvaro Puente Mella 44fcb23717 refs #1233 reajuste ayuda contextual 2024-12-12 09:28:36 +01:00
Manuel Aranda Rosales 2631469b71 ogLive improvements. Groups UX
testing/ogGui-multibranch/pipeline/head This commit is unstable Details
2024-12-11 17:04:27 +01:00
Manuel Aranda Rosales 0550c1b25d refs #1089. Partition assistant. Added back button 2024-12-11 17:04:10 +01:00
Manuel Aranda Rosales a9afb951e2 refs #1090. CreateImage. Add back button 2024-12-11 17:03:26 +01:00
Manuel Aranda Rosales 5bc5a9d23a refs #1091. DeployImage Multicast mode 2024-12-11 17:03:02 +01:00
Manuel Aranda Rosales 016e5a821a refs #1090. PartitionAssistant changes
testing/ogGui-multibranch/pipeline/head This commit is unstable Details
2024-12-11 08:46:17 +01:00
Alvaro Puente Mella 4f905778f8 Merge branch 'main' of ssh://ognproject.evlt.uma.es:21987/opengnsys/oggui
testing/ogGui-multibranch/pipeline/head This commit is unstable Details
testing/ogGui-multibranch/pipeline/tag This commit is unstable Details
2024-12-05 15:22:59 +01:00
Alvaro Puente Mella faee839580 Merge branch 'develop' 2024-12-05 15:20:19 +01:00
Alvaro Puente Mella 5980f469ec Refactor groups component to update filter option
testing/ogGui-multibranch/pipeline/head This commit is unstable Details
2024-12-05 15:18:19 +01:00
Alvaro Puente Mella 7dcb6ecb1c Refactor groups component to update filter option value and improve client status display 2024-12-05 15:18:16 +01:00
Alvaro Puente Mella c7d6477bf6 Refactor groups component to update filter option value and add command execution functionality 2024-12-05 15:00:33 +01:00
Manuel Aranda Rosales 13cea10946 Added router in subnet. Advanced bootfile changes, and partition assistant updates
testing/ogGui-multibranch/pipeline/head This commit is unstable Details
2024-12-05 14:56:02 +01:00
Alvaro Puente Mella 81e20ec9f9 Refactor translation keys in HTML templates
testing/ogGui-multibranch/pipeline/head This commit is unstable Details
2024-12-05 14:16:23 +01:00
Alvaro Puente Mella 3a7eaddcce Merge branch 'develop' of ssh://ognproject.evlt.uma.es:21987/opengnsys/oggui into develop 2024-12-05 13:17:48 +01:00
Alvaro Puente Mella e9b4411ea7 Refactor groups component to update filter option value and add sync functionality 2024-12-05 13:09:22 +01:00
Manuel Aranda Rosales e3cf6a85d0 Added router in subnet. Advanced bootfile changes, and partition assistant updates
testing/ogGui-multibranch/pipeline/head This commit is unstable Details
2024-12-05 12:50:15 +01:00
Alvaro Puente Mella aecc16c332 Refactor groups component to update filter option value
testing/ogGui-multibranch/pipeline/head This commit is unstable Details
2024-12-04 15:27:43 +01:00
Alvaro Puente Mella aa9b82cda5 Refactor groups component to add sync functionality
testing/ogGui-multibranch/pipeline/head This commit is unstable Details
2024-12-03 12:33:40 +01:00
Manuel Aranda Rosales 2eb08d0198 Merge branch 'develop' of ssh://ognproject.evlt.uma.es:21987/opengnsys/oggui into develop
testing/ogGui-multibranch/pipeline/head This commit is unstable Details
2024-12-03 11:25:45 +01:00
Manuel Aranda Rosales 7e9e5c638c refs #917. Partition assistant 2024-12-03 11:25:42 +01:00
Alvaro Puente Mella 00d9fdf536 Refactor groups component tests
testing/ogGui-multibranch/pipeline/head This commit is unstable Details
2024-12-03 10:37:45 +01:00
Alvaro Puente Mella 252f961b73 Refactor groups view and update styles 2024-12-03 10:14:15 +01:00
Alvaro Puente Mella 21078a8ab0 Refactor groups component 2024-12-01 23:03:01 +01:00
Alvaro Puente Mella a6797f8f77 Refactor groups component 2024-12-01 20:00:16 +01:00
Alvaro Puente Mella 398e0ffa57 Refactor groups component 2024-12-01 00:55:38 +01:00
Alvaro Puente Mella d8dad3b14b Refactor groups 2024-11-28 17:32:19 +01:00
Manuel Aranda Rosales 9f40cd9426 Merge branch 'develop'
testing/ogGui-multibranch/pipeline/head This commit is unstable Details
testing/ogGui-multibranch/pipeline/tag This commit is unstable Details
2024-11-28 12:49:13 +01:00
Manuel Aranda Rosales 4d8c1f0991 Updated client view
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
testing/ogGui-multibranch/pipeline/pr-main This commit is unstable Details
2024-11-27 16:49:49 +01:00
Manuel Aranda Rosales ad0d4c1a40 Merge pull request 'develop' (#7) from develop into main
testing/ogGui-multibranch/pipeline/head This commit is unstable Details
testing/ogGui-multibranch/pipeline/tag This commit is unstable Details
Reviewed-on: #7
2024-11-26 12:45:08 +01:00
Manuel Aranda Rosales 5ea6242ae0 Merge branch 'develop' of ssh://ognproject.evlt.uma.es:21987/opengnsys/oggui into develop
testing/ogGui-multibranch/pipeline/head This commit is unstable Details
testing/ogGui-multibranch/pipeline/pr-main Build started... Details
2024-11-26 10:58:22 +01:00
Manuel Aranda Rosales 794682ec34 Updated pxe UX 2024-11-26 10:58:17 +01:00
Alvaro Puente Mella 1540156ffb Merge branch 'develop' for 0.6.2 release
testing/ogGui-multibranch/pipeline/head This commit is unstable Details
testing/ogGui-multibranch/pipeline/tag This commit is unstable Details
2024-11-22 13:21:03 +01:00
Alvaro Puente Mella 936e7b1392 Update changelog for version 0.6.1
testing/ogGui-multibranch/pipeline/head This commit is unstable Details
2024-11-22 13:19:51 +01:00
Alvaro Puente Mella 04b631ff08 Fix some test
testing/ogGui-multibranch/pipeline/head This commit is unstable Details
2024-11-22 13:15:46 +01:00
Manuel Aranda Rosales e943da7111 Big updates dhcp subnets
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2024-11-22 12:26:29 +01:00
Manuel Aranda Rosales a4ec3d00dc Merge branch 'develop' of ssh://ognproject.evlt.uma.es:21987/opengnsys/oggui into develop
testing/ogGui-multibranch/pipeline/head This commit is unstable Details
2024-11-22 08:30:45 +01:00
Manuel Aranda Rosales ddcf5fcc8c Updated client view 2024-11-22 08:30:38 +01:00
Alvaro Puente Mella a3a6bb0c23 Update changelog for version 0.6.0
testing/ogGui-multibranch/pipeline/head This commit is unstable Details
2024-11-21 12:43:07 +01:00
Alvaro Puente Mella 6b35bbe9d9 Fix some test for coverage and changelog
testing/ogGui-multibranch/pipeline/head This commit is unstable Details
2024-11-21 12:29:39 +01:00
Manuel Aranda Rosales 5962c2e051 Updated main groups classroom UX
testing/ogGui-multibranch/pipeline/head This commit is unstable Details
2024-11-21 12:17:06 +01:00
Manuel Aranda Rosales d1195239ad Updated main groups classroom UX
testing/ogGui-multibranch/pipeline/head This commit is unstable Details
2024-11-21 11:44:34 +01:00
Manuel Aranda Rosales bf4cf41194 Improvements in client view
testing/ogGui-multibranch/pipeline/tag This commit is unstable Details
testing/ogGui-multibranch/pipeline/head This commit is unstable Details
2024-11-20 18:41:25 +01:00
Manuel Aranda Rosales c6011bba15 Improvements in client view 2024-11-20 18:39:54 +01:00
Nicolas Arenas 2b2178a2eb Fix other typo
testing/ogGui-multibranch/pipeline/head This commit is unstable Details
testing/ogGui-multibranch/pipeline/tag This commit is unstable Details
2024-11-20 18:15:57 +01:00
Nicolas Arenas 17bd727eb3 Removes worng symbol
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2024-11-20 18:14:23 +01:00
Nicolas Arenas 60f425f873 Build tag id
testing/ogGui-multibranch/pipeline/tag There was a failure building this commit Details
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2024-11-20 16:34:52 +01:00
Alvaro Puente Mella ef0fdb9b34 refs #1138 Fix advance search bug
testing/ogGui-multibranch/pipeline/head This commit is unstable Details
2024-11-20 16:22:16 +01:00
Manuel Aranda Rosales 9304f95dfa Solve conflics
testing/ogGui-multibranch/pipeline/head This commit is unstable Details
2024-11-20 15:58:22 +01:00
Manuel Aranda Rosales 725acd7fac refs #1116. CreateImagen UX 2024-11-20 15:37:14 +01:00
Manuel Aranda Rosales 7a095a0f47 Improvment groops 2024-11-20 15:36:32 +01:00
Alvaro Puente Mella 3f0807841b refs #1138 Fix test and advance search bug
testing/ogGui-multibranch/pipeline/head This commit is unstable Details
2024-11-20 14:53:42 +01:00
Alvaro Puente Mella cfc23477df Update changelog for 0.5.0
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2024-11-19 14:04:24 +01:00
Alvaro Puente Mella 58a0639cb5 refs #1138 Fix translations 2024-11-19 13:35:46 +01:00
Alvaro Puente Mella 3dd127d46f refs #1138 Fix translations
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2024-11-19 12:34:52 +01:00
Alvaro Puente Mella a7472027e6 Merge branch 'develop' into oggui/translations 2024-11-19 12:34:29 +01:00
Alvaro Puente Mella 0cde34aedb refs #1138 Fix translations
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2024-11-18 12:37:52 +01:00
Manuel Aranda Rosales 0e3e3f56e3 refs #1157. Added env_vars route
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2024-11-18 11:39:29 +01:00
Alvaro Puente Mella 555357f52c refs #1138 Fix stash pop from add translate and es-en files 2024-11-15 13:57:34 +01:00
Alvaro Puente Mella bb41d9c956 refs #1138 Fix stash pop from add translate and es-en files 2024-11-15 13:57:20 +01:00
Manuel Aranda Rosales 298e9b0c38 Improvements after conflicts
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2024-11-15 13:30:38 +01:00
Alvaro Puente Mella 3ad5a11751 refs #1138 Add translate and es-en files
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2024-11-15 13:29:56 +01:00
Alvaro Puente Mella 55af0d10f8 refs #1138 Add translate and es-en files 2024-11-15 13:29:54 +01:00
Manuel Aranda Rosales cd09659e6c solve conflicts
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2024-11-15 13:16:29 +01:00
Manuel Aranda Rosales 1bde11689c ogDhcp improvements 2024-11-15 13:01:31 +01:00
Manuel Aranda Rosales 2d4b0e58ea ogBoot improvements 2024-11-15 13:01:11 +01:00
Manuel Aranda Rosales dfc7fd01ce refs #1079. Update repo admin 2024-11-15 13:00:04 +01:00
Manuel Aranda Rosales ddfbcdf46d refs #1048. Updated client view and partition assistant 2024-11-15 12:59:17 +01:00
Manuel Aranda Rosales 4dfc45b125 refs #1091. CreateImage logic 2024-11-15 12:57:45 +01:00
Alvaro Puente Mella 701223d1bb refs #1138 Add translate and es-en files
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2024-11-15 12:47:00 +01:00
Alvaro Puente Mella c168c87fc9 refs #1136 Add joyride in all components
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2024-11-13 12:39:49 +01:00
Alvaro Puente Mella 4109188913 Add joyride service for help
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2024-11-11 18:51:48 +01:00
Alvaro Puente Mella 39cf1c96c9 refs #1068 Bootfilename can be null & mattooltips
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2024-11-11 12:16:05 +01:00
Alvaro Puente Mella bb964aacea refs #1075 formatBytes server usage
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2024-11-08 12:47:23 +01:00
Alvaro Puente Mella 7d798f4d26 refs #1067 Refactor add clients to subnet
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2024-11-08 11:31:48 +01:00
Alvaro Puente Mella 7a0e13b829 refs #1065 Bug fix on edit software profiles
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2024-11-07 12:16:37 +01:00
Alvaro Puente Mella 0d403d8b8e refs #1077 Add model teplates for reference
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2024-11-07 11:11:32 +01:00
Alvaro Puente Mella 6d763f22eb refs #1060 Add execute command from clasroom menu
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2024-11-06 13:09:18 +01:00
Alvaro Puente Mella 8152d310f8 refs #1071 Add execute command on single commands
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2024-11-05 14:30:30 +01:00
Alvaro Puente Mella 3f895df458 Login bug fixed
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2024-11-05 10:54:54 +01:00
Alvaro Puente Mella 1a962479ba refs #1069 Move comands ond drop
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2024-11-05 10:49:38 +01:00
Alvaro Puente Mella fd64a0be84 refs #1045 Refactor create task stepper
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2024-11-04 12:45:42 +01:00
Alvaro Puente Mella e20ec14f5b refs #1061 OU style mod
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2024-11-04 11:05:52 +01:00
Manuel Aranda Rosales 1e40126734 Little improvements
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2024-10-29 16:54:34 +01:00
Manuel Aranda Rosales 3172b512c2 refs #1079. Commands edit readOnly disabled
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2024-10-29 16:54:13 +01:00
Manuel Aranda Rosales 151ea50c4f refs #1058. Fixed Software Profile bug
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2024-10-29 16:51:17 +01:00
Manuel Aranda Rosales ffebe422ba refs #1079. Added admin Repository
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2024-10-29 16:50:17 +01:00
Alvaro Puente Mella 83dc01aa1c refs #1064 change error icons from ou table
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2024-10-29 15:02:54 +01:00
Alvaro Puente Mella 99d309b883 refs #1062 Toggle sidebar on button
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2024-10-29 13:44:11 +01:00
Nicolas Arenas 36539a3d9e Build with correct ID for main
testing/ogGui-multibranch/pipeline/head Something is wrong with the build of this commit Details
2024-10-29 12:44:07 +01:00
Alvaro Puente Mella 07ac93c4af refs #1042 Advanced search size buttons
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2024-10-29 12:24:07 +01:00
Alvaro Puente Mella 1fca985cee refs #1042 Advanced search size adjust 2024-10-29 12:13:32 +01:00
Manuel Aranda Rosales 61b7c75588 refs #1048. Updated client view and partition assistant
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2024-10-29 11:57:41 +01:00
Alvaro Puente Mella c1b9a08f6c refs #1042 Advanced search size buttons
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
testing/ogGui-multibranch/pipeline/tag This commit looks good Details
2024-10-29 10:51:12 +01:00
Alvaro Puente Mella 852f894605 Login - show password bug fix 2024-10-29 10:51:12 +01:00
Nicolas Arenas 742a9ad72b Fix test results
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2024-10-29 10:27:12 +01:00
Alvaro Puente Mella cbae436535 Add tag version
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2024-10-25 14:47:33 +02:00
Manuel Aranda Rosales 48636d0933 Solve conflics
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2024-10-25 10:50:58 +02:00
Manuel Aranda Rosales 60a96170e1 refs #1048. Updated client view and partition assistant 2024-10-25 10:42:08 +02:00
Nicolas Arenas 2f3e119b54 Adjust branch variable for multi branch job
oggui-multibranch/pipeline/head This commit looks good Details
ogGui-multibranch/pipeline/head There was a failure building this commit Details
2024-10-25 06:44:46 +02:00
Nicolas Arenas a3e236beaa Fixing some typo
oggui-multibranch/pipeline/head There was a failure building this commit Details
2024-10-24 22:23:55 +02:00
Nicolas Arenas cdcea5ac46 Add intermediate step to publish alpine images 2024-10-24 21:48:24 +02:00
Nicolas Arenas af04ef771f Versión haciendo push de la imagen de desarrollo 2024-10-24 21:34:53 +02:00
Nicolas Arenas bc0a94935b Fix tag 2024-10-24 19:41:08 +02:00
Nicolas Arenas b280099886 Fix typo 2024-10-24 19:13:48 +02:00
Nicolas Arenas b4498a59f6 Fixing some typos 2024-10-24 19:09:39 +02:00
Nicolas Arenas 935af694eb Get test results 2024-10-24 19:03:52 +02:00
Nicolas Arenas 110565662d Update argumenst for running tests 2024-10-24 18:44:01 +02:00
Nicolas Arenas a11ca596c7 Running tests 2024-10-24 18:37:05 +02:00
Nicolas Arenas 97ceec1771 Install chromium in Dockerfile 2024-10-24 18:30:41 +02:00
Nicolas Arenas 10a81de899 Adds karma configuration for xunit reporting 2024-10-24 18:27:40 +02:00
Alvaro Puente Mella 7cede099af refs #1044 new dockerfile 2024-10-24 13:52:26 +02:00
Alvaro Puente Mella 38fef827a9 Merge branch 'develop' of ssh://ognproject.evlt.uma.es:21987/opengnsys/oggui into develop 2024-10-24 13:04:50 +02:00
Alvaro Puente Mella 2187373c23 refs #1044 Add karma.conf file and, junit reports and adjust test 2024-10-24 13:04:44 +02:00
Nicolas Arenas 6d8a93b0e4 refs #1028 Arregla el etiquetado de latest 2024-10-23 19:35:56 +02:00
Nicolas Arenas 5a3471d779 refs #1028 Arregla el tag de latest 2024-10-23 19:25:47 +02:00
Nicolas Arenas f3f72b2704 refs #1028 - Contenedor de oggui
- Se añade el tag a latest cuando se realiza el push al repo
2024-10-23 19:07:18 +02:00
Alvaro Puente Mella 87680723bc refs #985 Added loading in tables and fix some test 2024-10-23 10:39:42 +02:00
Manuel Aranda Rosales bbbaeb87fa Merge branch 'develop' of ssh://ognproject.evlt.uma.es:21987/opengnsys/oggui into develop 2024-10-23 08:35:30 +02:00
Manuel Aranda Rosales c8cdd2efaf Improvement text subnet 2024-10-23 08:35:22 +02:00
Nicolas Arenas 0f8bcbfa2c Merge to staer building Docker Containers with CI. 2024-10-22 23:30:55 +02:00
Manuel Aranda Rosales b0147c4eca Added button sync client 2024-10-22 21:40:12 +02:00
409 changed files with 20966 additions and 9084 deletions

8
.gitignore vendored
View File

@ -1 +1,9 @@
ogWebconsole/.env
ogWebconsole/test-results/ogGui-junit-report.xml
node_modules/
### Debian packaging
debian/oggui
debian/*.substvars
debian/*.log
debian/.debhelper/
debian/files

119
CHANGELOG.md 100644
View File

@ -0,0 +1,119 @@
# Changelog
## [0.11.2] - 2025-4-16
### Fixed
- Se ha corregido un error en la actualizacion del estado de los pcs en la vista tarjetas.
---
## [0.11.1] - 2025-4-16
### Improved
- Nuevos campos en la tabla de clientes. Tipo de firmware y mac.
## Fixed
- Se ha corregido error al crear OUs, que no refrescaba la web.
- Se ha corregido error en el formulario de creacion de imagenes. Si se seleccionaba una imagen para un versionado, no dejaba deseleccionar.
- Se ha corregido un bug en el particionador que impedia ejecutar, cuando eliminabamos una particion.
---
## [0.11.0] - 2025-4-11
### Added
- Se ha diseñado el nuevo formulario para poder ejecutar script. Sistema mejorado con variables etiquetadas.
- Se puede añadir descripcion a una imagen.
- Se han añadido al formulario de crear/editar repositorio, la posibilidad de añadir usuario y puerto ssh.
- Nuevo estado en pc => desconectado.
- Se ha añadido nueva accion para renombrar imagen monolitica.
### Improved
- Se ha mejorado la interfaz de usuario tanto para el despliegue de imagenes, como el particionado.
- Se ha mejorado la responsividad de la vista de grupos.
- Cambios en el comportamiento general de muchos componentes modales. Se han añadido spinners de carga mas intuitivos.
---
## [0.10.1] - 2025-3-27
### Improved
- Mejoras en el comportamiento del arbol de grupos.
- Nueva regexp para controlar las "macs" en la creacion de clientes.
---
## [0.10.0] - 2025-3-25
### Added
- Nuevo componenten de estado global.
- Servicio para que el ogGui obtenga de forma dinamica las variables de entorno.
- Nueva funcionalidad para convertir imagen en imagen virtual.
- Nueva funcionalidad para importar imágenes externas al sistema.
- Despliegue de imangenes sin cache. Cambios en el formulario de "despliegue".
### Improved
- Mejoras en la internacionalización.
- Nueva UX ogRepository. Ahora se gestionan las imagenes de forma mas sencilla.
- Cambios en ogLive. Mejora en la sincronizacion y obtención de datos en la API
### Fixed
- Cambios en la expresion regular para la validacion de documentos DHCP en la carga masiva de pc.
---
## [0.9.2] - 2025-03-19
### Changed
- Jenkinsfile to pubilsh packages in repo in case og release
---
## [0.9.1] - 2025-03-12
### Changed
- Se ha modificado el acceso a Mercure añadiendo nueva variable de entorno.
---
## [0.9.0] - 2025-3-4
### Added
- Integracion con Mercure. Subscriber tanto en "Trazas" con en "Clientes".
- Nueva funcionalidad para checkear la integridad de una imagen. Boton en apartado "imagenes" dentro del repositorio.
- Centralizacion de estilos.
- Nueva funcionalidad para realizar backup de imágenes.
- Botón para cancelar despliegues de imagenes. Aparece en "trazas" tan solo para los comendos "deploy" y para el estado "en progreso".
### Changed
- Nueva interfaz en "Grupos". Se ha aprovechado mejor el espacio y acortado el tamaño de las filas, para poder tener mas elementos por pantalla.
- Cambios en filtros de "Grupos". Ahora se pueden filtrar por "Centro" y "Unidad Organizativa" y estado. Ahora se busca en base de datos, y no en una lista de clientes dados.
- Refactorizados compontentes de crear/editar clientes en uno solo.
- Cambios en DHCP. Nueva UX en "ver clientes". Ahora tenemos un buscador detallado.
- Para gestionar/añadir clientes a subredes ahora tenemos un botón para "añadir todos" y tan solo nos aparecn los equipos que no estén previamente asignados en una subred.
---
## [0.7.0] - 2024-12-10
### Refactored
- Refactored the group screen, removing the separate tabs for clients, advanced search, and organizational units.
- Added support for partitioning functionality in the client detail view.
---
## [0.6.1] - 2024-11-19
### Improved
- Introduced a new automatic sync mode for the ogdhcp and ogBoot components.
- Improve test coverage.
- New view for clients inside the classroom on the main page.
---
## [0.6.0] - 2024-11-19
### Added
- Added functionality to execute actions from the menu in the general groups screen.
- Displayed the selected center on the general screen for better context.
- Implemented the option to collapse the sidebar for improved usability.
- Introduced a dropdown menu to select boot files (BootfileNames).
- Simplified the process of adding multiple clients in the subnets section.
- Enabled ogBoot to manage storage units more intuitively.
- Added preloading of template models in ogBoot for faster setup.
- Created a contextual help tour to guide users through various components and features.
### Improved
- Renamed the field "Reserved Room" to "Available RemotePC" for better clarity.
- Added view sizes for visual cards.
- Refactored the task stepper to improve efficiency and readability.
- Replaced red "X" icons with more consistent and user-friendly visuals.
- Allowed logical names for IP addresses in the subnets section.
- Enabled reordering of commands when creating a command group.
- Made predefined commands read-only to prevent accidental modifications.
- Simplified the task creation modal to enhance user experience.
- Adjusted the translation system to cover new elements and improve consistency (work in progress).
- New element view from clients on groups main view.
### Fixed
- Resolved an issue that prevented editing software profiles correctly.
- Fixed a bug where newly created commands failed to execute in the commands section.

View File

@ -0,0 +1,107 @@
@Library('jenkins-shared-library') _
pipeline {
agent {
label 'jenkins-slave'
}
environment {
DEBIAN_FRONTEND = 'noninteractive'
DEFAULT_DEV_NAME = 'Opengnsys Team'
DEFAULT_DEV_EMAIL = 'opengnsys@qindel.com'
}
options {
skipDefaultCheckout()
}
parameters {
string(name: 'DEV_NAME', defaultValue: '', description: 'Nombre del desarrollador')
string(name: 'DEV_EMAIL', defaultValue: '', description: 'Email del desarrollador')
}
stages {
stage('Prepare Workspace') {
steps {
script {
env.BUILD_DIR = "${WORKSPACE}/oggui"
sh "mkdir -p ${env.BUILD_DIR}"
}
}
}
stage('Checkout') {
steps {
dir("${env.BUILD_DIR}") {
checkout scm
}
}
}
stage('Generate Changelog') {
when {
expression {
return env.TAG_NAME != null
}
}
steps {
script {
def devName = params.DEV_NAME ? params.DEV_NAME : env.DEFAULT_DEV_NAME
def devEmail = params.DEV_EMAIL ? params.DEV_EMAIL : env.DEFAULT_DEV_EMAIL
generateDebianChangelog(env.BUILD_DIR, devName, devEmail)
}
}
}
stage('Generate Changelog (Nightly)'){
when {
branch 'main'
}
steps {
script {
def devName = params.DEV_NAME ? params.DEV_NAME : env.DEFAULT_DEV_NAME
def devEmail = params.DEV_EMAIL ? params.DEV_EMAIL : env.DEFAULT_DEV_EMAIL
generateDebianChangelog(env.BUILD_DIR, devName, devEmail,"nightly")
}
}
}
stage('Build') {
steps {
script {
construirPaquete(env.BUILD_DIR, "../artifacts", "172.17.8.68", "/var/tmp/opengnsys/debian-repo/oggui")
}
}
}
stage ('Publish to Debian Repository') {
when {
expression {
return env.TAG_NAME != null
}
}
agent { label 'debian-repo' }
steps {
script {
// Construir el patrón de versión esperado en el nombre del paquete
def versionPattern = "${env.TAG_NAME}-${env.BUILD_NUMBER}"
publicarEnAptly('/var/tmp/opengnsys/debian-repo/oggui', 'opengnsys-devel', versionPattern)
}
}
}
stage ('Publish to Debian Repository (Nightly)') {
when {
branch 'main'
}
agent { label 'debian-repo' }
steps {
script {
// Construir el patrón de versión esperado en el nombre del paquete
def versionPattern = "-${env.BUILD_NUMBER}~nightly"
publicarEnAptly('/var/tmp/opengnsys/debian-repo/oggui', 'nightly', versionPattern)
}
}
}
}
post {
always {
notifyBuildStatus('narenas@qindel.com')
}
}
}

34
bin/start-oggui.sh 100644
View File

@ -0,0 +1,34 @@
#!/bin/bash
set -e
CONFIG_FILE="/opt/opengnsys/oggui/src/.env"
HASH_FILE="/opt/opengnsys/oggui/var/lib/oggui/oggui.config.hash"
APP_DIR="/opt/opengnsys/oggui/browser"
SRC_DIR="/opt/opengnsys/oggui/src"
COMPILED_DIR=$SRC_DIR/dist
NGINX_SERVICE="nginx"
# Verificar si el archivo de configuración cambió
if [ -f "$CONFIG_FILE" ] && [ -f "$HASH_FILE" ]; then
OLD_HASH=$(cat "$HASH_FILE")
NEW_HASH=$(md5sum "$CONFIG_FILE")
if [ "$OLD_HASH" != "$NEW_HASH" ]; then
echo "🔄 Cambios detectados en $CONFIG_FILE, recompilando Angular..."
cd "$SRC_DIR"
npm install -g @angular/cli
npm install
/usr/local/bin/ng build --base-href=/ --output-path=dist/oggui --optimization=true --configuration=production --localize=false
md5sum "$CONFIG_FILE" > "$HASH_FILE"
exit 0
else
echo "No hay cambios en $CONFIG_FILE, no es necesario recompilar."
exit 0
fi
else
echo "Archivo de configuración no encontrado o sin hash previo. No se recompilará."
exit 1
fi
# Iniciar Nginx
systemctl restart "$NGINX_SERVICE"

14
debian/changelog vendored 100644
View File

@ -0,0 +1,14 @@
oggui (0.0.1-1) unstable; urgency=medium
* Add debian files
* Update .gitignore
* refs #1637 refactor: remove unused client edit and create components; add manage client component
* refs #1619. Style: enhance cards view layout and paginator integration in groups component
* refactor: update paginator settings and improve page change handling in groups component
* Merge branch 'develop' of ssh://ognproject.evlt.uma.es:21987/opengnsys/oggui into develop
* style: clean up and optimize CSS for groups component; enhance HTML structure and improve responsiveness
* Updated groups paginator
* Merge branch 'develop' of ssh://ognproject.evlt.uma.es:21987/opengnsys/oggui into develop
* refs #1567. New subnet field: 'dns'
-- Tu Nombre <tuemail@example.com> Mon, 10 Mar 2025 14:48:36 +0000

1
debian/compat vendored 100644
View File

@ -0,0 +1 @@
12

13
debian/control vendored 100644
View File

@ -0,0 +1,13 @@
Source: oggui
Section: web
Priority: optional
Maintainer: Nicolas Arenas <nicolas.arenas@qindel.com>
Build-Depends: debhelper (>= 12), nodejs, npm
Standards-Version: 4.5.0
Package: oggui
Architecture: any
Maintainer: Nicolas Arenas <nicolas.arenas@qindel.com>
Depends: ${shlibs:Depends}, ${misc:Depends}, nginx
Description: OpenGnsys GUI created for the Opengnsys Team
Opengnsys Graphical Intercface

1
debian/debhelper-build-stamp vendored 100644
View File

@ -0,0 +1 @@
oggui

2
debian/files vendored 100644
View File

@ -0,0 +1,2 @@
oggui_1.0.1+deb-pkg20250310-1_amd64.buildinfo web optional
oggui_1.0.1+deb-pkg20250310-1_amd64.deb web optional

10
debian/oggui.config vendored 100644
View File

@ -0,0 +1,10 @@
#!/bin/sh
set -e
. /usr/share/debconf/confmodule
db_input high opengnsys/oggui_ogcoreUrl || true
db_input high opengnsys/oggui_ogmercureUrl || true
db_go

4
debian/oggui.install vendored 100644
View File

@ -0,0 +1,4 @@
ogWebconsole/dist/oggui/browser /opt/opengnsys/oggui/
etc /opt/opengnsys/oggui/
ogWebconsole/ssl/* /opt/opengnsys/oggui/etc/nginx/certs/

61
debian/oggui.postinst vendored 100644
View File

@ -0,0 +1,61 @@
#!/bin/bash
set -e
. /usr/share/debconf/confmodule
db_get opengnsys/oggui_ogcoreUrl
OGCORE_URL="$RET"
db_get opengnsys/oggui_ogmercureUrl
OGMERCURE_URL="$RET"
# Asegurarse de que el usuario exista
USER="opengnsys"
CONFIG_FILE="/opt/opengnsys/oggui/browser/assets/config.json"
restore_config_if_modified() {
local new="$1"
local backup="$1.bak"
if [ -f "$backup" ]; then
if ! cmp -s "$new" "$backup"; then
echo ">>> Archivo modificado por el usuario detectado en $new"
echo " - Guardando archivo nuevo como ${new}.new"
mv -f "$new" "${new}.new"
echo " - Restaurando archivo anterior desde backup"
mv -f "$backup" "$new"
else
echo ">>> El archivo $new no ha cambiado desde la última versión, eliminando backup"
rm -f "$backup"
fi
fi
}
# Detectar si es una instalación nueva o una actualización
if [ "$1" = "configure" ] && [ -z "$2" ]; then
if [ ! -f "$CONFIG_FILE" ]; then
jq --arg apiUrl "$OGCORE_URL" --arg mercureUrl "$OGMERCURE_URL" \
'.apiUrl = $apiUrl | .mercureUrl = $mercureUrl' "$CONFIG_FILE" > "${CONFIG_FILE}.tmp" && mv "${CONFIG_FILE}.tmp" "$CONFIG_FILE"
fi
ln -s /opt/opengnsys/oggui/etc/nginx/oggui.conf /etc/nginx/sites-enabled/oggui.conf
ln -s $CONFIG_FILE /opt/opengnsys/oggui/etc/config.json
mkdir -p /etc/nginx/certs/
cp -p /opt/opengnsys/oggui/etc/nginx/certs/* /etc/nginx/certs/
chown -R www-data:www-data /etc/nginx/certs
systemctl daemon-reload
systemctl restart nginx
elif [ "$1" = "configure" ] && [ -n "$2" ]; then
cd /opt/opengnsys/oggui
echo "Actualización desde la versión $2"
# Si upgrade recupero los archivos de configuracion
echo ">>> Backup de archivos de configuración reales en /opt/opengnsys"
restore_config_if_modified "/opt/opengnsys/oggui/etc/nginx/oggui.conf"
restore_config_if_modified "$CONFIG_FILE"
fi
# Cambiar la propiedad de los archivos al usuario especificado
chown opengnsys:www-data /opt/opengnsys/
chown -R opengnsys:www-data /opt/opengnsys/oggui
exit 0

32
debian/oggui.postrm vendored 100755
View File

@ -0,0 +1,32 @@
#!/bin/sh
set -e
NGINX_FILE="/etc/nginx/sites-enabled/oggui.conf"
UNIT_FILE="/etc/systemd/system/oggui.service"
case "$1" in
remove)
echo "El paquete se está desinstalando..."
# Aquí puedes hacer limpieza de archivos o servicios
if [ -L "$NGINX_FILE" ]; then
rm -f "$NGINX_FILE"
systemctl restart nginx
fi
if [ -L "$UNIT_FILE" ]; then
rm -f "$UNIT_FILE"
systemctl daemon-reload
fi
;;
purge)
echo "Eliminando configuración residual..."
;;
upgrade)
echo "Actualizando paquete..."
;;
esac
exit 0

6
debian/oggui.postrm.debhelper vendored 100644
View File

@ -0,0 +1,6 @@
# Automatically added by dh_installdebconf/13.14.1ubuntu5
if [ "$1" = purge ] && [ -e /usr/share/debconf/confmodule ]; then
. /usr/share/debconf/confmodule
db_purge
fi
# End automatically added section

32
debian/oggui.preinst vendored 100644
View File

@ -0,0 +1,32 @@
#!/bin/bash
set -e
backup_file_if_exists() {
local original="$1"
local backup="$1.bak"
if [ -e "$original" ]; then
echo " - Guardando backup de $original en $backup"
cp -a "$original" "$backup"
fi
}
CONFIG_FILE="/opt/opengnsys/oggui/browser/assets/config.json"
# Asegurarse de que el usuario exista
USER="opengnsys"
HOME_DIR="/opt/opengnsys"
if id "$USER" &>/dev/null; then
echo "El usuario $USER ya existe."
else
echo "Creando el usuario $USER con home en $HOME_DIR."
useradd -m -d "$HOME_DIR" -s /bin/bash "$USER"
fi
# Si upgrade hago backup del archivo de configuración
if [ "$1" = "upgrade" ]; then
echo ">>> Backup de archivos de configuración reales en /opt/opengnsys"
backup_file_if_exists "/opt/opengnsys/oggui/etc/nginx/sites-available/oggui.conf"
backup_file_if_exists "$CONFIG_FILE"
fi
exit 0

13
debian/oggui.prerm vendored 100644
View File

@ -0,0 +1,13 @@
#!/bin/bash
set -e
set -x
# Solo eliminar archivos de configuración si se está eliminando el paquete
if [ "$1" = "remove" ] || [ "$1" = "purge" ]; then
rm -f /etc/nginx/sites-enabled/oggui.conf
systemctl daemon-reload
systemctl restart nginx
fi
exit 0

9
debian/oggui.templates vendored 100644
View File

@ -0,0 +1,9 @@
Template: opengnsys/oggui_ogcoreUrl
Type: string
Default: https://127.0.0.1:8443
Description: Introduzca la URL delAPI de OgCore
Template: opengnsys/oggui_ogmercureUrl
Type: string
Default: https://127.0.0.1:3000/.well-known/mercure
Description: Introduzca el endpoint de mercure

11
debian/rules vendored 100755
View File

@ -0,0 +1,11 @@
#!/usr/bin/make -f
%:
dh $@
override_dh_auto_build:
cd ogWebconsole && npm install
cd ogWebconsole && npx ng build --base-href=/ --output-path=dist/oggui --optimization=true --configuration=production
override_dh_auto_install:
dh_auto_install

View File

@ -0,0 +1,20 @@
server {
listen 4200 ssl;
server_name localhost;
root /opt/opengnsys/oggui/browser;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
# Manejo de archivos estáticos
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
try_files $uri =404;
}
ssl_certificate /opt/opengnsys/oggui/etc/nginx/certs/oggui.uds-test.net.crt.pem;
ssl_certificate_key /opt/opengnsys/oggui/etc/nginx/certs/oggui.uds-test.net.key.pem;
# Configuración para evitar problemas con rutas de Angular
error_page 404 /index.html;
}

View File

@ -1 +1,2 @@
NG_APP_BASE_API_URL=https://127.0.0.1:8443
# NG_APP_BASE_API_URL=https://127.0.0.1:8443
# NG_APP_OGCORE_MERCURE_BASE_URL=http://localhost:3000/.well-known/mercure

View File

@ -0,0 +1,2 @@
NG_APP_BASE_API_URL=https://localhost:8443
NG_APP_OGCORE_MERCURE_BASE_URL=http://localhost:3000/.well-known/mercure

View File

@ -41,3 +41,5 @@ testem.log
.DS_Store
Thumbs.db
test-results/

View File

@ -1,4 +1,4 @@
FROM node:22.1.0
FROM node:22.10-alpine
WORKDIR /app

View File

@ -0,0 +1,15 @@
FROM node:22.10
WORKDIR /app
RUN apt -y update && apt -y install chromium
RUN npm install -g npm@latest
RUN npm install -g @angular/cli@^12.0.0
COPY . /app
RUN npm install
EXPOSE 4200
CMD ["ng", "serve", "--host", "0.0.0.0", "--disable-host-check"]

View File

@ -3,9 +3,7 @@ pipeline {
environment {
DOCKER_REPO = "opengnsys"
DOCKER_CREDENTIALS = credentials('docker-hub-credentials')
DOCKER_TAG = "${env.BUILD_NUMBER}"
DOCKER_IMAGE_NAME = "oggui"
BRANCH_NAME = "${GIT_BRANCH.split("/")[1]}"
}
stages {
stage ('Checkout') {
@ -13,12 +11,53 @@ pipeline {
checkout scm
}
}
stage('Build Testing Image') {
steps {
sh "printenv"
echo 'Building....'
script {
def DOCKER_TAG = "${env.BUILD_NUMBER}"
dir('ogWebconsole') {
IMAGE_ID = "${DOCKER_REPO}/${DOCKER_IMAGE_NAME}:${BRANCH_NAME}-${DOCKER_TAG}"
IMAGE_ID_TESTING = "${DOCKER_REPO}/${DOCKER_IMAGE_NAME}:${BRANCH_NAME}-${DOCKER_TAG}-testing"
if (BRANCH_NAME == 'main') {
LATEST_ID = "${DOCKER_REPO}/${DOCKER_IMAGE_NAME}:latest"
} else {
LATEST_ID = "${DOCKER_REPO}/${DOCKER_IMAGE_NAME}:${BRANCH_NAME}-latest"
}
env.IMAGE_ID_TESTING = IMAGE_ID_TESTING
env.IMAGE_ID = IMAGE_ID
env.LATEST_ID = LATEST_ID
docker.build("${IMAGE_ID_TESTING}", "-f Dockerfile-testing .")
if (env.TAG_NAME) {
TAG_ID = "${DOCKER_REPO}/${DOCKER_IMAGE_NAME}:${env.TAG_NAME}"
}
}
}
}
}
stage('Testing') {
steps {
echo 'Running Tests....'
sh '''
cd ogWebconsole
docker run -p 4200:4200 --name oggui-testing -e CHROME_BIN=/usr/bin/chromium -v $(pwd)/karma.conf.js:/app/karma.conf.js -v $(pwd)/.env:/app/.env -d $IMAGE_ID_TESTING
docker exec oggui-testing ng test --watch=false --source-map=false --karma-config=karma.conf.js
'''
}
}
stage('Build') {
steps {
echo 'Building....'
script {
dir('ogWebconsole') {
docker.build("${DOCKER_REPO}/${DOCKER_IMAGE_NAME}:${BRANCH_NAME}-${DOCKER_TAG}", "-f Dockerfile .")
docker.build("${IMAGE_ID}", "-f Dockerfile .")
docker.build("${LATEST_ID}", "-f Dockerfile .")
if (env.TAG_NAME) {
docker.build("${TAG_ID}", "-f Dockerfile .")
}
}
}
}
@ -27,13 +66,46 @@ pipeline {
steps {
echo 'Pushing....'
script {
docker.withRegistry('https://index.docker.io/v1/', 'docker-hub-credentials' ) {
dir('ogWebconsole') {
docker.image("${DOCKER_REPO}/${DOCKER_IMAGE_NAME}:${BRANCH_NAME}-${DOCKER_TAG}").push()
docker.withRegistry('https://index.docker.io/v1/', 'docker-hub-credentials') {
docker.image("${IMAGE_ID}").push()
docker.image("${LATEST_ID}").push()
if (env.TAG_NAME) {
docker.image("${TAG_ID}").push()
}
}
}
}
}
}
post {
always {
echo 'Get test results....'
sh "mkdir -p test-results"
sh "docker cp oggui-testing:/app/test-results/ogGui-junit-report.xml ./test-results/ogGui-junit-report.xml"
junit '**/test-results/*.xml'
echo 'Cleaning up....'
sh "docker stop oggui-testing"
sh "docker rm oggui-testing"
sh "docker rmi ${IMAGE_ID} || true"
sh "docker rmi ${LATEST_ID} || true"
sh "docker rmi ${IMAGE_ID_TESTING} || true"
script {
def committerEmail = sh (
script: "git show -s --pretty=%ae",
returnStdout: true
).trim()
def buildResult = currentBuild.currentResult
mail to: committerEmail,
subject: "Opengnsys CI Build ${env.JOB_NAME} - ${env.BRANCH_NAME} - ${buildResult}",
body: """
<h1>Opengnsys CI Build ${JOB_NAME} - ${BRANCH_NAME} - ${buildResult}</h1>
<p>Build Number: ${BUILD_NUMBER}</p>
<p>Build URL: ${BUILD_URL}</p>º
Saludos cordiales,
Opengnsys CI
"""
}
}
}
}

View File

@ -4,12 +4,6 @@
"newProjectRoot": "projects",
"projects": {
"ogWebconsole": {
"i18n": {
"sourceLocale": "es",
"locales": {
"en-US": "src/locale/messages.en.json"
}
},
"projectType": "application",
"schematics": {
"@schematics/angular:component": {
@ -30,7 +24,7 @@
"builder": "@ngx-env/builder:application",
"options": {
"baseHref": "/oggui/",
"localize": true,
"localize": false,
"aot": true,
"outputPath": "dist/og-webconsole",
"index": "src/index.html",
@ -42,14 +36,22 @@
"tsConfig": "tsconfig.app.json",
"assets": [
"src/favicon.ico",
"src/assets"
"src/assets",
{
"glob": "**/*",
"input": "src/locale",
"output": "/locale"
}
],
"styles": [
"src/custom-theme.scss",
"src/styles.css",
"node_modules/ngx-toastr/toastr.css"
],
"scripts": []
"scripts": [],
"allowedCommonJsDependencies": [
"rfdc"
]
},
"configurations": {
"production": {
@ -61,8 +63,8 @@
},
{
"type": "anyComponentStyle",
"maximumWarning": "2kb",
"maximumError": "4kb"
"maximumWarning": "7kb",
"maximumError": "10kb"
}
],
"outputHashing": "all"
@ -71,16 +73,6 @@
"optimization": false,
"extractLicenses": false,
"sourceMap": false
},
"es": {
"localize": [
"es-ES"
]
},
"en": {
"localize": [
"en-US"
]
}
},
"defaultConfiguration": "production"
@ -99,29 +91,16 @@
},
"development": {
"buildTarget": "ogWebconsole:build:development"
},
"es": {
"buildTarget": "ogWebconsole:build:es"
},
"en": {
"buildTarget": "ogWebconsole:build:en"
}
},
"defaultConfiguration": "development"
},
"extract-i18n": {
"builder": "@ngx-env/builder:extract-i18n",
"options": {
"buildTarget": "ogWebconsole:build"
}
},
"test": {
"builder": "@ngx-env/builder:karma",
"options": {
"polyfills": [
"zone.js",
"zone.js/testing",
"@angular/localize/init"
"zone.js/testing"
],
"tsConfig": "tsconfig.spec.json",
"assets": [
@ -129,7 +108,8 @@
"src/assets"
],
"styles": [
"src/styles.css"
"src/styles.css",
"src/custom-theme.scss"
],
"scripts": []
}

View File

@ -0,0 +1,40 @@
module.exports = function(config) {
config.set({
frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage'),
require('karma-junit-reporter'),
require('@angular-devkit/build-angular/plugins/karma')
],
client: {
clearContext: false
},
reporters: ['progress', 'kjhtml', 'junit'],
junitReporter: {
outputDir: 'test-results',
outputFile: 'ogGui-junit-report.xml',
useBrowserName: false,
},
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['ChromeHeadlessNoSandbox'],
customLaunchers: {
ChromeHeadlessNoSandbox: {
base: 'ChromeHeadless',
flags: ['--no-sandbox','--disable-setuid-sandbox']
}
},
singleRun: false,
restartOnFileChange: true
});
};

View File

@ -1,12 +1,12 @@
{
"name": "og-webconsole",
"version": "0.0.0",
"version": "0.5.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "og-webconsole",
"version": "0.0.0",
"version": "0.5.0",
"dependencies": {
"@angular/animations": "^18.0.0",
"@angular/cdk": "~18.0.0",
@ -18,9 +18,13 @@
"@angular/platform-browser": "^18.0.0",
"@angular/platform-browser-dynamic": "^18.0.0",
"@angular/router": "^18.0.0",
"@ngx-translate/core": "^16.0.3",
"@ngx-translate/http-loader": "^16.0.0",
"@swimlane/ngx-charts": "^20.5.0",
"jwt-decode": "^4.0.0",
"ngx-joyride": "^2.5.0",
"ngx-toastr": "^19.0.0",
"papaparse": "^5.4.1",
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
"zone.js": "^0.14.6"
@ -32,12 +36,14 @@
"@angular/localize": "^18.1.0",
"@ngx-env/builder": "^18.0.1",
"@types/jasmine": "~5.1.0",
"@types/papaparse": "^5.3.15",
"jasmine-core": "~5.1.0",
"karma": "~6.4.0",
"karma-chrome-launcher": "~3.2.0",
"karma-coverage": "~2.2.0",
"karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "~2.1.0",
"karma-junit-reporter": "^2.0.1",
"typescript": "~5.4.5"
}
},
@ -4974,6 +4980,30 @@
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/@ngx-translate/core": {
"version": "16.0.3",
"resolved": "https://registry.npmjs.org/@ngx-translate/core/-/core-16.0.3.tgz",
"integrity": "sha512-UPse66z9tRUmIpeorYodXBQY6O4foUmj9jy9cCuuja7lqdOwRBWPzCWqc+qYIXv5L2QoqZdxgHtqoUz+Q9weSA==",
"dependencies": {
"tslib": "^2.3.0"
},
"peerDependencies": {
"@angular/common": ">=16",
"@angular/core": ">=16"
}
},
"node_modules/@ngx-translate/http-loader": {
"version": "16.0.0",
"resolved": "https://registry.npmjs.org/@ngx-translate/http-loader/-/http-loader-16.0.0.tgz",
"integrity": "sha512-l3okOHGVxZ1Bm55OpakSfXvI2yYmVmhYqgwGU4aIQIRUqpkBCrSDZnmrHTcZfsGJzXKB5E2D2rko9i28gBijmA==",
"dependencies": {
"tslib": "^2.3.0"
},
"peerDependencies": {
"@angular/common": ">=16",
"@angular/core": ">=16"
}
},
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@ -5874,6 +5904,15 @@
"@types/node": "*"
}
},
"node_modules/@types/papaparse": {
"version": "5.3.15",
"resolved": "https://registry.npmjs.org/@types/papaparse/-/papaparse-5.3.15.tgz",
"integrity": "sha512-JHe6vF6x/8Z85nCX4yFdDslN11d+1pr12E526X8WAfhadOeaOTx5AuIkvDKIBopfvlzpzkdMx4YyvSKCM9oqtw==",
"dev": true,
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/qs": {
"version": "6.9.15",
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.15.tgz",
@ -6532,9 +6571,9 @@
}
},
"node_modules/body-parser": {
"version": "1.20.2",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz",
"integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==",
"version": "1.20.3",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
"integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==",
"dev": true,
"dependencies": {
"bytes": "3.1.2",
@ -6545,7 +6584,7 @@
"http-errors": "2.0.0",
"iconv-lite": "0.4.24",
"on-finished": "2.4.1",
"qs": "6.11.0",
"qs": "6.13.0",
"raw-body": "2.5.2",
"type-is": "~1.6.18",
"unpipe": "1.0.0"
@ -7220,9 +7259,9 @@
"dev": true
},
"node_modules/cookie": {
"version": "0.4.2",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz",
"integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==",
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
"dev": true,
"engines": {
"node": ">= 0.6"
@ -8080,9 +8119,9 @@
}
},
"node_modules/engine.io": {
"version": "6.5.5",
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.5.tgz",
"integrity": "sha512-C5Pn8Wk+1vKBoHghJODM63yk8MvrO9EWZUfkAt5HAqIgPE4/8FF0PEGHXtEd40l223+cE5ABWuPzm38PHFXfMA==",
"version": "6.6.2",
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.2.tgz",
"integrity": "sha512-gmNvsYi9C8iErnZdVcJnvCpSKbWTt1E8+JZo8b+daLninywUWi5NQ5STSHZ9rFjFO7imNcvb8Pc5pe/wMR5xEw==",
"dev": true,
"dependencies": {
"@types/cookie": "^0.4.1",
@ -8090,7 +8129,7 @@
"@types/node": ">=10.0.0",
"accepts": "~1.3.4",
"base64id": "2.0.0",
"cookie": "~0.4.1",
"cookie": "~0.7.2",
"cors": "~2.8.5",
"debug": "~4.3.1",
"engine.io-parser": "~5.2.1",
@ -8396,37 +8435,37 @@
"dev": true
},
"node_modules/express": {
"version": "4.19.2",
"resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz",
"integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==",
"version": "4.21.1",
"resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz",
"integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==",
"dev": true,
"dependencies": {
"accepts": "~1.3.8",
"array-flatten": "1.1.1",
"body-parser": "1.20.2",
"body-parser": "1.20.3",
"content-disposition": "0.5.4",
"content-type": "~1.0.4",
"cookie": "0.6.0",
"cookie": "0.7.1",
"cookie-signature": "1.0.6",
"debug": "2.6.9",
"depd": "2.0.0",
"encodeurl": "~1.0.2",
"encodeurl": "~2.0.0",
"escape-html": "~1.0.3",
"etag": "~1.8.1",
"finalhandler": "1.2.0",
"finalhandler": "1.3.1",
"fresh": "0.5.2",
"http-errors": "2.0.0",
"merge-descriptors": "1.0.1",
"merge-descriptors": "1.0.3",
"methods": "~1.1.2",
"on-finished": "2.4.1",
"parseurl": "~1.3.3",
"path-to-regexp": "0.1.7",
"path-to-regexp": "0.1.10",
"proxy-addr": "~2.0.7",
"qs": "6.11.0",
"qs": "6.13.0",
"range-parser": "~1.2.1",
"safe-buffer": "5.2.1",
"send": "0.18.0",
"serve-static": "1.15.0",
"send": "0.19.0",
"serve-static": "1.16.2",
"setprototypeof": "1.2.0",
"statuses": "2.0.1",
"type-is": "~1.6.18",
@ -8438,9 +8477,9 @@
}
},
"node_modules/express/node_modules/cookie": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
"integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
"integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==",
"dev": true,
"engines": {
"node": ">= 0.6"
@ -8455,14 +8494,23 @@
"ms": "2.0.0"
}
},
"node_modules/express/node_modules/encodeurl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
"integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
"dev": true,
"engines": {
"node": ">= 0.8"
}
},
"node_modules/express/node_modules/finalhandler": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
"integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==",
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz",
"integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==",
"dev": true,
"dependencies": {
"debug": "2.6.9",
"encodeurl": "~1.0.2",
"encodeurl": "~2.0.0",
"escape-html": "~1.0.3",
"on-finished": "2.4.1",
"parseurl": "~1.3.3",
@ -10027,6 +10075,22 @@
"integrity": "sha512-VYz/BjjmC3klLJlLwA4Kw8ytk0zDSmbbDLNs794VnWmkcCB7I9aAL/D48VNQtmITyPvea2C3jdUMfc3kAoy0PQ==",
"dev": true
},
"node_modules/karma-junit-reporter": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/karma-junit-reporter/-/karma-junit-reporter-2.0.1.tgz",
"integrity": "sha512-VtcGfE0JE4OE1wn0LK8xxDKaTP7slN8DO3I+4xg6gAi1IoAHAXOJ1V9G/y45Xg6sxdxPOR3THCFtDlAfBo9Afw==",
"dev": true,
"dependencies": {
"path-is-absolute": "^1.0.0",
"xmlbuilder": "12.0.0"
},
"engines": {
"node": ">= 8"
},
"peerDependencies": {
"karma": ">=0.9"
}
},
"node_modules/karma-source-map-support": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/karma-source-map-support/-/karma-source-map-support-1.4.0.tgz",
@ -10766,10 +10830,13 @@
}
},
"node_modules/merge-descriptors": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
"integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==",
"dev": true
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
"integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
"dev": true,
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/merge-stream": {
"version": "2.0.0",
@ -10796,9 +10863,9 @@
}
},
"node_modules/micromatch": {
"version": "4.0.7",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz",
"integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==",
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
"dev": true,
"dependencies": {
"braces": "^3.0.3",
@ -11211,6 +11278,18 @@
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
"dev": true
},
"node_modules/ngx-joyride": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/ngx-joyride/-/ngx-joyride-2.5.0.tgz",
"integrity": "sha512-C/J8C4uWZjKl9aMmRBt9egVjuIpwWFplJgBZDl1EfqNVTJkdEC51nt9DpAOuDwOgkbArhJ9sZIk3bZT4vkud/w==",
"dependencies": {
"tslib": "^2.0.0"
},
"peerDependencies": {
"@angular/common": ">=8.2.14",
"@angular/core": ">=8.2.14"
}
},
"node_modules/ngx-toastr": {
"version": "19.0.0",
"resolved": "https://registry.npmjs.org/ngx-toastr/-/ngx-toastr-19.0.0.tgz",
@ -11560,9 +11639,9 @@
}
},
"node_modules/object-inspect": {
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz",
"integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==",
"version": "1.13.3",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz",
"integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==",
"dev": true,
"engines": {
"node": ">= 0.4"
@ -11887,6 +11966,11 @@
"node": "^16.14.0 || >=18.0.0"
}
},
"node_modules/papaparse": {
"version": "5.4.1",
"resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.4.1.tgz",
"integrity": "sha512-HipMsgJkZu8br23pW15uvo6sib6wne/4woLZPlFf3rpDyMe9ywEXUsuD7+6K9PRkJlVT51j/sCOYDKGGS3ZJrw=="
},
"node_modules/parent-module": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
@ -12035,9 +12119,9 @@
"dev": true
},
"node_modules/path-to-regexp": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
"integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==",
"version": "0.1.10",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz",
"integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==",
"dev": true
},
"node_modules/path-type": {
@ -12326,12 +12410,12 @@
}
},
"node_modules/qs": {
"version": "6.11.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
"integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
"version": "6.13.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
"integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
"dev": true,
"dependencies": {
"side-channel": "^1.0.4"
"side-channel": "^1.0.6"
},
"engines": {
"node": ">=0.6"
@ -12906,9 +12990,9 @@
}
},
"node_modules/send": {
"version": "0.18.0",
"resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
"integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==",
"version": "0.19.0",
"resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz",
"integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==",
"dev": true,
"dependencies": {
"debug": "2.6.9",
@ -13050,20 +13134,29 @@
"dev": true
},
"node_modules/serve-static": {
"version": "1.15.0",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz",
"integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==",
"version": "1.16.2",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",
"integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==",
"dev": true,
"dependencies": {
"encodeurl": "~1.0.2",
"encodeurl": "~2.0.0",
"escape-html": "~1.0.3",
"parseurl": "~1.3.3",
"send": "0.18.0"
"send": "0.19.0"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/serve-static/node_modules/encodeurl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
"integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
"dev": true,
"engines": {
"node": ">= 0.8"
}
},
"node_modules/set-function-length": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
@ -13227,16 +13320,16 @@
}
},
"node_modules/socket.io": {
"version": "4.7.5",
"resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.7.5.tgz",
"integrity": "sha512-DmeAkF6cwM9jSfmp6Dr/5/mfMwb5Z5qRrSXLpo3Fq5SqyU8CMF15jIN4ZhfSwu35ksM1qmHZDQ/DK5XTccSTvA==",
"version": "4.8.1",
"resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.1.tgz",
"integrity": "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==",
"dev": true,
"dependencies": {
"accepts": "~1.3.4",
"base64id": "~2.0.0",
"cors": "~2.8.5",
"debug": "~4.3.2",
"engine.io": "~6.5.2",
"engine.io": "~6.6.0",
"socket.io-adapter": "~2.5.2",
"socket.io-parser": "~4.2.4"
},
@ -14452,9 +14545,9 @@
}
},
"node_modules/webpack-dev-server/node_modules/http-proxy-middleware": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz",
"integrity": "sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==",
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.7.tgz",
"integrity": "sha512-fgVY8AV7qU7z/MmXJ/rxwbrtQH4jBQ9m7kp3llF0liB7glmFeVZFBepQb32T3y8n8k2+AEYuMPCpinYW+/CuRA==",
"dev": true,
"dependencies": {
"@types/http-proxy": "^1.17.8",
@ -14831,6 +14924,15 @@
}
}
},
"node_modules/xmlbuilder": {
"version": "12.0.0",
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-12.0.0.tgz",
"integrity": "sha512-lMo8DJ8u6JRWp0/Y4XLa/atVDr75H9litKlb2E5j3V3MesoL50EBgZDWoLT3F/LztVnG67GjPXLZpqcky/UMnQ==",
"dev": true,
"engines": {
"node": ">=6.0"
}
},
"node_modules/y18n": {
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",

View File

@ -1,6 +1,6 @@
{
"name": "og-webconsole",
"version": "0.0.0",
"version": "0.5.0",
"scripts": {
"ng": "ng",
"start": "ng serve",
@ -20,9 +20,13 @@
"@angular/platform-browser": "^18.0.0",
"@angular/platform-browser-dynamic": "^18.0.0",
"@angular/router": "^18.0.0",
"@ngx-translate/core": "^16.0.3",
"@ngx-translate/http-loader": "^16.0.0",
"@swimlane/ngx-charts": "^20.5.0",
"jwt-decode": "^4.0.0",
"ngx-joyride": "^2.5.0",
"ngx-toastr": "^19.0.0",
"papaparse": "^5.4.1",
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
"zone.js": "^0.14.6"
@ -34,12 +38,14 @@
"@angular/localize": "^18.1.0",
"@ngx-env/builder": "^18.0.1",
"@types/jasmine": "~5.1.0",
"@types/papaparse": "^5.3.15",
"jasmine-core": "~5.1.0",
"karma": "~6.4.0",
"karma-chrome-launcher": "~3.2.0",
"karma-coverage": "~2.2.0",
"karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "~2.1.0",
"karma-junit-reporter": "^2.0.1",
"typescript": "~5.4.5"
}
}

View File

@ -13,59 +13,78 @@ import { PXEimagesComponent } from './components/ogboot/pxe-images/pxe-images.co
import { PxeComponent } from './components/ogboot/pxe/pxe.component';
import { PxeBootFilesComponent } from './components/ogboot/pxe-boot-files/pxe-boot-files.component';
import {OgbootStatusComponent} from "./components/ogboot/ogboot-status/ogboot-status.component";
import { OgdhcpComponent } from './components/ogdhcp/ogdhcp.component';
import { OgDhcpSubnetsComponent } from './components/ogdhcp/og-dhcp-subnets/og-dhcp-subnets.component';
import { CalendarComponent } from "./components/calendar/calendar.component";
import { CommandsComponent } from './components/commands/main-commands/commands.component';
import { CommandsGroupsComponent } from './components/commands/commands-groups/commands-groups.component';
import { CommandsTaskComponent } from './components/commands/commands-task/commands-task.component';
import { TaskLogsComponent } from './components/commands/commands-task/task-logs/task-logs.component';
import { StatusComponent } from "./components/ogdhcp/og-dhcp-subnets/status/status.component";
import { ClientMainViewComponent } from './components/groups/components/client-main-view/client-main-view.component';
import { ImagesComponent } from './components/images/images.component';
import { RestoreImageComponent } from './components/groups/components/client-main-view/restore-image/restore-image.component';
import {SoftwareComponent} from "./components/software/software.component";
import {SoftwareProfileComponent} from "./components/software-profile/software-profile.component";
import {OperativeSystemComponent} from "./components/operative-system/operative-system.component";
import {
PartitionAssistantComponent
} from "./components/groups/components/client-main-view/partition-assistant/partition-assistant.component";
import {RepositoriesComponent} from "./components/repositories/repositories.component";
import {
CreateClientImageComponent
} from "./components/groups/components/client-main-view/create-image/create-image.component";
import {
DeployImageComponent
} from "./components/groups/components/client-main-view/deploy-image/deploy-image.component";
import {
MainRepositoryViewComponent
} from "./components/repositories/main-repository-view/main-repository-view.component";
import {EnvVarsComponent} from "./components/admin/env-vars/env-vars.component";
import {MenusComponent} from "./components/menus/menus.component";
import {OgDhcpSubnetsComponent} from "./components/ogdhcp/og-dhcp-subnets.component";
import {StatusComponent} from "./components/ogdhcp/status/status.component";
import {
RunScriptAssistantComponent
} from "./components/groups/components/client-main-view/run-script-assistant/run-script-assistant.component";
const routes: Routes = [
{ path: '', redirectTo: 'auth/login', pathMatch: 'full' },
{
path: '',
component: MainLayoutComponent,
children: [
{ path: 'dashboard', component: DashboardComponent },
{ path: 'admin', component: AdminComponent },
{ path: 'users', component: UsersComponent },
{ path: 'user-groups', component: RolesComponent },
{ path: 'groups', component: GroupsComponent },
{ path: 'pxe-images', component: PXEimagesComponent },
{ path: 'pxe', component: PxeComponent },
{ path: 'pxe-boot-file', component: PxeBootFilesComponent },
{ path: 'ogboot-status', component: OgbootStatusComponent },
{ path: 'dhcp', component: OgdhcpComponent },
{ path: 'subnets', component: OgDhcpSubnetsComponent },
{ path: 'ogdhcp-status', component: StatusComponent },
{ path: 'commands', component: CommandsComponent },
{ path: 'commands-groups', component: CommandsGroupsComponent },
{ path: 'commands-task', component: CommandsTaskComponent },
{ path: 'commands-logs', component: TaskLogsComponent },
{ path: 'calendars', component: CalendarComponent },
{ path: 'client/:id', component: ClientMainViewComponent },
{ path: 'images', component: ImagesComponent },
{ path: 'restore-image', component: RestoreImageComponent},
{ path: 'software', component: SoftwareComponent },
{ path: 'software-profiles', component: SoftwareProfileComponent },
{ path: 'operative-systems', component: OperativeSystemComponent },
],
},
{
path: 'auth',
component: AuthLayoutComponent,
children: [
{ path: 'login', component: LoginComponent },
],
},
{ path: '**', component: PageNotFoundComponent },
{ path: '', component: MainLayoutComponent,
children: [
{ path: 'dashboard', component: DashboardComponent },
{ path: 'admin', component: AdminComponent },
{ path: 'users', component: UsersComponent },
{ path: 'env-vars', component: EnvVarsComponent },
{ path: 'user-groups', component: RolesComponent },
{ path: 'groups', component: GroupsComponent },
{ path: 'pxe-images', component: PXEimagesComponent },
{ path: 'pxe', component: PxeComponent },
{ path: 'pxe-boot-file', component: PxeBootFilesComponent },
{ path: 'ogboot-status', component: OgbootStatusComponent },
{ path: 'subnets', component: OgDhcpSubnetsComponent },
{ path: 'ogdhcp-status', component: StatusComponent },
{ path: 'commands', component: CommandsComponent },
{ path: 'commands-groups', component: CommandsGroupsComponent },
{ path: 'commands-task', component: CommandsTaskComponent },
{ path: 'commands-logs', component: TaskLogsComponent },
{ path: 'calendars', component: CalendarComponent },
{ path: 'clients/deploy-image', component: DeployImageComponent },
{ path: 'clients/partition-assistant', component: PartitionAssistantComponent },
{ path: 'clients/run-script', component: RunScriptAssistantComponent },
{ path: 'clients/:id', component: ClientMainViewComponent },
{ path: 'clients/:id/create-image', component: CreateClientImageComponent },
{ path: 'repositories', component: RepositoriesComponent },
{ path: 'repository/:id', component: MainRepositoryViewComponent },
{ path: 'software', component: SoftwareComponent },
{ path: 'software-profiles', component: SoftwareProfileComponent },
{ path: 'operative-systems', component: OperativeSystemComponent },
{ path: 'menus', component: MenusComponent },
],
},
{
path: 'auth',
component: AuthLayoutComponent,
children: [
{ path: 'login', component: LoginComponent },
],
},
{ path: '**', component: PageNotFoundComponent },
];
@NgModule({

View File

@ -1,17 +1,23 @@
import { TestBed } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing'; // Asegúrate de que está importado
import { RouterTestingModule } from '@angular/router/testing';
import { TranslateModule, TranslateService } from '@ngx-translate/core';
import { AppComponent } from './app.component';
describe('AppComponent', () => {
let translateService: TranslateService;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [
RouterTestingModule
RouterTestingModule,
TranslateModule.forRoot()
],
declarations: [
AppComponent
],
}).compileComponents();
translateService = TestBed.inject(TranslateService);
});
it('should create the app', () => {
@ -20,4 +26,41 @@ describe('AppComponent', () => {
expect(app).toBeTruthy();
});
it(`should have as title 'ogWebconsole'`, () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.componentInstance;
expect(app.title).toEqual('ogWebconsole');
});
it('should set the language from localStorage on creation', () => {
spyOn(localStorage, 'getItem').and.returnValue('en'); // Simula que el idioma guardado es "en"
spyOn(translateService, 'use');
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
expect(localStorage.getItem).toHaveBeenCalledWith('language');
expect(translateService.use).toHaveBeenCalledWith('en');
});
it('should default to Spanish if no language is saved in localStorage', () => {
spyOn(localStorage, 'getItem').and.returnValue(null); // Simula que no hay idioma guardado
spyOn(translateService, 'use');
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
expect(localStorage.getItem).toHaveBeenCalledWith('language');
expect(translateService.use).toHaveBeenCalledWith('es');
});
it('should set language to Spanish in sessionStorage on ngOnInit', () => {
spyOn(sessionStorage, 'setItem');
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.componentInstance;
app.ngOnInit();
expect(sessionStorage.setItem).toHaveBeenCalledWith('language', 'es');
});
});

View File

@ -1,4 +1,5 @@
import { Component } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
@Component({
selector: 'app-root',
@ -7,4 +8,11 @@ import { Component } from '@angular/core';
})
export class AppComponent {
title = 'ogWebconsole';
constructor(private translateService: TranslateService) {
const savedLanguage = localStorage.getItem('language') || 'es';
this.translateService.use(savedLanguage);
}
ngOnInit() {
sessionStorage.setItem('language', 'es');
}
}

View File

@ -1,4 +1,5 @@
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { NgModule, CUSTOM_ELEMENTS_SCHEMA, LOCALE_ID, APP_INITIALIZER } from '@angular/core';
import { ConfigService } from './services/config.service';
import { BrowserModule } from '@angular/platform-browser';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
@ -8,7 +9,7 @@ import { HeaderComponent } from './layout/header/header.component';
import { SidebarComponent } from './layout/sidebar/sidebar.component';
import { LoginComponent } from './components/login/login.component';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { HTTP_INTERCEPTORS, provideHttpClient, withInterceptorsFromDi } from '@angular/common/http';
import { HTTP_INTERCEPTORS, HttpClient, provideHttpClient, withInterceptorsFromDi } from '@angular/common/http';
import { CustomInterceptor } from './core/services/custom.interceptor';
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
import { MatToolbarModule } from '@angular/material/toolbar';
@ -25,6 +26,7 @@ import { MatListModule } from '@angular/material/list';
import { UsersComponent } from './components/admin/users/users/users.component';
import { RolesComponent } from './components/admin/roles/roles/roles.component';
import { MatTableModule } from '@angular/material/table';
import { MatButtonToggleModule } from '@angular/material/button-toggle';
import { MatDialogModule } from '@angular/material/dialog';
import { AddUserModalComponent } from './components/admin/users/users/add-user-modal/add-user-modal.component';
import { MatSelectModule } from '@angular/material/select';
@ -32,17 +34,14 @@ import { AddRoleModalComponent } from './components/admin/roles/roles/add-role-m
import { ChangePasswordModalComponent } from './components/admin/users/users/change-password-modal/change-password-modal.component';
import { GroupsComponent } from './components/groups/groups.component';
import { MatDividerModule } from '@angular/material/divider';
import { CreateOrganizationalUnitComponent } from './components/groups/shared/organizational-units/create-organizational-unit/create-organizational-unit.component';
import { MatStepperModule } from '@angular/material/stepper';
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import { CreateClientComponent } from './components/groups/shared/clients/create-client/create-client.component';
import { DeleteModalComponent } from './shared/delete_modal/delete-modal/delete-modal.component';
import { EditOrganizationalUnitComponent } from './components/groups/shared/organizational-units/edit-organizational-unit/edit-organizational-unit.component';
import { EditClientComponent } from './components/groups/shared/clients/edit-client/edit-client.component';
import { ClassroomViewComponent } from './components/groups/shared/classroom-view/classroom-view.component';
import { MatProgressSpinner } from "@angular/material/progress-spinner";
import { MatProgressBarModule } from '@angular/material/progress-bar';
import { MatMenu, MatMenuItem, MatMenuTrigger } from "@angular/material/menu";
import {MatAutocomplete, MatAutocompleteTrigger} from "@angular/material/autocomplete";
import { MatAutocomplete, MatAutocompleteTrigger } from "@angular/material/autocomplete";
import { MatChip, MatChipListbox, MatChipOption, MatChipSet, MatChipsModule } from "@angular/material/chips";
import { ClientViewComponent } from './components/groups/shared/client-view/client-view.component';
import { MatTab, MatTabGroup } from "@angular/material/tabs";
@ -52,7 +51,6 @@ import { DragDropModule } from '@angular/cdk/drag-drop';
import { ToastrModule } from 'ngx-toastr';
import { ShowOrganizationalUnitComponent } from './components/groups/shared/organizational-units/show-organizational-unit/show-organizational-unit.component';
import { MatGridList, MatGridTile } from "@angular/material/grid-list";
import { TreeViewComponent } from './components/groups/shared/tree-view/tree-view.component';
import {
MatNestedTreeNode,
MatTree,
@ -65,7 +63,6 @@ import { LegendComponent } from './components/groups/shared/legend/legend.compon
import { ClassroomViewDialogComponent } from './components/groups/shared/classroom-view/classroom-view-modal';
import { MatPaginator } from "@angular/material/paginator";
import { SaveFiltersDialogComponent } from './components/groups/shared/save-filters-dialog/save-filters-dialog.component';
import { AcctionsModalComponent } from './components/groups/shared/acctions-modal/acctions-modal.component';
import { PXEimagesComponent } from './components/ogboot/pxe-images/pxe-images.component';
import { CreatePXEImageComponent } from './components/ogboot/pxe-images/create-image/create-image/create-image.component';
import { InfoImageComponent } from './components/ogboot/pxe-images/info-image/info-image/info-image.component';
@ -74,12 +71,7 @@ import { CreatePxeTemplateComponent } from './components/ogboot/pxe/create-pxeTe
import { PxeBootFilesComponent } from './components/ogboot/pxe-boot-files/pxe-boot-files.component';
import { MatExpansionPanel, MatExpansionPanelDescription, MatExpansionPanelTitle } from "@angular/material/expansion";
import { OgbootStatusComponent } from './components/ogboot/ogboot-status/ogboot-status.component';
import { CreatePxeBootFileComponent } from './components/ogboot/pxe-boot-files/create-pxeBootFile/create-pxe-boot-file/create-pxe-boot-file.component';
import { NgxChartsModule } from '@swimlane/ngx-charts';
import { OgdhcpComponent } from './components/ogdhcp/ogdhcp.component';
import { OgDhcpSubnetsComponent } from './components/ogdhcp/og-dhcp-subnets/og-dhcp-subnets.component';
import { CreateSubnetComponent } from './components/ogdhcp/og-dhcp-subnets/create-subnet/create-subnet.component';
import { AddClientsToSubnetComponent } from './components/ogdhcp/og-dhcp-subnets/add-clients-to-subnet/add-clients-to-subnet.component';
import { CommandsComponent } from './components/commands/main-commands/commands.component';
import { CommandDetailComponent } from './components/commands/main-commands/detail-command/command-detail.component';
import { CreateCommandComponent } from './components/commands/main-commands/create-command/create-command.component';
@ -87,7 +79,7 @@ import { MatDatepickerModule } from '@angular/material/datepicker';
import { MatNativeDateModule } from '@angular/material/core';
import { CalendarComponent } from './components/calendar/calendar.component';
import { CreateCalendarComponent } from './components/calendar/create-calendar/create-calendar.component';
import {MatRadioButton, MatRadioGroup} from "@angular/material/radio";
import { MatRadioButton, MatRadioGroup } from "@angular/material/radio";
import { CreateCalendarRuleComponent } from './components/calendar/create-calendar-rule/create-calendar-rule.component';
import { CommandsGroupsComponent } from './components/commands/commands-groups/commands-groups.component';
import { CommandsTaskComponent } from './components/commands/commands-task/commands-task.component';
@ -95,18 +87,13 @@ import { CreateCommandGroupComponent } from './components/commands/commands-grou
import { DetailCommandGroupComponent } from './components/commands/commands-groups/detail-command-group/detail-command-group.component';
import { CreateTaskComponent } from './components/commands/commands-task/create-task/create-task.component';
import { DetailTaskComponent } from './components/commands/commands-task/detail-task/detail-task.component';
import { ClientTabViewComponent } from './components/groups/components/client-tab-view/client-tab-view.component';
import { AdvancedSearchComponent } from './components/groups/components/advanced-search/advanced-search.component';
import { TaskLogsComponent } from './components/commands/commands-task/task-logs/task-logs.component';
import { OrganizationalUnitTabViewComponent } from './components/groups/components/organizational-unit-tab-view/organizational-unit-tab-view.component';
import { ServerInfoDialogComponent } from './components/ogdhcp/og-dhcp-subnets/server-info-dialog/server-info-dialog.component';
import { StatusComponent } from './components/ogdhcp/og-dhcp-subnets/status/status.component';
import {MatSliderModule} from '@angular/material/slider';
import { MatSliderModule } from '@angular/material/slider';
import { ClientMainViewComponent } from './components/groups/components/client-main-view/client-main-view.component';
import { ImagesComponent } from './components/images/images.component';
import { CreateImageComponent } from './components/images/create-image/create-image.component';
import { CreateClientImageComponent } from './components/groups/components/client-main-view/create-image/create-image.component';
import { PartitionAssistantComponent } from './components/groups/components/client-main-view/partition-assistant/partition-assistant.component';
import { RestoreImageComponent } from './components/groups/components/client-main-view/restore-image/restore-image.component';
import { SoftwareComponent } from './components/software/software.component';
import { CreateSoftwareComponent } from './components/software/create-software/create-software.component';
import { SoftwareProfileComponent } from './components/software-profile/software-profile.component';
@ -114,8 +101,59 @@ import { CreateSoftwareProfileComponent } from './components/software-profile/cr
import { OperativeSystemComponent } from './components/operative-system/operative-system.component';
import { CreateOperativeSystemComponent } from './components/operative-system/create-operative-system/create-operative-system.component';
import { ShowTemplateContentComponent } from './components/ogboot/pxe/show-template-content/show-template-content.component';
import { AddClientsToPxeComponent } from './components/ogboot/pxe/add-clients-to-pxe/add-clients-to-pxe.component';
import { ClientsComponent } from './components/ogboot/pxe/clients/clients.component';
import { RepositoriesComponent } from './components/repositories/repositories.component';
import { ManageRepositoryComponent } from './components/repositories/manage-repository/manage-repository.component';
import { ExecuteCommandComponent } from './components/commands/main-commands/execute-command/execute-command.component';
import { DeployImageComponent } from './components/groups/components/client-main-view/deploy-image/deploy-image.component';
import { MainRepositoryViewComponent } from './components/repositories/main-repository-view/main-repository-view.component';
import { ExecuteCommandOuComponent } from './components/groups/shared/execute-command-ou/execute-command-ou.component';
import { JoyrideModule } from 'ngx-joyride';
import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
import { TranslateHttpLoader } from '@ngx-translate/http-loader';
import { EnvVarsComponent } from './components/admin/env-vars/env-vars.component';
import { MatSortModule } from '@angular/material/sort';
import { MenusComponent } from './components/menus/menus.component';
import { CreateMenuComponent } from './components/menus/create-menu/create-menu.component';
import { CreateMultipleClientComponent } from './components/groups/shared/clients/create-multiple-client/create-multiple-client.component';
import { ExportImageComponent } from './components/images/export-image/export-image.component';
import { ImportImageComponent } from "./components/repositories/import-image/import-image.component";
import { LoadingComponent } from './shared/loading/loading.component';
import { InputDialogComponent } from './components/commands/commands-task/task-logs/input-dialog/input-dialog.component';
import { ManageOrganizationalUnitComponent } from './components/groups/shared/organizational-units/manage-organizational-unit/manage-organizational-unit.component';
import { BackupImageComponent } from './components/repositories/backup-image/backup-image.component';
import { ServerInfoDialogComponent } from "./components/ogdhcp/server-info-dialog/server-info-dialog.component";
import { StatusComponent } from "./components/ogdhcp/status/status.component";
import { OgDhcpSubnetsComponent } from "./components/ogdhcp/og-dhcp-subnets.component";
import { CreateSubnetComponent } from "./components/ogdhcp/create-subnet/create-subnet.component";
import { AddClientsToSubnetComponent } from "./components/ogdhcp/add-clients-to-subnet/add-clients-to-subnet.component";
import { ShowClientsComponent } from './components/ogdhcp/show-clients/show-clients.component';
import { OperationResultDialogComponent } from './components/ogdhcp/operation-result-dialog/operation-result-dialog.component';
import { ManageClientComponent } from './components/groups/shared/clients/manage-client/manage-client.component';
import { ConvertImageComponent } from './components/repositories/convert-image/convert-image.component';
import { registerLocaleData } from '@angular/common';
import localeEs from '@angular/common/locales/es';
import { GlobalStatusComponent } from './components/global-status/global-status.component';
import { ShowMonoliticImagesComponent } from './components/repositories/show-monolitic-images/show-monolitic-images.component';
import { StatusTabComponent } from './components/global-status/status-tab/status-tab.component';
import { ConvertImageToVirtualComponent } from './components/repositories/convert-image-to-virtual/convert-image-to-virtual.component';
import { RunScriptAssistantComponent } from './components/groups/components/client-main-view/run-script-assistant/run-script-assistant.component';
import {
SaveScriptComponent
} from "./components/groups/components/client-main-view/run-script-assistant/save-script/save-script.component";
import { EditImageComponent } from './components/repositories/edit-image/edit-image.component';
import { ShowGitImagesComponent } from './components/repositories/show-git-images/show-git-images.component';
import { RenameImageComponent } from './components/repositories/rename-image/rename-image.component';
export function HttpLoaderFactory(http: HttpClient) {
return new TranslateHttpLoader(http, './locale/', '.json');
}
export function initializeApp(configService: ConfigService) {
return () => configService.loadConfig();
}
registerLocaleData(localeEs, 'es-ES');
@NgModule({
declarations: [
AppComponent,
@ -132,19 +170,14 @@ import { ClientsComponent } from './components/ogboot/pxe/clients/clients.compon
AddRoleModalComponent,
ChangePasswordModalComponent,
GroupsComponent,
CreateOrganizationalUnitComponent,
CreateClientComponent,
ManageClientComponent,
DeleteModalComponent,
EditOrganizationalUnitComponent,
EditClientComponent,
ClassroomViewComponent,
ClientViewComponent,
ShowOrganizationalUnitComponent,
TreeViewComponent,
LegendComponent,
ClassroomViewDialogComponent,
SaveFiltersDialogComponent,
AcctionsModalComponent,
PXEimagesComponent,
CreatePXEImageComponent,
InfoImageComponent,
@ -152,8 +185,6 @@ import { ClientsComponent } from './components/ogboot/pxe/clients/clients.compon
CreatePxeTemplateComponent,
PxeBootFilesComponent,
OgbootStatusComponent,
CreatePxeBootFileComponent,
OgdhcpComponent,
OgDhcpSubnetsComponent,
CreateSubnetComponent,
AddClientsToSubnetComponent,
@ -162,6 +193,7 @@ import { ClientsComponent } from './components/ogboot/pxe/clients/clients.compon
CreateCommandComponent,
CalendarComponent,
CreateCalendarComponent,
CreateClientImageComponent,
CreateCalendarRuleComponent,
CommandsGroupsComponent,
CommandsTaskComponent,
@ -169,17 +201,13 @@ import { ClientsComponent } from './components/ogboot/pxe/clients/clients.compon
DetailCommandGroupComponent,
CreateTaskComponent,
DetailTaskComponent,
ClientTabViewComponent,
AdvancedSearchComponent,
TaskLogsComponent,
OrganizationalUnitTabViewComponent,
ServerInfoDialogComponent,
StatusComponent,
ClientMainViewComponent,
ImagesComponent,
CreateImageComponent,
PartitionAssistantComponent,
RestoreImageComponent,
SoftwareComponent,
CreateSoftwareComponent,
SoftwareProfileComponent,
@ -187,47 +215,85 @@ import { ClientsComponent } from './components/ogboot/pxe/clients/clients.compon
OperativeSystemComponent,
CreateOperativeSystemComponent,
ShowTemplateContentComponent,
AddClientsToPxeComponent,
ClientsComponent,
RepositoriesComponent,
ManageRepositoryComponent,
ExecuteCommandComponent,
ExecuteCommandOuComponent,
DeployImageComponent,
MainRepositoryViewComponent,
ExecuteCommandOuComponent,
EnvVarsComponent,
MenusComponent,
CreateMenuComponent,
CreateMultipleClientComponent,
ExportImageComponent,
ImportImageComponent,
LoadingComponent,
InputDialogComponent,
ManageOrganizationalUnitComponent,
BackupImageComponent,
ShowClientsComponent,
OperationResultDialogComponent,
ConvertImageComponent,
GlobalStatusComponent,
ShowMonoliticImagesComponent,
StatusTabComponent,
ConvertImageToVirtualComponent,
RunScriptAssistantComponent,
SaveScriptComponent,
EditImageComponent,
ShowGitImagesComponent,
RenameImageComponent
],
bootstrap: [AppComponent],
imports: [BrowserModule,
AppRoutingModule,
FormsModule,
ReactiveFormsModule,
MatToolbarModule,
MatIconModule,
MatButtonModule,
MatSidenavModule,
NoopAnimationsModule,
MatCardModule,
MatCheckboxModule,
MatFormFieldModule,
MatInputModule,
MatListModule,
MatTableModule,
MatDialogModule,
MatSelectModule,
MatDividerModule,
MatStepperModule,
DragDropModule,
MatSlideToggleModule, MatMenu, MatMenuTrigger, MatMenuItem, MatAutocomplete, MatChipListbox, MatChipOption, MatChipSet, MatChipsModule, MatChip, MatProgressSpinner, MatTabGroup, MatTab, MatTooltip,
MatExpansionModule,
NgxChartsModule,
MatDatepickerModule,
MatNativeDateModule,
MatSliderModule,
ToastrModule.forRoot(
{
timeOut: 5000,
positionClass: 'toast-bottom-right',
preventDuplicates: true,
progressBar: true,
progressAnimation: 'increasing',
closeButton: true
}
), MatGridList, MatTree, MatTreeNode, MatNestedTreeNode, MatTreeNodeToggle, MatTreeNodeDef, MatTreeNodePadding, MatTreeNodeOutlet, MatPaginator, MatGridTile, MatExpansionPanel, MatExpansionPanelTitle, MatExpansionPanelDescription, MatRadioGroup, MatRadioButton, MatAutocompleteTrigger
],
imports: [BrowserModule,
AppRoutingModule,
FormsModule,
ReactiveFormsModule,
MatToolbarModule,
MatIconModule,
MatButtonToggleModule,
MatButtonModule,
MatSidenavModule,
NoopAnimationsModule,
MatCardModule,
MatCheckboxModule,
MatFormFieldModule,
MatInputModule,
MatListModule,
MatTableModule,
MatDialogModule,
MatSelectModule,
MatDividerModule,
MatProgressBarModule,
MatStepperModule,
DragDropModule,
MatSlideToggleModule, MatMenu, MatMenuTrigger, MatMenuItem, MatAutocomplete, MatChipListbox, MatChipOption, MatChipSet, MatChipsModule, MatChip, MatProgressSpinner, MatTabGroup, MatTab, MatTooltip,
MatExpansionModule,
NgxChartsModule,
MatDatepickerModule,
MatNativeDateModule,
MatSliderModule,
MatSortModule,
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useFactory: HttpLoaderFactory,
deps: [HttpClient]
}
}),
JoyrideModule.forRoot(),
ToastrModule.forRoot(
{
timeOut: 5000,
positionClass: 'toast-bottom-right',
preventDuplicates: true,
progressBar: true,
progressAnimation: 'increasing',
closeButton: true
}
), MatGridList, MatTree, MatTreeNode, MatNestedTreeNode, MatTreeNodeToggle, MatTreeNodeDef, MatTreeNodePadding, MatTreeNodeOutlet, MatPaginator, MatGridTile, MatExpansionPanel, MatExpansionPanelTitle, MatExpansionPanelDescription, MatRadioGroup, MatRadioButton, MatAutocompleteTrigger
],
schemas: [
CUSTOM_ELEMENTS_SCHEMA,
],
@ -237,8 +303,16 @@ import { ClientsComponent } from './components/ogboot/pxe/clients/clients.compon
useClass: CustomInterceptor,
multi: true
},
{ provide: LOCALE_ID, useValue: 'es-ES' },
provideAnimationsAsync(),
provideHttpClient(withInterceptorsFromDi())
provideHttpClient(withInterceptorsFromDi()),
ConfigService,
{
provide: APP_INITIALIZER,
useFactory: initializeApp,
deps: [ConfigService],
multi: true
}
],
})
export class AppModule { }

View File

@ -14,16 +14,6 @@
margin: 0 10px;
}
/* Estilos de los botones */
button {
height: 150px;
width: 150px;
margin: 5px;
padding: 5px;
cursor: pointer;
transition: all 0.3s;
}
/* Estilos del texto debajo de los botones */
span{
margin: 0;

View File

@ -1,10 +1,10 @@
<div class="container">
<button mat-fab color="primary" class="fab-button" routerLink="/users">
<button class="action-button" routerLink="/users">
<mat-icon>group</mat-icon>
<span i18n="@@labelUsers">Usuarios</span>
<span>{{ 'labelUsers' | translate }}</span>
</button>
<button mat-fab color="primary" class="fab-button" routerLink="/user-groups">
<button class="action-button" routerLink="/user-groups">
<mat-icon>admin_panel_settings</mat-icon>
<span i18n="@@labelRoles">Roles</span>
<span>{{ 'labelRoles' | translate }}</span>
</button>
</div>

View File

@ -3,55 +3,46 @@ import { RouterTestingModule } from '@angular/router/testing';
import { AdminComponent } from './admin.component';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import { By } from '@angular/platform-browser';
import { TranslateModule } from '@ngx-translate/core';
import { Router } from '@angular/router';
describe('AdminComponent', () => {
let component: AdminComponent;
let fixture: ComponentFixture<AdminComponent>;
let router: Router;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [AdminComponent],
imports: [
RouterTestingModule, // Importa RouterTestingModule para manejar routerLink
MatButtonModule, // Importa MatButtonModule para botones
MatIconModule // Importa MatIconModule para iconos
RouterTestingModule,
MatButtonModule,
MatIconModule,
TranslateModule.forRoot()
]
}).compileComponents();
router = TestBed.inject(Router);
});
beforeEach(() => {
fixture = TestBed.createComponent(AdminComponent);
component = fixture.componentInstance;
fixture.detectChanges(); // Detecta cambios para renderizar el componente
fixture.detectChanges();
});
it('debería crear el componente', () => {
expect(component).toBeTruthy();
});
it('debería contener dos botones', () => {
const buttons = fixture.debugElement.queryAll(By.css('button'));
expect(buttons.length).toBe(2); // Verifica que hay dos botones
it('debería renderizar dos botones', () => {
const buttons = fixture.nativeElement.querySelectorAll('button');
expect(buttons.length).toBe(2);
});
it('el primer botón debería tener el texto "Usuarios"', () => {
const firstButton = fixture.debugElement.query(By.css('.fab-button:first-child'));
expect(firstButton.nativeElement.textContent).toContain('Usuarios'); // Verifica que el texto sea "Usuarios"
});
it('el segundo botón debería tener el texto "Roles"', () => {
const secondButton = fixture.debugElement.query(By.css('.fab-button:last-child'));
expect(secondButton.nativeElement.textContent).toContain('Roles'); // Verifica que el texto sea "Roles"
});
it('el primer botón debería tener el routerLink correcto', () => {
const firstButton = fixture.debugElement.query(By.css('.fab-button:first-child'));
expect(firstButton.nativeElement.getAttribute('ng-reflect-router-link')).toBe('/users'); // Verifica el routerLink
});
it('el segundo botón debería tener el routerLink correcto', () => {
const secondButton = fixture.debugElement.query(By.css('.fab-button:last-child'));
expect(secondButton.nativeElement.getAttribute('ng-reflect-router-link')).toBe('/user-groups'); // Verifica el routerLink
it('debería tener un botón con routerLink a "/users"', () => {
const button = fixture.nativeElement.querySelector('button[routerLink="/users"]');
expect(button).toBeTruthy();
expect(button.querySelector('mat-icon').textContent.trim()).toBe('group');
});
});

View File

@ -0,0 +1,30 @@
.env-settings {
padding: 0rem 1rem 0rem 1rem;
.mat-table {
margin-bottom: 16px;
width: 100%;
}
.value-input {
width: 100%;
}
.actions {
display: flex;
gap: 16px;
justify-content: flex-end;
margin-top: 16px;
}
}
.header-container {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px;
}
.mat-elevation-z8 {
box-shadow: 0px 0px 0px rgba(0,0,0,0.2);
}

View File

@ -0,0 +1,33 @@
<div class="env-settings">
<div class="header-container">
<div class="header-container-title">
<h2>Editar Variables de Entorno</h2>
</div>
</div>
<mat-table [dataSource]="envVars" class="mat-elevation-z8">
<!-- Nombre de la variable -->
<ng-container matColumnDef="name">
<mat-header-cell *matHeaderCellDef> Variable </mat-header-cell>
<mat-cell *matCellDef="let variable">{{ variable.name }}</mat-cell>
</ng-container>
<!-- Valor de la variable -->
<ng-container matColumnDef="value">
<mat-header-cell *matHeaderCellDef> Valor </mat-header-cell>
<mat-cell *matCellDef="let variable">
<mat-form-field class="value-input">
<input matInput [(ngModel)]="variable.value" />
</mat-form-field>
</mat-cell>
</ng-container>
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
<mat-row *matRowDef="let row; columns: displayedColumns;"></mat-row>
</mat-table>
<div class="actions">
<button class="action-button" (click)="loadEnvVars()">Recargar</button>
<button class="submit-button" (click)="saveEnvVars()">Guardar Cambios</button>
</div>
</div>

View File

@ -0,0 +1,71 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { EnvVarsComponent } from './env-vars.component';
import { provideHttpClient } from '@angular/common/http';
import { provideHttpClientTesting } from '@angular/common/http/testing';
import { ReactiveFormsModule, FormsModule, FormBuilder } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatDialogModule, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { TranslateModule } from '@ngx-translate/core';
import { ToastrModule, ToastrService } from 'ngx-toastr';
import { DataService } from '../users/users/data.service';
import { MatTableModule } from '@angular/material/table';
import { ConfigService } from '@services/config.service';
describe('EnvVarsComponent', () => {
let component: EnvVarsComponent;
let fixture: ComponentFixture<EnvVarsComponent>;
beforeEach(async () => {
const mockConfigService = {
apiUrl: 'http://mock-api-url'
};
await TestBed.configureTestingModule({
declarations: [EnvVarsComponent],
imports: [
ReactiveFormsModule,
FormsModule,
MatDialogModule,
MatFormFieldModule,
MatInputModule,
MatCheckboxModule,
MatButtonModule,
BrowserAnimationsModule,
MatTableModule,
ToastrModule.forRoot(),
TranslateModule.forRoot()
],
providers: [
FormBuilder,
ToastrService,
DataService,
provideHttpClient(),
provideHttpClientTesting(),
{
provide: MatDialogRef,
useValue: {}
},
{
provide: MAT_DIALOG_DATA,
useValue: {}
},
{
provide: ConfigService,
useValue: mockConfigService
}
]
}).compileComponents();
fixture = TestBed.createComponent(EnvVarsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,56 @@
import { Component } from '@angular/core';
import { HttpClient } from "@angular/common/http";
import { ToastrService } from "ngx-toastr";
import { ConfigService } from '@services/config.service';
@Component({
selector: 'app-env-vars',
templateUrl: './env-vars.component.html',
styleUrl: './env-vars.component.css'
})
export class EnvVarsComponent {
envVars: { name: string; value: string }[] = [];
displayedColumns: string[] = ['name', 'value'];
private apiUrl: string;
constructor(
private http: HttpClient,
private toastService: ToastrService,
private configService: ConfigService
) {
this.apiUrl = `${this.configService.apiUrl}/env-vars`;
}
ngOnInit(): void {
this.loadEnvVars();
}
loadEnvVars(): void {
this.http.get<{ vars: Record<string, string> }>(this.apiUrl).subscribe({
next: (response) => {
this.envVars = Object.entries(response.vars).map(([name, value]) => ({ name, value }));
},
error: (err) => {
console.error('Error al cargar las variables de entorno:', err);
this.toastService.error('No se pudieron cargar las variables de entorno.');
}
});
}
saveEnvVars(): void {
const vars = this.envVars.reduce((acc, variable) => {
acc[variable.name] = variable.value;
return acc;
}, {} as Record<string, string>);
this.http.post(this.apiUrl, { vars }).subscribe({
next: () => {
this.toastService.success('Variables de entorno guardadas correctamente.');
},
error: (err) => {
console.error('Error al guardar las variables de entorno:', err);
this.toastService.error('No se pudieron cargar las variables de entorno.');
}
});
}
}

View File

@ -1,7 +1,9 @@
.full-width {
width: 100%;
}
.form-container {
margin-top: 2em;
padding: 40px;
}
@ -15,16 +17,24 @@
margin-bottom: 16px;
}
.checkbox-group {
.checkbox-group {
margin: 15px 0;
align-items: flex-start;
}
.time-fields {
display: flex;
gap: 15px; /* Espacio entre los campos */
gap: 15px;
/* Espacio entre los campos */
}
.time-field {
flex: 1;
}
.action-container {
display: flex;
justify-content: flex-end;
gap: 1em;
padding: 1.5em;
}

View File

@ -1,22 +1,22 @@
<h1 mat-dialog-title i18n="@@dialogTitleAddRole">Añadir Rol</h1>
<h1 mat-dialog-title>{{ 'dialogTitleAddRole' | translate }}</h1>
<mat-dialog-content class="form-container">
<form [formGroup]="roleForm" class="role-form">
<mat-form-field appearance="fill" class="full-width">
<mat-label i18n="@@labelRoleName">Nombre</mat-label>
<mat-label>{{ 'labelRoleName' | translate }}</mat-label>
<input matInput formControlName="name" required>
</mat-form-field>
<section class="example-section">
<h4 i18n="@@sectionTitlePermissions">Permisos:</h4>
<p><mat-checkbox formControlName="superAdmin" i18n="@@checkboxSuperAdmin">Super Admin</mat-checkbox></p>
<p><mat-checkbox formControlName="orgAdmin" i18n="@@checkboxOrgAdmin">Admin de Unidad Organizativa</mat-checkbox></p>
<p><mat-checkbox formControlName="orgOperator" i18n="@@checkboxOrgOperator">Operador de Unidad Organizativa</mat-checkbox></p>
<p><mat-checkbox formControlName="orgMinimal" i18n="@@checkboxOrgMinimal">Unidad Organizativa Mínima</mat-checkbox></p>
<p><mat-checkbox formControlName="userRole" i18n="@@checkboxUserRole">Usuario</mat-checkbox></p>
<h4>{{ 'sectionTitlePermissions' | translate }}</h4>
<p><mat-checkbox formControlName="superAdmin">{{ 'checkboxSuperAdmin' | translate }}</mat-checkbox></p>
<p><mat-checkbox formControlName="orgAdmin">{{ 'checkboxOrgAdmin' | translate }}</mat-checkbox></p>
<p><mat-checkbox formControlName="orgOperator">{{ 'checkboxOrgOperator' | translate }}</mat-checkbox></p>
<p><mat-checkbox formControlName="orgMinimal">{{ 'checkboxOrgMinimal' | translate }}</mat-checkbox></p>
<p><mat-checkbox formControlName="userRole">{{ 'checkboxUserRole' | translate }}</mat-checkbox></p>
</section>
</form>
</mat-dialog-content>
<mat-dialog-actions align="end">
<button mat-button (click)="onNoClick()" i18n="@@buttonCancel">Cancelar</button>
<button mat-button (click)="onSubmit()" i18n="@@buttonAdd">Añadir</button>
<mat-dialog-actions class="action-container">
<button class="ordinary-button" (click)="onNoClick()">{{ 'buttonCancel' | translate }}</button>
<button class="submit-button" (click)="onSubmit()">{{ 'buttonAdd' | translate }}</button>
</mat-dialog-actions>

View File

@ -4,6 +4,7 @@ import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import {FormBuilder, FormGroup, Validators} from '@angular/forms';
import {DataService} from "../data.service";
import {ToastrService} from "ngx-toastr";
import { ConfigService } from '@services/config.service';
@Component({
selector: 'app-add-role-modal',
@ -11,9 +12,9 @@ import {ToastrService} from "ngx-toastr";
styleUrls: ['./add-role-modal.component.css']
})
export class AddRoleModalComponent {
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL;
roleForm: FormGroup<any>;
roleId: string | null = null;
baseUrl: string;
constructor(
public dialogRef: MatDialogRef<AddRoleModalComponent>,
@ -21,8 +22,10 @@ export class AddRoleModalComponent {
private http: HttpClient,
private fb: FormBuilder,
private dataService: DataService,
private toastService: ToastrService
private toastService: ToastrService,
private configService: ConfigService
) {
this.baseUrl = this.configService.apiUrl;
this.roleForm = this.fb.group({
name: ['', Validators.required],
superAdmin: [false],

View File

@ -2,15 +2,19 @@ import { Injectable } from '@angular/core';
import {HttpClient, HttpParams} from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError, map } from 'rxjs/operators';
import { ConfigService } from '@services/config.service';
@Injectable({
providedIn: 'root'
})
export class DataService {
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL;
private apiUrl = `${this.baseUrl}/user-groups?page=1&itemsPerPage=1000`;
baseUrl: string;
private apiUrl: string;
constructor(private http: HttpClient) {}
constructor(private http: HttpClient, private configService: ConfigService) {
this.baseUrl = this.configService.apiUrl;
this.apiUrl = `${this.baseUrl}/user-groups?page=1&itemsPerPage=1000`;
}
getUserGroups(filters: { [key: string]: string }): Observable<{ totalItems: any; data: any }> {
const params = new HttpParams({ fromObject: filters });

View File

@ -1,21 +1,30 @@
table {
width: 100%;
margin-top: 50px;
.header-container {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 10px;
border-bottom: 1px solid #ddd;
}
.search-container {
.header-container-title {
flex-grow: 1;
text-align: left;
margin-left: 1em;
}
table {
width: 100%;
}
.search-container {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
padding: 0 5px;
margin: 1.5rem 0rem 1.5rem 0rem;
box-sizing: border-box;
}
.divider {
margin: 20px 0;
}
.search-string {
flex: 2;
padding: 5px;
@ -26,15 +35,8 @@ table {
padding: 5px;
}
.header-container {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px;
}
.mat-elevation-z8 {
box-shadow: 0px 0px 0px rgba(0,0,0,0.2);
box-shadow: 0px 0px 0px rgba(0, 0, 0, 0.2);
}
.paginator-container {
@ -42,4 +44,3 @@ table {
justify-content: end;
margin-bottom: 30px;
}

View File

@ -1,39 +1,52 @@
<div class="header-container">
<h2 class="title" i18n="@@adminImagesTitle">Administrar Roles</h2>
<div class="header-container-title">
<h2>{{ 'adminRolesTitle' | translate }}</h2>
</div>
<div class="images-button-row">
<button mat-flat-button color="primary" (click)="addUser()">Añadir rol</button>
<button class="action-button" (click)="addUser()">
{{ 'addRole' | translate }}
</button>
</div>
</div>
<mat-divider class="divider"></mat-divider>
<div class="search-container">
<mat-form-field appearance="fill" class="search-string">
<mat-label i18n="@@searchLabel">Buscar nombre de rol</mat-label>
<input matInput placeholder="Búsqueda" [(ngModel)]="filters['name']" (keyup.enter)="search()" i18n-placeholder="@@searchPlaceholder">
<mat-label>{{ 'searchRoleLabel' | translate }}</mat-label>
<input matInput placeholder="{{ 'searchPlaceholder' | translate }}" [(ngModel)]="filters['name']"
(keyup.enter)="search()">
<mat-icon matSuffix>search</mat-icon>
<mat-hint i18n="@@searchHint">Pulsar 'enter' para buscar</mat-hint>
<mat-hint>{{ 'searchHint' | translate }}</mat-hint>
</mat-form-field>
</div>
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8">
<ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef">
<th mat-header-cell *matHeaderCellDef> {{ column.header }} </th>
<td mat-cell *matCellDef="let role"> {{ column.cell(role) }} </td>
</ng-container>
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef i18n="@@columnActions" style="text-align: center;">Acciones</th>
<td mat-cell *matCellDef="let role" style="text-align: center;">
<button mat-icon-button color="primary" (click)="editRole(role)" i18n="@@editImage"> <mat-icon>edit</mat-icon></button>
<button mat-icon-button color="warn" (click)="deleteRole(role)" i18n="@@buttonDelete" [disabled]="role.permissions.includes('ROLE_SUPER_ADMIN')"><mat-icon>delete</mat-icon></button>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
<app-loading [isLoading]="loading"></app-loading>
<div *ngIf="!loading">
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8">
<ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef">
<th mat-header-cell *matHeaderCellDef> {{ column.header }} </th>
<td mat-cell *matCellDef="let role"> {{ column.cell(role) }} </td>
</ng-container>
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef style="text-align: center;">{{ 'columnActions' | translate }}</th>
<td mat-cell *matCellDef="let role" style="text-align: center;">
<button mat-icon-button color="primary" (click)="editRole(role)">
<mat-icon>edit</mat-icon>
</button>
<button mat-icon-button color="warn" (click)="deleteRole(role)"
[disabled]="role.permissions.includes('ROLE_SUPER_ADMIN')">
<mat-icon>delete</mat-icon>
</button>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
</div>
<div class="paginator-container">
<mat-paginator [length]="length"
[pageSize]="itemsPerPage"
[pageIndex]="page"
[pageSizeOptions]="pageSizeOptions"
(page)="onPageChange($event)">
<mat-paginator [length]="length" [pageSize]="itemsPerPage" [pageIndex]="page" [pageSizeOptions]="pageSizeOptions"
(page)="onPageChange($event)">
</mat-paginator>
</div>

View File

@ -4,13 +4,15 @@ import { MatDialog } from '@angular/material/dialog';
import { HttpClient } from '@angular/common/http';
import { ToastrService } from 'ngx-toastr';
import { DataService } from './data.service';
import { of } from 'rxjs';
import { ConfigService } from '@services/config.service';
import { MatDivider } from '@angular/material/divider';
import { MatFormField } from '@angular/material/form-field';
import { MatLabel } from '@angular/material/form-field';
import { MatIcon } from '@angular/material/icon';
import { MatHint } from '@angular/material/form-field';
import { MatPaginator } from '@angular/material/paginator';
import { TranslateModule } from '@ngx-translate/core';
import { LoadingComponent } from '../../../../shared/loading/loading.component';
describe('RolesComponent', () => {
let component: RolesComponent;
let fixture: ComponentFixture<RolesComponent>;
@ -18,21 +20,25 @@ describe('RolesComponent', () => {
let mockHttpClient: jasmine.SpyObj<HttpClient>;
let mockToastrService: jasmine.SpyObj<ToastrService>;
let mockDataService: jasmine.SpyObj<DataService>;
let mockConfigService: jasmine.SpyObj<ConfigService>;
beforeEach(async () => {
const matDialogSpy = jasmine.createSpyObj('MatDialog', ['open']);
const httpClientSpy = jasmine.createSpyObj('HttpClient', ['get', 'post', 'put', 'delete']);
const toastrServiceSpy = jasmine.createSpyObj('ToastrService', ['success', 'error']);
const dataServiceSpy = jasmine.createSpyObj('DataService', ['getRoles']);
const configServiceSpy = jasmine.createSpyObj('ConfigService', [], { apiUrl: 'http://mock-api-url' });
await TestBed.configureTestingModule({
declarations: [RolesComponent],
imports: [MatDivider, MatFormField, MatLabel, MatIcon, MatHint, MatPaginator],
declarations: [RolesComponent, LoadingComponent],
imports: [MatDivider, MatFormField, MatLabel, MatIcon, MatHint, MatPaginator,
TranslateModule.forRoot()],
providers: [
{ provide: MatDialog, useValue: matDialogSpy },
{ provide: HttpClient, useValue: httpClientSpy },
{ provide: ToastrService, useValue: toastrServiceSpy },
{ provide: DataService, useValue: dataServiceSpy }
{ provide: DataService, useValue: dataServiceSpy },
{ provide: ConfigService, useValue: configServiceSpy }
]
}).compileComponents();
});
@ -44,9 +50,24 @@ describe('RolesComponent', () => {
mockHttpClient = TestBed.inject(HttpClient) as jasmine.SpyObj<HttpClient>;
mockToastrService = TestBed.inject(ToastrService) as jasmine.SpyObj<ToastrService>;
mockDataService = TestBed.inject(DataService) as jasmine.SpyObj<DataService>;
mockConfigService = TestBed.inject(ConfigService) as jasmine.SpyObj<ConfigService>;
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should have a default itemsPerPage value', () => {
expect(component.itemsPerPage).toBeDefined();
});
it('should initialize the dataSource', () => {
expect(component.dataSource).toBeDefined();
});
it('should have a defined columns array', () => {
expect(component.columns).toBeDefined();
expect(component.columns.length).toBeGreaterThan(0);
});
});

View File

@ -1,13 +1,13 @@
import { Component, OnInit } from '@angular/core';
import { MatTableDataSource } from '@angular/material/table';
import { MatDialog } from '@angular/material/dialog';
import { AddRoleModalComponent } from './add-role-modal/add-role-modal.component';
import { DeleteModalComponent } from '../../../../shared/delete_modal/delete-modal/delete-modal.component';
import { HttpClient } from '@angular/common/http';
import { ToastrService } from 'ngx-toastr';
import {DataService} from "./data.service";
import {CreateCalendarComponent} from "../../../calendar/create-calendar/create-calendar.component";
import {PageEvent} from "@angular/material/paginator";
import { DataService } from "./data.service";
import { PageEvent } from "@angular/material/paginator";
import { DeleteModalComponent } from '../../../../shared/delete_modal/delete-modal/delete-modal.component';
import { AddRoleModalComponent } from './add-role-modal/add-role-modal.component';
import { ConfigService } from '@services/config.service';
@Component({
selector: 'app-roles',
@ -15,10 +15,10 @@ import {PageEvent} from "@angular/material/paginator";
styleUrls: ['./roles.component.css']
})
export class RolesComponent implements OnInit {
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL;
baseUrl: string = this.configService.apiUrl;
dataSource = new MatTableDataSource<any>();
filters: { [key: string]: string } = {};
loading:boolean = false;
loading: boolean = false;
length: number = 0;
itemsPerPage: number = 10;
page: number = 0;
@ -49,7 +49,8 @@ export class RolesComponent implements OnInit {
public dialog: MatDialog,
private http: HttpClient,
private dataService: DataService,
private toastService: ToastrService
private toastService: ToastrService,
private configService: ConfigService
) {}
ngOnInit() {
@ -57,13 +58,16 @@ export class RolesComponent implements OnInit {
}
search() {
this.loading = true;
this.http.get<any>(`${this.apiUrl}?&page=${this.page + 1}&itemsPerPage=${this.itemsPerPage}`, { params: this.filters }).subscribe(
(data) => {
this.dataSource.data = data['hydra:member'];
this.length = data['hydra:totalItems'];
this.loading = false;
},
(error) => {
console.error('Error fetching commands', error);
console.error('Error fetching roles', error);
this.loading = false;
}
);
}
@ -114,7 +118,7 @@ export class RolesComponent implements OnInit {
});
}
onPageChange(event: any): void {
onPageChange(event: PageEvent): void {
this.page = event.pageIndex;
this.itemsPerPage = event.pageSize;
this.length = event.length;

View File

@ -1,30 +1,12 @@
.full-width {
width: 100%;
}
.form-container {
padding: 40px;
}
.form-group {
margin-top: 20px;
margin-bottom: 26px;
}
.full-width {
width: 100%;
margin-bottom: 16px;
}
.checkbox-group {
margin: 15px 0;
align-items: flex-start;
}
.time-fields {
.user-form {
display: flex;
gap: 15px; /* Espacio entre los campos */
flex-direction: column;
margin-top: 2rem;
}
.time-field {
flex: 1;
.action-container {
display: flex;
justify-content: flex-end;
gap: 1em;
padding: 1.5em;
}

View File

@ -1,17 +1,19 @@
<h1 mat-dialog-title i18n="@@dialogTitleAddRole">Añadir Usuario</h1>
<app-loading [isLoading]="loading"></app-loading>
<h1 mat-dialog-title>{{ isEditMode ? ('dialogTitleEditUser' | translate) : ('dialogTitleAddUser' | translate) }}</h1>
<mat-dialog-content class="form-container">
<form [formGroup]="userForm" class="user-form">
<mat-form-field appearance="fill" class="full-width">
<mat-label i18n="@@addUserlabelUsername">Nombre de usuario</mat-label>
<mat-label>{{ 'addUserlabelUsername' | translate }}</mat-label>
<input matInput formControlName="username" required>
</mat-form-field>
<mat-form-field appearance="fill" class="full-width">
<mat-label i18n="@@addUserlabelPassword">Contraseña</mat-label>
<mat-label>{{ 'addUserlabelPassword' | translate }}</mat-label>
<input matInput formControlName="password" type="password" required>
</mat-form-field>
<mat-form-field appearance="fill" class="full-width">
<mat-label i18n="@@labelRole">Rol</mat-label>
<mat-label>{{ 'labelRole' | translate }}</mat-label>
<mat-select formControlName="role" required>
<mat-option *ngFor="let group of userGroups" [value]="group['@id']">
{{ group.name }}
@ -20,16 +22,25 @@
</mat-form-field>
<mat-form-field appearance="fill" class="full-width">
<mat-label i18n="@@labelOrganizationalUnit">Unidad organiativa</mat-label>
<mat-label>{{ 'labelOrganizationalUnit' | translate }}</mat-label>
<mat-select multiple formControlName="organizationalUnits">
<mat-option *ngFor="let unit of organizationalUnits" [value]="unit['@id']">
{{unit.name}}
{{ unit.name }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field appearance="fill" class="full-width">
<mat-label>Vista tarjetas</mat-label>
<mat-select formControlName="groupsView" required>
<mat-option *ngFor="let option of views" [value]="option.value">
{{ option.name }}
</mat-option>
</mat-select>
</mat-form-field>
</form>
</mat-dialog-content>
<mat-dialog-actions align="end">
<button mat-button (click)="onNoClick()" i18n="@@buttonCancel">Cancelar</button>
<button mat-button (click)="onSubmit()" i18n="@@buttonAdd">Añadir</button>
<mat-dialog-actions class="action-container">
<button class="ordinary-button" (click)="onNoClick()">{{ 'buttonCancel' | translate }}</button>
<button class="submit-button" (click)="onSubmit()" [disabled]="userForm.invalid">{{ isEditMode ? ('buttonEdit' | translate) : ('buttonAdd' | translate) }}</button>
</mat-dialog-actions>

View File

@ -1,9 +1,10 @@
import { Component, EventEmitter, Inject, OnInit, Output } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import {ToastrService} from "ngx-toastr";
import {HttpClient} from "@angular/common/http";
import {DataService} from "../data.service";
import { ToastrService } from "ngx-toastr";
import { HttpClient } from "@angular/common/http";
import { DataService } from "../data.service";
import { ConfigService } from '@services/config.service';
interface UserGroup {
'@id': string;
@ -17,12 +18,20 @@ interface UserGroup {
styleUrls: ['./add-user-modal.component.css']
})
export class AddUserModalComponent implements OnInit {
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL;
baseUrl: string;
@Output() userAdded = new EventEmitter<void>();
@Output() userEdited = new EventEmitter<void>();
userForm: FormGroup<any>;
userGroups: UserGroup[] = [];
organizationalUnits: any[] = [];
userId: string | null = null;
loading: boolean = false;
isEditMode: boolean = false;
protected views = [
{ value: 'card', name: 'Tarjetas' },
{ value: 'list', name: 'Listado' },
];
constructor(
public dialogRef: MatDialogRef<AddUserModalComponent>,
@ -30,46 +39,60 @@ export class AddUserModalComponent implements OnInit {
private http: HttpClient,
private fb: FormBuilder,
private dataService: DataService,
private toastService: ToastrService
private toastService: ToastrService,
private configService: ConfigService
) {
this.baseUrl = this.configService.apiUrl;
this.userForm = this.fb.group({
username: ['', Validators.required],
password: ['', Validators.required],
role: ['', Validators.required],
groupsView: ['card', Validators.required],
organizationalUnits: [[]]
});
if (data) {
this.isEditMode = true;
}
}
ngOnInit(): void {
this.dataService.getUserGroups().subscribe((data) => {
this.userGroups = data['hydra:member'];
});
this.dataService.getOrganizationalUnits().subscribe((data) => {
this.organizationalUnits = data['hydra:member'].filter((item: any) => item.type === 'organizational-unit');
});
if (this.data) {
this.load()
this.load();
} else {
this.userForm.get('password')?.setValidators([Validators.required]);
}
}
load(): void {
this.loading = true;
this.dataService.getUser(this.data).subscribe({
next: (response) => {
console.log(response);
const organizationalUnitIds = response.allowedOrganizationalUnits.map((unit: any) => unit['@id']);
// Patch the values to the form
this.userForm.patchValue({
username: response.username,
role: response.userGroups[0]['@id'],
organizationalUnits: organizationalUnitIds
role: response.userGroups.length > 0 ? response.userGroups[0]['@id'] : null,
organizationalUnits: organizationalUnitIds,
groupsView: response.groupsView
});
this.userId = response['@id'];
this.userForm.get('password')?.clearValidators();
this.userForm.get('password')?.updateValueAndValidity();
this.loading = false;
},
error: (err) => {
console.error('Error fetching remote calendar:', err);
this.loading = false;
console.error('Error fetching user:', err);
}
});
}
@ -80,34 +103,46 @@ export class AddUserModalComponent implements OnInit {
onSubmit(): void {
if (this.userForm.valid) {
const payload = {
const payload: any = {
username: this.userForm.value.username,
allowedOrganizationalUnits: this.userForm.value.organizationalUnit,
password: this.userForm.value.password,
allowedOrganizationalUnits: this.userForm.value.organizationalUnits,
enabled: true,
userGroups: [this.userForm.value.role ]
userGroups: [this.userForm.value.role],
groupsView: this.userForm.value.groupsView
};
if (!this.userId && this.userForm.value.password) {
payload.password = this.userForm.value.password;
} else if (this.userId && this.userForm.value.password.trim() !== '') {
payload.password = this.userForm.value.password;
}
this.loading = true;
if (this.userId) {
this.http.put(`${this.baseUrl}${this.userId}`, payload).subscribe(
(response) => {
this.toastService.success('Usuario editado correctamente');
this.userEdited.emit();
this.dialogRef.close();
this.loading = false;
},
(error) => {
this.toastService.error(error['error']['hydra:description']);
console.error('Error al editar el rol', error);
this.loading = false;
}
);
} else {
this.http.post(`${this.baseUrl}/users`, payload).subscribe(
(response) => {
this.toastService.success('Usuario añadido correctamente');
this.userAdded.emit();
this.dialogRef.close();
this.loading = false;
},
(error) => {
this.toastService.error(error['error']['hydra:description']);
console.error('Error al añadir añadido', error);
this.loading = false;
}
);
}

View File

@ -1,6 +1,5 @@
.user-form .form-field {
display: block;
margin-bottom: 10px;
display: block;
}
.checkbox-group label {
@ -17,3 +16,14 @@ mat-spinner {
margin: 0 auto;
align-self: center;
}
.action-container {
display: flex;
justify-content: flex-end;
gap: 1em;
padding: 1.5em;
}
.form-container {
margin-top: 2em;
}

View File

@ -1,29 +1,29 @@
<h1 mat-dialog-title i18n="@@dialogTitleEditUser">Editar Usuario</h1>
<h1 mat-dialog-title>{{ 'dialogTitleChangePassword' | translate }}</h1>
<mat-dialog-content class="form-container">
<form [formGroup]="userForm" class="user-form">
<mat-form-field class="form-field">
<mat-label i18n="@@labelCurrentPassword">Contraseña actual</mat-label>
<mat-label>{{ 'labelCurrentPassword' | translate }}</mat-label>
<input matInput formControlName="currentPassword" type="password">
</mat-form-field>
<mat-form-field class="form-field">
<mat-label i18n="@@labelNewPassword">Nueva contraseña</mat-label>
<mat-label>{{ 'labelNewPassword' | translate }}</mat-label>
<input matInput formControlName="newPassword" type="password">
</mat-form-field>
<mat-form-field class="form-field">
<mat-label i18n="@@labelRepeatPassword">Repite la contraseña</mat-label>
<mat-label>{{ 'labelRepeatPassword' | translate }}</mat-label>
<input matInput formControlName="repeatNewPassword" type="password">
</mat-form-field>
<div class="error-message" *ngIf="passwordMismatch" i18n="@@errorPasswordMismatch">
Las contraseñas no coinciden
<div class="error-message" *ngIf="passwordMismatch">
{{ 'errorPasswordMismatch' | translate }}
</div>
<div class="error-message" *ngIf="updateError" i18n="@@errorUpdate">
<div class="error-message" *ngIf="updateError">
{{ resetPasswordError }}
</div>
</form>
</mat-dialog-content>
<mat-dialog-actions align="end">
<button mat-button (click)="onNoClick()" i18n="@@buttonCancel">Cancelar</button>
<button mat-button (click)="onSubmit()" [disabled]="loading" i18n="@@buttonEdit">Editar</button>
<mat-dialog-actions class="action-container">
<button class="ordinary-button" (click)="onNoClick()">{{ 'buttonCancel' | translate }}</button>
<button class="submit-button" (click)="onSubmit()" [disabled]="loading">{{ 'buttonEdit' | translate }}</button>
</mat-dialog-actions>

View File

@ -3,15 +3,19 @@ import { Injectable } from '@angular/core';
import {HttpClient, HttpParams} from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError, map } from 'rxjs/operators';
import { ConfigService } from '@services/config.service';
@Injectable({
providedIn: 'root'
})
export class DataService {
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL;
private apiUrl = `${this.baseUrl}/users?page=1&itemsPerPage=1000`;
baseUrl: string;
private apiUrl: string;
constructor(private http: HttpClient) {}
constructor(private http: HttpClient, private configService: ConfigService) {
this.baseUrl = this.configService.apiUrl;
this.apiUrl = `${this.baseUrl}/users?page=1&itemsPerPage=1000`;
}
getUsers(filters: { [key: string]: string }): Observable<{ totalItems: any; data: any }> {
const params = new HttpParams({ fromObject: filters });

View File

@ -1,6 +1,19 @@
.header-container {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 10px;
border-bottom: 1px solid #ddd;
}
.header-container-title {
flex-grow: 1;
text-align: left;
margin-left: 1em;
}
table {
width: 100%;
margin-top: 50px;
}
.search-container {
@ -8,14 +21,10 @@ table {
justify-content: space-between;
align-items: center;
width: 100%;
padding: 0 5px;
margin: 1.5rem 0rem 1.5rem 0rem;
box-sizing: border-box;
}
.divider {
margin: 20px 0;
}
.search-string {
flex: 2;
padding: 5px;
@ -26,13 +35,6 @@ table {
padding: 5px;
}
.header-container {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px;
}
.mat-elevation-z8 {
box-shadow: 0px 0px 0px rgba(0,0,0,0.2);
}

View File

@ -1,40 +1,60 @@
<div class="header-container">
<h2 class="title" i18n="@@adminImagesTitle">Administrar usuarios</h2>
<div class="header-container-title">
<h2>{{ 'adminUsersTitle' | translate }}</h2>
</div>
<div class="images-button-row">
<button mat-flat-button color="primary" (click)="addUser()">Añadir usuarios</button>
<button class="action-button" (click)="addUser()">
{{ 'addUser' | translate }}
</button>
</div>
</div>
<mat-divider class="divider"></mat-divider>
<div class="search-container">
<mat-form-field appearance="fill" class="search-string">
<mat-label i18n="@@searchLabel">Buscar nombre de usuario</mat-label>
<input matInput placeholder="Búsqueda" [(ngModel)]="filters['name']" (keyup.enter)="search()" i18n-placeholder="@@searchPlaceholder">
<mat-label>{{ 'searchLabel' | translate }}</mat-label>
<input matInput placeholder="{{ 'searchPlaceholder' | translate }}" [(ngModel)]="filters['name']"
(keyup.enter)="search()">
<mat-icon matSuffix>search</mat-icon>
<mat-hint i18n="@@searchHint">Pulsar 'enter' para buscar</mat-hint>
<mat-hint>{{ 'searchHint' | translate }}</mat-hint>
</mat-form-field>
</div>
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8">
<ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef">
<th mat-header-cell *matHeaderCellDef> {{ column.header }} </th>
<td mat-cell *matCellDef="let user"> {{ column.cell(user) }} </td>
</ng-container>
<app-loading [isLoading]="loading"></app-loading>
<div *ngIf="!loading">
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8">
<ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef">
<th mat-header-cell *matHeaderCellDef> {{ column.header }} </th>
<td mat-cell *matCellDef="let user">
<ng-container *ngIf="column.columnDef === 'groupsView'">
<mat-chip>
{{ user[column.columnDef] === 'card' ? 'Vista tarjetas' : 'Listado' }}
</mat-chip>
</ng-container>
<ng-container *ngIf="column.columnDef !== 'groupsView'">
{{ column.cell(user) }}
</ng-container>
</td>
</ng-container>
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef style="text-align: center;">{{ 'columnActions' | translate }}</th>
<td mat-cell *matCellDef="let user" style="text-align: center;">
<button mat-icon-button color="primary" (click)="editUser(user)">
<mat-icon>edit</mat-icon>
</button>
<button mat-icon-button color="warn" (click)="deleteUser(user)">
<mat-icon>delete</mat-icon>
</button>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
</div>
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef i18n="@@columnActions" style="text-align: center;">Acciones</th>
<td mat-cell *matCellDef="let user" style="text-align: center;">
<button mat-icon-button color="primary" (click)="editUser(user)" i18n="@@editImage"> <mat-icon>edit</mat-icon></button>
<button mat-icon-button color="warn" (click)="deleteUser(user)" i18n="@@buttonDelete"><mat-icon>delete</mat-icon></button>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
<div class="paginator-container">
<mat-paginator [length]="length"
[pageSize]="itemsPerPage"
[pageIndex]="page"
[pageSizeOptions]="pageSizeOptions"
(page)="onPageChange($event)">
<mat-paginator [length]="length" [pageSize]="itemsPerPage" [pageIndex]="page" [pageSizeOptions]="pageSizeOptions"
(page)="onPageChange($event)">
</mat-paginator>
</div>

View File

@ -6,16 +6,12 @@ import { HttpClientTestingModule } from '@angular/common/http/testing';
import { ToastrService } from 'ngx-toastr';
import { of } from 'rxjs';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { TranslateModule } from '@ngx-translate/core';
import { ConfigService } from '@services/config.service';
class MockUserService {
getUsers() {
return of({
'hydra:member': [
{ id: 1, username: 'user1', allowedOrganizationalUnits: [], roles: ['admin'] },
{ id: 2, username: 'user2', allowedOrganizationalUnits: [], roles: ['user'] }
]
});
}
class MockToastrService {
success() {}
error() {}
}
describe('UsersComponent', () => {
@ -23,20 +19,25 @@ describe('UsersComponent', () => {
let fixture: ComponentFixture<UsersComponent>;
beforeEach(async () => {
const mockConfigService = {
apiUrl: 'http://mock-api-url',
mercureUrl: 'http://mock-mercure-url'
};
await TestBed.configureTestingModule({
declarations: [UsersComponent],
imports: [
MatTableModule,
MatDialogModule,
HttpClientTestingModule,
TranslateModule.forRoot(),
],
providers: [
{ useClass: MockUserService },
{ provide: ToastrService, useValue: { success: () => {} } },
{ provide: ToastrService, useClass: MockToastrService },
{ provide: ConfigService, useValue: mockConfigService }
],
schemas: [NO_ERRORS_SCHEMA],
})
.compileComponents();
schemas: [NO_ERRORS_SCHEMA], // Ignorar elementos desconocidos
}).compileComponents();
fixture = TestBed.createComponent(UsersComponent);
component = fixture.componentInstance;
@ -47,4 +48,27 @@ describe('UsersComponent', () => {
expect(component).toBeTruthy();
});
it('should have default values for pagination', () => {
expect(component.itemsPerPage).toBe(10);
expect(component.page).toBe(0);
expect(component.pageSizeOptions).toEqual([5, 10, 20, 40, 100]);
});
it('should call search on init', () => {
spyOn(component, 'search');
component.ngOnInit();
expect(component.search).toHaveBeenCalled();
});
it('should initialize the dataSource', () => {
expect(component.dataSource).toBeDefined();
});
it('should define displayedColumns', () => {
expect(component.displayedColumns).toBeDefined();
expect(component.displayedColumns).toContain('id');
expect(component.displayedColumns).toContain('username');
expect(component.displayedColumns).toContain('roles');
expect(component.displayedColumns).toContain('actions');
});
});

View File

@ -5,20 +5,19 @@ import { AddUserModalComponent } from './add-user-modal/add-user-modal.component
import { DeleteModalComponent } from '../../../../shared/delete_modal/delete-modal/delete-modal.component';
import { HttpClient } from '@angular/common/http';
import { ToastrService } from 'ngx-toastr';
import {DataService} from "./data.service";
import {AddRoleModalComponent} from "../../roles/roles/add-role-modal/add-role-modal.component";
import { ConfigService } from '@services/config.service';
@Component({
selector: 'app-users',
templateUrl: './users.component.html',
styleUrls: ['./users.component.css']
})
export class UsersComponent implements OnInit {
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL;
baseUrl: string;
private apiUrl: string;
dataSource = new MatTableDataSource<any>();
filters: { [key: string]: string } = {};
loading:boolean = false;
loading: boolean = false;
length: number = 0;
itemsPerPage: number = 10;
page: number = 0;
@ -34,6 +33,11 @@ export class UsersComponent implements OnInit {
header: 'Nombre de Usuario',
cell: (user: any) => `${user.username}`
},
{
columnDef: 'groupsView',
header: 'Vista de Grupos',
cell: (user: any) => `${user.groupsView}`
},
{
columnDef: 'allowedOrganizationalUnits',
header: 'Unidades Organizacionales Permitidas',
@ -47,27 +51,31 @@ export class UsersComponent implements OnInit {
];
displayedColumns = [...this.columns.map(column => column.columnDef), 'actions'];
private apiUrl = `${this.baseUrl}/users`;
constructor(
public dialog: MatDialog,
private configService: ConfigService,
private http: HttpClient,
private dataService: DataService,
private toastService: ToastrService
) {}
) {
this.baseUrl = this.configService.apiUrl;
this.apiUrl = `${this.baseUrl}/users`;
}
ngOnInit() {
this.search();
}
search() {
this.loading = true;
this.http.get<any>(`${this.apiUrl}?&page=${this.page + 1}&itemsPerPage=${this.itemsPerPage}`, { params: this.filters }).subscribe(
(data) => {
this.dataSource.data = data['hydra:member'];
this.length = data['hydra:totalItems'];
this.loading = false;
},
(error) => {
console.error('Error fetching commands', error);
console.error('Error fetching users', error);
this.loading = false;
}
);
}
@ -78,7 +86,6 @@ export class UsersComponent implements OnInit {
dialogRef.componentInstance.userAdded.subscribe(() => {
this.search();
});
}
editUser(user: any): void {
@ -87,6 +94,10 @@ export class UsersComponent implements OnInit {
data: user['@id']
});
dialogRef.componentInstance.userEdited.subscribe(() => {
this.search();
});
dialogRef.afterClosed().subscribe(result => {
if (result) {
this.search();
@ -114,8 +125,6 @@ export class UsersComponent implements OnInit {
console.error('Error deleting user:', error);
}
});
} else {
console.log('User deletion cancelled');
}
});
}
@ -126,5 +135,4 @@ export class UsersComponent implements OnInit {
this.length = event.length;
this.search();
}
}

View File

@ -1,15 +1,20 @@
.title {
font-size: 24px;
.header-container {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 10px;
border-bottom: 1px solid #ddd;
}
.header-container-title {
flex-grow: 1;
text-align: left;
margin-left: 1em;
}
.calendar-button-row {
display: flex;
justify-content: flex-start;
margin-top: 16px;
}
.divider {
margin: 20px 0;
gap: 15px;
}
.lists-container {
@ -23,15 +28,14 @@
table {
width: 100%;
margin-top: 50px;
}
.search-container {
.search-container {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
padding: 0 5px;
margin: 1.5rem 0rem 1.5rem 0rem;
box-sizing: border-box;
}
@ -45,15 +49,8 @@ table {
padding: 5px;
}
.header-container {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px;
}
.mat-elevation-z8 {
box-shadow: 0px 0px 0px rgba(0,0,0,0.2);
box-shadow: 0px 0px 0px rgba(0, 0, 0, 0.2);
}
.paginator-container {

View File

@ -1,52 +1,74 @@
<div class="header-container">
<h2 class="title" i18n="@@adminImagesTitle">Administrar calendarios</h2>
<button mat-icon-button color="primary" (click)="iniciarTour()">
<mat-icon>help</mat-icon>
</button>
<div class="header-container-title">
<h2 joyrideStep="titleStep" text="{{ 'titleStepText' | translate }}">{{ 'adminCalendarsTitle' | translate }}</h2>
</div>
<div class="calendar-button-row">
<button mat-flat-button color="primary" (click)="addImage()">Añadir calendario</button>
<button joyrideStep="addButtonStep" text="{{ 'addButtonStepText' | translate }}" class="action-button"
(click)="addCalendar()">
{{ 'addCalendar' | translate }}
</button>
</div>
</div>
<mat-divider class="divider"></mat-divider>
<div class="search-container">
<mat-form-field appearance="fill" class="search-string">
<mat-label i18n="@@searchLabel">Buscar nombre de calendario</mat-label>
<input matInput placeholder="Búsqueda" [(ngModel)]="filters['name']" (keyup.enter)="search()" i18n-placeholder="@@searchPlaceholder">
<mat-form-field joyrideStep="searchStep" text="{{ 'searchStepText' | translate }}" appearance="fill"
class="search-string">
<mat-label>{{ 'searchCalendarLabel' | translate }}</mat-label>
<input matInput placeholder="{{ 'searchPlaceholder' | translate }}" [(ngModel)]="filters['name']"
(keyup.enter)="search()">
<mat-icon matSuffix>search</mat-icon>
<mat-hint i18n="@@searchHint">Pulsar 'enter' para buscar</mat-hint>
<mat-hint>{{ 'searchHint' | translate }}</mat-hint>
</mat-form-field>
</div>
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8">
<ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef">
<th mat-header-cell *matHeaderCellDef> {{ column.header }} </th>
<td mat-cell *matCellDef="let image" >
<ng-container *ngIf="column.columnDef === 'isDefault' || column.columnDef === 'installed'">
<mat-icon [color]="image[column.columnDef] ? 'primary' : 'warn'">
{{ image[column.columnDef] ? 'check_circle' : 'cancel' }}
</mat-icon>
</ng-container>
<ng-container *ngIf="column.columnDef !== 'isDefault' && column.columnDef !== 'installed' && column.columnDef !== 'downloadUrl'">
{{ column.cell(image) }}
</ng-container>
</td>
</ng-container>
<app-loading [isLoading]="loading"></app-loading>
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef i18n="@@columnActions" style="text-align: center;">Acciones</th>
<td mat-cell *matCellDef="let calendar" style="text-align: center;">
<button mat-icon-button color="primary" (click)="editCalendar(calendar)" i18n="@@editImage"> <mat-icon>edit</mat-icon></button>
<button *ngIf="!syncUds" mat-icon-button color="primary" (click)="sync(calendar)"><mat-icon>sync</mat-icon></button>
<button *ngIf="syncUds" mat-icon-button color="primary"><mat-spinner diameter="24"></mat-spinner></button>
<button mat-icon-button color="warn" (click)="deleteCalendar(calendar)" i18n="@@buttonDelete"><mat-icon>delete</mat-icon></button>
</td>
</ng-container>
<div *ngIf="!loading">
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8" joyrideStep="tableStep"
text="{{ 'tableStepText' | translate }}">
<ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef">
<th mat-header-cell *matHeaderCellDef> {{ column.header }} </th>
<td mat-cell *matCellDef="let image">
<ng-container *ngIf="column.columnDef === 'isDefault' || column.columnDef === 'installed'">
<mat-icon [color]="image[column.columnDef] ? 'primary' : 'warn'">
{{ image[column.columnDef] ? 'check_circle' : 'cancel' }}
</mat-icon>
</ng-container>
<ng-container
*ngIf="column.columnDef !== 'isDefault' && column.columnDef !== 'installed' && column.columnDef !== 'downloadUrl'">
{{ column.cell(image) }}
</ng-container>
</td>
</ng-container>
<ng-container matColumnDef="actions" joyrideStep="actionsStep" text="{{ 'actionsStepText' | translate }}">
<th mat-header-cell *matHeaderCellDef style="text-align: center;">{{ 'columnActions' | translate }}</th>
<td mat-cell *matCellDef="let calendar" style="text-align: center;">
<button mat-icon-button color="primary" (click)="editCalendar(calendar)">
<mat-icon>edit</mat-icon>
</button>
<button *ngIf="!syncUds" mat-icon-button color="primary" (click)="sync(calendar)">
<mat-icon>sync</mat-icon>
</button>
<button *ngIf="syncUds" mat-icon-button color="primary">
<app-loading [isLoading]="syncUds"></app-loading>
</button>
<button mat-icon-button color="warn" (click)="deleteCalendar(calendar)">
<mat-icon>delete</mat-icon>
</button>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
</div>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
<div class="paginator-container">
<mat-paginator [length]="length"
[pageSize]="itemsPerPage"
[pageIndex]="page"
[pageSizeOptions]="pageSizeOptions"
(page)="onPageChange($event)">
<mat-paginator [length]="length" [pageSize]="itemsPerPage" [pageIndex]="page" [pageSizeOptions]="pageSizeOptions"
(page)="onPageChange($event)">
</mat-paginator>
</div>

View File

@ -10,16 +10,26 @@ import { MatButtonModule } from '@angular/material/button';
import { MatTableModule } from '@angular/material/table';
import { MatPaginatorModule } from '@angular/material/paginator';
import { MatTooltipModule } from '@angular/material/tooltip';
import { FormsModule } from '@angular/forms'; // Importa FormsModule para ngModel
import { FormsModule } from '@angular/forms';
import { CalendarComponent } from './calendar.component';
import { MatProgressSpinner } from '@angular/material/progress-spinner';
import { JoyrideModule, JoyrideService } from 'ngx-joyride';
import { TranslateModule } from '@ngx-translate/core';
import { LoadingComponent } from '../../shared/loading/loading.component';
import { ConfigService } from '@services/config.service';
describe('CalendarComponent', () => {
let component: CalendarComponent;
let fixture: ComponentFixture<CalendarComponent>;
beforeEach(async () => {
const mockConfigService = {
apiUrl: 'http://mock-api-url',
mercureUrl: 'http://mock-mercure-url'
};
await TestBed.configureTestingModule({
declarations: [CalendarComponent],
declarations: [CalendarComponent, LoadingComponent],
imports: [
HttpClientTestingModule,
ToastrModule.forRoot(),
@ -32,7 +42,13 @@ describe('CalendarComponent', () => {
MatTableModule,
MatPaginatorModule,
MatTooltipModule,
FormsModule // Añade FormsModule aquí para que ngModel funcione
FormsModule,
MatProgressSpinner,
JoyrideModule.forRoot(),
TranslateModule.forRoot(),
],
providers: [
{ provide: ConfigService, useValue: mockConfigService }
]
})
.compileComponents();

View File

@ -1,13 +1,15 @@
import {Component, OnInit, signal} from '@angular/core';
import {MatTableDataSource} from "@angular/material/table";
import {DatePipe} from "@angular/common";
import {MatDialog} from "@angular/material/dialog";
import {HttpClient} from "@angular/common/http";
import {DataService} from "./data.service";
import {ToastrService} from "ngx-toastr";
import {PageEvent} from "@angular/material/paginator";
import {CreateCalendarComponent} from "./create-calendar/create-calendar.component";
import {DeleteModalComponent} from "../../shared/delete_modal/delete-modal/delete-modal.component";
import { Component, OnInit, signal } from '@angular/core';
import { MatTableDataSource } from "@angular/material/table";
import { DatePipe } from "@angular/common";
import { MatDialog } from "@angular/material/dialog";
import { HttpClient } from "@angular/common/http";
import { DataService } from "./data.service";
import { ToastrService } from "ngx-toastr";
import { PageEvent } from "@angular/material/paginator";
import { CreateCalendarComponent } from "./create-calendar/create-calendar.component";
import { DeleteModalComponent } from "../../shared/delete_modal/delete-modal/delete-modal.component";
import { JoyrideService } from 'ngx-joyride';
import { ConfigService } from '@services/config.service';
@Component({
selector: 'app-calendar',
@ -15,14 +17,15 @@ import {DeleteModalComponent} from "../../shared/delete_modal/delete-modal/delet
styleUrl: './calendar.component.css'
})
export class CalendarComponent implements OnInit {
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL;
baseUrl: string;
private apiUrl: string;
images: { downloadUrl: string; name: string; uuid: string }[] = [];
dataSource = new MatTableDataSource<any>();
length: number = 0;
itemsPerPage: number = 10;
page: number = 1;
pageSizeOptions: number[] = [5, 10, 20, 40, 100];
loading:boolean = false;
loading: boolean = false;
filters: { [key: string]: string } = {};
alertMessage: string | null = null;
readonly panelOpenState = signal(false);
@ -52,26 +55,28 @@ export class CalendarComponent implements OnInit {
];
displayedColumns = [...this.columns.map(column => column.columnDef), 'actions'];
private apiUrl = `${this.baseUrl}/remote-calendars`;
constructor(
public dialog: MatDialog,
private http: HttpClient,
private dataService: DataService,
private toastService: ToastrService
) {}
private toastService: ToastrService,
private configService: ConfigService,
private joyrideService: JoyrideService
) {
this.baseUrl = this.configService.apiUrl;
this.apiUrl = `${this.baseUrl}/remote-calendars`;
}
ngOnInit(): void {
this.search();
}
addImage(): void {
addCalendar(): void {
const dialogRef = this.dialog.open(CreateCalendarComponent, {
width: '400px'
});
dialogRef.afterClosed().subscribe(result => {
console.log('The dialog was closed');
this.search();
});
}
@ -84,24 +89,21 @@ export class CalendarComponent implements OnInit {
this.loading = false;
},
error => {
console.error('Error fetching og lives', error);
console.error('Error fetching calendars', error);
this.loading = false;
}
);
}
sync(calendar: any): void {
console.log('Syncing calendars');
this.syncUds = true;
this.http.post(`${this.apiUrl}/${calendar.uuid}/sync-uds`, {}).subscribe({
next: () => {
console.log('Calendars synced successfully');
this.toastService.success('Calendarios sincronizados correctamente');
this.search();
this.syncUds = false;
},
error: (error) => {
console.error('Error al sincronizar los calendarios:', error);
this.toastService.error(error.error['hydra:description']);
this.syncUds = false;
}
@ -133,28 +135,27 @@ export class CalendarComponent implements OnInit {
this.http.delete(apiUrl).subscribe({
next: () => {
console.log('Calendar deleted successfully');
this.search();
this.toastService.success('Calendar deleted successfully');
},
error: (error) => {
error: () => {
this.toastService.error('Error deleting calendar');
}
});
} else {
console.log('calendar deletion cancelled');
}
});
}
applyFilter() {
this.loading = true;
this.http.get<any>(`${this.apiUrl}?page=${this.page}&itemsPerPage=${this.itemsPerPage}`).subscribe({
next: (response) => {
this.dataSource.data = response['hydra:member'];
this.length = response['hydra:totalItems'];
this.loading = false;
},
error: (error) => {
console.error('Error al cargar las imágenes:', error);
error: () => {
this.loading = false;
}
});
}
@ -164,4 +165,12 @@ export class CalendarComponent implements OnInit {
this.itemsPerPage = event.pageSize;
this.applyFilter();
}
iniciarTour(): void {
this.joyrideService.startTour({
steps: ['titleStep', 'addButtonStep', 'searchStep', 'tableStep', 'actionsStep'],
showPrevButton: true,
themeColor: '#3f51b5'
});
}
}

View File

@ -28,3 +28,10 @@
.time-field {
flex: 1;
}
.action-container {
display: flex;
justify-content: flex-end;
gap: 1em;
padding: 1.5em;
}

View File

@ -1,17 +1,19 @@
<h2 mat-dialog-title>{{ isEditMode ? 'Editar' : 'Añadir' }} calendario</h2>
<h2 mat-dialog-title>{{ isEditMode ? ('editCalendar' | translate) : ('addCalendar' | translate) }}</h2>
<mat-dialog-content class="form-container">
<mat-slide-toggle [(ngModel)]="isRemoteAvailable" class="example-margin">¿Disponibilidad remoto?</mat-slide-toggle>
<mat-slide-toggle [(ngModel)]="isRemoteAvailable" class="example-margin">
{{ 'remoteAvailability' | translate }}
</mat-slide-toggle>
<div *ngIf="!isRemoteAvailable" class="form-group">
<mat-label>Selecciona los días de la semana</mat-label>
<mat-label>{{ 'selectWeekDays' | translate }}</mat-label>
<div class="row">
<div class="col-md-6 checkbox-group">
<mat-checkbox *ngFor="let day of weekDays.slice(0, (weekDays.length / 2) + 1)" [(ngModel)]="busyWeekDays[day]">
{{ day }}
</mat-checkbox>
</div>
<div class="col-md-6 checkbox-group ">
<mat-checkbox *ngFor="let day of weekDays.slice(weekDays.length / 2 + 1 )" [(ngModel)]="busyWeekDays[day]">
<div class="col-md-6 checkbox-group">
<mat-checkbox *ngFor="let day of weekDays.slice(weekDays.length / 2 + 1)" [(ngModel)]="busyWeekDays[day]">
{{ day }}
</mat-checkbox>
</div>
@ -19,32 +21,32 @@
<div class="time-fields">
<mat-form-field appearance="fill" class="time-field">
<mat-label>Hora de inicio</mat-label>
<input matInput [(ngModel)]="busyFromHour" type="time" placeholder="Selecciona la hora de inicio" [required]="!isRemoteAvailable">
<mat-label>{{ 'startTime' | translate }}</mat-label>
<input matInput [(ngModel)]="busyFromHour" type="time" placeholder="{{ 'startTimePlaceholder' | translate }}" [required]="!isRemoteAvailable">
</mat-form-field>
<mat-form-field appearance="fill" class="time-field">
<mat-label>Hora de fin</mat-label>
<input matInput [(ngModel)]="busyToHour" type="time" placeholder="Selecciona la hora de fin" [required]="!isRemoteAvailable">
<mat-label>{{ 'endTime' | translate }}</mat-label>
<input matInput [(ngModel)]="busyToHour" type="time" placeholder="{{ 'endTimePlaceholder' | translate }}" [required]="!isRemoteAvailable">
</mat-form-field>
</div>
</div>
<div *ngIf="isRemoteAvailable" class="form-group">
<mat-form-field appearance="fill" class="full-width">
<mat-label>Razón</mat-label>
<input matInput [(ngModel)]="availableReason" placeholder="Razon para la excepción" [required]="isRemoteAvailable">
<mat-label>{{ 'reasonLabel' | translate }}</mat-label>
<input matInput [(ngModel)]="availableReason" placeholder="{{ 'reasonPlaceholder' | translate }}" [required]="isRemoteAvailable">
</mat-form-field>
<div class="time-fields">
<mat-form-field appearance="fill" class="full-width">
<mat-label>Fecha de inicio</mat-label>
<mat-label>{{ 'startDate' | translate }}</mat-label>
<input matInput [matDatepicker]="picker1" [(ngModel)]="availableFromDate" [required]="isRemoteAvailable">
<mat-hint>MM/DD/YYYY</mat-hint>
<mat-datepicker-toggle matIconSuffix [for]="picker1"></mat-datepicker-toggle>
<mat-datepicker #picker1></mat-datepicker>
</mat-form-field>
<mat-form-field appearance="fill" class="full-width">
<mat-label>Fecha de fin</mat-label>
<mat-label>{{ 'endDate' | translate }}</mat-label>
<input matInput [matDatepicker]="picker2" [(ngModel)]="availableToDate" [required]="isRemoteAvailable">
<mat-hint>MM/DD/YYYY</mat-hint>
<mat-datepicker-toggle matIconSuffix [for]="picker2"></mat-datepicker-toggle>
@ -54,13 +56,13 @@
</div>
</mat-dialog-content>
<mat-dialog-actions align="end">
<button mat-button (click)="onNoClick()">Cancelar</button>
<mat-dialog-actions class="action-container">
<button class="ordinary-button" (click)="onNoClick()">{{ 'buttonCancel' | translate }}</button>
<button
mat-button
class="submit-button"
(click)="submitRule()"
cdkFocusInitial
[disabled]="(!isRemoteAvailable && (!busyFromHour || !busyToHour)) || (isRemoteAvailable && (!availableReason || !availableFromDate || !availableToDate))">
{{ isEditMode ? 'Guardar' : 'Añadir' }}
{{ isEditMode ? ('buttonSave' | translate) : ('buttonAdd' | translate) }}
</button>
</mat-dialog-actions>

View File

@ -1,7 +1,8 @@
import {Component, Inject} from '@angular/core';
import {ToastrService} from "ngx-toastr";
import {HttpClient} from "@angular/common/http";
import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog";
import { Component, Inject } from '@angular/core';
import { ToastrService } from "ngx-toastr";
import { HttpClient } from "@angular/common/http";
import { MAT_DIALOG_DATA, MatDialogRef } from "@angular/material/dialog";
import { ConfigService } from '@services/config.service';
@Component({
selector: 'app-create-calendar-rule',
@ -9,7 +10,7 @@ import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog";
styleUrl: './create-calendar-rule.component.css'
})
export class CreateCalendarRuleComponent {
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL;
baseUrl: string;
name: string = '';
remoteCalendarRules: any[] = [];
weekDays: string[] = ['Lunes', 'Martes', 'Miércoles', 'Jueves', 'Viernes', 'Sábado', 'Domingo'];
@ -29,20 +30,23 @@ export class CreateCalendarRuleComponent {
constructor(
private toastService: ToastrService,
private http: HttpClient,
private configService: ConfigService,
public dialogRef: MatDialogRef<CreateCalendarRuleComponent>,
@Inject(MAT_DIALOG_DATA) public data: any,
) { }
) {
this.baseUrl = this.configService.apiUrl;
}
ngOnInit(): void {
this.calendarId = this.data.calendar
if (this.data) {
this.isEditMode = true;
this.availableFromDate = this.data.rule? this.data.rule.availableFromDate : null;
this.availableToDate = this.data.rule? this.data.rule.availableToDate : null;
this.isRemoteAvailable = this.data.rule? this.data.rule.isRemoteAvailable : false;
this.availableReason = this.data.rule? this.data.rule.availableReason : null;
this.busyFromHour = this.data.rule? this.data.rule.busyFromHour : null;
this.busyToHour = this.data.rule? this.data.rule.busyToHour : null;
this.availableFromDate = this.data.rule ? this.data.rule.availableFromDate : null;
this.availableToDate = this.data.rule ? this.data.rule.availableToDate : null;
this.isRemoteAvailable = this.data.rule ? this.data.rule.isRemoteAvailable : false;
this.availableReason = this.data.rule ? this.data.rule.availableReason : null;
this.busyFromHour = this.data.rule ? this.data.rule.busyFromHour : null;
this.busyToHour = this.data.rule ? this.data.rule.busyToHour : null;
if (this.data.rule && this.data.rule.busyWeekDays) {
this.busyWeekDays = this.data.rule.busyWeekDays.reduce((acc: {
[x: string]: boolean;

View File

@ -1,6 +1,3 @@
.full-width {
width: 100%;
}
.form-container {
padding: 40px;
}
@ -12,7 +9,7 @@
.full-width {
width: 100%;
margin-bottom: 16px;
margin-top: 16px;
}
.additional-form {
@ -58,3 +55,9 @@
cursor: pointer;
}
.action-container {
display: flex;
justify-content: flex-end;
gap: 1em;
padding: 1.5em;
}

View File

@ -1,15 +1,16 @@
<h2 mat-dialog-title>{{ isEditMode ? 'Editar' : 'Añadir' }} calendario</h2>
<h2 mat-dialog-title>{{ isEditMode ? ('editCalendar' | translate) : ('addCalendar' | translate) }}</h2>
<mat-dialog-content class="form-container">
<!-- Campo para el nombre -->
<mat-form-field appearance="fill" class="full-width">
<mat-label>Nombre</mat-label>
<mat-label>{{ 'labelName' | translate }}</mat-label>
<input matInput [(ngModel)]="name" required>
<mat-icon *ngIf="isEditMode" matSuffix (click)="submitForm()">mode_edit</mat-icon>
</mat-form-field>
<div style="display: flex; justify-content: space-between; align-items: center;">
<div *ngIf="isEditMode" mat-subheader>Reglas</div>
<button mat-flat-button color="primary" *ngIf="isEditMode" (click)="createRule()" style="padding: 10px;">
Añadir regla
<div *ngIf="isEditMode" mat-subheader>{{ 'rulesHeader' | translate }}</div>
<button class="action-button" *ngIf="isEditMode" (click)="createRule()" style="padding: 10px;">
{{ 'addRule' | translate }}
</button>
</div>
@ -21,16 +22,16 @@
<div class="list-item-content">
<mat-icon matListItemIcon>event_available</mat-icon>
<div class="text-content">
<div matListItemTitle>{{ rule.isRemoteAvailable ? 'Disponible' : 'No disponible ( periodo presencial )' }}</div>
<div matListItemTitle>{{ rule.isRemoteAvailable ? ('statusAvailable' | translate) : ('statusUnavailable' | translate) }}</div>
<div matListItemLine *ngIf="!rule.isRemoteAvailable">{{ rule.busyFromHour }} - {{ rule.busyToHour }}</div>
<div matListItemLine *ngIf="!rule.isRemoteAvailable">{{ rule.busyWeekDaysMap }}</div>
<div matListItemLine *ngIf="rule.isRemoteAvailable">{{ rule.availableReason }} | {{ rule.availableFromDate | date }} - {{ rule.availableToDate | date }}</div>
</div>
<div class="icon-container">
<button mat-icon-button color="primary" class="right-icon" (click)="createRule(rule)" i18n="@@editImage">
<button mat-icon-button color="primary" class="right-icon" (click)="createRule(rule)">
<mat-icon>edit</mat-icon>
</button>
<button mat-icon-button color="warn" class="right-icon" (click)="deleteCalendarRule(rule)" >
<button mat-icon-button color="warn" class="right-icon" (click)="deleteCalendarRule(rule)">
<mat-icon>delete</mat-icon>
</button>
</div>
@ -40,7 +41,9 @@
</mat-list>
</mat-dialog-content>
<mat-dialog-actions align="end">
<button mat-button (click)="onNoClick()">Cancelar</button>
<button mat-button (click)="submitForm()" [disabled]="!name || name === ''" cdkFocusInitial> Guardar </button>
<mat-dialog-actions class="action-container">
<button class="ordinary-button" (click)="onNoClick()">{{ 'buttonCancel' | translate }}</button>
<button class="submit-button" (click)="submitForm()" [disabled]="!name || name === ''" cdkFocusInitial>
{{ 'buttonSave' | translate }}
</button>
</mat-dialog-actions>

View File

@ -1,10 +1,11 @@
import {Component, Inject, OnInit} from '@angular/core';
import {ToastrService} from "ngx-toastr";
import {HttpClient} from "@angular/common/http";
import {MAT_DIALOG_DATA, MatDialog, MatDialogRef} from "@angular/material/dialog";
import {CreateCalendarRuleComponent} from "../create-calendar-rule/create-calendar-rule.component";
import {DataService} from "../data.service";
import {DeleteModalComponent} from "../../../shared/delete_modal/delete-modal/delete-modal.component";
import { Component, Inject, OnInit } from '@angular/core';
import { ToastrService } from "ngx-toastr";
import { HttpClient } from "@angular/common/http";
import { MAT_DIALOG_DATA, MatDialog, MatDialogRef } from "@angular/material/dialog";
import { CreateCalendarRuleComponent } from "../create-calendar-rule/create-calendar-rule.component";
import { DataService } from "../data.service";
import { DeleteModalComponent } from "../../../shared/delete_modal/delete-modal/delete-modal.component";
import { ConfigService } from '@services/config.service';
@Component({
selector: 'app-create-calendar',
@ -12,7 +13,7 @@ import {DeleteModalComponent} from "../../../shared/delete_modal/delete-modal/de
styleUrl: './create-calendar.component.css'
})
export class CreateCalendarComponent implements OnInit {
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL;
baseUrl: string;
name: string = '';
remoteCalendarRules: any[] = [];
isEditMode: boolean = false;
@ -22,11 +23,14 @@ export class CreateCalendarComponent implements OnInit {
constructor(
private toastService: ToastrService,
private http: HttpClient,
private configService: ConfigService,
public dialogRef: MatDialogRef<CreateCalendarComponent>,
@Inject(MAT_DIALOG_DATA) public data: any,
public dialog: MatDialog,
private dataService: DataService,
) { }
) {
this.baseUrl = this.configService.apiUrl;
}
ngOnInit(): void {
if (this.data) {

View File

@ -1,16 +1,20 @@
import { Injectable } from '@angular/core';
import {HttpClient, HttpParams} from '@angular/common/http';
import { HttpClient, HttpParams } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError, map } from 'rxjs/operators';
import { ConfigService } from '@services/config.service';
@Injectable({
providedIn: 'root'
})
export class DataService {
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL;
private apiUrl = `${this.baseUrl}/remote-calendars?page=1&itemsPerPage=1000`;
baseUrl: string;
private apiUrl: string;
constructor(private http: HttpClient) {}
constructor(private http: HttpClient, private configService: ConfigService) {
this.baseUrl = this.configService.apiUrl;
this.apiUrl = `${this.baseUrl}/remote-calendars?page=1&itemsPerPage=1000`;
}
getRemoteCalendars(filters: { [key: string]: string }): Observable<any[]> {
const params = new HttpParams({ fromObject: filters });

View File

@ -1,11 +1,12 @@
.title {
font-size: 24px;
.header-container-title {
flex-grow: 1;
text-align: left;
margin-left: 1em;
}
.calendar-button-row {
.command-groups-button-row {
display: flex;
justify-content: flex-start;
margin-top: 16px;
gap: 15px;
}
.divider {
@ -27,16 +28,15 @@
table {
width: 100%;
margin-top: 50px;
}
.search-container {
.search-container {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
padding: 0 5px;
box-sizing: border-box;
margin: 1.5rem 0rem 1.5rem 0rem;
}
.search-string {
@ -53,11 +53,12 @@ table {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px;
padding: 10px 10px;
border-bottom: 1px solid #ddd;
}
.mat-elevation-z8 {
box-shadow: 0px 0px 0px rgba(0,0,0,0.2);
box-shadow: 0px 0px 0px rgba(0, 0, 0, 0.2);
}
.paginator-container {
@ -75,4 +76,3 @@ table {
background-color: #F44336 !important;
color: white !important;
}

View File

@ -1,57 +1,75 @@
<div class="header-container">
<h2 class="title" i18n="@@adminCommandGroupsTitle">Administrar Grupos de Comandos</h2>
<div class="command-groups-button-row">
<button mat-flat-button color="primary" (click)="openCreateCommandGroupModal()">Añadir Grupo de Comandos</button>
</div>
<button mat-icon-button color="primary" (click)="iniciarTour()">
<mat-icon>help</mat-icon>
</button>
<div class="header-container-title">
<h2 joyrideStep="titleStep" text="{{ 'titleStepText' | translate }}">{{ 'adminCommandGroupsTitle' | translate }}</h2>
</div>
<mat-divider class="divider"></mat-divider>
<div class="search-container">
<mat-form-field appearance="fill" class="search-string">
<mat-label i18n="@@searchLabel">Buscar nombre de grupo</mat-label>
<input matInput placeholder="Búsqueda" [(ngModel)]="filters['name']" (keyup.enter)="search()" i18n-placeholder="@@searchPlaceholder">
<mat-icon matSuffix>search</mat-icon>
<mat-hint i18n="@@searchHint">Pulsar 'enter' para buscar</mat-hint>
</mat-form-field>
<div class="command-groups-button-row">
<button class="action-button" (click)="openCreateCommandGroupModal()" joyrideStep="addCommandGroupStep" text="{{ 'addCommandGroupStepText' | translate }}">
{{ 'addCommandGroup' | translate }}
</button>
</div>
</div>
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8">
<ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef">
<th mat-header-cell *matHeaderCellDef> {{ column.header }} </th>
<td mat-cell *matCellDef="let commandGroup">
<ng-container *ngIf="column.columnDef !== 'commands'">
{{ column.cell(commandGroup) }}
</ng-container>
<div class="search-container" joyrideStep="searchStep" text="{{ 'searchStepText' | translate }}">
<mat-form-field appearance="fill" class="search-string">
<mat-label>{{ 'searchGroupNameLabel' | translate }}</mat-label>
<input matInput placeholder="{{ 'searchPlaceholder' | translate }}" [(ngModel)]="filters['name']" (keyup.enter)="search()">
<mat-icon matSuffix>search</mat-icon>
<mat-hint>{{ 'searchHint' | translate }}</mat-hint>
</mat-form-field>
</div>
<ng-container *ngIf="column.columnDef === 'commands'">
<button mat-button [matMenuTriggerFor]="menu">Ver comandos</button>
<mat-menu #menu="matMenu">
<button mat-menu-item *ngFor="let command of commandGroup.commands">
{{ command.name }}
</button>
</mat-menu>
</ng-container>
</td>
</ng-container>
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef i18n="@@columnActions" style="text-align: center;">Acciones</th>
<td mat-cell *matCellDef="let client" style="text-align: center;">
<button mat-icon-button color="info" (click)="viewGroupDetails(client)"><mat-icon i18n="@@deleteElementTooltip">visibility</mat-icon></button>
<button mat-icon-button color="primary" (click)="editCommandGroup(client)" i18n="@@editImage"> <mat-icon>edit</mat-icon></button>
<button mat-icon-button color="warn" (click)="deleteCommandGroup(client)">
<mat-icon i18n="@@deleteElementTooltip">delete</mat-icon>
</button>
</td>
</ng-container>
<div *ngIf="loading" class="loading-container" joyrideStep="loadingStep" text="{{ 'loadingStepText' | translate }}">
<app-loading [isLoading]="loading"></app-loading>
</div>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
<div *ngIf="!loading">
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8" joyrideStep="tableStep" text="{{ 'tableStepText' | translate }}">
<ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef">
<th mat-header-cell *matHeaderCellDef> {{ column.header }} </th>
<td mat-cell *matCellDef="let commandGroup">
<ng-container *ngIf="column.columnDef !== 'commands'">
{{ column.cell(commandGroup) }}
</ng-container>
<ng-container *ngIf="column.columnDef === 'commands'" joyrideStep="viewCommandsStep" text="{{ 'viewCommandsStepText' | translate }}">
<button class="action-button" [matMenuTriggerFor]="menu">{{ 'viewCommands' | translate }}</button>
<mat-menu #menu="matMenu">
<button mat-menu-item *ngFor="let command of commandGroup.commands">
{{ command.name }}
</button>
</mat-menu>
</ng-container>
</td>
</ng-container>
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef style="text-align: center;">{{ 'columnActions' | translate }}</th>
<td mat-cell *matCellDef="let client" style="text-align: center;" joyrideStep="actionsStep" text="{{ 'actionsStepText' | translate }}">
<button mat-icon-button color="info" (click)="viewGroupDetails(client)">
<mat-icon>visibility</mat-icon>
</button>
<button mat-icon-button color="primary" (click)="editCommandGroup(client)">
<mat-icon>edit</mat-icon>
</button>
<button mat-icon-button color="warn" (click)="deleteCommandGroup(client)">
<mat-icon>delete</mat-icon>
</button>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
</div>
<div class="paginator-container">
<mat-paginator [length]="length"
[pageSize]="itemsPerPage"
[pageIndex]="page"
[pageSizeOptions]="pageSizeOptions"
(page)="onPageChange($event)">
</mat-paginator>
</div>
<div class="paginator-container" joyrideStep="paginationStep" text="{{ 'paginationStepText' | translate }}">
<mat-paginator [length]="length"
[pageSize]="itemsPerPage"
[pageIndex]="page"
[pageSizeOptions]="pageSizeOptions"
(page)="onPageChange($event)">
</mat-paginator>
</div>

View File

@ -2,11 +2,13 @@ import { Component, OnInit } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { MatDialog } from '@angular/material/dialog';
import { ToastrService } from 'ngx-toastr';
import { CreateCommandGroupComponent } from './create-command-group/create-command-group.component'
import { CreateCommandGroupComponent } from './create-command-group/create-command-group.component';
import { DetailCommandGroupComponent } from './detail-command-group/detail-command-group.component';
import { DeleteModalComponent } from '../../../shared/delete_modal/delete-modal/delete-modal.component';
import {MatTableDataSource} from "@angular/material/table";
import {DatePipe} from "@angular/common";
import { MatTableDataSource } from "@angular/material/table";
import { DatePipe } from "@angular/common";
import { JoyrideService } from 'ngx-joyride';
import { ConfigService } from '@services/config.service';
@Component({
selector: 'app-commands-groups',
@ -14,7 +16,8 @@ import {DatePipe} from "@angular/common";
styleUrls: ['./commands-groups.component.css']
})
export class CommandsGroupsComponent implements OnInit {
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL;
baseUrl: string;
private apiUrl: string;
dataSource = new MatTableDataSource<any>();
filters: { [key: string]: string | boolean } = {};
length: number = 0;
@ -22,6 +25,7 @@ export class CommandsGroupsComponent implements OnInit {
page: number = 0;
pageSizeOptions: number[] = [10, 20, 40, 100];
datePipe: DatePipe = new DatePipe('es-ES');
loading: boolean = false;
columns = [
{
columnDef: 'id',
@ -45,22 +49,28 @@ export class CommandsGroupsComponent implements OnInit {
}
];
displayedColumns = [...this.columns.map(column => column.columnDef), 'actions'];
private apiUrl = `${this.baseUrl}/command-groups`;
constructor(private http: HttpClient, private dialog: MatDialog, private toastService: ToastrService) {}
constructor(private http: HttpClient, private dialog: MatDialog, private toastService: ToastrService,
private configService: ConfigService, private joyrideService: JoyrideService) {
this.baseUrl = this.configService.apiUrl;
this.apiUrl = `${this.baseUrl}/command-groups`;
}
ngOnInit(): void {
this.search();
}
search(): void {
this.loading = true;
this.http.get<any>(`${this.apiUrl}?page=${this.page + 1}&itemsPerPage=${this.itemsPerPage}`, { params: this.filters }).subscribe(
(data) => {
this.dataSource.data = data['hydra:member'];
this.length = data['hydra:totalItems'];
this.loading = false;
},
(error) => {
console.error('Error fetching command groups', error);
this.loading = false;
}
);
}
@ -110,4 +120,21 @@ export class CommandsGroupsComponent implements OnInit {
this.length = event.length;
this.search();
}
iniciarTour(): void {
this.joyrideService.startTour({
steps: [
'titleStep',
'addCommandGroupStep',
'searchStep',
'tableStep',
'viewCommandsStep',
'actionsStep',
'paginationStep'
],
showPrevButton: true,
themeColor: '#3f51b5'
});
}
}

View File

@ -1,8 +1,8 @@
.create-command-group-container {
padding: 20px;
max-width: 800px; /* Ancho máximo del contenedor */
margin: auto; /* Centra el contenedor en la pantalla */
background-color: #fff; /* Fondo blanco para el contenedor */
max-width: 800px;
margin: auto;
background-color: #fff;
}
.form-container {
@ -24,14 +24,14 @@
width: 48%;
display: flex;
flex-direction: column;
max-height: 200px; /* Limita la altura máxima para evitar desbordamiento */
max-height: 200px;
}
.table-wrapper {
flex: 1;
overflow-y: auto; /* Scroll para la tabla si hay demasiados comandos */
border: 1px solid #ccc; /* Borde para la tabla */
border-radius: 4px; /* Bordes redondeados */
overflow-y: auto;
border: 1px solid #ccc;
border-radius: 4px;
}
.selected-commands-list {
@ -40,7 +40,7 @@
padding: 10px;
background-color: #f9f9f9;
flex: 1;
overflow-y: auto; /* Scroll para los comandos seleccionados */
overflow-y: auto;
}
.commands-container {
@ -60,7 +60,7 @@
.remove-icon {
cursor: pointer;
color: #f44336; /* Rojo para eliminar */
color: #f44336;
}
.chevron-icon {
@ -74,29 +74,34 @@
justify-content: space-between;
}
.available-commands, .selected-commands {
width: 48%;
display: flex;
flex-direction: column;
max-height: 500px; /* Limita la altura máxima para evitar desbordamiento */
max-height: 500px;
}
.table-wrapper {
flex: 1;
overflow-y: auto; /* Scroll para la tabla si hay demasiados comandos */
border: 1px solid #ccc; /* Borde para la tabla */
border-radius: 4px; /* Bordes redondeados */
max-height: 400px; /* Establece la altura máxima */
overflow-y: auto;
border: 1px solid #ccc;
border-radius: 4px;
max-height: 400px;
}
/* Para asegurar que el componente sea responsivo en pantallas pequeñas */
@media (max-width: 600px) {
.command-selection {
flex-direction: column; /* Cambia a columna en pantallas pequeñas */
flex-direction: column;
}
.available-commands, .selected-commands {
width: 100%; /* Ocupa el ancho completo */
margin-bottom: 20px; /* Espacio entre elementos */
width: 100%;
margin-bottom: 20px;
}
}
.action-container {
display: flex;
justify-content: flex-end;
gap: 1em;
padding: 1.5em;
}

View File

@ -1,25 +1,27 @@
<h2 mat-dialog-title>{{ editing ? 'Editar' : 'Crear' }} grupo de comando</h2>
<h2 mat-dialog-title>{{ editing ? ('editCommandGroup' | translate) : ('createCommandGroup' | translate) }}</h2>
<mat-dialog-content class="form-container">
<form class="command-group-form" (ngSubmit)="onSubmit()">
<app-loading [isLoading]="loading"></app-loading>
<form *ngIf="!loading" class="command-group-form" (ngSubmit)="onSubmit()">
<mat-form-field>
<mat-label>Nombre del Grupo</mat-label>
<mat-label>{{ 'groupNameLabel' | translate }}</mat-label>
<input matInput [(ngModel)]="groupName" name="groupName" required />
</mat-form-field>
<mat-slide-toggle [(ngModel)]="enabled" name="enabled">Habilitado</mat-slide-toggle>
<mat-slide-toggle [(ngModel)]="enabled" name="enabled">{{ 'enabledToggle' | translate }}</mat-slide-toggle>
<div class="command-selection">
<div class="available-commands">
<h3>Comandos Disponibles</h3>
<div class="table-wrapper"> <!-- Agregar este contenedor -->
<h3>{{ 'availableCommandsTitle' | translate }}</h3>
<div class="table-wrapper">
<table mat-table [dataSource]="availableCommands" class="mat-elevation-z8">
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef> Nombre </th>
<th mat-header-cell *matHeaderCellDef> {{ 'nameColumn' | translate }} </th>
<td mat-cell *matCellDef="let command"> {{ command.name }} </td>
</ng-container>
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef> Acciones </th>
<th mat-header-cell *matHeaderCellDef> {{ 'actionsColumn' | translate }} </th>
<td mat-cell *matCellDef="let command">
<button mat-icon-button type="button" (click)="addCommand(command)">
<mat-icon>add</mat-icon>
@ -34,18 +36,16 @@
</div>
<div class="selected-commands">
<h3>Comandos Seleccionados</h3>
<div class="selected-commands-list">
<h3>{{ 'selectedCommandsTitle' | translate }}</h3>
<div class="selected-commands-list" cdkDropList (cdkDropListDropped)="drop($event)">
<div class="commands-container">
<ng-container *ngFor="let command of selectedCommands; let last = last">
<div *ngFor="let command of selectedCommands" cdkDrag>
<div class="command-item">
<mat-icon class="drag-handle" cdkDragHandle>drag_indicator</mat-icon>
{{ command.name }}
<mat-icon class="remove-icon" (click)="removeCommand(command)">close</mat-icon>
</div>
<ng-container *ngIf="!last">
<mat-icon class="chevron-icon">chevron_right</mat-icon>
</ng-container>
</ng-container>
</div>
</div>
</div>
</div>
@ -53,8 +53,7 @@
</form>
</mat-dialog-content>
<mat-dialog-actions align="end">
<button mat-button (click)="close()">Cancelar</button>
<button mat-button (click)="onSubmit()" cdkFocusInitial> Guardar </button>
<mat-dialog-actions class="action-container">
<button class="ordinary-button" (click)="close()">{{ 'buttonCancel' | translate }}</button>
<button class="submit-button" (click)="onSubmit()" cdkFocusInitial>{{ 'buttonSave' | translate }}</button>
</mat-dialog-actions>

View File

@ -2,6 +2,8 @@ import { Component, OnInit, Inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { ToastrService } from 'ngx-toastr';
import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop';
import { ConfigService } from '@services/config.service';
@Component({
selector: 'app-create-command-group',
@ -9,20 +11,25 @@ import { ToastrService } from 'ngx-toastr';
styleUrls: ['./create-command-group.component.css']
})
export class CreateCommandGroupComponent implements OnInit {
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL;
baseUrl: string;
availableCommands: any[] = [];
selectedCommands: any[] = [];
groupName: string = '';
enabled: boolean = true;
editing: boolean = false;
private apiUrl = `${this.baseUrl}/commands`;
loading: boolean = false;
private apiUrl: string;
constructor(
private http: HttpClient,
private dialogRef: MatDialogRef<CreateCommandGroupComponent>,
private toastService: ToastrService,
private configService: ConfigService,
@Inject(MAT_DIALOG_DATA) public data: any
) {}
) {
this.baseUrl = this.configService.apiUrl;
this.apiUrl = `${this.baseUrl}/commands`;
}
ngOnInit(): void {
this.loadAvailableCommands();
@ -34,12 +41,15 @@ export class CreateCommandGroupComponent implements OnInit {
}
loadAvailableCommands(): void {
this.loading = true;
this.http.get<any>(this.apiUrl).subscribe(
(data) => {
this.availableCommands = data['hydra:member'];
this.loading = false;
},
(error) => {
console.error('Error fetching available commands', error);
this.loading = false;
}
);
}
@ -51,7 +61,9 @@ export class CreateCommandGroupComponent implements OnInit {
}
addCommand(command: any): void {
this.selectedCommands.push(command);
if (!this.selectedCommands.includes(command)) {
this.selectedCommands.push(command);
}
}
removeCommand(command: any): void {
@ -61,6 +73,10 @@ export class CreateCommandGroupComponent implements OnInit {
}
}
drop(event: CdkDragDrop<any[]>): void {
moveItemInArray(this.selectedCommands, event.previousIndex, event.currentIndex);
}
onSubmit(): void {
const payload = {
name: this.groupName,
@ -77,6 +93,7 @@ export class CreateCommandGroupComponent implements OnInit {
},
error: (error) => {
console.error('Error actualizando el grupo de comandos', error);
this.toastService.error('Error al actualizar el grupo de comandos');
}
});
} else {
@ -87,6 +104,7 @@ export class CreateCommandGroupComponent implements OnInit {
},
error: (error) => {
console.error('Error creando el grupo de comandos', error);
this.toastService.error('Error al crear el grupo de comandos');
}
});
}

View File

@ -58,19 +58,6 @@
padding: 10px 20px;
}
button {
background-color: #3f51b5; /* Color primario */
color: white;
padding: 10px 15px;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background-color: #2c387e; /* Color primario oscuro */
}
@media (max-width: 600px) {
.mat-card {
margin: 10px 0;
@ -86,15 +73,6 @@
}
}
.cancel-button {
background-color: #dc3545;
color: white;
border-radius: 5px;
}
.cancel-button:hover {
opacity: 0.9;
}
.create-command-group-container {
padding: 20px;
}
@ -146,8 +124,9 @@
color: #666;
}
.command-group-actions {
margin-top: 20px;
.action-container {
display: flex;
justify-content: space-between;
justify-content: flex-end;
gap: 1em;
padding: 1.5em;
}

View File

@ -1,20 +1,22 @@
<div class="detail-command-group-container">
<h2>Detalles del Grupo de Comandos</h2>
<h2>{{ 'commandGroupDetailsTitle' | translate }}</h2>
<mat-card>
<app-loading [isLoading]="loading"></app-loading>
<mat-card *ngIf="!loading">
<mat-card-header>
<mat-card-title>{{ data.name }}</mat-card-title>
<mat-card-subtitle>Creado por: {{ data.createdBy }}</mat-card-subtitle>
<mat-card-subtitle>{{ 'createdBy' | translate }}: {{ data.createdBy }}</mat-card-subtitle>
</mat-card-header>
<mat-card-content>
<p><strong>ID del Grupo:</strong> {{ data.uuid }}</p>
<p><strong>Fecha de Creación:</strong> {{ data.createdAt | date:'short' }}</p>
<p><strong>{{ 'groupId' | translate }}:</strong> {{ data.uuid }}</p>
<p><strong>{{ 'creationDate' | translate }}:</strong> {{ data.createdAt | date:'short' }}</p>
<h3>Comandos Incluidos:</h3>
<h3>{{ 'includedCommands' | translate }}</h3>
<table mat-table [dataSource]="data.commands" class="mat-elevation-z8">
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef> Nombre </th>
<th mat-header-cell *matHeaderCellDef> {{ 'nameColumn' | translate }} </th>
<td mat-cell *matCellDef="let command"> {{ command.name }} </td>
</ng-container>
@ -27,31 +29,30 @@
<tr mat-row *matRowDef="let row; columns: ['name', 'uuid'];"></tr>
</table>
</mat-card-content>
</mat-card>
<!-- Sección para seleccionar clientes -->
<div class="additional-section" *ngIf="showClientSelect">
<div class="additional-section" *ngIf="showClientSelect && !loading">
<form [formGroup]="form">
<h4>Selecciona los clientes:</h4>
<h4>{{ 'selectClients' | translate }}</h4>
<mat-form-field appearance="fill">
<mat-label>Clientes</mat-label>
<mat-label>{{ 'clientsLabel' | translate }}</mat-label>
<mat-select formControlName="selectedClients" multiple (selectionChange)="onClientSelectionChange($event)">
<mat-option *ngFor="let client of clients" [value]="client.uuid">
{{ client.name }}
</mat-option>
</mat-select>
<mat-error *ngIf="form.get('selectedClients')?.invalid && form.get('selectedClients')?.touched">
Debes seleccionar al menos un cliente.
{{ 'selectAtLeastOneClient' | translate }}
</mat-error>
</mat-form-field>
</form>
</div>
<div class="command-group-actions">
<button mat-flat-button color="primary" (click)="toggleClientSelect()">
{{ showClientSelect ? 'Ejecutar' : 'Programar Ejecución' }}
<div class="action-container" *ngIf="!loading">
<button class="ordinary-button" (click)="close()">{{ 'buttonCancel' | translate }}</button>
<button [ngClass]="showClientSelect ? 'submit-button' : 'action-button'" (click)="toggleClientSelect()">
{{ showClientSelect ? ('execute' | translate) : ('scheduleExecution' | translate) }}
</button>
<button mat-flat-button color="warn" (click)="close()">Cancelar</button>
</div>
</div>

View File

@ -3,6 +3,7 @@ import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { HttpClient } from '@angular/common/http';
import { ToastrService } from 'ngx-toastr';
import { ConfigService } from '@services/config.service';
@Component({
selector: 'app-detail-command-group',
@ -10,36 +11,51 @@ import { ToastrService } from 'ngx-toastr';
styleUrls: ['./detail-command-group.component.css']
})
export class DetailCommandGroupComponent implements OnInit {
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL;
baseUrl: string;
form!: FormGroup;
clients: any[] = [];
showClientSelect = false; // Ocultar selección de clientes inicialmente
showClientSelect = false;
canExecute = false;
loading: boolean = false;
constructor(
@Inject(MAT_DIALOG_DATA) public data: any,
private dialogRef: MatDialogRef<DetailCommandGroupComponent>,
private fb: FormBuilder,
private configService: ConfigService,
private http: HttpClient,
private toastService: ToastrService
) { }
) {
this.baseUrl = this.configService.apiUrl;
}
ngOnInit(): void {
this.form = this.fb.group({
selectedClients: [[], Validators.required],
});
// Obtener la lista de clientes
this.http.get<any>(`${this.baseUrl}/clients?page=1&itemsPerPage=30`).subscribe(response => {
this.clients = response['hydra:member'];
this.loadClients();
}
loadClients(): void {
this.loading = true;
this.http.get<any>(`${this.baseUrl}/clients?page=1&itemsPerPage=30`).subscribe({
next: (response) => {
this.clients = response['hydra:member'];
this.loading = false;
},
error: (error) => {
console.error('Error fetching clients:', error);
this.loading = false;
}
});
}
toggleClientSelect(): void {
if (!this.showClientSelect) {
this.showClientSelect = true; // Mostrar selección de clientes
this.showClientSelect = true;
} else {
this.execute(); // Ejecutar si ya está visible
this.execute();
}
}
@ -50,13 +66,16 @@ export class DetailCommandGroupComponent implements OnInit {
};
const apiUrl = `${this.baseUrl}/command-groups/${this.data.uuid}/execute`;
this.loading = true;
this.http.post(apiUrl, payload).subscribe({
next: () => {
this.dialogRef.close();
this.toastService.success('Grupo de comandos ejecutado exitosamente');
this.loading = false;
},
error: (error) => {
console.error('Error ejecutando grupo de comandos:', error);
this.loading = false;
}
});
} else {

View File

@ -1,11 +1,12 @@
.title {
font-size: 24px;
.header-container-title {
flex-grow: 1;
text-align: left;
margin-left: 1em;
}
.calendar-button-row {
.task-button-row {
display: flex;
justify-content: flex-start;
margin-top: 16px;
gap: 15px;
}
.divider {
@ -27,7 +28,6 @@
table {
width: 100%;
margin-top: 50px;
}
.search-container {
@ -35,7 +35,7 @@ table {
justify-content: space-between;
align-items: center;
width: 100%;
padding: 0 5px;
margin: 1.5rem 0rem 1.5rem 0rem;
box-sizing: border-box;
}
@ -53,7 +53,8 @@ table {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px;
padding: 10px 10px;
border-bottom: 1px solid #ddd;
}
.mat-elevation-z8 {

View File

@ -1,63 +1,75 @@
<div class="header-container">
<h2 class="title">Administrar Tareas</h2>
<button mat-icon-button color="primary" (click)="iniciarTour()">
<mat-icon>help</mat-icon>
</button>
<div class="header-container-title">
<h2 class="title" joyrideStep="titleStep" text="{{ 'titleStepText' | translate }}">{{ 'manageTasksTitle' | translate }}</h2>
</div>
<div class="task-button-row">
<button mat-flat-button color="primary" (click)="openCreateTaskModal()">Añadir Tarea</button>
<button class="action-button" (click)="openCreateTaskModal()" joyrideStep="addTaskStep" text="{{ 'addTaskStepText' | translate }}">
{{ 'addTask' | translate }}
</button>
</div>
</div>
<mat-divider class="divider"></mat-divider>
<div class="search-container">
<div class="search-container" joyrideStep="searchStep" text="{{ 'searchStepText' | translate }}">
<mat-form-field appearance="fill" class="search-string">
<mat-label>Buscar tarea</mat-label>
<input matInput placeholder="Búsqueda" [(ngModel)]="filters['name']" (keyup.enter)="search()" />
<mat-label>{{ 'searchTaskLabel' | translate }}</mat-label>
<input matInput placeholder="{{ 'searchPlaceholder' | translate }}" [(ngModel)]="filters['name']" (keyup.enter)="search()" />
<mat-icon matSuffix>search</mat-icon>
<mat-hint>Pulsar 'enter' para buscar</mat-hint>
<mat-hint>{{ 'searchHint' | translate }}</mat-hint>
</mat-form-field>
</div>
<table mat-table [dataSource]="tasks" class="mat-elevation-z8">
<ng-container matColumnDef="taskid">
<th mat-header-cell *matHeaderCellDef> Id</th>
<td mat-cell *matCellDef="let task"> {{ task.id }} </td>
</ng-container>
<div *ngIf="!loading">
<table mat-table [dataSource]="tasks" class="mat-elevation-z8" joyrideStep="tableStep" text="{{ 'tableStepText' | translate }}">
<ng-container matColumnDef="taskid">
<th mat-header-cell *matHeaderCellDef> {{ 'idColumn' | translate }} </th>
<td mat-cell *matCellDef="let task"> {{ task.id }} </td>
</ng-container>
<ng-container matColumnDef="notes">
<th mat-header-cell *matHeaderCellDef> Info</th>
<td mat-cell *matCellDef="let task"> {{ task.notes }} </td>
</ng-container>
<ng-container matColumnDef="notes">
<th mat-header-cell *matHeaderCellDef> {{ 'infoColumn' | translate }} </th>
<td mat-cell *matCellDef="let task"> {{ task.notes }} </td>
</ng-container>
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef> Creado por </th>
<td mat-cell *matCellDef="let task"> {{ task.createdBy }} </td>
</ng-container>
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef> {{ 'createdByColumn' | translate }} </th>
<td mat-cell *matCellDef="let task"> {{ task.createdBy }} </td>
</ng-container>
<ng-container matColumnDef="scheduledDate">
<th mat-header-cell *matHeaderCellDef> Fecha de Ejecución </th>
<td mat-cell *matCellDef="let task"> {{ task.dateTime | date:'short' }} </td>
</ng-container>
<ng-container matColumnDef="scheduledDate">
<th mat-header-cell *matHeaderCellDef> {{ 'executionDateColumn' | translate }} </th>
<td mat-cell *matCellDef="let task"> {{ task.dateTime | date:'short' }} </td>
</ng-container>
<ng-container matColumnDef="enabled">
<th mat-header-cell *matHeaderCellDef> Estado </th>
<td mat-cell *matCellDef="let task"> {{ task.enabled ? 'Habilitado' : 'Deshabilitado' }} </td>
</ng-container>
<ng-container matColumnDef="enabled">
<th mat-header-cell *matHeaderCellDef> {{ 'statusColumn' | translate }} </th>
<td mat-cell *matCellDef="let task"> {{ task.enabled ? ('enabled' | translate) : ('disabled' | translate) }} </td>
</ng-container>
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef i18n="@@columnActions" style="text-align: center;">Acciones</th>
<td mat-cell *matCellDef="let task" style="text-align: center;">
<button mat-icon-button color="info" (click)="viewTaskDetails(task)"><mat-icon i18n="@@deleteElementTooltip">visibility</mat-icon></button>
<button mat-icon-button color="primary" (click)="editTask(task)" i18n="@@editImage"> <mat-icon>edit</mat-icon></button>
<button mat-icon-button color="warn" (click)="deleteTask(task)">
<mat-icon i18n="@@deleteElementTooltip">delete</mat-icon>
</button>
</td>
</ng-container>
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef style="text-align: center;">{{ 'columnActions' | translate }}</th>
<td mat-cell *matCellDef="let task" style="text-align: center;" joyrideStep="actionsStep" text="{{ 'actionsStepText' | translate }}">
<button mat-icon-button color="info" (click)="viewTaskDetails(task)">
<mat-icon>visibility</mat-icon>
</button>
<button mat-icon-button color="primary" (click)="editTask(task)">
<mat-icon>edit</mat-icon>
</button>
<button mat-icon-button color="warn" (click)="deleteTask(task)">
<mat-icon>delete</mat-icon>
</button>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
</div>
<mat-paginator [length]="length"
<mat-paginator joyrideStep="paginationStep" text="{{ 'paginationStepText' | translate }}"
[length]="length"
[pageSize]="itemsPerPage"
[pageSizeOptions]="pageSizeOptions"
(page)="onPageChange($event)">

View File

@ -10,11 +10,19 @@ import { FormsModule } from '@angular/forms';
import { MatInputModule } from '@angular/material/input';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { TranslateModule } from '@ngx-translate/core';
import { LoadingComponent } from '../../../shared/loading/loading.component';
import { JoyrideModule, JoyrideService, JoyrideStepService } from 'ngx-joyride';
import { ConfigService } from '@services/config.service';
describe('CommandsTaskComponent', () => {
let component: CommandsTaskComponent;
let fixture: ComponentFixture<CommandsTaskComponent>;
let mockConfigService: jasmine.SpyObj<ConfigService>;
beforeEach(async () => {
const configServiceSpy = jasmine.createSpyObj('ConfigService', [], { apiUrl: 'http://mock-api-url' });
await TestBed.configureTestingModule({
imports: [
HttpClientTestingModule,
@ -25,11 +33,18 @@ describe('CommandsTaskComponent', () => {
MatPaginatorModule,
FormsModule,
MatInputModule,
BrowserAnimationsModule
BrowserAnimationsModule,
TranslateModule.forRoot(),
JoyrideModule.forRoot(),
],
declarations: [CommandsTaskComponent, LoadingComponent],
providers: [
{ provide: ConfigService, useValue: configServiceSpy }
],
declarations: [CommandsTaskComponent],
schemas: [NO_ERRORS_SCHEMA]
}).compileComponents();
mockConfigService = TestBed.inject(ConfigService) as jasmine.SpyObj<ConfigService>;
});
beforeEach(() => {

View File

@ -5,6 +5,8 @@ import { ToastrService } from 'ngx-toastr';
import { CreateTaskComponent } from './create-task/create-task.component';
import { DetailTaskComponent } from './detail-task/detail-task.component';
import { DeleteModalComponent } from '../../../shared/delete_modal/delete-modal/delete-modal.component';
import { JoyrideService } from 'ngx-joyride';
import { ConfigService } from '@services/config.service';
@Component({
selector: 'app-commands-task',
@ -12,17 +14,23 @@ import { DeleteModalComponent } from '../../../shared/delete_modal/delete-modal/
styleUrls: ['./commands-task.component.css']
})
export class CommandsTaskComponent implements OnInit {
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL;
baseUrl: string;
tasks: any[] = [];
filters: { [key: string]: string | boolean } = {};
length: number = 0;
itemsPerPage: number = 10;
page: number = 1;
pageSizeOptions: number[] = [5, 10, 20, 40, 100];
displayedColumns: string[] = ['taskid','notes','name', 'scheduledDate', 'enabled', 'actions'];
private apiUrl = `${this.baseUrl}/command-tasks`;
displayedColumns: string[] = ['taskid', 'notes', 'name', 'scheduledDate', 'enabled', 'actions'];
loading: boolean = false;
private apiUrl: string;
constructor(private http: HttpClient, private dialog: MatDialog, private toastService: ToastrService) {}
constructor(private http: HttpClient, private dialog: MatDialog, private toastService: ToastrService,
private configService: ConfigService,
private joyrideService: JoyrideService) {
this.baseUrl = this.configService.apiUrl;
this.apiUrl = `${this.baseUrl}/command-tasks`;
}
ngOnInit(): void {
this.loadTasks();
@ -34,13 +42,16 @@ export class CommandsTaskComponent implements OnInit {
}
loadTasks(): void {
this.loading = true;
this.http.get<any>(`${this.apiUrl}?page=${this.page}&itemsPerPage=${this.itemsPerPage}`, { params: this.filters }).subscribe(
(data) => {
this.tasks = data['hydra:member'];
this.length = data['hydra:totalItems'];
this.loading = false;
},
(error) => {
console.error('Error fetching tasks', error);
this.loading = false;
}
);
}
@ -48,7 +59,7 @@ export class CommandsTaskComponent implements OnInit {
viewTaskDetails(task: any): void {
this.dialog.open(DetailTaskComponent, {
width: '800px',
data: {task},
data: { task },
}).afterClosed().subscribe(() => this.loadTasks());
}
@ -89,4 +100,20 @@ export class CommandsTaskComponent implements OnInit {
this.itemsPerPage = event.pageSize;
this.loadTasks();
}
iniciarTour(): void {
this.joyrideService.startTour({
steps: [
'titleStep',
'addTaskStep',
'searchStep',
'tableStep',
'actionsStep',
'paginationStep'
],
showPrevButton: true,
themeColor: '#3f51b5'
});
}
}

View File

@ -12,10 +12,17 @@
.button-container {
display: flex;
justify-content: space-between;
justify-content: flex-end;
margin-top: 20px;
padding: 1.5rem;
}
mat-form-field {
margin-bottom: 16px; /* Espaciado entre campos */
margin-bottom: 16px;
}
.section-title {
margin-top: 24px;
margin-bottom: 8px;
font-weight: 500;
}

View File

@ -1,108 +1,106 @@
<h2 mat-dialog-title class="dialog-title">{{ editing ? 'Editar Tarea' : 'Crear Tarea' }}</h2>
<h2 mat-dialog-title class="dialog-title">{{ editing ? ('editTask' | translate) : ('createTask' | translate) }}</h2>
<form [formGroup]="taskForm" class="task-form">
<mat-dialog-content>
<mat-horizontal-stepper linear>
<!-- Paso 1: Información y Selecciona Comandos -->
<mat-step label="Información y Selecciona Comandos">
<mat-form-field appearance="fill" class="full-width">
<mat-label>Información</mat-label>
<textarea matInput formControlName="notes" placeholder="Ingresa tus notas aquí"></textarea>
</mat-form-field>
<mat-form-field appearance="fill" class="full-width">
<mat-label>Selecciona Comandos</mat-label>
<mat-select formControlName="commandGroup" (selectionChange)="onCommandGroupChange()">
<mat-option *ngFor="let group of availableCommandGroups" [value]="group.uuid">
{{ group.name }}
</mat-option>
</mat-select>
<mat-error *ngIf="taskForm.get('commandGroup')?.invalid">Este campo es obligatorio</mat-error>
</mat-form-field>
<h3 class="section-title">Información</h3>
<mat-divider></mat-divider>
<mat-form-field appearance="fill" class="full-width">
<mat-label>Información</mat-label>
<textarea matInput formControlName="notes" placeholder="Ingresa tus notas aquí"></textarea>
</mat-form-field>
<div class="button-container">
<button mat-raised-button color="primary" matStepperNext [disabled]="taskForm.get('commandGroup')?.invalid">Continuar</button>
</div>
</mat-step>
<h3 class="section-title">{{ 'informationSectionTitle' | translate }}</h3>
<mat-divider></mat-divider>
<mat-form-field appearance="fill" class="full-width">
<mat-label>{{ 'informationLabel' | translate }}</mat-label>
<textarea matInput formControlName="notes" placeholder="{{ 'notesPlaceholder' | translate }}"></textarea>
</mat-form-field>
<!-- Paso 2: Selecciona Comandos Individuales -->
<mat-step label="Selecciona Comandos Individuales">
<mat-form-field appearance="fill" class="full-width">
<mat-label>Selecciona Comandos Individuales (Opcional)</mat-label>
<mat-select formControlName="extraCommands" multiple>
<mat-option *ngFor="let command of availableIndividualCommands" [value]="command.uuid">
{{ command.name }}
</mat-option>
</mat-select>
</mat-form-field>
<h3 class="section-title">{{ 'commandSelectionSectionTitle' | translate }}</h3>
<mat-divider></mat-divider>
<mat-form-field appearance="fill" class="full-width">
<mat-label>{{ 'selectCommandsLabel' | translate }}</mat-label>
<mat-select formControlName="commandGroup" (selectionChange)="onCommandGroupChange()">
<mat-option *ngFor="let group of availableCommandGroups" [value]="group.uuid">
{{ group.name }}
</mat-option>
</mat-select>
<mat-error *ngIf="taskForm.get('commandGroup')?.invalid">{{ 'requiredFieldError' | translate }}</mat-error>
</mat-form-field>
<div class="button-container">
<button mat-button matStepperPrevious>Atrás</button>
<button mat-raised-button color="primary" matStepperNext>Continuar</button>
</div>
</mat-step>
<mat-form-field appearance="fill" class="full-width">
<mat-label>{{ 'selectIndividualCommandsLabel' | translate }}</mat-label>
<mat-select formControlName="extraCommands" multiple>
<mat-option *ngFor="let command of availableIndividualCommands" [value]="command.uuid">
{{ command.name }}
</mat-option>
</mat-select>
</mat-form-field>
<!-- Paso 3: Fecha de Ejecución y Hora -->
<mat-step label="Fecha de Ejecución y Hora">
<mat-form-field appearance="fill" class="full-width">
<mat-label>Fecha de Ejecución</mat-label>
<input matInput [matDatepicker]="picker" formControlName="date" placeholder="Selecciona una fecha">
<mat-datepicker-toggle matSuffix [for]="picker"></mat-datepicker-toggle>
<mat-datepicker #picker></mat-datepicker>
<mat-error *ngIf="taskForm.get('date')?.invalid">Este campo es obligatorio</mat-error>
</mat-form-field>
<h3 class="section-title">{{ 'executionDateTimeSectionTitle' | translate }}</h3>
<mat-divider></mat-divider>
<mat-form-field appearance="fill" class="full-width">
<mat-label>{{ 'executionDateLabel' | translate }}</mat-label>
<input matInput [matDatepicker]="picker" formControlName="date" placeholder="{{ 'selectDatePlaceholder' | translate }}">
<mat-datepicker-toggle matSuffix [for]="picker"></mat-datepicker-toggle>
<mat-datepicker #picker></mat-datepicker>
<mat-error *ngIf="taskForm.get('date')?.invalid">{{ 'requiredFieldError' | translate }}</mat-error>
</mat-form-field>
<mat-form-field appearance="fill" class="full-width">
<mat-label>Hora de Ejecución</mat-label>
<input matInput type="time" formControlName="time" placeholder="Selecciona una hora">
<mat-error *ngIf="taskForm.get('time')?.invalid">Este campo es obligatorio</mat-error>
</mat-form-field>
<mat-form-field appearance="fill" class="full-width">
<mat-label>{{ 'executionTimeLabel' | translate }}</mat-label>
<input matInput type="time" formControlName="time" placeholder="{{ 'selectTimePlaceholder' | translate }}">
<mat-error *ngIf="taskForm.get('time')?.invalid">{{ 'requiredFieldError' | translate }}</mat-error>
</mat-form-field>
<div class="button-container">
<button mat-button matStepperPrevious>Atrás</button>
<button mat-raised-button color="primary" matStepperNext>Continuar</button>
</div>
</mat-step>
<h3 class="section-title">{{ 'destinationSelectionSectionTitle' | translate }}</h3>
<mat-divider></mat-divider>
<mat-form-field appearance="fill" class="full-width">
<mat-label>{{ 'selectOrganizationalUnitLabel' | translate }}</mat-label>
<mat-select formControlName="organizationalUnit" (selectionChange)="onOrganizationalUnitChange()">
<mat-option *ngFor="let unit of availableOrganizationalUnits" [value]="unit['@id']">
{{ unit.name }}
</mat-option>
</mat-select>
<mat-error *ngIf="taskForm.get('organizationalUnit')?.invalid">{{ 'requiredFieldError' | translate }}</mat-error>
</mat-form-field>
<!-- Paso 4: Selecciona Unidad Organizacional, Aula y Clientes -->
<mat-step label="Selecciona Unidad Organizacional, Aula y Clientes">
<mat-form-field appearance="fill" class="full-width">
<mat-label>Selecciona Unidad Organizacional</mat-label>
<mat-select formControlName="organizationalUnit" (selectionChange)="onOrganizationalUnitChange()">
<mat-option *ngFor="let unit of availableOrganizationalUnits" [value]="unit['@id']">
{{ unit.name }}
</mat-option>
</mat-select>
<mat-error *ngIf="taskForm.get('organizationalUnit')?.invalid">Este campo es obligatorio</mat-error>
</mat-form-field>
<mat-form-field appearance="fill" class="full-width">
<mat-label>{{ 'selectClassroomLabel' | translate }}</mat-label>
<mat-select formControlName="selectedChild" (selectionChange)="onChildChange()">
<mat-option *ngFor="let child of selectedUnitChildren" [value]="child['@id']">
{{ child.name }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field appearance="fill" class="full-width">
<mat-label>Selecciona aula</mat-label>
<mat-select formControlName="selectedChild" (selectionChange)="onChildChange()">
<mat-option *ngFor="let child of selectedUnitChildren" [value]="child['@id']">
{{ child.name }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field appearance="fill" class="full-width">
<mat-label>{{ 'selectClientsLabel' | translate }}</mat-label>
<mat-select formControlName="selectedClients" multiple>
<mat-option (click)="toggleSelectAll()" [selected]="areAllSelected()">
{{ 'selectAllClients' | translate }}
</mat-option>
<mat-option *ngFor="let client of selectedClients" [value]="client.uuid">
{{ client.name }} ({{ client.ip }})
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field appearance="fill" class="full-width">
<mat-label>Selecciona Clientes</mat-label>
<mat-select formControlName="selectedClients" multiple>
<mat-option (click)="toggleSelectAll()" [selected]="areAllSelected()">
Seleccionar todos
</mat-option>
<mat-option *ngFor="let client of selectedClients" [value]="client.uuid">
{{ client.name }} ({{ client.ip }})
</mat-option>
</mat-select>
</mat-form-field>
<div class="button-container">
<button mat-button matStepperPrevious>Atrás</button>
<button mat-raised-button color="primary" (click)="saveTask()">Guardar</button>
</div>
</mat-step>
</mat-horizontal-stepper>
<mat-form-field appearance="fill" class="full-width">
<mat-label>Selecciona Clientes</mat-label>
<mat-select formControlName="selectedClients" multiple>
<mat-option (click)="toggleSelectAll()" [selected]="areAllSelected()">
Seleccionar todos
</mat-option>
<mat-option *ngFor="let client of selectedClients" [value]="client.uuid">
{{ client.name }} ({{ client.ip }})
</mat-option>
</mat-select>
</mat-form-field>
</mat-dialog-content>
</form>
<div class="button-container">
<button class="submit-button" (click)="saveTask()">{{ 'buttonSave' | translate }}</button>
</div>

View File

@ -3,6 +3,7 @@ import { HttpClient } from '@angular/common/http';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { ToastrService } from 'ngx-toastr';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { ConfigService } from '@services/config.service';
@Component({
selector: 'app-create-task',
@ -10,12 +11,12 @@ import { FormBuilder, FormGroup, Validators } from '@angular/forms';
styleUrls: ['./create-task.component.css']
})
export class CreateTaskComponent implements OnInit {
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL;
baseUrl: string;
taskForm: FormGroup;
availableCommandGroups: any[] = [];
selectedGroupCommands: any[] = [];
availableIndividualCommands: any[] = [];
apiUrl = `${this.baseUrl}/command-tasks`;
apiUrl: string;
editing: boolean = false;
availableOrganizationalUnits: any[] = [];
selectedUnitChildren: any[] = [];
@ -25,10 +26,13 @@ export class CreateTaskComponent implements OnInit {
constructor(
private fb: FormBuilder,
private http: HttpClient,
private configService: ConfigService,
private toastr: ToastrService,
public dialogRef: MatDialogRef<CreateTaskComponent>,
@Inject(MAT_DIALOG_DATA) public data: any
) {
this.baseUrl = this.configService.apiUrl;
this.apiUrl = `${this.baseUrl}/command-tasks`;
this.taskForm = this.fb.group({
commandGroup: ['', Validators.required],
extraCommands: [[]],
@ -49,8 +53,6 @@ export class CreateTaskComponent implements OnInit {
this.editing = true;
this.loadTaskData(this.data.task);
}
console.log(this.data);
}
loadCommandGroups(): void {
@ -101,29 +103,53 @@ export class CreateTaskComponent implements OnInit {
}
}
onCommandGroupChange(): void {
const selectedGroupId = this.taskForm.get('commandGroup')?.value;
this.http.get<any>(`${this.baseUrl}/command-groups/${selectedGroupId}`).subscribe(
(data) => {
this.selectedGroupCommands = data.commands;
},
(error) => {
this.toastr.error('Error al cargar los comandos del grupo seleccionado');
private collectClassrooms(unit: any): any[] {
let classrooms = [];
if (unit.type === 'classroom') {
classrooms.push(unit);
}
if (unit.children && unit.children.length > 0) {
for (let child of unit.children) {
classrooms = classrooms.concat(this.collectClassrooms(child));
}
);
}
return classrooms;
}
onOrganizationalUnitChange(): void {
const selectedUnitId = this.taskForm.get('organizationalUnit')?.value;
const selectedUnit = this.availableOrganizationalUnits.find(unit => unit['@id'] === selectedUnitId);
this.selectedUnitChildren = selectedUnit ? selectedUnit.children : [];
if (selectedUnit) {
this.selectedUnitChildren = this.collectClassrooms(selectedUnit);
} else {
this.selectedUnitChildren = [];
}
this.taskForm.patchValue({ selectedChild: '', selectedClients: [] });
this.selectedClients = [];
this.selectedClientIds.clear();
}
onChildChange(): void {
const selectedChildId = this.taskForm.get('selectedChild')?.value;
this.http.get<any>(`${this.baseUrl}${selectedChildId}`).subscribe(
if (!selectedChildId) {
this.selectedClients = [];
return;
}
const url = `${this.baseUrl}${selectedChildId}`.replace(/([^:]\/)\/+/g, '$1');
this.http.get<any>(url).subscribe(
(data) => {
this.selectedClients = data.clients;
if (Array.isArray(data.clients) && data.clients.length > 0) {
this.selectedClients = data.clients;
} else {
this.selectedClients = [];
this.toastr.warning('El aula seleccionada no tiene clientes.');
}
this.taskForm.patchValue({ selectedClients: [] });
this.selectedClientIds.clear();
},
@ -147,6 +173,18 @@ export class CreateTaskComponent implements OnInit {
return this.selectedClients.length > 0 && this.selectedClients.every(client => this.selectedClientIds.has(client.uuid));
}
onCommandGroupChange(): void {
const selectedGroupId = this.taskForm.get('commandGroup')?.value;
this.http.get<any>(`${this.baseUrl}/command-groups/${selectedGroupId}`).subscribe(
(data) => {
this.selectedGroupCommands = data.commands;
},
(error) => {
this.toastr.error('Error al cargar los comandos del grupo seleccionado');
}
);
}
saveTask(): void {
if (this.taskForm.invalid) {
this.toastr.error('Por favor, rellene todos los campos obligatorios');
@ -156,8 +194,8 @@ export class CreateTaskComponent implements OnInit {
const formData = this.taskForm.value;
const dateTime = this.combineDateAndTime(formData.date, formData.time);
const selectedCommands = formData.extraCommands && formData.extraCommands.length > 0
? formData.extraCommands.map((id: any) => `/commands/${id}`)
: null;
? formData.extraCommands.map((id: any) => `/commands/${id}`)
: null;
const payload: any = {
commandGroups: formData.commandGroup ? [`/command-groups/${formData.commandGroup}`] : null,

View File

@ -58,25 +58,3 @@
padding: 10px 20px;
}
button {
background-color: #3f51b5;
color: white;
padding: 10px 15px;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background-color: #2c387e;
}
.cancel-button {
background-color: #dc3545;
color: white;
}
.cancel-button:hover {
opacity: 0.9;
}

View File

@ -1,56 +1,55 @@
<div class="detail-task-container">
<h2>Detalles de la Tarea</h2>
<h2>{{ 'taskDetailsTitle' | translate }}</h2>
<mat-card>
<mat-card-header>
<mat-card-subtitle>Creado por: {{ task.createdBy }}</mat-card-subtitle>
</mat-card-header>
<mat-card>
<mat-card-header>
<mat-card-subtitle>{{ 'createdBy' | translate }}: {{ task.createdBy }}</mat-card-subtitle>
</mat-card-header>
<mat-card-content>
<p><strong>ID de la Tarea:</strong> {{ task.uuid }}</p>
<p><strong>Estado:</strong> {{ task.status }}</p>
<p><strong>Fecha de Creación:</strong> {{ task.createdAt | date: 'short' }}</p>
<p><strong>Notas:</strong> {{ task.notes }}</p>
<mat-card-content>
<p><strong>{{ 'taskId' | translate }}:</strong> {{ task.uuid }}</p>
<p><strong>{{ 'status' | translate }}:</strong> {{ task.status }}</p>
<p><strong>{{ 'creationDate' | translate }}:</strong> {{ task.createdAt | date: 'short' }}</p>
<p><strong>{{ 'notes' | translate }}:</strong> {{ task.notes }}</p>
<h3>Grupos de Comandos Incluidos:</h3>
<table mat-table [dataSource]="task.commandGroups" class="mat-elevation-z8">
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef> Grupo de Comandos </th>
<td mat-cell *matCellDef="let group"> {{ group.name }} </td>
<h3>{{ 'includedCommandGroups' | translate }}</h3>
<table mat-table [dataSource]="task.commandGroups" class="mat-elevation-z8">
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef> {{ 'commandGroupColumn' | translate }} </th>
<td mat-cell *matCellDef="let group"> {{ group.name }} </td>
</ng-container>
<ng-container matColumnDef="uuid">
<th mat-header-cell *matHeaderCellDef> UUID </th>
<td mat-cell *matCellDef="let group"> {{ group.uuid }} </td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="['name', 'uuid']"></tr>
<tr mat-row *matRowDef="let row; columns: ['name', 'uuid'];"></tr>
</table>
<h3>{{ 'commandsToExecute' | translate }}</h3>
<div *ngFor="let group of task.commandGroups">
<p><strong>{{ 'group' | translate }}: </strong>{{ group.name }}</p>
<table mat-table [dataSource]="group.commands" class="mat-elevation-z8">
<ng-container matColumnDef="commandName">
<th mat-header-cell *matHeaderCellDef> {{ 'commandColumn' | translate }} </th>
<td mat-cell *matCellDef="let command"> {{ command.name }} </td>
</ng-container>
<ng-container matColumnDef="uuid">
<ng-container matColumnDef="commandUuid">
<th mat-header-cell *matHeaderCellDef> UUID </th>
<td mat-cell *matCellDef="let group"> {{ group.uuid }} </td>
<td mat-cell *matCellDef="let command"> {{ command.uuid }} </td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="['name', 'uuid']"></tr>
<tr mat-row *matRowDef="let row; columns: ['name', 'uuid'];"></tr>
<tr mat-header-row *matHeaderRowDef="['commandName', 'commandUuid']"></tr>
<tr mat-row *matRowDef="let row; columns: ['commandName', 'commandUuid'];"></tr>
</table>
</div>
</mat-card-content>
</mat-card>
<h3>Comandos a ejecutar:</h3>
<div *ngFor="let group of task.commandGroups">
<p><strong>Grupo: </strong>{{ group.name }}</p>
<table mat-table [dataSource]="group.commands" class="mat-elevation-z8">
<ng-container matColumnDef="commandName">
<th mat-header-cell *matHeaderCellDef> Comando </th>
<td mat-cell *matCellDef="let command"> {{ command.name }} </td>
</ng-container>
<ng-container matColumnDef="commandUuid">
<th mat-header-cell *matHeaderCellDef> UUID </th>
<td mat-cell *matCellDef="let command"> {{ command.uuid }} </td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="['commandName', 'commandUuid']"></tr>
<tr mat-row *matRowDef="let row; columns: ['commandName', 'commandUuid'];"></tr>
</table>
</div>
</mat-card-content>
</mat-card>
<div class="task-actions">
<button mat-flat-button class="cancel-button" (click)="closeDialog()">Cerrar</button>
</div>
<div class="task-actions">
<button class="ordinary-button" (click)="closeDialog()">Cancel</button>
</div>
</div>

View File

@ -13,11 +13,9 @@ export class DetailTaskComponent {
public dialogRef: MatDialogRef<DetailTaskComponent>,
@Inject(MAT_DIALOG_DATA) public data: any
) {
this.task = data.task; // Asignamos la tarea que viene en el modal
console.log('tasaas',this.task);
this.task = data.task;
}
// Método opcional para cerrar el modal
closeDialog(): void {
this.dialogRef.close();
}

View File

@ -0,0 +1,7 @@
<h1 mat-dialog-title>{{ 'inputDetails' | translate }}</h1>
<div mat-dialog-content>
<pre>{{ data.input | json }}</pre>
</div>
<div mat-dialog-actions align="end">
<button class="ordinary-button" (click)="close()">{{ 'closeButton' | translate }}</button>
</div>

View File

@ -0,0 +1,47 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { InputDialogComponent } from './input-dialog.component';
import {MAT_DIALOG_DATA, MatDialogModule, MatDialogRef} from "@angular/material/dialog";
import {FormBuilder} from "@angular/forms";
import {ToastrService} from "ngx-toastr";
import {provideHttpClient} from "@angular/common/http";
import {provideHttpClientTesting} from "@angular/common/http/testing";
import {TranslateModule} from "@ngx-translate/core";
describe('InputDialogComponent', () => {
let component: InputDialogComponent;
let fixture: ComponentFixture<InputDialogComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [InputDialogComponent],
imports: [
MatDialogModule,
TranslateModule.forRoot(),
],
providers: [
FormBuilder,
ToastrService,
provideHttpClient(),
provideHttpClientTesting(),
{
provide: MatDialogRef,
useValue: {}
},
{
provide: MAT_DIALOG_DATA,
useValue: {}
}
]
})
.compileComponents();
fixture = TestBed.createComponent(InputDialogComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,18 @@
import { Component, Inject } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
@Component({
selector: 'app-input-dialog',
templateUrl: './input-dialog.component.html',
styleUrl: './input-dialog.component.css'
})
export class InputDialogComponent {
constructor(
public dialogRef: MatDialogRef<InputDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: { input: any }
) {}
close(): void {
this.dialogRef.close();
}
}

View File

@ -1,15 +1,20 @@
.title {
font-size: 24px;
.header-container {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 10px;
border-bottom: 1px solid #ddd;
}
.header-container-title {
flex-grow: 1;
text-align: left;
margin-left: 1em;
}
.calendar-button-row {
display: flex;
justify-content: flex-start;
margin-top: 16px;
}
.divider {
margin: 20px 0;
gap: 15px;
}
.lists-container {
@ -27,14 +32,13 @@
table {
width: 100%;
margin-top: 50px;
}
.search-container {
.search-container {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 5px;
margin: 1.5rem 0rem 1.5rem 0rem;
box-sizing: border-box;
}
@ -53,15 +57,14 @@ table {
padding: 5px;
}
.header-container {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px;
.mat-elevation-z8 {
box-shadow: 0px 0px 0px rgba(0, 0, 0, 0.2);
}
.mat-elevation-z8 {
box-shadow: 0px 0px 0px rgba(0,0,0,0.2);
.progress-container {
display: flex;
align-items: center;
gap: 10px;
}
.paginator-container {
@ -70,13 +73,46 @@ table {
margin-bottom: 30px;
}
.mat-chip-readonly-true {
background-color: #4CAF50 !important;
color: white !important;
.chip-failed {
background-color: #e87979 !important;
color: white;
}
.mat-chip-readonly-false {
background-color: #F44336 !important;
color: white !important;
.chip-success {
background-color: #46c446 !important;
color: white;
}
.chip-pending {
background-color: #bebdbd !important;
color: black;
}
.chip-in-progress {
background-color: #f5a623 !important;
color: white;
}
.status-progress-flex {
display: flex;
align-items: center;
gap: 8px;
}
button.cancel-button {
display: flex;
align-items: center;
justify-content: center;
padding: 5px;
}
.cancel-button {
color: red;
background-color: transparent;
border: none;
padding: 0;
}
.cancel-button mat-icon {
color: red;
}

View File

@ -1,50 +1,128 @@
<div class="header-container">
<h2 class="title" i18n="@@adminCommandsTitle">Trazas de comandos y procedimientos</h2>
<button mat-icon-button color="primary" (click)="iniciarTour()">
<mat-icon>help</mat-icon>
</button>
<div class="header-container-title">
<h2 joyrideStep="titleStep" text="{{ 'titleStepText' | translate }}">{{ 'adminCommandsTitle' |
translate }}</h2>
</div>
<div class="images-button-row">
<button mat-flat-button color="primary" (click)="resetFilters()">Reiniciar filtros</button>
<button class="action-button" (click)="resetFilters()" joyrideStep="resetFiltersStep"
text="{{ 'resetFiltersStepText' | translate }}">
{{ 'resetFilters' | translate }}
</button>
</div>
</div>
<mat-divider class="divider"></mat-divider>
<div class="search-container">
<mat-form-field appearance="fill" class="search-select">
<input type="text" matInput [formControl]="clientControl" [matAutocomplete]="clientAuto" placeholder="Seleccione un cliente">
<mat-autocomplete #clientAuto="matAutocomplete" [displayWith]="displayFnClient" (optionSelected)="onOptionClientSelected($event.option.value)">
<mat-form-field appearance="fill" class="search-select" joyrideStep="clientSelectStep"
text="{{ 'clientSelectStepText' | translate }}">
<input type="text" matInput [formControl]="clientControl" [matAutocomplete]="clientAuto"
placeholder="{{ 'filterClientPlaceholder' | translate }}">
<mat-autocomplete #clientAuto="matAutocomplete" [displayWith]="displayFnClient"
(optionSelected)="onOptionClientSelected($event.option.value)">
<mat-option *ngFor="let client of filteredClients | async" [value]="client">
{{ client.name }}
</mat-option>
</mat-autocomplete>
</mat-form-field>
<!-- Autocomplete para seleccionar un comando -->
<mat-form-field appearance="fill" class="search-select">
<input type="text" matInput [formControl]="commandControl" [matAutocomplete]="commandAuto" placeholder="Seleccione un comando">
<mat-autocomplete #commandAuto="matAutocomplete" [displayWith]="displayFnCommand" (optionSelected)="onOptionCommandSelected($event.option.value)">
<mat-form-field appearance="fill" class="search-select" joyrideStep="commandSelectStep"
text="{{ 'commandSelectStepText' | translate }}">
<input type="text" matInput [formControl]="commandControl" [matAutocomplete]="commandAuto"
placeholder="{{ 'filterCommandPlaceholder' | translate }}">
<mat-autocomplete #commandAuto="matAutocomplete" [displayWith]="displayFnCommand"
(optionSelected)="onOptionCommandSelected($event.option.value)">
<mat-option *ngFor="let command of filteredCommands | async" [value]="command">
{{ command.name }}
</mat-option>
</mat-autocomplete>
</mat-form-field>
<mat-form-field appearance="fill" class="search-boolean">
<mat-label i18n="@@searchLabel">Estado</mat-label>
<mat-select [(ngModel)]="filters['status']" (selectionChange)="loadTraces()" placeholder="Seleccionar opción">
<mat-option [value]="undefined">Todos</mat-option>
<mat-option [value]="'failed'">Fallido</mat-option>
<mat-option [value]="'pending'">Pendiente de ejecutar</mat-option>
<mat-option [value]="'in-progress'">Ejecutando</mat-option>
<mat-option [value]="'success'">Completado con éxito</mat-option>
<mat-option [value]="'cancelled'">Cancelado</mat-option>
</mat-select>
</mat-form-field>
</div>
<table mat-table [dataSource]="traces" class="mat-elevation-z8">
<ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef">
<th mat-header-cell *matHeaderCellDef> {{ column.header }} </th>
<td mat-cell *matCellDef="let trace" >
<ng-container >
{{ column.cell(trace) }}
</ng-container>
</td>
</ng-container>
<app-loading [isLoading]="loading"></app-loading>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
<div *ngIf="!loading">
<table mat-table [dataSource]="traces" class="mat-elevation-z8" joyrideStep="tableStep"
text="{{ 'tableStepText' | translate }}">
<ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef">
<th mat-header-cell *matHeaderCellDef> {{ column.header }} </th>
<td mat-cell *matCellDef="let trace">
<div class="paginator-container">
<mat-paginator [length]="length"
[pageSize]="itemsPerPage"
[pageIndex]="page"
[pageSizeOptions]="pageSizeOptions"
(page)="onPageChange($event)">
<ng-container [ngSwitch]="column.columnDef">
<ng-container *ngSwitchCase="'status'">
<ng-container *ngIf="trace.status === 'in-progress' && trace.progress; else statusChip">
<div class="progress-container">
<mat-progress-bar class="example-margin" [mode]="mode" [value]="trace.progress" [bufferValue]="bufferValue">
</mat-progress-bar>
<span>{{trace.progress}}%</span>
</div>
</ng-container>
<ng-template #statusChip>
<div class="status-progress-flex">
<mat-chip [ngClass]="{
'chip-failed': trace.status === 'failed',
'chip-success': trace.status === 'success',
'chip-pending': trace.status === 'pending',
'chip-in-progress': trace.status === 'in-progress',
'chip-cancelled': trace.status === 'cancelled'
}">
{{
trace.status === 'failed' ? 'Fallido' :
trace.status === 'in-progress' ? 'En ejecución' :
trace.status === 'success' ? 'Finalizado con éxito' :
trace.status === 'pending' ? 'Pendiente de ejecutar' :
trace.status === 'cancelled' ? 'Cancelado' :
trace.status
}}
</mat-chip>
<button *ngIf="trace.status === 'in-progress' && trace.command === 'deploy-image'"
mat-icon-button
(click)="cancelTrace(trace)"
class="cancel-button"
matTooltip="Cancelar transmisión de imagen">
<mat-icon>cancel</mat-icon>
</button>
</div>
</ng-template>
</ng-container>
<ng-container *ngSwitchCase="'input'">
<button mat-icon-button (click)="openInputModal(trace.input)">
<mat-icon>info</mat-icon>
</button>
</ng-container>
<ng-container *ngSwitchDefault>
{{ column.cell(trace) }}
</ng-container>
</ng-container>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
</div>
<div class="paginator-container" joyrideStep="paginationStep" text="{{ 'paginationStepText' | translate }}">
<mat-paginator [length]="length" [pageSize]="itemsPerPage" [pageIndex]="page" [pageSizeOptions]="pageSizeOptions"
(page)="onPageChange($event)">
</mat-paginator>
</div>

View File

@ -1,9 +1,16 @@
import { Component, OnInit } from '@angular/core';
import { ChangeDetectorRef, Component, OnInit } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import {Observable, startWith} from 'rxjs';
import {FormControl} from "@angular/forms";
import {map} from "rxjs/operators";
import {DatePipe} from "@angular/common";
import { Observable, forkJoin } from 'rxjs';
import { FormControl } from '@angular/forms';
import { map, startWith } from 'rxjs/operators';
import { DatePipe } from '@angular/common';
import { JoyrideService } from 'ngx-joyride';
import { MatDialog } from "@angular/material/dialog";
import { InputDialogComponent } from "./input-dialog/input-dialog.component";
import { ProgressBarMode } from '@angular/material/progress-bar';
import { DeleteModalComponent } from "../../../../shared/delete_modal/delete-modal/delete-modal.component";
import { ToastrService } from "ngx-toastr";
import { ConfigService } from '@services/config.service';
@Component({
selector: 'app-task-logs',
@ -11,7 +18,8 @@ import {DatePipe} from "@angular/common";
styleUrls: ['./task-logs.component.css']
})
export class TaskLogsComponent implements OnInit {
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL;
baseUrl: string;
mercureUrl: string;
traces: any[] = [];
groupedTraces: any[] = [];
commands: any[] = [];
@ -22,6 +30,9 @@ export class TaskLogsComponent implements OnInit {
loading: boolean = true;
pageSizeOptions: number[] = [10, 20, 30, 50];
datePipe: DatePipe = new DatePipe('es-ES');
mode: ProgressBarMode = 'buffer';
progress = 0;
bufferValue = 0;
columns = [
{
@ -32,7 +43,7 @@ export class TaskLogsComponent implements OnInit {
{
columnDef: 'command',
header: 'Comando',
cell: (trace: any) => `${trace.command?.name}`
cell: (trace: any) => `${trace.command}`
},
{
columnDef: 'client',
@ -44,16 +55,31 @@ export class TaskLogsComponent implements OnInit {
header: 'Estado',
cell: (trace: any) => `${trace.status}`
},
{
columnDef: 'jobId',
header: 'Hilo de trabajo',
cell: (trace: any) => `${trace.jobId}`
},
{
columnDef: 'input',
header: 'Input',
cell: (trace: any) => `${trace.input}`
},
{
columnDef: 'output',
header: 'Logs',
cell: (trace: any) => `${trace.output}`
},
{
columnDef: 'executedAt',
header: 'Programacion de ejecución',
header: 'Programación de ejecución',
cell: (trace: any) => `${this.datePipe.transform(trace.executedAt, 'dd/MM/yyyy hh:mm:ss')}`,
},
{
columnDef: 'createdAt',
header: 'Fecha de creación',
cell: (trace: any) => `${this.datePipe.transform(trace.createdAt, 'dd/MM/yyyy hh:mm:ss')}`,
}
columnDef: 'finishedAt',
header: 'Finalización',
cell: (trace: any) => `${this.datePipe.transform(trace.finishedAt, 'dd/MM/yyyy hh:mm:ss')}`,
},
];
displayedColumns = [...this.columns.map(column => column.columnDef)];
@ -63,12 +89,21 @@ export class TaskLogsComponent implements OnInit {
filteredCommands!: Observable<any[]>;
commandControl = new FormControl();
constructor(private http: HttpClient) {}
constructor(private http: HttpClient,
private joyrideService: JoyrideService,
private dialog: MatDialog,
private cdr: ChangeDetectorRef,
private configService: ConfigService,
private toastService: ToastrService
) {
this.baseUrl = this.configService.apiUrl;
this.mercureUrl = this.configService.mercureUrl;
}
ngOnInit(): void {
this.loadTraces();
this.loadCommands();
this.loadClients();
//this.loadClients();
this.filteredCommands = this.commandControl.valueChanges.pipe(
startWith(''),
map(value => (typeof value === 'string' ? value : value?.name)),
@ -79,8 +114,39 @@ export class TaskLogsComponent implements OnInit {
map(value => (typeof value === 'string' ? value : value?.name)),
map(name => (name ? this._filterClients(name) : this.clients.slice()))
);
const eventSource = new EventSource(`${this.mercureUrl}?topic=`
+ encodeURIComponent(`traces`));
eventSource.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data && data['@id']) {
this.updateTracesStatus(data['@id'], data.status, data.progress);
}
}
}
private updateTracesStatus(clientUuid: string, newStatus: string, progress: Number): void {
const traceIndex = this.traces.findIndex(trace => trace['@id'] === clientUuid);
if (traceIndex !== -1) {
const updatedTraces = [...this.traces];
updatedTraces[traceIndex] = {
...updatedTraces[traceIndex],
status: newStatus,
progress: progress
};
this.traces = updatedTraces;
this.cdr.detectChanges();
console.log(`Estado actualizado para la traza ${clientUuid}: ${newStatus}`);
} else {
console.warn(`Traza con UUID ${clientUuid} no encontrado en la lista.`);
}
}
private _filterClients(name: string): any[] {
const filterValue = name.toLowerCase();
return this.clients.filter(client => client.name.toLowerCase().includes(filterValue));
@ -109,41 +175,87 @@ export class TaskLogsComponent implements OnInit {
this.loadTraces();
}
openInputModal(inputData: any): void {
this.dialog.open(InputDialogComponent, {
width: '700px',
data: { input: inputData }
});
}
cancelTrace(trace: any): void {
this.dialog.open(DeleteModalComponent, {
width: '300px',
data: { name: trace.jobId },
}).afterClosed().subscribe((result) => {
if (result) {
this.http.post(`${this.baseUrl}/traces/server/${trace.uuid}/cancel`, {}).subscribe({
next: () => {
this.toastService.success('Transmision de imagen cancelada');
this.loadTraces();
},
error: (error) => {
this.toastService.error(error.error['hydra:description']);
console.error(error.error['hydra:description']);
}
});
}
});
}
loadTraces(): void {
this.loading = true;
const url = `${this.baseUrl}/traces?page=${this.page + 1}&itemsPerPage=${this.itemsPerPage}`;
this.http.get<any>(url, { params: this.filters }).subscribe(
const params = { ...this.filters };
if (params['status'] === undefined) {
delete params['status'];
}
this.http.get<any>(url, { params }).subscribe(
(data) => {
this.traces = data['hydra:member'];
this.length = data['hydra:totalItems'];
this.groupedTraces = this.groupByCommandId(this.traces);
this.loading = false;
},
(error) => {
console.error('Error fetching traces', error);
this.loading = false;
}
);
}
loadCommands() {
this.http.get<any>( `${this.baseUrl}/commands?&page=1&itemsPerPage=10000`).subscribe(
this.loading = true;
this.http.get<any>(`${this.baseUrl}/commands?&page=1&itemsPerPage=10000`).subscribe(
response => {
this.commands = response['hydra:member'];
this.loading = false;
},
error => {
console.error('Error fetching parent units:', error);
console.error('Error fetching commands:', error);
this.loading = false;
}
);
}
loadClients() {
this.http.get<any>( `${this.baseUrl}/clients?&page=1&itemsPerPage=10000`).subscribe(
this.loading = true;
this.http.get<any>(`${this.baseUrl}/clients?&page=1&itemsPerPage=10000`).subscribe(
response => {
this.clients = response['hydra:member'];
this.loading = false;
const clientIds = response['hydra:member'].map((client: any) => client['@id']);
const clientDetailsRequests: Observable<any>[] = clientIds.map((id: string) => this.http.get<any>(`${this.baseUrl}${id}`));
forkJoin(clientDetailsRequests).subscribe(
(clients: any[]) => {
this.clients = clients;
this.loading = false;
},
(error: any) => {
console.error('Error fetching client details:', error);
this.loading = false;
}
);
},
error => {
console.error('Error fetching parent units:', error);
(error: any) => {
console.error('Error fetching clients:', error);
this.loading = false;
}
);
@ -178,4 +290,20 @@ export class TaskLogsComponent implements OnInit {
this.length = event.length;
this.loadTraces();
}
iniciarTour(): void {
this.joyrideService.startTour({
steps: [
'titleStep',
'resetFiltersStep',
'clientSelectStep',
'commandSelectStep',
'tableStep',
'paginationStep'
],
showPrevButton: true,
themeColor: '#3f51b5'
});
}
}

View File

@ -1,11 +1,12 @@
.title {
font-size: 24px;
.header-container-title {
flex-grow: 1;
text-align: left;
margin-left: 1em;
}
.calendar-button-row {
.command-button-row {
display: flex;
justify-content: flex-start;
margin-top: 16px;
gap: 15px;
}
.divider {
@ -27,7 +28,6 @@
table {
width: 100%;
margin-top: 50px;
}
.search-container {
@ -35,8 +35,8 @@ table {
justify-content: space-between;
align-items: center;
width: 100%;
padding: 0 5px;
box-sizing: border-box;
margin: 1.5rem 0rem 1.5rem 0rem;
}
.search-string {
@ -53,7 +53,8 @@ table {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px;
padding: 10px 10px;
border-bottom: 1px solid #ddd;
}
.mat-elevation-z8 {

View File

@ -1,49 +1,60 @@
<div class="header-container">
<h2 class="title" i18n="@@adminCommandsTitle">Administrar Comandos</h2>
<div class="command-button-row">
<button mat-flat-button color="primary" (click)="openCreateCommandModal()">Añadir Comando</button>
<button mat-icon-button color="primary" (click)="iniciarTour()">
<mat-icon>help</mat-icon>
</button>
<div class="header-container-title">
<h2 joyrideStep="titleStep" text="{{ 'titleStepText' | translate }}">{{ 'CommandsTitle' | translate }}</h2>
</div>
<div class="command-button-row" joyrideStep="addCommandStep" text="{{ 'addCommandStepText' | translate }}">
<button class="action-button" (click)="openCreateCommandModal()">{{ 'addCommand' | translate }}</button>
</div>
</div>
<mat-divider class="divider"></mat-divider>
<div class="search-container">
<div class="search-container" joyrideStep="searchStep" text="{{ 'searchStepText' | translate }}">
<mat-form-field appearance="fill" class="search-string">
<mat-label i18n="@@searchLabel">Buscar nombre de comando</mat-label>
<input matInput placeholder="Búsqueda" [(ngModel)]="filters['name']" (keyup.enter)="search()" i18n-placeholder="@@searchPlaceholder">
<mat-label>{{ 'searchCommandLabel' | translate }}</mat-label>
<input matInput placeholder="{{ 'searchPlaceholder' | translate }}" [(ngModel)]="filters['name']" (keyup.enter)="search()" />
<mat-icon matSuffix>search</mat-icon>
<mat-hint i18n="@@searchHint">Pulsar 'enter' para buscar</mat-hint>
<mat-hint>{{ 'searchHint' | translate }}</mat-hint>
</mat-form-field>
</div>
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8">
<ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef">
<th mat-header-cell *matHeaderCellDef> {{ column.header }} </th>
<td mat-cell *matCellDef="let command" >
<ng-container *ngIf="column.columnDef !== 'readOnly'">
{{ column.cell(command) }}
</ng-container>
<ng-container *ngIf="column.columnDef === 'readOnly'" >
<mat-chip *ngIf="command.readOnly" class="mat-chip-readonly-true"><mat-icon style="color:white;">check</mat-icon></mat-chip>
<mat-chip *ngIf="!command.readOnly" class="mat-chip-readonly-false"> <mat-icon style="color:white;">close</mat-icon></mat-chip>
</ng-container>
</td>
</ng-container>
<div *ngIf="loading" class="loading-container" joyrideStep="loadingStep" text="{{ 'loadingStepText' | translate }}">
<app-loading [isLoading]="loading"></app-loading>
</div>
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef i18n="@@columnActions" style="text-align: center;">Acciones</th>
<td mat-cell *matCellDef="let client" style="text-align: center;">
<button mat-icon-button color="info" (click)="viewDetails($event, client)"><mat-icon i18n="@@deleteElementTooltip">visibility</mat-icon></button>
<button mat-icon-button color="primary" (click)="editCommand($event, client)" i18n="@@editImage"> <mat-icon>edit</mat-icon></button>
<button mat-icon-button color="warn" (click)="deleteCommand($event, client)">
<mat-icon i18n="@@deleteElementTooltip">delete</mat-icon>
</button>
</td>
</ng-container>
<div *ngIf="!loading">
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8" joyrideStep="tableStep" text="{{ 'tableStepText' | translate }}">
<ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef">
<th mat-header-cell *matHeaderCellDef> {{ column.header }} </th>
<td mat-cell *matCellDef="let command">
<ng-container *ngIf="column.columnDef !== 'readOnly'">
{{ column.cell(command) }}
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
<ng-container *ngIf="column.columnDef === 'readOnly'">
<mat-icon [color]="command[column.columnDef] ? 'primary' : 'warn'">
{{ command[column.columnDef] ? 'check_circle' : 'cancel' }}
</mat-icon>
</ng-container>
</td>
</ng-container>
<div class="paginator-container">
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef style="text-align: center;">{{ 'columnActions' | translate }}</th>
<td mat-cell *matCellDef="let command" style="text-align: center;" joyrideStep="actionsStep" text="{{ 'actionsStepText' | translate }}">
<button mat-icon-button color="info" (click)="viewDetails($event, command)"><mat-icon>visibility</mat-icon></button>
<button mat-icon-button color="primary" [disabled]="command.readOnly" (click)="editCommand($event, command)"><mat-icon>edit</mat-icon></button>
<button mat-icon-button color="warn" [disabled]="command.readOnly" (click)="deleteCommand($event, command)"><mat-icon>delete</mat-icon></button>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
</div>
<div class="paginator-container" joyrideStep="paginationStep" text="{{ 'paginationStepText' | translate }}">
<mat-paginator [length]="length"
[pageSize]="itemsPerPage"
[pageIndex]="page"

View File

@ -1,33 +1,73 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http';
import { TreeViewComponent } from '../../groups/shared/tree-view/tree-view.component';
import { MatDialogModule } from '@angular/material/dialog'; // <-- Import MatDialogModule
import { MatFormFieldModule } from '@angular/material/form-field'; // Import for mat-form-field
import { MatInputModule } from '@angular/material/input'; // Import for matInput
import { MatDividerModule } from '@angular/material/divider'; // Import for mat-divider
import { ToastrModule } from 'ngx-toastr'; // Import for Toastr
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material/dialog';
import { ToastrModule } from 'ngx-toastr';
import { MatTableModule } from '@angular/material/table';
import { DatePipe } from '@angular/common';
import { CommandsComponent } from './commands.component';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatDividerModule } from '@angular/material/divider';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { MatPaginatorModule } from '@angular/material/paginator';
import { MatProgressSpinner, MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatSelectModule } from '@angular/material/select';
import { MatTooltipModule } from '@angular/material/tooltip';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { NgxChartsModule } from '@swimlane/ngx-charts';
import { TranslateModule } from '@ngx-translate/core';
import { JoyrideModule } from 'ngx-joyride';
import { LoadingComponent } from '../../../shared/loading/loading.component';
import { ConfigService } from '@services/config.service';
describe('TreeViewComponent', () => {
let component: TreeViewComponent;
let fixture: ComponentFixture<TreeViewComponent>;
describe('CommandsComponent', () => {
let component: CommandsComponent;
let fixture: ComponentFixture<CommandsComponent>;
beforeEach(async () => {
const mockConfigService = {
apiUrl: 'http://mock-api-url',
mercureUrl: 'http://mock-mercure-url'
};
await TestBed.configureTestingModule({
declarations: [TreeViewComponent],
declarations: [CommandsComponent, LoadingComponent],
imports: [
MatDialogModule, // <-- Add MatDialogModule here
MatFormFieldModule, // <-- For mat-form-field
MatInputModule, // <-- For matInput
MatDividerModule, // <-- For mat-divider
ToastrModule.forRoot() // <-- For ToastrService
HttpClientTestingModule,
ToastrModule.forRoot(),
BrowserAnimationsModule,
MatDividerModule,
MatFormFieldModule,
MatInputModule,
MatIconModule,
MatButtonModule,
MatTableModule,
MatPaginatorModule,
MatTooltipModule,
FormsModule,
MatProgressSpinner,
MatProgressSpinnerModule,
MatDialogModule,
ReactiveFormsModule,
MatSelectModule,
NgxChartsModule,
DatePipe,
TranslateModule.forRoot(),
JoyrideModule.forRoot(),
],
providers: [
provideHttpClient(withInterceptorsFromDi())
{ provide: MatDialogRef, useValue: {} },
{ provide: MAT_DIALOG_DATA, useValue: {} },
{ provide: ConfigService, useValue: mockConfigService }
]
})
.compileComponents();
fixture = TestBed.createComponent(TreeViewComponent);
}).compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(CommandsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
@ -35,4 +75,5 @@ describe('TreeViewComponent', () => {
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -5,8 +5,10 @@ import { ToastrService } from 'ngx-toastr';
import { CommandDetailComponent } from './detail-command/command-detail.component';
import { CreateCommandComponent } from './create-command/create-command.component';
import { DeleteModalComponent } from '../../../shared/delete_modal/delete-modal/delete-modal.component';
import {MatTableDataSource} from "@angular/material/table";
import {DatePipe} from "@angular/common";
import { MatTableDataSource } from '@angular/material/table';
import { DatePipe } from '@angular/common';
import { ConfigService } from '@services/config.service';
import { JoyrideService } from 'ngx-joyride';
@Component({
selector: 'app-commands',
@ -14,7 +16,8 @@ import {DatePipe} from "@angular/common";
styleUrls: ['./commands.component.css']
})
export class CommandsComponent implements OnInit {
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL;
baseUrl: string;
private apiUrl: string;
dataSource = new MatTableDataSource<any>();
filters: { [key: string]: string | boolean } = {};
length: number = 0;
@ -22,6 +25,7 @@ export class CommandsComponent implements OnInit {
page: number = 0;
pageSizeOptions: number[] = [10, 20, 40, 100];
datePipe: DatePipe = new DatePipe('es-ES');
loading: boolean = false;
columns = [
{
columnDef: 'id',
@ -45,22 +49,28 @@ export class CommandsComponent implements OnInit {
}
];
displayedColumns = [...this.columns.map(column => column.columnDef), 'actions'];
private apiUrl = `${this.baseUrl}/commands`;
constructor(private http: HttpClient, private dialog: MatDialog, private toastService: ToastrService) {}
constructor(private http: HttpClient, private dialog: MatDialog, private toastService: ToastrService,
private joyrideService: JoyrideService, private configService: ConfigService) {
this.baseUrl = this.configService.apiUrl;
this.apiUrl = `${this.baseUrl}/commands`;
}
ngOnInit(): void {
this.search();
}
search(): void {
this.http.get<any>(`${this.apiUrl}?page=${this.page +1 }&itemsPerPage=${this.itemsPerPage}`, { params: this.filters }).subscribe(
this.loading = true;
this.http.get<any>(`${this.apiUrl}?page=${this.page + 1}&itemsPerPage=${this.itemsPerPage}`, { params: this.filters }).subscribe(
(data) => {
this.dataSource.data = data['hydra:member'];
this.length = data['hydra:totalItems'];
this.loading = false;
},
(error) => {
console.error('Error fetching commands', error);
this.loading = false;
}
);
}
@ -70,24 +80,24 @@ export class CommandsComponent implements OnInit {
this.dialog.open(CommandDetailComponent, {
width: '800px',
data: command,
}).afterClosed().subscribe(() => this.search());
});
}
openCreateCommandModal(): void {
this.dialog.open(CreateCommandComponent, {
width: '600px',
width: '800px',
}).afterClosed().subscribe(() => this.search());
}
editCommand(event: MouseEvent, command: any): void {
event.stopPropagation();
this.dialog.open(CreateCommandComponent, {
width: '600px',
width: '800px',
data: command['@id']
}).afterClosed().subscribe(() => this.search());
}
deleteCommand(event: MouseEvent,command: any): void {
deleteCommand(event: MouseEvent, command: any): void {
event.stopPropagation();
this.dialog.open(DeleteModalComponent, {
width: '300px',
@ -113,4 +123,19 @@ export class CommandsComponent implements OnInit {
this.length = event.length;
this.search();
}
iniciarTour(): void {
this.joyrideService.startTour({
steps: [
'titleStep',
'addCommandStep',
'searchStep',
'tableStep',
'actionsStep'
],
showPrevButton: true,
themeColor: '#3f51b5'
});
}
}

View File

@ -7,12 +7,6 @@
.form-group {
margin-top: 20px;
margin-bottom: 26px;
}
.full-width {
width: 100%;
margin-bottom: 16px;
}
.additional-form {
@ -58,3 +52,20 @@
cursor: pointer;
}
.action-container {
display: flex;
justify-content: flex-end;
gap: 1em;
padding: 1.5em;
}
.checkbox-with-hint {
display: flex;
flex-direction: column;
}
.hint-text {
font-size: 12px;
color: gray;
margin-left: 40px;
}

Some files were not shown because too many files have changed in this diff Show More