Merge branch 'develop' of ssh://ognproject.evlt.uma.es:21987/opengnsys/oggui into develop
testing/ogGui-multibranch/pipeline/head This commit is unstable
Details
testing/ogGui-multibranch/pipeline/head This commit is unstable
Details
commit
2eb08d0198
|
@ -24,6 +24,7 @@
|
||||||
"jwt-decode": "^4.0.0",
|
"jwt-decode": "^4.0.0",
|
||||||
"ngx-joyride": "^2.5.0",
|
"ngx-joyride": "^2.5.0",
|
||||||
"ngx-toastr": "^19.0.0",
|
"ngx-toastr": "^19.0.0",
|
||||||
|
"papaparse": "^5.4.1",
|
||||||
"rxjs": "~7.8.0",
|
"rxjs": "~7.8.0",
|
||||||
"tslib": "^2.3.0",
|
"tslib": "^2.3.0",
|
||||||
"zone.js": "^0.14.6"
|
"zone.js": "^0.14.6"
|
||||||
|
@ -35,6 +36,7 @@
|
||||||
"@angular/localize": "^18.1.0",
|
"@angular/localize": "^18.1.0",
|
||||||
"@ngx-env/builder": "^18.0.1",
|
"@ngx-env/builder": "^18.0.1",
|
||||||
"@types/jasmine": "~5.1.0",
|
"@types/jasmine": "~5.1.0",
|
||||||
|
"@types/papaparse": "^5.3.15",
|
||||||
"jasmine-core": "~5.1.0",
|
"jasmine-core": "~5.1.0",
|
||||||
"karma": "~6.4.0",
|
"karma": "~6.4.0",
|
||||||
"karma-chrome-launcher": "~3.2.0",
|
"karma-chrome-launcher": "~3.2.0",
|
||||||
|
@ -5902,6 +5904,15 @@
|
||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/papaparse": {
|
||||||
|
"version": "5.3.15",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/papaparse/-/papaparse-5.3.15.tgz",
|
||||||
|
"integrity": "sha512-JHe6vF6x/8Z85nCX4yFdDslN11d+1pr12E526X8WAfhadOeaOTx5AuIkvDKIBopfvlzpzkdMx4YyvSKCM9oqtw==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/qs": {
|
"node_modules/@types/qs": {
|
||||||
"version": "6.9.15",
|
"version": "6.9.15",
|
||||||
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.15.tgz",
|
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.15.tgz",
|
||||||
|
@ -11955,6 +11966,11 @@
|
||||||
"node": "^16.14.0 || >=18.0.0"
|
"node": "^16.14.0 || >=18.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/papaparse": {
|
||||||
|
"version": "5.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.4.1.tgz",
|
||||||
|
"integrity": "sha512-HipMsgJkZu8br23pW15uvo6sib6wne/4woLZPlFf3rpDyMe9ywEXUsuD7+6K9PRkJlVT51j/sCOYDKGGS3ZJrw=="
|
||||||
|
},
|
||||||
"node_modules/parent-module": {
|
"node_modules/parent-module": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
|
||||||
|
|
|
@ -26,6 +26,7 @@
|
||||||
"jwt-decode": "^4.0.0",
|
"jwt-decode": "^4.0.0",
|
||||||
"ngx-joyride": "^2.5.0",
|
"ngx-joyride": "^2.5.0",
|
||||||
"ngx-toastr": "^19.0.0",
|
"ngx-toastr": "^19.0.0",
|
||||||
|
"papaparse": "^5.4.1",
|
||||||
"rxjs": "~7.8.0",
|
"rxjs": "~7.8.0",
|
||||||
"tslib": "^2.3.0",
|
"tslib": "^2.3.0",
|
||||||
"zone.js": "^0.14.6"
|
"zone.js": "^0.14.6"
|
||||||
|
@ -37,6 +38,7 @@
|
||||||
"@angular/localize": "^18.1.0",
|
"@angular/localize": "^18.1.0",
|
||||||
"@ngx-env/builder": "^18.0.1",
|
"@ngx-env/builder": "^18.0.1",
|
||||||
"@types/jasmine": "~5.1.0",
|
"@types/jasmine": "~5.1.0",
|
||||||
|
"@types/papaparse": "^5.3.15",
|
||||||
"jasmine-core": "~5.1.0",
|
"jasmine-core": "~5.1.0",
|
||||||
"karma": "~6.4.0",
|
"karma": "~6.4.0",
|
||||||
"karma-chrome-launcher": "~3.2.0",
|
"karma-chrome-launcher": "~3.2.0",
|
||||||
|
|
|
@ -1,321 +1,559 @@
|
||||||
.groupLists-container {
|
.card-container {
|
||||||
display: flex;
|
display: grid;
|
||||||
flex-wrap: wrap;
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||||
height: auto;
|
gap: 20px;
|
||||||
margin-bottom: 30px;
|
padding: 20px;
|
||||||
}
|
|
||||||
|
|
||||||
.search-container {
|
|
||||||
display: flex;
|
|
||||||
flex-grow: 1;
|
|
||||||
margin: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-container mat-form-field {
|
|
||||||
width: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card {
|
|
||||||
flex-grow: 1;
|
|
||||||
margin: 10px;
|
|
||||||
|
|
||||||
border: 2px solid rgba(102, 102, 102, 0.103)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-container {
|
.header-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
height: 100px;
|
padding: 20px;
|
||||||
padding: 10px;
|
background-color: #f5f5f5;
|
||||||
}
|
border-bottom: 1px solid #ddd;
|
||||||
|
|
||||||
.unidad-card {
|
|
||||||
flex: 1 1 20%;
|
|
||||||
background-color: #fafafa;
|
|
||||||
height: 600px;
|
|
||||||
overflow-y: auto;
|
|
||||||
box-shadow: none !important;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.elements-card {
|
|
||||||
flex: 1 1 75%;
|
|
||||||
background-color: #fafafa;
|
|
||||||
height: 600px;
|
|
||||||
overflow-y: auto;
|
|
||||||
box-shadow: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.element-content {
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
margin-left: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.details-card, .classroom-view {
|
|
||||||
flex: 1 1 25%;
|
|
||||||
}
|
|
||||||
|
|
||||||
mat-card-title {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
margin: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title-with-breadcrumb {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
mat-card-subtitle {
|
|
||||||
font-size: 0.875rem;
|
|
||||||
color: rgba(0, 0, 0, 0.54);
|
|
||||||
}
|
|
||||||
|
|
||||||
mat-card-subtitle a {
|
|
||||||
cursor: pointer;
|
|
||||||
text-decoration: underline;
|
|
||||||
color: #929292;
|
|
||||||
}
|
|
||||||
|
|
||||||
mat-card-subtitle a:hover {
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.groups-button-row {
|
.groups-button-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 10px;
|
gap: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.item-content {
|
.button-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 100%;
|
justify-content: center;
|
||||||
|
margin: 20px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.item-content mat-icon {
|
button[mat-raised-button] {
|
||||||
margin-right: 10px;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 8px 16px;
|
||||||
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.clickable-item:hover {
|
mat-card {
|
||||||
|
background-color: #ffffff;
|
||||||
|
border-radius: 10px;
|
||||||
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||||
|
transition: transform 0.2s, box-shadow 0.2s;
|
||||||
|
overflow: hidden;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-card:hover {
|
||||||
|
transform: translateY(-4px);
|
||||||
|
box-shadow: 0 8px 12px rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.unidad-card {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
padding: 16px;
|
||||||
|
font-size: 14px;
|
||||||
.selected-item {
|
|
||||||
background-color: #e0e0e0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.actions {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
margin-left: auto;
|
flex-direction: column;
|
||||||
align-self: center;
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.unidad-card.selected-item {
|
||||||
|
border: 2px solid #1976d2;
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-card-title {
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-card-title mat-icon {
|
||||||
|
font-size: 20px;
|
||||||
|
margin-right: 8px;
|
||||||
|
color: #1976d2;
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-card-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 10px;
|
||||||
|
margin-top: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.actions mat-icon {
|
.actions mat-icon {
|
||||||
cursor: pointer;
|
|
||||||
margin-left: 16px;
|
|
||||||
color: #757575;
|
color: #757575;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: color 0.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.actions mat-icon:hover {
|
.actions mat-icon:hover {
|
||||||
color: #212121;
|
color: #1976d2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.empty-list {
|
.empty-list {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
height: 100%;
|
height: 200px;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #777;
|
||||||
}
|
}
|
||||||
|
|
||||||
mat-spinner {
|
.search-container {
|
||||||
margin: 0 auto;
|
|
||||||
align-self: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
gap: 20px;
|
||||||
}
|
margin: 20px 0;
|
||||||
.classroomBtn-container {
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
width: 100%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.container {
|
.search-container mat-form-field {
|
||||||
display: flex;
|
flex: 1;
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header mat-form-field {
|
|
||||||
width: 300px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.main-content {
|
|
||||||
display: flex;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.filters {
|
.filters {
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
display: flex;
|
background-color: #f9f9f9;
|
||||||
flex-direction: column;
|
border: 1px solid #ddd;
|
||||||
width: 300px;
|
border-radius: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.saved-filter {
|
.details-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
width: 300px;
|
gap: 20px;
|
||||||
margin-bottom: 10px;
|
padding: 20px;
|
||||||
padding: 10px;
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.results {
|
.details-wrapper {
|
||||||
|
width: 95%;
|
||||||
|
padding: 20px;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.details-placeholder {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.results-container {
|
button[mat-raised-button] {
|
||||||
display: grid;
|
align-self: flex-start;
|
||||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1024px) {
|
||||||
|
.card-container {
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-container {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.groups-button-row {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
mat-card {
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.unidad-card {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.details-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 20px;
|
||||||
|
padding: 30px;
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 20px auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-tree {
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
border-right: 1px solid #ddd;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
margin: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-tree mat-tree-node {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 6px;
|
||||||
|
transition: background-color 0.2s, color 0.2s;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-tree mat-tree-node:hover {
|
||||||
|
background-color: #e3f2fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-tree mat-tree-node button.mat-icon-button {
|
||||||
|
margin-left: auto;
|
||||||
|
color: #757575;
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-tree mat-tree-node button.mat-icon-button:hover {
|
||||||
|
color: #1976d2;
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-tree mat-tree-node span {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-tree mat-tree-node mat-icon {
|
||||||
|
margin-right: 10px;
|
||||||
|
color: #757575;
|
||||||
|
transition: color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-tree mat-tree-node.expandable mat-icon {
|
||||||
|
color: black;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-tree mat-tree-node.expandable.disabled mat-icon {
|
||||||
|
color: grey;
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-tree mat-tree-node:hover mat-icon {
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-tree mat-tree-node mat-icon.node-icon {
|
||||||
|
color: #757575;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-tree mat-tree-node mat-icon.node-icon.organizational-unit {
|
||||||
|
color: #1976d2;
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-tree mat-tree-node mat-icon.node-icon.classroom {
|
||||||
|
color: #757575;
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-tree mat-tree-node mat-icon.node-icon.client {
|
||||||
|
color: #757575;
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-tree mat-tree-node mat-icon.node-icon.group {
|
||||||
|
color: #757575;
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-tree mat-tree-node button.mat-icon-button {
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-tree mat-tree-node button.mat-icon-button.disabled-toggle {
|
||||||
|
color: grey;
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-tree mat-tree-node button.mat-icon-button.disabled-toggle:hover {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-tree mat-tree-node:hover {
|
||||||
|
background-color: #e3f2fd;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-tree mat-tree-node.disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-tree mat-tree-node.disabled:hover {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected-node {
|
||||||
|
background-color: #e0f7fa;
|
||||||
|
border-left: 4px solid #3F51B5;
|
||||||
|
padding-left: calc(16px - 4px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-menu-item .mat-menu-item-submenu-icon {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filters-container {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.result-card {
|
.filters-container mat-form-field {
|
||||||
width: 100%;
|
flex: 1 1 100%;
|
||||||
max-width: 250px;
|
max-width: 300px;
|
||||||
height: 250px;
|
|
||||||
}
|
|
||||||
.paginator-container {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
margin-bottom: 30px;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.divider {
|
.filter-container {
|
||||||
margin: 20px 0;
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
mat-card {
|
.pc-og-live {
|
||||||
margin-bottom: 20px;
|
color: #4caf50;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mat-tooltip {
|
.pc-busy {
|
||||||
white-space: pre-line;
|
color: #ff9800;
|
||||||
}
|
}
|
||||||
|
|
||||||
.classroom-grid {
|
.pc-windows {
|
||||||
|
color: #0078d7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pc-linux {
|
||||||
|
color: #f0ad4e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pc-macos {
|
||||||
|
color: #999999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pc-off {
|
||||||
|
color: #f44336;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clients-card-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
justify-content: flex-start; /* Opcional: para alinear a la izquierda */
|
padding: 16px;
|
||||||
|
justify-content: flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
.classroom-item {
|
.classroom-item {
|
||||||
flex: 0 1 calc(16.66% - 16px); /* 6 columnas */
|
flex: 1 1 calc(25% - 16px);
|
||||||
max-width: calc(16.66% - 16px);
|
max-width: calc(25% - 16px);
|
||||||
text-align: center;
|
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.classroom-pc {
|
.classroom-pc {
|
||||||
position: relative;
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 16px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 8px;
|
text-align: center;
|
||||||
background-color: #f4f4f4;
|
|
||||||
border-radius: 8px;
|
|
||||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.pc-image {
|
.classroom-pc .pc-image {
|
||||||
width: 80px;
|
width: 64px;
|
||||||
height: 80px;
|
height: 64px;
|
||||||
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pc-details {
|
.pc-details {
|
||||||
margin-top: 8px;
|
margin-bottom: 8px;
|
||||||
font-size: 12px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.client-name {
|
.pc-details .client-name,
|
||||||
font-weight: bold;
|
.pc-details .client-ip,
|
||||||
|
.pc-details .client-mac {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
font-size: 14px;
|
||||||
|
|
||||||
.client-ip,
|
|
||||||
.client-mac {
|
|
||||||
color: #666;
|
color: #666;
|
||||||
font-size: 10px;
|
|
||||||
display: block;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.pc-actions {
|
.pc-actions button {
|
||||||
margin-top: 8px;
|
margin: 0 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-container {
|
||||||
|
width: 25%;
|
||||||
|
padding: 16px;
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clients-container {
|
||||||
|
width: 75%;
|
||||||
|
padding: 16px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clients-container h3 {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
font-size: 1.5em;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.client-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: 8px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.pc-og-live {
|
.client-card {
|
||||||
border: 2px solid #4caf50;
|
background-color: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||||
|
width: 100%;
|
||||||
|
max-width: 300px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
padding: 15px;
|
||||||
|
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pc-busy {
|
.client-card:hover {
|
||||||
border: 2px solid #ff9800;
|
transform: translateY(-5px);
|
||||||
|
box-shadow: 0 6px 10px rgba(0, 0, 0, 0.15);
|
||||||
}
|
}
|
||||||
|
|
||||||
.pc-off {
|
.client-image {
|
||||||
border: 2px solid #f44336;
|
width: 60px;
|
||||||
|
height: 60px;
|
||||||
|
margin-bottom: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pc-linux {
|
.client-details {
|
||||||
border: 2px solid #9c27b0;
|
margin-top: 10px;
|
||||||
}
|
|
||||||
|
|
||||||
.pc-windows {
|
|
||||||
border: 2px solid #2196f3;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Pantallas medianas: 4 columnas */
|
|
||||||
@media (max-width: 1024px) {
|
|
||||||
.classroom-item {
|
|
||||||
flex: 0 1 calc(25% - 16px); /* 4 columnas */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Pantallas pequeñas: 2 columnas */
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.classroom-item {
|
|
||||||
flex: 0 1 calc(50% - 16px); /* 2 columnas */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Pantallas muy pequeñas: 1 columna */
|
|
||||||
@media (max-width: 480px) {
|
|
||||||
.classroom-item {
|
|
||||||
flex: 0 1 100%; /* 1 columna */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.client-text {
|
|
||||||
font-size: 0.8rem;
|
|
||||||
color: rgba(0, 0, 0, 0.54);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.client-name {
|
.client-name {
|
||||||
font-size: 0.9rem;
|
display: block;
|
||||||
text-align: center;
|
font-size: 1.2em;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.client-ip {
|
||||||
|
display: block;
|
||||||
|
font-size: 0.9em;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
button[mat-raised-button] {
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clients-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clients-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.client-item-list {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 8px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.client-details-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.view-toggle-container {
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clients-grid {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clients-list .list-item-content {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 100%;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.client-card, .list-item-content {
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.client-card {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.client-image {
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-actions-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-button {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.view-toggle-container {
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filters-container {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 16px;
|
||||||
|
margin: 16px 0;
|
||||||
|
padding: 0 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filters-container mat-form-field {
|
||||||
|
flex: 1 1 300px;
|
||||||
|
max-width: 300px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,156 +1,309 @@
|
||||||
<mat-tab-group (selectedTabChange)="onTabChange($event)">
|
<div class="header-container" joyrideStep="tabsStep" text="{{ 'tabsStepText' | translate }}">
|
||||||
<mat-tab label="{{ 'generalTabLabel' | translate }}">
|
<button mat-icon-button color="primary" (click)="iniciarTour()">
|
||||||
<div class="header-container" joyrideStep="tabsStep" text="{{ 'tabsStepText' | translate }}">
|
<mat-icon>help</mat-icon>
|
||||||
<button mat-icon-button color="primary" (click)="iniciarTour()">
|
</button>
|
||||||
<mat-icon>help</mat-icon>
|
<h2 class="title" joyrideStep="groupsTitleStepText" text="{{ 'groupsTitleStepText' | translate }}">
|
||||||
|
{{ 'adminGroupsTitle' | translate }}
|
||||||
|
</h2>
|
||||||
|
<div class="groups-button-row" joyrideStep="addStep" text="{{ 'groupsAddStepText' | translate }}">
|
||||||
|
<button mat-flat-button color="primary" (click)="addOU($event)"
|
||||||
|
matTooltip="{{ 'newOrganizationalUnitTooltip' | translate }}" matTooltipShowDelay="1000">
|
||||||
|
{{ 'newOrganizationalUnitButton' | translate }}
|
||||||
|
</button>
|
||||||
|
<button mat-flat-button color="primary" (click)="addClient($event)" matTooltipShowDelay="1000">
|
||||||
|
{{ 'newClientButton' | translate }}
|
||||||
|
</button>
|
||||||
|
<button mat-flat-button (click)="openBottomSheet()" joyrideStep="keyStep" text="{{ 'keyStepText' | translate }}"
|
||||||
|
matTooltipShowDelay="1000">
|
||||||
|
{{ 'legendButton' | translate }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<mat-expansion-panel *ngIf="isTreeViewActive" class="filters-panel">
|
||||||
|
<mat-expansion-panel-header>
|
||||||
|
<mat-panel-title>Filtros</mat-panel-title>
|
||||||
|
</mat-expansion-panel-header>
|
||||||
|
<div class="filters-container">
|
||||||
|
<mat-form-field appearance="outline">
|
||||||
|
<mat-select (selectionChange)="loadSelectedFilter($event.value)" placeholder="Cargar filtro">
|
||||||
|
<mat-option *ngFor="let savedFilter of savedFilterNames" [value]="savedFilter">
|
||||||
|
{{ savedFilter[0] }}
|
||||||
|
</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
<mat-form-field appearance="outline">
|
||||||
|
<mat-label>Buscar en el árbol</mat-label>
|
||||||
|
<input matInput (input)="onTreeFilterInput($event)" placeholder="Buscar nombre o tipo">
|
||||||
|
</mat-form-field>
|
||||||
|
<mat-form-field appearance="outline">
|
||||||
|
<mat-label>Filtrar por tipo</mat-label>
|
||||||
|
<mat-select [(value)]="selectedTreeFilter" (selectionChange)="filterTree(searchTerm, $event.value)">
|
||||||
|
<mat-option [value]="">Todos</mat-option>
|
||||||
|
<mat-option value="organizational-unit">Grupos de aulas</mat-option>
|
||||||
|
<mat-option value="classroom">Aulas</mat-option>
|
||||||
|
<mat-option value="group">Grupos de ordenadores</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
<mat-form-field appearance="outline">
|
||||||
|
<mat-label>Buscar cliente</mat-label>
|
||||||
|
<input matInput (input)="onClientFilterInput($event)" placeholder="Buscar nombre, IP, estado o MAC">
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
</mat-expansion-panel>
|
||||||
|
|
||||||
|
<div *ngIf="!selectedUnidad; else detailsTemplate" class="card-container">
|
||||||
|
<mat-card *ngFor="let unidad of organizationalUnits"
|
||||||
|
[ngClass]="{'selected-item': unidad === selectedUnidad, 'clickable-item': true}"
|
||||||
|
(click)="onSelectUnidad(unidad)" class="unidad-card small-card">
|
||||||
|
<mat-card-header>
|
||||||
|
<mat-card-title>
|
||||||
|
<mat-icon>apartment</mat-icon> {{ unidad.name }}
|
||||||
|
</mat-card-title>
|
||||||
|
</mat-card-header>
|
||||||
|
<mat-card-actions>
|
||||||
|
<div class="button-container">
|
||||||
|
<button mat-raised-button color="primary" [matMenuTriggerFor]="menu" (click)="$event.stopPropagation()">
|
||||||
|
<mat-icon>menu</mat-icon>
|
||||||
|
{{ 'Menu' | translate }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<mat-menu #menu="matMenu">
|
||||||
|
<button mat-menu-item (click)="onEditClick($event, unidad.type, unidad.uuid)">
|
||||||
|
<mat-icon matTooltip="{{ 'editUnitTooltip' | translate }}" matTooltipHideDelay="0">edit</mat-icon>
|
||||||
|
<span>{{ 'editUnitMenu' | translate }}</span>
|
||||||
|
</button>
|
||||||
|
<button mat-menu-item (click)="onShowDetailsClick($event, unidad)">
|
||||||
|
<mat-icon matTooltip="{{ 'viewUnitTooltip' | translate }}" matTooltipHideDelay="0">visibility</mat-icon>
|
||||||
|
<span>{{ 'viewUnitMenu' | translate }}</span>
|
||||||
|
</button>
|
||||||
|
<button mat-menu-item (click)="addOU($event, unidad)">
|
||||||
|
<mat-icon matTooltip="{{ 'addInternalUnitTooltip' | translate }}" matTooltipHideDelay="0">add_home_work</mat-icon>
|
||||||
|
<span>{{ 'addInternalUnitMenu' | translate }}</span>
|
||||||
|
</button>
|
||||||
|
<button mat-menu-item (click)="addClient($event, unidad)">
|
||||||
|
<mat-icon matTooltip="{{ 'addClientTooltip' | translate }}" matTooltipHideDelay="0">devices</mat-icon>
|
||||||
|
<span>{{ 'addClientMenu' | translate }}</span>
|
||||||
|
</button>
|
||||||
|
<button mat-menu-item (click)="onTreeClick($event, unidad)">
|
||||||
|
<mat-icon matTooltip="{{ 'viewTreeTooltip' | translate }}" matTooltipHideDelay="0">account_tree</mat-icon>
|
||||||
|
<span>{{ 'viewTreeMenu' | translate }}</span>
|
||||||
|
</button>
|
||||||
|
<button mat-menu-item (click)="onDeleteClick($event, unidad)">
|
||||||
|
<mat-icon>delete</mat-icon>
|
||||||
|
<span>Delete</span>
|
||||||
|
</button>
|
||||||
|
</mat-menu>
|
||||||
|
</mat-card-actions>
|
||||||
|
</mat-card>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ng-template #detailsTemplate>
|
||||||
|
<div class="header-actions-container">
|
||||||
|
<button mat-raised-button color="primary" (click)="clearSelection()" class="back-button">
|
||||||
|
<mat-icon>arrow_back</mat-icon>
|
||||||
|
{{ 'Back' | translate }}
|
||||||
|
</button>
|
||||||
|
<div class="view-toggle-container" *ngIf="selectedDetail">
|
||||||
|
<button mat-button color="primary" (click)="toggleView('card')" [disabled]="currentView === 'card'">
|
||||||
|
<mat-icon>grid_view</mat-icon> {{ 'Vista Tarjeta' | translate }}
|
||||||
</button>
|
</button>
|
||||||
<h2 class="title" joyrideStep="groupsTitleStepText" text="{{ 'groupsTitleStepText' | translate }}">{{ 'adminGroupsTitle' | translate }}</h2>
|
<button mat-button color="primary" (click)="toggleView('list')" [disabled]="currentView === 'list'">
|
||||||
<div class="groups-button-row" joyrideStep="addStep" text="{{ 'groupsAddStepText' | translate }}">
|
<mat-icon>list</mat-icon> {{ 'Vista Lista' | translate }}
|
||||||
<button mat-flat-button color="primary" (click)="addOU($event)" matTooltip="{{ 'newOrganizationalUnitTooltip' | translate }}" matTooltipShowDelay="1000">{{ 'newOrganizationalUnitButton' | translate }}</button>
|
</button>
|
||||||
<button mat-flat-button color="primary" (click)="addClient($event)" matTooltipShowDelay="1000">{{ 'newClientButton' | translate }}</button>
|
</div>
|
||||||
<button mat-raised-button (click)="openBottomSheet()" joyrideStep="keyStep" text="{{ 'keyStepText' | translate }}" matTooltipShowDelay="1000">{{ 'legendButton' | translate }}</button>
|
</div>
|
||||||
|
<div class="main-container">
|
||||||
|
<div class="tree-container">
|
||||||
|
<h2>{{ selectedUnidad?.name }}</h2>
|
||||||
|
<mat-tree [dataSource]="treeDataSource" [treeControl]="treeControl">
|
||||||
|
<mat-tree-node [ngClass]="{'selected-node': node === selectedNode}" *matTreeNodeDef="let node; when: hasChild" matTreeNodePadding (click)="onNodeClick(node)">
|
||||||
|
<button mat-icon-button matTreeNodeToggle [disabled]="!node.expandable" [ngClass]="{'disabled-toggle': !node.expandable}">
|
||||||
|
<mat-icon>{{ treeControl.isExpanded(node) ? 'expand_more' : 'chevron_right' }}</mat-icon>
|
||||||
|
</button>
|
||||||
|
<mat-icon class="node-icon {{ node.type }}">
|
||||||
|
{{
|
||||||
|
node.type === 'organizational-unit' ? 'apartment'
|
||||||
|
: node.type === 'classrooms-group' ? 'meeting_room'
|
||||||
|
: node.type === 'classroom' ? 'school'
|
||||||
|
: node.type === 'clients-group' ? 'lan'
|
||||||
|
: node.type === 'client' ? 'computer'
|
||||||
|
: 'group'
|
||||||
|
}}
|
||||||
|
</mat-icon>
|
||||||
|
<span>{{ node.name }}</span>
|
||||||
|
<button mat-icon-button [matMenuTriggerFor]="menu" (click)="setSelectedNode(node); $event.stopPropagation()">
|
||||||
|
<mat-icon>more_vert</mat-icon>
|
||||||
|
</button>
|
||||||
|
</mat-tree-node>
|
||||||
|
<mat-tree-node [ngClass]="{'selected-node': node === selectedNode}" *matTreeNodeDef="let node; when: isLeafNode" matTreeNodePadding (click)="onNodeClick(node)">
|
||||||
|
<button mat-icon-button matTreeNodeToggle [disabled]="true" class="disabled-toggle"></button>
|
||||||
|
<mat-icon style="color: green;">
|
||||||
|
{{
|
||||||
|
node.type === 'organizational-unit' ? 'apartment'
|
||||||
|
: node.type === 'classrooms-group' ? 'meeting_room'
|
||||||
|
: node.type === 'classroom' ? 'school'
|
||||||
|
: node.type === 'clients-group' ? 'lan'
|
||||||
|
: node.type === 'client' ? 'computer'
|
||||||
|
: 'group'
|
||||||
|
}}
|
||||||
|
</mat-icon>
|
||||||
|
<span>{{ node.name }}</span>
|
||||||
|
<ng-container *ngIf="node.type === 'client'">
|
||||||
|
<span> - IP: {{ node.ip }}</span>
|
||||||
|
</ng-container>
|
||||||
|
<button mat-icon-button [matMenuTriggerFor]="menu" (click)="setSelectedNode(node)">
|
||||||
|
<mat-icon>more_vert</mat-icon>
|
||||||
|
</button>
|
||||||
|
</mat-tree-node>
|
||||||
|
</mat-tree>
|
||||||
|
</div>
|
||||||
|
<mat-menu restoreFocus=false #commandMenu="matMenu">
|
||||||
|
<button mat-menu-item *ngFor="let command of commands" (click)="executeCommand(command, selectedNode)">
|
||||||
|
<span>{{ command.name }}</span>
|
||||||
|
</button>
|
||||||
|
</mat-menu>
|
||||||
|
<mat-menu #menu="matMenu">
|
||||||
|
<button *ngIf="selectedNode?.type === 'classroom'" mat-menu-item [matMenuTriggerFor]="commandMenu" (click)="fetchCommands()">
|
||||||
|
<mat-icon>play_arrow</mat-icon>
|
||||||
|
<span>Ejecutar Comando</span>
|
||||||
|
</button>
|
||||||
|
<button mat-menu-item (click)="onShowDetailsClick($event, selectedNode)">
|
||||||
|
<mat-icon matTooltip="{{ 'viewUnitTooltip' | translate }}" matTooltipHideDelay="0">visibility</mat-icon>
|
||||||
|
<span>{{ 'viewUnitMenu' | translate }}</span>
|
||||||
|
</button>
|
||||||
|
<button *ngIf="selectedNode?.type === 'classroom'" mat-menu-item (click)="onRoomMap(selectedNode)">
|
||||||
|
<mat-icon>map</mat-icon>
|
||||||
|
<span>Plano aula</span>
|
||||||
|
</button>
|
||||||
|
<button mat-menu-item (click)="addClient($event, selectedNode)">
|
||||||
|
<mat-icon>add</mat-icon>
|
||||||
|
<span>Añadir clientes</span>
|
||||||
|
</button>
|
||||||
|
<button mat-menu-item (click)="addOU($event, selectedNode)">
|
||||||
|
<mat-icon>playlist_add</mat-icon>
|
||||||
|
<span>Añadir unidad organizativa</span>
|
||||||
|
</button>
|
||||||
|
<button mat-menu-item (click)="onEditNode($event, selectedNode)">
|
||||||
|
<mat-icon>edit</mat-icon>
|
||||||
|
<span>Edit</span>
|
||||||
|
</button>
|
||||||
|
<button mat-menu-item (click)="onDeleteClick($event, selectedNode)">
|
||||||
|
<mat-icon>delete</mat-icon>
|
||||||
|
<span>Delete</span>
|
||||||
|
</button>
|
||||||
|
</mat-menu>
|
||||||
|
<div class="clients-container" *ngIf="selectedClients.length > 0">
|
||||||
|
<h3>Clientes {{ selectedNode?.name ? 'del ' + selectedNode?.name : '' }}</h3>
|
||||||
|
<div class="clients-grid" *ngIf="currentView === 'card'">
|
||||||
|
<div *ngFor="let client of selectedClients" class="client-item">
|
||||||
|
<div class="client-card">
|
||||||
|
<img src="assets/images/client.png" alt="Client Icon" class="client-image" />
|
||||||
|
<div class="client-details">
|
||||||
|
<span class="client-name">{{ client.name }}</span>
|
||||||
|
<span class="client-ip">{{ client.ip }}</span>
|
||||||
|
<mat-chip [ngClass]="{
|
||||||
|
'chip-og-live': client.status === 'og-live',
|
||||||
|
'chip-busy': client.status === 'busy',
|
||||||
|
'chip-windows': client.status === 'windows' || client.status === 'windows-session',
|
||||||
|
'chip-linux': client.status === 'linux' || client.status === 'linux-session',
|
||||||
|
'chip-macos': client.status === 'macos',
|
||||||
|
'chip-off': client.status === 'off'
|
||||||
|
}">
|
||||||
|
{{ client.status || 'off' }}
|
||||||
|
</mat-chip>
|
||||||
|
<button mat-raised-button color="primary" [matMenuTriggerFor]="clientMenu">Acciones</button>
|
||||||
|
<mat-menu #clientMenu="matMenu">
|
||||||
|
<button mat-menu-item (click)="onEditClick($event, client.type, client.uuid)">
|
||||||
|
<mat-icon>edit</mat-icon>
|
||||||
|
<span>Edit</span>
|
||||||
|
</button>
|
||||||
|
<button mat-menu-item (click)="onShowClientDetail($event, client)">
|
||||||
|
<mat-icon>visibility</mat-icon>
|
||||||
|
<span>Ver detalles</span>
|
||||||
|
</button>
|
||||||
|
<button mat-menu-item (click)="onDeleteClick($event, client, selectedNode)">
|
||||||
|
<mat-icon>delete</mat-icon>
|
||||||
|
<span>Delete</span>
|
||||||
|
</button>
|
||||||
|
</mat-menu>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<mat-paginator [pageSize]="10" [pageSizeOptions]="[5, 10, 20]" showFirstLastButtons></mat-paginator>
|
||||||
|
</div>
|
||||||
|
<div class="clients-table" *ngIf="currentView === 'list'">
|
||||||
|
<table mat-table [dataSource]="selectedClients" class="mat-elevation-z8">
|
||||||
|
<ng-container matColumnDef="name">
|
||||||
|
<th mat-header-cell *matHeaderCellDef> Nombre </th>
|
||||||
|
<td mat-cell *matCellDef="let client"> {{ client.name }} </td>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container matColumnDef="ip">
|
||||||
|
<th mat-header-cell *matHeaderCellDef> IP </th>
|
||||||
|
<td mat-cell *matCellDef="let client"> {{ client.ip }} </td>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container matColumnDef="mac">
|
||||||
|
<th mat-header-cell *matHeaderCellDef> MAC </th>
|
||||||
|
<td mat-cell *matCellDef="let client"> {{ client.mac }} </td>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container matColumnDef="oglive">
|
||||||
|
<th mat-header-cell *matHeaderCellDef> OG Live </th>
|
||||||
|
<td mat-cell *matCellDef="let client"> {{ client.oglive }} </td>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container matColumnDef="status">
|
||||||
|
<th mat-header-cell *matHeaderCellDef> Estado </th>
|
||||||
|
<td mat-cell *matCellDef="let client">
|
||||||
|
<mat-chip [ngClass]="{
|
||||||
|
'chip-og-live': client.status === 'og-live',
|
||||||
|
'chip-busy': client.status === 'busy',
|
||||||
|
'chip-windows': client.status === 'windows' || client.status === 'windows-session',
|
||||||
|
'chip-linux': client.status === 'linux' || client.status === 'linux-session',
|
||||||
|
'chip-macos': client.status === 'macos',
|
||||||
|
'chip-off': client.status === 'off'
|
||||||
|
}">
|
||||||
|
{{ client.status || 'off' }}
|
||||||
|
</mat-chip>
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container matColumnDef="mantenimiento">
|
||||||
|
<th mat-header-cell *matHeaderCellDef> Mantenimiento </th>
|
||||||
|
<td mat-cell *matCellDef="let client"> {{ client.mantenimiento }} </td>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container matColumnDef="subnet">
|
||||||
|
<th mat-header-cell *matHeaderCellDef> Subred </th>
|
||||||
|
<td mat-cell *matCellDef="let client"> {{ client.subnet }} </td>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container matColumnDef="pxeTemplate">
|
||||||
|
<th mat-header-cell *matHeaderCellDef> Plantilla PXE </th>
|
||||||
|
<td mat-cell *matCellDef="let client"> {{ client.pxeTemplate }} </td>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container matColumnDef="actions">
|
||||||
|
<th mat-header-cell *matHeaderCellDef> Acciones </th>
|
||||||
|
<td mat-cell *matCellDef="let client">
|
||||||
|
<button mat-icon-button [matMenuTriggerFor]="clientMenu">
|
||||||
|
<mat-icon>more_vert</mat-icon>
|
||||||
|
</button>
|
||||||
|
<mat-menu #clientMenu="matMenu">
|
||||||
|
<button mat-menu-item (click)="onEditClick($event, client.type, client.uuid)">
|
||||||
|
<mat-icon>edit</mat-icon>
|
||||||
|
<span>Edit</span>
|
||||||
|
</button>
|
||||||
|
<button mat-menu-item (click)="onShowClientDetail($event, client)">
|
||||||
|
<mat-icon>visibility</mat-icon>
|
||||||
|
<span>Ver detalles</span>
|
||||||
|
</button>
|
||||||
|
<button mat-menu-item (click)="onDeleteClick($event, client, selectedNode)">
|
||||||
|
<mat-icon>delete</mat-icon>
|
||||||
|
<span>Delete</span>
|
||||||
|
</button>
|
||||||
|
</mat-menu>
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
<tr mat-header-row *matHeaderRowDef="['name', 'ip', 'mac', 'oglive', 'status', 'mantenimiento', 'subnet', 'pxeTemplate', 'actions']"></tr>
|
||||||
|
<tr mat-row *matRowDef="let row; columns: ['name', 'ip', 'mac', 'oglive', 'status', 'mantenimiento', 'subnet', 'pxeTemplate', 'actions'];"></tr>
|
||||||
|
</table>
|
||||||
|
<mat-paginator [pageSize]="10" [pageSizeOptions]="[5, 10, 20]" showFirstLastButtons></mat-paginator>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<div class="groupLists-container">
|
</ng-template>
|
||||||
<mat-card class="card unidad-card" joyrideStep="unitStep" text="{{ 'unitStepText' | translate }}" matTooltipShowDelay="1000" matTooltipPosition="above">
|
|
||||||
<mat-card-title>{{ 'organizationalUnitTitle' | translate }}</mat-card-title>
|
|
||||||
<mat-card-content>
|
|
||||||
<mat-spinner *ngIf="loading"></mat-spinner>
|
|
||||||
<mat-list *ngIf="!loading">
|
|
||||||
<mat-list-item *ngFor="let unidad of organizationalUnits" [ngClass]="{'selected-item': unidad === selectedUnidad, 'clickable-item': true}" (click)="onSelectUnidad(unidad)">
|
|
||||||
<div class="item-content">
|
|
||||||
<mat-icon>apartment</mat-icon>
|
|
||||||
{{ unidad.name }}
|
|
||||||
<span class="actions">
|
|
||||||
<mat-icon mat-button [matMenuTriggerFor]="menu" (click)="$event.stopPropagation()">more_vert</mat-icon>
|
|
||||||
<mat-menu #menu="matMenu">
|
|
||||||
<button mat-menu-item (click)="onTreeClick($event, unidad)">
|
|
||||||
<mat-icon matTooltip="{{ 'viewTreeTooltip' | translate }}" matTooltipHideDelay="0">account_tree</mat-icon>
|
|
||||||
<span>{{ 'viewTreeMenu' | translate }}</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button mat-menu-item (click)="onEditClick($event, unidad.type, unidad.uuid)">
|
|
||||||
<mat-icon matTooltip="{{ 'editUnitTooltip' | translate }}" matTooltipHideDelay="0">edit</mat-icon>
|
|
||||||
<span>{{ 'editUnitMenu' | translate }}</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button mat-menu-item (click)="onShowClick($event, unidad)">
|
|
||||||
<mat-icon matTooltip="{{ 'viewUnitTooltip' | translate }}" matTooltipHideDelay="0">visibility</mat-icon>
|
|
||||||
<span>{{ 'viewUnitMenu' | translate }}</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button mat-menu-item (click)="addOU($event, unidad)">
|
|
||||||
<mat-icon matTooltip="{{ 'addInternalUnitTooltip' | translate }}" matTooltipHideDelay="0">add_home_work</mat-icon>
|
|
||||||
<span>{{ 'addInternalUnitMenu' | translate }}</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button mat-menu-item (click)="addClient($event, unidad)">
|
|
||||||
<mat-icon matTooltip="{{ 'addClientTooltip' | translate }}" matTooltipHideDelay="0">devices</mat-icon>
|
|
||||||
<span>{{ 'addClientMenu' | translate }}</span>
|
|
||||||
</button>
|
|
||||||
</mat-menu>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</mat-list-item>
|
|
||||||
</mat-list>
|
|
||||||
</mat-card-content>
|
|
||||||
</mat-card>
|
|
||||||
|
|
||||||
<mat-card class="card elements-card">
|
|
||||||
<mat-card-title>
|
|
||||||
<div class="title-with-breadcrumb">
|
|
||||||
<span>{{ 'internalElementsTitle' | translate }}</span>
|
|
||||||
<mat-card-subtitle>
|
|
||||||
<ng-container *ngFor="let crumb of breadcrumb; let i = index">
|
|
||||||
<a (click)="navigateToBreadcrumb(i)">{{ crumb }}</a>
|
|
||||||
<span *ngIf="i < breadcrumb.length - 1"> > </span>
|
|
||||||
</ng-container>
|
|
||||||
</mat-card-subtitle>
|
|
||||||
</div>
|
|
||||||
</mat-card-title>
|
|
||||||
<mat-card-content class="element-content">
|
|
||||||
<mat-spinner *ngIf="loadingChildren"></mat-spinner>
|
|
||||||
|
|
||||||
<!-- Mostrar lista normal si no es un aula -->
|
|
||||||
<mat-list *ngIf="!loadingChildren && selectedDetail?.type !== 'classroom'">
|
|
||||||
<div *ngIf="children.length === 0" class="empty-list">
|
|
||||||
<mat-icon>info</mat-icon>
|
|
||||||
<span>{{ 'noInternalElementsMessage' | translate }}</span>
|
|
||||||
</div>
|
|
||||||
<mat-list-item *ngFor="let child of children"
|
|
||||||
[ngClass]="{'selected-item': child === selectedUnidad, 'clickable-item': true}"
|
|
||||||
(click)="onSelectChild(child)">
|
|
||||||
<div class="item-content">
|
|
||||||
<mat-icon [ngSwitch]="child.type">
|
|
||||||
<ng-container *ngSwitchCase="'organizational-unit'">apartment</ng-container>
|
|
||||||
<ng-container *ngSwitchCase="'classrooms-group'">meeting_room</ng-container>
|
|
||||||
<ng-container *ngSwitchCase="'classroom'">school</ng-container>
|
|
||||||
<ng-container *ngSwitchCase="'client'">computer</ng-container>
|
|
||||||
<ng-container *ngSwitchCase="'clients-group'">lan</ng-container>
|
|
||||||
<ng-container *ngSwitchDefault>help_outline</ng-container>
|
|
||||||
</mat-icon>
|
|
||||||
{{ child.name }}
|
|
||||||
<div class="actions">
|
|
||||||
<mat-icon mat-button [matMenuTriggerFor]="menu" (click)="$event.stopPropagation()">more_vert</mat-icon>
|
|
||||||
<mat-menu #menu="matMenu">
|
|
||||||
<button mat-menu-item (click)="onEditClick($event, child.type, child.uuid)">
|
|
||||||
<mat-icon>edit</mat-icon>
|
|
||||||
<span>{{ 'editElementMenu' | translate }}</span>
|
|
||||||
</button>
|
|
||||||
<button mat-menu-item (click)="onDeleteClick($event, child.uuid, child.name, child.type)">
|
|
||||||
<mat-icon>delete</mat-icon>
|
|
||||||
<span>{{ 'deleteElementMenu' | translate }}</span>
|
|
||||||
</button>
|
|
||||||
</mat-menu>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</mat-list-item>
|
|
||||||
</mat-list>
|
|
||||||
|
|
||||||
<!-- Mostrar cuadrícula si es un aula -->
|
|
||||||
<div *ngIf="selectedDetail?.type === 'classroom'" class="classroom-grid">
|
|
||||||
<div *ngFor="let pc of selectedDetail.clients" class="classroom-item">
|
|
||||||
<div class="classroom-pc" [ngClass]="{
|
|
||||||
'pc-og-live': pc.status === 'og-live',
|
|
||||||
'pc-busy': pc.status === 'busy',
|
|
||||||
'pc-windows': pc.status === 'windows' || pc.status === 'windows-session',
|
|
||||||
'pc-linux': pc.status === 'linux' || pc.status === 'linux-session',
|
|
||||||
'pc-macos': pc.status === 'macos',
|
|
||||||
'pc-off': pc.status === 'off'
|
|
||||||
}">
|
|
||||||
<img mat-card-image src="assets/images/client.png" alt="PC Icon" class="pc-image">
|
|
||||||
<div class="pc-details">
|
|
||||||
<span class="client-name">{{ pc.name }}</span>
|
|
||||||
<span class="client-ip">{{ pc.ip }}</span>
|
|
||||||
<span class="client-mac">{{ pc.mac }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="pc-actions">
|
|
||||||
<button mat-icon-button color="primary" (click)="onEditClick($event, 'client', pc.uuid)">
|
|
||||||
<mat-icon>edit</mat-icon>
|
|
||||||
</button>
|
|
||||||
<button mat-icon-button color="warn" (click)="onDeleteClick($event, pc.uuid, pc.name, 'client')">
|
|
||||||
<mat-icon>delete</mat-icon>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</mat-card-content>
|
|
||||||
</mat-card>
|
|
||||||
</div>
|
|
||||||
</mat-tab>
|
|
||||||
|
|
||||||
<mat-tab label="{{ 'advancedSearchTabLabel' | translate }}">
|
|
||||||
<app-advanced-search></app-advanced-search>
|
|
||||||
</mat-tab>
|
|
||||||
|
|
||||||
<mat-tab label="{{ 'clientsTabLabel' | translate }}">
|
|
||||||
<app-client-tab-view #clientTab></app-client-tab-view>
|
|
||||||
</mat-tab>
|
|
||||||
|
|
||||||
<mat-tab label="{{ 'organizationalUnitsTabLabel' | translate }}">
|
|
||||||
<app-organizational-unit-tab-view #organizationalUnitTab></app-organizational-unit-tab-view>
|
|
||||||
</mat-tab>
|
|
||||||
</mat-tab-group>
|
|
||||||
|
|
|
@ -113,131 +113,4 @@ describe('GroupsComponent', () => {
|
||||||
expect(component.onSelectUnidad).toHaveBeenCalledWith(unidad);
|
expect(component.onSelectUnidad).toHaveBeenCalledWith(unidad);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should call onSelectChild method', () => {
|
|
||||||
spyOn(component, 'onSelectChild');
|
|
||||||
const child = { id: '1', name: 'Test', type: 'unit' } as any;
|
|
||||||
component.onSelectChild(child);
|
|
||||||
expect(component.onSelectChild).toHaveBeenCalledWith(child);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should call navigateToBreadcrumb method', () => {
|
|
||||||
spyOn(component, 'navigateToBreadcrumb');
|
|
||||||
component.navigateToBreadcrumb(1);
|
|
||||||
expect(component.navigateToBreadcrumb).toHaveBeenCalledWith(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should call loadChildrenAndClients method', () => {
|
|
||||||
spyOn(component, 'loadChildrenAndClients');
|
|
||||||
component.loadChildrenAndClients('1');
|
|
||||||
expect(component.loadChildrenAndClients).toHaveBeenCalledWith('1');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should call onDeleteClick method', () => {
|
|
||||||
spyOn(component, 'onDeleteClick');
|
|
||||||
const event = new MouseEvent('click');
|
|
||||||
component.onDeleteClick(event, 'uuid', 'name', 'client');
|
|
||||||
expect(component.onDeleteClick).toHaveBeenCalledWith(event, 'uuid', 'name', 'client');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should call onEditClick method', () => {
|
|
||||||
spyOn(component, 'onEditClick');
|
|
||||||
const event = new MouseEvent('click');
|
|
||||||
component.onEditClick(event, 'client', 'uuid');
|
|
||||||
expect(component.onEditClick).toHaveBeenCalledWith(event, 'client', 'uuid');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should call onShowClick method', () => {
|
|
||||||
spyOn(component, 'onShowClick');
|
|
||||||
const event = new MouseEvent('click');
|
|
||||||
component.onShowClick(event, { type: 'unit' });
|
|
||||||
expect(component.onShowClick).toHaveBeenCalledWith(event, { type: 'unit' });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should call onTreeClick method', () => {
|
|
||||||
spyOn(component, 'onTreeClick');
|
|
||||||
const event = new MouseEvent('click');
|
|
||||||
component.onTreeClick(event, { type: 'unit' });
|
|
||||||
expect(component.onTreeClick).toHaveBeenCalledWith(event, { type: 'unit' });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should call onExecuteCommand method', () => {
|
|
||||||
spyOn(component, 'onExecuteCommand');
|
|
||||||
const event = new MouseEvent('click');
|
|
||||||
component.onExecuteCommand(event, 'child', 'name', 'type');
|
|
||||||
expect(component.onExecuteCommand).toHaveBeenCalledWith(event, 'child', 'name', 'type');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should call openSnackBar method', () => {
|
|
||||||
spyOn(component, 'openSnackBar');
|
|
||||||
component.openSnackBar(true, 'message');
|
|
||||||
expect(component.openSnackBar).toHaveBeenCalledWith(true, 'message');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should call openBottomSheet method', () => {
|
|
||||||
spyOn(component, 'openBottomSheet');
|
|
||||||
component.openBottomSheet();
|
|
||||||
expect(component.openBottomSheet).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should call roomMap method', () => {
|
|
||||||
spyOn(component, 'roomMap');
|
|
||||||
component.roomMap();
|
|
||||||
expect(component.roomMap).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should call applyFilter method', () => {
|
|
||||||
spyOn(component, 'applyFilter');
|
|
||||||
component.applyFilter();
|
|
||||||
expect(component.applyFilter).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should call onPageChange method', () => {
|
|
||||||
spyOn(component, 'onPageChange');
|
|
||||||
const event = { pageIndex: 1, pageSize: 10 } as any;
|
|
||||||
component.onPageChange(event);
|
|
||||||
expect(component.onPageChange).toHaveBeenCalledWith(event);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should call saveFilters method', () => {
|
|
||||||
spyOn(component, 'saveFilters');
|
|
||||||
component.saveFilters();
|
|
||||||
expect(component.saveFilters).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should call loadSelectedFilter method', () => {
|
|
||||||
spyOn(component, 'loadSelectedFilter');
|
|
||||||
component.loadSelectedFilter(['name', 'uuid']);
|
|
||||||
expect(component.loadSelectedFilter).toHaveBeenCalledWith(['name', 'uuid']);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should call onCheckboxChange method', () => {
|
|
||||||
spyOn(component, 'onCheckboxChange');
|
|
||||||
const event = { checked: true } as any;
|
|
||||||
component.onCheckboxChange(event, 'name', 'uuid');
|
|
||||||
expect(component.onCheckboxChange).toHaveBeenCalledWith(event, 'name', 'uuid');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should call toggleSelectAll method', () => {
|
|
||||||
spyOn(component, 'toggleSelectAll');
|
|
||||||
component.toggleSelectAll();
|
|
||||||
expect(component.toggleSelectAll).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should call isSelected method', () => {
|
|
||||||
spyOn(component, 'isSelected');
|
|
||||||
component.isSelected('name');
|
|
||||||
expect(component.isSelected).toHaveBeenCalledWith('name');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should call sendActions method', () => {
|
|
||||||
spyOn(component, 'sendActions');
|
|
||||||
component.sendActions();
|
|
||||||
expect(component.sendActions).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should call iniciarTour method', () => {
|
|
||||||
spyOn(component, 'iniciarTour');
|
|
||||||
component.iniciarTour();
|
|
||||||
expect(component.iniciarTour).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,33 +1,46 @@
|
||||||
import {Component, OnInit, ViewChild} from '@angular/core';
|
import { Component, OnInit, ViewChild } from '@angular/core';
|
||||||
import { DataService } from './services/data.service';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import { ClientCollection, UnidadOrganizativa } from './model/model';
|
import { Router } from '@angular/router';
|
||||||
import { MatDialog } from '@angular/material/dialog';
|
import { MatDialog } from '@angular/material/dialog';
|
||||||
|
import { MatBottomSheet } from '@angular/material/bottom-sheet';
|
||||||
|
import { MatTabChangeEvent } from '@angular/material/tabs';
|
||||||
|
import { ToastrService } from 'ngx-toastr';
|
||||||
|
import { JoyrideService } from 'ngx-joyride';
|
||||||
|
import { FlatTreeControl } from '@angular/cdk/tree';
|
||||||
|
import { MatTreeFlatDataSource, MatTreeFlattener } from '@angular/material/tree';
|
||||||
|
import { DataService } from './services/data.service';
|
||||||
|
import { UnidadOrganizativa } from './model/model';
|
||||||
import { CreateOrganizationalUnitComponent } from './shared/organizational-units/create-organizational-unit/create-organizational-unit.component';
|
import { CreateOrganizationalUnitComponent } from './shared/organizational-units/create-organizational-unit/create-organizational-unit.component';
|
||||||
import { DeleteModalComponent } from '../../shared/delete_modal/delete-modal/delete-modal.component';
|
|
||||||
import { CreateClientComponent } from './shared/clients/create-client/create-client.component';
|
import { CreateClientComponent } from './shared/clients/create-client/create-client.component';
|
||||||
import { EditOrganizationalUnitComponent } from './shared/organizational-units/edit-organizational-unit/edit-organizational-unit.component';
|
import { EditOrganizationalUnitComponent } from './shared/organizational-units/edit-organizational-unit/edit-organizational-unit.component';
|
||||||
import { EditClientComponent } from './shared/clients/edit-client/edit-client.component';
|
import { EditClientComponent } from './shared/clients/edit-client/edit-client.component';
|
||||||
import { ShowOrganizationalUnitComponent} from "./shared/organizational-units/show-organizational-unit/show-organizational-unit.component";
|
import { ShowOrganizationalUnitComponent } from './shared/organizational-units/show-organizational-unit/show-organizational-unit.component';
|
||||||
import {ToastrService} from "ngx-toastr";
|
import { TreeViewComponent } from './shared/tree-view/tree-view.component';
|
||||||
import {TreeViewComponent} from "./shared/tree-view/tree-view.component";
|
import { LegendComponent } from './shared/legend/legend.component';
|
||||||
import {MatBottomSheet} from "@angular/material/bottom-sheet";
|
import { ClientTabViewComponent } from './components/client-tab-view/client-tab-view.component';
|
||||||
import {LegendComponent} from "./shared/legend/legend.component";
|
import { OrganizationalUnitTabViewComponent } from './components/organizational-unit-tab-view/organizational-unit-tab-view.component';
|
||||||
|
import { DeleteModalComponent } from '../../shared/delete_modal/delete-modal/delete-modal.component';
|
||||||
import { ClassroomViewDialogComponent } from './shared/classroom-view/classroom-view-modal';
|
import { ClassroomViewDialogComponent } from './shared/classroom-view/classroom-view-modal';
|
||||||
import {HttpClient} from "@angular/common/http";
|
|
||||||
import {PageEvent} from "@angular/material/paginator";
|
interface TreeNode {
|
||||||
import { SaveFiltersDialogComponent } from './shared/save-filters-dialog/save-filters-dialog.component';
|
clients?: any[];
|
||||||
import { AcctionsModalComponent } from './shared/acctions-modal/acctions-modal.component';
|
name: string;
|
||||||
import {MatTableDataSource} from "@angular/material/table";
|
type: string;
|
||||||
import {DatePipe} from "@angular/common";
|
children?: TreeNode[];
|
||||||
import {AdvancedSearchComponent} from "./components/advanced-search/advanced-search.component";
|
ip?: string;
|
||||||
import {MatTabChangeEvent} from "@angular/material/tabs";
|
'@id'?: string;
|
||||||
import {ClientTabViewComponent} from "./components/client-tab-view/client-tab-view.component";
|
hasClients?: boolean;
|
||||||
import {
|
status?: string ;
|
||||||
OrganizationalUnitTabViewComponent
|
}
|
||||||
} from "./components/organizational-unit-tab-view/organizational-unit-tab-view.component";
|
|
||||||
import { ExecuteCommandComponent } from '../commands/main-commands/execute-command/execute-command.component';
|
interface FlatNode {
|
||||||
import { ExecuteCommandOuComponent } from './shared/execute-command-ou/execute-command-ou.component';
|
name: string;
|
||||||
import { JoyrideService } from 'ngx-joyride';
|
type: string;
|
||||||
|
level: number;
|
||||||
|
expandable: boolean;
|
||||||
|
ip?: string;
|
||||||
|
hasClients?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-groups',
|
selector: 'app-groups',
|
||||||
|
@ -36,62 +49,87 @@ import { JoyrideService } from 'ngx-joyride';
|
||||||
})
|
})
|
||||||
export class GroupsComponent implements OnInit {
|
export class GroupsComponent implements OnInit {
|
||||||
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL;
|
baseUrl: string = import.meta.env.NG_APP_BASE_API_URL;
|
||||||
dataSource = new MatTableDataSource<any>();
|
|
||||||
organizationalUnits: UnidadOrganizativa[] = [];
|
organizationalUnits: UnidadOrganizativa[] = [];
|
||||||
selectedUnidad: UnidadOrganizativa | null = null;
|
selectedUnidad: UnidadOrganizativa | null = null;
|
||||||
selectedDetail: any | null = null;
|
selectedDetail: any | null = null;
|
||||||
children: any[] = [];
|
loading: boolean = false;
|
||||||
breadcrumb: string[] = [];
|
loadingChildren: boolean = false;
|
||||||
clientsData: any[] = [];
|
|
||||||
breadcrumbData: any[] = [];
|
|
||||||
loading:boolean = false;
|
|
||||||
loadingChildren:boolean = false;
|
|
||||||
searchTerm: string = '';
|
searchTerm: string = '';
|
||||||
selectedFilter1: string = 'none';
|
treeControl: FlatTreeControl<FlatNode>;
|
||||||
selectedFilter2: string = 'none';
|
treeFlattener: MatTreeFlattener<TreeNode, FlatNode>;
|
||||||
selectedFilterOS: string[] = [];
|
treeDataSource: MatTreeFlatDataSource<TreeNode, FlatNode>;
|
||||||
selectedFilterStatus: string[] = [];
|
selectedNode: TreeNode | null = null;
|
||||||
filterIP: string = '';
|
commands: any[] = [];
|
||||||
filterMAC: string = '';
|
commandsLoading: boolean = false;
|
||||||
filterName: string = '';
|
selectedClients: any[] = [];
|
||||||
filteredResults: any[] = [];
|
cols: number = 4;
|
||||||
|
selectedClientsOriginal: any[] = [];
|
||||||
|
currentView: 'card' | 'list' = 'list';
|
||||||
|
isTreeViewActive: boolean = false;
|
||||||
savedFilterNames: any[] = [];
|
savedFilterNames: any[] = [];
|
||||||
length: number = 0;
|
selectedTreeFilter: string = '';
|
||||||
itemsPerPage: number = 10;
|
@ViewChild('clientTab') clientTabComponent!: ClientTabViewComponent;
|
||||||
page: number = 1;
|
@ViewChild('organizationalUnitTab') organizationalUnitTabComponent!: OrganizationalUnitTabViewComponent;
|
||||||
pageSizeOptions: number[] = [5, 10, 25, 100];
|
|
||||||
selectedElements: any[] = [];
|
|
||||||
isAllSelected: boolean = false;
|
|
||||||
filters: { [key: string]: string } = {};
|
|
||||||
datePipe: DatePipe = new DatePipe('es-ES');
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private dataService: DataService,
|
private http: HttpClient,
|
||||||
public dialog: MatDialog,
|
private router: Router,
|
||||||
private toastService: ToastrService,
|
private dataService: DataService,
|
||||||
private _bottomSheet: MatBottomSheet,
|
public dialog: MatDialog,
|
||||||
private http: HttpClient,
|
private _bottomSheet: MatBottomSheet,
|
||||||
private joyrideService: JoyrideService
|
private joyrideService: JoyrideService,
|
||||||
) {}
|
private toastr: ToastrService
|
||||||
|
) {
|
||||||
|
this.treeFlattener = new MatTreeFlattener<TreeNode, FlatNode>(
|
||||||
|
(node: TreeNode, level: number) => ({
|
||||||
|
name: node.name,
|
||||||
|
type: node.type,
|
||||||
|
level,
|
||||||
|
expandable: !!node.children?.length,
|
||||||
|
hasClients: node.hasClients,
|
||||||
|
ip: node.ip,
|
||||||
|
['@id']: node['@id']
|
||||||
|
}),
|
||||||
|
node => node.level,
|
||||||
|
node => node.expandable,
|
||||||
|
node => node.children
|
||||||
|
);
|
||||||
|
|
||||||
|
this.treeControl = new FlatTreeControl<FlatNode>(
|
||||||
|
node => node.level,
|
||||||
|
node => node.expandable
|
||||||
|
);
|
||||||
|
|
||||||
|
this.treeDataSource = new MatTreeFlatDataSource(this.treeControl, this.treeFlattener);
|
||||||
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.search();
|
this.search();
|
||||||
this.getFilters();
|
this.getFilters();
|
||||||
|
this.updateGridCols();
|
||||||
|
window.addEventListener('resize', () => this.updateGridCols());
|
||||||
|
}
|
||||||
|
toggleView(view: 'card' | 'list') {
|
||||||
|
this.currentView = view;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewChild('clientTab') clientTabComponent!: ClientTabViewComponent;
|
updateGridCols(): void {
|
||||||
@ViewChild('organizationalUnitTab') organizationalUnitTabComponent!: OrganizationalUnitTabViewComponent;
|
const width = window.innerWidth;
|
||||||
|
this.cols = width <= 600 ? 1 : width <= 960 ? 2 : width <= 1280 ? 3 : 4;
|
||||||
|
}
|
||||||
|
|
||||||
onTabChange(event: MatTabChangeEvent) {
|
clearSelection(): void {
|
||||||
switch (event.index) {
|
this.selectedUnidad = null;
|
||||||
case 2:
|
this.selectedDetail = null;
|
||||||
this.clientTabComponent.search();
|
this.selectedClients = [];
|
||||||
break;
|
this.isTreeViewActive = false;
|
||||||
case 3:
|
}
|
||||||
this.organizationalUnitTabComponent.search();
|
|
||||||
break;
|
onTabChange(event: MatTabChangeEvent): void {
|
||||||
default:
|
if (event.index === 2) {
|
||||||
break;
|
this.clientTabComponent.search();
|
||||||
|
} else if (event.index === 3) {
|
||||||
|
this.organizationalUnitTabComponent.search();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,6 +144,21 @@ export class GroupsComponent implements OnInit {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
loadSelectedFilter(savedFilter: any) {
|
||||||
|
const url = `${this.baseUrl}/views/` + savedFilter[1];
|
||||||
|
console.log('llamando a:', url);
|
||||||
|
|
||||||
|
this.dataService.getFilter(savedFilter[1]).subscribe(response => {
|
||||||
|
console.log('Response from server:', response.filters);
|
||||||
|
if (response) {
|
||||||
|
console.log('Filter1:', response.filters);
|
||||||
|
}
|
||||||
|
}, error => {
|
||||||
|
console.error('Error:', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
search(): void {
|
search(): void {
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
this.dataService.getOrganizationalUnits(this.searchTerm).subscribe(
|
this.dataService.getOrganizationalUnits(this.searchTerm).subscribe(
|
||||||
|
@ -120,70 +173,112 @@ export class GroupsComponent implements OnInit {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
onSelectUnidad(unidad: UnidadOrganizativa): void {
|
onSelectUnidad(unidad: any): void {
|
||||||
this.selectedUnidad = unidad;
|
this.selectedUnidad = unidad;
|
||||||
this.selectedDetail = unidad;
|
this.selectedDetail = unidad;
|
||||||
this.breadcrumb = [unidad.name];
|
|
||||||
this.breadcrumbData = [unidad];
|
this.selectedClients = this.collectAllClients(unidad);
|
||||||
this.loadChildrenAndClients(unidad.id);
|
this.selectedClientsOriginal = [...this.selectedClients];
|
||||||
|
|
||||||
|
this.loadChildrenAndClients(unidad.id).then(fullData => {
|
||||||
|
const treeData = this.convertToTreeData(fullData);
|
||||||
|
this.treeDataSource.data = treeData[0]?.children || [];
|
||||||
|
});
|
||||||
|
|
||||||
|
this.isTreeViewActive = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
onSelectChild(child: any): void {
|
private collectAllClients(node: any): any[] {
|
||||||
this.selectedDetail = child;
|
let clients = node.clients || [];
|
||||||
if (child.type !== 'client' && child.uuid && child.id) {
|
if (node.children && node.children.length > 0) {
|
||||||
this.breadcrumb.push(child.name || child.name);
|
node.children.forEach((child: any) => {
|
||||||
this.breadcrumbData.push(child);
|
clients = clients.concat(this.collectAllClients(child));
|
||||||
this.loadChildrenAndClients(child.id);
|
});
|
||||||
|
}
|
||||||
|
return clients;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async loadChildrenAndClients(id: string): Promise<any> {
|
||||||
|
try {
|
||||||
|
const childrenData = await this.dataService.getChildren(id).toPromise();
|
||||||
|
const processHierarchy = (nodes: UnidadOrganizativa[]): TreeNode[] => {
|
||||||
|
return nodes.map(node => ({
|
||||||
|
name: node.name,
|
||||||
|
type: node.type,
|
||||||
|
'@id': node['@id'],
|
||||||
|
children: node.children ? processHierarchy(node.children) : [],
|
||||||
|
clients: node.clients || []
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
...this.selectedUnidad,
|
||||||
|
children: childrenData ? processHierarchy(childrenData) : []
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading children:', error);
|
||||||
|
return this.selectedUnidad;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
navigateToBreadcrumb(index: number): void {
|
convertToTreeData(data: any): TreeNode[] {
|
||||||
this.breadcrumb = this.breadcrumb.slice(0, index + 1);
|
const processNode = (node: UnidadOrganizativa): TreeNode => ({
|
||||||
const target = this.breadcrumbData[index];
|
name: node.name,
|
||||||
this.breadcrumbData = this.breadcrumbData.slice(0, index + 1);
|
type: node.type,
|
||||||
this.selectedDetail = target;
|
'@id': node['@id'],
|
||||||
this.loadChildrenAndClients(target.id);
|
children: node.children?.map(processNode) || [],
|
||||||
|
hasClients: node.clients && node.clients.length > 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
return [processNode(data)];
|
||||||
}
|
}
|
||||||
|
|
||||||
loadChildrenAndClients(id: string): void {
|
onNodeClick(node: TreeNode): void {
|
||||||
this.loadingChildren = true
|
this.selectedNode = node;
|
||||||
this.dataService.getChildren(id).subscribe(
|
this.selectedClients = node.clients || [];
|
||||||
childrenData => {
|
this.selectedClientsOriginal = [...this.selectedClients];
|
||||||
this.dataService.getClients(id).subscribe(
|
if (node.hasClients) {
|
||||||
clientsData => {
|
const url = `${this.baseUrl}${node['@id']}`;
|
||||||
this.clientsData = clientsData;
|
this.http.get(url).subscribe(
|
||||||
const newChildren = [...childrenData, ...clientsData];
|
(data: any) => {
|
||||||
|
this.selectedClientsOriginal = [...data.clients];
|
||||||
if (newChildren.length > 0) {
|
this.selectedClients = data.clients || [];
|
||||||
this.children = newChildren;
|
},
|
||||||
} else {
|
(error) => {
|
||||||
this.children = [];
|
console.error('Error fetching clients:', error);
|
||||||
}
|
}
|
||||||
this.loadingChildren = false
|
);
|
||||||
},
|
} else {
|
||||||
error => {
|
this.selectedClients = [];
|
||||||
console.error('Error fetching clients', error);
|
this.selectedClientsOriginal = [];
|
||||||
this.clientsData = [];
|
}
|
||||||
this.children = [];
|
|
||||||
this.loadingChildren = false
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
|
||||||
error => {
|
|
||||||
console.error('Error fetching children', error);
|
|
||||||
this.children = [];
|
|
||||||
this.loadingChildren = false
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
addOU(event: MouseEvent, parent:any = null): void {
|
getNodeIcon(node: any): string {
|
||||||
|
console.log('Node:', node);
|
||||||
|
switch (node.type) {
|
||||||
|
case 'organizational-unit': return 'apartment';
|
||||||
|
case 'classrooms-group': return 'doors';
|
||||||
|
case 'classroom': return 'school';
|
||||||
|
case 'clients-group': return 'lan';
|
||||||
|
case 'client': return 'computer';
|
||||||
|
default: return 'group';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addOU(event: MouseEvent, parent: any = null): void {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
const dialogRef = this.dialog.open(CreateOrganizationalUnitComponent, { data: { parent }, width: '900px'});
|
const dialogRef = this.dialog.open(CreateOrganizationalUnitComponent, { data: { parent }, width: '900px' });
|
||||||
dialogRef.afterClosed().subscribe(() => {
|
dialogRef.afterClosed().subscribe(() => {
|
||||||
this.dataService.getOrganizationalUnits().subscribe(
|
this.dataService.getOrganizationalUnits().subscribe(
|
||||||
data => {
|
data => {
|
||||||
this.organizationalUnits = data
|
this.organizationalUnits = data;
|
||||||
|
this.loadChildrenAndClients(this.selectedUnidad?.id || '').then(updatedData => {
|
||||||
|
const treeData = this.convertToTreeData(updatedData);
|
||||||
|
this.treeDataSource.data = treeData[0]?.children || [];
|
||||||
|
});
|
||||||
},
|
},
|
||||||
error => console.error('Error fetching unidades organizativas', error)
|
error => console.error('Error fetching unidades organizativas', error)
|
||||||
);
|
);
|
||||||
|
@ -192,262 +287,198 @@ export class GroupsComponent implements OnInit {
|
||||||
|
|
||||||
addClient(event: MouseEvent, organizationalUnit: any = null): void {
|
addClient(event: MouseEvent, organizationalUnit: any = null): void {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
|
||||||
const dialogRef = this.dialog.open(CreateClientComponent, { data: { organizationalUnit }, width: '900px' });
|
const dialogRef = this.dialog.open(CreateClientComponent, { data: { organizationalUnit }, width: '900px' });
|
||||||
|
|
||||||
dialogRef.afterClosed().subscribe(() => {
|
dialogRef.afterClosed().subscribe(() => {
|
||||||
this.dataService.getOrganizationalUnits().subscribe(
|
this.dataService.getOrganizationalUnits().subscribe(
|
||||||
data => {
|
data => {
|
||||||
this.organizationalUnits = data;
|
this.organizationalUnits = data;
|
||||||
|
this.loadChildrenAndClients(this.selectedUnidad?.id || '').then(updatedData => {
|
||||||
if (organizationalUnit && organizationalUnit.id) {
|
const treeData = this.convertToTreeData(updatedData);
|
||||||
this.loadChildrenAndClients(organizationalUnit.id);
|
this.treeDataSource.data = treeData[0]?.children || [];
|
||||||
}
|
});
|
||||||
},
|
if (organizationalUnit && organizationalUnit.id) {
|
||||||
error => console.error('Error fetching unidades organizativas', error)
|
this.loadChildrenAndClients(organizationalUnit.id);
|
||||||
);
|
this.refreshClients(organizationalUnit);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error => console.error('Error fetching unidades organizativas', error)
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
onDeleteClick(event: MouseEvent, uuid: string, name: string, type: string): void {
|
|
||||||
|
setSelectedNode(node: TreeNode): void {
|
||||||
|
this.selectedNode = node;
|
||||||
|
}
|
||||||
|
|
||||||
|
onEditNode(event: MouseEvent, node: TreeNode | null): void {
|
||||||
|
if (!node) return;
|
||||||
|
|
||||||
|
const uuid = node['@id'] ? node['@id'].split('/').pop() : '';
|
||||||
|
const type = node.type;
|
||||||
|
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
if (type === 'client') {
|
|
||||||
const dialogRef = this.dialog.open(DeleteModalComponent, {
|
|
||||||
width: '400px',
|
|
||||||
data: { name }
|
|
||||||
});
|
|
||||||
|
|
||||||
dialogRef.afterClosed().subscribe(result => {
|
if (type !== 'client') {
|
||||||
if (result) {
|
this.dialog.open(EditOrganizationalUnitComponent, { data: { uuid }, width: '900px' });
|
||||||
this.dataService.deleteElement(uuid, type).subscribe(
|
|
||||||
() => {
|
|
||||||
this.loadChildrenAndClients(this.selectedUnidad?.id || '');
|
|
||||||
this.dataService.getOrganizationalUnits().subscribe(
|
|
||||||
data => this.organizationalUnits = data,
|
|
||||||
error => console.error('Error fetching unidades organizativas', error)
|
|
||||||
);
|
|
||||||
this.openSnackBar(false, 'Entidad eliminada exitosamente')
|
|
||||||
},
|
|
||||||
error => {
|
|
||||||
console.error('Error deleting element', error)
|
|
||||||
this.openSnackBar(true, error.error['hydra:description'])
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
const dialogDeleteGroupRef = this.dialog.open(DeleteModalComponent, {
|
this.dialog.open(EditClientComponent, { data: { uuid }, width: '900px' });
|
||||||
width: '400px',
|
|
||||||
data: { name }
|
|
||||||
});
|
|
||||||
|
|
||||||
dialogDeleteGroupRef.afterClosed().subscribe(result => {
|
|
||||||
if (result && result === 'delete') {
|
|
||||||
this.dataService.deleteElement(uuid, type).subscribe(
|
|
||||||
() => {
|
|
||||||
this.loadChildrenAndClients(this.selectedUnidad?.id || '');
|
|
||||||
this.dataService.getOrganizationalUnits().subscribe(
|
|
||||||
data => this.organizationalUnits = data,
|
|
||||||
error => console.error('Error fetching unidades organizativas', error)
|
|
||||||
);
|
|
||||||
this.openSnackBar(false, 'Entidad eliminada exitosamente')
|
|
||||||
},
|
|
||||||
error => {
|
|
||||||
console.error('Error deleting element', error)
|
|
||||||
this.openSnackBar(true, error.error['hydra:description'])
|
|
||||||
}
|
|
||||||
);
|
|
||||||
} else if (result && result === 'change') {
|
|
||||||
this.dataService.changeParent(uuid).subscribe(
|
|
||||||
() => {
|
|
||||||
this.loadChildrenAndClients(this.selectedUnidad?.id || '');
|
|
||||||
this.dataService.getOrganizationalUnits().subscribe(
|
|
||||||
data => this.organizationalUnits = data,
|
|
||||||
error => console.error('Error fetching unidades organizativas', error)
|
|
||||||
);
|
|
||||||
this.openSnackBar(false, 'Entidad eliminada exitosamente')
|
|
||||||
},
|
|
||||||
error => {
|
|
||||||
console.error('Error deleting element', error)
|
|
||||||
this.openSnackBar(true, error.error['hydra:description'])
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onDelete(node: TreeNode | null): void {
|
||||||
|
console.log('Deleting node:', node);
|
||||||
|
}
|
||||||
|
|
||||||
|
onDeleteClick(event: MouseEvent, node: TreeNode | null, clientNode?: TreeNode | null): void {
|
||||||
|
console.log('Deleting node or client:', node);
|
||||||
|
|
||||||
|
const uuid = node && node['@id'] ? node['@id'].split('/').pop() || '' : '';
|
||||||
|
const name = node?.name || 'Elemento desconocido';
|
||||||
|
const type = node?.type || '';
|
||||||
|
|
||||||
|
event.stopPropagation();
|
||||||
|
|
||||||
|
const dialogRef = this.dialog.open(DeleteModalComponent, {
|
||||||
|
width: '400px',
|
||||||
|
data: { name }
|
||||||
|
});
|
||||||
|
|
||||||
|
dialogRef.afterClosed().subscribe(result => {
|
||||||
|
if (result === true) {
|
||||||
|
this.dataService.deleteElement(uuid, type).subscribe(
|
||||||
|
() => {
|
||||||
|
console.log('Entity deleted successfully:', uuid);
|
||||||
|
|
||||||
|
this.loadChildrenAndClients(this.selectedUnidad?.id || '').then(updatedData => {
|
||||||
|
const treeData = this.convertToTreeData(updatedData);
|
||||||
|
this.treeDataSource.data = treeData[0]?.children || [];
|
||||||
|
});
|
||||||
|
|
||||||
|
if (type === 'client' && clientNode) {
|
||||||
|
console.log('Refreshing clients for node:', clientNode);
|
||||||
|
this.refreshClients(clientNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.dataService.getOrganizationalUnits().subscribe(
|
||||||
|
data => {
|
||||||
|
this.organizationalUnits = data;
|
||||||
|
},
|
||||||
|
error => console.error('Error fetching unidades organizativas:', error)
|
||||||
|
);
|
||||||
|
|
||||||
|
this.toastr.success('Entidad eliminada exitosamente');
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
console.error('Error deleting entity:', error);
|
||||||
|
this.toastr.error('Error al eliminar la entidad', error.message);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private refreshClients(node: TreeNode): void {
|
||||||
|
if (!node || !node['@id']) {
|
||||||
|
console.warn('Node or @id is missing, clearing clients.');
|
||||||
|
this.selectedClients = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = `${this.baseUrl}${node['@id']}`;
|
||||||
|
console.log('Fetching clients for node with URL:', url);
|
||||||
|
|
||||||
|
this.http.get(url).subscribe(
|
||||||
|
(data: any) => {
|
||||||
|
console.log('Response data:', data);
|
||||||
|
if (data && Array.isArray(data.clients)) {
|
||||||
|
this.selectedClients = data.clients;
|
||||||
|
console.log('Clients updated successfully:', this.selectedClients);
|
||||||
|
} else {
|
||||||
|
console.warn('No "clients" field found in response, clearing clients.');
|
||||||
|
this.selectedClients = [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
console.error('Error refreshing clients:', error);
|
||||||
|
const errorMessage = error.status === 404
|
||||||
|
? 'No se encontraron clientes para este nodo.'
|
||||||
|
: 'Error al comunicarse con el servidor.';
|
||||||
|
this.toastr.error(errorMessage);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
onEditClick(event: MouseEvent, type: any, uuid: string): void {
|
onEditClick(event: MouseEvent, type: any, uuid: string): void {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
if (type != "client") {
|
if (type != 'client') {
|
||||||
const dialogRef = this.dialog.open(EditOrganizationalUnitComponent, { data: { uuid }, width: '900px'});
|
this.dialog.open(EditOrganizationalUnitComponent, { data: { uuid }, width: '900px' });
|
||||||
} else {
|
} else {
|
||||||
const dialogRef = this.dialog.open(EditClientComponent, { data: { uuid }, width: '900px' } );
|
this.dialog.open(EditClientComponent, { data: { uuid }, width: '900px' });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onShowClick(event: MouseEvent, data: any): void {
|
onRoomMap(room: any): void {
|
||||||
|
this.http.get(`${this.baseUrl}`+ room['@id']).subscribe(
|
||||||
|
(response: any) => {
|
||||||
|
this.dialog.open(ClassroomViewDialogComponent, {
|
||||||
|
width: '90vw',
|
||||||
|
data: { clients: response.clients }
|
||||||
|
});
|
||||||
|
},
|
||||||
|
(error: any) => {
|
||||||
|
console.error('Error en la solicitud HTTP:', error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchCommands(): void {
|
||||||
|
this.commandsLoading = true;
|
||||||
|
this.http.get(`${this.baseUrl}`+'/commands?page=1&itemsPerPage=30').subscribe(
|
||||||
|
(response: any) => {
|
||||||
|
this.commands = response['hydra:member'];
|
||||||
|
this.commandsLoading = false;
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
console.error('Error fetching commands:', error);
|
||||||
|
this.commandsLoading = false;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
executeCommand(command: any, selectedNode: any): void {
|
||||||
|
this.toastr.success('Ejecutando comando: ' + command.name + " en " + selectedNode.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
onClientActions(client: any): void {
|
||||||
|
console.log('Client actions:', client);
|
||||||
|
}
|
||||||
|
|
||||||
|
onShowClientDetail(event: MouseEvent, client: any): void {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
if (data.type != "client") {
|
this.router.navigate(['clients', client.uuid], { state: { clientData: client } });
|
||||||
const dialogRef = this.dialog.open(ShowOrganizationalUnitComponent, { data: { data }, width: '700px'});
|
}
|
||||||
|
|
||||||
|
onShowDetailsClick(event: MouseEvent, data: any): void {
|
||||||
|
event.stopPropagation();
|
||||||
|
if (data.type != 'client') {
|
||||||
|
this.dialog.open(ShowOrganizationalUnitComponent, { data: { data }, width: '700px' });
|
||||||
|
}
|
||||||
|
if (data.type == 'client') {
|
||||||
|
this.router.navigate(['clients', data['@id'].split('/').pop()], { state: { clientData: data } });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onTreeClick(event: MouseEvent, data: any): void {
|
onTreeClick(event: MouseEvent, data: any): void {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
if (data.type != "client") {
|
if (data.type != 'client') {
|
||||||
const dialogRef = this.dialog.open(TreeViewComponent, { data: { data }, width: '800px'});
|
this.dialog.open(TreeViewComponent, { data: { data }, width: '800px' });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onExecuteCommand(event: MouseEvent, child: any, name: string, type:string): void {
|
|
||||||
console.log('Executing command on:', child);
|
|
||||||
|
|
||||||
this.dialog.open(ExecuteCommandOuComponent, {
|
|
||||||
width: '50%',
|
|
||||||
data: { childUnitUuid: child }
|
|
||||||
}).afterClosed().subscribe((result) => {
|
|
||||||
if (result) {
|
|
||||||
console.log('Comando ejecutado con éxito');
|
|
||||||
} else {
|
|
||||||
console.log('Ejecución de comando cancelada');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
openSnackBar(isError: boolean, message: string) {
|
|
||||||
if (isError) {
|
|
||||||
this.toastService.error(' Error al eliminar la entidad: ' + message, 'Error');
|
|
||||||
} else
|
|
||||||
this.toastService.success(message, 'Éxito');
|
|
||||||
}
|
|
||||||
|
|
||||||
openBottomSheet(): void {
|
openBottomSheet(): void {
|
||||||
this._bottomSheet.open(LegendComponent);
|
this._bottomSheet.open(LegendComponent);
|
||||||
}
|
}
|
||||||
|
|
||||||
roomMap(): void {
|
|
||||||
if (this.selectedDetail && this.selectedDetail.type === 'classroom') {
|
|
||||||
const dialogRef = this.dialog.open(ClassroomViewDialogComponent, {
|
|
||||||
width: '90vw',
|
|
||||||
data: { clients: this.clientsData }
|
|
||||||
});
|
|
||||||
|
|
||||||
dialogRef.afterClosed().subscribe(result => {
|
|
||||||
console.log('The dialog was closed');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
applyFilter() {
|
|
||||||
this.dataService.getFilteredResults(this.selectedFilter1, this.selectedFilter2, this.filterName, this.filterIP, this.filterMAC, this.page, this.itemsPerPage)
|
|
||||||
.subscribe(
|
|
||||||
response => {
|
|
||||||
this.filteredResults = response.results;
|
|
||||||
this.length = response.total;
|
|
||||||
},
|
|
||||||
error => {
|
|
||||||
console.error('Error al obtener los resultados filtrados', error);
|
|
||||||
this.filteredResults = [];
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
onPageChange(event: PageEvent) {
|
|
||||||
this.page = event.pageIndex;
|
|
||||||
this.itemsPerPage = event.pageSize;
|
|
||||||
this.applyFilter();
|
|
||||||
}
|
|
||||||
|
|
||||||
saveFilters() {
|
|
||||||
const dialogRef = this.dialog.open(SaveFiltersDialogComponent);
|
|
||||||
|
|
||||||
dialogRef.afterClosed().subscribe(result => {
|
|
||||||
if (result) {
|
|
||||||
const filters = {
|
|
||||||
name: result,
|
|
||||||
favourite: true,
|
|
||||||
filters: {
|
|
||||||
filter0: this.filterName,
|
|
||||||
filter1: this.selectedFilter1,
|
|
||||||
filter2: this.selectedFilter2,
|
|
||||||
filter3: this.selectedFilterOS,
|
|
||||||
filter4: this.selectedFilterStatus,
|
|
||||||
filter5: this.filterIP,
|
|
||||||
filter6: this.filterMAC,
|
|
||||||
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
this.http.post(`${this.baseUrl}/views`, filters).subscribe(response => {
|
|
||||||
console.log('Response from server:', response);
|
|
||||||
this.toastService.success('Se ha guardado el filtro correctamente');
|
|
||||||
}, error => {
|
|
||||||
console.error('Error:', error);
|
|
||||||
this.toastService.error(error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
loadSelectedFilter(savedFilter: any) {
|
|
||||||
const url = `${this.baseUrl}/views/` + savedFilter[1];
|
|
||||||
console.log('llamando a:', url);
|
|
||||||
|
|
||||||
this.dataService.getFilter(savedFilter[1]).subscribe(response => {
|
|
||||||
console.log('Response from server:', response.filters);
|
|
||||||
if (response) {
|
|
||||||
console.log('Filter1:', response.filters);
|
|
||||||
this.filterName = response.filters.filter0 || '';
|
|
||||||
this.selectedFilter1 = response.filters.filter1 || null;
|
|
||||||
this.selectedFilter2 = response.filters.filter2 || '';
|
|
||||||
|
|
||||||
this.selectedFilterOS = response.filters.filter3 || [];
|
|
||||||
this.selectedFilterStatus = response.filters.filter4 || [];
|
|
||||||
this.filterIP = response.filters.filter5 || '';
|
|
||||||
this.filterMAC = response.filters.filter6 || '';
|
|
||||||
|
|
||||||
this.applyFilter();
|
|
||||||
}
|
|
||||||
}, error => {
|
|
||||||
console.error('Error:', error);
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
onCheckboxChange(event: any, name: string, uuid: string) {
|
|
||||||
if (event.checked) {
|
|
||||||
this.selectedElements.push(uuid);
|
|
||||||
} else {
|
|
||||||
const index = this.selectedElements.indexOf(name);
|
|
||||||
if (index > -1) {
|
|
||||||
this.selectedElements.splice(index, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.isAllSelected = this.selectedElements.length === this.filteredResults.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleSelectAll() {
|
|
||||||
this.isAllSelected = !this.isAllSelected;
|
|
||||||
|
|
||||||
if (this.isAllSelected) {
|
|
||||||
this.selectedElements = this.filteredResults.map(result => result.uuid);
|
|
||||||
} else {
|
|
||||||
this.selectedElements = [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
isSelected(name: string): boolean {
|
|
||||||
return this.selectedElements.includes(name);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
sendActions() {
|
|
||||||
const dialogRef = this.dialog.open(AcctionsModalComponent, { data: { selectedElements: this.selectedElements }, width: '700px'});
|
|
||||||
}
|
|
||||||
|
|
||||||
iniciarTour(): void {
|
iniciarTour(): void {
|
||||||
this.joyrideService.startTour({
|
this.joyrideService.startTour({
|
||||||
steps: ['groupsTitleStepText', 'addStep', 'keyStep', 'unitStep', 'elementsStep', 'tabsStep'],
|
steps: ['groupsTitleStepText', 'addStep', 'keyStep', 'unitStep', 'elementsStep', 'tabsStep'],
|
||||||
|
@ -455,4 +486,63 @@ export class GroupsComponent implements OnInit {
|
||||||
themeColor: '#3f51b5'
|
themeColor: '#3f51b5'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hasChild = (_: number, node: FlatNode): boolean => node.expandable;
|
||||||
|
isLeafNode = (_: number, node: FlatNode): boolean => !node.expandable;
|
||||||
|
|
||||||
|
filterTree(searchTerm: string, filterType: string): void {
|
||||||
|
const filterNodes = (nodes: any[]): any[] => {
|
||||||
|
return nodes
|
||||||
|
.map(node => {
|
||||||
|
const matchesName = node.name.toLowerCase().includes(searchTerm.toLowerCase());
|
||||||
|
const matchesType = filterType ? node.type.toLowerCase() === filterType.toLowerCase() : true;
|
||||||
|
|
||||||
|
const filteredChildren = node.children ? filterNodes(node.children) : [];
|
||||||
|
|
||||||
|
if (matchesName && matchesType || filteredChildren.length > 0) {
|
||||||
|
return { ...node, children: filteredChildren };
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
})
|
||||||
|
.filter(node => node !== null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const filteredData = filterNodes(this.treeDataSource.data);
|
||||||
|
|
||||||
|
this.treeDataSource.data = filteredData;
|
||||||
|
}
|
||||||
|
|
||||||
|
onTreeFilterInput(event: Event): void {
|
||||||
|
const input = event.target as HTMLInputElement;
|
||||||
|
const searchTerm = input?.value || '';
|
||||||
|
this.filterTree(searchTerm, this.selectedTreeFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
onClientFilterInput(event: Event): void {
|
||||||
|
const input = event.target as HTMLInputElement;
|
||||||
|
const searchTerm = input?.value || '';
|
||||||
|
this.filterClients(searchTerm);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
filterClients(searchTerm: string): void {
|
||||||
|
if (!searchTerm) {
|
||||||
|
this.selectedClients = [...this.selectedClientsOriginal];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const lowerTerm = searchTerm.toLowerCase();
|
||||||
|
|
||||||
|
this.selectedClients = this.selectedClientsOriginal.filter(client => {
|
||||||
|
const matchesName = client.name.toLowerCase().includes(lowerTerm);
|
||||||
|
const matchesIP = client.ip?.toLowerCase().includes(lowerTerm) || false;
|
||||||
|
const matchesStatus = client.status?.toLowerCase().includes(lowerTerm) || false;
|
||||||
|
const matchesMac = client.mac?.toLowerCase().includes(lowerTerm) || false;
|
||||||
|
|
||||||
|
return matchesName || matchesIP || matchesStatus || matchesMac;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,9 @@ export interface Aula {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UnidadOrganizativa {
|
export interface UnidadOrganizativa {
|
||||||
|
clients: any[];
|
||||||
|
children: UnidadOrganizativa[];
|
||||||
|
'@id'?: string;
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
uuid: string;
|
uuid: string;
|
||||||
|
|
|
@ -90,6 +90,8 @@ export class DataService {
|
||||||
const url = type === 'client'
|
const url = type === 'client'
|
||||||
? `${this.baseUrl}/clients/${uuid}`
|
? `${this.baseUrl}/clients/${uuid}`
|
||||||
: `${this.baseUrl}/organizational-units/${uuid}`;
|
: `${this.baseUrl}/organizational-units/${uuid}`;
|
||||||
|
|
||||||
|
console.log('DELETE URL:', url); // Depuración
|
||||||
return this.http.delete<void>(url).pipe(
|
return this.http.delete<void>(url).pipe(
|
||||||
catchError(error => {
|
catchError(error => {
|
||||||
console.error('Error deleting element', error);
|
console.error('Error deleting element', error);
|
||||||
|
@ -97,6 +99,7 @@ export class DataService {
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
changeParent(uuid: string): Observable<void> {
|
changeParent(uuid: string): Observable<void> {
|
||||||
const url = `${this.baseUrl}/organizational-units/${uuid}/change-parent`;
|
const url = `${this.baseUrl}/organizational-units/${uuid}/change-parent`;
|
||||||
|
@ -183,6 +186,16 @@ export class DataService {
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getOrganizationalUnitById(id: string): Observable<any> {
|
||||||
|
const url = `${this.baseUrl}/organizational-units/${id}`;
|
||||||
|
return this.http.get<any>(url).pipe(
|
||||||
|
catchError(error => {
|
||||||
|
console.error('Error fetching organizational unit', error);
|
||||||
|
return throwError(error);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,18 +31,11 @@ mat-card {
|
||||||
}
|
}
|
||||||
|
|
||||||
.client-info {
|
.client-info {
|
||||||
position: absolute;
|
|
||||||
top: 20px;
|
|
||||||
left: 5px;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
background-color: rgba(0, 0, 0, 0);
|
|
||||||
color: black;
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 15px;
|
margin-top: 5px;
|
||||||
box-sizing: border-box;
|
font-size: medium;
|
||||||
|
color: gray;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.client-name {
|
.client-name {
|
||||||
|
|
|
@ -10,12 +10,9 @@
|
||||||
<mat-card appearance="outlined">
|
<mat-card appearance="outlined">
|
||||||
<div class="client-image-container">
|
<div class="client-image-container">
|
||||||
<img mat-card-image src="assets/images/client.png" alt="{{ 'clientAlt' | translate }}" class="client-image"/>
|
<img mat-card-image src="assets/images/client.png" alt="{{ 'clientAlt' | translate }}" class="client-image"/>
|
||||||
<div class="client-info">
|
</div>
|
||||||
<div class="client-name">{{ client.name }}</div>
|
<div class="client-info">
|
||||||
<div class="client-details">
|
<span>{{ client.name }}</span>
|
||||||
<span>{{ client.ip }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</mat-card>
|
</mat-card>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,63 +1,161 @@
|
||||||
h1 {
|
.create-client-container {
|
||||||
text-align: center;
|
|
||||||
font-family: 'Roboto', sans-serif;
|
|
||||||
font-weight: 400;
|
|
||||||
color: #3f51b5;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.network-form {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 15px;
|
padding: 16px;
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
font-size: 14px;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-field {
|
h1, h3, h4 {
|
||||||
width: 100%;
|
margin: 0 0 16px;
|
||||||
margin-top: 10px;
|
color: #333;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
font-size: 16px;
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inputs-container {
|
||||||
|
display: flex;
|
||||||
|
gap: 24px;
|
||||||
|
margin-top: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mat-dialog-content {
|
.mat-dialog-content {
|
||||||
padding: 50px;
|
flex: 1;
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 16px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
min-width: 600px;
|
||||||
|
max-width: 90vw;
|
||||||
|
width: 800px;
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
.create-multiple-client-container {
|
||||||
text-transform: none;
|
flex: 1;
|
||||||
font-size: 16px;
|
background-color: #f9f9f9;
|
||||||
font-weight: 500;
|
border-radius: 8px;
|
||||||
|
padding: 16px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.mat-slide-toggle {
|
.client-form {
|
||||||
margin-top: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
mat-option .unit-name {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
mat-option .unit-path {
|
|
||||||
display: block;
|
|
||||||
font-size: 0.8em;
|
|
||||||
color: gray;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading-spinner {
|
|
||||||
display: block;
|
|
||||||
margin: 0 auto;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.create-client-container {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.grid-form {
|
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(2, 1fr);
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||||
gap: 20px;
|
gap: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-field {
|
.form-field {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mat-form-field {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scrollable-table {
|
||||||
|
max-height: 200px;
|
||||||
|
overflow-y: auto;
|
||||||
|
margin-top: 16px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
|
||||||
|
th, td {
|
||||||
|
text-align: left;
|
||||||
|
padding: 8px;
|
||||||
|
border-bottom: 1px solid #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
background-color: #f1f1f1;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr:hover {
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:last-child {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-dialog-actions {
|
||||||
|
margin-top: 16px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.mat-raised-button {
|
||||||
|
text-transform: none;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-spinner {
|
||||||
|
margin: 16px auto;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-button {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: #007BFF;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-button:hover {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-divider {
|
||||||
|
margin: 0 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="file"] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.inputs-container {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-dialog-content, .create-multiple-client-container {
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scrollable-table {
|
||||||
|
max-height: 150px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,92 +1,136 @@
|
||||||
<div class="create-client-container">
|
<div class="create-client-container mat-elevation-z4">
|
||||||
<h1 mat-dialog-title>{{ 'addClientDialogTitle' | translate }}</h1>
|
<h1>{{ 'addClientTitle' | translate }}s</h1>
|
||||||
<div class="mat-dialog-content" [ngClass]="{'loading': loading}">
|
<div class="inputs-container">
|
||||||
<mat-spinner class="loading-spinner" *ngIf="loading"></mat-spinner>
|
<div class="mat-dialog-content" [ngClass]="{'loading': loading}">
|
||||||
<form [formGroup]="clientForm" class="client-form grid-form" *ngIf="!loading">
|
|
||||||
<mat-form-field class="form-field">
|
<form [formGroup]="clientForm" class="client-form grid-form" *ngIf="!loading">
|
||||||
<mat-label>{{ 'organizationalUnitLabel' | translate }}</mat-label>
|
<mat-form-field class="form-field">
|
||||||
<mat-select formControlName="organizationalUnit">
|
<mat-label>{{ 'organizationalUnitLabel' | translate }}</mat-label>
|
||||||
<mat-option *ngFor="let unit of parentUnits" [value]="unit['@id']">
|
<mat-select formControlName="organizationalUnit">
|
||||||
<div class="unit-name">{{ unit.name }}</div>
|
<mat-option *ngFor="let unit of parentUnits" [value]="unit['@id']">
|
||||||
<div class="unit-path">{{ unit.path }}</div>
|
<div class="unit-name">{{ unit.name }}</div>
|
||||||
</mat-option>
|
</mat-option>
|
||||||
</mat-select>
|
</mat-select>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
</form>
|
||||||
|
|
||||||
<mat-form-field class="form-field">
|
<div *ngIf="!isSingleClientForm; else singleClientForm">
|
||||||
<mat-label>{{ 'nameLabel' | translate }}</mat-label>
|
<h3>Añadir múltiples clientes</h3>
|
||||||
<input matInput formControlName="name">
|
<div class="upload-container">
|
||||||
</mat-form-field>
|
<button mat-raised-button color="primary" (click)="fileInput.click()">Subir fichero</button>
|
||||||
|
<input #fileInput type="file" (change)="onFileUpload($event)" accept=".csv" hidden>
|
||||||
|
<p>o añadelos manualmente:</p>
|
||||||
|
<div *ngIf="showTextarea">
|
||||||
|
<textarea #textarea matInput placeholder="host bbaa-it1-1..." rows="20" cols="100"></textarea>
|
||||||
|
<button mat-raised-button color="primary" (click)="onTextarea(textarea.value)">cargar</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<mat-form-field class="form-field">
|
<h4 *ngIf="uploadedClients.length > 0">Clientes importados:</h4>
|
||||||
<mat-label>{{ 'ogLiveLabel' | translate }}</mat-label>
|
<div class="scrollable-table">
|
||||||
<mat-select formControlName="ogLive">
|
<table mat-table [dataSource]="uploadedClients" class="mat-elevation-z8" *ngIf="uploadedClients.length > 0">
|
||||||
<mat-option *ngFor="let oglive of ogLives" [value]="oglive['@id']">
|
<ng-container matColumnDef="name">
|
||||||
{{ oglive.name }}
|
<th mat-header-cell *matHeaderCellDef> Nombre </th>
|
||||||
</mat-option>
|
<td mat-cell *matCellDef="let client"> {{ client.name }} </td>
|
||||||
</mat-select>
|
</ng-container>
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field class="form-field">
|
<ng-container matColumnDef="ip">
|
||||||
<mat-label>{{ 'serialNumberLabel' | translate }}</mat-label>
|
<th mat-header-cell *matHeaderCellDef> IP </th>
|
||||||
<input matInput formControlName="serialNumber">
|
<td mat-cell *matCellDef="let client"> {{ client.ip }} </td>
|
||||||
</mat-form-field>
|
</ng-container>
|
||||||
|
|
||||||
<mat-form-field class="form-field">
|
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||||
<mat-label>{{ 'netifaceLabel' | translate }}</mat-label>
|
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
|
||||||
<mat-select formControlName="netiface">
|
</table>
|
||||||
<mat-option *ngFor="let type of netifaceTypes" [value]="type.value">
|
</div>
|
||||||
{{ type.name }}
|
</div>
|
||||||
</mat-option>
|
|
||||||
</mat-select>
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field class="form-field">
|
<!-- Añadir uon cliente -->
|
||||||
<mat-label>{{ 'netDriverLabel' | translate }}</mat-label>
|
|
||||||
<mat-select formControlName="netDriver">
|
|
||||||
<mat-option *ngFor="let type of netDriverTypes" [value]="type.value">
|
|
||||||
{{ type.name }}
|
|
||||||
</mat-option>
|
|
||||||
</mat-select>
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field class="form-field">
|
<ng-template #singleClientForm>
|
||||||
<mat-label>{{ 'macLabel' | translate }}</mat-label>
|
<h3>Añadir un cliente</h3>
|
||||||
<mat-hint>{{ 'macHint' | translate }}</mat-hint>
|
<mat-spinner class="loading-spinner" *ngIf="loading"></mat-spinner>
|
||||||
<input matInput formControlName="mac">
|
<form [formGroup]="clientForm" class="client-form grid-form" *ngIf="!loading">
|
||||||
<mat-error>{{ 'macError' | translate }}</mat-error>
|
|
||||||
</mat-form-field>
|
<mat-form-field class="form-field">
|
||||||
|
<mat-label>{{ 'nameLabel' | translate }}</mat-label>
|
||||||
|
<input matInput formControlName="name">
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
<mat-form-field class="form-field">
|
<mat-form-field class="form-field">
|
||||||
<mat-label>{{ 'ipLabel' | translate }}</mat-label>
|
<mat-label>{{ 'ogLiveLabel' | translate }}</mat-label>
|
||||||
<mat-hint>{{ 'ipHint' | translate }}</mat-hint>
|
<mat-select formControlName="ogLive">
|
||||||
<input matInput formControlName="ip">
|
<mat-option *ngFor="let oglive of ogLives" [value]="oglive['@id']">
|
||||||
<mat-error>{{ 'ipError' | translate }}</mat-error>
|
{{ oglive.name }}
|
||||||
</mat-form-field>
|
</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
<mat-form-field class="form-field">
|
<mat-form-field class="form-field">
|
||||||
<mat-label>{{ 'templateLabel' | translate }}</mat-label>
|
<mat-label>{{ 'serialNumberLabel' | translate }}</mat-label>
|
||||||
<mat-select formControlName="template">
|
<input matInput formControlName="serialNumber">
|
||||||
<mat-option *ngFor="let template of templates" [value]="template['@id']">
|
</mat-form-field>
|
||||||
{{ template.name }}
|
|
||||||
</mat-option>
|
|
||||||
</mat-select>
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field class="form-field">
|
<mat-form-field class="form-field">
|
||||||
<mat-label>{{ 'hardwareProfileLabel' | translate }}</mat-label>
|
<mat-label>{{ 'netifaceLabel' | translate }}</mat-label>
|
||||||
<mat-select formControlName="hardwareProfile">
|
<mat-select formControlName="netiface">
|
||||||
<mat-option *ngFor="let unit of hardwareProfiles" [value]="unit['@id']">
|
<mat-option *ngFor="let type of netifaceTypes" [value]="type.value">
|
||||||
{{ unit.description }}
|
{{ type.name }}
|
||||||
</mat-option>
|
</mat-option>
|
||||||
</mat-select>
|
</mat-select>
|
||||||
<mat-error>{{ 'hardwareProfileError' | translate }}</mat-error>
|
</mat-form-field>
|
||||||
</mat-form-field>
|
|
||||||
</form>
|
<mat-form-field class="form-field">
|
||||||
|
<mat-label>{{ 'netDriverLabel' | translate }}</mat-label>
|
||||||
|
<mat-select formControlName="netDriver">
|
||||||
|
<mat-option *ngFor="let type of netDriverTypes" [value]="type.value">
|
||||||
|
{{ type.name }}
|
||||||
|
</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<mat-form-field class="form-field">
|
||||||
|
<mat-label>{{ 'macLabel' | translate }}</mat-label>
|
||||||
|
<mat-hint>{{ 'macHint' | translate }}</mat-hint>
|
||||||
|
<input matInput formControlName="mac">
|
||||||
|
<mat-error>{{ 'macError' | translate }}</mat-error>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<mat-form-field class="form-field">
|
||||||
|
<mat-label>{{ 'ipLabel' | translate }}</mat-label>
|
||||||
|
<mat-hint>{{ 'ipHint' | translate }}</mat-hint>
|
||||||
|
<input matInput formControlName="ip">
|
||||||
|
<mat-error>{{ 'ipError' | translate }}</mat-error>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<mat-form-field class="form-field">
|
||||||
|
<mat-label>{{ 'templateLabel' | translate }}</mat-label>
|
||||||
|
<mat-select formControlName="template">
|
||||||
|
<mat-option *ngFor="let template of templates" [value]="template['@id']">
|
||||||
|
{{ template.name }}
|
||||||
|
</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<mat-form-field class="form-field">
|
||||||
|
<mat-label>{{ 'hardwareProfileLabel' | translate }}</mat-label>
|
||||||
|
<mat-select formControlName="hardwareProfile">
|
||||||
|
<mat-option *ngFor="let unit of hardwareProfiles" [value]="unit['@id']">
|
||||||
|
{{ unit.description }}
|
||||||
|
</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
<mat-error>{{ 'hardwareProfileError' | translate }}</mat-error>
|
||||||
|
</mat-form-field>
|
||||||
|
</form>
|
||||||
|
</ng-template>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div mat-dialog-actions align="end">
|
<div mat-dialog-actions align="end">
|
||||||
<button mat-button (click)="onNoClick()">{{ 'cancelButton' | translate }}</button>
|
<button mat-button (click)="toggleClientForm()">
|
||||||
<button mat-button [disabled]="!clientForm.valid" (click)="onSubmit()">{{ 'addButton' | translate }}</button>
|
{{ isSingleClientForm ? 'Añadir múltiples clientes' : 'Añadir un único cliente' }}
|
||||||
|
</button>
|
||||||
|
<button mat-button color="warn" (click)="onNoClick()">{{ 'cancelButton' | translate }}</button>
|
||||||
|
<button mat-button color="primary" (click)="onSubmit()">{{ 'addButton' | translate }}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||||
import { ToastrService } from 'ngx-toastr';
|
import { ToastrService } from 'ngx-toastr';
|
||||||
import { DataService } from '../../../services/data.service';
|
import { DataService } from '../../../services/data.service';
|
||||||
|
import * as Papa from 'papaparse';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-create-client',
|
selector: 'app-create-client',
|
||||||
|
@ -18,16 +19,19 @@ export class CreateClientComponent implements OnInit {
|
||||||
hardwareProfiles: any[] = [];
|
hardwareProfiles: any[] = [];
|
||||||
ogLives: any[] = [];
|
ogLives: any[] = [];
|
||||||
templates: any[] = [];
|
templates: any[] = [];
|
||||||
private errorForm: boolean = false;
|
uploadedClients: any[] = [];
|
||||||
|
loading: boolean = false;
|
||||||
|
displayedColumns: string[] = ['name', 'ip'];
|
||||||
|
isSingleClientForm: boolean = false;
|
||||||
|
showTextarea: boolean = true;
|
||||||
protected netifaceTypes = [
|
protected netifaceTypes = [
|
||||||
{ "name": 'Eth0', "value": "eth0" },
|
{ name: 'Eth0', value: 'eth0' },
|
||||||
{ "name": 'Eth1', "value": "eth1" },
|
{ name: 'Eth1', value: 'eth1' },
|
||||||
{ "name": 'Eth2', "value": "eth2" },
|
{ name: 'Eth2', value: 'eth2' }
|
||||||
];
|
];
|
||||||
protected netDriverTypes = [
|
protected netDriverTypes = [
|
||||||
{ "name": 'Generic', "value": "generic" },
|
{ name: 'Generic', value: 'generic' }
|
||||||
];
|
];
|
||||||
loading: boolean = false;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private fb: FormBuilder,
|
private fb: FormBuilder,
|
||||||
|
@ -37,16 +41,22 @@ export class CreateClientComponent implements OnInit {
|
||||||
private toastService: ToastrService,
|
private toastService: ToastrService,
|
||||||
private dataService: DataService,
|
private dataService: DataService,
|
||||||
@Inject(MAT_DIALOG_DATA) public data: any
|
@Inject(MAT_DIALOG_DATA) public data: any
|
||||||
) { }
|
) {}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
console.log(this.data);
|
this.initForm();
|
||||||
this.loadParentUnits();
|
this.loadParentUnits();
|
||||||
this.loadHardwareProfiles();
|
this.loadHardwareProfiles();
|
||||||
this.loadOgLives();
|
this.loadOgLives();
|
||||||
this.loadPxeTemplates()
|
this.loadPxeTemplates();
|
||||||
|
}
|
||||||
|
|
||||||
|
initForm(): void {
|
||||||
this.clientForm = this.fb.group({
|
this.clientForm = this.fb.group({
|
||||||
organizationalUnit: [this.data.organizationalUnit ? this.data.organizationalUnit['@id'] : null, Validators.required],
|
organizationalUnit: [
|
||||||
|
this.data.organizationalUnit ? this.data.organizationalUnit['@id'] : null,
|
||||||
|
Validators.required
|
||||||
|
],
|
||||||
name: ['', Validators.required],
|
name: ['', Validators.required],
|
||||||
serialNumber: [''],
|
serialNumber: [''],
|
||||||
netiface: null,
|
netiface: null,
|
||||||
|
@ -54,25 +64,14 @@ export class CreateClientComponent implements OnInit {
|
||||||
mac: ['', Validators.required],
|
mac: ['', Validators.required],
|
||||||
ip: ['', Validators.required],
|
ip: ['', Validators.required],
|
||||||
template: [null],
|
template: [null],
|
||||||
hardwareProfile: [this.data.organizationalUnit && this.data.organizationalUnit.networkSettings && this.data.organizationalUnit.networkSettings.hardwareProfile ? this.data.organizationalUnit.networkSettings.hardwareProfile['@id'] : null],
|
hardwareProfile: [
|
||||||
|
this.data.organizationalUnit?.networkSettings?.hardwareProfile?.['@id'] || null
|
||||||
|
],
|
||||||
ogLive: [null]
|
ogLive: [null]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
loadHardwareProfiles(): void {
|
loadParentUnits(): void {
|
||||||
this.dataService.getHardwareProfiles().subscribe(
|
|
||||||
(data: any[]) => {
|
|
||||||
this.hardwareProfiles = data;
|
|
||||||
this.loading = false;
|
|
||||||
},
|
|
||||||
(error: any) => {
|
|
||||||
console.error('Error fetching hardware profiles', error);
|
|
||||||
this.loading = false;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
loadParentUnits() {
|
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
const url = `${this.baseUrl}/organizational-units?page=1&itemsPerPage=10000`;
|
const url = `${this.baseUrl}/organizational-units?page=1&itemsPerPage=10000`;
|
||||||
|
|
||||||
|
@ -88,9 +87,22 @@ export class CreateClientComponent implements OnInit {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
loadOgLives() {
|
loadHardwareProfiles(): void {
|
||||||
|
this.dataService.getHardwareProfiles().subscribe(
|
||||||
|
(data: any[]) => {
|
||||||
|
this.hardwareProfiles = data;
|
||||||
|
this.loading = false;
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
console.error('Error fetching hardware profiles:', error);
|
||||||
|
this.loading = false;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
loadOgLives(): void {
|
||||||
const url = `${this.baseUrl}/og-lives?page=1&itemsPerPage=30`;
|
const url = `${this.baseUrl}/og-lives?page=1&itemsPerPage=30`;
|
||||||
|
|
||||||
this.http.get<any>(url).subscribe(
|
this.http.get<any>(url).subscribe(
|
||||||
response => {
|
response => {
|
||||||
this.ogLives = response['hydra:member'];
|
this.ogLives = response['hydra:member'];
|
||||||
|
@ -109,39 +121,123 @@ export class CreateClientComponent implements OnInit {
|
||||||
this.templates = response['hydra:member'];
|
this.templates = response['hydra:member'];
|
||||||
},
|
},
|
||||||
error => {
|
error => {
|
||||||
console.error('Error fetching ogLives:', error);
|
console.error('Error fetching PXE templates:', error);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
onSubmit() {
|
onFileUpload(event: any): void {
|
||||||
if (this.clientForm.valid) {
|
const file = event.target.files[0];
|
||||||
this.errorForm = false;
|
if (file) {
|
||||||
const formData = this.clientForm.value;
|
const reader = new FileReader();
|
||||||
formData.ogLive = formData.ogLive;
|
|
||||||
this.http.post(`${this.baseUrl}/clients`, formData).subscribe(
|
reader.onload = (e: any) => {
|
||||||
response => {
|
const textData = e.target.result;
|
||||||
this.dialogRef.close(response);
|
const regex = /host\s+(\S+)\s+\{\s+hardware\s+ethernet\s+([\da-fA-F:]+);\s+fixed-address\s+([\d.]+);\s+\}/g;
|
||||||
this.openSnackBar(false, 'Cliente creado exitosamente');
|
let match;
|
||||||
},
|
const clients = [];
|
||||||
error => {
|
|
||||||
console.error('Error during POST:', error);
|
while ((match = regex.exec(textData)) !== null) {
|
||||||
this.errorForm = true;
|
clients.push({
|
||||||
this.openSnackBar(true, 'Error al crear el cliente: ' + error.error['hydra:description']);
|
name: match[1],
|
||||||
|
mac: match[2],
|
||||||
|
ip: match[3]
|
||||||
|
});
|
||||||
}
|
}
|
||||||
);
|
|
||||||
|
if (clients.length > 0) {
|
||||||
|
this.uploadedClients = clients;
|
||||||
|
this.toastService.success('Archivo cargado correctamente, los datos están listos para enviarse.', 'Éxito');
|
||||||
|
this.showTextarea = false;
|
||||||
|
} else {
|
||||||
|
this.toastService.error('No se encontraron datos válidos', 'Error');
|
||||||
|
this.showTextarea = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
reader.readAsText(file);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onTextarea(text: string): void {
|
||||||
|
const regex = /host\s+(\S+)\s+\{\s+hardware\s+ethernet\s+([\da-fA-F:]+);\s+fixed-address\s+([\d.]+);\s+\}/g;
|
||||||
|
let match;
|
||||||
|
const clients = [];
|
||||||
|
|
||||||
|
while ((match = regex.exec(text)) !== null) {
|
||||||
|
clients.push({
|
||||||
|
name: match[1],
|
||||||
|
mac: match[2],
|
||||||
|
ip: match[3]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clients.length > 0) {
|
||||||
|
this.uploadedClients = clients;
|
||||||
|
this.toastService.success('Datos cargados correctamente, los datos están listos para enviarse.', 'Éxito');
|
||||||
|
this.showTextarea = false;
|
||||||
|
} else {
|
||||||
|
this.toastService.error('No se encontraron datos válidos', 'Error');
|
||||||
|
this.showTextarea = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onSubmit(): void {
|
||||||
|
if (this.isSingleClientForm) {
|
||||||
|
if (this.clientForm.valid) {
|
||||||
|
const formData = this.clientForm.value;
|
||||||
|
console.log('Form data:', formData);
|
||||||
|
this.http.post(`${this.baseUrl}/clients`, formData).subscribe(
|
||||||
|
response => {
|
||||||
|
this.toastService.success('Cliente creado exitosamente', 'Éxito');
|
||||||
|
this.dialogRef.close(response);
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
console.error('Error durante POST:', error);
|
||||||
|
this.toastService.error('Error al crear el cliente', 'Error');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (this.uploadedClients.length > 0) {
|
||||||
|
this.uploadedClients.forEach(client => {
|
||||||
|
const formData = {
|
||||||
|
organizationalUnit: this.clientForm.value.organizationalUnit || null,
|
||||||
|
name: client.name || null,
|
||||||
|
mac: client.mac || null,
|
||||||
|
ip: client.ip || null,
|
||||||
|
template: this.clientForm.value.template || null,
|
||||||
|
hardwareProfile: this.clientForm.value.hardwareProfile || null,
|
||||||
|
ogLive: this.clientForm.value.ogLive || null,
|
||||||
|
serialNumber: null,
|
||||||
|
netiface: null,
|
||||||
|
netDriver: null
|
||||||
|
};
|
||||||
|
|
||||||
|
this.http.post(`${this.baseUrl}/clients`, formData).subscribe(
|
||||||
|
response => {
|
||||||
|
this.toastService.success(`Cliente ${client.name} creado exitosamente`, 'Éxito');
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
console.error(`Error al crear el cliente ${client.name}:`, error);
|
||||||
|
this.toastService.error(`Error al crear el cliente ${client.name}`, 'Error');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.uploadedClients = [];
|
||||||
|
this.dialogRef.close();
|
||||||
|
} else {
|
||||||
|
this.toastService.error('No hay clientes cargados para añadir', 'Error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleClientForm(): void {
|
||||||
|
this.isSingleClientForm = !this.isSingleClientForm;
|
||||||
|
}
|
||||||
|
|
||||||
onNoClick(): void {
|
onNoClick(): void {
|
||||||
this.dialogRef.close();
|
this.dialogRef.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
openSnackBar(isError: boolean, message: string) {
|
|
||||||
if (isError) {
|
|
||||||
this.toastService.error(' Error al crear el cliente: ' + message, 'Error');
|
|
||||||
} else {
|
|
||||||
this.toastService.success(message, 'Éxito');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,4 +19,14 @@
|
||||||
<mat-icon matListItemIcon>computer</mat-icon>
|
<mat-icon matListItemIcon>computer</mat-icon>
|
||||||
<div matListItemTitle>{{ 'clientTitle' | translate }}</div>
|
<div matListItemTitle>{{ 'clientTitle' | translate }}</div>
|
||||||
</mat-list-item>
|
</mat-list-item>
|
||||||
|
|
||||||
|
<mat-list-item>
|
||||||
|
<mat-icon matListItemIcon style="color: green;">school</mat-icon>
|
||||||
|
<div matListItemTitle>Disponible acceso remoto</div>
|
||||||
|
</mat-list-item>
|
||||||
|
<mat-list-item>
|
||||||
|
<mat-icon matListItemIcon style="color: rgb(209, 5, 5);">school</mat-icon>
|
||||||
|
<div matListItemTitle>No disponible acceso remoto</div>
|
||||||
|
</mat-list-item>
|
||||||
|
|
||||||
</mat-list>
|
</mat-list>
|
||||||
|
|
|
@ -419,5 +419,6 @@
|
||||||
"menus": "Menus",
|
"menus": "Menus",
|
||||||
"TOOLTIP_MENUS": "Menu management (option disabled)",
|
"TOOLTIP_MENUS": "Menu management (option disabled)",
|
||||||
"search": "Search",
|
"search": "Search",
|
||||||
"TOOLTIP_SEARCH": "Search function (option disabled)"
|
"TOOLTIP_SEARCH": "Search function (option disabled)",
|
||||||
|
"detailsOf": "Details of"
|
||||||
}
|
}
|
||||||
|
|
|
@ -420,5 +420,9 @@
|
||||||
"menus": "Menús",
|
"menus": "Menús",
|
||||||
"TOOLTIP_MENUS": "Gestión de menús (opción deshabilitada)",
|
"TOOLTIP_MENUS": "Gestión de menús (opción deshabilitada)",
|
||||||
"search": "Buscar",
|
"search": "Buscar",
|
||||||
"TOOLTIP_SEARCH": "Función de búsqueda (opción deshabilitada)"
|
"TOOLTIP_SEARCH": "Función de búsqueda (opción deshabilitada)",
|
||||||
|
"detailsOf": "Detalles de",
|
||||||
|
"editUnitMenu": "Editar",
|
||||||
|
"addInternalUnitMenu": "Añadir",
|
||||||
|
"addClientMenu": "Añadir cliente"
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue