diff --git a/ogWebconsole/src/app/app.module.ts b/ogWebconsole/src/app/app.module.ts index ab4e07b..7f5df2f 100644 --- a/ogWebconsole/src/app/app.module.ts +++ b/ogWebconsole/src/app/app.module.ts @@ -114,6 +114,7 @@ import { TranslateHttpLoader } from '@ngx-translate/http-loader'; import { EnvVarsComponent } from './components/admin/env-vars/env-vars.component'; import { MatSortModule } from '@angular/material/sort'; import { MenusComponent } from './components/menus/menus.component'; +import { AssetManagerModalComponent } from './components/menus/asset-manager-modal/asset-manager-modal.component'; import { CreateMenuComponent } from './components/menus/create-menu/create-menu.component'; import { CreateMultipleClientComponent } from './components/groups/shared/clients/create-multiple-client/create-multiple-client.component'; import { ExportImageComponent } from './components/images/export-image/export-image.component'; @@ -171,7 +172,6 @@ import { HardwareProfileComponent } from './components/hardware-profile/hardware import { HardwareCollectionModalComponent } from './components/hardware-profile/hardware-collection-modal/hardware-collection-modal.component'; import { HardwareTypeComponent } from './components/hardware-type/hardware-type.component'; import { ShowDetailsComponent } from './components/hardware-type/show-details/show-details.component'; - export function HttpLoaderFactory(http: HttpClient) { return new TranslateHttpLoader(http, './locale/', '.json'); } @@ -298,7 +298,8 @@ registerLocaleData(localeEs, 'es-ES'); HardwareProfileComponent, HardwareCollectionModalComponent, HardwareTypeComponent, - ShowDetailsComponent + ShowDetailsComponent, + AssetManagerModalComponent ], bootstrap: [AppComponent], imports: [BrowserModule, diff --git a/ogWebconsole/src/app/components/menus/asset-manager-modal/asset-manager-modal.component.css b/ogWebconsole/src/app/components/menus/asset-manager-modal/asset-manager-modal.component.css new file mode 100644 index 0000000..3193de9 --- /dev/null +++ b/ogWebconsole/src/app/components/menus/asset-manager-modal/asset-manager-modal.component.css @@ -0,0 +1,51 @@ +.asset-actions-bar { + display: flex; + align-items: center; + margin-bottom: 1rem; + gap: 8px; +} + +.spacer { + flex: 1 1 auto; +} + +.assets-list-container { + height: 400px; + overflow: auto; + position: relative; + border: 1px solid #ccc; + border-radius: 4px; +} + +.loading-overlay { + position: absolute; + top: 0; left: 0; right: 0; bottom: 0; + background: rgba(255,255,255,0.7); + display: flex; + justify-content: center; + align-items: center; + z-index: 10; + font-weight: bold; +} + +table { + width: 100%; +} + +.folder-link { + font-weight: bold; + color: #1976d2; + text-decoration: none; + cursor: pointer; +} + +.folder-link:hover { + text-decoration: underline; +} + +.empty-state { + text-align: center; + padding: 2rem; + color: #666; + font-style: italic; +} diff --git a/ogWebconsole/src/app/components/menus/asset-manager-modal/asset-manager-modal.component.html b/ogWebconsole/src/app/components/menus/asset-manager-modal/asset-manager-modal.component.html new file mode 100644 index 0000000..0ae8197 --- /dev/null +++ b/ogWebconsole/src/app/components/menus/asset-manager-modal/asset-manager-modal.component.html @@ -0,0 +1,94 @@ +

+ Gestionar Assets: public/assets/{{ currentPath }} +

+ +
+ + + + + + + + +
+ + + @if (isUploading) { + + } + + +
+ @if (isLoading) { +
Cargando...
+ } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tipo + + {{ element.type === 'folder' ? 'folder' : 'insert_drive_file' }} + + Nombre + @if (element.type === 'folder') { + + {{ element.name }} + + } @else { + {{ element.name }} + } + Tamaño + {{ element.type === 'file' ? formatBytes(element.size) : '--' }} + Acciones + @if (element.type === 'file') { + + } + +
No hay archivos en este directorio
+
+ +
+ + + diff --git a/ogWebconsole/src/app/components/menus/asset-manager-modal/asset-manager-modal.component.spec.ts b/ogWebconsole/src/app/components/menus/asset-manager-modal/asset-manager-modal.component.spec.ts new file mode 100644 index 0000000..997137e --- /dev/null +++ b/ogWebconsole/src/app/components/menus/asset-manager-modal/asset-manager-modal.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { AssetManagerModalComponent } from './asset-manager-modal.component'; + +describe('AssetManagerModalComponent', () => { + let component: AssetManagerModalComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [AssetManagerModalComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(AssetManagerModalComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/ogWebconsole/src/app/components/menus/asset-manager-modal/asset-manager-modal.component.ts b/ogWebconsole/src/app/components/menus/asset-manager-modal/asset-manager-modal.component.ts new file mode 100644 index 0000000..b95eeeb --- /dev/null +++ b/ogWebconsole/src/app/components/menus/asset-manager-modal/asset-manager-modal.component.ts @@ -0,0 +1,143 @@ +import { Component, OnInit } from '@angular/core'; +import { MatDialogRef, MatDialog } from '@angular/material/dialog'; +import { ToastrService } from 'ngx-toastr'; +import { DataService } from '../data.service'; +import { DeleteModalComponent } from '../../../shared/delete_modal/delete-modal/delete-modal.component'; + +export interface AssetNode { + name: string; + type: 'folder' | 'file'; + size: number | null; + path: string; +} + +@Component({ + selector: 'app-asset-manager-modal', + templateUrl: './asset-manager-modal.component.html', + styleUrls: ['./asset-manager-modal.component.css'] +}) +export class AssetManagerModalComponent implements OnInit { + currentPath: string = ''; + assets: AssetNode[] = []; + isLoading: boolean = false; + isUploading: boolean = false; + displayedColumns: string[] = ['icon', 'name', 'size', 'actions']; + + constructor( + public dialogRef: MatDialogRef, + private dialog: MatDialog, + private dataService: DataService, + private toastService: ToastrService + ) { } + + ngOnInit(): void { + this.loadAssets(); + } + + loadAssets(): void { + this.isLoading = true; + this.dataService.getAssets(this.currentPath).subscribe({ + next: (data) => { + this.assets = data; + this.isLoading = false; + }, + error: (err) => { + this.isLoading = false; + this.toastService.error('Error al cargar la ruta actual.'); + console.error(err); + + // If we failed traversing down, fallback to root + if (this.currentPath !== '') { + this.currentPath = ''; + this.loadAssets(); + } + } + }); + } + + enterFolder(folderName: string): void { + if (this.currentPath === '') { + this.currentPath = folderName; + } else { + this.currentPath = this.currentPath + '/' + folderName; + } + this.loadAssets(); + } + + goUp(): void { + if (this.currentPath === '') return; + + const parts = this.currentPath.split('/'); + parts.pop(); + this.currentPath = parts.join('/'); + this.loadAssets(); + } + + onFileSelected(event: any): void { + const file = event.target.files[0]; + if (file) { + const isZip = file.name.toLowerCase().endsWith('.zip'); + this.isUploading = true; + + this.dataService.uploadAssets(file, this.currentPath, isZip).subscribe({ + next: (response) => { + this.isUploading = false; + this.toastService.success(response.message || 'Fichero subido correctamente.'); + this.loadAssets(); // Refresh directory + }, + error: (error) => { + this.isUploading = false; + this.toastService.error(error?.error?.message || 'Error al subir el fichero.'); + console.error(error); + } + }); + } + + // Reset input so it triggers (change) next time even if it's the same file name + event.target.value = ''; + } + + deleteAsset(path: string): void { + this.dialog.open(DeleteModalComponent, { + width: '300px', + data: { name: path } + }).afterClosed().subscribe((result) => { + if (result) { + this.isLoading = true; + this.dataService.deleteAsset(path).subscribe({ + next: (res) => { + this.toastService.success('Elemento eliminado correctamente.'); + this.loadAssets(); + }, + error: (err) => { + this.isLoading = false; + this.toastService.error(err?.error?.message || 'No se pudo eliminar el elemento.'); + } + }); + } + }); + } + + copyPath(path: string): void { + // Generate an absolute-like path starting with /assets/ + const fullUrl = `/assets/${path}`; + navigator.clipboard.writeText(fullUrl).then(() => { + this.toastService.info(`Enlace copiado: ${fullUrl}`); + }).catch(err => { + console.error('Error al copiar al portapapeles', err); + }); + } + + formatBytes(bytes: number | null): string { + if (bytes === null || bytes === 0) return '0 Bytes'; + const k = 1024; + const dm = 2; + const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]; + } + + close(): void { + this.dialogRef.close(); + } +} diff --git a/ogWebconsole/src/app/components/menus/data.service.ts b/ogWebconsole/src/app/components/menus/data.service.ts index 354afb5..6086d5c 100644 --- a/ogWebconsole/src/app/components/menus/data.service.ts +++ b/ogWebconsole/src/app/components/menus/data.service.ts @@ -32,7 +32,7 @@ export class DataService { }), catchError(error => { console.error('Error fetching commands', error); - return throwError(error); + return throwError(() => error); }) ); } @@ -48,10 +48,44 @@ export class DataService { }), catchError(error => { console.error('Error fetching menus', error); - return throwError(error); + return throwError(() => error); }) ); } + getAssets(path: string = ''): Observable { + const params = new HttpParams().set('path', path); + return this.http.get(`${this.baseUrl}/ogassets/list`, { params }).pipe( + catchError(error => { + console.error('Error fetching assets', error); + return throwError(() => error); + }) + ); + } + + uploadAssets(file: File, path: string = '', extractZip: boolean = false): Observable { + const formData = new FormData(); + formData.append('file', file); + formData.append('path', path); + formData.append('extractZip', String(extractZip)); + + return this.http.post(`${this.baseUrl}/ogassets/upload`, formData).pipe( + catchError(error => { + console.error('Error uploading assets', error); + return throwError(() => error); + }) + ); + } + + deleteAsset(path: string): Observable { + return this.http.delete(`${this.baseUrl}/ogassets/delete`, { + body: { path } + }).pipe( + catchError(error => { + console.error('Error deleting asset', error); + return throwError(() => error); + }) + ); + } } diff --git a/ogWebconsole/src/app/components/menus/menus.component.html b/ogWebconsole/src/app/components/menus/menus.component.html index 12ecb01..7864952 100644 --- a/ogWebconsole/src/app/components/menus/menus.component.html +++ b/ogWebconsole/src/app/components/menus/menus.component.html @@ -7,6 +7,7 @@ Administrar menús
+
diff --git a/ogWebconsole/src/app/components/menus/menus.component.ts b/ogWebconsole/src/app/components/menus/menus.component.ts index ebb542a..a223f48 100644 --- a/ogWebconsole/src/app/components/menus/menus.component.ts +++ b/ogWebconsole/src/app/components/menus/menus.component.ts @@ -8,6 +8,7 @@ import { JoyrideService } from "ngx-joyride"; import { Router } from "@angular/router"; import { DeleteModalComponent } from "../../shared/delete_modal/delete-modal/delete-modal.component"; import { CreateMenuComponent } from "./create-menu/create-menu.component"; +import { AssetManagerModalComponent } from "./asset-manager-modal/asset-manager-modal.component"; import { ConfigService } from '@services/config.service'; @Component({ @@ -85,6 +86,17 @@ export class MenusComponent implements OnInit { }); } + openAssetManager(): void { + const dialogRef = this.dialog.open(AssetManagerModalComponent, { + width: '800px', + height: '600px' + }); + + dialogRef.afterClosed().subscribe(() => { + // Nothing specific to do upon close for now + }); + } + search(): void { this.loading = true; this.http.get(`${this.apiUrl}?page=${this.page + 1}&itemsPerPage=${this.itemsPerPage}`, { params: this.filters }).subscribe(