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(