@flusys/ng-storage 1.1.1-beta → 2.0.0-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. package/README.md +142 -5
  2. package/fesm2022/{flusys-ng-storage-file-manager-form.component-H3OZS0SW.mjs → flusys-ng-storage-file-manager-form.component-Bv8ER8J0.mjs} +110 -341
  3. package/fesm2022/flusys-ng-storage-file-manager-form.component-Bv8ER8J0.mjs.map +1 -0
  4. package/fesm2022/flusys-ng-storage-file-manager-list.component-McM1zSlE.mjs +304 -0
  5. package/fesm2022/flusys-ng-storage-file-manager-list.component-McM1zSlE.mjs.map +1 -0
  6. package/fesm2022/flusys-ng-storage-folder-form.component-fRFiMFev.mjs +155 -0
  7. package/fesm2022/flusys-ng-storage-folder-form.component-fRFiMFev.mjs.map +1 -0
  8. package/fesm2022/flusys-ng-storage-folder-list.component-C4xHI8SO.mjs +194 -0
  9. package/fesm2022/flusys-ng-storage-folder-list.component-C4xHI8SO.mjs.map +1 -0
  10. package/fesm2022/{flusys-ng-storage-storage-config-form.component-CjIAxfHC.mjs → flusys-ng-storage-storage-config-form.component-DEMexjmg.mjs} +217 -300
  11. package/fesm2022/flusys-ng-storage-storage-config-form.component-DEMexjmg.mjs.map +1 -0
  12. package/fesm2022/flusys-ng-storage-storage-config-list.component-iJez-rII.mjs +187 -0
  13. package/fesm2022/flusys-ng-storage-storage-config-list.component-iJez-rII.mjs.map +1 -0
  14. package/fesm2022/flusys-ng-storage.mjs +247 -204
  15. package/fesm2022/flusys-ng-storage.mjs.map +1 -1
  16. package/package.json +4 -4
  17. package/types/flusys-ng-storage.d.ts +101 -151
  18. package/fesm2022/flusys-ng-storage-file-manager-form.component-H3OZS0SW.mjs.map +0 -1
  19. package/fesm2022/flusys-ng-storage-file-manager-list.component-4pb6TafD.mjs +0 -475
  20. package/fesm2022/flusys-ng-storage-file-manager-list.component-4pb6TafD.mjs.map +0 -1
  21. package/fesm2022/flusys-ng-storage-folder-form.component-DQ0QY7P0.mjs +0 -253
  22. package/fesm2022/flusys-ng-storage-folder-form.component-DQ0QY7P0.mjs.map +0 -1
  23. package/fesm2022/flusys-ng-storage-folder-list.component-D-oMaEgl.mjs +0 -267
  24. package/fesm2022/flusys-ng-storage-folder-list.component-D-oMaEgl.mjs.map +0 -1
  25. package/fesm2022/flusys-ng-storage-storage-config-form.component-CjIAxfHC.mjs.map +0 -1
  26. package/fesm2022/flusys-ng-storage-storage-config-list.component-jHk8O9Mx.mjs +0 -328
  27. package/fesm2022/flusys-ng-storage-storage-config-list.component-jHk8O9Mx.mjs.map +0 -1
@@ -1,12 +1,14 @@
1
1
  import * as i0 from '@angular/core';
2
- import { inject, Injectable, Component } from '@angular/core';
2
+ import { inject, signal, computed, Directive, Injectable, ChangeDetectionStrategy, Component } from '@angular/core';
3
+ import { Router, RouterOutlet, RouterLink, RouterLinkActive } from '@angular/router';
4
+ import { APP_CONFIG, DEFAULT_APP_NAME, BaseApiService, getDefaultStorageConfigId } from '@flusys/ng-core';
5
+ import { LAYOUT_AUTH_STATE } from '@flusys/ng-layout';
6
+ import { BaseListPage, ApiResourceService, PROFILE_UPLOAD_PROVIDER, permissionGuard, FILE_PERMISSIONS, FOLDER_PERMISSIONS, STORAGE_CONFIG_PERMISSIONS, PermissionValidatorService } from '@flusys/ng-shared';
7
+ import { MessageService } from 'primeng/api';
3
8
  import { map } from 'rxjs/operators';
4
- import { switchMap } from 'rxjs';
5
- import { BaseApiService, getDefaultStorageConfigId } from '@flusys/ng-core';
9
+ import { firstValueFrom, switchMap } from 'rxjs';
6
10
  import { HttpClient } from '@angular/common/http';
7
- import { ApiResourceService, PROFILE_UPLOAD_PROVIDER } from '@flusys/ng-shared';
8
11
  import { CommonModule } from '@angular/common';
9
- import { RouterOutlet, RouterLink, RouterLinkActive } from '@angular/router';
10
12
 
11
13
  /**
12
14
  * File Location Enum
@@ -32,135 +34,219 @@ var ImageFormat;
32
34
  })(ImageFormat || (ImageFormat = {}));
33
35
 
34
36
  /**
35
- * File Manager API Service
36
- * Handles file metadata CRUD operations
37
- * Endpoint: POST /storage/file-manager/*
38
- *
39
- * Note: Company feature enabled - all queries automatically scoped to user's company
37
+ * Provider configurations for all storage types
38
+ */
39
+ const PROVIDER_CONFIGS = {
40
+ [FileLocationEnum.LOCAL]: {
41
+ label: 'Local',
42
+ icon: 'pi pi-folder',
43
+ severity: 'secondary',
44
+ },
45
+ [FileLocationEnum.AWS]: {
46
+ label: 'AWS S3',
47
+ icon: 'pi pi-cloud',
48
+ severity: 'warn',
49
+ },
50
+ [FileLocationEnum.AZURE]: {
51
+ label: 'Azure Blob',
52
+ icon: 'pi pi-cloud',
53
+ severity: 'info',
54
+ },
55
+ [FileLocationEnum.SFTP]: {
56
+ label: 'SFTP',
57
+ icon: 'pi pi-server',
58
+ severity: 'contrast',
59
+ },
60
+ };
61
+ /**
62
+ * Get provider label for display
63
+ */
64
+ function getProviderLabel(storage) {
65
+ const enumValue = storage;
66
+ return PROVIDER_CONFIGS[enumValue]?.label ?? storage;
67
+ }
68
+ /**
69
+ * Get provider icon class
70
+ */
71
+ function getProviderIcon(storage) {
72
+ const enumValue = storage;
73
+ return PROVIDER_CONFIGS[enumValue]?.icon ?? 'pi pi-database';
74
+ }
75
+ /**
76
+ * Get provider severity for p-tag
77
+ */
78
+ function getProviderSeverity(storage) {
79
+ const enumValue = storage;
80
+ return PROVIDER_CONFIGS[enumValue]?.severity ?? 'secondary';
81
+ }
82
+ /**
83
+ * Get file icon based on content type
84
+ */
85
+ function getFileIcon(contentType) {
86
+ if (contentType.startsWith('image/'))
87
+ return 'pi pi-image';
88
+ if (contentType.startsWith('video/'))
89
+ return 'pi pi-video';
90
+ if (contentType.startsWith('audio/'))
91
+ return 'pi pi-volume-up';
92
+ if (contentType.includes('pdf'))
93
+ return 'pi pi-file-pdf';
94
+ if (contentType.includes('word') || contentType.includes('document'))
95
+ return 'pi pi-file-word';
96
+ if (contentType.includes('excel') || contentType.includes('spreadsheet'))
97
+ return 'pi pi-file-excel';
98
+ return 'pi pi-file';
99
+ }
100
+ /**
101
+ * Format file size from bytes to human readable string
40
102
  */
103
+ function formatFileSize(bytes) {
104
+ if (bytes <= 0)
105
+ return '0 Bytes';
106
+ const k = 1024;
107
+ const sizes = ['Bytes', 'KB', 'MB', 'GB'];
108
+ const i = Math.min(Math.floor(Math.log(bytes) / Math.log(k)), sizes.length - 1);
109
+ return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + ' ' + sizes[i];
110
+ }
111
+ /**
112
+ * Storage provider options for dropdown selects
113
+ */
114
+ const STORAGE_PROVIDERS = [
115
+ { label: 'AWS S3', value: FileLocationEnum.AWS },
116
+ { label: 'Azure Blob Storage', value: FileLocationEnum.AZURE },
117
+ { label: 'SFTP', value: FileLocationEnum.SFTP },
118
+ { label: 'Local Storage', value: FileLocationEnum.LOCAL },
119
+ ];
120
+
121
+ class BaseStoragePage {
122
+ router = inject(Router);
123
+ messageService = inject(MessageService);
124
+ appConfig = inject(APP_CONFIG);
125
+ companyContext = inject(LAYOUT_AUTH_STATE);
126
+ isLoading = signal(false, ...(ngDevMode ? [{ debugName: "isLoading" }] : []));
127
+ showCompanyInfo = computed(() => this.appConfig.enableCompanyFeature, ...(ngDevMode ? [{ debugName: "showCompanyInfo" }] : []));
128
+ currentCompanyName = computed(() => this.companyContext.currentCompanyInfo()?.name ?? DEFAULT_APP_NAME, ...(ngDevMode ? [{ debugName: "currentCompanyName" }] : []));
129
+ showSuccess(detail, summary = 'Success') {
130
+ this.messageService.add({ severity: 'success', summary, detail });
131
+ }
132
+ showError(detail, summary = 'Error') {
133
+ this.messageService.add({ severity: 'error', summary, detail });
134
+ }
135
+ navigateTo(path) {
136
+ this.router.navigate(path);
137
+ }
138
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: BaseStoragePage, deps: [], target: i0.ɵɵFactoryTarget.Directive });
139
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.1.3", type: BaseStoragePage, isStandalone: true, ngImport: i0 });
140
+ }
141
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: BaseStoragePage, decorators: [{
142
+ type: Directive
143
+ }] });
144
+ class BaseStorageListPage extends BaseListPage {
145
+ companyContext = inject(LAYOUT_AUTH_STATE);
146
+ pageSize = signal(50, ...(ngDevMode ? [{ debugName: "pageSize" }] : []));
147
+ totalRecords = this.total;
148
+ currentCompanyName = computed(() => this.companyContext.currentCompanyInfo()?.name ?? DEFAULT_APP_NAME, ...(ngDevMode ? [{ debugName: "currentCompanyName" }] : []));
149
+ getDeleteConfirmMessage(item) {
150
+ const name = item.name ?? 'this item';
151
+ return `Are you sure you want to delete "${name}"?`;
152
+ }
153
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: BaseStorageListPage, deps: null, target: i0.ɵɵFactoryTarget.Directive });
154
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.1.3", type: BaseStorageListPage, isStandalone: true, usesInheritance: true, ngImport: i0 });
155
+ }
156
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: BaseStorageListPage, decorators: [{
157
+ type: Directive
158
+ }] });
159
+
41
160
  class FileManagerApiService extends ApiResourceService {
42
161
  constructor() {
43
- const http = inject(HttpClient);
44
- super('storage/file-manager', http);
162
+ super('storage/file-manager', inject(HttpClient));
45
163
  }
46
- /**
47
- * Get multiple files with valid URLs
48
- * POST /storage/file-manager/get-files
49
- * Regenerates presigned URLs if expired
50
- */
51
164
  getFiles(fileIds) {
52
165
  const requests = fileIds.map((id) => ({ id }));
53
166
  return this.http.post(`${this.baseUrl}/get-files`, requests);
54
167
  }
55
- /**
56
- * Get files by folder
57
- * POST /storage/file-manager/get-all
58
- * Filtered by folder ID and company (if enabled)
59
- */
60
- getFilesByFolder(folderId, page = 1, limit = 50) {
61
- return this.getAll('', {
62
- filter: folderId ? { folderId } : {},
63
- pagination: { currentPage: page - 1, pageSize: limit },
64
- select: [],
65
- sort: {},
66
- });
67
- }
68
- /**
69
- * Get private files (requires permission)
70
- * POST /storage/file-manager/get-all
71
- * Filtered by isPrivate and user permission 'storage.file.viewPrivate'
72
- */
73
- getPrivateFiles(page = 1, limit = 50) {
74
- return this.getAll('', {
75
- filter: { isPrivate: true },
76
- pagination: { currentPage: page - 1, pageSize: limit },
77
- select: [],
78
- sort: {},
79
- });
80
- }
81
- /**
82
- * Create new file manager record
83
- * POST /storage/file-manager/insert
84
- */
85
- create(dto) {
168
+ createFile(dto) {
86
169
  return this.http.post(`${this.baseUrl}/insert`, dto);
87
170
  }
171
+ async createFileAsync(dto) {
172
+ return firstValueFrom(this.createFile(dto));
173
+ }
88
174
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: FileManagerApiService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
89
175
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: FileManagerApiService, providedIn: 'root' });
90
176
  }
91
177
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: FileManagerApiService, decorators: [{
92
178
  type: Injectable,
93
- args: [{
94
- providedIn: 'root',
95
- }]
179
+ args: [{ providedIn: 'root' }]
96
180
  }], ctorParameters: () => [] });
97
181
 
98
182
  /**
99
- * Upload Service
100
- * Handles file upload/delete operations
183
+ * Upload Service - File upload/delete operations
101
184
  * Endpoint: POST /storage/upload/*
102
- *
103
- * Features:
104
- * - Company-scoped uploads (if company feature enabled)
105
- * - Image compression
106
- * - Multiple storage providers
107
- * - File validation (size, type)
108
- * - Default storage config from environment (services.storage.defaultStorageConfigId)
109
185
  */
110
186
  class UploadService extends BaseApiService {
111
187
  fileManagerApi = inject(FileManagerApiService);
112
188
  constructor() {
113
189
  super('storage');
114
190
  }
115
- /**
116
- * Get default storage config ID from app config
117
- * Used when no storageConfigId is provided in upload options
118
- */
191
+ /** Get default storage config ID from app config */
119
192
  getDefaultConfigId() {
120
193
  return getDefaultStorageConfigId(this.appConfig);
121
194
  }
122
- /**
123
- * Upload single file
124
- * POST /storage/upload/single-file?storageConfigId=x&folderPath=y
125
- *
126
- * @param file - File to upload
127
- * @param options - Upload options (storage config, compression, etc.)
128
- */
195
+ /** Upload single file - POST /storage/upload/single-file */
129
196
  uploadSingleFile(file, options) {
130
197
  const formData = new FormData();
131
198
  formData.append('file', file);
132
- const queryParams = this.buildQueryParams(options);
133
- const url = queryParams
134
- ? `${this.getUrl('upload/single-file')}?${queryParams}`
135
- : this.getUrl('upload/single-file');
136
- return this.http.post(url, formData);
199
+ return this.http.post(this.buildUploadUrl('single-file', options), formData);
137
200
  }
138
- /**
139
- * Upload multiple files (max 50)
140
- * POST /storage/upload/multiple-file?storageConfigId=x&folderPath=y
141
- *
142
- * @param files - Array of files to upload
143
- * @param options - Upload options
144
- */
201
+ /** Upload multiple files (max 50) - POST /storage/upload/multiple-file */
145
202
  uploadMultipleFiles(files, options) {
146
203
  const formData = new FormData();
147
204
  files.forEach((file) => formData.append('files', file));
148
- const queryParams = this.buildQueryParams(options);
149
- const url = queryParams
150
- ? `${this.getUrl('upload/multiple-file')}?${queryParams}`
151
- : this.getUrl('upload/multiple-file');
152
- return this.http.post(url, formData);
205
+ return this.http.post(this.buildUploadUrl('multiple-file', options), formData);
206
+ }
207
+ /** Delete single file - POST /storage/upload/delete-single-file */
208
+ deleteSingleFile(key, storageConfigId) {
209
+ return this.http.post(this.getUrl('upload/delete-single-file'), this.buildDeleteBody({ key }, storageConfigId));
210
+ }
211
+ /** Delete multiple files - POST /storage/upload/delete-multiple-file */
212
+ deleteMultipleFiles(keys, storageConfigId) {
213
+ return this.http.post(this.getUrl('upload/delete-multiple-file'), this.buildDeleteBody({ keys }, storageConfigId));
214
+ }
215
+ /** Get file download URL - GET /storage/upload/file/:filePath */
216
+ getFileUrl(key) {
217
+ return this.getUrl(`upload/file/${key}`);
218
+ }
219
+ /** Upload file and register in file manager - returns file manager ID (UUID) */
220
+ uploadAndRegister(file, options) {
221
+ return this.uploadSingleFile(file, options).pipe(switchMap((response) => {
222
+ if (!response.success || !response.data) {
223
+ throw new Error(response.message || 'Upload failed');
224
+ }
225
+ return this.fileManagerApi.createFile({
226
+ name: response.data.name,
227
+ key: response.data.key,
228
+ size: String(response.data.size),
229
+ contentType: response.data.contentType,
230
+ isPrivate: options?.isPrivate ?? false,
231
+ folderId: options?.folderId,
232
+ storageConfigId: this.resolveConfigId(options?.storageConfigId),
233
+ });
234
+ }));
235
+ }
236
+ // ─── Private Helpers ───────────────────────────────────────────────────────
237
+ resolveConfigId(storageConfigId) {
238
+ return storageConfigId ?? this.getDefaultConfigId();
239
+ }
240
+ buildUploadUrl(endpoint, options) {
241
+ const params = this.buildQueryParams(options);
242
+ const baseUrl = this.getUrl(`upload/${endpoint}`);
243
+ return params ? `${baseUrl}?${params}` : baseUrl;
153
244
  }
154
- /**
155
- * Build query string from upload options
156
- * Uses default storageConfigId from app config if not provided in options
157
- */
158
245
  buildQueryParams(options) {
159
246
  const params = new URLSearchParams();
160
- // Use provided storageConfigId or fall back to default from config
161
- const storageConfigId = options?.storageConfigId ?? this.getDefaultConfigId();
162
- if (storageConfigId)
163
- params.append('storageConfigId', storageConfigId);
247
+ const configId = this.resolveConfigId(options?.storageConfigId);
248
+ if (configId)
249
+ params.append('storageConfigId', configId);
164
250
  if (options?.folderPath)
165
251
  params.append('folderPath', options.folderPath);
166
252
  if (options?.maxWidth)
@@ -175,74 +261,16 @@ class UploadService extends BaseApiService {
175
261
  params.append('compress', options.compress.toString());
176
262
  return params.toString();
177
263
  }
178
- /**
179
- * Delete single file
180
- * POST /storage/upload/delete-single-file
181
- *
182
- * @param key - File key/path to delete
183
- * @param storageConfigId - Optional storage config ID (uses default from config if not provided)
184
- */
185
- deleteSingleFile(key, storageConfigId) {
186
- const configId = storageConfigId ?? this.getDefaultConfigId();
187
- return this.http.post(this.getUrl('upload/delete-single-file'), { key, ...(configId && { storageConfigId: configId }) });
188
- }
189
- /**
190
- * Delete multiple files
191
- * POST /storage/upload/delete-multiple-file
192
- *
193
- * @param keys - Array of file keys/paths to delete
194
- * @param storageConfigId - Optional storage config ID (uses default from config if not provided)
195
- */
196
- deleteMultipleFiles(keys, storageConfigId) {
197
- const configId = storageConfigId ?? this.getDefaultConfigId();
198
- return this.http.post(this.getUrl('upload/delete-multiple-file'), { keys, ...(configId && { storageConfigId: configId }) });
199
- }
200
- /**
201
- * Get file download URL
202
- * GET /storage/upload/file/:filePath
203
- *
204
- * @param key - File key/path
205
- * @returns URL for direct file access
206
- */
207
- getFileUrl(key) {
208
- return this.getUrl(`upload/file/${key}`);
209
- }
210
- /**
211
- * Upload file and register in file manager
212
- * Returns file manager ID (UUID) for storing in references like profilePictureId
213
- *
214
- * @param file - File to upload
215
- * @param options - Upload options (storage config, compression, etc.)
216
- * @returns Observable with file manager record (id is the UUID to store)
217
- */
218
- uploadAndRegister(file, options) {
219
- return this.uploadSingleFile(file, options).pipe(switchMap((uploadResponse) => {
220
- if (!uploadResponse.success || !uploadResponse.data) {
221
- throw new Error(uploadResponse.message || 'Upload failed');
222
- }
223
- const uploadedFile = uploadResponse.data;
224
- const storageConfigId = options?.storageConfigId ?? this.getDefaultConfigId();
225
- // Create file manager record
226
- return this.fileManagerApi.create({
227
- name: uploadedFile.name,
228
- key: uploadedFile.key,
229
- size: String(uploadedFile.size),
230
- contentType: uploadedFile.contentType,
231
- isPrivate: options?.isPrivate ?? false,
232
- folderId: options?.folderId,
233
- storageConfigId,
234
- location: FileLocationEnum.LOCAL,
235
- });
236
- }));
264
+ buildDeleteBody(data, storageConfigId) {
265
+ const configId = this.resolveConfigId(storageConfigId);
266
+ return { ...data, ...(configId && { storageConfigId: configId }) };
237
267
  }
238
268
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: UploadService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
239
269
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: UploadService, providedIn: 'root' });
240
270
  }
241
271
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: UploadService, decorators: [{
242
272
  type: Injectable,
243
- args: [{
244
- providedIn: 'root',
245
- }]
273
+ args: [{ providedIn: 'root' }]
246
274
  }], ctorParameters: () => [] });
247
275
 
248
276
  /**
@@ -363,9 +391,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
363
391
 
364
392
  /**
365
393
  * Storage Module Routes
366
- * All storage management routes: configs, files, folders
367
- *
368
- * All routes are protected by permission guards to prevent direct URL access.
394
+ * All routes protected by permission guards
369
395
  */
370
396
  const STORAGE_ROUTES = [
371
397
  {
@@ -375,63 +401,68 @@ const STORAGE_ROUTES = [
375
401
  // File Manager Routes
376
402
  {
377
403
  path: 'files',
404
+ canActivate: [permissionGuard(FILE_PERMISSIONS.READ)],
378
405
  children: [
379
406
  {
380
407
  path: '',
381
- loadComponent: () => import('./flusys-ng-storage-file-manager-list.component-4pb6TafD.mjs').then((m) => m.FileManagerListComponent),
408
+ loadComponent: () => import('./flusys-ng-storage-file-manager-list.component-McM1zSlE.mjs').then((m) => m.FileManagerListComponent),
382
409
  },
383
410
  {
384
411
  path: 'new',
385
- loadComponent: () => import('./flusys-ng-storage-file-manager-form.component-H3OZS0SW.mjs').then((m) => m.FileManagerFormComponent),
412
+ canActivate: [permissionGuard(FILE_PERMISSIONS.CREATE)],
413
+ loadComponent: () => import('./flusys-ng-storage-file-manager-form.component-Bv8ER8J0.mjs').then((m) => m.FileManagerFormComponent),
386
414
  },
387
415
  {
388
416
  path: ':id',
389
- loadComponent: () => import('./flusys-ng-storage-file-manager-form.component-H3OZS0SW.mjs').then((m) => m.FileManagerFormComponent),
417
+ canActivate: [permissionGuard(FILE_PERMISSIONS.UPDATE)],
418
+ loadComponent: () => import('./flusys-ng-storage-file-manager-form.component-Bv8ER8J0.mjs').then((m) => m.FileManagerFormComponent),
390
419
  },
391
420
  ],
392
421
  },
393
422
  // Folder Routes
394
423
  {
395
424
  path: 'folders',
425
+ canActivate: [permissionGuard(FOLDER_PERMISSIONS.READ)],
396
426
  children: [
397
427
  {
398
428
  path: '',
399
- loadComponent: () => import('./flusys-ng-storage-folder-list.component-D-oMaEgl.mjs').then((m) => m.FolderListComponent),
429
+ loadComponent: () => import('./flusys-ng-storage-folder-list.component-C4xHI8SO.mjs').then((m) => m.FolderListComponent),
400
430
  },
401
431
  {
402
432
  path: 'new',
403
- loadComponent: () => import('./flusys-ng-storage-folder-form.component-DQ0QY7P0.mjs').then((m) => m.FolderFormComponent),
433
+ canActivate: [permissionGuard(FOLDER_PERMISSIONS.CREATE)],
434
+ loadComponent: () => import('./flusys-ng-storage-folder-form.component-fRFiMFev.mjs').then((m) => m.FolderFormComponent),
404
435
  },
405
436
  {
406
437
  path: ':id',
407
- loadComponent: () => import('./flusys-ng-storage-folder-form.component-DQ0QY7P0.mjs').then((m) => m.FolderFormComponent),
438
+ canActivate: [permissionGuard(FOLDER_PERMISSIONS.UPDATE)],
439
+ loadComponent: () => import('./flusys-ng-storage-folder-form.component-fRFiMFev.mjs').then((m) => m.FolderFormComponent),
408
440
  },
409
441
  ],
410
442
  },
411
443
  // Storage Config Routes
412
444
  {
413
445
  path: 'configs',
446
+ canActivate: [permissionGuard(STORAGE_CONFIG_PERMISSIONS.READ)],
414
447
  children: [
415
448
  {
416
449
  path: '',
417
- loadComponent: () => import('./flusys-ng-storage-storage-config-list.component-jHk8O9Mx.mjs').then((m) => m.StorageConfigListComponent),
450
+ loadComponent: () => import('./flusys-ng-storage-storage-config-list.component-iJez-rII.mjs').then((m) => m.StorageConfigListComponent),
418
451
  },
419
452
  {
420
453
  path: 'new',
421
- loadComponent: () => import('./flusys-ng-storage-storage-config-form.component-CjIAxfHC.mjs').then((m) => m.StorageConfigFormComponent),
454
+ canActivate: [permissionGuard(STORAGE_CONFIG_PERMISSIONS.CREATE)],
455
+ loadComponent: () => import('./flusys-ng-storage-storage-config-form.component-DEMexjmg.mjs').then((m) => m.StorageConfigFormComponent),
422
456
  },
423
457
  {
424
458
  path: ':id',
425
- loadComponent: () => import('./flusys-ng-storage-storage-config-form.component-CjIAxfHC.mjs').then((m) => m.StorageConfigFormComponent),
459
+ canActivate: [permissionGuard(STORAGE_CONFIG_PERMISSIONS.UPDATE)],
460
+ loadComponent: () => import('./flusys-ng-storage-storage-config-form.component-DEMexjmg.mjs').then((m) => m.StorageConfigFormComponent),
426
461
  },
427
462
  ],
428
463
  },
429
- // Default redirect to files
430
- {
431
- path: '',
432
- redirectTo: 'files',
433
- pathMatch: 'full',
434
- },
464
+ // Default redirect
465
+ { path: '', redirectTo: 'files', pathMatch: 'full' },
435
466
  ],
436
467
  },
437
468
  ];
@@ -441,81 +472,93 @@ const STORAGE_ROUTES = [
441
472
  *
442
473
  * Main container for Storage module with tab navigation
443
474
  * Routes: Files | Folders | Configurations
475
+ * Tabs are filtered based on user permissions
444
476
  */
445
477
  class StorageContainerComponent {
446
- /** All available tabs */
447
- tabs = [
478
+ permissionValidator = inject(PermissionValidatorService);
479
+ /** All available tabs with required permissions */
480
+ allTabs = [
448
481
  {
449
482
  id: 'files',
450
483
  label: 'Files',
451
484
  icon: 'pi pi-file',
452
485
  route: 'files',
486
+ permission: FILE_PERMISSIONS.READ,
453
487
  },
454
488
  {
455
489
  id: 'folders',
456
490
  label: 'Folders',
457
491
  icon: 'pi pi-folder',
458
492
  route: 'folders',
493
+ permission: FOLDER_PERMISSIONS.READ,
459
494
  },
460
495
  {
461
496
  id: 'configs',
462
497
  label: 'Configurations',
463
498
  icon: 'pi pi-cog',
464
499
  route: 'configs',
500
+ permission: STORAGE_CONFIG_PERMISSIONS.READ,
465
501
  },
466
502
  ];
503
+ /** Visible tabs based on user permissions */
504
+ visibleTabs = computed(() => this.allTabs.filter((tab) => this.permissionValidator.hasPermission(tab.permission)), ...(ngDevMode ? [{ debugName: "visibleTabs" }] : []));
505
+ /** Dynamic subtitle based on visible tabs */
506
+ subtitle = computed(() => {
507
+ const tabs = this.visibleTabs();
508
+ if (tabs.length === 0)
509
+ return 'No permissions available';
510
+ return `Manage ${tabs.map((t) => t.label.toLowerCase()).join(', ')}`;
511
+ }, ...(ngDevMode ? [{ debugName: "subtitle" }] : []));
467
512
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: StorageContainerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
468
513
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: StorageContainerComponent, isStandalone: true, selector: "lib-storage-container", ngImport: i0, template: `
469
- <div class="storage-page p-4">
470
- <div class="storage-header mb-4">
471
- <h1 class="text-2xl font-bold m-0">Storage</h1>
472
- <p class="text-muted-color mt-1">Manage files, folders, and storage configurations</p>
514
+ <div class="p-2 sm:p-4">
515
+ <div class="mb-4">
516
+ <h1 class="text-xl sm:text-2xl font-bold m-0">Storage</h1>
517
+ <p class="text-muted-color mt-1 text-sm sm:text-base">{{ subtitle() }}</p>
473
518
  </div>
474
519
 
475
- <div class="storage-tabs flex gap-1 mb-4 border-b border-surface-200">
476
- @for (tab of tabs; track tab.id) {
520
+ <div class="scrollbar-hide flex gap-1 mb-4 border-b border-surface overflow-x-auto flex-nowrap">
521
+ @for (tab of visibleTabs(); track tab.id) {
477
522
  <a
478
523
  [routerLink]="tab.route"
479
- routerLinkActive="active"
480
- class="tab-link flex items-center gap-2 px-4 py-2 rounded-t-lg cursor-pointer transition-colors">
524
+ routerLinkActive="tab-active"
525
+ class="flex items-center gap-2 px-4 py-2 rounded-t-lg cursor-pointer transition-colors text-muted-color no-underline border-b-2 border-transparent -mb-px hover:bg-surface-hover [&.tab-active]:text-primary [&.tab-active]:border-primary [&.tab-active]:bg-surface-ground whitespace-nowrap shrink-0"
526
+ >
481
527
  <i [class]="tab.icon"></i>
482
528
  <span>{{ tab.label }}</span>
483
529
  </a>
484
530
  }
485
531
  </div>
486
532
 
487
- <div class="storage-content">
488
- <router-outlet />
489
- </div>
533
+ <router-outlet />
490
534
  </div>
491
- `, isInline: true, styles: [".tab-link{color:var(--text-color-secondary, #6b7280);text-decoration:none;border-bottom:2px solid transparent;margin-bottom:-1px}.tab-link.active{color:var(--primary-color, #3b82f6);border-bottom-color:var(--primary-color, #3b82f6);background-color:var(--surface-ground, #f9fafb)}.tab-link:hover:not(.active){background-color:var(--surface-hover, #f3f4f6)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: RouterOutlet, selector: "router-outlet", inputs: ["name", "routerOutletData"], outputs: ["activate", "deactivate", "attach", "detach"], exportAs: ["outlet"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: RouterLinkActive, selector: "[routerLinkActive]", inputs: ["routerLinkActiveOptions", "ariaCurrentWhenActive", "routerLinkActive"], outputs: ["isActiveChange"], exportAs: ["routerLinkActive"] }] });
535
+ `, isInline: true, styles: [".scrollbar-hide{-ms-overflow-style:none;scrollbar-width:none}.scrollbar-hide::-webkit-scrollbar{display:none}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: RouterOutlet, selector: "router-outlet", inputs: ["name", "routerOutletData"], outputs: ["activate", "deactivate", "attach", "detach"], exportAs: ["outlet"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: RouterLinkActive, selector: "[routerLinkActive]", inputs: ["routerLinkActiveOptions", "ariaCurrentWhenActive", "routerLinkActive"], outputs: ["isActiveChange"], exportAs: ["routerLinkActive"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
492
536
  }
493
537
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: StorageContainerComponent, decorators: [{
494
538
  type: Component,
495
- args: [{ selector: 'lib-storage-container', standalone: true, imports: [CommonModule, RouterOutlet, RouterLink, RouterLinkActive], template: `
496
- <div class="storage-page p-4">
497
- <div class="storage-header mb-4">
498
- <h1 class="text-2xl font-bold m-0">Storage</h1>
499
- <p class="text-muted-color mt-1">Manage files, folders, and storage configurations</p>
539
+ args: [{ selector: 'lib-storage-container', changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, RouterOutlet, RouterLink, RouterLinkActive], template: `
540
+ <div class="p-2 sm:p-4">
541
+ <div class="mb-4">
542
+ <h1 class="text-xl sm:text-2xl font-bold m-0">Storage</h1>
543
+ <p class="text-muted-color mt-1 text-sm sm:text-base">{{ subtitle() }}</p>
500
544
  </div>
501
545
 
502
- <div class="storage-tabs flex gap-1 mb-4 border-b border-surface-200">
503
- @for (tab of tabs; track tab.id) {
546
+ <div class="scrollbar-hide flex gap-1 mb-4 border-b border-surface overflow-x-auto flex-nowrap">
547
+ @for (tab of visibleTabs(); track tab.id) {
504
548
  <a
505
549
  [routerLink]="tab.route"
506
- routerLinkActive="active"
507
- class="tab-link flex items-center gap-2 px-4 py-2 rounded-t-lg cursor-pointer transition-colors">
550
+ routerLinkActive="tab-active"
551
+ class="flex items-center gap-2 px-4 py-2 rounded-t-lg cursor-pointer transition-colors text-muted-color no-underline border-b-2 border-transparent -mb-px hover:bg-surface-hover [&.tab-active]:text-primary [&.tab-active]:border-primary [&.tab-active]:bg-surface-ground whitespace-nowrap shrink-0"
552
+ >
508
553
  <i [class]="tab.icon"></i>
509
554
  <span>{{ tab.label }}</span>
510
555
  </a>
511
556
  }
512
557
  </div>
513
558
 
514
- <div class="storage-content">
515
- <router-outlet />
516
- </div>
559
+ <router-outlet />
517
560
  </div>
518
- `, styles: [".tab-link{color:var(--text-color-secondary, #6b7280);text-decoration:none;border-bottom:2px solid transparent;margin-bottom:-1px}.tab-link.active{color:var(--primary-color, #3b82f6);border-bottom-color:var(--primary-color, #3b82f6);background-color:var(--surface-ground, #f9fafb)}.tab-link:hover:not(.active){background-color:var(--surface-hover, #f3f4f6)}\n"] }]
561
+ `, styles: [".scrollbar-hide{-ms-overflow-style:none;scrollbar-width:none}.scrollbar-hide::-webkit-scrollbar{display:none}\n"] }]
519
562
  }] });
520
563
 
521
564
  var storageContainer_component = /*#__PURE__*/Object.freeze({
@@ -532,5 +575,5 @@ var storageContainer_component = /*#__PURE__*/Object.freeze({
532
575
  * Generated bundle index. Do not edit.
533
576
  */
534
577
 
535
- export { FileLocationEnum, FileManagerApiService, FolderApiService, ImageFormat, ProfileUploadProviderAdapter, STORAGE_ROUTES, StorageConfigApiService, StorageContainerComponent, UploadService, provideStorageProviders };
578
+ export { BaseStorageListPage, BaseStoragePage, FileLocationEnum, FileManagerApiService, FolderApiService, ImageFormat, ProfileUploadProviderAdapter, STORAGE_PROVIDERS, STORAGE_ROUTES, StorageConfigApiService, StorageContainerComponent, UploadService, formatFileSize, getFileIcon, getProviderIcon, getProviderLabel, getProviderSeverity, provideStorageProviders };
536
579
  //# sourceMappingURL=flusys-ng-storage.mjs.map