Compare commits

...

320 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
392 changed files with 15611 additions and 9626 deletions

7
.gitignore vendored
View File

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

View File

@ -1,7 +1,97 @@
# Changelog # 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.5.0] - 2024-11-19 ---
## [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
- Added functionality to execute actions from the menu in the general groups screen. - Added functionality to execute actions from the menu in the general groups screen.
- Displayed the selected center on the general screen for better context. - Displayed the selected center on the general screen for better context.
@ -14,7 +104,7 @@
### Improved ### Improved
- Renamed the field "Reserved Room" to "Available RemotePC" for better clarity. - Renamed the field "Reserved Room" to "Available RemotePC" for better clarity.
- Added view sizes for visual cards (`mat-card`). - Added view sizes for visual cards.
- Refactored the task stepper to improve efficiency and readability. - Refactored the task stepper to improve efficiency and readability.
- Replaced red "X" icons with more consistent and user-friendly visuals. - Replaced red "X" icons with more consistent and user-friendly visuals.
- Allowed logical names for IP addresses in the subnets section. - Allowed logical names for IP addresses in the subnets section.
@ -22,10 +112,8 @@
- Made predefined commands read-only to prevent accidental modifications. - Made predefined commands read-only to prevent accidental modifications.
- Simplified the task creation modal to enhance user experience. - Simplified the task creation modal to enhance user experience.
- Adjusted the translation system to cover new elements and improve consistency (work in progress). - Adjusted the translation system to cover new elements and improve consistency (work in progress).
- New element view from clients on groups main view.
### Fixed ### Fixed
- Resolved an issue that prevented editing software profiles correctly. - Resolved an issue that prevented editing software profiles correctly.
- Fixed a bug where newly created commands failed to execute in the commands section. - 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 .DS_Store
Thumbs.db Thumbs.db
test-results/

View File

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

View File

@ -24,6 +24,7 @@
"jwt-decode": "^4.0.0", "jwt-decode": "^4.0.0",
"ngx-joyride": "^2.5.0", "ngx-joyride": "^2.5.0",
"ngx-toastr": "^19.0.0", "ngx-toastr": "^19.0.0",
"papaparse": "^5.4.1",
"rxjs": "~7.8.0", "rxjs": "~7.8.0",
"tslib": "^2.3.0", "tslib": "^2.3.0",
"zone.js": "^0.14.6" "zone.js": "^0.14.6"
@ -35,6 +36,7 @@
"@angular/localize": "^18.1.0", "@angular/localize": "^18.1.0",
"@ngx-env/builder": "^18.0.1", "@ngx-env/builder": "^18.0.1",
"@types/jasmine": "~5.1.0", "@types/jasmine": "~5.1.0",
"@types/papaparse": "^5.3.15",
"jasmine-core": "~5.1.0", "jasmine-core": "~5.1.0",
"karma": "~6.4.0", "karma": "~6.4.0",
"karma-chrome-launcher": "~3.2.0", "karma-chrome-launcher": "~3.2.0",
@ -5902,6 +5904,15 @@
"@types/node": "*" "@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": { "node_modules/@types/qs": {
"version": "6.9.15", "version": "6.9.15",
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.15.tgz", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.15.tgz",
@ -11955,6 +11966,11 @@
"node": "^16.14.0 || >=18.0.0" "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": { "node_modules/parent-module": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",

View File

@ -26,6 +26,7 @@
"jwt-decode": "^4.0.0", "jwt-decode": "^4.0.0",
"ngx-joyride": "^2.5.0", "ngx-joyride": "^2.5.0",
"ngx-toastr": "^19.0.0", "ngx-toastr": "^19.0.0",
"papaparse": "^5.4.1",
"rxjs": "~7.8.0", "rxjs": "~7.8.0",
"tslib": "^2.3.0", "tslib": "^2.3.0",
"zone.js": "^0.14.6" "zone.js": "^0.14.6"
@ -37,6 +38,7 @@
"@angular/localize": "^18.1.0", "@angular/localize": "^18.1.0",
"@ngx-env/builder": "^18.0.1", "@ngx-env/builder": "^18.0.1",
"@types/jasmine": "~5.1.0", "@types/jasmine": "~5.1.0",
"@types/papaparse": "^5.3.15",
"jasmine-core": "~5.1.0", "jasmine-core": "~5.1.0",
"karma": "~6.4.0", "karma": "~6.4.0",
"karma-chrome-launcher": "~3.2.0", "karma-chrome-launcher": "~3.2.0",

View File

@ -13,14 +13,11 @@ import { PXEimagesComponent } from './components/ogboot/pxe-images/pxe-images.co
import { PxeComponent } from './components/ogboot/pxe/pxe.component'; import { PxeComponent } from './components/ogboot/pxe/pxe.component';
import { PxeBootFilesComponent } from './components/ogboot/pxe-boot-files/pxe-boot-files.component'; import { PxeBootFilesComponent } from './components/ogboot/pxe-boot-files/pxe-boot-files.component';
import {OgbootStatusComponent} from "./components/ogboot/ogboot-status/ogboot-status.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 { CalendarComponent } from "./components/calendar/calendar.component";
import { CommandsComponent } from './components/commands/main-commands/commands.component'; import { CommandsComponent } from './components/commands/main-commands/commands.component';
import { CommandsGroupsComponent } from './components/commands/commands-groups/commands-groups.component'; import { CommandsGroupsComponent } from './components/commands/commands-groups/commands-groups.component';
import { CommandsTaskComponent } from './components/commands/commands-task/commands-task.component'; import { CommandsTaskComponent } from './components/commands/commands-task/commands-task.component';
import { TaskLogsComponent } from './components/commands/commands-task/task-logs/task-logs.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 { ClientMainViewComponent } from './components/groups/components/client-main-view/client-main-view.component';
import { ImagesComponent } from './components/images/images.component'; import { ImagesComponent } from './components/images/images.component';
import {SoftwareComponent} from "./components/software/software.component"; import {SoftwareComponent} from "./components/software/software.component";
@ -31,7 +28,7 @@ import {
} from "./components/groups/components/client-main-view/partition-assistant/partition-assistant.component"; } from "./components/groups/components/client-main-view/partition-assistant/partition-assistant.component";
import {RepositoriesComponent} from "./components/repositories/repositories.component"; import {RepositoriesComponent} from "./components/repositories/repositories.component";
import { import {
CreateImageComponent CreateClientImageComponent
} from "./components/groups/components/client-main-view/create-image/create-image.component"; } from "./components/groups/components/client-main-view/create-image/create-image.component";
import { import {
DeployImageComponent DeployImageComponent
@ -40,11 +37,15 @@ import {
MainRepositoryViewComponent MainRepositoryViewComponent
} from "./components/repositories/main-repository-view/main-repository-view.component"; } from "./components/repositories/main-repository-view/main-repository-view.component";
import {EnvVarsComponent} from "./components/admin/env-vars/env-vars.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 = [ const routes: Routes = [
{ path: '', redirectTo: 'auth/login', pathMatch: 'full' }, { path: '', redirectTo: 'auth/login', pathMatch: 'full' },
{ { path: '', component: MainLayoutComponent,
path: '',
component: MainLayoutComponent,
children: [ children: [
{ path: 'dashboard', component: DashboardComponent }, { path: 'dashboard', component: DashboardComponent },
{ path: 'admin', component: AdminComponent }, { path: 'admin', component: AdminComponent },
@ -56,7 +57,6 @@ const routes: Routes = [
{ path: 'pxe', component: PxeComponent }, { path: 'pxe', component: PxeComponent },
{ path: 'pxe-boot-file', component: PxeBootFilesComponent }, { path: 'pxe-boot-file', component: PxeBootFilesComponent },
{ path: 'ogboot-status', component: OgbootStatusComponent }, { path: 'ogboot-status', component: OgbootStatusComponent },
{ path: 'dhcp', component: OgdhcpComponent },
{ path: 'subnets', component: OgDhcpSubnetsComponent }, { path: 'subnets', component: OgDhcpSubnetsComponent },
{ path: 'ogdhcp-status', component: StatusComponent }, { path: 'ogdhcp-status', component: StatusComponent },
{ path: 'commands', component: CommandsComponent }, { path: 'commands', component: CommandsComponent },
@ -64,26 +64,27 @@ const routes: Routes = [
{ path: 'commands-task', component: CommandsTaskComponent }, { path: 'commands-task', component: CommandsTaskComponent },
{ path: 'commands-logs', component: TaskLogsComponent }, { path: 'commands-logs', component: TaskLogsComponent },
{ path: 'calendars', component: CalendarComponent }, { 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', component: ClientMainViewComponent },
{ path: 'clients/:id/partition-assistant', component: PartitionAssistantComponent }, { path: 'clients/:id/create-image', component: CreateClientImageComponent },
{ path: 'clients/:id/create-image', component: CreateImageComponent },
{ path: 'clients/:id/deploy-image', component: DeployImageComponent },
{ path: 'images', component: ImagesComponent },
{ path: 'repositories', component: RepositoriesComponent }, { path: 'repositories', component: RepositoriesComponent },
{ path: 'repository/:id', component: MainRepositoryViewComponent }, { path: 'repository/:id', component: MainRepositoryViewComponent },
{ path: 'software', component: SoftwareComponent }, { path: 'software', component: SoftwareComponent },
{ path: 'software-profiles', component: SoftwareProfileComponent }, { path: 'software-profiles', component: SoftwareProfileComponent },
{ path: 'operative-systems', component: OperativeSystemComponent }, { path: 'operative-systems', component: OperativeSystemComponent },
{ path: 'menus', component: MenusComponent },
], ],
}, },
{ {
path: 'auth', path: 'auth',
component: AuthLayoutComponent, component: AuthLayoutComponent,
children: [ children: [
{ path: 'login', component: LoginComponent }, { path: 'login', component: LoginComponent },
], ],
}, },
{ path: '**', component: PageNotFoundComponent }, { path: '**', component: PageNotFoundComponent },
]; ];
@NgModule({ @NgModule({

View File

@ -1,9 +1,11 @@
import { TestBed } from '@angular/core/testing'; import { TestBed } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing'; import { RouterTestingModule } from '@angular/router/testing';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule, TranslateService } from '@ngx-translate/core';
import { AppComponent } from './app.component'; import { AppComponent } from './app.component';
describe('AppComponent', () => { describe('AppComponent', () => {
let translateService: TranslateService;
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
imports: [ imports: [
@ -14,6 +16,8 @@ describe('AppComponent', () => {
AppComponent AppComponent
], ],
}).compileComponents(); }).compileComponents();
translateService = TestBed.inject(TranslateService);
}); });
it('should create the app', () => { it('should create the app', () => {
@ -21,4 +25,42 @@ describe('AppComponent', () => {
const app = fixture.componentInstance; const app = fixture.componentInstance;
expect(app).toBeTruthy(); 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 { 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 { BrowserModule } from '@angular/platform-browser';
import { AppRoutingModule } from './app-routing.module'; import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component'; import { AppComponent } from './app.component';
@ -25,6 +26,7 @@ import { MatListModule } from '@angular/material/list';
import { UsersComponent } from './components/admin/users/users/users.component'; import { UsersComponent } from './components/admin/users/users/users.component';
import { RolesComponent } from './components/admin/roles/roles/roles.component'; import { RolesComponent } from './components/admin/roles/roles/roles.component';
import { MatTableModule } from '@angular/material/table'; import { MatTableModule } from '@angular/material/table';
import { MatButtonToggleModule } from '@angular/material/button-toggle';
import { MatDialogModule } from '@angular/material/dialog'; import { MatDialogModule } from '@angular/material/dialog';
import { AddUserModalComponent } from './components/admin/users/users/add-user-modal/add-user-modal.component'; import { AddUserModalComponent } from './components/admin/users/users/add-user-modal/add-user-modal.component';
import { MatSelectModule } from '@angular/material/select'; 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 { ChangePasswordModalComponent } from './components/admin/users/users/change-password-modal/change-password-modal.component';
import { GroupsComponent } from './components/groups/groups.component'; import { GroupsComponent } from './components/groups/groups.component';
import { MatDividerModule } from '@angular/material/divider'; 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 { MatStepperModule } from '@angular/material/stepper';
import { MatSlideToggleModule } from '@angular/material/slide-toggle'; 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 { 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 { ClassroomViewComponent } from './components/groups/shared/classroom-view/classroom-view.component';
import { MatProgressSpinner } from "@angular/material/progress-spinner"; import { MatProgressSpinner } from "@angular/material/progress-spinner";
import { MatProgressBarModule } from '@angular/material/progress-bar';
import { MatMenu, MatMenuItem, MatMenuTrigger } from "@angular/material/menu"; 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 { MatChip, MatChipListbox, MatChipOption, MatChipSet, MatChipsModule } from "@angular/material/chips";
import { ClientViewComponent } from './components/groups/shared/client-view/client-view.component'; import { ClientViewComponent } from './components/groups/shared/client-view/client-view.component';
import { MatTab, MatTabGroup } from "@angular/material/tabs"; import { MatTab, MatTabGroup } from "@angular/material/tabs";
@ -52,7 +51,6 @@ import { DragDropModule } from '@angular/cdk/drag-drop';
import { ToastrModule } from 'ngx-toastr'; import { ToastrModule } from 'ngx-toastr';
import { ShowOrganizationalUnitComponent } from './components/groups/shared/organizational-units/show-organizational-unit/show-organizational-unit.component'; import { ShowOrganizationalUnitComponent } from './components/groups/shared/organizational-units/show-organizational-unit/show-organizational-unit.component';
import { MatGridList, MatGridTile } from "@angular/material/grid-list"; import { MatGridList, MatGridTile } from "@angular/material/grid-list";
import { TreeViewComponent } from './components/groups/shared/tree-view/tree-view.component';
import { import {
MatNestedTreeNode, MatNestedTreeNode,
MatTree, 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 { ClassroomViewDialogComponent } from './components/groups/shared/classroom-view/classroom-view-modal';
import { MatPaginator } from "@angular/material/paginator"; import { MatPaginator } from "@angular/material/paginator";
import { SaveFiltersDialogComponent } from './components/groups/shared/save-filters-dialog/save-filters-dialog.component'; 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 { PXEimagesComponent } from './components/ogboot/pxe-images/pxe-images.component';
import { CreatePXEImageComponent } from './components/ogboot/pxe-images/create-image/create-image/create-image.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'; 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 { PxeBootFilesComponent } from './components/ogboot/pxe-boot-files/pxe-boot-files.component';
import { MatExpansionPanel, MatExpansionPanelDescription, MatExpansionPanelTitle } from "@angular/material/expansion"; import { MatExpansionPanel, MatExpansionPanelDescription, MatExpansionPanelTitle } from "@angular/material/expansion";
import { OgbootStatusComponent } from './components/ogboot/ogboot-status/ogboot-status.component'; 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 { 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 { CommandsComponent } from './components/commands/main-commands/commands.component';
import { CommandDetailComponent } from './components/commands/main-commands/detail-command/command-detail.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'; 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 { MatNativeDateModule } from '@angular/material/core';
import { CalendarComponent } from './components/calendar/calendar.component'; import { CalendarComponent } from './components/calendar/calendar.component';
import { CreateCalendarComponent } from './components/calendar/create-calendar/create-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 { CreateCalendarRuleComponent } from './components/calendar/create-calendar-rule/create-calendar-rule.component';
import { CommandsGroupsComponent } from './components/commands/commands-groups/commands-groups.component'; import { CommandsGroupsComponent } from './components/commands/commands-groups/commands-groups.component';
import { CommandsTaskComponent } from './components/commands/commands-task/commands-task.component'; import { CommandsTaskComponent } from './components/commands/commands-task/commands-task.component';
@ -95,16 +87,12 @@ import { CreateCommandGroupComponent } from './components/commands/commands-grou
import { DetailCommandGroupComponent } from './components/commands/commands-groups/detail-command-group/detail-command-group.component'; 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 { CreateTaskComponent } from './components/commands/commands-task/create-task/create-task.component';
import { DetailTaskComponent } from './components/commands/commands-task/detail-task/detail-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 { 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 { MatSliderModule } from '@angular/material/slider';
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 { ClientMainViewComponent } from './components/groups/components/client-main-view/client-main-view.component'; import { ClientMainViewComponent } from './components/groups/components/client-main-view/client-main-view.component';
import { ImagesComponent } from './components/images/images.component'; import { ImagesComponent } from './components/images/images.component';
import { CreateImageComponent } from './components/images/create-image/create-image.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 { PartitionAssistantComponent } from './components/groups/components/client-main-view/partition-assistant/partition-assistant.component';
import { SoftwareComponent } from './components/software/software.component'; import { SoftwareComponent } from './components/software/software.component';
import { CreateSoftwareComponent } from './components/software/create-software/create-software.component'; import { CreateSoftwareComponent } from './components/software/create-software/create-software.component';
@ -113,10 +101,8 @@ import { CreateSoftwareProfileComponent } from './components/software-profile/cr
import { OperativeSystemComponent } from './components/operative-system/operative-system.component'; import { OperativeSystemComponent } from './components/operative-system/operative-system.component';
import { CreateOperativeSystemComponent } from './components/operative-system/create-operative-system/create-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 { 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 { RepositoriesComponent } from './components/repositories/repositories.component';
import { CreateRepositoryComponent } from './components/repositories/create-repository/create-repository.component'; import { ManageRepositoryComponent } from './components/repositories/manage-repository/manage-repository.component';
import { ExecuteCommandComponent } from './components/commands/main-commands/execute-command/execute-command.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 { 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 { MainRepositoryViewComponent } from './components/repositories/main-repository-view/main-repository-view.component';
@ -125,11 +111,49 @@ import { JoyrideModule } from 'ngx-joyride';
import { TranslateModule, TranslateLoader } from '@ngx-translate/core'; import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
import { TranslateHttpLoader } from '@ngx-translate/http-loader'; import { TranslateHttpLoader } from '@ngx-translate/http-loader';
import { EnvVarsComponent } from './components/admin/env-vars/env-vars.component'; 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) { export function HttpLoaderFactory(http: HttpClient) {
return new TranslateHttpLoader(http, './locale/', '.json'); return new TranslateHttpLoader(http, './locale/', '.json');
} }
export function initializeApp(configService: ConfigService) {
return () => configService.loadConfig();
}
registerLocaleData(localeEs, 'es-ES');
@NgModule({ @NgModule({
declarations: [ declarations: [
AppComponent, AppComponent,
@ -146,19 +170,14 @@ export function HttpLoaderFactory(http: HttpClient) {
AddRoleModalComponent, AddRoleModalComponent,
ChangePasswordModalComponent, ChangePasswordModalComponent,
GroupsComponent, GroupsComponent,
CreateOrganizationalUnitComponent, ManageClientComponent,
CreateClientComponent,
DeleteModalComponent, DeleteModalComponent,
EditOrganizationalUnitComponent,
EditClientComponent,
ClassroomViewComponent, ClassroomViewComponent,
ClientViewComponent, ClientViewComponent,
ShowOrganizationalUnitComponent, ShowOrganizationalUnitComponent,
TreeViewComponent,
LegendComponent, LegendComponent,
ClassroomViewDialogComponent, ClassroomViewDialogComponent,
SaveFiltersDialogComponent, SaveFiltersDialogComponent,
AcctionsModalComponent,
PXEimagesComponent, PXEimagesComponent,
CreatePXEImageComponent, CreatePXEImageComponent,
InfoImageComponent, InfoImageComponent,
@ -166,8 +185,6 @@ export function HttpLoaderFactory(http: HttpClient) {
CreatePxeTemplateComponent, CreatePxeTemplateComponent,
PxeBootFilesComponent, PxeBootFilesComponent,
OgbootStatusComponent, OgbootStatusComponent,
CreatePxeBootFileComponent,
OgdhcpComponent,
OgDhcpSubnetsComponent, OgDhcpSubnetsComponent,
CreateSubnetComponent, CreateSubnetComponent,
AddClientsToSubnetComponent, AddClientsToSubnetComponent,
@ -176,6 +193,7 @@ export function HttpLoaderFactory(http: HttpClient) {
CreateCommandComponent, CreateCommandComponent,
CalendarComponent, CalendarComponent,
CreateCalendarComponent, CreateCalendarComponent,
CreateClientImageComponent,
CreateCalendarRuleComponent, CreateCalendarRuleComponent,
CommandsGroupsComponent, CommandsGroupsComponent,
CommandsTaskComponent, CommandsTaskComponent,
@ -183,10 +201,7 @@ export function HttpLoaderFactory(http: HttpClient) {
DetailCommandGroupComponent, DetailCommandGroupComponent,
CreateTaskComponent, CreateTaskComponent,
DetailTaskComponent, DetailTaskComponent,
ClientTabViewComponent,
AdvancedSearchComponent,
TaskLogsComponent, TaskLogsComponent,
OrganizationalUnitTabViewComponent,
ServerInfoDialogComponent, ServerInfoDialogComponent,
StatusComponent, StatusComponent,
ClientMainViewComponent, ClientMainViewComponent,
@ -200,16 +215,35 @@ export function HttpLoaderFactory(http: HttpClient) {
OperativeSystemComponent, OperativeSystemComponent,
CreateOperativeSystemComponent, CreateOperativeSystemComponent,
ShowTemplateContentComponent, ShowTemplateContentComponent,
AddClientsToPxeComponent,
ClientsComponent,
RepositoriesComponent, RepositoriesComponent,
CreateRepositoryComponent, ManageRepositoryComponent,
ExecuteCommandComponent, ExecuteCommandComponent,
ExecuteCommandOuComponent, ExecuteCommandOuComponent,
DeployImageComponent, DeployImageComponent,
MainRepositoryViewComponent, MainRepositoryViewComponent,
ExecuteCommandOuComponent, ExecuteCommandOuComponent,
EnvVarsComponent, EnvVarsComponent,
MenusComponent,
CreateMenuComponent,
CreateMultipleClientComponent,
ExportImageComponent,
ImportImageComponent,
LoadingComponent,
InputDialogComponent,
ManageOrganizationalUnitComponent,
BackupImageComponent,
ShowClientsComponent,
OperationResultDialogComponent,
ConvertImageComponent,
GlobalStatusComponent,
ShowMonoliticImagesComponent,
StatusTabComponent,
ConvertImageToVirtualComponent,
RunScriptAssistantComponent,
SaveScriptComponent,
EditImageComponent,
ShowGitImagesComponent,
RenameImageComponent
], ],
bootstrap: [AppComponent], bootstrap: [AppComponent],
imports: [BrowserModule, imports: [BrowserModule,
@ -218,6 +252,7 @@ export function HttpLoaderFactory(http: HttpClient) {
ReactiveFormsModule, ReactiveFormsModule,
MatToolbarModule, MatToolbarModule,
MatIconModule, MatIconModule,
MatButtonToggleModule,
MatButtonModule, MatButtonModule,
MatSidenavModule, MatSidenavModule,
NoopAnimationsModule, NoopAnimationsModule,
@ -230,6 +265,7 @@ export function HttpLoaderFactory(http: HttpClient) {
MatDialogModule, MatDialogModule,
MatSelectModule, MatSelectModule,
MatDividerModule, MatDividerModule,
MatProgressBarModule,
MatStepperModule, MatStepperModule,
DragDropModule, DragDropModule,
MatSlideToggleModule, MatMenu, MatMenuTrigger, MatMenuItem, MatAutocomplete, MatChipListbox, MatChipOption, MatChipSet, MatChipsModule, MatChip, MatProgressSpinner, MatTabGroup, MatTab, MatTooltip, MatSlideToggleModule, MatMenu, MatMenuTrigger, MatMenuItem, MatAutocomplete, MatChipListbox, MatChipOption, MatChipSet, MatChipsModule, MatChip, MatProgressSpinner, MatTabGroup, MatTab, MatTooltip,
@ -238,6 +274,7 @@ export function HttpLoaderFactory(http: HttpClient) {
MatDatepickerModule, MatDatepickerModule,
MatNativeDateModule, MatNativeDateModule,
MatSliderModule, MatSliderModule,
MatSortModule,
TranslateModule.forRoot({ TranslateModule.forRoot({
loader: { loader: {
provide: TranslateLoader, provide: TranslateLoader,
@ -266,8 +303,16 @@ export function HttpLoaderFactory(http: HttpClient) {
useClass: CustomInterceptor, useClass: CustomInterceptor,
multi: true multi: true
}, },
{ provide: LOCALE_ID, useValue: 'es-ES' },
provideAnimationsAsync(), provideAnimationsAsync(),
provideHttpClient(withInterceptorsFromDi()) provideHttpClient(withInterceptorsFromDi()),
ConfigService,
{
provide: APP_INITIALIZER,
useFactory: initializeApp,
deps: [ConfigService],
multi: true
}
], ],
}) })
export class AppModule { } export class AppModule { }

View File

@ -14,16 +14,6 @@
margin: 0 10px; 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 */ /* Estilos del texto debajo de los botones */
span{ span{
margin: 0; margin: 0;

View File

@ -1,9 +1,9 @@
<div class="container"> <div class="container">
<button mat-fab color="primary" class="fab-button" routerLink="/users"> <button class="action-button" routerLink="/users">
<mat-icon>group</mat-icon> <mat-icon>group</mat-icon>
<span>{{ 'labelUsers' | translate }}</span> <span>{{ 'labelUsers' | translate }}</span>
</button> </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> <mat-icon>admin_panel_settings</mat-icon>
<span>{{ 'labelRoles' | translate }}</span> <span>{{ 'labelRoles' | translate }}</span>
</button> </button>

View File

@ -4,10 +4,12 @@ import { AdminComponent } from './admin.component';
import { MatButtonModule } from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon'; import { MatIconModule } from '@angular/material/icon';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { Router } from '@angular/router';
describe('AdminComponent', () => { describe('AdminComponent', () => {
let component: AdminComponent; let component: AdminComponent;
let fixture: ComponentFixture<AdminComponent>; let fixture: ComponentFixture<AdminComponent>;
let router: Router;
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
@ -19,6 +21,8 @@ describe('AdminComponent', () => {
TranslateModule.forRoot() TranslateModule.forRoot()
] ]
}).compileComponents(); }).compileComponents();
router = TestBed.inject(Router);
}); });
beforeEach(() => { beforeEach(() => {
@ -30,4 +34,15 @@ describe('AdminComponent', () => {
it('debería crear el componente', () => { it('debería crear el componente', () => {
expect(component).toBeTruthy(); expect(component).toBeTruthy();
}); });
it('debería renderizar dos botones', () => {
const buttons = fixture.nativeElement.querySelectorAll('button');
expect(buttons.length).toBe(2);
});
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

@ -1,9 +1,5 @@
.env-settings { .env-settings {
padding: 16px; padding: 0rem 1rem 0rem 1rem;
h1 {
margin-bottom: 16px;
}
.mat-table { .mat-table {
margin-bottom: 16px; margin-bottom: 16px;
@ -19,10 +15,6 @@
gap: 16px; gap: 16px;
justify-content: flex-end; justify-content: flex-end;
margin-top: 16px; margin-top: 16px;
button {
min-width: 120px;
}
} }
} }

View File

@ -1,5 +1,9 @@
<div class="env-settings"> <div class="env-settings">
<h1>Editar Variables de Entorno</h1> <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"> <mat-table [dataSource]="envVars" class="mat-elevation-z8">
<!-- Nombre de la variable --> <!-- Nombre de la variable -->
@ -22,8 +26,8 @@
<mat-row *matRowDef="let row; columns: displayedColumns;"></mat-row> <mat-row *matRowDef="let row; columns: displayedColumns;"></mat-row>
</mat-table> </mat-table>
<div class="actions" > <div class="actions">
<button mat-raised-button color="primary" (click)="saveEnvVars()">Guardar Cambios</button> <button class="action-button" (click)="loadEnvVars()">Recargar</button>
<button mat-raised-button color="accent" (click)="loadEnvVars()">Recargar</button> <button class="submit-button" (click)="saveEnvVars()">Guardar Cambios</button>
</div> </div>
</div> </div>

View File

@ -13,12 +13,17 @@ import { TranslateModule } from '@ngx-translate/core';
import { ToastrModule, ToastrService } from 'ngx-toastr'; import { ToastrModule, ToastrService } from 'ngx-toastr';
import { DataService } from '../users/users/data.service'; import { DataService } from '../users/users/data.service';
import { MatTableModule } from '@angular/material/table'; import { MatTableModule } from '@angular/material/table';
import { ConfigService } from '@services/config.service';
describe('EnvVarsComponent', () => { describe('EnvVarsComponent', () => {
let component: EnvVarsComponent; let component: EnvVarsComponent;
let fixture: ComponentFixture<EnvVarsComponent>; let fixture: ComponentFixture<EnvVarsComponent>;
beforeEach(async () => { beforeEach(async () => {
const mockConfigService = {
apiUrl: 'http://mock-api-url'
};
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [EnvVarsComponent], declarations: [EnvVarsComponent],
imports: [ imports: [
@ -47,6 +52,10 @@ describe('EnvVarsComponent', () => {
{ {
provide: MAT_DIALOG_DATA, provide: MAT_DIALOG_DATA,
useValue: {} useValue: {}
},
{
provide: ConfigService,
useValue: mockConfigService
} }
] ]
}).compileComponents(); }).compileComponents();

View File

@ -1,6 +1,7 @@
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import {HttpClient} from "@angular/common/http"; import { HttpClient } from "@angular/common/http";
import {ToastrService} from "ngx-toastr"; import { ToastrService } from "ngx-toastr";
import { ConfigService } from '@services/config.service';
@Component({ @Component({
selector: 'app-env-vars', selector: 'app-env-vars',
@ -8,16 +9,17 @@ import {ToastrService} from "ngx-toastr";
styleUrl: './env-vars.component.css' styleUrl: './env-vars.component.css'
}) })
export class EnvVarsComponent { export class EnvVarsComponent {
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL;
envVars: { name: string; value: string }[] = []; envVars: { name: string; value: string }[] = [];
displayedColumns: string[] = ['name', 'value']; displayedColumns: string[] = ['name', 'value'];
private apiUrl: string;
private apiUrl = `${this.baseUrl}/env-vars`;
constructor( constructor(
private http: HttpClient, private http: HttpClient,
private toastService: ToastrService, private toastService: ToastrService,
) {} private configService: ConfigService
) {
this.apiUrl = `${this.configService.apiUrl}/env-vars`;
}
ngOnInit(): void { ngOnInit(): void {
this.loadEnvVars(); this.loadEnvVars();

View File

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

View File

@ -16,7 +16,7 @@
</section> </section>
</form> </form>
</mat-dialog-content> </mat-dialog-content>
<mat-dialog-actions align="end"> <mat-dialog-actions class="action-container">
<button mat-button (click)="onNoClick()">{{ 'buttonCancel' | translate }}</button> <button class="ordinary-button" (click)="onNoClick()">{{ 'buttonCancel' | translate }}</button>
<button mat-button (click)="onSubmit()">{{ 'buttonAdd' | translate }}</button> <button class="submit-button" (click)="onSubmit()">{{ 'buttonAdd' | translate }}</button>
</mat-dialog-actions> </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 {FormBuilder, FormGroup, Validators} from '@angular/forms';
import {DataService} from "../data.service"; import {DataService} from "../data.service";
import {ToastrService} from "ngx-toastr"; import {ToastrService} from "ngx-toastr";
import { ConfigService } from '@services/config.service';
@Component({ @Component({
selector: 'app-add-role-modal', selector: 'app-add-role-modal',
@ -11,9 +12,9 @@ import {ToastrService} from "ngx-toastr";
styleUrls: ['./add-role-modal.component.css'] styleUrls: ['./add-role-modal.component.css']
}) })
export class AddRoleModalComponent { export class AddRoleModalComponent {
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL;
roleForm: FormGroup<any>; roleForm: FormGroup<any>;
roleId: string | null = null; roleId: string | null = null;
baseUrl: string;
constructor( constructor(
public dialogRef: MatDialogRef<AddRoleModalComponent>, public dialogRef: MatDialogRef<AddRoleModalComponent>,
@ -21,8 +22,10 @@ export class AddRoleModalComponent {
private http: HttpClient, private http: HttpClient,
private fb: FormBuilder, private fb: FormBuilder,
private dataService: DataService, private dataService: DataService,
private toastService: ToastrService private toastService: ToastrService,
private configService: ConfigService
) { ) {
this.baseUrl = this.configService.apiUrl;
this.roleForm = this.fb.group({ this.roleForm = this.fb.group({
name: ['', Validators.required], name: ['', Validators.required],
superAdmin: [false], superAdmin: [false],

View File

@ -2,15 +2,19 @@ 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 { Observable, throwError } from 'rxjs';
import { catchError, map } from 'rxjs/operators'; import { catchError, map } from 'rxjs/operators';
import { ConfigService } from '@services/config.service';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
export class DataService { export class DataService {
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL; baseUrl: string;
private apiUrl = `${this.baseUrl}/user-groups?page=1&itemsPerPage=1000`; 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 }> { getUserGroups(filters: { [key: string]: string }): Observable<{ totalItems: any; data: any }> {
const params = new HttpParams({ fromObject: filters }); 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 { table {
width: 100%; width: 100%;
margin-top: 50px;
} }
.search-container { .search-container {
@ -8,14 +21,10 @@ table {
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
width: 100%; width: 100%;
padding: 0 5px; margin: 1.5rem 0rem 1.5rem 0rem;
box-sizing: border-box; box-sizing: border-box;
} }
.divider {
margin: 20px 0;
}
.search-string { .search-string {
flex: 2; flex: 2;
padding: 5px; padding: 5px;
@ -26,15 +35,8 @@ table {
padding: 5px; padding: 5px;
} }
.header-container {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px;
}
.mat-elevation-z8 { .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 { .paginator-container {
@ -42,4 +44,3 @@ table {
justify-content: end; justify-content: end;
margin-bottom: 30px; margin-bottom: 30px;
} }

View File

@ -1,24 +1,25 @@
<div class="header-container"> <div class="header-container">
<h2 class="title">{{ 'adminRolesTitle' | translate }}</h2> <div class="header-container-title">
<h2>{{ 'adminRolesTitle' | translate }}</h2>
</div>
<div class="images-button-row"> <div class="images-button-row">
<button mat-flat-button color="primary" (click)="addUser()"> <button class="action-button" (click)="addUser()">
{{ 'addRole' | translate }} {{ 'addRole' | translate }}
</button> </button>
</div> </div>
</div> </div>
<mat-divider class="divider"></mat-divider>
<div class="search-container"> <div class="search-container">
<mat-form-field appearance="fill" class="search-string"> <mat-form-field appearance="fill" class="search-string">
<mat-label>{{ 'searchRoleLabel' | translate }}</mat-label> <mat-label>{{ 'searchRoleLabel' | translate }}</mat-label>
<input matInput placeholder="{{ 'searchPlaceholder' | translate }}" [(ngModel)]="filters['name']" (keyup.enter)="search()"> <input matInput placeholder="{{ 'searchPlaceholder' | translate }}" [(ngModel)]="filters['name']"
(keyup.enter)="search()">
<mat-icon matSuffix>search</mat-icon> <mat-icon matSuffix>search</mat-icon>
<mat-hint>{{ 'searchHint' | translate }}</mat-hint> <mat-hint>{{ 'searchHint' | translate }}</mat-hint>
</mat-form-field> </mat-form-field>
</div> </div>
<div *ngIf="loading" class="loading-container"> <app-loading [isLoading]="loading"></app-loading>
<mat-spinner></mat-spinner>
</div>
<div *ngIf="!loading"> <div *ngIf="!loading">
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8"> <table mat-table [dataSource]="dataSource" class="mat-elevation-z8">
@ -33,7 +34,8 @@
<button mat-icon-button color="primary" (click)="editRole(role)"> <button mat-icon-button color="primary" (click)="editRole(role)">
<mat-icon>edit</mat-icon> <mat-icon>edit</mat-icon>
</button> </button>
<button mat-icon-button color="warn" (click)="deleteRole(role)" [disabled]="role.permissions.includes('ROLE_SUPER_ADMIN')"> <button mat-icon-button color="warn" (click)="deleteRole(role)"
[disabled]="role.permissions.includes('ROLE_SUPER_ADMIN')">
<mat-icon>delete</mat-icon> <mat-icon>delete</mat-icon>
</button> </button>
</td> </td>
@ -44,10 +46,7 @@
</div> </div>
<div class="paginator-container"> <div class="paginator-container">
<mat-paginator [length]="length" <mat-paginator [length]="length" [pageSize]="itemsPerPage" [pageIndex]="page" [pageSizeOptions]="pageSizeOptions"
[pageSize]="itemsPerPage"
[pageIndex]="page"
[pageSizeOptions]="pageSizeOptions"
(page)="onPageChange($event)"> (page)="onPageChange($event)">
</mat-paginator> </mat-paginator>
</div> </div>

View File

@ -4,7 +4,7 @@ import { MatDialog } from '@angular/material/dialog';
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import { ToastrService } from 'ngx-toastr'; import { ToastrService } from 'ngx-toastr';
import { DataService } from './data.service'; import { DataService } from './data.service';
import { of } from 'rxjs'; import { ConfigService } from '@services/config.service';
import { MatDivider } from '@angular/material/divider'; import { MatDivider } from '@angular/material/divider';
import { MatFormField } from '@angular/material/form-field'; import { MatFormField } from '@angular/material/form-field';
import { MatLabel } from '@angular/material/form-field'; import { MatLabel } from '@angular/material/form-field';
@ -12,6 +12,7 @@ import { MatIcon } from '@angular/material/icon';
import { MatHint } from '@angular/material/form-field'; import { MatHint } from '@angular/material/form-field';
import { MatPaginator } from '@angular/material/paginator'; import { MatPaginator } from '@angular/material/paginator';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { LoadingComponent } from '../../../../shared/loading/loading.component';
describe('RolesComponent', () => { describe('RolesComponent', () => {
let component: RolesComponent; let component: RolesComponent;
let fixture: ComponentFixture<RolesComponent>; let fixture: ComponentFixture<RolesComponent>;
@ -19,22 +20,25 @@ describe('RolesComponent', () => {
let mockHttpClient: jasmine.SpyObj<HttpClient>; let mockHttpClient: jasmine.SpyObj<HttpClient>;
let mockToastrService: jasmine.SpyObj<ToastrService>; let mockToastrService: jasmine.SpyObj<ToastrService>;
let mockDataService: jasmine.SpyObj<DataService>; let mockDataService: jasmine.SpyObj<DataService>;
let mockConfigService: jasmine.SpyObj<ConfigService>;
beforeEach(async () => { beforeEach(async () => {
const matDialogSpy = jasmine.createSpyObj('MatDialog', ['open']); const matDialogSpy = jasmine.createSpyObj('MatDialog', ['open']);
const httpClientSpy = jasmine.createSpyObj('HttpClient', ['get', 'post', 'put', 'delete']); const httpClientSpy = jasmine.createSpyObj('HttpClient', ['get', 'post', 'put', 'delete']);
const toastrServiceSpy = jasmine.createSpyObj('ToastrService', ['success', 'error']); const toastrServiceSpy = jasmine.createSpyObj('ToastrService', ['success', 'error']);
const dataServiceSpy = jasmine.createSpyObj('DataService', ['getRoles']); const dataServiceSpy = jasmine.createSpyObj('DataService', ['getRoles']);
const configServiceSpy = jasmine.createSpyObj('ConfigService', [], { apiUrl: 'http://mock-api-url' });
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [RolesComponent], declarations: [RolesComponent, LoadingComponent],
imports: [MatDivider, MatFormField, MatLabel, MatIcon, MatHint, MatPaginator, imports: [MatDivider, MatFormField, MatLabel, MatIcon, MatHint, MatPaginator,
TranslateModule.forRoot()], TranslateModule.forRoot()],
providers: [ providers: [
{ provide: MatDialog, useValue: matDialogSpy }, { provide: MatDialog, useValue: matDialogSpy },
{ provide: HttpClient, useValue: httpClientSpy }, { provide: HttpClient, useValue: httpClientSpy },
{ provide: ToastrService, useValue: toastrServiceSpy }, { provide: ToastrService, useValue: toastrServiceSpy },
{ provide: DataService, useValue: dataServiceSpy } { provide: DataService, useValue: dataServiceSpy },
{ provide: ConfigService, useValue: configServiceSpy }
] ]
}).compileComponents(); }).compileComponents();
}); });
@ -46,9 +50,24 @@ describe('RolesComponent', () => {
mockHttpClient = TestBed.inject(HttpClient) as jasmine.SpyObj<HttpClient>; mockHttpClient = TestBed.inject(HttpClient) as jasmine.SpyObj<HttpClient>;
mockToastrService = TestBed.inject(ToastrService) as jasmine.SpyObj<ToastrService>; mockToastrService = TestBed.inject(ToastrService) as jasmine.SpyObj<ToastrService>;
mockDataService = TestBed.inject(DataService) as jasmine.SpyObj<DataService>; mockDataService = TestBed.inject(DataService) as jasmine.SpyObj<DataService>;
mockConfigService = TestBed.inject(ConfigService) as jasmine.SpyObj<ConfigService>;
}); });
it('should create', () => { it('should create', () => {
expect(component).toBeTruthy(); 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

@ -7,6 +7,7 @@ import { DataService } from "./data.service";
import { PageEvent } from "@angular/material/paginator"; import { PageEvent } from "@angular/material/paginator";
import { DeleteModalComponent } from '../../../../shared/delete_modal/delete-modal/delete-modal.component'; import { DeleteModalComponent } from '../../../../shared/delete_modal/delete-modal/delete-modal.component';
import { AddRoleModalComponent } from './add-role-modal/add-role-modal.component'; import { AddRoleModalComponent } from './add-role-modal/add-role-modal.component';
import { ConfigService } from '@services/config.service';
@Component({ @Component({
selector: 'app-roles', selector: 'app-roles',
@ -14,7 +15,7 @@ import { AddRoleModalComponent } from './add-role-modal/add-role-modal.component
styleUrls: ['./roles.component.css'] styleUrls: ['./roles.component.css']
}) })
export class RolesComponent implements OnInit { export class RolesComponent implements OnInit {
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL; baseUrl: string = this.configService.apiUrl;
dataSource = new MatTableDataSource<any>(); dataSource = new MatTableDataSource<any>();
filters: { [key: string]: string } = {}; filters: { [key: string]: string } = {};
loading: boolean = false; loading: boolean = false;
@ -48,7 +49,8 @@ export class RolesComponent implements OnInit {
public dialog: MatDialog, public dialog: MatDialog,
private http: HttpClient, private http: HttpClient,
private dataService: DataService, private dataService: DataService,
private toastService: ToastrService private toastService: ToastrService,
private configService: ConfigService
) {} ) {}
ngOnInit() { ngOnInit() {

View File

@ -1,30 +1,12 @@
.full-width { .user-form {
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 {
display: flex; display: flex;
gap: 15px; /* Espacio entre los campos */ flex-direction: column;
margin-top: 2rem;
} }
.time-field { .action-container {
flex: 1; display: flex;
justify-content: flex-end;
gap: 1em;
padding: 1.5em;
} }

View File

@ -1,4 +1,6 @@
<h1 mat-dialog-title>{{ 'dialogTitleAddUser' | translate }}</h1> <app-loading [isLoading]="loading"></app-loading>
<h1 mat-dialog-title>{{ isEditMode ? ('dialogTitleEditUser' | translate) : ('dialogTitleAddUser' | translate) }}</h1>
<mat-dialog-content class="form-container"> <mat-dialog-content class="form-container">
<form [formGroup]="userForm" class="user-form"> <form [formGroup]="userForm" class="user-form">
<mat-form-field appearance="fill" class="full-width"> <mat-form-field appearance="fill" class="full-width">
@ -27,9 +29,18 @@
</mat-option> </mat-option>
</mat-select> </mat-select>
</mat-form-field> </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> </form>
</mat-dialog-content> </mat-dialog-content>
<mat-dialog-actions align="end"> <mat-dialog-actions class="action-container">
<button mat-button (click)="onNoClick()">{{ 'buttonCancel' | translate }}</button> <button class="ordinary-button" (click)="onNoClick()">{{ 'buttonCancel' | translate }}</button>
<button mat-button (click)="onSubmit()">{{ 'buttonAdd' | translate }}</button> <button class="submit-button" (click)="onSubmit()" [disabled]="userForm.invalid">{{ isEditMode ? ('buttonEdit' | translate) : ('buttonAdd' | translate) }}</button>
</mat-dialog-actions> </mat-dialog-actions>

View File

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

View File

@ -1,4 +1,4 @@
<h1 mat-dialog-title>{{ 'dialogTitleEditUser' | translate }}</h1> <h1 mat-dialog-title>{{ 'dialogTitleChangePassword' | translate }}</h1>
<mat-dialog-content class="form-container"> <mat-dialog-content class="form-container">
<form [formGroup]="userForm" class="user-form"> <form [formGroup]="userForm" class="user-form">
<mat-form-field class="form-field"> <mat-form-field class="form-field">
@ -23,7 +23,7 @@
</div> </div>
</form> </form>
</mat-dialog-content> </mat-dialog-content>
<mat-dialog-actions align="end"> <mat-dialog-actions class="action-container">
<button mat-button (click)="onNoClick()">{{ 'buttonCancel' | translate }}</button> <button class="ordinary-button" (click)="onNoClick()">{{ 'buttonCancel' | translate }}</button>
<button mat-button (click)="onSubmit()" [disabled]="loading">{{ 'buttonEdit' | translate }}</button> <button class="submit-button" (click)="onSubmit()" [disabled]="loading">{{ 'buttonEdit' | translate }}</button>
</mat-dialog-actions> </mat-dialog-actions>

View File

@ -3,15 +3,19 @@ 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 { Observable, throwError } from 'rxjs';
import { catchError, map } from 'rxjs/operators'; import { catchError, map } from 'rxjs/operators';
import { ConfigService } from '@services/config.service';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
export class DataService { export class DataService {
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL; baseUrl: string;
private apiUrl = `${this.baseUrl}/users?page=1&itemsPerPage=1000`; 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 }> { getUsers(filters: { [key: string]: string }): Observable<{ totalItems: any; data: any }> {
const params = new HttpParams({ fromObject: filters }); 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 { table {
width: 100%; width: 100%;
margin-top: 50px;
} }
.search-container { .search-container {
@ -8,14 +21,10 @@ table {
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
width: 100%; width: 100%;
padding: 0 5px; margin: 1.5rem 0rem 1.5rem 0rem;
box-sizing: border-box; box-sizing: border-box;
} }
.divider {
margin: 20px 0;
}
.search-string { .search-string {
flex: 2; flex: 2;
padding: 5px; padding: 5px;
@ -26,13 +35,6 @@ table {
padding: 5px; padding: 5px;
} }
.header-container {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px;
}
.mat-elevation-z8 { .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);
} }

View File

@ -1,30 +1,40 @@
<div class="header-container"> <div class="header-container">
<h2 class="title">{{ 'adminImagesTitle' | translate }}</h2> <div class="header-container-title">
<h2>{{ 'adminUsersTitle' | translate }}</h2>
</div>
<div class="images-button-row"> <div class="images-button-row">
<button mat-flat-button color="primary" (click)="addUser()"> <button class="action-button" (click)="addUser()">
{{ 'addUser' | translate }} {{ 'addUser' | translate }}
</button> </button>
</div> </div>
</div> </div>
<mat-divider class="divider"></mat-divider>
<div class="search-container"> <div class="search-container">
<mat-form-field appearance="fill" class="search-string"> <mat-form-field appearance="fill" class="search-string">
<mat-label>{{ 'searchLabel' | translate }}</mat-label> <mat-label>{{ 'searchLabel' | translate }}</mat-label>
<input matInput placeholder="{{ 'searchPlaceholder' | translate }}" [(ngModel)]="filters['name']" (keyup.enter)="search()"> <input matInput placeholder="{{ 'searchPlaceholder' | translate }}" [(ngModel)]="filters['name']"
(keyup.enter)="search()">
<mat-icon matSuffix>search</mat-icon> <mat-icon matSuffix>search</mat-icon>
<mat-hint>{{ 'searchHint' | translate }}</mat-hint> <mat-hint>{{ 'searchHint' | translate }}</mat-hint>
</mat-form-field> </mat-form-field>
</div> </div>
<div *ngIf="loading" class="loading-container"> <app-loading [isLoading]="loading"></app-loading>
<mat-spinner></mat-spinner>
</div>
<div *ngIf="!loading"> <div *ngIf="!loading">
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8"> <table mat-table [dataSource]="dataSource" class="mat-elevation-z8">
<ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef"> <ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef">
<th mat-header-cell *matHeaderCellDef> {{ column.header }} </th> <th mat-header-cell *matHeaderCellDef> {{ column.header }} </th>
<td mat-cell *matCellDef="let user"> {{ column.cell(user) }} </td> <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>
<ng-container matColumnDef="actions"> <ng-container matColumnDef="actions">
@ -44,10 +54,7 @@
</div> </div>
<div class="paginator-container"> <div class="paginator-container">
<mat-paginator [length]="length" <mat-paginator [length]="length" [pageSize]="itemsPerPage" [pageIndex]="page" [pageSizeOptions]="pageSizeOptions"
[pageSize]="itemsPerPage"
[pageIndex]="page"
[pageSizeOptions]="pageSizeOptions"
(page)="onPageChange($event)"> (page)="onPageChange($event)">
</mat-paginator> </mat-paginator>
</div> </div>

View File

@ -7,16 +7,11 @@ import { ToastrService } from 'ngx-toastr';
import { of } from 'rxjs'; import { of } from 'rxjs';
import { NO_ERRORS_SCHEMA } from '@angular/core'; import { NO_ERRORS_SCHEMA } from '@angular/core';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { ConfigService } from '@services/config.service';
class MockUserService { class MockToastrService {
getUsers() { success() {}
return of({ error() {}
'hydra:member': [
{ id: 1, username: 'user1', allowedOrganizationalUnits: [], roles: ['admin'] },
{ id: 2, username: 'user2', allowedOrganizationalUnits: [], roles: ['user'] }
]
});
}
} }
describe('UsersComponent', () => { describe('UsersComponent', () => {
@ -24,21 +19,25 @@ describe('UsersComponent', () => {
let fixture: ComponentFixture<UsersComponent>; let fixture: ComponentFixture<UsersComponent>;
beforeEach(async () => { beforeEach(async () => {
const mockConfigService = {
apiUrl: 'http://mock-api-url',
mercureUrl: 'http://mock-mercure-url'
};
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [UsersComponent], declarations: [UsersComponent],
imports: [ imports: [
MatTableModule, MatTableModule,
MatDialogModule, MatDialogModule,
HttpClientTestingModule, HttpClientTestingModule,
TranslateModule.forRoot() TranslateModule.forRoot(),
], ],
providers: [ providers: [
{ useClass: MockUserService }, { provide: ToastrService, useClass: MockToastrService },
{ provide: ToastrService, useValue: { success: () => {} } }, { provide: ConfigService, useValue: mockConfigService }
], ],
schemas: [NO_ERRORS_SCHEMA], schemas: [NO_ERRORS_SCHEMA], // Ignorar elementos desconocidos
}) }).compileComponents();
.compileComponents();
fixture = TestBed.createComponent(UsersComponent); fixture = TestBed.createComponent(UsersComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
@ -49,4 +48,27 @@ describe('UsersComponent', () => {
expect(component).toBeTruthy(); 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,7 +5,7 @@ import { AddUserModalComponent } from './add-user-modal/add-user-modal.component
import { DeleteModalComponent } from '../../../../shared/delete_modal/delete-modal/delete-modal.component'; import { DeleteModalComponent } from '../../../../shared/delete_modal/delete-modal/delete-modal.component';
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import { ToastrService } from 'ngx-toastr'; import { ToastrService } from 'ngx-toastr';
import { DataService } from "./data.service"; import { ConfigService } from '@services/config.service';
@Component({ @Component({
selector: 'app-users', selector: 'app-users',
@ -13,7 +13,8 @@ import { DataService } from "./data.service";
styleUrls: ['./users.component.css'] styleUrls: ['./users.component.css']
}) })
export class UsersComponent implements OnInit { export class UsersComponent implements OnInit {
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL; baseUrl: string;
private apiUrl: string;
dataSource = new MatTableDataSource<any>(); dataSource = new MatTableDataSource<any>();
filters: { [key: string]: string } = {}; filters: { [key: string]: string } = {};
loading: boolean = false; loading: boolean = false;
@ -32,6 +33,11 @@ export class UsersComponent implements OnInit {
header: 'Nombre de Usuario', header: 'Nombre de Usuario',
cell: (user: any) => `${user.username}` cell: (user: any) => `${user.username}`
}, },
{
columnDef: 'groupsView',
header: 'Vista de Grupos',
cell: (user: any) => `${user.groupsView}`
},
{ {
columnDef: 'allowedOrganizationalUnits', columnDef: 'allowedOrganizationalUnits',
header: 'Unidades Organizacionales Permitidas', header: 'Unidades Organizacionales Permitidas',
@ -45,14 +51,15 @@ export class UsersComponent implements OnInit {
]; ];
displayedColumns = [...this.columns.map(column => column.columnDef), 'actions']; displayedColumns = [...this.columns.map(column => column.columnDef), 'actions'];
private apiUrl = `${this.baseUrl}/users`;
constructor( constructor(
public dialog: MatDialog, public dialog: MatDialog,
private configService: ConfigService,
private http: HttpClient, private http: HttpClient,
private dataService: DataService,
private toastService: ToastrService private toastService: ToastrService
) {} ) {
this.baseUrl = this.configService.apiUrl;
this.apiUrl = `${this.baseUrl}/users`;
}
ngOnInit() { ngOnInit() {
this.search(); this.search();
@ -87,6 +94,10 @@ export class UsersComponent implements OnInit {
data: user['@id'] data: user['@id']
}); });
dialogRef.componentInstance.userEdited.subscribe(() => {
this.search();
});
dialogRef.afterClosed().subscribe(result => { dialogRef.afterClosed().subscribe(result => {
if (result) { if (result) {
this.search(); this.search();

View File

@ -1,15 +1,20 @@
.title { .header-container {
font-size: 24px; 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 { .calendar-button-row {
display: flex; display: flex;
justify-content: flex-start; gap: 15px;
margin-top: 16px;
}
.divider {
margin: 20px 0;
} }
.lists-container { .lists-container {
@ -23,7 +28,6 @@
table { table {
width: 100%; width: 100%;
margin-top: 50px;
} }
.search-container { .search-container {
@ -31,7 +35,7 @@ table {
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
width: 100%; width: 100%;
padding: 0 5px; margin: 1.5rem 0rem 1.5rem 0rem;
box-sizing: border-box; box-sizing: border-box;
} }
@ -45,15 +49,8 @@ table {
padding: 5px; padding: 5px;
} }
.header-container {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px;
}
.mat-elevation-z8 { .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 { .paginator-container {

View File

@ -2,30 +2,33 @@
<button mat-icon-button color="primary" (click)="iniciarTour()"> <button mat-icon-button color="primary" (click)="iniciarTour()">
<mat-icon>help</mat-icon> <mat-icon>help</mat-icon>
</button> </button>
<h2 joyrideStep="titleStep" text="{{ 'titleStepText' | translate }}" class="title">{{ 'adminCalendarsTitle' | translate }}</h2> <div class="header-container-title">
<h2 joyrideStep="titleStep" text="{{ 'titleStepText' | translate }}">{{ 'adminCalendarsTitle' | translate }}</h2>
</div>
<div class="calendar-button-row"> <div class="calendar-button-row">
<button joyrideStep="addButtonStep" text="{{ 'addButtonStepText' | translate }}" mat-flat-button color="primary" (click)="addImage()"> <button joyrideStep="addButtonStep" text="{{ 'addButtonStepText' | translate }}" class="action-button"
(click)="addCalendar()">
{{ 'addCalendar' | translate }} {{ 'addCalendar' | translate }}
</button> </button>
</div> </div>
</div> </div>
<mat-divider class="divider"></mat-divider>
<div class="search-container"> <div class="search-container">
<mat-form-field joyrideStep="searchStep" text="{{ 'searchStepText' | translate }}" appearance="fill" class="search-string"> <mat-form-field joyrideStep="searchStep" text="{{ 'searchStepText' | translate }}" appearance="fill"
class="search-string">
<mat-label>{{ 'searchCalendarLabel' | translate }}</mat-label> <mat-label>{{ 'searchCalendarLabel' | translate }}</mat-label>
<input matInput placeholder="{{ 'searchPlaceholder' | translate }}" [(ngModel)]="filters['name']" (keyup.enter)="search()"> <input matInput placeholder="{{ 'searchPlaceholder' | translate }}" [(ngModel)]="filters['name']"
(keyup.enter)="search()">
<mat-icon matSuffix>search</mat-icon> <mat-icon matSuffix>search</mat-icon>
<mat-hint>{{ 'searchHint' | translate }}</mat-hint> <mat-hint>{{ 'searchHint' | translate }}</mat-hint>
</mat-form-field> </mat-form-field>
</div> </div>
<div *ngIf="loading" class="loading-container"> <app-loading [isLoading]="loading"></app-loading>
<mat-spinner></mat-spinner>
</div>
<div *ngIf="!loading"> <div *ngIf="!loading">
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8" joyrideStep="tableStep" text="{{ 'tableStepText' | translate }}"> <table mat-table [dataSource]="dataSource" class="mat-elevation-z8" joyrideStep="tableStep"
text="{{ 'tableStepText' | translate }}">
<ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef"> <ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef">
<th mat-header-cell *matHeaderCellDef> {{ column.header }} </th> <th mat-header-cell *matHeaderCellDef> {{ column.header }} </th>
<td mat-cell *matCellDef="let image"> <td mat-cell *matCellDef="let image">
@ -34,7 +37,8 @@
{{ image[column.columnDef] ? 'check_circle' : 'cancel' }} {{ image[column.columnDef] ? 'check_circle' : 'cancel' }}
</mat-icon> </mat-icon>
</ng-container> </ng-container>
<ng-container *ngIf="column.columnDef !== 'isDefault' && column.columnDef !== 'installed' && column.columnDef !== 'downloadUrl'"> <ng-container
*ngIf="column.columnDef !== 'isDefault' && column.columnDef !== 'installed' && column.columnDef !== 'downloadUrl'">
{{ column.cell(image) }} {{ column.cell(image) }}
</ng-container> </ng-container>
</td> </td>
@ -50,7 +54,7 @@
<mat-icon>sync</mat-icon> <mat-icon>sync</mat-icon>
</button> </button>
<button *ngIf="syncUds" mat-icon-button color="primary"> <button *ngIf="syncUds" mat-icon-button color="primary">
<mat-spinner diameter="24"></mat-spinner> <app-loading [isLoading]="syncUds"></app-loading>
</button> </button>
<button mat-icon-button color="warn" (click)="deleteCalendar(calendar)"> <button mat-icon-button color="warn" (click)="deleteCalendar(calendar)">
<mat-icon>delete</mat-icon> <mat-icon>delete</mat-icon>
@ -64,10 +68,7 @@
</div> </div>
<div class="paginator-container"> <div class="paginator-container">
<mat-paginator [length]="length" <mat-paginator [length]="length" [pageSize]="itemsPerPage" [pageIndex]="page" [pageSizeOptions]="pageSizeOptions"
[pageSize]="itemsPerPage"
[pageIndex]="page"
[pageSizeOptions]="pageSizeOptions"
(page)="onPageChange($event)"> (page)="onPageChange($event)">
</mat-paginator> </mat-paginator>
</div> </div>

View File

@ -15,14 +15,21 @@ import { CalendarComponent } from './calendar.component';
import { MatProgressSpinner } from '@angular/material/progress-spinner'; import { MatProgressSpinner } from '@angular/material/progress-spinner';
import { JoyrideModule, JoyrideService } from 'ngx-joyride'; import { JoyrideModule, JoyrideService } from 'ngx-joyride';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { LoadingComponent } from '../../shared/loading/loading.component';
import { ConfigService } from '@services/config.service';
describe('CalendarComponent', () => { describe('CalendarComponent', () => {
let component: CalendarComponent; let component: CalendarComponent;
let fixture: ComponentFixture<CalendarComponent>; let fixture: ComponentFixture<CalendarComponent>;
beforeEach(async () => { beforeEach(async () => {
const mockConfigService = {
apiUrl: 'http://mock-api-url',
mercureUrl: 'http://mock-mercure-url'
};
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [CalendarComponent], declarations: [CalendarComponent, LoadingComponent],
imports: [ imports: [
HttpClientTestingModule, HttpClientTestingModule,
ToastrModule.forRoot(), ToastrModule.forRoot(),
@ -40,6 +47,9 @@ describe('CalendarComponent', () => {
JoyrideModule.forRoot(), JoyrideModule.forRoot(),
TranslateModule.forRoot(), TranslateModule.forRoot(),
], ],
providers: [
{ provide: ConfigService, useValue: mockConfigService }
]
}) })
.compileComponents(); .compileComponents();

View File

@ -9,6 +9,7 @@ import { PageEvent } from "@angular/material/paginator";
import { CreateCalendarComponent } from "./create-calendar/create-calendar.component"; import { CreateCalendarComponent } from "./create-calendar/create-calendar.component";
import { DeleteModalComponent } from "../../shared/delete_modal/delete-modal/delete-modal.component"; import { DeleteModalComponent } from "../../shared/delete_modal/delete-modal/delete-modal.component";
import { JoyrideService } from 'ngx-joyride'; import { JoyrideService } from 'ngx-joyride';
import { ConfigService } from '@services/config.service';
@Component({ @Component({
selector: 'app-calendar', selector: 'app-calendar',
@ -16,7 +17,8 @@ import { JoyrideService } from 'ngx-joyride';
styleUrl: './calendar.component.css' styleUrl: './calendar.component.css'
}) })
export class CalendarComponent implements OnInit { 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 }[] = []; images: { downloadUrl: string; name: string; uuid: string }[] = [];
dataSource = new MatTableDataSource<any>(); dataSource = new MatTableDataSource<any>();
length: number = 0; length: number = 0;
@ -52,21 +54,24 @@ export class CalendarComponent implements OnInit {
} }
]; ];
displayedColumns = [...this.columns.map(column => column.columnDef), 'actions']; displayedColumns = [...this.columns.map(column => column.columnDef), 'actions'];
private apiUrl = `${this.baseUrl}/remote-calendars`;
constructor( constructor(
public dialog: MatDialog, public dialog: MatDialog,
private http: HttpClient, private http: HttpClient,
private dataService: DataService, private dataService: DataService,
private toastService: ToastrService, private toastService: ToastrService,
private configService: ConfigService,
private joyrideService: JoyrideService private joyrideService: JoyrideService
) {} ) {
this.baseUrl = this.configService.apiUrl;
this.apiUrl = `${this.baseUrl}/remote-calendars`;
}
ngOnInit(): void { ngOnInit(): void {
this.search(); this.search();
} }
addImage(): void { addCalendar(): void {
const dialogRef = this.dialog.open(CreateCalendarComponent, { const dialogRef = this.dialog.open(CreateCalendarComponent, {
width: '400px' width: '400px'
}); });

View File

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

View File

@ -56,10 +56,10 @@
</div> </div>
</mat-dialog-content> </mat-dialog-content>
<mat-dialog-actions align="end"> <mat-dialog-actions class="action-container">
<button mat-button (click)="onNoClick()">{{ 'buttonCancel' | translate }}</button> <button class="ordinary-button" (click)="onNoClick()">{{ 'buttonCancel' | translate }}</button>
<button <button
mat-button class="submit-button"
(click)="submitRule()" (click)="submitRule()"
cdkFocusInitial cdkFocusInitial
[disabled]="(!isRemoteAvailable && (!busyFromHour || !busyToHour)) || (isRemoteAvailable && (!availableReason || !availableFromDate || !availableToDate))"> [disabled]="(!isRemoteAvailable && (!busyFromHour || !busyToHour)) || (isRemoteAvailable && (!availableReason || !availableFromDate || !availableToDate))">

View File

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

View File

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

View File

@ -9,7 +9,7 @@
<div style="display: flex; justify-content: space-between; align-items: center;"> <div style="display: flex; justify-content: space-between; align-items: center;">
<div *ngIf="isEditMode" mat-subheader>{{ 'rulesHeader' | translate }}</div> <div *ngIf="isEditMode" mat-subheader>{{ 'rulesHeader' | translate }}</div>
<button mat-flat-button color="primary" *ngIf="isEditMode" (click)="createRule()" style="padding: 10px;"> <button class="action-button" *ngIf="isEditMode" (click)="createRule()" style="padding: 10px;">
{{ 'addRule' | translate }} {{ 'addRule' | translate }}
</button> </button>
</div> </div>
@ -41,9 +41,9 @@
</mat-list> </mat-list>
</mat-dialog-content> </mat-dialog-content>
<mat-dialog-actions align="end"> <mat-dialog-actions class="action-container">
<button mat-button (click)="onNoClick()">{{ 'buttonCancel' | translate }}</button> <button class="ordinary-button" (click)="onNoClick()">{{ 'buttonCancel' | translate }}</button>
<button mat-button (click)="submitForm()" [disabled]="!name || name === ''" cdkFocusInitial> <button class="submit-button" (click)="submitForm()" [disabled]="!name || name === ''" cdkFocusInitial>
{{ 'buttonSave' | translate }} {{ 'buttonSave' | translate }}
</button> </button>
</mat-dialog-actions> </mat-dialog-actions>

View File

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

View File

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

View File

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

View File

@ -2,16 +2,16 @@
<button mat-icon-button color="primary" (click)="iniciarTour()"> <button mat-icon-button color="primary" (click)="iniciarTour()">
<mat-icon>help</mat-icon> <mat-icon>help</mat-icon>
</button> </button>
<h2 class="title" joyrideStep="titleStep" text="{{ 'titleStepText' | translate }}">{{ 'adminCommandGroupsTitle' | translate }}</h2> <div class="header-container-title">
<h2 joyrideStep="titleStep" text="{{ 'titleStepText' | translate }}">{{ 'adminCommandGroupsTitle' | translate }}</h2>
</div>
<div class="command-groups-button-row"> <div class="command-groups-button-row">
<button mat-flat-button color="primary" (click)="openCreateCommandGroupModal()" joyrideStep="addCommandGroupStep" text="{{ 'addCommandGroupStepText' | translate }}"> <button class="action-button" (click)="openCreateCommandGroupModal()" joyrideStep="addCommandGroupStep" text="{{ 'addCommandGroupStepText' | translate }}">
{{ 'addCommandGroup' | translate }} {{ 'addCommandGroup' | translate }}
</button> </button>
</div> </div>
</div> </div>
<mat-divider class="divider"></mat-divider>
<div class="search-container" joyrideStep="searchStep" text="{{ 'searchStepText' | translate }}"> <div class="search-container" joyrideStep="searchStep" text="{{ 'searchStepText' | translate }}">
<mat-form-field appearance="fill" class="search-string"> <mat-form-field appearance="fill" class="search-string">
<mat-label>{{ 'searchGroupNameLabel' | translate }}</mat-label> <mat-label>{{ 'searchGroupNameLabel' | translate }}</mat-label>
@ -22,7 +22,7 @@
</div> </div>
<div *ngIf="loading" class="loading-container" joyrideStep="loadingStep" text="{{ 'loadingStepText' | translate }}"> <div *ngIf="loading" class="loading-container" joyrideStep="loadingStep" text="{{ 'loadingStepText' | translate }}">
<mat-spinner></mat-spinner> <app-loading [isLoading]="loading"></app-loading>
</div> </div>
<div *ngIf="!loading"> <div *ngIf="!loading">
@ -35,7 +35,7 @@
</ng-container> </ng-container>
<ng-container *ngIf="column.columnDef === 'commands'" joyrideStep="viewCommandsStep" text="{{ 'viewCommandsStepText' | translate }}"> <ng-container *ngIf="column.columnDef === 'commands'" joyrideStep="viewCommandsStep" text="{{ 'viewCommandsStepText' | translate }}">
<button mat-button [matMenuTriggerFor]="menu">{{ 'viewCommands' | translate }}</button> <button class="action-button" [matMenuTriggerFor]="menu">{{ 'viewCommands' | translate }}</button>
<mat-menu #menu="matMenu"> <mat-menu #menu="matMenu">
<button mat-menu-item *ngFor="let command of commandGroup.commands"> <button mat-menu-item *ngFor="let command of commandGroup.commands">
{{ command.name }} {{ command.name }}

View File

@ -8,6 +8,7 @@ import { DeleteModalComponent } from '../../../shared/delete_modal/delete-modal/
import { MatTableDataSource } from "@angular/material/table"; import { MatTableDataSource } from "@angular/material/table";
import { DatePipe } from "@angular/common"; import { DatePipe } from "@angular/common";
import { JoyrideService } from 'ngx-joyride'; import { JoyrideService } from 'ngx-joyride';
import { ConfigService } from '@services/config.service';
@Component({ @Component({
selector: 'app-commands-groups', selector: 'app-commands-groups',
@ -15,7 +16,8 @@ import { JoyrideService } from 'ngx-joyride';
styleUrls: ['./commands-groups.component.css'] styleUrls: ['./commands-groups.component.css']
}) })
export class CommandsGroupsComponent implements OnInit { export class CommandsGroupsComponent implements OnInit {
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL; baseUrl: string;
private apiUrl: string;
dataSource = new MatTableDataSource<any>(); dataSource = new MatTableDataSource<any>();
filters: { [key: string]: string | boolean } = {}; filters: { [key: string]: string | boolean } = {};
length: number = 0; length: number = 0;
@ -47,10 +49,12 @@ export class CommandsGroupsComponent implements OnInit {
} }
]; ];
displayedColumns = [...this.columns.map(column => column.columnDef), 'actions']; 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 joyrideService: JoyrideService) {} private configService: ConfigService, private joyrideService: JoyrideService) {
this.baseUrl = this.configService.apiUrl;
this.apiUrl = `${this.baseUrl}/command-groups`;
}
ngOnInit(): void { ngOnInit(): void {
this.search(); this.search();

View File

@ -98,3 +98,10 @@
margin-bottom: 20px; margin-bottom: 20px;
} }
} }
.action-container {
display: flex;
justify-content: flex-end;
gap: 1em;
padding: 1.5em;
}

View File

@ -1,8 +1,6 @@
<h2 mat-dialog-title>{{ editing ? ('editCommandGroup' | translate) : ('createCommandGroup' | translate) }}</h2> <h2 mat-dialog-title>{{ editing ? ('editCommandGroup' | translate) : ('createCommandGroup' | translate) }}</h2>
<mat-dialog-content class="form-container"> <mat-dialog-content class="form-container">
<div *ngIf="loading" class="loading-container"> <app-loading [isLoading]="loading"></app-loading>
<mat-spinner></mat-spinner>
</div>
<form *ngIf="!loading" class="command-group-form" (ngSubmit)="onSubmit()"> <form *ngIf="!loading" class="command-group-form" (ngSubmit)="onSubmit()">
<mat-form-field> <mat-form-field>
@ -55,7 +53,7 @@
</form> </form>
</mat-dialog-content> </mat-dialog-content>
<mat-dialog-actions align="end"> <mat-dialog-actions class="action-container">
<button mat-button (click)="close()">{{ 'buttonCancel' | translate }}</button> <button class="ordinary-button" (click)="close()">{{ 'buttonCancel' | translate }}</button>
<button mat-button (click)="onSubmit()" cdkFocusInitial>{{ 'buttonSave' | translate }}</button> <button class="submit-button" (click)="onSubmit()" cdkFocusInitial>{{ 'buttonSave' | translate }}</button>
</mat-dialog-actions> </mat-dialog-actions>

View File

@ -3,6 +3,7 @@ import { HttpClient } from '@angular/common/http';
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { ToastrService } from 'ngx-toastr'; import { ToastrService } from 'ngx-toastr';
import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop'; import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop';
import { ConfigService } from '@services/config.service';
@Component({ @Component({
selector: 'app-create-command-group', selector: 'app-create-command-group',
@ -10,21 +11,25 @@ import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop';
styleUrls: ['./create-command-group.component.css'] styleUrls: ['./create-command-group.component.css']
}) })
export class CreateCommandGroupComponent implements OnInit { export class CreateCommandGroupComponent implements OnInit {
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL; baseUrl: string;
availableCommands: any[] = []; availableCommands: any[] = [];
selectedCommands: any[] = []; selectedCommands: any[] = [];
groupName: string = ''; groupName: string = '';
enabled: boolean = true; enabled: boolean = true;
editing: boolean = false; editing: boolean = false;
loading: boolean = false; loading: boolean = false;
private apiUrl = `${this.baseUrl}/commands`; private apiUrl: string;
constructor( constructor(
private http: HttpClient, private http: HttpClient,
private dialogRef: MatDialogRef<CreateCommandGroupComponent>, private dialogRef: MatDialogRef<CreateCommandGroupComponent>,
private toastService: ToastrService, private toastService: ToastrService,
private configService: ConfigService,
@Inject(MAT_DIALOG_DATA) public data: any @Inject(MAT_DIALOG_DATA) public data: any
) {} ) {
this.baseUrl = this.configService.apiUrl;
this.apiUrl = `${this.baseUrl}/commands`;
}
ngOnInit(): void { ngOnInit(): void {
this.loadAvailableCommands(); this.loadAvailableCommands();

View File

@ -58,19 +58,6 @@
padding: 10px 20px; 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) { @media (max-width: 600px) {
.mat-card { .mat-card {
margin: 10px 0; 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 { .create-command-group-container {
padding: 20px; padding: 20px;
} }
@ -146,8 +124,9 @@
color: #666; color: #666;
} }
.command-group-actions { .action-container {
margin-top: 20px;
display: flex; display: flex;
justify-content: space-between; justify-content: flex-end;
gap: 1em;
padding: 1.5em;
} }

View File

@ -1,10 +1,7 @@
<div class="detail-command-group-container"> <div class="detail-command-group-container">
<h2>{{ 'commandGroupDetailsTitle' | translate }}</h2> <h2>{{ 'commandGroupDetailsTitle' | translate }}</h2>
<!-- Indicador de carga --> <app-loading [isLoading]="loading"></app-loading>
<div *ngIf="loading" class="loading-container">
<mat-spinner></mat-spinner>
</div>
<mat-card *ngIf="!loading"> <mat-card *ngIf="!loading">
<mat-card-header> <mat-card-header>
@ -52,10 +49,10 @@
</form> </form>
</div> </div>
<div class="command-group-actions" *ngIf="!loading"> <div class="action-container" *ngIf="!loading">
<button mat-flat-button color="primary" (click)="toggleClientSelect()"> <button class="ordinary-button" (click)="close()">{{ 'buttonCancel' | translate }}</button>
<button [ngClass]="showClientSelect ? 'submit-button' : 'action-button'" (click)="toggleClientSelect()">
{{ showClientSelect ? ('execute' | translate) : ('scheduleExecution' | translate) }} {{ showClientSelect ? ('execute' | translate) : ('scheduleExecution' | translate) }}
</button> </button>
<button mat-flat-button color="warn" (click)="close()">{{ 'buttonCancel' | translate }}</button>
</div> </div>
</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 { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import { ToastrService } from 'ngx-toastr'; import { ToastrService } from 'ngx-toastr';
import { ConfigService } from '@services/config.service';
@Component({ @Component({
selector: 'app-detail-command-group', selector: 'app-detail-command-group',
@ -10,7 +11,7 @@ import { ToastrService } from 'ngx-toastr';
styleUrls: ['./detail-command-group.component.css'] styleUrls: ['./detail-command-group.component.css']
}) })
export class DetailCommandGroupComponent implements OnInit { export class DetailCommandGroupComponent implements OnInit {
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL; baseUrl: string;
form!: FormGroup; form!: FormGroup;
clients: any[] = []; clients: any[] = [];
showClientSelect = false; showClientSelect = false;
@ -21,9 +22,12 @@ export class DetailCommandGroupComponent implements OnInit {
@Inject(MAT_DIALOG_DATA) public data: any, @Inject(MAT_DIALOG_DATA) public data: any,
private dialogRef: MatDialogRef<DetailCommandGroupComponent>, private dialogRef: MatDialogRef<DetailCommandGroupComponent>,
private fb: FormBuilder, private fb: FormBuilder,
private configService: ConfigService,
private http: HttpClient, private http: HttpClient,
private toastService: ToastrService private toastService: ToastrService
) { } ) {
this.baseUrl = this.configService.apiUrl;
}
ngOnInit(): void { ngOnInit(): void {
this.form = this.fb.group({ this.form = this.fb.group({

View File

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

View File

@ -2,16 +2,16 @@
<button mat-icon-button color="primary" (click)="iniciarTour()"> <button mat-icon-button color="primary" (click)="iniciarTour()">
<mat-icon>help</mat-icon> <mat-icon>help</mat-icon>
</button> </button>
<div class="header-container-title">
<h2 class="title" joyrideStep="titleStep" text="{{ 'titleStepText' | translate }}">{{ 'manageTasksTitle' | translate }}</h2> <h2 class="title" joyrideStep="titleStep" text="{{ 'titleStepText' | translate }}">{{ 'manageTasksTitle' | translate }}</h2>
</div>
<div class="task-button-row"> <div class="task-button-row">
<button mat-flat-button color="primary" (click)="openCreateTaskModal()" joyrideStep="addTaskStep" text="{{ 'addTaskStepText' | translate }}"> <button class="action-button" (click)="openCreateTaskModal()" joyrideStep="addTaskStep" text="{{ 'addTaskStepText' | translate }}">
{{ 'addTask' | translate }} {{ 'addTask' | translate }}
</button> </button>
</div> </div>
</div> </div>
<mat-divider class="divider"></mat-divider>
<div class="search-container" joyrideStep="searchStep" text="{{ 'searchStepText' | translate }}"> <div class="search-container" joyrideStep="searchStep" text="{{ 'searchStepText' | translate }}">
<mat-form-field appearance="fill" class="search-string"> <mat-form-field appearance="fill" class="search-string">
<mat-label>{{ 'searchTaskLabel' | translate }}</mat-label> <mat-label>{{ 'searchTaskLabel' | translate }}</mat-label>

View File

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

View File

@ -6,6 +6,7 @@ import { CreateTaskComponent } from './create-task/create-task.component';
import { DetailTaskComponent } from './detail-task/detail-task.component'; import { DetailTaskComponent } from './detail-task/detail-task.component';
import { DeleteModalComponent } from '../../../shared/delete_modal/delete-modal/delete-modal.component'; import { DeleteModalComponent } from '../../../shared/delete_modal/delete-modal/delete-modal.component';
import { JoyrideService } from 'ngx-joyride'; import { JoyrideService } from 'ngx-joyride';
import { ConfigService } from '@services/config.service';
@Component({ @Component({
selector: 'app-commands-task', selector: 'app-commands-task',
@ -13,7 +14,7 @@ import { JoyrideService } from 'ngx-joyride';
styleUrls: ['./commands-task.component.css'] styleUrls: ['./commands-task.component.css']
}) })
export class CommandsTaskComponent implements OnInit { export class CommandsTaskComponent implements OnInit {
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL; baseUrl: string;
tasks: any[] = []; tasks: any[] = [];
filters: { [key: string]: string | boolean } = {}; filters: { [key: string]: string | boolean } = {};
length: number = 0; length: number = 0;
@ -22,10 +23,14 @@ export class CommandsTaskComponent implements OnInit {
pageSizeOptions: number[] = [5, 10, 20, 40, 100]; pageSizeOptions: number[] = [5, 10, 20, 40, 100];
displayedColumns: string[] = ['taskid', 'notes', 'name', 'scheduledDate', 'enabled', 'actions']; displayedColumns: string[] = ['taskid', 'notes', 'name', 'scheduledDate', 'enabled', 'actions'];
loading: boolean = false; loading: boolean = false;
private apiUrl = `${this.baseUrl}/command-tasks`; private apiUrl: string;
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,
private joyrideService: JoyrideService) {
this.baseUrl = this.configService.apiUrl;
this.apiUrl = `${this.baseUrl}/command-tasks`;
}
ngOnInit(): void { ngOnInit(): void {
this.loadTasks(); this.loadTasks();

View File

@ -14,6 +14,7 @@
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;
margin-top: 20px; margin-top: 20px;
padding: 1.5rem;
} }
mat-form-field { mat-form-field {

View File

@ -87,10 +87,6 @@
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
<div class="button-container">
<button mat-raised-button color="primary" (click)="saveTask()">{{ 'buttonSave' | translate }}</button>
</div>
<mat-form-field appearance="fill" class="full-width"> <mat-form-field appearance="fill" class="full-width">
<mat-label>Selecciona Clientes</mat-label> <mat-label>Selecciona Clientes</mat-label>
<mat-select formControlName="selectedClients" multiple> <mat-select formControlName="selectedClients" multiple>
@ -102,10 +98,9 @@
</mat-option> </mat-option>
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
<div class="button-container">
<button mat-raised-button color="primary" (click)="saveTask()">Guardar</button>
</div>
</mat-dialog-content> </mat-dialog-content>
</form> </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 { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { ToastrService } from 'ngx-toastr'; import { ToastrService } from 'ngx-toastr';
import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { ConfigService } from '@services/config.service';
@Component({ @Component({
selector: 'app-create-task', selector: 'app-create-task',
@ -10,12 +11,12 @@ import { FormBuilder, FormGroup, Validators } from '@angular/forms';
styleUrls: ['./create-task.component.css'] styleUrls: ['./create-task.component.css']
}) })
export class CreateTaskComponent implements OnInit { export class CreateTaskComponent implements OnInit {
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL; baseUrl: string;
taskForm: FormGroup; taskForm: FormGroup;
availableCommandGroups: any[] = []; availableCommandGroups: any[] = [];
selectedGroupCommands: any[] = []; selectedGroupCommands: any[] = [];
availableIndividualCommands: any[] = []; availableIndividualCommands: any[] = [];
apiUrl = `${this.baseUrl}/command-tasks`; apiUrl: string;
editing: boolean = false; editing: boolean = false;
availableOrganizationalUnits: any[] = []; availableOrganizationalUnits: any[] = [];
selectedUnitChildren: any[] = []; selectedUnitChildren: any[] = [];
@ -25,10 +26,13 @@ export class CreateTaskComponent implements OnInit {
constructor( constructor(
private fb: FormBuilder, private fb: FormBuilder,
private http: HttpClient, private http: HttpClient,
private configService: ConfigService,
private toastr: ToastrService, private toastr: ToastrService,
public dialogRef: MatDialogRef<CreateTaskComponent>, public dialogRef: MatDialogRef<CreateTaskComponent>,
@Inject(MAT_DIALOG_DATA) public data: any @Inject(MAT_DIALOG_DATA) public data: any
) { ) {
this.baseUrl = this.configService.apiUrl;
this.apiUrl = `${this.baseUrl}/command-tasks`;
this.taskForm = this.fb.group({ this.taskForm = this.fb.group({
commandGroup: ['', Validators.required], commandGroup: ['', Validators.required],
extraCommands: [[]], extraCommands: [[]],

View File

@ -58,25 +58,3 @@
padding: 10px 20px; 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

@ -50,6 +50,6 @@
</mat-card> </mat-card>
<div class="task-actions"> <div class="task-actions">
<button mat-flat-button class="cancel-button" (click)="closeDialog()">{{ 'buttonClose' | translate }}</button> <button class="ordinary-button" (click)="closeDialog()">Cancel</button>
</div> </div>
</div> </div>

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 { .header-container {
font-size: 24px; 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 { .calendar-button-row {
display: flex; display: flex;
justify-content: flex-start; gap: 15px;
margin-top: 16px;
}
.divider {
margin: 20px 0;
} }
.lists-container { .lists-container {
@ -27,14 +32,13 @@
table { table {
width: 100%; width: 100%;
margin-top: 50px;
} }
.search-container { .search-container {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
padding: 0 5px; margin: 1.5rem 0rem 1.5rem 0rem;
box-sizing: border-box; box-sizing: border-box;
} }
@ -53,15 +57,14 @@ table {
padding: 5px; padding: 5px;
} }
.header-container { .mat-elevation-z8 {
display: flex; box-shadow: 0px 0px 0px rgba(0, 0, 0, 0.2);
justify-content: space-between;
align-items: center;
padding: 10px;
} }
.mat-elevation-z8 { .progress-container {
box-shadow: 0px 0px 0px rgba(0,0,0,0.2); display: flex;
align-items: center;
gap: 10px;
} }
.paginator-container { .paginator-container {
@ -71,12 +74,12 @@ table {
} }
.chip-failed { .chip-failed {
background-color: #f15d5d !important; background-color: #e87979 !important;
color: white; color: white;
} }
.chip-success { .chip-success {
background-color: #32c532 !important; background-color: #46c446 !important;
color: white; color: white;
} }
@ -90,3 +93,26 @@ table {
color: white; 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

@ -2,29 +2,39 @@
<button mat-icon-button color="primary" (click)="iniciarTour()"> <button mat-icon-button color="primary" (click)="iniciarTour()">
<mat-icon>help</mat-icon> <mat-icon>help</mat-icon>
</button> </button>
<h2 class="title" joyrideStep="titleStep" text="{{ 'titleStepText' | translate }}">{{ 'adminCommandsTitle' | translate }}</h2>
<div class="header-container-title">
<h2 joyrideStep="titleStep" text="{{ 'titleStepText' | translate }}">{{ 'adminCommandsTitle' |
translate }}</h2>
</div>
<div class="images-button-row"> <div class="images-button-row">
<button mat-flat-button color="primary" (click)="resetFilters()" joyrideStep="resetFiltersStep" text="{{ 'resetFiltersStepText' | translate }}"> <button class="action-button" (click)="resetFilters()" joyrideStep="resetFiltersStep"
text="{{ 'resetFiltersStepText' | translate }}">
{{ 'resetFilters' | translate }} {{ 'resetFilters' | translate }}
</button> </button>
</div> </div>
</div> </div>
<mat-divider class="divider"></mat-divider>
<div class="search-container"> <div class="search-container">
<mat-form-field appearance="fill" class="search-select" joyrideStep="clientSelectStep" text="{{ 'clientSelectStepText' | translate }}"> <mat-form-field appearance="fill" class="search-select" joyrideStep="clientSelectStep"
<input type="text" matInput [formControl]="clientControl" [matAutocomplete]="clientAuto" placeholder="{{ 'selectClientPlaceholder' | translate }}"> text="{{ 'clientSelectStepText' | translate }}">
<mat-autocomplete #clientAuto="matAutocomplete" [displayWith]="displayFnClient" (optionSelected)="onOptionClientSelected($event.option.value)"> <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"> <mat-option *ngFor="let client of filteredClients | async" [value]="client">
{{ client.name }} {{ client.name }}
</mat-option> </mat-option>
</mat-autocomplete> </mat-autocomplete>
</mat-form-field> </mat-form-field>
<mat-form-field appearance="fill" class="search-select" joyrideStep="commandSelectStep" text="{{ 'commandSelectStepText' | translate }}"> <mat-form-field appearance="fill" class="search-select" joyrideStep="commandSelectStep"
<input type="text" matInput [formControl]="commandControl" [matAutocomplete]="commandAuto" placeholder="{{ 'selectCommandPlaceholder' | translate }}"> text="{{ 'commandSelectStepText' | translate }}">
<mat-autocomplete #commandAuto="matAutocomplete" [displayWith]="displayFnCommand" (optionSelected)="onOptionCommandSelected($event.option.value)"> <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"> <mat-option *ngFor="let command of filteredCommands | async" [value]="command">
{{ command.name }} {{ command.name }}
</mat-option> </mat-option>
@ -33,59 +43,86 @@
<mat-form-field appearance="fill" class="search-boolean"> <mat-form-field appearance="fill" class="search-boolean">
<mat-label i18n="@@searchLabel">Estado</mat-label> <mat-label i18n="@@searchLabel">Estado</mat-label>
<mat-select [(ngModel)]="filters['status']" (selectionChange)="loadTraces()" placeholder="Seleccionar opción" > <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]="'failed'">Fallido</mat-option>
<mat-option [value]="'pending'">Pendiente de ejecutar</mat-option> <mat-option [value]="'pending'">Pendiente de ejecutar</mat-option>
<mat-option [value]="'in-progress'">Ejecutando</mat-option> <mat-option [value]="'in-progress'">Ejecutando</mat-option>
<mat-option [value]="'success'">Completado con éxito</mat-option> <mat-option [value]="'success'">Completado con éxito</mat-option>
<mat-option [value]="'cancelled'">Cancelado</mat-option>
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
</div> </div>
<div *ngIf="loading" class="loading-container"> <app-loading [isLoading]="loading"></app-loading>
<mat-spinner></mat-spinner>
</div>
<div *ngIf="!loading"> <div *ngIf="!loading">
<table mat-table [dataSource]="traces" class="mat-elevation-z8" joyrideStep="tableStep" text="{{ 'tableStepText' | translate }}"> <table mat-table [dataSource]="traces" class="mat-elevation-z8" joyrideStep="tableStep"
text="{{ 'tableStepText' | translate }}">
<ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef"> <ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef">
<th mat-header-cell *matHeaderCellDef> {{ column.header }} </th> <th mat-header-cell *matHeaderCellDef> {{ column.header }} </th>
<td mat-cell *matCellDef="let trace"> <td mat-cell *matCellDef="let trace">
<ng-container *ngIf="column.columnDef === 'status'; else defaultCell"> <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]="{ <mat-chip [ngClass]="{
'chip-failed': trace.status === 'failed', 'chip-failed': trace.status === 'failed',
'chip-success': trace.status === 'success', 'chip-success': trace.status === 'success',
'chip-pending': trace.status === 'pending', 'chip-pending': trace.status === 'pending',
'chip-in-progress': trace.status === 'in-progress' 'chip-in-progress': trace.status === 'in-progress',
'chip-cancelled': trace.status === 'cancelled'
}"> }">
{{ {{
trace.status === 'failed' ? 'Fallido' : trace.status === 'failed' ? 'Fallido' :
trace.status === 'in-progress' ? 'En ejecución' :
trace.status === 'success' ? 'Finalizado con éxito' : trace.status === 'success' ? 'Finalizado con éxito' :
trace.status === 'pending' ? 'Pendiente de ejecutar' : trace.status === 'pending' ? 'Pendiente de ejecutar' :
trace.status === 'in-progress' ? 'Ejecutando' : trace.status === 'cancelled' ? 'Cancelado' :
trace.status trace.status
}} }}
</mat-chip> </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>
<ng-template #defaultCell> <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) }} {{ column.cell(trace) }}
</ng-template> </ng-container>
</ng-container>
</td> </td>
</ng-container> </ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr> <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr> <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table> </table>
</div> </div>
<div class="paginator-container" joyrideStep="paginationStep" text="{{ 'paginationStepText' | translate }}"> <div class="paginator-container" joyrideStep="paginationStep" text="{{ 'paginationStepText' | translate }}">
<mat-paginator [length]="length" <mat-paginator [length]="length" [pageSize]="itemsPerPage" [pageIndex]="page" [pageSizeOptions]="pageSizeOptions"
[pageSize]="itemsPerPage"
[pageIndex]="page"
[pageSizeOptions]="pageSizeOptions"
(page)="onPageChange($event)"> (page)="onPageChange($event)">
</mat-paginator> </mat-paginator>
</div> </div>

View File

@ -1,10 +1,16 @@
import { Component, OnInit } from '@angular/core'; import { ChangeDetectorRef, Component, OnInit } from '@angular/core';
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs'; import { Observable, forkJoin } from 'rxjs';
import { FormControl } from '@angular/forms'; import { FormControl } from '@angular/forms';
import { map, startWith } from 'rxjs/operators'; import { map, startWith } from 'rxjs/operators';
import { DatePipe } from '@angular/common'; import { DatePipe } from '@angular/common';
import { JoyrideService } from 'ngx-joyride'; 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({ @Component({
selector: 'app-task-logs', selector: 'app-task-logs',
@ -12,7 +18,8 @@ import { JoyrideService } from 'ngx-joyride';
styleUrls: ['./task-logs.component.css'] styleUrls: ['./task-logs.component.css']
}) })
export class TaskLogsComponent implements OnInit { export class TaskLogsComponent implements OnInit {
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL; baseUrl: string;
mercureUrl: string;
traces: any[] = []; traces: any[] = [];
groupedTraces: any[] = []; groupedTraces: any[] = [];
commands: any[] = []; commands: any[] = [];
@ -23,6 +30,9 @@ export class TaskLogsComponent implements OnInit {
loading: boolean = true; loading: boolean = true;
pageSizeOptions: number[] = [10, 20, 30, 50]; pageSizeOptions: number[] = [10, 20, 30, 50];
datePipe: DatePipe = new DatePipe('es-ES'); datePipe: DatePipe = new DatePipe('es-ES');
mode: ProgressBarMode = 'buffer';
progress = 0;
bufferValue = 0;
columns = [ columns = [
{ {
@ -50,6 +60,16 @@ export class TaskLogsComponent implements OnInit {
header: 'Hilo de trabajo', header: 'Hilo de trabajo',
cell: (trace: any) => `${trace.jobId}` 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', columnDef: 'executedAt',
header: 'Programación de ejecución', header: 'Programación de ejecución',
@ -70,12 +90,20 @@ export class TaskLogsComponent implements OnInit {
commandControl = new FormControl(); commandControl = new FormControl();
constructor(private http: HttpClient, constructor(private http: HttpClient,
private joyrideService: JoyrideService) {} 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 { ngOnInit(): void {
this.loadTraces(); this.loadTraces();
this.loadCommands(); this.loadCommands();
this.loadClients(); //this.loadClients();
this.filteredCommands = this.commandControl.valueChanges.pipe( this.filteredCommands = this.commandControl.valueChanges.pipe(
startWith(''), startWith(''),
map(value => (typeof value === 'string' ? value : value?.name)), map(value => (typeof value === 'string' ? value : value?.name)),
@ -86,7 +114,38 @@ export class TaskLogsComponent implements OnInit {
map(value => (typeof value === 'string' ? value : value?.name)), map(value => (typeof value === 'string' ? value : value?.name)),
map(name => (name ? this._filterClients(name) : this.clients.slice())) 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[] { private _filterClients(name: string): any[] {
const filterValue = name.toLowerCase(); const filterValue = name.toLowerCase();
@ -116,10 +175,41 @@ export class TaskLogsComponent implements OnInit {
this.loadTraces(); 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 { loadTraces(): void {
this.loading = true; this.loading = true;
const url = `${this.baseUrl}/traces?page=${this.page + 1}&itemsPerPage=${this.itemsPerPage}`; 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) => { (data) => {
this.traces = data['hydra:member']; this.traces = data['hydra:member'];
this.length = data['hydra:totalItems']; this.length = data['hydra:totalItems'];
@ -151,10 +241,20 @@ export class TaskLogsComponent implements OnInit {
this.loading = true; this.loading = true;
this.http.get<any>(`${this.baseUrl}/clients?&page=1&itemsPerPage=10000`).subscribe( this.http.get<any>(`${this.baseUrl}/clients?&page=1&itemsPerPage=10000`).subscribe(
response => { response => {
this.clients = response['hydra:member']; 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; this.loading = false;
}, },
error => { (error: any) => {
console.error('Error fetching client details:', error);
this.loading = false;
}
);
},
(error: any) => {
console.error('Error fetching clients:', error); console.error('Error fetching clients:', error);
this.loading = false; this.loading = false;
} }

View File

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

View File

@ -2,14 +2,14 @@
<button mat-icon-button color="primary" (click)="iniciarTour()"> <button mat-icon-button color="primary" (click)="iniciarTour()">
<mat-icon>help</mat-icon> <mat-icon>help</mat-icon>
</button> </button>
<h2 class="title" joyrideStep="titleStep" text="{{ 'titleStepText' | translate }}">{{ 'CommandsTitle' | translate }}</h2> <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 }}"> <div class="command-button-row" joyrideStep="addCommandStep" text="{{ 'addCommandStepText' | translate }}">
<button mat-flat-button color="primary" (click)="openCreateCommandModal()">{{ 'addCommand' | translate }}</button> <button class="action-button" (click)="openCreateCommandModal()">{{ 'addCommand' | translate }}</button>
</div> </div>
</div> </div>
<mat-divider class="divider"></mat-divider>
<div class="search-container" joyrideStep="searchStep" text="{{ 'searchStepText' | translate }}"> <div class="search-container" joyrideStep="searchStep" text="{{ 'searchStepText' | translate }}">
<mat-form-field appearance="fill" class="search-string"> <mat-form-field appearance="fill" class="search-string">
<mat-label>{{ 'searchCommandLabel' | translate }}</mat-label> <mat-label>{{ 'searchCommandLabel' | translate }}</mat-label>
@ -20,7 +20,7 @@
</div> </div>
<div *ngIf="loading" class="loading-container" joyrideStep="loadingStep" text="{{ 'loadingStepText' | translate }}"> <div *ngIf="loading" class="loading-container" joyrideStep="loadingStep" text="{{ 'loadingStepText' | translate }}">
<mat-spinner></mat-spinner> <app-loading [isLoading]="loading"></app-loading>
</div> </div>
<div *ngIf="!loading"> <div *ngIf="!loading">
@ -31,9 +31,11 @@
<ng-container *ngIf="column.columnDef !== 'readOnly'"> <ng-container *ngIf="column.columnDef !== 'readOnly'">
{{ column.cell(command) }} {{ column.cell(command) }}
</ng-container> </ng-container>
<ng-container *ngIf="column.columnDef === 'readOnly'"> <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-icon [color]="command[column.columnDef] ? 'primary' : 'warn'">
<mat-chip *ngIf="!command.readOnly" class="mat-chip-readonly-false"><mat-icon style="color:white;">close</mat-icon></mat-chip> {{ command[column.columnDef] ? 'check_circle' : 'cancel' }}
</mat-icon>
</ng-container> </ng-container>
</td> </td>
</ng-container> </ng-container>
@ -41,7 +43,6 @@
<ng-container matColumnDef="actions"> <ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef style="text-align: center;">{{ 'columnActions' | translate }}</th> <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 }}"> <td mat-cell *matCellDef="let command" style="text-align: center;" joyrideStep="actionsStep" text="{{ 'actionsStepText' | translate }}">
<button mat-icon-button color="info" (click)="executeCommand($event, command)"><mat-icon>play_arrow</mat-icon></button>
<button mat-icon-button color="info" (click)="viewDetails($event, command)"><mat-icon>visibility</mat-icon></button> <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="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> <button mat-icon-button color="warn" [disabled]="command.readOnly" (click)="deleteCommand($event, command)"><mat-icon>delete</mat-icon></button>

View File

@ -19,14 +19,21 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { NgxChartsModule } from '@swimlane/ngx-charts'; import { NgxChartsModule } from '@swimlane/ngx-charts';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { JoyrideModule } from 'ngx-joyride'; import { JoyrideModule } from 'ngx-joyride';
import { LoadingComponent } from '../../../shared/loading/loading.component';
import { ConfigService } from '@services/config.service';
describe('CommandsComponent', () => { describe('CommandsComponent', () => {
let component: CommandsComponent; let component: CommandsComponent;
let fixture: ComponentFixture<CommandsComponent>; let fixture: ComponentFixture<CommandsComponent>;
beforeEach(async () => { beforeEach(async () => {
const mockConfigService = {
apiUrl: 'http://mock-api-url',
mercureUrl: 'http://mock-mercure-url'
};
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [CommandsComponent], declarations: [CommandsComponent, LoadingComponent],
imports: [ imports: [
HttpClientTestingModule, HttpClientTestingModule,
ToastrModule.forRoot(), ToastrModule.forRoot(),
@ -52,7 +59,8 @@ describe('CommandsComponent', () => {
], ],
providers: [ providers: [
{ provide: MatDialogRef, useValue: {} }, { provide: MatDialogRef, useValue: {} },
{ provide: MAT_DIALOG_DATA, useValue: {} } { provide: MAT_DIALOG_DATA, useValue: {} },
{ provide: ConfigService, useValue: mockConfigService }
] ]
}).compileComponents(); }).compileComponents();

View File

@ -7,7 +7,7 @@ import { CreateCommandComponent } from './create-command/create-command.componen
import { DeleteModalComponent } from '../../../shared/delete_modal/delete-modal/delete-modal.component'; import { DeleteModalComponent } from '../../../shared/delete_modal/delete-modal/delete-modal.component';
import { MatTableDataSource } from '@angular/material/table'; import { MatTableDataSource } from '@angular/material/table';
import { DatePipe } from '@angular/common'; import { DatePipe } from '@angular/common';
import { ExecuteCommandComponent } from './execute-command/execute-command.component'; import { ConfigService } from '@services/config.service';
import { JoyrideService } from 'ngx-joyride'; import { JoyrideService } from 'ngx-joyride';
@Component({ @Component({
@ -16,7 +16,8 @@ import { JoyrideService } from 'ngx-joyride';
styleUrls: ['./commands.component.css'] styleUrls: ['./commands.component.css']
}) })
export class CommandsComponent implements OnInit { export class CommandsComponent implements OnInit {
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL; baseUrl: string;
private apiUrl: string;
dataSource = new MatTableDataSource<any>(); dataSource = new MatTableDataSource<any>();
filters: { [key: string]: string | boolean } = {}; filters: { [key: string]: string | boolean } = {};
length: number = 0; length: number = 0;
@ -48,10 +49,12 @@ export class CommandsComponent implements OnInit {
} }
]; ];
displayedColumns = [...this.columns.map(column => column.columnDef), 'actions']; 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 joyrideService: JoyrideService, private configService: ConfigService) {
this.baseUrl = this.configService.apiUrl;
this.apiUrl = `${this.baseUrl}/commands`;
}
ngOnInit(): void { ngOnInit(): void {
this.search(); this.search();
@ -82,14 +85,14 @@ export class CommandsComponent implements OnInit {
openCreateCommandModal(): void { openCreateCommandModal(): void {
this.dialog.open(CreateCommandComponent, { this.dialog.open(CreateCommandComponent, {
width: '600px', width: '800px',
}).afterClosed().subscribe(() => this.search()); }).afterClosed().subscribe(() => this.search());
} }
editCommand(event: MouseEvent, command: any): void { editCommand(event: MouseEvent, command: any): void {
event.stopPropagation(); event.stopPropagation();
this.dialog.open(CreateCommandComponent, { this.dialog.open(CreateCommandComponent, {
width: '600px', width: '800px',
data: command['@id'] data: command['@id']
}).afterClosed().subscribe(() => this.search()); }).afterClosed().subscribe(() => this.search());
} }
@ -114,19 +117,6 @@ export class CommandsComponent implements OnInit {
}); });
} }
executeCommand(event: MouseEvent, command: any): void {
this.dialog.open(ExecuteCommandComponent, {
width: '50%',
data: { commandData: command }
}).afterClosed().subscribe((result) => {
if (result) {
console.log('Comando ejecutado con éxito');
} else {
console.log('Ejecución de comando cancelada');
}
});
}
onPageChange(event: any): void { onPageChange(event: any): void {
this.page = event.pageIndex; this.page = event.pageIndex;
this.itemsPerPage = event.pageSize; this.itemsPerPage = event.pageSize;

View File

@ -7,12 +7,6 @@
.form-group { .form-group {
margin-top: 20px; margin-top: 20px;
margin-bottom: 26px;
}
.full-width {
width: 100%;
margin-bottom: 16px;
} }
.additional-form { .additional-form {
@ -58,3 +52,20 @@
cursor: pointer; 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;
}

View File

@ -13,7 +13,13 @@
<div class="checkbox-group"> <div class="checkbox-group">
<mat-checkbox formControlName="readOnly">{{ 'readOnlyLabel' | translate }}</mat-checkbox> <mat-checkbox formControlName="readOnly">{{ 'readOnlyLabel' | translate }}</mat-checkbox>
<mat-checkbox formControlName="enabled">{{ 'enabledLabel' | translate }}</mat-checkbox> <mat-checkbox formControlName="enabled">{{ 'enabledLabel' | translate }}</mat-checkbox>
<div class="checkbox-with-hint">
<mat-checkbox formControlName="parameters">{{ 'parameters' | translate }}</mat-checkbox>
<span class="hint-text">Si se selecciona esta opción los parámetros deben indicarse en el script con el símbolo &#64;.</span>
</div>
</div> </div>
<mat-form-field appearance="fill" class="full-width"> <mat-form-field appearance="fill" class="full-width">
@ -23,7 +29,7 @@
</form> </form>
</mat-dialog-content> </mat-dialog-content>
<mat-dialog-actions align="end"> <mat-dialog-actions class="action-container">
<button mat-button (click)="onCancel($event)">{{ 'buttonCancel' | translate }}</button> <button class="ordinary-button" (click)="onCancel($event)">{{ 'buttonCancel' | translate }}</button>
<button mat-button (click)="onSubmit()" cdkFocusInitial>{{ 'buttonSave' | translate }}</button> <button class="submit-button" (click)="onSubmit()" cdkFocusInitial>{{ 'buttonSave' | translate }}</button>
</mat-dialog-actions> </mat-dialog-actions>

View File

@ -13,12 +13,18 @@ import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatButtonModule } from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { ConfigService } from '@services/config.service';
describe('CreateCommandComponent', () => { describe('CreateCommandComponent', () => {
let component: CreateCommandComponent; let component: CreateCommandComponent;
let fixture: ComponentFixture<CreateCommandComponent>; let fixture: ComponentFixture<CreateCommandComponent>;
beforeEach(async () => { beforeEach(async () => {
const mockConfigService = {
apiUrl: 'http://mock-api-url',
mercureUrl: 'http://mock-mercure-url'
};
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [CreateCommandComponent], declarations: [CreateCommandComponent],
imports: [ imports: [
@ -45,7 +51,8 @@ describe('CreateCommandComponent', () => {
{ {
provide: MAT_DIALOG_DATA, provide: MAT_DIALOG_DATA,
useValue: {} useValue: {}
} },
{ provide: ConfigService, useValue: mockConfigService }
] ]
}).compileComponents(); }).compileComponents();
}); });

View File

@ -1,19 +1,19 @@
import { Component, Inject } from '@angular/core'; import {Component, Inject, OnInit} from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import { ToastrService } from 'ngx-toastr'; import { ToastrService } from 'ngx-toastr';
import {DataService} from "../data.service"; import { DataService } from "../data.service";
import { ConfigService } from "@services/config.service";
@Component({ @Component({
selector: 'app-create-command', selector: 'app-create-command',
templateUrl: './create-command.component.html', templateUrl: './create-command.component.html',
styleUrls: ['./create-command.component.css'] styleUrls: ['./create-command.component.css']
}) })
export class CreateCommandComponent { export class CreateCommandComponent implements OnInit{
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL; baseUrl: string;
createCommandForm: FormGroup<any>; createCommandForm: FormGroup<any>;
private apiUrl = `${this.baseUrl}/commands`;
commandId: string | null = null; commandId: string | null = null;
constructor( constructor(
@ -21,13 +21,16 @@ export class CreateCommandComponent {
private http: HttpClient, private http: HttpClient,
public dialogRef: MatDialogRef<CreateCommandComponent>, public dialogRef: MatDialogRef<CreateCommandComponent>,
private toastService: ToastrService, private toastService: ToastrService,
private configService: ConfigService,
private dataService: DataService, private dataService: DataService,
@Inject(MAT_DIALOG_DATA) public data: any @Inject(MAT_DIALOG_DATA) public data: any
) { ) {
this.baseUrl = this.configService.apiUrl;
this.createCommandForm = this.fb.group({ this.createCommandForm = this.fb.group({
name: ['', Validators.required], name: ['', Validators.required],
script: [''], script: [''],
readOnly: [false], readOnly: [false],
parameters: [false],
enabled: [true], enabled: [true],
comments: [''], comments: [''],
}); });
@ -42,12 +45,12 @@ export class CreateCommandComponent {
load(): void { load(): void {
this.dataService.getCommand(this.data).subscribe({ this.dataService.getCommand(this.data).subscribe({
next: (response) => { next: (response) => {
console.log(response);
this.createCommandForm = this.fb.group({ this.createCommandForm = this.fb.group({
name: [response.name, Validators.required], name: [response.name, Validators.required],
notes: [response.notes], notes: [response.notes],
script: [response.script], script: [response.script],
readOnly: [response.readOnly], readOnly: [response.readOnly],
parameters: [response.parameters],
enabled: [response.enabled], enabled: [response.enabled],
}); });
this.commandId = response['@id']; this.commandId = response['@id'];
@ -82,7 +85,6 @@ export class CreateCommandComponent {
}, },
(error) => { (error) => {
this.toastService.error(error['error']['hydra:description']); this.toastService.error(error['error']['hydra:description']);
console.error('Error al editar el comando', error);
} }
); );
} else { } else {
@ -93,7 +95,6 @@ export class CreateCommandComponent {
}, },
(error) => { (error) => {
this.toastService.error(error['error']['hydra:description']); this.toastService.error(error['error']['hydra:description']);
console.error('Error al añadir comando', error);
} }
); );
} }

View File

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

View File

@ -4,6 +4,7 @@ import { MAT_DIALOG_DATA, MatDialog, MatDialogRef } from '@angular/material/dial
import { CreateCommandComponent } from '../create-command/create-command.component'; import { CreateCommandComponent } from '../create-command/create-command.component';
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import { ToastrService } from 'ngx-toastr'; import { ToastrService } from 'ngx-toastr';
import { ConfigService } from '@services/config.service';
@Component({ @Component({
selector: 'app-command-detail', selector: 'app-command-detail',
@ -11,7 +12,7 @@ import { ToastrService } from 'ngx-toastr';
styleUrls: ['./command-detail.component.css'] styleUrls: ['./command-detail.component.css']
}) })
export class CommandDetailComponent implements OnInit { export class CommandDetailComponent implements OnInit {
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL; baseUrl: string;
form!: FormGroup; form!: FormGroup;
clients: any[] = []; clients: any[] = [];
showClientSelect = false; showClientSelect = false;
@ -20,12 +21,15 @@ export class CommandDetailComponent implements OnInit {
constructor( constructor(
private fb: FormBuilder, private fb: FormBuilder,
private configService: ConfigService,
private http: HttpClient, private http: HttpClient,
public dialogRef: MatDialogRef<CommandDetailComponent>, public dialogRef: MatDialogRef<CommandDetailComponent>,
private dialog: MatDialog, private dialog: MatDialog,
private toastService: ToastrService, private toastService: ToastrService,
@Inject(MAT_DIALOG_DATA) public data: any @Inject(MAT_DIALOG_DATA) public data: any
) { } ) {
this.baseUrl = this.configService.apiUrl;
}
ngOnInit(): void { ngOnInit(): void {
this.form = this.fb.group({ this.form = this.fb.group({
@ -52,7 +56,7 @@ export class CommandDetailComponent implements OnInit {
dialogRef.afterClosed().subscribe(result => { dialogRef.afterClosed().subscribe(result => {
if (result) { if (result) {
this.toastService.success('Comando editado' ); this.toastService.success('Comando editado');
this.data.command = result; this.data.command = result;
} }
}); });

View File

@ -1,40 +1,24 @@
<h2 mat-dialog-title>{{ 'executeCommandTitle' | translate }}</h2> <ng-container [ngSwitch]="buttonType">
<button *ngSwitchCase="'icon'" mat-icon-button color="primary" [matMenuTriggerFor]="commandMenu"
[disabled]="disabled">
<mat-icon>{{ icon }}</mat-icon>
</button>
<mat-dialog-content class="form-container"> <button class="action-button" [disabled]="clientData.length === 0 || disabled" *ngSwitchCase="'text'"
<form [formGroup]="form" class="command-form"> [matMenuTriggerFor]="commandMenu">
{{ buttonText }}
</button>
<mat-form-field appearance="fill" class="full-width"> <button mat-menu-item *ngSwitchCase="'menu-item'" [matMenuTriggerFor]="commandMenu" [disabled]="disabled">
<mat-label>{{ 'organizationalUnitLabel' | translate }}</mat-label> <mat-icon>{{ icon }}</mat-icon>
<mat-select formControlName="unit"> <span>{{ buttonText }}</span>
<mat-option *ngFor="let unit of units" [value]="unit.uuid">{{ unit.name }}</mat-option> </button>
</mat-select> </ng-container>
</mat-form-field>
<mat-form-field appearance="fill" class="full-width"> <mat-menu #commandMenu="matMenu">
<mat-label>{{ 'subOrganizationalUnitLabel' | translate }}</mat-label> <button mat-menu-item [disabled]="command.disabled
<mat-select formControlName="childUnit"> || (command.slug === 'create-image' && clientData.length > 1)" *ngFor="let command of arrayCommands"
<mat-option *ngFor="let child of childUnits" [value]="child.uuid">{{ child.name }}</mat-option> (click)="onCommandSelect(command.slug)">
</mat-select> {{ command.name }}
</mat-form-field> </button>
</mat-menu>
<div class="checkbox-group">
<label>{{ 'clientsLabel' | translate }}</label>
<div *ngIf="clients.length > 0">
<mat-checkbox *ngFor="let client of clients"
(change)="toggleClientSelection(client.uuid)"
[checked]="form.get('clientSelection')?.value.includes(client.uuid)">
{{ client.name }}
</mat-checkbox>
</div>
<div *ngIf="clients.length === 0">
<p>{{ 'noClientsAvailable' | translate }}</p>
</div>
</div>
</form>
</mat-dialog-content>
<mat-dialog-actions align="end">
<button mat-button (click)="closeModal()">{{ 'buttonCancel' | translate }}</button>
<button mat-button (click)="executeCommand()" [disabled]="!form.get('clientSelection')?.value.length">{{ 'buttonExecute' | translate }}</button>
</mat-dialog-actions>

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