Compare commits

...

382 Commits

Author SHA1 Message Date
Manuel Aranda Rosales ff9ea3d1f1 Merge pull request 'develop' (#32) from develop into main
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
Reviewed-on: #32
2025-06-27 13:54:53 +02:00
Manuel Aranda Rosales d6a092ce75 Merge branch 'main' into develop
testing/ogGui-multibranch/pipeline/pr-main Build queued... Details
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-06-27 13:54:28 +02:00
Manuel Aranda Rosales 483168146b Added changelog 2025-06-27 13:54:04 +02:00
Manuel Aranda Rosales 2cc10615a1 Merge pull request 'develop' (#31) 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: #31
2025-06-27 12:46:30 +02:00
Manuel Aranda Rosales f16581e4ed Merge branch 'main' into develop
testing/ogGui-multibranch/pipeline/pr-main Build queued... Details
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-06-27 12:43:55 +02:00
Manuel Aranda Rosales f2e4f5d081 Added logs real time ogLive 2025-06-27 12:43:27 +02:00
Manuel Aranda Rosales 24f45e6ba6 Merge pull request 'develop' (#30) from develop into main
oggui-debian-package/pipeline/head This commit looks good Details
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
Reviewed-on: #30
2025-06-27 10:03:27 +02:00
Manuel Aranda Rosales c2c5bb68be 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-06-27 10:03:12 +02:00
Manuel Aranda Rosales 01390a1fab Updated trace filters and added logs in real time
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-06-27 09:57:34 +02:00
Manuel Aranda Rosales 642a439f21 Updated trace and fixed status global 2025-06-27 09:36:56 +02:00
Manuel Aranda Rosales d11d3f2d75 Merge pull request 'Updated maximum file size' (#29) from develop into main
oggui-debian-package/pipeline/head This commit looks good Details
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
Reviewed-on: #29
2025-06-27 08:17:02 +02:00
Manuel Aranda Rosales 0403385421 Updated maximum file size
testing/ogGui-multibranch/pipeline/pr-main Build queued... Details
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-06-27 08:16:09 +02:00
Manuel Aranda Rosales e024c7a246 Merge pull request 'develop' (#28) from develop into main
oggui-debian-package/pipeline/head There was a failure building this commit Details
testing/ogGui-multibranch/pipeline/head This commit looks good Details
oggui-debian-package/pipeline/tag There was a failure building this commit Details
Reviewed-on: #28
2025-06-26 16:23:03 +02:00
Manuel Aranda Rosales ef1316c166 Merge branch 'main' into develop
testing/ogGui-multibranch/pipeline/pr-main There was a failure building this commit Details
2025-06-26 16:22:40 +02:00
Manuel Aranda Rosales a3d60a55df Added changelog
testing/ogGui-multibranch/pipeline/head This commit looks good Details
testing/ogGui-multibranch/pipeline/pr-main There was a failure building this commit Details
2025-06-26 16:20:18 +02:00
Manuel Aranda Rosales 3a52ebee39 Fixed conflicts
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-06-26 16:05:23 +02:00
Manuel Aranda Rosales f528404725 refs #2338. Kill job constants
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-06-26 15:59:40 +02:00
Manuel Aranda Rosales ce00b92751 refs #2339. Assistants new UX
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-06-26 15:58:56 +02:00
Manuel Aranda Rosales 212c4f9eec refs #2252. Queue actions
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-06-26 15:57:04 +02:00
Manuel Aranda Rosales 90d969ccd3 refs 2335. Groups new UX
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-06-26 15:55:19 +02:00
Manuel Aranda Rosales d526bb851a refs 2336. Traces new UX
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-06-26 15:53:40 +02:00
Manuel Aranda Rosales 537a220fc4 refs 2334. Scroll to top button 2025-06-26 15:50:42 +02:00
Manuel Aranda Rosales 3e8f8cc3db refs 2337. Global status new UX
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-06-26 15:49:44 +02:00
Manuel Aranda Rosales 5b8dba4835 refs #1984. Integration ogGit. Crete and deploy Image. Show git images in repository
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-06-26 15:47:44 +02:00
Manuel Aranda Rosales a1c2fb7c2e refs #1984. Integration ogGit. Crete and deploy Image. Show git images in repository
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-06-26 15:45:37 +02:00
Manuel Aranda Rosales c290c94675 Merge pull request 'develop' (#27) from develop into main
oggui-debian-package/pipeline/tag This commit looks good Details
oggui-debian-package/pipeline/head This commit looks good Details
Reviewed-on: #27
2025-06-09 12:25:12 +02:00
Manuel Aranda Rosales 9216137dee Merge branch 'main' into develop 2025-06-09 12:24:14 +02:00
Manuel Aranda Rosales 15b37d9158 Fixed test 2025-06-09 12:23:07 +02:00
Manuel Aranda Rosales b6c00e12d8 Fixed test 2025-06-09 12:21:20 +02:00
Manuel Aranda Rosales b4ba0b1244 refs #2182. Changed Output messages 2025-06-09 12:05:35 +02:00
Lucas Lara García 06d0a83aab refs #2169 Refactor legend component to enhance client status display and add translations for client states 2025-06-04 13:13:30 +02:00
Lucas Lara García 3bc07b56cd refs #2171 Add LogoutGuard and update AuthService logout method to support redirect options 2025-06-04 10:07:18 +02:00
Lucas Lara García e964c6b47a refs #2148 Refactor dialog components to use <mat-dialog-content> and <mat-dialog-actions> for better responsiveness 2025-06-03 10:58:41 +02:00
Lucas Lara García 36f709f7c1 Hide edit user button for super-admin 2025-06-02 10:59:00 +02:00
Manuel Aranda Rosales f840272e0d Merge pull request 'develop' (#25) from develop into main
oggui-debian-package/pipeline/tag There was a failure building this commit Details
oggui-debian-package/pipeline/head There was a failure building this commit Details
Reviewed-on: #25
2025-06-02 08:38:22 +02:00
Manuel Aranda Rosales a8817bf49a Solve conflics 2025-06-02 08:37:50 +02:00
Manuel Aranda Rosales 6c40d78f15 solve conflicts 2025-06-02 08:34:39 +02:00
Manuel Aranda Rosales c2f3ea3caa Added changelog 2025-06-02 08:32:26 +02:00
Manuel Aranda Rosales 62001e4a44 Fixed test 2025-06-02 08:29:21 +02:00
Manuel Aranda Rosales 6c7951be31 refs #2118. Fixed bug in manage-repo form 2025-06-02 08:09:59 +02:00
Manuel Aranda Rosales f94d522420 refs #2158. Added script to existint command-task 2025-06-02 08:09:20 +02:00
Lucas Lara García 8882fd40a5 test: enhance ChangeParentComponent tests with necessary imports and providers 2025-05-30 09:31:56 +02:00
Manuel Aranda Rosales 4e23723717 refs #2098. Move clients to different OU
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-05-29 16:00:53 +02:00
Manuel Aranda Rosales 1ce50857ac refs #2098. Move clients to different OU 2025-05-29 15:31:27 +02:00
Lucas Lara García 3cd46e1166 feat: implement page not found component with styled layout
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-05-29 11:58:47 +02:00
Lucas Lara García 7137768939 refactor: remove Admin and Dashboard components; implement role-based routing and guards
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-05-29 11:51:20 +02:00
Lucas Lara García 79188ffbc5 refs #2078 and #2079 Add user category checks for client management actions in groups component
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-05-28 12:15:58 +02:00
Lucas Lara García 84fd9d0335 refs #2078 and #2079 Implement role-based command filtering and UI adjustments for user permissions
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-05-27 14:14:01 +02:00
Lucas Lara García a26f2481fa fix: update BootSoPartition and RemoveCacheImage component tests with new dependencies and mock data
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-05-26 13:10:51 +02:00
Manuel Aranda Rosales a6ff85139b Removed temp. ogGit
oggui-debian-package/pipeline/head There was a failure building this commit Details
oggui-debian-package/pipeline/tag There was a failure building this commit Details
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-05-23 14:14:12 +02:00
Lucas Lara García c7ed41a1a5 refs #2089 and #2091 Implement AuthService for user authentication and role management
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-05-23 13:56:07 +02:00
Manuel Aranda Rosales 4e6dbde59c 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-05-23 13:28:33 +02:00
Manuel Aranda Rosales fcfcd58d93 refs #2085. RemoveCacheImage 2025-05-23 13:28:22 +02:00
Manuel Aranda Rosales 98b4d3c4f0 refs #2074. BootOs 2025-05-23 13:27:43 +02:00
Lucas Lara García faf3fa0613 fix: reset pagination on filter change in ClientTaskLogsComponent
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-05-22 09:28:05 +02:00
Lucas Lara García 11b1c26f50 Text fixed: add MatInputModule to ClientTaskLogsComponent test setup
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-05-21 09:58:56 +02:00
Lucas Lara García cd45009751 refs #2049 Improve date validation and update parameter naming in task logs
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-05-20 16:20:05 +02:00
Lucas Lara García b55f15f16b refs #2049 enhance date filtering in task logs with max date constraint and validation
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-05-20 14:29:11 +02:00
Lucas Lara García c824953b1e refactor: add date filters to task logs component and improve validation
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-05-20 13:59:10 +02:00
Lucas Lara García 9d21a6fefc refactor: streamline trace loading parameters and enhance logging
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-05-20 13:51:17 +02:00
Lucas Lara García a84372214b refactor: simplify progress container structure and enhance trace status display
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-05-20 13:26:35 +02:00
Lucas Lara García 4c8b6c7dbd refactor: update task logs table to improve trace information display and localization
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-05-20 13:17:52 +02:00
Manuel Aranda Rosales d83f0148fb Merge pull request 'develop' (#23) from develop into main
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
Reviewed-on: #23
2025-05-19 16:58:23 +02:00
Manuel Aranda Rosales 3d2f9dfa5e Merge branch 'main' into develop
testing/ogGui-multibranch/pipeline/pr-main Build queued... Details
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-05-19 16:58:05 +02:00
Manuel Aranda Rosales 6433adbecd Added changelog
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-05-19 16:57:07 +02:00
Manuel Aranda Rosales 35bb0e6e62 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-05-19 16:19:43 +02:00
Manuel Aranda Rosales 76835be2a2 Improvements 2025-05-19 16:19:37 +02:00
Lucas Lara García a6ce1ee4cf fix: update command filter to use ID or UUID for selection
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-05-19 13:32:00 +02:00
Lucas Lara García a57675631f refs #2040 update joyride steps and translations for task logs component
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-05-19 13:26:31 +02:00
Manuel Aranda Rosales 9fcfeb7f4e 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-05-19 08:13:00 +02:00
Manuel Aranda Rosales f70dcd109a Assistants improvements 2025-05-19 08:12:53 +02:00
Manuel Aranda Rosales 2f202e5037 refs #2031. New config tls var 2025-05-19 08:12:07 +02:00
Lucas Lara García b0d255a7d1 refactor: prevent default setting of organizational unit when not in edit mode and streamline data loading logic
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-05-16 13:46:15 +02:00
Lucas Lara García 4ff21afb57 refactor: conditionally update network settings based on excludeParentChanges flag
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-05-16 12:59:03 +02:00
Lucas Lara García 639f55edb3 refactor: update command filter to use name instead of id and remove date range handling 2025-05-14 10:07:19 +02:00
Nicolas Arenas f41cbf2cef Adding oggui postinst debug
oggui-debian-package/pipeline/head This commit looks good Details
2025-05-14 06:12:46 +02:00
Nicolas Arenas b82f139c9d Adding oggui postinst debug
oggui-debian-package/pipeline/head This commit looks good Details
2025-05-14 06:04:51 +02:00
Manuel Aranda Rosales bad345c2fd Merge pull request 'develop' (#22) from develop into main
oggui-debian-package/pipeline/head This commit looks good Details
oggui-debian-package/pipeline/tag This commit looks good Details
Reviewed-on: #22
2025-05-13 08:37:13 +02:00
Manuel Aranda Rosales 56a8c563ff Merge branch 'main' into develop 2025-05-13 08:36:28 +02:00
Manuel Aranda Rosales 14986ce524 Added changelog 2025-05-13 08:35:43 +02:00
Manuel Aranda Rosales 15be37a4d0 refs #1984. Added partitionInfo to list 2025-05-13 08:26:58 +02:00
Manuel Aranda Rosales 1c1f75811c refs #1984. Git integration UX changes 2025-05-12 22:44:55 +02:00
Manuel Aranda Rosales e3adf08bd9 refs #1984. Git integration UX changes 2025-05-12 16:28:48 +02:00
Manuel Aranda Rosales 114483410b refs #1827. UX changes. New calendar styles 2025-05-12 16:26:35 +02:00
Lucas Lara García b65d715443 Refactor command filter to use command ID instead of name in ClientTaskLogs component
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-05-09 14:27:42 +02:00
Lucas Lara García 53d1191ddf Erefs #1963 nhance ClientTaskLogs component to include date range filtering in trace loading and reset functionality
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-05-09 13:52:28 +02:00
Lucas Lara García 600d5f9b37 refs #1963 Enhance loadTraces method to include command and status filters in HTTP parameters
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-05-09 13:41:00 +02:00
Lucas Lara García d10a209a25 Enhance unit tests for ClientTaskLogs and OutputDialog components by adding necessary imports, providers, and schemas
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-05-09 13:07:19 +02:00
Manuel Aranda Rosales 757de78dc4 refs #1969. Partition changes
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-05-08 17:09:56 +02:00
Manuel Aranda Rosales 7ea5013cf4 refs #1972. General styles changed. Show image details
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-05-08 16:33:22 +02:00
Lucas Lara García 5777be9417 Enhance resetFilters method to include client input parameter and update template button accordingly
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-05-08 14:13:11 +02:00
Lucas Lara García 28336603ad Refactor resetFilters method to accept input parameters for command and status filters
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-05-08 14:11:45 +02:00
Lucas Lara García 60684d2c50 Implement ClientTaskLogs component with enhanced UI and functionality
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-05-08 13:59:24 +02:00
Lucas Lara García 1ba62b9283 Add ClientTaskLogs component and integrate with Groups component
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
- Introduced ClientTaskLogsComponent to display client task logs.
- Updated GroupsComponent to include a button for opening client task logs.
- Added translations for 'procedimientosCliente' in both English and Spanish locales.
- Created associated HTML, CSS, and spec files for ClientTaskLogsComponent.
2025-05-08 13:02:38 +02:00
Manuel Aranda Rosales 242f7a374c refs #1972. General styles changed. New 2025
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-05-08 12:56:14 +02:00
Manuel Aranda Rosales a5730a1de4 refs #1971. Trace UX changes. Some improvements and fixed filters 2025-05-08 12:54:58 +02:00
Manuel Aranda Rosales 81766471ee Fixed wrong dir
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-05-08 09:06:20 +02:00
Manuel Aranda Rosales 9a84e45cb8 Pushed app.module 2025-05-08 09:05:56 +02:00
Manuel Aranda Rosales ef3158a045 refs #1922. Show new fields in clients table.
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-05-08 08:06:57 +02:00
Manuel Aranda Rosales 9ba8c1771d refs #1906. Updated UX to CommandTask
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-05-08 08:03:45 +02:00
Manuel Aranda Rosales 59e8c3842e Merge branch 'develop' of ssh://ognproject.evlt.uma.es:21987/opengnsys/oggui into develop 2025-05-08 08:01:38 +02:00
Manuel Aranda Rosales fc51481977 refs #1968. Pxe some UX improvements 2025-05-08 08:01:14 +02:00
Lucas Lara García f35ba106ba refs #1953 Refactor manage organizational unit component: replace hardcoded strings with translation keys and update localization files
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-05-07 13:45:40 +02:00
Lucas Lara García 2dce55ce1d Refactor legend component: update remote access titles to use translation keys and enhance localization files
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-05-07 12:19:00 +02:00
Lucas Lara García bdd6206e18 Refactor execute command component: replace command names with translation keys and update localization files for execute commands
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-05-07 11:58:51 +02:00
Lucas Lara García a3f99958a3 Refactor groups component: update tour steps, improve button layout, and enhance translation keys
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-05-06 14:09:12 +02:00
Lucas Lara García 8cc1854d09 Refactor partition type display: simplify client name presentation and improve table structure
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-05-02 13:34:02 +02:00
Lucas Lara García f6dbd6dad9 refs #1941 Refactor partition type organizer: enhance data structure and improve client grouping logic
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-05-02 13:24:06 +02:00
Manuel Aranda Rosales 35512ce65f General improvements
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-04-30 13:25:37 +02:00
Manuel Aranda Rosales 1e05176758 refs #1827. UX changes. New calendar styles
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-04-30 13:17:16 +02:00
Manuel Aranda Rosales 83ab784c48 refs #1922. Show new fields in clients table. Fixed wrong details 2025-04-30 13:16:18 +02:00
Manuel Aranda Rosales bf100943a0 refs #1906. Updated UX to CommandTask
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-04-30 13:15:06 +02:00
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
Manuel Aranda Rosales 1df6578efd Merge branch 'develop' of ssh://ognproject.evlt.uma.es:21987/opengnsys/oggui into develop 2025-04-29 14:15:31 +02:00
Lucas Lara García ed073deb5d Refactor runScriptContext handling in ExecuteCommandComponent and RunScriptAssistantComponent for improved data management; update related HTML to reflect changes.
testing/ogGui-multibranch/pipeline/head This commit looks good Details
oggui-debian-package/pipeline/head Something is wrong with the build of this commit Details
2025-04-29 14:05:43 +02:00
Manuel Aranda Rosales d47eaf49a7 Merge branch 'develop' of ssh://ognproject.evlt.uma.es:21987/opengnsys/oggui into develop 2025-04-28 08:20:16 +02:00
Lucas Lara García 34ea12fc50 Translate table headers in PartitionTypeOrganizatorComponent to Spanish for better localization
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-04-25 14:23:39 +02:00
Lucas Lara García 6fd04fb46e refs #1889 Add PartitionTypeOrganizatorComponent: implement partition management modal and integrate into GroupsComponent
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-04-25 14:02:15 +02:00
Lucas Lara García 2bc17e8b56 Refactor updateCommandStates method in ExecuteCommandComponent: restore and optimize command state management logic for improved functionality
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-04-24 14:09:54 +02:00
Lucas Lara García 9582ce338c refs #1928 Add runScriptContext input to ExecuteCommandComponent and update related components for improved script execution context handling
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
2025-04-24 14:08:42 +02:00
Lucas Lara García e8b713ea09 refs #1931 Refactor table styles in ClientDetailsComponent: remove border-radius and box-shadow for a cleaner look
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-04-23 15:03:59 +02:00
Lucas Lara García 256b3ba788 refs #1931 Enhance ClientDetailsComponent layout and styling: increase dialog width, adjust chart dimensions, and improve disk layout for better data presentation
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-04-23 14:56:17 +02:00
Lucas Lara García f016c66d55 refs #1931 Refactor ClientMainViewComponent and related files: remove component and styles, update routing, and adjust dialog dimensions for improved client detail display
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-04-23 14:22:12 +02:00
Lucas Lara García 70e21c6ca2 refs #1931 Refactor ClientDetailsComponent to enhance layout and improve data handling in client details view
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-04-23 13:51:45 +02:00
Lucas Lara García a40be684b5 refs #1931 Load partitions data and log relevant information for debugging
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-04-23 13:05:33 +02:00
Lucas Lara García ca0140e275 refs #1931 Add ClientDetailsComponent for enhanced client information display and management
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-04-23 12:45:32 +02:00
Lucas Lara García 88aaf39b65 refs #1884 Update command enabling logic to include 'create-image' for active clients
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-04-22 13:30:01 +02:00
Lucas Lara García a418d26615 refs #1884 Refactor command state handling to improve client status management and enable/disable commands based on client states
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-04-22 13:19:22 +02:00
Lucas Lara García 6621f7a8fe refs #1884 Enhance command execution logic to handle 'disconnected' state and update UI for multiple clients
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-04-22 12:59:49 +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
Lucas Lara García 558a3205ab refs #1884 Add clientState input to execute-command component for dynamic state handling
testing/ogGui-multibranch/pipeline/head This commit looks good Details
2025-04-21 14:56:09 +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
464 changed files with 29091 additions and 8663 deletions

7
.gitignore vendored
View File

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

View File

@ -1,20 +1,165 @@
# Changelog
## [0.16.0] - 2025-06-27
### Added
- Sistema de logs en tiempo real.
### Improved
- Se ha mejorado el comportamiento de algunos filtros en la parte de trazas.
---
## [0.15.0] - 2025-06-26
### Added
- Se ha añadido integracion con OgGit. Ahora se pueden crear y desplegar imagenes.
- Ahora se pueden gestionar cola de acciones y vaciarlas tanto a nivel de aula como de cliente.
- Nuevos componentes que ayudan a la mejora general de la UX
- Mejora en el comportamiento de los asistentes.
- Se puede cancelar tareas desde la parte de Trazas
### Changed
- Se ha cambiado la vista de las imagenes de Git
---
## [0.14.1] - 2025-06-09
### Fixed
- Se han corregido los errores en produccion que hacia que no salieran mensajes desde la API correctamente.
---
## [0.14.0] - 2025-06-02
### Added
- Se ha añadido funcionalidad de usuarios/roles para separar las vistas segun los permisos.
- Nuevo boton de "mover" en la pantalla de grupos, para mover equipos entre aulas y grupos.
### Improved
- Se ha completado la opcion de inicion de sesion y eliminar imagen cache.
---
## [0.13.1] - 2025-05-23
### Changed
- Desactivado temporalmente la funcionalidad de ogGit.
---
## [0.13.0] - 2025-05-20
### Added
- Se ha añadido nuevo campo "SSL_ENABLED" en el apartado configuracion.
### Improved
- Mejoras en el asistente de particionado.
- Se han añadido mejoras en los filtros de las trazas.
---
## [0.12.0] - 2025-5-13
### Added
- Se ha añadido un nuevo modal del detalle de las acciones ejecutadas por cada cliente.
- Se ha añadido un modulo para la gestion de las tareas y acciones programadas.
- Se han añadido nuevos campos en el listado general de clientes.
### Improved
- Se ha cambiado la pagina de detalles de un cliente, por un modal.
- Se han actualizado gran parte de las ayudas contextuales de las distintas parrillas de datos.
- Se ha mejorado y corregido los errores del particionador.
- Mejoras en la pantalla de trazas.
- Cambios en la estetica general de la aplicacion
- Añadida la primera version de la la integracion con ogGit
- Se ha mejorado la responsividad de la aplicacion, para pantallas pequeñas.
### Fixed
- Se ha corregido un error que hacia que no apareciesen los calendarios en la pantalla de editar OU.
- En la pantalla de hacer deploy, al seleccionar imagen ahora deja desmarcarla.
## [0.11.2] - 2025-4-16
### Fixed
- Se ha corregido un error en la actualizacion del estado de los pcs en la vista tarjetas.
---
## [0.11.1] - 2025-4-16
### Improved
- Nuevos campos en la tabla de clientes. Tipo de firmware y mac.
## Fixed
- Se ha corregido error al crear OUs, que no refrescaba la web.
- Se ha corregido error en el formulario de creacion de imagenes. Si se seleccionaba una imagen para un versionado, no dejaba deseleccionar.
- Se ha corregido un bug en el particionador que impedia ejecutar, cuando eliminabamos una particion.
---
## [0.11.0] - 2025-4-11
### Added
- Se ha diseñado el nuevo formulario para poder ejecutar script. Sistema mejorado con variables etiquetadas.
- Se puede añadir descripcion a una imagen.
- Se han añadido al formulario de crear/editar repositorio, la posibilidad de añadir usuario y puerto ssh.
- Nuevo estado en pc => desconectado.
- Se ha añadido nueva accion para renombrar imagen monolitica.
### Improved
- Se ha mejorado la interfaz de usuario tanto para el despliegue de imagenes, como el particionado.
- Se ha mejorado la responsividad de la vista de grupos.
- Cambios en el comportamiento general de muchos componentes modales. Se han añadido spinners de carga mas intuitivos.
---
## [0.10.1] - 2025-3-27
### Improved
- Mejoras en el comportamiento del arbol de grupos.
- Nueva regexp para controlar las "macs" en la creacion de clientes.
---
## [0.10.0] - 2025-3-25
### Added
- Nuevo componenten de estado global.
- Servicio para que el ogGui obtenga de forma dinamica las variables de entorno.
- Nueva funcionalidad para convertir imagen en imagen virtual.
- Nueva funcionalidad para importar imágenes externas al sistema.
- Despliegue de imangenes sin cache. Cambios en el formulario de "despliegue".
### Improved
- Mejoras en la internacionalización.
- Nueva UX ogRepository. Ahora se gestionan las imagenes de forma mas sencilla.
- Cambios en ogLive. Mejora en la sincronizacion y obtención de datos en la API
### Fixed
- Cambios en la expresion regular para la validacion de documentos DHCP en la carga masiva de pc.
---
## [0.9.2] - 2025-03-19
### Changed
- Jenkinsfile to pubilsh packages in repo in case og release
---
## [0.9.1] - 2025-03-12
### Changed
- Se ha modificado el acceso a Mercure añadiendo nueva variable de entorno.
---
## [0.9.0] - 2025-3-4
### Added
- Integracion con Mercure. Subscriber tanto en "Trazas" con en "Clientes".
- Nueva funcionalidad para checkear la integridad de una imagen. Boton en apartado "imagenes" dentro del repositorio.
- Centralizacion de estilos.
- Nueva funcionalidad para realizar backup de imágenes.
- Botón para cancelar despliegues de imagenes. Aparece en "trazas" tan solo para los comendos "deploy" y para el estado "en progreso".
### Changed
- Nueva interfaz en "Grupos". Se ha aprovechado mejor el espacio y acortado el tamaño de las filas, para poder tener mas elementos por pantalla.
- Cambios en filtros de "Grupos". Ahora se pueden filtrar por "Centro" y "Unidad Organizativa" y estado. Ahora se busca en base de datos, y no en una lista de clientes dados.
- Refactorizados compontentes de crear/editar clientes en uno solo.
- Cambios en DHCP. Nueva UX en "ver clientes". Ahora tenemos un buscador detallado.
- Para gestionar/añadir clientes a subredes ahora tenemos un botón para "añadir todos" y tan solo nos aparecn los equipos que no estén previamente asignados en una subred.
---
## [0.7.0] - 2024-12-10
### Refactored
- Refactored the group screen, removing the separate tabs for clients, advanced search, and organizational units.
- Added support for partitioning functionality in the client detail view.
---
## [0.6.1] - 2024-11-19
### Improved
- Introduced a new automatic sync mode for the ogdhcp and ogBoot components.
- Improve test coverage.
- New view for clients inside the classroom on the main page.
---
## [0.6.0] - 2024-11-19
### Added
- Added functionality to execute actions from the menu in the general groups screen.
- Displayed the selected center on the general screen for better context.

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/

58
debian/oggui.postinst vendored 100644
View File

@ -0,0 +1,58 @@
#!/bin/bash
set -e
set -x
. /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
jq --arg apiUrl "$OGCORE_URL" --arg mercureUrl "$OGMERCURE_URL" '.apiUrl = $apiUrl | .mercureUrl = $mercureUrl' "$CONFIG_FILE" > "${CONFIG_FILE}.tmp" && mv "${CONFIG_FILE}.tmp" "$CONFIG_FILE"
ln -s /opt/opengnsys/oggui/etc/nginx/oggui.conf /etc/nginx/sites-enabled/oggui.conf
ln -s $CONFIG_FILE /opt/opengnsys/oggui/etc/config.json
mkdir -p /etc/nginx/certs/
cp -p /opt/opengnsys/oggui/etc/nginx/certs/* /etc/nginx/certs/
chown -R www-data:www-data /etc/nginx/certs
systemctl daemon-reload
systemctl restart nginx
elif [ "$1" = "configure" ] && [ -n "$2" ]; then
cd /opt/opengnsys/oggui
echo "Actualización desde la versión $2"
# Si upgrade recupero los archivos de configuracion
echo ">>> Backup de archivos de configuración reales en /opt/opengnsys"
restore_config_if_modified "/opt/opengnsys/oggui/etc/nginx/oggui.conf"
restore_config_if_modified "$CONFIG_FILE"
fi
# Cambiar la propiedad de los archivos al usuario especificado
chown opengnsys:www-data /opt/opengnsys/
chown -R opengnsys:www-data /opt/opengnsys/oggui
exit 0

32
debian/oggui.postrm vendored 100755
View File

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

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

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

32
debian/oggui.preinst vendored 100644
View File

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

13
debian/oggui.prerm vendored 100644
View File

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

9
debian/oggui.templates vendored 100644
View File

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

11
debian/rules vendored 100755
View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -4,12 +4,6 @@
"newProjectRoot": "projects",
"projects": {
"ogWebconsole": {
"i18n": {
"sourceLocale": "es",
"locales": {
"en-US": "src/locale/en.json"
}
},
"projectType": "application",
"schematics": {
"@schematics/angular:component": {
@ -30,7 +24,7 @@
"builder": "@ngx-env/builder:application",
"options": {
"baseHref": "/oggui/",
"localize": true,
"localize": false,
"aot": true,
"outputPath": "dist/og-webconsole",
"index": "src/index.html",
@ -41,20 +35,23 @@
],
"tsConfig": "tsconfig.app.json",
"assets": [
"src/favicon.ico",
"src/assets",
{
"glob": "**/*",
"input": "src/locale",
"output": "/locale"
}
],
"src/favicon.ico",
"src/assets",
{
"glob": "**/*",
"input": "src/locale",
"output": "/locale"
}
],
"styles": [
"src/custom-theme.scss",
"src/styles.css",
"node_modules/ngx-toastr/toastr.css"
],
"scripts": []
"scripts": [],
"allowedCommonJsDependencies": [
"rfdc"
]
},
"configurations": {
"production": {
@ -66,8 +63,8 @@
},
{
"type": "anyComponentStyle",
"maximumWarning": "2kb",
"maximumError": "4kb"
"maximumWarning": "35kb",
"maximumError": "40kb"
}
],
"outputHashing": "all"
@ -76,16 +73,6 @@
"optimization": false,
"extractLicenses": false,
"sourceMap": false
},
"es": {
"localize": [
"es-ES"
]
},
"en": {
"localize": [
"en-US"
]
}
},
"defaultConfiguration": "production"
@ -104,29 +91,16 @@
},
"development": {
"buildTarget": "ogWebconsole:build:development"
},
"es": {
"buildTarget": "ogWebconsole:build:es"
},
"en": {
"buildTarget": "ogWebconsole:build:en"
}
},
"defaultConfiguration": "development"
},
"extract-i18n": {
"builder": "@ngx-env/builder:extract-i18n",
"options": {
"buildTarget": "ogWebconsole:build"
}
},
"test": {
"builder": "@ngx-env/builder:karma",
"options": {
"polyfills": [
"zone.js",
"zone.js/testing",
"@angular/localize/init"
"zone.js/testing"
],
"tsConfig": "tsconfig.spec.json",
"assets": [
@ -146,4 +120,4 @@
"cli": {
"analytics": "95fac95c-8936-41a8-8c9c-1fae82fe6912"
}
}
}

View File

@ -3,35 +3,28 @@ import { RouterModule, Routes } from '@angular/router';
import { MainLayoutComponent } from './layout/main-layout/main-layout.component';
import { AuthLayoutComponent } from './layout/auth-layout/auth-layout.component';
import { LoginComponent } from './components/login/login.component';
import { DashboardComponent } from './components/dashboard/dashboard.component';
import { PageNotFoundComponent } from './shared/page-not-found/page-not-found.component';
import { AdminComponent } from './components/admin/admin.component';
import { UsersComponent } from './components/admin/users/users/users.component';
import { RolesComponent } from './components/admin/roles/roles/roles.component';
import { GroupsComponent } from './components/groups/groups.component';
import { PXEimagesComponent } from './components/ogboot/pxe-images/pxe-images.component';
import { PxeComponent } from './components/ogboot/pxe/pxe.component';
import { PxeBootFilesComponent } from './components/ogboot/pxe-boot-files/pxe-boot-files.component';
import {OgbootStatusComponent} from "./components/ogboot/ogboot-status/ogboot-status.component";
import { OgdhcpComponent } from './components/ogdhcp/ogdhcp.component';
import { OgDhcpSubnetsComponent } from './components/ogdhcp/og-dhcp-subnets/og-dhcp-subnets.component';
import { OgbootStatusComponent } from "./components/ogboot/ogboot-status/ogboot-status.component";
import { CalendarComponent } from "./components/calendar/calendar.component";
import { CommandsComponent } from './components/commands/main-commands/commands.component';
import { CommandsGroupsComponent } from './components/commands/commands-groups/commands-groups.component';
import { CommandsTaskComponent } from './components/commands/commands-task/commands-task.component';
import { TaskLogsComponent } from './components/commands/commands-task/task-logs/task-logs.component';
import { StatusComponent } from "./components/ogdhcp/og-dhcp-subnets/status/status.component";
import { ClientMainViewComponent } from './components/groups/components/client-main-view/client-main-view.component';
import { ImagesComponent } from './components/images/images.component';
import {SoftwareComponent} from "./components/software/software.component";
import {SoftwareProfileComponent} from "./components/software-profile/software-profile.component";
import {OperativeSystemComponent} from "./components/operative-system/operative-system.component";
import { TaskLogsComponent } from './components/task-logs/task-logs.component';
import { SoftwareComponent } from "./components/software/software.component";
import { SoftwareProfileComponent } from "./components/software-profile/software-profile.component";
import { OperativeSystemComponent } from "./components/operative-system/operative-system.component";
import {
PartitionAssistantComponent
} from "./components/groups/components/client-main-view/partition-assistant/partition-assistant.component";
import {RepositoriesComponent} from "./components/repositories/repositories.component";
import { RepositoriesComponent } from "./components/repositories/repositories.component";
import {
CreateImageComponent
CreateClientImageComponent
} from "./components/groups/components/client-main-view/create-image/create-image.component";
import {
DeployImageComponent
@ -39,48 +32,52 @@ import {
import {
MainRepositoryViewComponent
} from "./components/repositories/main-repository-view/main-repository-view.component";
import {EnvVarsComponent} from "./components/admin/env-vars/env-vars.component";
import {MenusComponent} from "./components/menus/menus.component";
import { 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";
import { roleGuard } from './guards/role.guard';
import { LogoutGuard } from './guards/logout.guard';
const routes: Routes = [
{ path: '', redirectTo: 'auth/login', pathMatch: 'full' },
{ path: '', component: MainLayoutComponent,
{
path: '', component: MainLayoutComponent,
children: [
{ path: 'dashboard', component: DashboardComponent },
{ path: 'admin', component: AdminComponent },
{ path: 'users', component: UsersComponent },
{ path: 'env-vars', component: EnvVarsComponent },
{ path: 'user-groups', component: RolesComponent },
{ path: 'users', component: UsersComponent, canActivate: [roleGuard], data: { allowedRoles: ['super-admin'] } },
{ path: 'env-vars', component: EnvVarsComponent, canActivate: [roleGuard], data: { allowedRoles: ['super-admin'] } },
{ path: 'roles', component: RolesComponent, canActivate: [roleGuard], data: { allowedRoles: ['super-admin'] } },
{ path: 'groups', component: GroupsComponent },
{ path: 'pxe-images', component: PXEimagesComponent },
{ path: 'pxe', component: PxeComponent },
{ path: 'pxe-boot-file', component: PxeBootFilesComponent },
{ path: 'ogboot-status', component: OgbootStatusComponent },
{ path: 'dhcp', component: OgdhcpComponent },
{ path: 'subnets', component: OgDhcpSubnetsComponent },
{ path: 'ogdhcp-status', component: StatusComponent },
{ path: 'commands', component: CommandsComponent },
{ path: 'commands-groups', component: CommandsGroupsComponent },
{ path: 'commands-task', component: CommandsTaskComponent },
{ path: 'commands-logs', component: TaskLogsComponent },
{ path: 'calendars', component: CalendarComponent },
{ path: 'clients/:id', component: ClientMainViewComponent },
{ path: 'clients/:id/partition-assistant', component: PartitionAssistantComponent },
{ path: 'clients/:id/create-image', component: CreateImageComponent },
{ path: 'clients/:id/deploy-image', component: DeployImageComponent },
{ path: 'images', component: ImagesComponent },
{ path: 'repositories', component: RepositoriesComponent },
{ path: 'repository/:id', component: MainRepositoryViewComponent },
{ path: 'software', component: SoftwareComponent },
{ path: 'software-profiles', component: SoftwareProfileComponent },
{ path: 'operative-systems', component: OperativeSystemComponent },
{ path: 'menus', component: MenusComponent },
{ path: 'pxe-images', component: PXEimagesComponent, canActivate: [roleGuard], data: { allowedRoles: ['super-admin', 'ou-admin'] } },
{ path: 'pxe', component: PxeComponent, canActivate: [roleGuard], data: { allowedRoles: ['super-admin', 'ou-admin'] } },
{ path: 'pxe-boot-file', component: PxeBootFilesComponent, canActivate: [roleGuard], data: { allowedRoles: ['super-admin', 'ou-admin'] } },
{ path: 'ogboot-status', component: OgbootStatusComponent, canActivate: [roleGuard], data: { allowedRoles: ['super-admin', 'ou-admin'] } },
{ path: 'subnets', component: OgDhcpSubnetsComponent, canActivate: [roleGuard], data: { allowedRoles: ['super-admin', 'ou-admin'] } },
{ path: 'ogdhcp-status', component: StatusComponent, canActivate: [roleGuard], data: { allowedRoles: ['super-admin', 'ou-admin'] } },
{ path: 'commands', component: CommandsComponent, canActivate: [roleGuard], data: { allowedRoles: ['super-admin', 'ou-admin'] } },
{ path: 'commands-groups', component: CommandsGroupsComponent, canActivate: [roleGuard], data: { allowedRoles: ['super-admin', 'ou-admin'] } },
{ path: 'commands-task', component: CommandsTaskComponent, canActivate: [roleGuard], data: { allowedRoles: ['super-admin', 'ou-admin'] } },
{ path: 'commands-logs', component: TaskLogsComponent, canActivate: [roleGuard], data: { allowedRoles: ['super-admin', 'ou-admin'] } },
{ path: 'calendars', component: CalendarComponent, canActivate: [roleGuard], data: { allowedRoles: ['super-admin', 'ou-admin'] } },
{ path: 'clients/deploy-image', component: DeployImageComponent, canActivate: [roleGuard], data: { allowedRoles: ['super-admin', 'ou-admin'] } },
{ path: 'clients/partition-assistant', component: PartitionAssistantComponent, canActivate: [roleGuard], data: { allowedRoles: ['super-admin', 'ou-admin'] } },
{ path: 'clients/run-script', component: RunScriptAssistantComponent, canActivate: [roleGuard], data: { allowedRoles: ['super-admin', 'ou-admin'] } },
{ path: 'clients/:id/create-image', component: CreateClientImageComponent, canActivate: [roleGuard], data: { allowedRoles: ['super-admin', 'ou-admin'] } },
{ path: 'repositories', component: RepositoriesComponent, canActivate: [roleGuard], data: { allowedRoles: ['super-admin', 'ou-admin'] } },
{ path: 'repository/:id', component: MainRepositoryViewComponent, canActivate: [roleGuard], data: { allowedRoles: ['super-admin', 'ou-admin'] } },
{ path: 'software', component: SoftwareComponent, canActivate: [roleGuard], data: { allowedRoles: ['super-admin', 'ou-admin'] } },
{ path: 'software-profiles', component: SoftwareProfileComponent, canActivate: [roleGuard], data: { allowedRoles: ['super-admin', 'ou-admin'] } },
{ path: 'operative-systems', component: OperativeSystemComponent, canActivate: [roleGuard], data: { allowedRoles: ['super-admin', 'ou-admin'] } },
{ path: 'menus', component: MenusComponent, canActivate: [roleGuard], data: { allowedRoles: ['super-admin', 'ou-admin'] } },
],
},
{
path: 'auth',
component: AuthLayoutComponent,
children: [
{ path: 'login', component: LoginComponent },
{ path: 'login', component: LoginComponent, canActivate: [LogoutGuard] },
],
},
{ path: '**', component: PageNotFoundComponent },

View File

@ -1,4 +1,5 @@
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { NgModule, CUSTOM_ELEMENTS_SCHEMA, LOCALE_ID, APP_INITIALIZER } from '@angular/core';
import { ConfigService } from './services/config.service';
import { BrowserModule } from '@angular/platform-browser';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
@ -16,7 +17,6 @@ import { MatIconModule } from '@angular/material/icon';
import { MatButtonModule } from '@angular/material/button';
import { MatSidenavModule } from '@angular/material/sidenav';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { AdminComponent } from './components/admin/admin.component';
import { MatCardModule } from '@angular/material/card';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatFormFieldModule } from '@angular/material/form-field';
@ -25,6 +25,7 @@ import { MatListModule } from '@angular/material/list';
import { UsersComponent } from './components/admin/users/users/users.component';
import { RolesComponent } from './components/admin/roles/roles/roles.component';
import { MatTableModule } from '@angular/material/table';
import { MatButtonToggleModule } from '@angular/material/button-toggle';
import { MatDialogModule } from '@angular/material/dialog';
import { AddUserModalComponent } from './components/admin/users/users/add-user-modal/add-user-modal.component';
import { MatSelectModule } from '@angular/material/select';
@ -32,17 +33,14 @@ import { AddRoleModalComponent } from './components/admin/roles/roles/add-role-m
import { ChangePasswordModalComponent } from './components/admin/users/users/change-password-modal/change-password-modal.component';
import { GroupsComponent } from './components/groups/groups.component';
import { MatDividerModule } from '@angular/material/divider';
import { CreateOrganizationalUnitComponent } from './components/groups/shared/organizational-units/create-organizational-unit/create-organizational-unit.component';
import { MatStepperModule } from '@angular/material/stepper';
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import { CreateClientComponent } from './components/groups/shared/clients/create-client/create-client.component';
import { DeleteModalComponent } from './shared/delete_modal/delete-modal/delete-modal.component';
import { EditOrganizationalUnitComponent } from './components/groups/shared/organizational-units/edit-organizational-unit/edit-organizational-unit.component';
import { EditClientComponent } from './components/groups/shared/clients/edit-client/edit-client.component';
import { ClassroomViewComponent } from './components/groups/shared/classroom-view/classroom-view.component';
import { MatProgressSpinner } from "@angular/material/progress-spinner";
import { MatProgressBarModule } from '@angular/material/progress-bar';
import { MatMenu, MatMenuItem, MatMenuTrigger } from "@angular/material/menu";
import {MatAutocomplete, MatAutocompleteTrigger} from "@angular/material/autocomplete";
import { MatAutocomplete, MatAutocompleteTrigger } from "@angular/material/autocomplete";
import { MatChip, MatChipListbox, MatChipOption, MatChipSet, MatChipsModule } from "@angular/material/chips";
import { ClientViewComponent } from './components/groups/shared/client-view/client-view.component';
import { MatTab, MatTabGroup } from "@angular/material/tabs";
@ -52,7 +50,6 @@ import { DragDropModule } from '@angular/cdk/drag-drop';
import { ToastrModule } from 'ngx-toastr';
import { ShowOrganizationalUnitComponent } from './components/groups/shared/organizational-units/show-organizational-unit/show-organizational-unit.component';
import { MatGridList, MatGridTile } from "@angular/material/grid-list";
import { TreeViewComponent } from './components/groups/shared/tree-view/tree-view.component';
import {
MatNestedTreeNode,
MatTree,
@ -65,21 +62,15 @@ import { LegendComponent } from './components/groups/shared/legend/legend.compon
import { ClassroomViewDialogComponent } from './components/groups/shared/classroom-view/classroom-view-modal';
import { MatPaginator } from "@angular/material/paginator";
import { SaveFiltersDialogComponent } from './components/groups/shared/save-filters-dialog/save-filters-dialog.component';
import { AcctionsModalComponent } from './components/groups/shared/acctions-modal/acctions-modal.component';
import { PXEimagesComponent } from './components/ogboot/pxe-images/pxe-images.component';
import { CreatePXEImageComponent } from './components/ogboot/pxe-images/create-image/create-image/create-image.component';
import { InfoImageComponent } from './components/ogboot/pxe-images/info-image/info-image/info-image.component';
import { PxeComponent } from './components/ogboot/pxe/pxe.component';
import { CreatePxeTemplateComponent } from './components/ogboot/pxe/create-pxeTemplate/create-pxe-template.component';
import { CreatePxeTemplateComponent } from './components/ogboot/pxe/manage-pxeTemplate/create-pxe-template.component';
import { PxeBootFilesComponent } from './components/ogboot/pxe-boot-files/pxe-boot-files.component';
import { MatExpansionPanel, MatExpansionPanelDescription, MatExpansionPanelTitle } from "@angular/material/expansion";
import { OgbootStatusComponent } from './components/ogboot/ogboot-status/ogboot-status.component';
import { CreatePxeBootFileComponent } from './components/ogboot/pxe-boot-files/create-pxeBootFile/create-pxe-boot-file/create-pxe-boot-file.component';
import { NgxChartsModule } from '@swimlane/ngx-charts';
import { OgdhcpComponent } from './components/ogdhcp/ogdhcp.component';
import { OgDhcpSubnetsComponent } from './components/ogdhcp/og-dhcp-subnets/og-dhcp-subnets.component';
import { CreateSubnetComponent } from './components/ogdhcp/og-dhcp-subnets/create-subnet/create-subnet.component';
import { AddClientsToSubnetComponent } from './components/ogdhcp/og-dhcp-subnets/add-clients-to-subnet/add-clients-to-subnet.component';
import { CommandsComponent } from './components/commands/main-commands/commands.component';
import { CommandDetailComponent } from './components/commands/main-commands/detail-command/command-detail.component';
import { CreateCommandComponent } from './components/commands/main-commands/create-command/create-command.component';
@ -87,7 +78,7 @@ import { MatDatepickerModule } from '@angular/material/datepicker';
import { MatNativeDateModule } from '@angular/material/core';
import { CalendarComponent } from './components/calendar/calendar.component';
import { CreateCalendarComponent } from './components/calendar/create-calendar/create-calendar.component';
import {MatRadioButton, MatRadioGroup} from "@angular/material/radio";
import { MatRadioButton, MatRadioGroup } from "@angular/material/radio";
import { CreateCalendarRuleComponent } from './components/calendar/create-calendar-rule/create-calendar-rule.component';
import { CommandsGroupsComponent } from './components/commands/commands-groups/commands-groups.component';
import { CommandsTaskComponent } from './components/commands/commands-task/commands-task.component';
@ -95,13 +86,12 @@ import { CreateCommandGroupComponent } from './components/commands/commands-grou
import { DetailCommandGroupComponent } from './components/commands/commands-groups/detail-command-group/detail-command-group.component';
import { CreateTaskComponent } from './components/commands/commands-task/create-task/create-task.component';
import { DetailTaskComponent } from './components/commands/commands-task/detail-task/detail-task.component';
import { TaskLogsComponent } from './components/commands/commands-task/task-logs/task-logs.component';
import { ServerInfoDialogComponent } from './components/ogdhcp/og-dhcp-subnets/server-info-dialog/server-info-dialog.component';
import { StatusComponent } from './components/ogdhcp/og-dhcp-subnets/status/status.component';
import {MatSliderModule} from '@angular/material/slider';
import { ClientMainViewComponent } from './components/groups/components/client-main-view/client-main-view.component';
import { TaskLogsComponent } from './components/task-logs/task-logs.component';
import { MatSliderModule } from '@angular/material/slider';
import { ImagesComponent } from './components/images/images.component';
import { CreateImageComponent } from './components/images/create-image/create-image.component';
import { CreateClientImageComponent } from './components/groups/components/client-main-view/create-image/create-image.component';
import { CreateRepositoryModalComponent } from './components/groups/components/client-main-view/create-image/create-repository-modal/create-repository-modal.component';
import { PartitionAssistantComponent } from './components/groups/components/client-main-view/partition-assistant/partition-assistant.component';
import { SoftwareComponent } from './components/software/software.component';
import { CreateSoftwareComponent } from './components/software/create-software/create-software.component';
@ -111,7 +101,7 @@ import { OperativeSystemComponent } from './components/operative-system/operativ
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 { 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 { 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';
@ -124,10 +114,62 @@ 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/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 {NgOptimizedImage, 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 { ShowGitCommitsComponent } from './components/repositories/show-git-images/show-git-images.component';
import { RenameImageComponent } from './components/repositories/rename-image/rename-image.component';
import { ClientDetailsComponent } from './components/groups/shared/client-details/client-details.component';
import { PartitionTypeOrganizatorComponent } from './components/groups/shared/partition-type-organizator/partition-type-organizator.component';
import { CreateTaskScheduleComponent } from './components/commands/commands-task/create-task-schedule/create-task-schedule.component';
import { ShowTaskScheduleComponent } from './components/commands/commands-task/show-task-schedule/show-task-schedule.component';
import { ShowTaskScriptComponent } from './components/commands/commands-task/show-task-script/show-task-script.component';
import { CreateTaskScriptComponent } from './components/commands/commands-task/create-task-script/create-task-script.component';
import { ViewParametersModalComponent } from './components/commands/commands-task/show-task-script/view-parameters-modal/view-parameters-modal.component';
import { OutputDialogComponent } from './components/task-logs/output-dialog/output-dialog.component';
import { ClientTaskLogsComponent } from './components/task-logs/client-task-logs/client-task-logs.component';
import { BootSoPartitionComponent } from './components/commands/main-commands/execute-command/boot-so-partition/boot-so-partition.component';
import { RemoveCacheImageComponent } from './components/commands/main-commands/execute-command/remove-cache-image/remove-cache-image.component';
import { ChangeParentComponent } from './components/groups/shared/change-parent/change-parent.component';
import { SoftwareProfilePartitionComponent } from './components/commands/main-commands/execute-command/software-profile-partition/software-profile-partition.component';
import { ClientPendingTasksComponent } from './components/task-logs/client-pending-tasks/client-pending-tasks.component';
import { QueueConfirmationModalComponent } from './shared/queue-confirmation-modal/queue-confirmation-modal.component';
import { ModalOverlayComponent } from './shared/modal-overlay/modal-overlay.component';
import { ScrollToTopComponent } from './shared/scroll-to-top/scroll-to-top.component';
export function HttpLoaderFactory(http: HttpClient) {
return new TranslateHttpLoader(http, './locale/', '.json');
}
export function initializeApp(configService: ConfigService) {
return () => configService.loadConfig();
}
registerLocaleData(localeEs, 'es-ES');
@NgModule({
declarations: [
AppComponent,
@ -136,7 +178,6 @@ export function HttpLoaderFactory(http: HttpClient) {
HeaderComponent,
SidebarComponent,
LoginComponent,
AdminComponent,
MainLayoutComponent,
UsersComponent,
RolesComponent,
@ -144,19 +185,15 @@ export function HttpLoaderFactory(http: HttpClient) {
AddRoleModalComponent,
ChangePasswordModalComponent,
GroupsComponent,
CreateOrganizationalUnitComponent,
CreateClientComponent,
ManageClientComponent,
DeleteModalComponent,
EditOrganizationalUnitComponent,
EditClientComponent,
QueueConfirmationModalComponent,
ClassroomViewComponent,
ClientViewComponent,
ShowOrganizationalUnitComponent,
TreeViewComponent,
LegendComponent,
ClassroomViewDialogComponent,
SaveFiltersDialogComponent,
AcctionsModalComponent,
PXEimagesComponent,
CreatePXEImageComponent,
InfoImageComponent,
@ -164,8 +201,6 @@ export function HttpLoaderFactory(http: HttpClient) {
CreatePxeTemplateComponent,
PxeBootFilesComponent,
OgbootStatusComponent,
CreatePxeBootFileComponent,
OgdhcpComponent,
OgDhcpSubnetsComponent,
CreateSubnetComponent,
AddClientsToSubnetComponent,
@ -174,6 +209,9 @@ export function HttpLoaderFactory(http: HttpClient) {
CreateCommandComponent,
CalendarComponent,
CreateCalendarComponent,
CreateClientImageComponent,
CreateRepositoryModalComponent,
PartitionAssistantComponent,
CreateCalendarRuleComponent,
CommandsGroupsComponent,
CommandsTaskComponent,
@ -184,10 +222,8 @@ export function HttpLoaderFactory(http: HttpClient) {
TaskLogsComponent,
ServerInfoDialogComponent,
StatusComponent,
ClientMainViewComponent,
ImagesComponent,
CreateImageComponent,
PartitionAssistantComponent,
SoftwareComponent,
CreateSoftwareComponent,
SoftwareProfileComponent,
@ -196,64 +232,99 @@ export function HttpLoaderFactory(http: HttpClient) {
CreateOperativeSystemComponent,
ShowTemplateContentComponent,
RepositoriesComponent,
CreateRepositoryComponent,
ManageRepositoryComponent,
ExecuteCommandComponent,
ExecuteCommandOuComponent,
DeployImageComponent,
MainRepositoryViewComponent,
ExecuteCommandOuComponent,
EnvVarsComponent,
MenusComponent,
CreateMenuComponent,
CreateMultipleClientComponent
CreateMultipleClientComponent,
ExportImageComponent,
ImportImageComponent,
LoadingComponent,
InputDialogComponent,
ManageOrganizationalUnitComponent,
BackupImageComponent,
ShowClientsComponent,
OperationResultDialogComponent,
ConvertImageComponent,
GlobalStatusComponent,
ShowMonoliticImagesComponent,
StatusTabComponent,
ConvertImageToVirtualComponent,
RunScriptAssistantComponent,
SaveScriptComponent,
EditImageComponent,
ShowGitCommitsComponent,
RenameImageComponent,
ClientDetailsComponent,
PartitionTypeOrganizatorComponent,
CreateTaskScheduleComponent,
ShowTaskScheduleComponent,
ShowTaskScriptComponent,
CreateTaskScriptComponent,
ViewParametersModalComponent,
OutputDialogComponent,
ClientTaskLogsComponent,
BootSoPartitionComponent,
RemoveCacheImageComponent,
ChangeParentComponent,
SoftwareProfilePartitionComponent,
ClientPendingTasksComponent,
ModalOverlayComponent,
ScrollToTopComponent
],
bootstrap: [AppComponent],
imports: [BrowserModule,
AppRoutingModule,
FormsModule,
ReactiveFormsModule,
MatToolbarModule,
MatIconModule,
MatButtonModule,
MatSidenavModule,
NoopAnimationsModule,
MatCardModule,
MatCheckboxModule,
MatFormFieldModule,
MatInputModule,
MatListModule,
MatTableModule,
MatDialogModule,
MatSelectModule,
MatDividerModule,
MatStepperModule,
DragDropModule,
MatSlideToggleModule, MatMenu, MatMenuTrigger, MatMenuItem, MatAutocomplete, MatChipListbox, MatChipOption, MatChipSet, MatChipsModule, MatChip, MatProgressSpinner, MatTabGroup, MatTab, MatTooltip,
MatExpansionModule,
NgxChartsModule,
MatDatepickerModule,
MatNativeDateModule,
MatSliderModule,
MatSortModule,
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useFactory: HttpLoaderFactory,
deps: [HttpClient]
}
}),
JoyrideModule.forRoot(),
ToastrModule.forRoot(
{
timeOut: 5000,
positionClass: 'toast-bottom-right',
preventDuplicates: true,
progressBar: true,
progressAnimation: 'increasing',
closeButton: true
}
), MatGridList, MatTree, MatTreeNode, MatNestedTreeNode, MatTreeNodeToggle, MatTreeNodeDef, MatTreeNodePadding, MatTreeNodeOutlet, MatPaginator, MatGridTile, MatExpansionPanel, MatExpansionPanelTitle, MatExpansionPanelDescription, MatRadioGroup, MatRadioButton, MatAutocompleteTrigger
],
imports: [BrowserModule,
AppRoutingModule,
FormsModule,
ReactiveFormsModule,
MatToolbarModule,
MatIconModule,
MatButtonToggleModule,
MatButtonModule,
MatSidenavModule,
NoopAnimationsModule,
MatCardModule,
MatCheckboxModule,
MatFormFieldModule,
MatInputModule,
MatListModule,
MatTableModule,
MatDialogModule,
MatSelectModule,
MatDividerModule,
MatProgressBarModule,
MatStepperModule,
DragDropModule,
MatSlideToggleModule, MatMenu, MatMenuTrigger, MatMenuItem, MatAutocomplete, MatChipListbox, MatChipOption, MatChipSet, MatChipsModule, MatChip, MatProgressSpinner, MatTabGroup, MatTab, MatTooltip,
MatExpansionModule,
NgxChartsModule,
MatDatepickerModule,
MatNativeDateModule,
MatSliderModule,
MatSortModule,
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useFactory: HttpLoaderFactory,
deps: [HttpClient]
}
}),
JoyrideModule.forRoot(),
ToastrModule.forRoot(
{
timeOut: 5000,
positionClass: 'toast-bottom-right',
preventDuplicates: true,
progressBar: true,
progressAnimation: 'increasing',
closeButton: true
}
), MatGridList, MatTree, MatTreeNode, MatNestedTreeNode, MatTreeNodeToggle, MatTreeNodeDef, MatTreeNodePadding, MatTreeNodeOutlet, MatPaginator, MatGridTile, MatExpansionPanel, MatExpansionPanelTitle, MatExpansionPanelDescription, MatRadioGroup, MatRadioButton, MatAutocompleteTrigger, NgOptimizedImage
],
schemas: [
CUSTOM_ELEMENTS_SCHEMA,
],
@ -263,8 +334,16 @@ export function HttpLoaderFactory(http: HttpClient) {
useClass: CustomInterceptor,
multi: true
},
{ provide: LOCALE_ID, useValue: 'es-ES' },
provideAnimationsAsync(),
provideHttpClient(withInterceptorsFromDi())
provideHttpClient(withInterceptorsFromDi()),
ConfigService,
{
provide: APP_INITIALIZER,
useFactory: initializeApp,
deps: [ConfigService],
multi: true
}
],
})
export class AppModule { }

View File

@ -1,58 +0,0 @@
/* Estilos del contenedor para centrar los botones */
.container {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}
/* Estilos del contenedor de cada botón y texto */
.button-container {
display: flex;
flex-direction: column;
align-items: center;
margin: 0 10px;
}
/* Estilos de los botones */
button {
height: 150px;
width: 150px;
margin: 5px;
padding: 5px;
cursor: pointer;
transition: all 0.3s;
}
/* Estilos del texto debajo de los botones */
span{
margin: 0;
font-size: 20px;
text-align: center;
}
/* Media query para hacer los botones responsive */
@media (max-width: 900px) {
button {
height: 120px;
width: 120px;
}
}
@media (max-width: 600px) {
button {
height: 90px;
width: 90px;
}
}
@media (max-width: 400px) {
button {
height: 70px;
width: 70px;
}
span{
font-size: 14px;
}
}

View File

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

View File

@ -1,57 +0,0 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { AdminComponent } from './admin.component';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import { TranslateModule } from '@ngx-translate/core';
import { Router } from '@angular/router';
describe('AdminComponent', () => {
let component: AdminComponent;
let fixture: ComponentFixture<AdminComponent>;
let router: Router;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [AdminComponent],
imports: [
RouterTestingModule,
MatButtonModule,
MatIconModule,
TranslateModule.forRoot()
]
}).compileComponents();
router = TestBed.inject(Router);
});
beforeEach(() => {
fixture = TestBed.createComponent(AdminComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('debería crear el componente', () => {
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');
});
it('debería aplicar la clase "fab-button" a ambos botones', () => {
const buttons = fixture.nativeElement.querySelectorAll('button');
buttons.forEach((button: HTMLElement) => {
expect(button.classList).toContain('fab-button');
});
});
});

View File

@ -1,13 +0,0 @@
import { Component } from '@angular/core';
@Component({
selector: 'app-admin',
templateUrl: './admin.component.html',
styleUrl: './admin.component.css'
})
export class AdminComponent {
}

View File

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

View File

@ -1,5 +1,9 @@
<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">
<!-- Nombre de la variable -->
@ -12,18 +16,26 @@
<ng-container matColumnDef="value">
<mat-header-cell *matHeaderCellDef> Valor </mat-header-cell>
<mat-cell *matCellDef="let variable">
<mat-form-field class="value-input">
<!-- Si es booleano, usamos checkbox -->
<mat-checkbox *ngIf="isBoolean(variable.value)"
[checked]="variable.value === 'true'"
(change)="variable.value = $event.checked ? 'true' : 'false'">
</mat-checkbox>
<!-- Si no es booleano, usamos input -->
<mat-form-field *ngIf="!isBoolean(variable.value)" class="value-input">
<input matInput [(ngModel)]="variable.value" />
</mat-form-field>
</mat-cell>
</ng-container>
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
<mat-row *matRowDef="let row; columns: displayedColumns;"></mat-row>
</mat-table>
<div class="actions" >
<button mat-raised-button color="primary" (click)="saveEnvVars()">Guardar Cambios</button>
<button mat-raised-button color="accent" (click)="loadEnvVars()">Recargar</button>
<div class="actions">
<button class="action-button" (click)="loadEnvVars()">Recargar</button>
<button class="submit-button" (click)="saveEnvVars()">Guardar Cambios</button>
</div>
</div>

View File

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

View File

@ -1,6 +1,7 @@
import { Component } from '@angular/core';
import {HttpClient} from "@angular/common/http";
import {ToastrService} from "ngx-toastr";
import { HttpClient } from "@angular/common/http";
import { ToastrService } from "ngx-toastr";
import { ConfigService } from '@services/config.service';
@Component({
selector: 'app-env-vars',
@ -8,16 +9,17 @@ import {ToastrService} from "ngx-toastr";
styleUrl: './env-vars.component.css'
})
export class EnvVarsComponent {
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL;
envVars: { name: string; value: string }[] = [];
displayedColumns: string[] = ['name', 'value'];
private apiUrl = `${this.baseUrl}/env-vars`;
private apiUrl: string;
constructor(
private http: HttpClient,
private toastService: ToastrService,
) {}
private configService: ConfigService
) {
this.apiUrl = `${this.configService.apiUrl}/env-vars`;
}
ngOnInit(): void {
this.loadEnvVars();
@ -29,12 +31,15 @@ export class EnvVarsComponent {
this.envVars = Object.entries(response.vars).map(([name, value]) => ({ name, value }));
},
error: (err) => {
console.error('Error al cargar las variables de entorno:', err);
this.toastService.error('No se pudieron cargar las variables de entorno.');
}
});
}
isBoolean(value: string): boolean {
return value === 'true' || value === 'false';
}
saveEnvVars(): void {
const vars = this.envVars.reduce((acc, variable) => {
acc[variable.name] = variable.value;

View File

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

View File

@ -16,7 +16,7 @@
</section>
</form>
</mat-dialog-content>
<mat-dialog-actions align="end">
<button mat-button (click)="onNoClick()">{{ 'buttonCancel' | translate }}</button>
<button mat-button (click)="onSubmit()">{{ 'buttonAdd' | translate }}</button>
<mat-dialog-actions class="action-container">
<button class="ordinary-button" (click)="onNoClick()">{{ 'buttonCancel' | translate }}</button>
<button class="submit-button" (click)="onSubmit()">{{ 'buttonAdd' | translate }}</button>
</mat-dialog-actions>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,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">
<form [formGroup]="userForm" class="user-form">
<mat-form-field appearance="fill" class="full-width">
@ -27,9 +29,18 @@
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field appearance="fill" class="full-width">
<mat-label>Vista tarjetas</mat-label>
<mat-select formControlName="groupsView" required>
<mat-option *ngFor="let option of views" [value]="option.value">
{{ option.name }}
</mat-option>
</mat-select>
</mat-form-field>
</form>
</mat-dialog-content>
<mat-dialog-actions align="end">
<button mat-button (click)="onNoClick()">{{ 'buttonCancel' | translate }}</button>
<button mat-button (click)="onSubmit()">{{ 'buttonAdd' | translate }}</button>
</mat-dialog-actions>
<mat-dialog-actions class="action-container">
<button class="ordinary-button" (click)="onNoClick()">{{ 'buttonCancel' | translate }}</button>
<button class="submit-button" (click)="onSubmit()" [disabled]="userForm.invalid">{{ isEditMode ? ('buttonEdit' | translate) : ('buttonAdd' | translate) }}</button>
</mat-dialog-actions>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -7,6 +7,7 @@ import { ToastrService } from 'ngx-toastr';
import { of } from 'rxjs';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { TranslateModule } from '@ngx-translate/core';
import { ConfigService } from '@services/config.service';
class MockToastrService {
success() {}
@ -18,6 +19,11 @@ describe('UsersComponent', () => {
let fixture: ComponentFixture<UsersComponent>;
beforeEach(async () => {
const mockConfigService = {
apiUrl: 'http://mock-api-url',
mercureUrl: 'http://mock-mercure-url'
};
await TestBed.configureTestingModule({
declarations: [UsersComponent],
imports: [
@ -28,6 +34,7 @@ describe('UsersComponent', () => {
],
providers: [
{ provide: ToastrService, useClass: MockToastrService },
{ provide: ConfigService, useValue: mockConfigService }
],
schemas: [NO_ERRORS_SCHEMA], // Ignorar elementos desconocidos
}).compileComponents();

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 { HttpClient } from '@angular/common/http';
import { ToastrService } from 'ngx-toastr';
import { DataService } from "./data.service";
import { ConfigService } from '@services/config.service';
@Component({
selector: 'app-users',
@ -13,7 +13,8 @@ import { DataService } from "./data.service";
styleUrls: ['./users.component.css']
})
export class UsersComponent implements OnInit {
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL;
baseUrl: string;
private apiUrl: string;
dataSource = new MatTableDataSource<any>();
filters: { [key: string]: string } = {};
loading: boolean = false;
@ -32,6 +33,11 @@ export class UsersComponent implements OnInit {
header: 'Nombre de Usuario',
cell: (user: any) => `${user.username}`
},
{
columnDef: 'groupsView',
header: 'Vista de Grupos',
cell: (user: any) => `${user.groupsView}`
},
{
columnDef: 'allowedOrganizationalUnits',
header: 'Unidades Organizacionales Permitidas',
@ -45,14 +51,15 @@ export class UsersComponent implements OnInit {
];
displayedColumns = [...this.columns.map(column => column.columnDef), 'actions'];
private apiUrl = `${this.baseUrl}/users`;
constructor(
public dialog: MatDialog,
private configService: ConfigService,
private http: HttpClient,
private dataService: DataService,
private toastService: ToastrService
) {}
) {
this.baseUrl = this.configService.apiUrl;
this.apiUrl = `${this.baseUrl}/users`;
}
ngOnInit() {
this.search();
@ -87,6 +94,10 @@ export class UsersComponent implements OnInit {
data: user['@id']
});
dialogRef.componentInstance.userEdited.subscribe(() => {
this.search();
});
dialogRef.afterClosed().subscribe(result => {
if (result) {
this.search();
@ -124,4 +135,4 @@ export class UsersComponent implements OnInit {
this.length = event.length;
this.search();
}
}
}

View File

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

View File

@ -2,30 +2,33 @@
<button mat-icon-button color="primary" (click)="iniciarTour()">
<mat-icon>help</mat-icon>
</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">
<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 }}
</button>
</div>
</div>
<mat-divider class="divider"></mat-divider>
<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>
<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-hint>{{ 'searchHint' | translate }}</mat-hint>
</mat-form-field>
</div>
<div *ngIf="loading" class="loading-container">
<mat-spinner></mat-spinner>
</div>
<app-loading [isLoading]="loading"></app-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">
<th mat-header-cell *matHeaderCellDef> {{ column.header }} </th>
<td mat-cell *matCellDef="let image">
@ -34,7 +37,8 @@
{{ image[column.columnDef] ? 'check_circle' : 'cancel' }}
</mat-icon>
</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) }}
</ng-container>
</td>
@ -50,7 +54,7 @@
<mat-icon>sync</mat-icon>
</button>
<button *ngIf="syncUds" mat-icon-button color="primary">
<mat-spinner diameter="24"></mat-spinner>
<app-loading [isLoading]="syncUds"></app-loading>
</button>
<button mat-icon-button color="warn" (click)="deleteCalendar(calendar)">
<mat-icon>delete</mat-icon>
@ -64,10 +68,7 @@
</div>
<div class="paginator-container">
<mat-paginator [length]="length"
[pageSize]="itemsPerPage"
[pageIndex]="page"
[pageSizeOptions]="pageSizeOptions"
(page)="onPageChange($event)">
<mat-paginator [length]="length" [pageSize]="itemsPerPage" [pageIndex]="page" [pageSizeOptions]="pageSizeOptions"
(page)="onPageChange($event)">
</mat-paginator>
</div>
</div>

View File

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

View File

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

View File

@ -22,9 +22,91 @@
.time-fields {
display: flex;
gap: 15px; /* Espacio entre los campos */
gap: 15px;
}
.hour-fields {
display: flex;
gap: 15px;
}
.time-field {
flex: 1;
}
.action-container {
display: flex;
justify-content: flex-end;
gap: 1em;
padding: 1.5em;
}
.custom-text {
font-style: italic;
font-size: 0.875rem;
color: #666;
margin: 4px 0 12px;
}
.weekday-toggle-group {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin: 40px 0 40px 0;
width: 100%;
justify-content: space-between;
}
.weekday-toggle {
flex: 1 1 calc(14.28% - 10px);
padding: 10px 0;
border-radius: 999px;
border: 1px solid #ccc;
background-color: #f5f5f5;
cursor: pointer;
font-size: 14px;
text-align: center;
transition: all 0.2s ease;
min-width: 40px;
}
.weekday-toggle.selected {
background-color: #1976d2;
color: white;
border-color: #1976d2;
}
.availability-summary {
background-color: #e3f2fd;
border-left: 4px solid #2196f3;
padding: 12px 16px;
margin-top: 16px;
border-radius: 6px;
display: flex;
align-items: center;
gap: 8px;
font-size: 14px;
}
.summary-text {
color: #0d47a1;
line-height: 1.4;
}
.unavailability-summary {
background-color: #ffebee;
border-left: 4px solid #d32f2f;
padding: 12px 16px;
margin-top: 16px;
border-radius: 6px;
display: flex;
align-items: center;
gap: 8px;
font-size: 14px;
}
.summary-text {
color: #b71c1c;
line-height: 1.4;
}

View File

@ -1,24 +1,26 @@
<h2 mat-dialog-title>{{ isEditMode ? ('editCalendar' | translate) : ('addCalendar' | translate) }}</h2>
<mat-dialog-content class="form-container">
<mat-slide-toggle [(ngModel)]="isRemoteAvailable" class="example-margin">
<mat-checkbox [(ngModel)]="isRemoteAvailable">
{{ 'remoteAvailability' | translate }}
</mat-slide-toggle>
</mat-checkbox>
<mat-divider style="margin: 10px 0;"></mat-divider>
<div *ngIf="!isRemoteAvailable" class="form-group">
<mat-label>{{ 'selectWeekDays' | translate }}</mat-label>
<div class="row">
<div class="col-md-6 checkbox-group">
<mat-checkbox *ngFor="let day of weekDays.slice(0, (weekDays.length / 2) + 1)" [(ngModel)]="busyWeekDays[day]">
{{ day }}
</mat-checkbox>
</div>
<div class="col-md-6 checkbox-group">
<mat-checkbox *ngFor="let day of weekDays.slice(weekDays.length / 2 + 1)" [(ngModel)]="busyWeekDays[day]">
{{ day }}
</mat-checkbox>
</div>
<p class="custom-text"> (Los dias y horas seleccionados se marcarán como aula no disponible para remote pc.) </p>
<div class="weekday-toggle-group full-width">
<button
*ngFor="let day of weekDays"
type="button"
class="weekday-toggle"
[class.selected]="busyWeekDays[day]"
(click)="busyWeekDays[day] = !busyWeekDays[day]">
{{ day.slice(0, 3) }}
</button>
</div>
<div class="time-fields">
<mat-form-field appearance="fill" class="time-field">
<mat-label>{{ 'startTime' | translate }}</mat-label>
@ -30,12 +32,24 @@
<input matInput [(ngModel)]="busyToHour" type="time" placeholder="{{ 'endTimePlaceholder' | translate }}" [required]="!isRemoteAvailable">
</mat-form-field>
</div>
<mat-divider></mat-divider>
<div class="unavailability-summary" *ngIf="busyFromHour && busyToHour && busyWeekDays && getSelectedDays().length">
<mat-icon style="width: 50px;" color="warn">block</mat-icon>
<span class="summary-text">
El aula estará <strong>no disponible</strong> para Remote PC los días:
<strong>{{ getSelectedDays().join(', ') }}</strong> de
<strong>{{ busyFromHour }}</strong> a <strong>{{ busyToHour }}</strong>.
</span>
</div>
</div>
<div *ngIf="isRemoteAvailable" class="form-group">
<mat-form-field appearance="fill" class="full-width">
<mat-label>{{ 'reasonLabel' | translate }}</mat-label>
<input matInput [(ngModel)]="availableReason" placeholder="{{ 'reasonPlaceholder' | translate }}" [required]="isRemoteAvailable">
<mat-hint>Razón por la cual el aula SI está disponible para su uso en Remote PC</mat-hint>
</mat-form-field>
<div class="time-fields">
<mat-form-field appearance="fill" class="full-width">
@ -53,13 +67,39 @@
<mat-datepicker #picker2></mat-datepicker>
</mat-form-field>
</div>
<div class="hour-fields">
<mat-form-field appearance="fill" class="time-field">
<mat-label>{{ 'startTime' | translate }}</mat-label>
<input matInput [(ngModel)]="busyFromHour" type="time" placeholder="{{ 'startTimePlaceholder' | translate }}" [required]="!isRemoteAvailable">
</mat-form-field>
<mat-form-field appearance="fill" class="time-field">
<mat-label>{{ 'endTime' | translate }}</mat-label>
<input matInput [(ngModel)]="busyToHour" type="time" placeholder="{{ 'endTimePlaceholder' | translate }}" [required]="!isRemoteAvailable">
</mat-form-field>
</div>
<mat-divider></mat-divider>
<div class="availability-summary" *ngIf="availableFromDate && availableToDate">
<mat-icon color="primary" style="width: 50px;">info</mat-icon>
<span class="summary-text">
El aula estará <strong>disponible</strong> para reserva desde el
<strong>{{ availableFromDate | date:'fullDate' }}</strong> hasta el
<strong>{{ availableToDate | date:'fullDate' }}</strong>
<span *ngIf="busyFromHour && busyToHour">
en el horario de <strong>{{ busyFromHour }}</strong> a <strong>{{ busyToHour }}</strong>.
</span>
</span>
</div>
</div>
</mat-dialog-content>
<mat-dialog-actions align="end">
<button mat-button (click)="onNoClick()">{{ 'buttonCancel' | translate }}</button>
<mat-dialog-actions class="action-container">
<button class="ordinary-button" (click)="onNoClick()">{{ 'buttonCancel' | translate }}</button>
<button
mat-button
class="submit-button"
(click)="submitRule()"
cdkFocusInitial
[disabled]="(!isRemoteAvailable && (!busyFromHour || !busyToHour)) || (isRemoteAvailable && (!availableReason || !availableFromDate || !availableToDate))">

View File

@ -1,7 +1,8 @@
import {Component, Inject} from '@angular/core';
import {ToastrService} from "ngx-toastr";
import {HttpClient} from "@angular/common/http";
import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog";
import { Component, Inject } from '@angular/core';
import { ToastrService } from "ngx-toastr";
import { HttpClient } from "@angular/common/http";
import { MAT_DIALOG_DATA, MatDialogRef } from "@angular/material/dialog";
import { ConfigService } from '@services/config.service';
@Component({
selector: 'app-create-calendar-rule',
@ -9,7 +10,7 @@ import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog";
styleUrl: './create-calendar-rule.component.css'
})
export class CreateCalendarRuleComponent {
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL;
baseUrl: string;
name: string = '';
remoteCalendarRules: any[] = [];
weekDays: string[] = ['Lunes', 'Martes', 'Miércoles', 'Jueves', 'Viernes', 'Sábado', 'Domingo'];
@ -29,20 +30,23 @@ export class CreateCalendarRuleComponent {
constructor(
private toastService: ToastrService,
private http: HttpClient,
private configService: ConfigService,
public dialogRef: MatDialogRef<CreateCalendarRuleComponent>,
@Inject(MAT_DIALOG_DATA) public data: any,
) { }
) {
this.baseUrl = this.configService.apiUrl;
}
ngOnInit(): void {
this.calendarId = this.data.calendar
if (this.data) {
this.isEditMode = true;
this.availableFromDate = this.data.rule? this.data.rule.availableFromDate : null;
this.availableToDate = this.data.rule? this.data.rule.availableToDate : null;
this.isRemoteAvailable = this.data.rule? this.data.rule.isRemoteAvailable : false;
this.availableReason = this.data.rule? this.data.rule.availableReason : null;
this.busyFromHour = this.data.rule? this.data.rule.busyFromHour : null;
this.busyToHour = this.data.rule? this.data.rule.busyToHour : null;
this.availableFromDate = this.data.rule ? this.data.rule.availableFromDate : null;
this.availableToDate = this.data.rule ? this.data.rule.availableToDate : null;
this.isRemoteAvailable = this.data.rule ? this.data.rule.isRemoteAvailable : false;
this.availableReason = this.data.rule ? this.data.rule.availableReason : null;
this.busyFromHour = this.data.rule ? this.data.rule.busyFromHour : null;
this.busyToHour = this.data.rule ? this.data.rule.busyToHour : null;
if (this.data.rule && this.data.rule.busyWeekDays) {
this.busyWeekDays = this.data.rule.busyWeekDays.reduce((acc: {
[x: string]: boolean;
@ -60,8 +64,8 @@ export class CreateCalendarRuleComponent {
this.dialogRef.close();
}
toggleAdditionalForm(): void {
this.showAdditionalForm = !this.showAdditionalForm;
getSelectedDays(): string[] {
return Object.keys(this.busyWeekDays || {}).filter(day => this.busyWeekDays[day]);
}
getSelectedDaysIndices() {
@ -70,6 +74,11 @@ export class CreateCalendarRuleComponent {
.filter(index => index !== -1);
}
convertDateToLocalISO(date: Date): string {
const adjustedDate = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()));
return adjustedDate.toISOString();
}
submitRule(): void {
this.getSelectedDaysIndices()
const selectedDaysArray = Object.keys(this.busyWeekDays).map((day, index) => this.busyWeekDays[index]);
@ -79,8 +88,8 @@ export class CreateCalendarRuleComponent {
busyWeekDays: this.selectedDaysIndices,
busyFromHour: this.busyFromHour,
busyToHour: this.busyToHour,
availableFromDate: this.availableFromDate,
availableToDate: this.availableToDate,
availableFromDate: this.availableFromDate ? this.convertDateToLocalISO(this.availableFromDate) : null,
availableToDate: this.availableToDate ? this.convertDateToLocalISO(this.availableToDate) : null,
isRemoteAvailable: this.isRemoteAvailable,
availableReason: this.availableReason
};
@ -89,7 +98,7 @@ export class CreateCalendarRuleComponent {
this.http.put(`${this.baseUrl}${this.ruleId}`, formData)
.subscribe({
next: (response) => {
this.toastService.success('Calendar updated successfully');
this.toastService.success('Calendar rule updated successfully');
this.dialogRef.close(true);
},
error: (error) => {
@ -101,7 +110,7 @@ export class CreateCalendarRuleComponent {
this.http.post(`${this.baseUrl}/remote-calendar-rules`, formData)
.subscribe({
next: (response) => {
this.toastService.success('Calendar created successfully');
this.toastService.success('Calendar rule created successfully');
this.dialogRef.close(true);
},
error: (error) => {

View File

@ -1,6 +1,3 @@
.full-width {
width: 100%;
}
.form-container {
padding: 40px;
}
@ -12,7 +9,7 @@
.full-width {
width: 100%;
margin-bottom: 16px;
margin-top: 16px;
}
.additional-form {
@ -28,7 +25,7 @@
.time-fields {
display: flex;
gap: 15px; /* Espacio entre los campos */
gap: 15px;
}
.time-field {
@ -37,24 +34,42 @@
.list-item-content {
display: flex;
align-items: flex-start; /* Alinea el contenido al inicio */
justify-content: space-between; /* Espacio entre los textos y los íconos */
width: 100%; /* Asegúrate de que el contenido ocupe todo el ancho */
align-items: flex-start;
justify-content: space-between;
width: 100%;
}
.text-content {
flex-grow: 1; /* Permite que este contenedor ocupe el espacio disponible */
margin-right: 16px; /* Espaciado a la derecha para separar de los íconos */
flex-grow: 1;
margin-right: 16px;
margin-left: 10px;
margin-bottom: 16px;
}
.icon-container {
display: flex;
align-items: center; /* Alinea los íconos verticalmente */
align-items: center;
}
.right-icon {
margin-left: 8px; /* Espaciado entre los íconos */
margin-left: 8px;
cursor: pointer;
}
.action-container {
display: flex;
justify-content: flex-end;
gap: 1em;
padding: 1.5em;
}
.rule-available {
background-color: #e8f5e9;
border-left: 4px solid #4caf50;
}
.rule-unavailable {
background-color: #ffebee;
border-left: 4px solid #f44336;
}

View File

@ -6,10 +6,10 @@
<input matInput [(ngModel)]="name" required>
<mat-icon *ngIf="isEditMode" matSuffix (click)="submitForm()">mode_edit</mat-icon>
</mat-form-field>
<div style="display: flex; justify-content: space-between; align-items: center;">
<div *ngIf="isEditMode" mat-subheader>{{ '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 }}
</button>
</div>
@ -18,14 +18,20 @@
<mat-list *ngIf="isEditMode">
<ng-container *ngFor="let rule of remoteCalendarRules;">
<mat-list-item>
<mat-list-item
[ngClass]="{
'rule-available': rule.isRemoteAvailable,
'rule-unavailable': !rule.isRemoteAvailable
}"
>
<div class="list-item-content">
<mat-icon matListItemIcon>event_available</mat-icon>
<div class="text-content">
<div matListItemTitle>{{ rule.isRemoteAvailable ? ('statusAvailable' | translate) : ('statusUnavailable' | translate) }}</div>
<div matListItemLine *ngIf="!rule.isRemoteAvailable">{{ rule.busyFromHour }} - {{ rule.busyToHour }}</div>
<div matListItemLine *ngIf="!rule.isRemoteAvailable">{{ rule.busyWeekDaysMap }}</div>
<div matListItemLine *ngIf="rule.isRemoteAvailable">{{ rule.availableReason }} | {{ rule.availableFromDate | date }} - {{ rule.availableToDate | date }}</div>
<div matListItemTitle>{{ rule.isRemoteAvailable ? ('remotePcStatusAvailable' | translate) : ('remotePcStatusUnavailable' | translate) }}</div>
<div matListItemLine *ngIf="!rule.isRemoteAvailable">Días: <strong>{{ rule.busyWeekDaysMap }}</strong></div>
<div matListItemLine *ngIf="rule.isRemoteAvailable">Razón: {{ rule.availableReason }}</div>
<div matListItemLine *ngIf="rule.isRemoteAvailable">Días: <strong>{{ rule.availableFromDate | date }} - {{ rule.availableToDate | date }}</strong></div>
<div matListItemLine>Horario: {{ rule.busyFromHour }} - {{ rule.busyToHour }}</div>
</div>
<div class="icon-container">
<button mat-icon-button color="primary" class="right-icon" (click)="createRule(rule)">
@ -41,9 +47,9 @@
</mat-list>
</mat-dialog-content>
<mat-dialog-actions align="end">
<button mat-button (click)="onNoClick()">{{ 'buttonCancel' | translate }}</button>
<button mat-button (click)="submitForm()" [disabled]="!name || name === ''" cdkFocusInitial>
<mat-dialog-actions class="action-container">
<button class="ordinary-button" (click)="onNoClick()">{{ 'buttonCancel' | translate }}</button>
<button class="submit-button" (click)="submitForm()" [disabled]="!name || name === ''" cdkFocusInitial>
{{ 'buttonSave' | translate }}
</button>
</mat-dialog-actions>

View File

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

View File

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

View File

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

View File

@ -2,16 +2,16 @@
<button mat-icon-button color="primary" (click)="iniciarTour()">
<mat-icon>help</mat-icon>
</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">
<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 }}
</button>
</div>
</div>
<mat-divider class="divider"></mat-divider>
<div class="search-container" joyrideStep="searchStep" text="{{ 'searchStepText' | translate }}">
<mat-form-field appearance="fill" class="search-string">
<mat-label>{{ 'searchGroupNameLabel' | translate }}</mat-label>
@ -22,7 +22,7 @@
</div>
<div *ngIf="loading" class="loading-container" joyrideStep="loadingStep" text="{{ 'loadingStepText' | translate }}">
<mat-spinner></mat-spinner>
<app-loading [isLoading]="loading"></app-loading>
</div>
<div *ngIf="!loading">
@ -33,15 +33,6 @@
<ng-container *ngIf="column.columnDef !== 'commands'">
{{ column.cell(commandGroup) }}
</ng-container>
<ng-container *ngIf="column.columnDef === 'commands'" joyrideStep="viewCommandsStep" text="{{ 'viewCommandsStepText' | translate }}">
<button mat-button [matMenuTriggerFor]="menu">{{ 'viewCommands' | translate }}</button>
<mat-menu #menu="matMenu">
<button mat-menu-item *ngFor="let command of commandGroup.commands">
{{ command.name }}
</button>
</mat-menu>
</ng-container>
</td>
</ng-container>

View File

@ -8,6 +8,7 @@ import { DeleteModalComponent } from '../../../shared/delete_modal/delete-modal/
import { MatTableDataSource } from "@angular/material/table";
import { DatePipe } from "@angular/common";
import { JoyrideService } from 'ngx-joyride';
import { ConfigService } from '@services/config.service';
@Component({
selector: 'app-commands-groups',
@ -15,7 +16,8 @@ import { JoyrideService } from 'ngx-joyride';
styleUrls: ['./commands-groups.component.css']
})
export class CommandsGroupsComponent implements OnInit {
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL;
baseUrl: string;
private apiUrl: string;
dataSource = new MatTableDataSource<any>();
filters: { [key: string]: string | boolean } = {};
length: number = 0;
@ -47,10 +49,12 @@ export class CommandsGroupsComponent implements OnInit {
}
];
displayedColumns = [...this.columns.map(column => column.columnDef), 'actions'];
private apiUrl = `${this.baseUrl}/command-groups`;
constructor(private http: HttpClient, private dialog: MatDialog, private toastService: ToastrService,
private joyrideService: JoyrideService) {}
constructor(private http: HttpClient, private dialog: MatDialog, private toastService: ToastrService,
private configService: ConfigService, private joyrideService: JoyrideService) {
this.baseUrl = this.configService.apiUrl;
this.apiUrl = `${this.baseUrl}/command-groups`;
}
ngOnInit(): void {
this.search();
@ -120,17 +124,17 @@ export class CommandsGroupsComponent implements OnInit {
iniciarTour(): void {
this.joyrideService.startTour({
steps: [
'titleStep',
'addCommandGroupStep',
'searchStep',
'tableStep',
'viewCommandsStep',
'actionsStep',
'paginationStep'
'titleStep',
'addCommandGroupStep',
'searchStep',
'tableStep',
'viewCommandsStep',
'actionsStep',
'paginationStep'
],
showPrevButton: true,
themeColor: '#3f51b5'
});
}
}

View File

@ -98,3 +98,10 @@
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>
<mat-dialog-content class="form-container">
<div *ngIf="loading" class="loading-container">
<mat-spinner></mat-spinner>
</div>
<app-loading [isLoading]="loading"></app-loading>
<form *ngIf="!loading" class="command-group-form" (ngSubmit)="onSubmit()">
<mat-form-field>
@ -55,7 +53,7 @@
</form>
</mat-dialog-content>
<mat-dialog-actions align="end">
<button mat-button (click)="close()">{{ 'buttonCancel' | translate }}</button>
<button mat-button (click)="onSubmit()" cdkFocusInitial>{{ 'buttonSave' | translate }}</button>
</mat-dialog-actions>
<mat-dialog-actions class="action-container">
<button class="ordinary-button" (click)="close()">{{ 'buttonCancel' | translate }}</button>
<button class="submit-button" (click)="onSubmit()" cdkFocusInitial>{{ 'buttonSave' | translate }}</button>
</mat-dialog-actions>

View File

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

View File

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

View File

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

View File

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

View File

@ -1,17 +1,19 @@
<app-loading [isLoading]="loading"></app-loading>
<div class="header-container">
<button mat-icon-button color="primary" (click)="iniciarTour()">
<mat-icon>help</mat-icon>
</button>
<h2 class="title" joyrideStep="titleStep" text="{{ 'titleStepText' | translate }}">{{ 'manageTasksTitle' | translate }}</h2>
<div class="header-container-title">
<h2 class="title" joyrideStep="titleStep" text="{{ 'titleStepText' | translate }}">{{ 'manageTasksTitle' | translate }}</h2>
</div>
<div class="task-button-row">
<button mat-flat-button color="primary" (click)="openCreateTaskModal()" joyrideStep="addTaskStep" text="{{ 'addTaskStepText' | translate }}">
<button class="action-button" (click)="openCreateTaskModal()" joyrideStep="addTaskStep" text="{{ 'addTaskStepText' | translate }}">
{{ 'addTask' | translate }}
</button>
</div>
</div>
<mat-divider class="divider"></mat-divider>
<div class="search-container" joyrideStep="searchStep" text="{{ 'searchStepText' | translate }}">
<mat-form-field appearance="fill" class="search-string">
<mat-label>{{ 'searchTaskLabel' | translate }}</mat-label>
@ -23,36 +25,28 @@
<div *ngIf="!loading">
<table mat-table [dataSource]="tasks" class="mat-elevation-z8" joyrideStep="tableStep" text="{{ 'tableStepText' | translate }}">
<ng-container matColumnDef="taskid">
<th mat-header-cell *matHeaderCellDef> {{ 'idColumn' | translate }} </th>
<td mat-cell *matCellDef="let task"> {{ task.id }} </td>
</ng-container>
<ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef">
<th mat-header-cell *matHeaderCellDef> {{ column.header }} </th>
<td mat-cell *matCellDef="let task">
<ng-container *ngIf="column.columnDef !== 'management'">
{{ column.cell(task) }}
</ng-container>
<ng-container matColumnDef="notes">
<th mat-header-cell *matHeaderCellDef> {{ 'infoColumn' | translate }} </th>
<td mat-cell *matCellDef="let task"> {{ task.notes }} </td>
</ng-container>
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef> {{ 'createdByColumn' | translate }} </th>
<td mat-cell *matCellDef="let task"> {{ task.createdBy }} </td>
</ng-container>
<ng-container matColumnDef="scheduledDate">
<th mat-header-cell *matHeaderCellDef> {{ 'executionDateColumn' | translate }} </th>
<td mat-cell *matCellDef="let task"> {{ task.dateTime | date:'short' }} </td>
</ng-container>
<ng-container matColumnDef="enabled">
<th mat-header-cell *matHeaderCellDef> {{ 'statusColumn' | translate }} </th>
<td mat-cell *matCellDef="let task"> {{ task.enabled ? ('enabled' | translate) : ('disabled' | translate) }} </td>
<ng-container *ngIf="column.columnDef === 'management'">
<button class="action-button" (click)="openShowScheduleDialog(task)"> Programaciones</button>
<button class="action-button" style="margin-left: 0.5vw;" (click)="openShowScriptDialog(task)">Acciones</button>
</ng-container>
</td>
</ng-container>
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef style="text-align: center;">{{ 'columnActions' | translate }}</th>
<td mat-cell *matCellDef="let task" style="text-align: center;" joyrideStep="actionsStep" text="{{ 'actionsStepText' | translate }}">
<button mat-icon-button color="info" (click)="viewTaskDetails(task)">
<mat-icon>visibility</mat-icon>
<button mat-icon-button color="primary" (click)="manageScheduleAction(task)">
<mat-icon>watch</mat-icon>
</button>
<button mat-icon-button color="primary" (click)="manageScriptAction(task)">
<mat-icon>code-blocks</mat-icon>
</button>
<button mat-icon-button color="primary" (click)="editTask(task)">
<mat-icon>edit</mat-icon>

View File

@ -7,16 +7,22 @@ import { MatDividerModule } from '@angular/material/divider';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatPaginatorModule } from '@angular/material/paginator';
import { FormsModule } from '@angular/forms';
import { MatInputModule } from '@angular/material/input';
import { MatInputModule } from '@angular/material/input';
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 { LoadingComponent } from '../../../shared/loading/loading.component';
import { JoyrideModule, JoyrideService, JoyrideStepService } from 'ngx-joyride';
import { ConfigService } from '@services/config.service';
describe('CommandsTaskComponent', () => {
let component: CommandsTaskComponent;
let fixture: ComponentFixture<CommandsTaskComponent>;
let mockConfigService: jasmine.SpyObj<ConfigService>;
beforeEach(async () => {
const configServiceSpy = jasmine.createSpyObj('ConfigService', [], { apiUrl: 'http://mock-api-url' });
await TestBed.configureTestingModule({
imports: [
HttpClientTestingModule,
@ -31,9 +37,14 @@ describe('CommandsTaskComponent', () => {
TranslateModule.forRoot(),
JoyrideModule.forRoot(),
],
declarations: [CommandsTaskComponent],
declarations: [CommandsTaskComponent, LoadingComponent],
providers: [
{ provide: ConfigService, useValue: configServiceSpy }
],
schemas: [NO_ERRORS_SCHEMA]
}).compileComponents();
mockConfigService = TestBed.inject(ConfigService) as jasmine.SpyObj<ConfigService>;
});
beforeEach(() => {

View File

@ -6,6 +6,14 @@ import { CreateTaskComponent } from './create-task/create-task.component';
import { DetailTaskComponent } from './detail-task/detail-task.component';
import { DeleteModalComponent } from '../../../shared/delete_modal/delete-modal/delete-modal.component';
import { JoyrideService } from 'ngx-joyride';
import { ConfigService } from '@services/config.service';
import {CreateTaskScheduleComponent} from "./create-task-schedule/create-task-schedule.component";
import {ShowClientsComponent} from "../../ogdhcp/show-clients/show-clients.component";
import {Subnet} from "../../ogdhcp/og-dhcp-subnets.component";
import {ShowTaskScheduleComponent} from "./show-task-schedule/show-task-schedule.component";
import {ShowTaskScriptComponent} from "./show-task-script/show-task-script.component";
import {CreateTaskScriptComponent} from "./create-task-script/create-task-script.component";
import {DatePipe} from "@angular/common";
@Component({
selector: 'app-commands-task',
@ -13,19 +21,34 @@ import { JoyrideService } from 'ngx-joyride';
styleUrls: ['./commands-task.component.css']
})
export class CommandsTaskComponent implements OnInit {
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL;
baseUrl: string;
tasks: any[] = [];
filters: { [key: string]: string | boolean } = {};
length: number = 0;
itemsPerPage: number = 10;
page: number = 1;
pageSizeOptions: number[] = [5, 10, 20, 40, 100];
displayedColumns: string[] = ['taskid', 'notes', 'name', 'scheduledDate', 'enabled', 'actions'];
loading: boolean = false;
private apiUrl = `${this.baseUrl}/command-tasks`;
datePipe: DatePipe = new DatePipe('es-ES');
constructor(private http: HttpClient, private dialog: MatDialog, private toastService: ToastrService,
private joyrideService: JoyrideService) {}
columns = [
{ columnDef: 'id', header: 'ID', cell: (task: any) => task.id },
{ columnDef: 'name', header: 'Nombre de tarea', cell: (task: any) => task.name },
{ columnDef: 'organizationalUnit', header: 'Ámbito', cell: (task: any) => task.organizationalUnit.name },
{ columnDef: 'management', header: 'Gestiones', cell: (task: any) => task.schedules },
{ columnDef: 'nextExecution', header: 'Próxima ejecución', cell: (task: any) => this.datePipe.transform(task.nextExecution, 'dd/MM/yyyy HH:mm:ss', 'UTC') },
{ columnDef: 'createdBy', header: 'Creado por', cell: (task: any) => task.createdBy },
];
displayedColumns: string[] = ['id', 'name', 'organizationalUnit', 'management', 'nextExecution', 'createdBy', 'actions'];
loading: boolean = false;
private apiUrl: string;
constructor(private http: HttpClient, private dialog: MatDialog, private toastService: ToastrService,
private configService: ConfigService,
private joyrideService: JoyrideService) {
this.baseUrl = this.configService.apiUrl;
this.apiUrl = `${this.baseUrl}/command-tasks`;
}
ngOnInit(): void {
this.loadTasks();
@ -51,24 +74,25 @@ export class CommandsTaskComponent implements OnInit {
);
}
viewTaskDetails(task: any): void {
this.dialog.open(DetailTaskComponent, {
width: '800px',
data: { task },
}).afterClosed().subscribe(() => this.loadTasks());
}
openCreateTaskModal(): void {
this.dialog.open(CreateTaskComponent, {
width: '800px',
}).afterClosed().subscribe(() => this.loadTasks());
}).afterClosed().subscribe(result => {
if (result) {
this.loadTasks();
}
})
}
editTask(task: any): void {
this.dialog.open(CreateTaskComponent, {
width: '800px',
data: { task },
}).afterClosed().subscribe(() => this.loadTasks());
}).afterClosed().subscribe(result => {
if (result) {
this.loadTasks();
}
})
}
deleteTask(task: any): void {
@ -90,12 +114,66 @@ export class CommandsTaskComponent implements OnInit {
});
}
manageScheduleAction(task: any): void {
this.dialog.open(CreateTaskScheduleComponent, {
width: '800px',
data: { task },
}).afterClosed().subscribe( result => {
if (result) {
this.loadTasks();
}
})
}
manageScriptAction(task: any): void {
this.dialog.open(CreateTaskScriptComponent, {
width: '900px',
data: { task },
}).afterClosed().subscribe( result => {
if (result) {
this.loadTasks();
}
})
}
onPageChange(event: any): void {
this.page = event.pageIndex + 1;
this.itemsPerPage = event.pageSize;
this.loadTasks();
}
openShowScheduleDialog(commandTask: any) {
const dialogRef = this.dialog.open(ShowTaskScheduleComponent, {
width: '85vw',
height: '85vh',
maxWidth: '85vw',
maxHeight: '85vh',
data: { commandTask: commandTask }
});
dialogRef.afterClosed().subscribe(result => {
if (result) {
this.loadTasks();
}
});
}
openShowScriptDialog(commandTask: any) {
const dialogRef = this.dialog.open(ShowTaskScriptComponent, {
width: '85vw',
height: '85vh',
maxWidth: '85vw',
maxHeight: '85vh',
data: { commandTask: commandTask }
});
dialogRef.afterClosed().subscribe(result => {
if (result) {
this.loadTasks();
}
});
}
iniciarTour(): void {
this.joyrideService.startTour({
steps: [
@ -110,5 +188,5 @@ export class CommandsTaskComponent implements OnInit {
themeColor: '#3f51b5'
});
}
}

View File

@ -0,0 +1,128 @@
.dialog-title {
font-weight: bold;
}
.task-form {
padding: 20px;
}
.full-width {
width: 100%;
}
.custom-time {
display: flex;
gap: 15px;
}
.w-half {
width: 50%;
}
mat-form-field {
margin-bottom: 16px;
}
form {
display: flex;
flex-direction: column;
gap: 16px;
margin: auto;
}
mat-form-field {
width: 100%;
}
.action-container {
display: flex;
justify-content: flex-end;
gap: 1em;
padding: 1.5em;
}
.weekday-toggle-group {
display: flex;
justify-content: space-between;
gap: 8px;
margin-bottom: 16px;
}
.weekday-toggle {
flex: 1;
padding: 8px 0;
border: 1px solid #ccc;
border-radius: 4px;
background: #f5f5f5;
cursor: pointer;
font-weight: 500;
transition: background 0.2s ease;
}
.weekday-toggle.selected {
background: #1976d2;
color: white;
border-color: #1976d2;
}
.month-toggle-group {
display: flex;
flex-wrap: wrap;
gap: 6px;
margin-bottom: 16px;
}
.month-toggle-row {
display: flex;
gap: 8px;
margin-bottom: 8px;
justify-content: space-between;
}
.month-toggle {
flex: 1;
padding: 8px;
min-width: 48px;
border: 1px solid #ccc;
border-radius: 6px;
background-color: #f0f0f0;
text-align: center;
cursor: pointer;
transition: background-color 0.2s ease;
}
.month-toggle.selected {
background-color: #4caf50;
color: white;
border-color: #4caf50;
}
.summary-card {
background: #f9fafb;
border-left: 5px solid #3f51b5;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
border-radius: 12px;
padding: 16px;
transition: box-shadow 0.3s ease;
}
.summary-card:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.summary-card {
background-color: #e3f2fd;
border-left: 4px solid #2196f3;
padding: 12px 16px;
margin-top: 16px;
border-radius: 6px;
display: flex;
align-items: center;
gap: 8px;
font-size: 14px;
}
.summary-text {
color: #0d47a1;
line-height: 1.4;
}

View File

@ -0,0 +1,87 @@
<h2 mat-dialog-title class="dialog-title">Programar accion</h2>
<mat-dialog-content class="dialog-content">
<form [formGroup]="form" (ngSubmit)="onSubmit()" class="task-form">
<mat-form-field appearance="fill" class="w-full">
<mat-label>Repetición</mat-label>
<mat-select formControlName="recurrenceType">
<mat-option *ngFor="let type of recurrenceTypes" [value]="type">{{ type | titlecase }}</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field appearance="fill" class="w-full" *ngIf="form.get('recurrenceType')?.value === 'none'">
<mat-label>Fecha de ejecución</mat-label>
<input matInput [matDatepicker]="oneTimePicker" formControlName="executionDate">
<mat-datepicker-toggle matSuffix [for]="oneTimePicker"></mat-datepicker-toggle>
<mat-datepicker #oneTimePicker></mat-datepicker>
</mat-form-field>
<mat-form-field appearance="fill" class="w-full">
<mat-label>Hora</mat-label>
<input matInput formControlName="executionTime" placeholder="08:00" type="time">
</mat-form-field>
<!-- Mostrar solo si no es 'none' -->
<div *ngIf="form.get('recurrenceType')?.value !== 'none'" class="mb-4">
<label>Días de la semana:</label>
<div class="weekday-toggle-group">
<button
*ngFor="let day of weekDays"
type="button"
class="weekday-toggle"
[class.selected]="selectedDays[day]"
(click)="toggleDay(day)">
{{ day.slice(0, 3) }}
</button>
</div>
</div>
<!-- Selección de meses -->
<div *ngIf="form.get('recurrenceType')?.value !== 'none'" >
<label>Meses:</label>
<div class="month-toggle-row" *ngFor="let row of monthRows">
<button
*ngFor="let month of row"
type="button"
class="month-toggle"
[class.selected]="selectedMonths[month]"
(click)="toggleMonth(month)">
{{ month.slice(0, 3) }}
</button>
</div>
</div>
<!-- Rango de fechas -->
<div *ngIf="form.get('recurrenceType')?.value !== 'none'" class="custom-time" formGroupName="recurrenceDetails">
<mat-form-field appearance="fill" class="w-half">
<mat-label>Desde</mat-label>
<input matInput [matDatepicker]="fromPicker" formControlName="initDate">
<mat-datepicker-toggle matSuffix [for]="fromPicker"></mat-datepicker-toggle>
<mat-datepicker #fromPicker></mat-datepicker>
</mat-form-field>
<mat-form-field appearance="fill" class="w-half">
<mat-label>Hasta</mat-label>
<input matInput [matDatepicker]="toPicker" formControlName="endDate">
<mat-datepicker-toggle matSuffix [for]="toPicker"></mat-datepicker-toggle>
<mat-datepicker #toPicker></mat-datepicker>
</mat-form-field>
</div>
<mat-checkbox formControlName="enabled">Activar tarea</mat-checkbox>
<mat-card *ngIf="summaryText" class="summary-card">
<mat-icon color="primary" style="width: 50px;">info</mat-icon>
<span class="summary-text">
{{ summaryText }}
</span>
</mat-card>
</form>
</mat-dialog-content>
<mat-dialog-actions class="action-container">
<button class="ordinary-button" (click)="onCancel()">{{ 'buttonCancel' | translate }}</button>
<button class="submit-button" (click)="onSubmit()" >{{ 'buttonSave' | translate }}</button>
</mat-dialog-actions>

View File

@ -0,0 +1,100 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { CreateTaskScheduleComponent } from './create-task-schedule.component';
import {LoadingComponent} from "../../../../shared/loading/loading.component";
import {HttpClientTestingModule} from "@angular/common/http/testing";
import {ToastrModule} from "ngx-toastr";
import {BrowserAnimationsModule} from "@angular/platform-browser/animations";
import {MatDividerModule} from "@angular/material/divider";
import {MatFormFieldModule} from "@angular/material/form-field";
import {MatInputModule} from "@angular/material/input";
import {MatIconModule} from "@angular/material/icon";
import {MatButtonModule} from "@angular/material/button";
import {MatTableModule} from "@angular/material/table";
import {MatPaginatorModule} from "@angular/material/paginator";
import {MatTooltipModule} from "@angular/material/tooltip";
import {FormsModule, ReactiveFormsModule} from "@angular/forms";
import {MatProgressSpinnerModule} from "@angular/material/progress-spinner";
import {MAT_DIALOG_DATA, MatDialogModule, MatDialogRef} from "@angular/material/dialog";
import {MatSelectModule} from "@angular/material/select";
import {MatTabsModule} from "@angular/material/tabs";
import {MatAutocompleteModule} from "@angular/material/autocomplete";
import {MatListModule} from "@angular/material/list";
import {MatCardModule} from "@angular/material/card";
import {MatMenuModule} from "@angular/material/menu";
import {MatTreeModule} from "@angular/material/tree";
import {TranslateModule, TranslateService} from "@ngx-translate/core";
import {JoyrideModule} from "ngx-joyride";
import {ConfigService} from "@services/config.service";
import {ActivatedRoute} from "@angular/router";
import {MatDatepickerModule} from "@angular/material/datepicker";
import {MatButtonToggleModule} from "@angular/material/button-toggle";
import {
DateAdapter,
MAT_DATE_FORMATS,
MAT_NATIVE_DATE_FORMATS,
MatNativeDateModule,
provideNativeDateAdapter
} from "@angular/material/core";
import {MatCheckboxModule} from "@angular/material/checkbox";
describe('CreateTaskScheduleComponent', () => {
let component: CreateTaskScheduleComponent;
let fixture: ComponentFixture<CreateTaskScheduleComponent>;
beforeEach(async () => {
const mockConfigService = {
apiUrl: 'http://mock-api-url',
mercureUrl: 'http://mock-mercure-url'
};
await TestBed.configureTestingModule({
declarations: [CreateTaskScheduleComponent, LoadingComponent],
imports: [
HttpClientTestingModule,
ToastrModule.forRoot(),
BrowserAnimationsModule,
MatDividerModule,
MatFormFieldModule,
MatInputModule,
MatIconModule,
MatButtonModule,
MatTableModule,
MatPaginatorModule,
MatTooltipModule,
FormsModule,
ReactiveFormsModule,
MatProgressSpinnerModule,
MatDialogModule,
MatSelectModule,
MatTabsModule,
MatAutocompleteModule,
MatListModule,
MatCardModule,
MatMenuModule,
MatTreeModule,
MatDatepickerModule,
MatButtonToggleModule,
MatNativeDateModule,
MatCheckboxModule,
TranslateModule.forRoot(),
JoyrideModule.forRoot(),
],
providers: [
{ provide: MatDialogRef, useValue: {} },
{ provide: MAT_DIALOG_DATA, useValue: { data: { id: 123 } } },
{ provide: ConfigService, useValue: mockConfigService },
{ provide: ActivatedRoute, useValue: { queryParams: { subscribe: () => {} } } },
{ provide: MAT_DATE_FORMATS, useValue: MAT_NATIVE_DATE_FORMATS },
]
}).compileComponents();
fixture = TestBed.createComponent(CreateTaskScheduleComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,203 @@
import {Component, Inject, OnInit} from '@angular/core';
import {FormBuilder, FormGroup} from "@angular/forms";
import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog";
import {HttpClient} from "@angular/common/http";
import {ToastrService} from "ngx-toastr";
import {ConfigService} from "@services/config.service";
@Component({
selector: 'app-create-task-schedule',
templateUrl: './create-task-schedule.component.html',
styleUrl: './create-task-schedule.component.css'
})
export class CreateTaskScheduleComponent implements OnInit{
form: FormGroup;
baseUrl: string;
apiUrl: string;
recurrenceTypes = ['none', 'custom'];
weekDays: string[] = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'];
isSingleDateSelected: boolean = true;
monthsList: string[] = [
'january', 'february', 'march', 'april', 'may', 'june',
'july', 'august', 'september', 'october', 'november', 'december'
];
monthRows: string[][] = [];
editing: boolean = false;
selectedMonths: { [key: string]: boolean } = {};
selectedDays: { [key: string]: boolean } = {};
constructor(
private fb: FormBuilder,
public dialogRef: MatDialogRef<CreateTaskScheduleComponent>,
private http: HttpClient,
private toastr: ToastrService,
private configService: ConfigService,
@Inject(MAT_DIALOG_DATA) public data: any
) {
this.baseUrl = this.configService.apiUrl;
this.apiUrl = `${this.baseUrl}/command-task-schedules`;
this.form = this.fb.group({
executionDate: [new Date()],
executionTime: ['08:00'],
recurrenceType: ['none'],
recurrenceDetails: this.fb.group({
daysOfWeek: [[]],
months: this.fb.control([]),
initDate: [null],
endDate: [null]
}),
enabled: [true]
});
if (this.data.schedule) {
this.editing = true;
this.loadData();
}
this.form.get('recurrenceType')?.valueChanges.subscribe((value) => {
if (value === 'none') {
this.form.get('recurrenceDetails')?.disable();
} else {
this.form.get('recurrenceDetails')?.enable();
}
});
}
ngOnInit(): void {
this.monthRows = [
this.monthsList.slice(0, 6),
this.monthsList.slice(6, 12)
];
}
loadData(): void {
this.http.get<any>(`${this.baseUrl}${this.data.schedule['@id']}`).subscribe(
(data) => {
const formattedExecutionTime = this.formatExecutionTime(data.executionTime);
this.form.patchValue({
executionDate: data.executionDate,
executionTime: formattedExecutionTime,
recurrenceType: data.recurrenceType,
recurrenceDetails: {
...data.recurrenceDetails,
initDate: data.recurrenceDetails.initDate || null,
endDate: data.recurrenceDetails.endDate || null,
daysOfWeek: data.recurrenceDetails.daysOfWeek || [],
months: data.recurrenceDetails.months || []
},
enabled: data.enabled
});
this.selectedDays = data.recurrenceDetails.daysOfWeek.reduce((acc: any, day: string) => {
acc[day] = true;
return acc;
}, {});
this.selectedMonths = data.recurrenceDetails.months.reduce((acc: any, month: string) => {
acc[month] = true;
return acc;
}, {});
},
(error) => {
console.error('Error loading schedule data', error);
}
);
}
formatExecutionTime(time: string | Date): string {
const date = (time instanceof Date) ? time : new Date(time);
if (isNaN(date.getTime())) {
console.error('Invalid execution time:', time);
return '';
}
return date.toISOString().substring(11, 16);
}
convertDateToLocalISO(date: Date): string {
date = new Date(date);
const adjustedDate = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()));
return adjustedDate.toISOString();
}
onSubmit() {
const formData = this.form.value;
const payload: any = {
commandTask: this.data.task['@id'],
executionDate: formData.recurrenceType === 'none' ? this.convertDateToLocalISO(formData.executionDate) : null,
executionTime: formData.executionTime,
recurrenceType: formData.recurrenceType,
recurrenceDetails: {
...formData.recurrenceDetails,
initDate: formData.recurrenceDetails?.initDate || null,
endDate: formData.recurrenceDetails?.endDate || null,
daysOfWeek: formData.recurrenceDetails?.daysOfWeek || [],
months: formData.recurrenceDetails?.months || []
},
enabled: formData.enabled
}
if (this.editing) {
const taskId = this.data.task.uuid;
this.http.patch<any>(`${this.baseUrl}${this.data.schedule['@id']}`, payload).subscribe({
next: () => {
this.toastr.success('Programacion de tarea actualizada con éxito');
this.dialogRef.close(true);
},
error: () => {
this.toastr.error('Error al actualizar la tarea');
}
});
} else {
this.http.post<any>(this.apiUrl, payload).subscribe({
next: () => {
this.toastr.success('Programacion de tarea creada con éxito');
this.dialogRef.close(true);
},
error: () => {
this.toastr.error('Error al crear la tarea');
}
});
}
}
onCancel(): void {
this.dialogRef.close(false);
}
get summaryText(): string {
const recurrence = this.form.get('recurrenceType')?.value;
const start = this.form.get('recurrenceType')?.value === 'none' ? this.form.get('executionDate')?.value : this.form.get('recurrenceDetails.initDate')?.value;
const end = this.form.get('recurrenceType')?.value === 'none' ? this.form.get('executionDate')?.value : this.form.get('recurrenceDetails.endDate')?.value;
const time = this.form.get('executionTime')?.value;
const days = Object.keys(this.selectedDays).filter(day => this.selectedDays[day]);
const months = Object.keys(this.selectedMonths).filter(month => this.selectedMonths[month]);
if (recurrence === 'none') {
return `Esta acción se ejecutará una sola vez el ${ this.formatDate(start)} a las ${time}.`;
}
return `Esta acción se ejecutará todos los ${days.join(', ')} de ${months.join(', ')}, desde el ${this.formatDate(start)} hasta el ${this.formatDate(end)} a las ${time}.`;
}
formatDate(date: string | Date): string {
const realDate = (date instanceof Date) ? date : new Date(date);
return new Intl.DateTimeFormat('es-ES', { dateStyle: 'long' }).format(realDate);
}
toggleDay(day: string) {
this.selectedDays[day] = !this.selectedDays[day];
const days = Object.keys(this.selectedDays).filter(d => this.selectedDays[d]);
this.form.get('recurrenceDetails.daysOfWeek')?.setValue(days);
}
toggleMonth(month: string) {
this.selectedMonths[month] = !this.selectedMonths[month];
const months = Object.keys(this.selectedMonths).filter(m => this.selectedMonths[m]);
this.form.get('recurrenceDetails.months')?.setValue(months);
}
}

View File

@ -0,0 +1,284 @@
.divider {
margin: 20px 0;
}
table {
width: 100%;
margin-top: 50px;
}
.task-form {
padding: 20px;
}
.search-container {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px;
box-sizing: border-box;
}
.deploy-container {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px;
gap: 10px;
}
.script-container {
gap: 20px;
padding: 20px;
background-color: #eaeff6;
border-radius: 12px;
margin-top: 20px;
}
.script-content {
flex: 2;
min-width: 60%;
}
.script-params {
flex: 1;
min-width: 35%;
}
@media (max-width: 768px) {
.script-container {
flex-direction: column;
}
.script-content, .script-params {
min-width: 100%;
}
}
.select-container {
margin-top: 20px;
align-items: center;
padding: 20px;
box-sizing: border-box;
}
.input-group {
display: flex;
flex-wrap: wrap;
gap: 16px;
margin-top: 20px;
}
.input-field {
flex: 1 1 calc(33.33% - 16px);
min-width: 250px;
}
.script-preview {
background-color: #f4f4f4;
border: 1px solid #ccc;
padding: 10px;
border-radius: 5px;
font-family: monospace;
white-space: pre-wrap;
min-height: 50px;
}
.custom-width {
width: 50%;
margin-bottom: 16px;
}
.search-string {
flex: 2;
padding: 5px;
}
.search-boolean {
flex: 1;
padding: 5px;
}
.header-container {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 10px;
border-bottom: 1px solid #ddd;
}
.mat-elevation-z8 {
box-shadow: 0px 0px 0px rgba(0,0,0,0.2);
}
.paginator-container {
display: flex;
justify-content: end;
margin-bottom: 30px;
}
.clients-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
gap: 8px;
}
.client-item {
position: relative;
}
.client-card {
background: #ffffff;
border-radius: 6px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
overflow: hidden;
position: relative;
padding: 8px;
text-align: center;
cursor: pointer;
transition: background-color 0.3s, transform 0.2s;
&:hover {
background-color: #f0f0f0;
transform: scale(1.02);
}
}
.client-details {
margin-top: 4px;
}
.client-name {
font-size: 0.9em;
font-weight: 600;
color: #333;
margin-bottom: 5px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 150px;
display: inline-block;
}
.client-ip {
display: block;
font-size: 0.9em;
color: #666;
}
.header-container-title {
flex-grow: 1;
text-align: left;
padding-left: 1em;
}
.button-row {
display: flex;
padding-right: 1em;
}
.client-card {
background: #ffffff;
border-radius: 6px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
overflow: hidden;
position: relative;
padding: 8px;
text-align: center;
cursor: pointer;
transition: background-color 0.3s, transform 0.2s;
&:hover {
background-color: #f0f0f0;
transform: scale(1.02);
}
}
::ng-deep .custom-tooltip {
white-space: pre-line !important;
max-width: 200px;
background: rgba(0, 0, 0, 0.8);
color: white;
padding: 8px;
border-radius: 4px;
}
.selected-client {
background-color: #a0c2e5 !important;
color: white !important;
}
.button-row {
display: flex;
padding-right: 1em;
}
.disabled-client {
pointer-events: none;
opacity: 0.5;
}
.action-button {
margin-top: 10px;
margin-bottom: 10px;
}
.action-container {
display: flex;
justify-content: flex-end;
gap: 1em;
padding: 1.5em;
}
.mat-expansion-panel-header-description {
justify-content: space-between;
align-items: center;
}
.new-command-container {
display: flex;
flex-direction: column;
gap: 10px;
padding: 15px;
background-color: #eaeff6;
border: 1px solid #ddd;
border-radius: 8px;
box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.1);
margin-top: 15px;
}
.new-command-container mat-form-field {
width: 100%;
}
.new-command-container textarea {
font-family: monospace;
resize: vertical;
}
.new-command-container .action-button {
align-self: flex-end;
color: white;
padding: 8px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
}
.full-width {
width: 100%;
}
.script-selector-card {
margin: 20px 20px;
padding: 16px;
}
.toggle-options {
display: flex;
justify-content: start;
margin: 16px 0;
}

View File

@ -0,0 +1,65 @@
<h2 mat-dialog-title class="dialog-title">Añadir acción a: {{ data.task?.name }}</h2>
<mat-dialog-content class="dialog-content">
<div class="task-form">
<div class="toggle-options">
<mat-button-toggle-group [(ngModel)]="commandType" exclusive>
<mat-button-toggle value="new">
<mat-icon>edit</mat-icon> Nuevo Script
</mat-button-toggle>
<mat-button-toggle value="existing">
<mat-icon>storage</mat-icon> Script Guardado
</mat-button-toggle>
</mat-button-toggle-group>
</div>
<div *ngIf="commandType === 'new'" class="new-command-container">
<mat-form-field appearance="fill" class="custom-width">
<mat-label>Orden de ejecucion </mat-label>
<input matInput type="number" [(ngModel)]="executionOrder" placeholder="Orden de ejecución">
</mat-form-field>
<mat-form-field appearance="fill" class="full-width">
<mat-label>Ingrese el script</mat-label>
<textarea matInput [(ngModel)]="newScript" rows="6" placeholder="Escriba su script aquí"></textarea>
</mat-form-field>
<button mat-flat-button color="primary" (click)="saveNewScript()">Guardar Script</button>
</div>
<div *ngIf="commandType === 'existing'">
<mat-form-field appearance="fill" class="custom-width">
<mat-label>Seleccione script a ejecutar</mat-label>
<mat-select [(ngModel)]="selectedScript" (selectionChange)="onScriptChange()">
<mat-option *ngFor="let script of scripts" [value]="script">{{ script.name }}</mat-option>
</mat-select>
</mat-form-field>
</div>
<div *ngIf="selectedScript && commandType === 'existing'" class="script-container">
<mat-form-field appearance="fill" class="custom-width">
<mat-label>Orden de ejecucion </mat-label>
<input matInput type="number" [(ngModel)]="executionOrder" placeholder="Orden de ejecución">
</mat-form-field>
<div class="script-content">
<h3>Script:</h3>
<div class="script-preview" [innerHTML]="scriptContent"></div>
</div>
<div class="script-params" *ngIf="parameterNames.length > 0">
<h3>Ingrese los parámetros:</h3>
<div *ngFor="let paramName of parameterNames">
<mat-form-field appearance="fill" class="full-width">
<mat-label>{{ paramName }}</mat-label>
<input matInput [ngModel]="parameters[paramName]" (ngModelChange)="onParamChange(paramName, $event)" placeholder="Valor para {{ paramName }}">
</mat-form-field>
</div>
</div>
</div>
</div>
</mat-dialog-content>
<mat-dialog-actions class="action-container">
<button class="ordinary-button" (click)="onCancel()">{{ 'buttonCancel' | translate }}</button>
<button class="submit-button" (click)="onSubmit()" >{{ 'buttonSave' | translate }}</button>
</mat-dialog-actions>

View File

@ -0,0 +1,89 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { CreateTaskScriptComponent } from './create-task-script.component';
import {GroupsComponent} from "../../../groups/groups.component";
import {ExecuteCommandComponent} from "../../main-commands/execute-command/execute-command.component";
import {LoadingComponent} from "../../../../shared/loading/loading.component";
import {HttpClientTestingModule} from "@angular/common/http/testing";
import {ToastrModule} from "ngx-toastr";
import {BrowserAnimationsModule} from "@angular/platform-browser/animations";
import {MatDividerModule} from "@angular/material/divider";
import {MatFormFieldModule} from "@angular/material/form-field";
import {MatInputModule} from "@angular/material/input";
import {MatIconModule} from "@angular/material/icon";
import {MatButtonModule} from "@angular/material/button";
import {MatTableModule} from "@angular/material/table";
import {MatPaginatorModule} from "@angular/material/paginator";
import {MatTooltipModule} from "@angular/material/tooltip";
import {FormsModule, ReactiveFormsModule} from "@angular/forms";
import {MatProgressSpinnerModule} from "@angular/material/progress-spinner";
import {MAT_DIALOG_DATA, MatDialogModule, MatDialogRef} from "@angular/material/dialog";
import {MatSelectModule} from "@angular/material/select";
import {MatTabsModule} from "@angular/material/tabs";
import {MatAutocompleteModule} from "@angular/material/autocomplete";
import {MatListModule} from "@angular/material/list";
import {MatCardModule} from "@angular/material/card";
import {MatMenuModule} from "@angular/material/menu";
import {MatTreeModule} from "@angular/material/tree";
import {TranslateModule, TranslateService} from "@ngx-translate/core";
import {JoyrideModule} from "ngx-joyride";
import {ConfigService} from "@services/config.service";
import {ActivatedRoute} from "@angular/router";
import {MatButtonToggleModule} from "@angular/material/button-toggle";
describe('CreateTaskScriptComponent', () => {
let component: CreateTaskScriptComponent;
let fixture: ComponentFixture<CreateTaskScriptComponent>;
beforeEach(async () => {
const mockConfigService = {
apiUrl: 'http://mock-api-url',
mercureUrl: 'http://mock-mercure-url'
};
await TestBed.configureTestingModule({
declarations: [CreateTaskScriptComponent, LoadingComponent],
imports: [
HttpClientTestingModule,
ToastrModule.forRoot(),
BrowserAnimationsModule,
MatDividerModule,
MatFormFieldModule,
MatInputModule,
MatIconModule,
MatButtonModule,
MatTableModule,
MatPaginatorModule,
MatTooltipModule,
FormsModule,
ReactiveFormsModule,
MatProgressSpinnerModule,
MatDialogModule,
MatSelectModule,
MatTabsModule,
MatAutocompleteModule,
MatListModule,
MatCardModule,
MatMenuModule,
MatButtonToggleModule,
MatTreeModule,
TranslateModule.forRoot(),
JoyrideModule.forRoot(),
],
providers: [
{ provide: MatDialogRef, useValue: {} },
{ provide: MAT_DIALOG_DATA, useValue: { data: { id: 123 } } },
{ provide: ConfigService, useValue: mockConfigService },
{ provide: ActivatedRoute, useValue: { queryParams: { subscribe: () => {} } } },
]
}).compileComponents();
fixture = TestBed.createComponent(CreateTaskScriptComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,138 @@
import {Component, EventEmitter, Inject, OnInit, Output} from '@angular/core';
import {SelectionModel} from "@angular/cdk/collections";
import {HttpClient} from "@angular/common/http";
import {ToastrService} from "ngx-toastr";
import {ConfigService} from "@services/config.service";
import {ActivatedRoute, Router} from "@angular/router";
import {MAT_DIALOG_DATA, MatDialog, MatDialogRef} from "@angular/material/dialog";
import {
SaveScriptComponent
} from "../../../groups/components/client-main-view/run-script-assistant/save-script/save-script.component";
import {FormBuilder, FormGroup} from "@angular/forms";
@Component({
selector: 'app-create-task-script',
templateUrl: './create-task-script.component.html',
styleUrl: './create-task-script.component.css'
})
export class CreateTaskScriptComponent implements OnInit {
form: FormGroup;
baseUrl: string;
@Output() dataChange = new EventEmitter<any>();
errorMessage = '';
loading: boolean = false;
scripts: any[] = [];
scriptContent: string = "";
parameters: any = {};
commandType: string = 'existing';
selectedScript: any = null;
newScript: string = '';
executionOrder: Number = 0;
selection = new SelectionModel(true, []);
parameterNames: string[] = Object.keys(this.parameters);
constructor(
private fb: FormBuilder,
private http: HttpClient,
public dialogRef: MatDialogRef<CreateTaskScriptComponent>,
private toastService: ToastrService,
private configService: ConfigService,
private router: Router,
private dialog: MatDialog,
private route: ActivatedRoute,
@Inject(MAT_DIALOG_DATA) public data: any
) {
this.baseUrl = this.configService.apiUrl;
this.loadScripts()
this.form = this.fb.group({
content: [''],
order: [''],
})
}
ngOnInit(): void {
}
loadScripts(): void {
this.loading = true;
this.http.get(`${this.baseUrl}/commands?readOnly=false&enabled=true`).subscribe((data: any) => {
this.scripts = data['hydra:member'];
this.loading = false;
}, (error) => {
this.toastService.error(error.error['hydra:description']);
this.loading = false;
});
}
saveNewScript() {
if (!this.newScript.trim()) {
this.toastService.error('Debe ingresar un script antes de guardar.');
return;
}
const dialogRef = this.dialog.open(SaveScriptComponent, {
width: '400px',
data: this.newScript
});
dialogRef.afterClosed().subscribe(result => {
if (result) {
this.toastService.success('Script guardado correctamente');
}
});
}
onScriptChange() {
if (this.selectedScript) {
this.scriptContent = this.selectedScript.script;
const matches = this.scriptContent.match(/@(\w+)/g) || [];
const uniqueParams = Array.from(new Set(matches.map(m => m.slice(1))));
this.parameters = {};
uniqueParams.forEach(param => this.parameters[param] = '');
this.parameterNames = uniqueParams;
this.updateScript();
}
}
onParamChange(name: string, value: string): void {
this.parameters[name] = value;
this.updateScript();
}
updateScript(): void {
let updatedScript = this.selectedScript.script;
for (const [key, value] of Object.entries(this.parameters)) {
const regex = new RegExp(`@${key}\\b`, 'g');
updatedScript = updatedScript.replace(regex, value || `@${key}`);
}
this.scriptContent = updatedScript;
}
onSubmit() {
this.http.post(`${this.baseUrl}/command-task-scripts`, {
commandTask: this.data.task['@id'],
content: this.commandType === 'existing' ? this.scriptContent : this.newScript,
order: this.executionOrder,
type: 'run-script',
}).subscribe({
next: () => {
this.toastService.success('Tarea creada con éxito');
this.dialogRef.close(true);
},
error: (error) => {
this.toastService.error(error.error['hydra:description']);
}
})
}
onCancel(): void {
this.dialogRef.close(false);
}
}

View File

@ -6,22 +6,60 @@
padding: 20px;
}
.select-task {
padding: 20px;
margin-bottom: 16px;
}
.full-width {
width: 100%;
}
.button-container {
display: flex;
justify-content: flex-end;
margin-top: 20px;
}
mat-form-field {
margin-bottom: 16px;
}
.loading-spinner {
display: block;
margin: 0 auto;
align-items: center;
justify-content: center;
}
.section-title {
margin-top: 24px;
margin-bottom: 8px;
font-weight: 500;
}
.summary-section {
background-color: #f9f9f9;
border-bottom: 1px solid #ddd;
margin-bottom: 10px;
}
.summary-block {
margin-top: 10px;
}
.date-time-row {
display: flex;
gap: 16px;
margin-top: 12px;
}
.half-width {
flex: 1;
min-width: 0;
}
.full-width {
width: 100%;
}
.action-container {
display: flex;
justify-content: flex-end;
gap: 1em;
padding: 1.5em;
}

View File

@ -1,111 +1,83 @@
<h2 mat-dialog-title class="dialog-title">{{ editing ? ('editTask' | translate) : ('createTask' | translate) }}</h2>
<h2 mat-dialog-title class="dialog-title">
{{ editing ? ('editTask' | translate) : ('createTask' | translate) }}
</h2>
<form [formGroup]="taskForm" class="task-form">
<mat-dialog-content>
<mat-dialog-content class="dialog-content">
<mat-spinner class="loading-spinner" *ngIf="loading"></mat-spinner>
<h3 class="section-title">Información</h3>
<mat-divider></mat-divider>
<!-- Toggle entre crear o añadir -->
<mat-radio-group *ngIf="data?.source === 'assistant'" [(ngModel)]="taskMode" class="task-mode-selection" name="taskMode">
<mat-radio-button value="create">Crear tarea</mat-radio-button>
<mat-radio-button value="add">Introducir en tarea existente</mat-radio-button>
</mat-radio-group>
<!-- Selección de tarea existente -->
<div *ngIf="taskMode === 'add'" class="select-task">
<mat-form-field appearance="fill" class="full-width">
<mat-label>Información</mat-label>
<textarea matInput formControlName="notes" placeholder="Ingresa tus notas aquí"></textarea>
<mat-label>Seleccione una tarea</mat-label>
<mat-select [(ngModel)]="selectedExistingTask" name="existingTask">
<mat-option *ngFor="let task of existingTasks" [value]="task">{{ task.name }}</mat-option>
</mat-select>
</mat-form-field>
<h3 class="section-title">{{ 'informationSectionTitle' | translate }}</h3>
<mat-divider></mat-divider>
<mat-form-field appearance="fill" class="full-width">
<mat-label>{{ 'informationLabel' | translate }}</mat-label>
<mat-label>Orden de ejecución</mat-label>
<input
matInput
type="number"
[(ngModel)]="executionOrder"
name="executionOrder"
min="1"
placeholder="Introduce el orden"
>
</mat-form-field>
</div>
<!-- Formulario de nueva tarea -->
<form *ngIf="taskMode === 'create' && taskForm && !loading" [formGroup]="taskForm" class="task-form">
<mat-form-field appearance="fill" class="full-width">
<mat-label>{{ 'nameLabel' | translate }}</mat-label>
<input matInput formControlName="name" placeholder="{{ 'nameLabel' | translate }}">
<mat-error *ngIf="taskForm.get('name')?.invalid">{{ 'requiredFieldError' | translate }}</mat-error>
</mat-form-field>
<mat-form-field appearance="fill" class="full-width">
<mat-label>{{ 'notesLabel' | translate }}</mat-label>
<textarea matInput formControlName="notes" placeholder="{{ 'notesPlaceholder' | translate }}"></textarea>
</mat-form-field>
<h3 class="section-title">{{ 'commandSelectionSectionTitle' | translate }}</h3>
<mat-divider></mat-divider>
<mat-form-field appearance="fill" class="full-width">
<mat-label>{{ 'selectCommandsLabel' | translate }}</mat-label>
<mat-select formControlName="commandGroup" (selectionChange)="onCommandGroupChange()">
<mat-option *ngFor="let group of availableCommandGroups" [value]="group.uuid">
{{ group.name }}
</mat-option>
</mat-select>
<mat-error *ngIf="taskForm.get('commandGroup')?.invalid">{{ 'requiredFieldError' | translate }}</mat-error>
</mat-form-field>
<mat-form-field appearance="fill" class="full-width">
<mat-label>{{ 'selectIndividualCommandsLabel' | translate }}</mat-label>
<mat-select formControlName="extraCommands" multiple>
<mat-option *ngFor="let command of availableIndividualCommands" [value]="command.uuid">
{{ command.name }}
</mat-option>
<mat-label>Ámbito</mat-label>
<mat-select formControlName="scope" (selectionChange)="onScopeChange($event.value)">
<mat-option value="organizational-unit">Unidad Organizativa</mat-option>
<mat-option value="classrooms-group">Grupo de aulas</mat-option>
<mat-option value="classroom">Aulas</mat-option>
<mat-option value="clients-group">Grupos de clientes</mat-option>
</mat-select>
</mat-form-field>
<h3 class="section-title">{{ 'executionDateTimeSectionTitle' | translate }}</h3>
<mat-divider></mat-divider>
<mat-form-field appearance="fill" class="full-width">
<mat-label>{{ 'executionDateLabel' | translate }}</mat-label>
<input matInput [matDatepicker]="picker" formControlName="date" placeholder="{{ 'selectDatePlaceholder' | translate }}">
<mat-datepicker-toggle matSuffix [for]="picker"></mat-datepicker-toggle>
<mat-datepicker #picker></mat-datepicker>
<mat-error *ngIf="taskForm.get('date')?.invalid">{{ 'requiredFieldError' | translate }}</mat-error>
</mat-form-field>
<mat-form-field appearance="fill" class="full-width">
<mat-label>{{ 'executionTimeLabel' | translate }}</mat-label>
<input matInput type="time" formControlName="time" placeholder="{{ 'selectTimePlaceholder' | translate }}">
<mat-error *ngIf="taskForm.get('time')?.invalid">{{ 'requiredFieldError' | translate }}</mat-error>
</mat-form-field>
<h3 class="section-title">{{ 'destinationSelectionSectionTitle' | translate }}</h3>
<mat-divider></mat-divider>
<mat-form-field appearance="fill" class="full-width">
<mat-label>{{ 'selectOrganizationalUnitLabel' | translate }}</mat-label>
<mat-select formControlName="organizationalUnit" (selectionChange)="onOrganizationalUnitChange()">
<mat-label>{{ 'organizationalUnitLabel' | translate }}</mat-label>
<mat-select formControlName="organizationalUnit">
<mat-option *ngFor="let unit of availableOrganizationalUnits" [value]="unit['@id']">
{{ unit.name }}
</mat-option>
</mat-select>
<mat-error *ngIf="taskForm.get('organizationalUnit')?.invalid">{{ 'requiredFieldError' | translate }}</mat-error>
</mat-form-field>
<mat-form-field appearance="fill" class="full-width">
<mat-label>{{ 'selectClassroomLabel' | translate }}</mat-label>
<mat-select formControlName="selectedChild" (selectionChange)="onChildChange()">
<mat-option *ngFor="let child of selectedUnitChildren" [value]="child['@id']">
{{ child.name }}
<div class="unit-name">{{ unit.name }}</div>
<div style="font-size: smaller; color: gray;">{{ unit.path }}</div>
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field appearance="fill" class="full-width">
<mat-label>{{ 'selectClientsLabel' | translate }}</mat-label>
<mat-select formControlName="selectedClients" multiple>
<mat-option (click)="toggleSelectAll()" [selected]="areAllSelected()">
{{ 'selectAllClients' | translate }}
</mat-option>
<mat-option *ngFor="let client of selectedClients" [value]="client.uuid">
{{ client.name }} ({{ client.ip }})
</mat-option>
</mat-select>
</mat-form-field>
<mat-checkbox *ngIf="!editing" formControlName="scheduleAfterCreate">
¿Quieres programar la tarea al finalizar su creación?
</mat-checkbox>
</form>
</mat-dialog-content>
<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-label>Selecciona Clientes</mat-label>
<mat-select formControlName="selectedClients" multiple>
<mat-option (click)="toggleSelectAll()" [selected]="areAllSelected()">
Seleccionar todos
</mat-option>
<mat-option *ngFor="let client of selectedClients" [value]="client.uuid">
{{ client.name }} ({{ client.ip }})
</mat-option>
</mat-select>
</mat-form-field>
<div class="button-container">
<button mat-raised-button color="primary" (click)="saveTask()">Guardar</button>
</div>
</mat-dialog-content>
</form>
<mat-dialog-actions class="action-container">
<button class="ordinary-button" (click)="close()">{{ 'buttonCancel' | translate }}</button>
<button
class="submit-button"
(click)="taskMode === 'create' ? saveTask() : addToExistingTask()"
>
{{ 'buttonSave' | translate }}
</button>
</mat-dialog-actions>

View File

@ -1,8 +1,12 @@
import { Component, OnInit, Inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import {MAT_DIALOG_DATA, MatDialog, MatDialogRef} from '@angular/material/dialog';
import { ToastrService } from 'ngx-toastr';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { ConfigService } from '@services/config.service';
import {of} from "rxjs";
import {startWith, switchMap} from "rxjs/operators";
import {CreateTaskScheduleComponent} from "../create-task-schedule/create-task-schedule.component";
@Component({
selector: 'app-create-task',
@ -10,176 +14,193 @@ import { FormBuilder, FormGroup, Validators } from '@angular/forms';
styleUrls: ['./create-task.component.css']
})
export class CreateTaskComponent implements OnInit {
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL;
baseUrl: string;
taskForm: FormGroup;
availableCommandGroups: any[] = [];
selectedGroupCommands: any[] = [];
availableIndividualCommands: any[] = [];
apiUrl = `${this.baseUrl}/command-tasks`;
apiUrl: string;
editing: boolean = false;
availableOrganizationalUnits: any[] = [];
selectedUnitChildren: any[] = [];
selectedClients: any[] = [];
selectedClientIds: Set<string> = new Set();
clients: any[] = [];
allOrganizationalUnits: any[] = [];
loading: boolean = false;
taskMode: 'create' | 'add' = 'create';
existingTasks: any[] = [];
selectedExistingTask: string | null = null;
executionOrder: number | null = null;
constructor(
private fb: FormBuilder,
private http: HttpClient,
private configService: ConfigService,
private toastr: ToastrService,
private dialog: MatDialog,
public dialogRef: MatDialogRef<CreateTaskComponent>,
@Inject(MAT_DIALOG_DATA) public data: any
) {
this.baseUrl = this.configService.apiUrl;
this.apiUrl = `${this.baseUrl}/command-tasks`;
this.taskForm = this.fb.group({
commandGroup: ['', Validators.required],
extraCommands: [[]],
date: ['', Validators.required],
time: ['', Validators.required],
scope: [ this.data?.scope ? this.data.scope : '', Validators.required],
name: ['', Validators.required],
organizationalUnit: [ this.data?.organizationalUnit ? this.data.organizationalUnit : null, Validators.required],
notes: [''],
organizationalUnit: ['', Validators.required],
selectedChild: [''],
selectedClients: [[]]
scheduleAfterCreate: [false]
});
}
ngOnInit(): void {
this.loadCommandGroups();
this.loadIndividualCommands();
this.loadOrganizationalUnits();
if (this.data && this.data.task) {
this.editing = true;
this.loadTaskData(this.data.task);
}
this.loading = true;
const observables = [
this.loadCommandGroups(),
this.loadIndividualCommands(),
this.loadOrganizationalUnits(),
this.startUnitsFilter(),
this.loadTasks()
];
Promise.all(observables).then(() => {
if (this.data.task) {
this.editing = true;
this.loadData().then(() => {
this.loading = false;
})
} else {
this.loading = false;
}
}).catch(() => {
this.loading = false;
})
}
loadCommandGroups(): void {
loadData(): Promise<void> {
return new Promise((resolve, reject) => {
this.http.get<any>(`${this.baseUrl}${this.data.task['@id']}`).subscribe(
(data) => {
this.taskForm.patchValue({
name: data.name,
scope: data.scope,
organizationalUnit: data.organizationalUnit ? data.organizationalUnit['@id'] : null,
notes: data.notes,
});
resolve();
},
(error) => {
this.toastr.error('Error al cargar los datos de la tarea');
reject(error);
}
);
})
}
loadTasks(): Promise<void> {
return new Promise((resolve, reject) => {
this.http.get<any>(`${this.apiUrl}?page=1&itemsPerPage=100`).subscribe(
(data) => {
this.existingTasks = data['hydra:member'];
resolve();
},
(error) => {
this.toastr.error('Error al cargar las tareas existentes');
reject(error);
}
);
});
}
onScopeChange(scope: string): void {
this.filterUnits(scope).subscribe(filteredUnits => {
this.availableOrganizationalUnits = filteredUnits;
this.taskForm.get('organizationalUnit')?.setValue('');
});
}
startUnitsFilter(): Promise<void> {
return new Promise((resolve, reject) => {
this.taskForm.get('scope')?.valueChanges.pipe(
startWith(this.taskForm.get('scope')?.value),
switchMap((value) => this.filterUnits(value))
).subscribe(filteredUnits => {
this.availableOrganizationalUnits = filteredUnits;
resolve();
}, error => {
this.toastr.error('Error al filtrar las unidades organizacionales');
reject(error);
});
})
}
filterUnits(value: string) {
const filtered = this.allOrganizationalUnits.filter(unit => unit.type === value);
return of(filtered);
}
loadCommandGroups(): Promise<void> {
return new Promise((resolve, reject) => {
this.http.get<any>(`${this.baseUrl}/command-groups`).subscribe(
(data) => {
this.availableCommandGroups = data['hydra:member'];
resolve();
},
(error) => {
this.toastr.error('Error al cargar los grupos de comandos');
}
);
}
loadIndividualCommands(): void {
this.http.get<any>(`${this.baseUrl}/commands`).subscribe(
(data) => {
this.availableIndividualCommands = data['hydra:member'];
},
(error) => {
this.toastr.error('Error al cargar los comandos individuales');
}
);
}
loadOrganizationalUnits(): void {
this.http.get<any>(`${this.baseUrl}/organizational-units?page=1&itemsPerPage=30`).subscribe(
(data) => {
this.availableOrganizationalUnits = data['hydra:member'].filter((unit: any) => unit['type'] === 'organizational-unit');
},
(error) => {
this.toastr.error('Error al cargar las unidades organizacionales');
}
);
}
loadTaskData(task: any): void {
this.taskForm.patchValue({
commandGroup: task.commandGroup ? task.commandGroup['@id'] : '',
extraCommands: task.commands ? task.commands.map((cmd: any) => cmd['@id']) : [],
date: task.dateTime ? task.dateTime.split('T')[0] : '',
time: task.dateTime ? task.dateTime.split('T')[1].slice(0, 5) : '',
notes: task.notes || '',
organizationalUnit: task.organizationalUnit ? task.organizationalUnit['@id'] : ''
reject(error);
});
});
if (task.commandGroup) {
this.selectedGroupCommands = task.commandGroup.commands;
}
}
private collectClassrooms(unit: any): any[] {
let classrooms = [];
if (unit.type === 'classroom') {
classrooms.push(unit);
}
if (unit.children && unit.children.length > 0) {
for (let child of unit.children) {
classrooms = classrooms.concat(this.collectClassrooms(child));
}
}
return classrooms;
loadIndividualCommands(): Promise<void> {
return new Promise((resolve, reject) => {
this.http.get<any>(`${this.baseUrl}/commands`).subscribe(
(data) => {
this.availableIndividualCommands = data['hydra:member'];
resolve();
},
(error) => {
this.toastr.error('Error al cargar los comandos individuales');
reject(error);
});
});
}
onOrganizationalUnitChange(): void {
const selectedUnitId = this.taskForm.get('organizationalUnit')?.value;
const selectedUnit = this.availableOrganizationalUnits.find(unit => unit['@id'] === selectedUnitId);
if (selectedUnit) {
this.selectedUnitChildren = this.collectClassrooms(selectedUnit);
} else {
this.selectedUnitChildren = [];
}
this.taskForm.patchValue({ selectedChild: '', selectedClients: [] });
this.selectedClients = [];
this.selectedClientIds.clear();
loadOrganizationalUnits(): Promise<void> {
return new Promise((resolve, reject) => {
this.http.get<any>(`${this.baseUrl}/organizational-units?page=1&itemsPerPage=100`).subscribe(
(data) => {
this.allOrganizationalUnits = data['hydra:member'];
this.availableOrganizationalUnits = [...this.allOrganizationalUnits];
resolve();
},
(error) => {
this.toastr.error('Error al cargar las unidades organizacionales');
reject(error);
}
);
});
}
onChildChange(): void {
const selectedChildId = this.taskForm.get('selectedChild')?.value;
if (!selectedChildId) {
this.selectedClients = [];
addToExistingTask() {
if (!this.selectedExistingTask) {
this.toastr.error('Debes seleccionar una tarea existente.');
return;
}
const url = `${this.baseUrl}${selectedChildId}`.replace(/([^:]\/)\/+/g, '$1');
this.http.get<any>(url).subscribe(
(data) => {
if (Array.isArray(data.clients) && data.clients.length > 0) {
this.selectedClients = data.clients;
} else {
this.selectedClients = [];
this.toastr.warning('El aula seleccionada no tiene clientes.');
}
this.taskForm.patchValue({ selectedClients: [] });
this.selectedClientIds.clear();
},
(error) => {
this.toastr.error('Error al cargar los detalles del aula seleccionada');
}
);
}
toggleSelectAll() {
const allSelected = this.areAllSelected();
if (allSelected) {
this.selectedClientIds.clear();
} else {
this.selectedClients.forEach(client => this.selectedClientIds.add(client.uuid));
if (this.executionOrder == null || this.executionOrder < 1) {
this.toastr.error('Debes introducir un orden de ejecución válido (mayor que 0).');
return;
}
this.taskForm.get('selectedClients')!.setValue(Array.from(this.selectedClientIds));
const data = {
taskId: this.selectedExistingTask,
executionOrder: this.executionOrder
};
this.toastr.success('Tarea actualizada con éxito');
this.dialogRef.close(data);
}
areAllSelected(): boolean {
return this.selectedClients.length > 0 && this.selectedClients.every(client => this.selectedClientIds.has(client.uuid));
}
onCommandGroupChange(): void {
const selectedGroupId = this.taskForm.get('commandGroup')?.value;
this.http.get<any>(`${this.baseUrl}/command-groups/${selectedGroupId}`).subscribe(
(data) => {
this.selectedGroupCommands = data.commands;
},
(error) => {
this.toastr.error('Error al cargar los comandos del grupo seleccionado');
}
);
}
saveTask(): void {
if (this.taskForm.invalid) {
@ -188,22 +209,14 @@ export class CreateTaskComponent implements OnInit {
}
const formData = this.taskForm.value;
const dateTime = this.combineDateAndTime(formData.date, formData.time);
const selectedCommands = formData.extraCommands && formData.extraCommands.length > 0
? formData.extraCommands.map((id: any) => `/commands/${id}`)
: null;
const payload: any = {
commandGroups: formData.commandGroup ? [`/command-groups/${formData.commandGroup}`] : null,
dateTime: dateTime,
name: formData.name,
scope: formData.scope,
organizationalUnit: formData.organizationalUnit,
notes: formData.notes || '',
clients: Array.from(this.selectedClientIds).map((uuid: string) => `/clients/${uuid}`),
};
if (selectedCommands) {
payload.commands = selectedCommands;
}
if (this.editing) {
const taskId = this.data.task.uuid;
this.http.patch<any>(`${this.apiUrl}/${taskId}`, payload).subscribe({
@ -217,9 +230,21 @@ export class CreateTaskComponent implements OnInit {
});
} else {
this.http.post<any>(this.apiUrl, payload).subscribe({
next: () => {
next: response => {
this.toastr.success('Tarea creada con éxito');
this.dialogRef.close(true);
this.dialogRef.close(response);
if (formData.scheduleAfterCreate) {
const dialogRef = this.dialog.open(CreateTaskScheduleComponent, {
width: '800px',
data: { task: response }
});
dialogRef.afterClosed().subscribe(result => {
if (result) {
this.toastr.success('Tarea programada correctamente');
}
});
}
},
error: () => {
this.toastr.error('Error al crear la tarea');
@ -228,14 +253,7 @@ export class CreateTaskComponent implements OnInit {
}
}
combineDateAndTime(date: string, time: string): string {
const dateObj = new Date(date);
const [hours, minutes] = time.split(':').map(Number);
dateObj.setHours(hours, minutes, 0);
return dateObj.toISOString();
}
close(): void {
this.dialogRef.close();
this.dialogRef.close(false);
}
}

View File

@ -57,26 +57,4 @@
justify-content: flex-end;
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>
<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>

View File

@ -0,0 +1,89 @@
.full-width {
width: 100%;
}
form {
padding: 20px;
}
.spacing-container {
margin-top: 20px;
margin-bottom: 16px;
}
.list-item-content {
display: flex;
align-items: flex-start;
justify-content: space-between;
width: 100%;
}
.text-content {
flex-grow: 1;
margin-right: 16px;
margin-left: 10px;
}
.icon-container {
display: flex;
align-items: center;
}
.right-icon {
margin-left: 8px;
cursor: pointer;
}
.action-container {
display: flex;
justify-content: flex-end;
gap: 1em;
padding: 1.5em;
}
.lists-container {
padding: 16px;
}
.search-container {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
margin: 1.5rem 0rem 1.5rem 0rem;
box-sizing: border-box;
}
.search-string {
flex: 1;
padding: 5px;
}
.search-select {
flex: 1;
padding: 5px;
}
table {
width: 100%;
}
.mat-elevation-z8 {
box-shadow: 0px 0px 0px rgba(0, 0, 0, 0.2);
}
.paginator-container {
display: flex;
justify-content: end;
margin-bottom: 30px;
}
mat-spinner {
margin: 0 auto;
align-self: center;
}
.subnets-button-row {
display: flex;
gap: 15px;
}

View File

@ -0,0 +1,100 @@
<app-loading [isLoading]="loading"></app-loading>
<h2 mat-dialog-title>Gestionar programaciones de tareas en {{ data.commandTask?.name }}</h2>
<mat-dialog-content>
<div class="search-container">
<mat-form-field appearance="fill" class="search-string" joyrideStep="searchNameStep"
text="Busca subredes por nombre para localizar una subred específica rápidamente.">
<mat-label i18n="@@searchLabel">Buscar nombre del cliente</mat-label>
<input matInput placeholder="Búsqueda" [(ngModel)]="filters['name']" i18n-placeholder="@@searchPlaceholder"
(keyup.enter)="loadData()" i18n-placeholder="@@searchPlaceholder">
<mat-icon matSuffix>search</mat-icon>
<button *ngIf="filters['name']" mat-icon-button matSuffix aria-label="Clear tree search"
(click)="filters['name'] = ''; loadData()">
<mat-icon>close</mat-icon>
</button>
<mat-hint i18n="@@searchHint">Pulsar 'enter' para buscar</mat-hint>
</mat-form-field>
<mat-form-field appearance="fill" class="search-string" joyrideStep="searchIpStep" text="Busca programaciones por tipo.">
<mat-label i18n="@@searchLabel">Buscar por tipo</mat-label>
<input matInput placeholder="Búsqueda" [(ngModel)]="filters['recurrence']" i18n-placeholder="@@searchPlaceholder"
(keyup.enter)="loadData()" i18n-placeholder="@@searchPlaceholder">
<mat-icon matSuffix>search</mat-icon>
<button *ngIf="filters['ip']" mat-icon-button matSuffix aria-label="Clear tree search"
(click)="filters['ip'] = ''; loadData()">
<mat-icon>close</mat-icon>
</button>
<mat-hint i18n="@@searchHint">Pulsar 'enter' para buscar</mat-hint>
</mat-form-field>
</div>
<app-loading [isLoading]="loading"></app-loading>
<table *ngIf="!loading" mat-table [dataSource]="dataSource" class="mat-elevation-z8" joyrideStep="tableStep"
text="Visualiza y administra las subredes listadas según los filtros aplicados.">
<ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef">
<th mat-header-cell *matHeaderCellDef> {{ column.header }} </th>
<td mat-cell *matCellDef="let schedule">
<ng-container *ngIf="column.columnDef === 'recurrenceType'">
<mat-chip style="padding: 10px; margin: 5px;">
<ng-container *ngIf="column.cell(schedule) === 'none'; else scheduledTemplate">
No programado
<div style="font-size: 12px;">
{{ schedule.executionDate | date }}
</div>
</ng-container>
<ng-template #scheduledTemplate>
Programado
<div style="font-size: 12px;">
{{ schedule.recurrenceDetails.initDate | date }} → {{ schedule.recurrenceDetails.endDate | date}}
</div>
</ng-template>
</mat-chip>
</ng-container>
<ng-container *ngIf="column.columnDef === 'executionTime'">
{{ schedule.executionTime | date: 'HH:mm' }}
</ng-container>
<ng-container *ngIf="column.columnDef !== 'recurrenceType' && column.columnDef !== 'executionTime' && column.columnDef !== 'enabled'">
{{ column.cell(schedule) }}
</ng-container>
<ng-container *ngIf="column.columnDef === 'enabled'">
<mat-chip>
<ng-container *ngIf="schedule.enabled">
Activo
</ng-container>
<ng-container *ngIf="!schedule.enabled">
Inactivo
</ng-container>
</mat-chip>
</ng-container>
</td>
</ng-container>
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef i18n="@@columnActions" style="text-align: center;">Acciones</th>
<td mat-cell *matCellDef="let schedule" style="text-align: center;">
<button mat-icon-button color="primary" (click)="editSchedule(schedule)">
<mat-icon i18n="@@deleteElementTooltip">edit</mat-icon>
</button>
<button mat-icon-button color="warn" (click)="deleteSchedule(schedule)">
<mat-icon i18n="@@deleteElementTooltip">delete</mat-icon>
</button>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
<div class="paginator-container" joyrideStep="paginationStep"
text="Navega entre las páginas de subredes usando el paginador.">
<mat-paginator [length]="length" [pageSize]="itemsPerPage" [pageIndex]="page" [pageSizeOptions]="pageSizeOptions"
(page)="onPageChange($event)">
</mat-paginator>
</div>
</mat-dialog-content>
<mat-dialog-actions class="action-container">
<button class="ordinary-button" (click)="onNoClick()">Cerrar</button>
</mat-dialog-actions>

View File

@ -0,0 +1,86 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ShowTaskScheduleComponent } from './show-task-schedule.component';
import {LoadingComponent} from "../../../../shared/loading/loading.component";
import {HttpClientTestingModule} from "@angular/common/http/testing";
import {ToastrModule} from "ngx-toastr";
import {BrowserAnimationsModule} from "@angular/platform-browser/animations";
import {MatDividerModule} from "@angular/material/divider";
import {MatFormFieldModule} from "@angular/material/form-field";
import {MatInputModule} from "@angular/material/input";
import {MatIconModule} from "@angular/material/icon";
import {MatButtonModule} from "@angular/material/button";
import {MatTableModule} from "@angular/material/table";
import {MatPaginatorModule} from "@angular/material/paginator";
import {MatTooltipModule} from "@angular/material/tooltip";
import {FormsModule, ReactiveFormsModule} from "@angular/forms";
import {MatProgressSpinnerModule} from "@angular/material/progress-spinner";
import {MAT_DIALOG_DATA, MatDialogModule, MatDialogRef} from "@angular/material/dialog";
import {MatSelectModule} from "@angular/material/select";
import {MatTabsModule} from "@angular/material/tabs";
import {MatAutocompleteModule} from "@angular/material/autocomplete";
import {MatListModule} from "@angular/material/list";
import {MatCardModule} from "@angular/material/card";
import {MatMenuModule} from "@angular/material/menu";
import {MatTreeModule} from "@angular/material/tree";
import {TranslateModule} from "@ngx-translate/core";
import {JoyrideModule} from "ngx-joyride";
import {ConfigService} from "@services/config.service";
import {ActivatedRoute} from "@angular/router";
import {CreateTaskScheduleComponent} from "../create-task-schedule/create-task-schedule.component";
describe('ShowTaskScheduleComponent', () => {
let component: ShowTaskScheduleComponent;
let fixture: ComponentFixture<ShowTaskScheduleComponent>;
beforeEach(async () => {
const mockConfigService = {
apiUrl: 'http://mock-api-url',
mercureUrl: 'http://mock-mercure-url'
};
await TestBed.configureTestingModule({
declarations: [ShowTaskScheduleComponent, LoadingComponent],
imports: [
HttpClientTestingModule,
ToastrModule.forRoot(),
BrowserAnimationsModule,
MatDividerModule,
MatFormFieldModule,
MatInputModule,
MatIconModule,
MatButtonModule,
MatTableModule,
MatPaginatorModule,
MatTooltipModule,
FormsModule,
ReactiveFormsModule,
MatProgressSpinnerModule,
MatDialogModule,
MatSelectModule,
MatTabsModule,
MatAutocompleteModule,
MatListModule,
MatCardModule,
MatMenuModule,
MatTreeModule,
TranslateModule.forRoot(),
JoyrideModule.forRoot(),
],
providers: [
{ provide: MatDialogRef, useValue: {} },
{ provide: MAT_DIALOG_DATA, useValue: { data: { id: 123 } } },
{ provide: ConfigService, useValue: mockConfigService },
{ provide: ActivatedRoute, useValue: { queryParams: { subscribe: () => {} } } },
]
}).compileComponents();
fixture = TestBed.createComponent(ShowTaskScheduleComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,107 @@
import {Component, Inject, OnInit} from '@angular/core';
import {MatTableDataSource} from "@angular/material/table";
import {Client} from "../../../groups/model/model";
import {ToastrService} from "ngx-toastr";
import {HttpClient} from "@angular/common/http";
import {MAT_DIALOG_DATA, MatDialog, MatDialogRef} from "@angular/material/dialog";
import {ConfigService} from "@services/config.service";
import {DeleteModalComponent} from "../../../../shared/delete_modal/delete-modal/delete-modal.component";
import {CreateTaskScheduleComponent} from "../create-task-schedule/create-task-schedule.component";
import {DatePipe} from "@angular/common";
@Component({
selector: 'app-show-task-schedule',
templateUrl: './show-task-schedule.component.html',
styleUrl: './show-task-schedule.component.css'
})
export class ShowTaskScheduleComponent implements OnInit{
baseUrl: string;
dataSource = new MatTableDataSource<any>([]);
length = 0;
itemsPerPage: number = 10;
pageSizeOptions: number[] = [5, 10, 20];
page = 0;
loading: boolean = false;
filters: { [key: string]: string } = {};
datePipe: DatePipe = new DatePipe('es-ES');
columns = [
{ columnDef: 'id', header: 'ID', cell: (schedule: any) => schedule.id },
{ columnDef: 'recurrenceType', header: 'Recurrencia', cell: (schedule: any) => schedule.recurrenceType },
{ columnDef: 'time', header: 'Hora de ejecución', cell: (schedule: any) => this.datePipe.transform(schedule.executionTime, 'HH:mm', 'UTC') },
{ columnDef: 'daysOfWeek', header: 'Dias de la semana', cell: (schedule: any) => schedule.recurrenceDetails.daysOfWeek },
{ columnDef: 'months', header: 'Meses', cell: (schedule: any) => schedule.recurrenceDetails.months },
{ columnDef: 'enabled', header: 'Activo', cell: (schedule: any) => schedule.enabled }
];
displayedColumns: string[] = ['id', 'recurrenceType', 'time', 'daysOfWeek', 'months', 'enabled', 'actions'];
constructor(
private toastService: ToastrService,
private http: HttpClient,
public dialogRef: MatDialogRef<ShowTaskScheduleComponent>,
public dialog: MatDialog,
private configService: ConfigService,
@Inject(MAT_DIALOG_DATA) public data: any
) {
this.baseUrl = this.configService.apiUrl;
}
ngOnInit(): void {
if (this.data) {
this.loadData();
}
}
loadData() {
this.loading = true;
this.http.get<any>(`${this.baseUrl}/command-task-schedules?page=${this.page + 1}&itemsPerPage=${this.itemsPerPage}&commandTask.id=${this.data.commandTask?.id}`, { params: this.filters }).subscribe(
(data) => {
this.dataSource.data = data['hydra:member'];
this.length = data['hydra:totalItems'];
this.loading = false;
},
(error) => {
this.loading = false;
}
);
}
editSchedule(schedule: any): void {
this.dialog.open(CreateTaskScheduleComponent, {
width: '800px',
data: { schedule: schedule, task: this.data.commandTask }
}).afterClosed().subscribe(() => this.loadData());
}
deleteSchedule(schedule: any): void {
const dialogRef = this.dialog.open(DeleteModalComponent, {
width: '300px',
data: { name: 'tarea programada' }
});
dialogRef.afterClosed().subscribe(result => {
if (result) {
this.http.delete(`${this.baseUrl}${schedule['@id']}`).subscribe(
() => {
this.toastService.success('Programación eliminada correctamente');
this.loadData();
},
(error) => {
this.toastService.error(error.error['hydra:description']);
}
);
}
})
}
onNoClick(): void {
this.dialogRef.close(false);
}
onPageChange(event: any) {
this.page = event.pageIndex;
this.itemsPerPage = event.pageSize;
this.loadData()
}
}

View File

@ -0,0 +1,89 @@
.full-width {
width: 100%;
}
form {
padding: 20px;
}
.spacing-container {
margin-top: 20px;
margin-bottom: 16px;
}
.list-item-content {
display: flex;
align-items: flex-start;
justify-content: space-between;
width: 100%;
}
.text-content {
flex-grow: 1;
margin-right: 16px;
margin-left: 10px;
}
.icon-container {
display: flex;
align-items: center;
}
.right-icon {
margin-left: 8px;
cursor: pointer;
}
.action-container {
display: flex;
justify-content: flex-end;
gap: 1em;
padding: 1.5em;
}
.lists-container {
padding: 16px;
}
.search-container {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
margin: 1.5rem 0rem 1.5rem 0rem;
box-sizing: border-box;
}
.search-string {
flex: 1;
padding: 5px;
}
.search-select {
flex: 1;
padding: 5px;
}
table {
width: 100%;
}
.mat-elevation-z8 {
box-shadow: 0px 0px 0px rgba(0, 0, 0, 0.2);
}
.paginator-container {
display: flex;
justify-content: end;
margin-bottom: 30px;
}
mat-spinner {
margin: 0 auto;
align-self: center;
}
.subnets-button-row {
display: flex;
gap: 15px;
}

View File

@ -0,0 +1,70 @@
<app-loading [isLoading]="loading"></app-loading>
<h2 mat-dialog-title>Gestionar scripts de tareas en {{ data.commandTask?.name }}</h2>
<mat-dialog-content>
<div class="search-container">
<mat-form-field appearance="fill" class="search-string" joyrideStep="searchNameStep"
text="Busca subredes por nombre para localizar una subred específica rápidamente.">
<mat-label i18n="@@searchLabel">Buscar contenido de script</mat-label>
<input matInput placeholder="Búsqueda" [(ngModel)]="filters['name']" i18n-placeholder="@@searchPlaceholder"
(keyup.enter)="loadData()" i18n-placeholder="@@searchPlaceholder">
<mat-icon matSuffix>search</mat-icon>
<button *ngIf="filters['name']" mat-icon-button matSuffix aria-label="Clear tree search"
(click)="filters['name'] = ''; loadData()">
<mat-icon>close</mat-icon>
</button>
<mat-hint i18n="@@searchHint">Pulsar 'enter' para buscar</mat-hint>
</mat-form-field>
</div>
<app-loading [isLoading]="loading"></app-loading>
<table *ngIf="!loading" mat-table [dataSource]="dataSource" class="mat-elevation-z8" joyrideStep="tableStep"
text="Visualiza y administra las subredes listadas según los filtros aplicados.">
<ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef">
<th mat-header-cell *matHeaderCellDef> {{ column.header }} </th>
<td mat-cell *matCellDef="let schedule">
<ng-container *ngIf="column.columnDef === 'content'; else checkOtherColumn">
<div style="background-color: #f5f5f5; padding: 8px; border: 1px solid #ccc; border-radius: 4px; font-family: monospace; white-space: pre-wrap; font-size: 13px;">
{{ column.cell(schedule) }}
</div>
</ng-container>
<ng-template #checkOtherColumn>
<ng-container *ngIf="column.columnDef === 'parameters'; else normalCell">
<button mat-stroked-button color="primary" (click)="openParametersModal(schedule.parameters)">
Ver parámetros
</button>
</ng-container>
</ng-template>
<ng-template #normalCell>
{{ column.cell(schedule) }}
</ng-template>
</td>
</ng-container>
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef i18n="@@columnActions" style="text-align: center;">Acciones</th>
<td mat-cell *matCellDef="let schedule" style="text-align: center;">
<button mat-icon-button color="warn" (click)="deleteTaskScript(schedule)">
<mat-icon i18n="@@deleteElementTooltip">delete</mat-icon>
</button>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
<div class="paginator-container" joyrideStep="paginationStep"
text="Navega entre las páginas de subredes usando el paginador.">
<mat-paginator [length]="length" [pageSize]="itemsPerPage" [pageIndex]="page" [pageSizeOptions]="pageSizeOptions"
(page)="onPageChange($event)">
</mat-paginator>
</div>
</mat-dialog-content>
<mat-dialog-actions class="action-container">
<button class="ordinary-button" (click)="onNoClick()">Cerrar</button>
</mat-dialog-actions>

View File

@ -0,0 +1,86 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ShowTaskScriptComponent } from './show-task-script.component';
import {LoadingComponent} from "../../../../shared/loading/loading.component";
import {HttpClientTestingModule} from "@angular/common/http/testing";
import {ToastrModule} from "ngx-toastr";
import {BrowserAnimationsModule} from "@angular/platform-browser/animations";
import {MatDividerModule} from "@angular/material/divider";
import {MatFormFieldModule} from "@angular/material/form-field";
import {MatInputModule} from "@angular/material/input";
import {MatIconModule} from "@angular/material/icon";
import {MatButtonModule} from "@angular/material/button";
import {MatTableModule} from "@angular/material/table";
import {MatPaginatorModule} from "@angular/material/paginator";
import {MatTooltipModule} from "@angular/material/tooltip";
import {FormsModule, ReactiveFormsModule} from "@angular/forms";
import {MatProgressSpinnerModule} from "@angular/material/progress-spinner";
import {MAT_DIALOG_DATA, MatDialogModule, MatDialogRef} from "@angular/material/dialog";
import {MatSelectModule} from "@angular/material/select";
import {MatTabsModule} from "@angular/material/tabs";
import {MatAutocompleteModule} from "@angular/material/autocomplete";
import {MatListModule} from "@angular/material/list";
import {MatCardModule} from "@angular/material/card";
import {MatMenuModule} from "@angular/material/menu";
import {MatTreeModule} from "@angular/material/tree";
import {TranslateModule} from "@ngx-translate/core";
import {JoyrideModule} from "ngx-joyride";
import {ConfigService} from "@services/config.service";
import {ActivatedRoute} from "@angular/router";
import {ShowTaskScheduleComponent} from "../show-task-schedule/show-task-schedule.component";
describe('ShowTaskScriptComponent', () => {
let component: ShowTaskScriptComponent;
let fixture: ComponentFixture<ShowTaskScriptComponent>;
beforeEach(async () => {
const mockConfigService = {
apiUrl: 'http://mock-api-url',
mercureUrl: 'http://mock-mercure-url'
};
await TestBed.configureTestingModule({
declarations: [ShowTaskScriptComponent, LoadingComponent],
imports: [
HttpClientTestingModule,
ToastrModule.forRoot(),
BrowserAnimationsModule,
MatDividerModule,
MatFormFieldModule,
MatInputModule,
MatIconModule,
MatButtonModule,
MatTableModule,
MatPaginatorModule,
MatTooltipModule,
FormsModule,
ReactiveFormsModule,
MatProgressSpinnerModule,
MatDialogModule,
MatSelectModule,
MatTabsModule,
MatAutocompleteModule,
MatListModule,
MatCardModule,
MatMenuModule,
MatTreeModule,
TranslateModule.forRoot(),
JoyrideModule.forRoot(),
],
providers: [
{ provide: MatDialogRef, useValue: {} },
{ provide: MAT_DIALOG_DATA, useValue: { data: { id: 123 } } },
{ provide: ConfigService, useValue: mockConfigService },
{ provide: ActivatedRoute, useValue: { queryParams: { subscribe: () => {} } } },
]
}).compileComponents();
fixture = TestBed.createComponent(ShowTaskScriptComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,104 @@
import {Component, Inject, OnInit} from '@angular/core';
import {MatTableDataSource} from "@angular/material/table";
import {ToastrService} from "ngx-toastr";
import {HttpClient} from "@angular/common/http";
import {MAT_DIALOG_DATA, MatDialog, MatDialogRef} from "@angular/material/dialog";
import {ConfigService} from "@services/config.service";
import {CreateTaskScheduleComponent} from "../create-task-schedule/create-task-schedule.component";
import {DeleteModalComponent} from "../../../../shared/delete_modal/delete-modal/delete-modal.component";
import {ViewParametersModalComponent} from "./view-parameters-modal/view-parameters-modal.component";
@Component({
selector: 'app-show-task-script',
templateUrl: './show-task-script.component.html',
styleUrl: './show-task-script.component.css'
})
export class ShowTaskScriptComponent implements OnInit{
baseUrl: string;
dataSource = new MatTableDataSource<any>([]);
length = 0;
itemsPerPage: number = 10;
pageSizeOptions: number[] = [5, 10, 20];
page = 0;
loading: boolean = false;
filters: { [key: string]: string } = {};
columns = [
{ columnDef: 'id', header: 'ID', cell: (client: any) => client.id },
{ columnDef: 'order', header: 'Orden', cell: (client: any) => client.order },
{ columnDef: 'content', header: 'Script', cell: (client: any) => client.content },
{ columnDef: 'type', header: 'Type', cell: (client: any) => client.type },
{ columnDef: 'parameters', header: 'Parameters', cell: (client: any) => client.parameters },
];
displayedColumns: string[] = ['id', 'order', 'type', 'parameters', 'content', 'actions'];
constructor(
private toastService: ToastrService,
private http: HttpClient,
public dialogRef: MatDialogRef<ShowTaskScriptComponent>,
public dialog: MatDialog,
private configService: ConfigService,
@Inject(MAT_DIALOG_DATA) public data: any
) {
this.baseUrl = this.configService.apiUrl;
}
ngOnInit(): void {
if (this.data) {
this.loadData();
}
}
loadData() {
this.loading = true;
this.http.get<any>(`${this.baseUrl}/command-task-scripts?page=${this.page + 1}&itemsPerPage=${this.itemsPerPage}&commandTask.id=${this.data.commandTask?.id}`, { params: this.filters }).subscribe(
(data) => {
this.dataSource.data = data['hydra:member'];
this.length = data['hydra:totalItems'];
this.loading = false;
},
(error) => {
this.loading = false;
}
);
}
deleteTaskScript(schedule: any): void {
const dialogRef = this.dialog.open(DeleteModalComponent, {
width: '300px',
data: { name: 'script de una tarea' }
});
dialogRef.afterClosed().subscribe(result => {
if (result) {
this.http.delete(`${this.baseUrl}${schedule['@id']}`).subscribe(
() => {
this.toastService.success('Eliminado correctamente');
this.loadData();
},
(error) => {
this.toastService.error(error.error['hydra:description']);
}
);
}
})
}
onNoClick(): void {
this.dialogRef.close(false);
}
openParametersModal(parameters: any): void {
this.dialog.open(ViewParametersModalComponent, {
width: '900px',
data: parameters
});
}
onPageChange(event: any) {
this.page = event.pageIndex;
this.itemsPerPage = event.pageSize;
this.loadData()
}
}

View File

@ -0,0 +1,7 @@
<h2 mat-dialog-title>Parámetros</h2>
<mat-dialog-content>
<pre>{{ data | json }}</pre>
</mat-dialog-content>
<mat-dialog-actions align="end">
<button mat-button (click)="close()">Cerrar</button>
</mat-dialog-actions>

View File

@ -0,0 +1,69 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ViewParametersModalComponent } from './view-parameters-modal.component';
import {LoadingComponent} from "../../../../../shared/loading/loading.component";
import {HttpClientTestingModule, provideHttpClientTesting} from "@angular/common/http/testing";
import {ToastrModule, ToastrService} from "ngx-toastr";
import {BrowserAnimationsModule} from "@angular/platform-browser/animations";
import {MatDividerModule} from "@angular/material/divider";
import {MatFormFieldModule} from "@angular/material/form-field";
import {MatInputModule} from "@angular/material/input";
import {MatIconModule} from "@angular/material/icon";
import {MatButtonModule} from "@angular/material/button";
import {MatTableModule} from "@angular/material/table";
import {MatPaginatorModule} from "@angular/material/paginator";
import {MatTooltipModule} from "@angular/material/tooltip";
import {FormBuilder, FormsModule, ReactiveFormsModule} from "@angular/forms";
import {MatProgressSpinnerModule} from "@angular/material/progress-spinner";
import {MAT_DIALOG_DATA, MatDialogModule, MatDialogRef} from "@angular/material/dialog";
import {MatSelectModule} from "@angular/material/select";
import {MatTabsModule} from "@angular/material/tabs";
import {MatAutocompleteModule} from "@angular/material/autocomplete";
import {MatListModule} from "@angular/material/list";
import {MatCardModule} from "@angular/material/card";
import {MatMenuModule} from "@angular/material/menu";
import {MatTreeModule} from "@angular/material/tree";
import {TranslateModule} from "@ngx-translate/core";
import {JoyrideModule} from "ngx-joyride";
import {ConfigService} from "@services/config.service";
import {ActivatedRoute} from "@angular/router";
import {InputDialogComponent} from "../../../../task-logs/input-dialog/input-dialog.component";
import {provideHttpClient} from "@angular/common/http";
describe('ViewParametersModalComponent', () => {
let component: ViewParametersModalComponent;
let fixture: ComponentFixture<ViewParametersModalComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ViewParametersModalComponent],
imports: [
MatDialogModule,
TranslateModule.forRoot(),
],
providers: [
FormBuilder,
ToastrService,
provideHttpClient(),
provideHttpClientTesting(),
{
provide: MatDialogRef,
useValue: {}
},
{
provide: MAT_DIALOG_DATA,
useValue: {}
}
]
})
.compileComponents();
fixture = TestBed.createComponent(ViewParametersModalComponent);
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-view-parameters-modal',
templateUrl: './view-parameters-modal.component.html',
styleUrl: './view-parameters-modal.component.css'
})
export class ViewParametersModalComponent {
constructor(
public dialogRef: MatDialogRef<ViewParametersModalComponent>,
@Inject(MAT_DIALOG_DATA) public data: any
) {}
close(): void {
this.dialogRef.close();
}
}

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