@flusys/ng-storage 1.0.0-beta → 1.0.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 (28) hide show
  1. package/README.md +209 -5
  2. package/fesm2022/flusys-ng-storage-file-manager-form.component-Bv8ER8J0.mjs +484 -0
  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-Bc95TVD0.mjs → flusys-ng-storage-storage-config-form.component-DEMexjmg.mjs} +242 -292
  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 +363 -199
  15. package/fesm2022/flusys-ng-storage.mjs.map +1 -1
  16. package/package.json +9 -9
  17. package/types/flusys-ng-storage.d.ts +150 -127
  18. package/fesm2022/flusys-ng-storage-file-manager-form.component-DVZsFYrj.mjs +0 -715
  19. package/fesm2022/flusys-ng-storage-file-manager-form.component-DVZsFYrj.mjs.map +0 -1
  20. package/fesm2022/flusys-ng-storage-file-manager-list.component-CwnyNoYb.mjs +0 -475
  21. package/fesm2022/flusys-ng-storage-file-manager-list.component-CwnyNoYb.mjs.map +0 -1
  22. package/fesm2022/flusys-ng-storage-folder-form.component-DH-KCz21.mjs +0 -253
  23. package/fesm2022/flusys-ng-storage-folder-form.component-DH-KCz21.mjs.map +0 -1
  24. package/fesm2022/flusys-ng-storage-folder-list.component-iIys4NvL.mjs +0 -267
  25. package/fesm2022/flusys-ng-storage-folder-list.component-iIys4NvL.mjs.map +0 -1
  26. package/fesm2022/flusys-ng-storage-storage-config-form.component-Bc95TVD0.mjs.map +0 -1
  27. package/fesm2022/flusys-ng-storage-storage-config-list.component-Bfomqyr3.mjs +0 -328
  28. package/fesm2022/flusys-ng-storage-storage-config-list.component-Bfomqyr3.mjs.map +0 -1
@@ -1,10 +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';
8
+ import { map } from 'rxjs/operators';
9
+ import { firstValueFrom, switchMap } from 'rxjs';
3
10
  import { HttpClient } from '@angular/common/http';
4
- import { ApiResourceService } from '@flusys/ng-shared';
5
- import { BaseApiService } from '@flusys/ng-core';
6
11
  import { CommonModule } from '@angular/common';
7
- import { RouterOutlet, RouterLink, RouterLinkActive } from '@angular/router';
8
12
 
9
13
  /**
10
14
  * File Location Enum
@@ -30,62 +34,317 @@ var ImageFormat;
30
34
  })(ImageFormat || (ImageFormat = {}));
31
35
 
32
36
  /**
33
- * File Manager API Service
34
- * Handles file metadata CRUD operations
35
- * Endpoint: POST /storage/file-manager/*
36
- *
37
- * 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
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
38
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
+
39
160
  class FileManagerApiService extends ApiResourceService {
40
161
  constructor() {
41
- const http = inject(HttpClient);
42
- super('storage/file-manager', http);
162
+ super('storage/file-manager', inject(HttpClient));
43
163
  }
44
- /**
45
- * Get multiple files with valid URLs
46
- * POST /storage/file-manager/get-files
47
- * Regenerates presigned URLs if expired
48
- */
49
164
  getFiles(fileIds) {
50
165
  const requests = fileIds.map((id) => ({ id }));
51
166
  return this.http.post(`${this.baseUrl}/get-files`, requests);
52
167
  }
53
- /**
54
- * Get files by folder
55
- * POST /storage/file-manager/get-all
56
- * Filtered by folder ID and company (if enabled)
57
- */
58
- getFilesByFolder(folderId, page = 1, limit = 50) {
59
- return this.getAll('', {
60
- filter: folderId ? { folderId } : {},
61
- pagination: { currentPage: page - 1, pageSize: limit },
62
- select: [],
63
- sort: {},
64
- });
168
+ createFile(dto) {
169
+ return this.http.post(`${this.baseUrl}/insert`, dto);
65
170
  }
66
- /**
67
- * Get private files (requires permission)
68
- * POST /storage/file-manager/get-all
69
- * Filtered by isPrivate and user permission 'storage.file.viewPrivate'
70
- */
71
- getPrivateFiles(page = 1, limit = 50) {
72
- return this.getAll('', {
73
- filter: { isPrivate: true },
74
- pagination: { currentPage: page - 1, pageSize: limit },
75
- select: [],
76
- sort: {},
77
- });
171
+ async createFileAsync(dto) {
172
+ return firstValueFrom(this.createFile(dto));
78
173
  }
79
174
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: FileManagerApiService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
80
175
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: FileManagerApiService, providedIn: 'root' });
81
176
  }
82
177
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: FileManagerApiService, decorators: [{
83
178
  type: Injectable,
84
- args: [{
85
- providedIn: 'root',
86
- }]
179
+ args: [{ providedIn: 'root' }]
180
+ }], ctorParameters: () => [] });
181
+
182
+ /**
183
+ * Upload Service - File upload/delete operations
184
+ * Endpoint: POST /storage/upload/*
185
+ */
186
+ class UploadService extends BaseApiService {
187
+ fileManagerApi = inject(FileManagerApiService);
188
+ constructor() {
189
+ super('storage');
190
+ }
191
+ /** Get default storage config ID from app config */
192
+ getDefaultConfigId() {
193
+ return getDefaultStorageConfigId(this.appConfig);
194
+ }
195
+ /** Upload single file - POST /storage/upload/single-file */
196
+ uploadSingleFile(file, options) {
197
+ const formData = new FormData();
198
+ formData.append('file', file);
199
+ return this.http.post(this.buildUploadUrl('single-file', options), formData);
200
+ }
201
+ /** Upload multiple files (max 50) - POST /storage/upload/multiple-file */
202
+ uploadMultipleFiles(files, options) {
203
+ const formData = new FormData();
204
+ files.forEach((file) => formData.append('files', file));
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;
244
+ }
245
+ buildQueryParams(options) {
246
+ const params = new URLSearchParams();
247
+ const configId = this.resolveConfigId(options?.storageConfigId);
248
+ if (configId)
249
+ params.append('storageConfigId', configId);
250
+ if (options?.folderPath)
251
+ params.append('folderPath', options.folderPath);
252
+ if (options?.maxWidth)
253
+ params.append('maxWidth', options.maxWidth.toString());
254
+ if (options?.maxHeight)
255
+ params.append('maxHeight', options.maxHeight.toString());
256
+ if (options?.quality)
257
+ params.append('quality', options.quality.toString());
258
+ if (options?.format)
259
+ params.append('format', options.format);
260
+ if (options?.compress !== undefined)
261
+ params.append('compress', options.compress.toString());
262
+ return params.toString();
263
+ }
264
+ buildDeleteBody(data, storageConfigId) {
265
+ const configId = this.resolveConfigId(storageConfigId);
266
+ return { ...data, ...(configId && { storageConfigId: configId }) };
267
+ }
268
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: UploadService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
269
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: UploadService, providedIn: 'root' });
270
+ }
271
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: UploadService, decorators: [{
272
+ type: Injectable,
273
+ args: [{ providedIn: 'root' }]
87
274
  }], ctorParameters: () => [] });
88
275
 
276
+ /**
277
+ * Profile Upload Provider Adapter
278
+ *
279
+ * Implements IProfileUploadProvider interface for ng-auth profile page.
280
+ * Uses UploadService.uploadAndRegister() to upload files and register in file manager.
281
+ *
282
+ * @example
283
+ * // In app.config.ts
284
+ * providers: [
285
+ * ...provideStorageProviders(),
286
+ * ]
287
+ */
288
+ class ProfileUploadProviderAdapter {
289
+ uploadService = inject(UploadService);
290
+ /**
291
+ * Upload profile picture and register in file manager
292
+ * Returns file manager ID (UUID) for storing in user.profilePictureId
293
+ */
294
+ uploadProfilePicture(file, options) {
295
+ return this.uploadService
296
+ .uploadAndRegister(file, {
297
+ folderPath: options?.folderPath ?? 'profile-pictures',
298
+ compress: options?.compress ?? true,
299
+ maxWidth: options?.maxWidth ?? 400,
300
+ maxHeight: options?.maxHeight ?? 400,
301
+ })
302
+ .pipe(map((response) => ({
303
+ success: response.success,
304
+ message: response.message,
305
+ data: response.data
306
+ ? {
307
+ id: response.data.id,
308
+ name: response.data.name,
309
+ key: response.data.key,
310
+ size: parseFloat(response.data.size) * 1024, // Convert KB to bytes
311
+ contentType: response.data.contentType,
312
+ }
313
+ : undefined,
314
+ })));
315
+ }
316
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: ProfileUploadProviderAdapter, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
317
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: ProfileUploadProviderAdapter });
318
+ }
319
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: ProfileUploadProviderAdapter, decorators: [{
320
+ type: Injectable
321
+ }] });
322
+
323
+ /**
324
+ * Provide Storage Providers
325
+ *
326
+ * Registers storage-related provider implementations.
327
+ * Call this in app.config.ts to enable storage features in other packages.
328
+ *
329
+ * @example
330
+ * // In app.config.ts
331
+ * export const appConfig: ApplicationConfig = {
332
+ * providers: [
333
+ * ...provideStorageProviders(),
334
+ * ],
335
+ * };
336
+ *
337
+ * @returns Array of providers for storage functionality
338
+ */
339
+ function provideStorageProviders() {
340
+ return [
341
+ {
342
+ provide: PROFILE_UPLOAD_PROVIDER,
343
+ useClass: ProfileUploadProviderAdapter,
344
+ },
345
+ ];
346
+ }
347
+
89
348
  /**
90
349
  * Folder API Service
91
350
  * Handles folder CRUD operations
@@ -130,121 +389,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
130
389
  }]
131
390
  }], ctorParameters: () => [] });
132
391
 
133
- /**
134
- * Upload Service
135
- * Handles file upload/delete operations
136
- * Endpoint: POST /storage/upload/*
137
- *
138
- * Features:
139
- * - Company-scoped uploads (if company feature enabled)
140
- * - Image compression
141
- * - Multiple storage providers
142
- * - File validation (size, type)
143
- */
144
- class UploadService extends BaseApiService {
145
- constructor() {
146
- super('storage');
147
- }
148
- /**
149
- * Upload single file
150
- * POST /storage/upload/single-file?storageConfigId=x&folderPath=y
151
- *
152
- * @param file - File to upload
153
- * @param options - Upload options (storage config, compression, etc.)
154
- */
155
- uploadSingleFile(file, options) {
156
- const formData = new FormData();
157
- formData.append('file', file);
158
- const queryParams = this.buildQueryParams(options);
159
- const url = queryParams
160
- ? `${this.getUrl('upload/single-file')}?${queryParams}`
161
- : this.getUrl('upload/single-file');
162
- return this.http.post(url, formData);
163
- }
164
- /**
165
- * Upload multiple files (max 50)
166
- * POST /storage/upload/multiple-file?storageConfigId=x&folderPath=y
167
- *
168
- * @param files - Array of files to upload
169
- * @param options - Upload options
170
- */
171
- uploadMultipleFiles(files, options) {
172
- const formData = new FormData();
173
- files.forEach((file) => formData.append('files', file));
174
- const queryParams = this.buildQueryParams(options);
175
- const url = queryParams
176
- ? `${this.getUrl('upload/multiple-file')}?${queryParams}`
177
- : this.getUrl('upload/multiple-file');
178
- return this.http.post(url, formData);
179
- }
180
- /**
181
- * Build query string from upload options
182
- */
183
- buildQueryParams(options) {
184
- if (!options)
185
- return '';
186
- const params = new URLSearchParams();
187
- if (options.storageConfigId)
188
- params.append('storageConfigId', options.storageConfigId);
189
- if (options.folderPath)
190
- params.append('folderPath', options.folderPath);
191
- if (options.maxWidth)
192
- params.append('maxWidth', options.maxWidth.toString());
193
- if (options.maxHeight)
194
- params.append('maxHeight', options.maxHeight.toString());
195
- if (options.quality)
196
- params.append('quality', options.quality.toString());
197
- if (options.format)
198
- params.append('format', options.format);
199
- if (options.compress !== undefined)
200
- params.append('compress', options.compress.toString());
201
- return params.toString();
202
- }
203
- /**
204
- * Delete single file
205
- * POST /storage/upload/delete-single-file
206
- *
207
- * @param key - File key/path to delete
208
- * @param storageConfigId - Optional storage config ID (uses default if not provided)
209
- */
210
- deleteSingleFile(key, storageConfigId) {
211
- return this.http.post(this.getUrl('upload/delete-single-file'), { key, ...(storageConfigId && { storageConfigId }) });
212
- }
213
- /**
214
- * Delete multiple files
215
- * POST /storage/upload/delete-multiple-file
216
- *
217
- * @param keys - Array of file keys/paths to delete
218
- * @param storageConfigId - Optional storage config ID (uses default if not provided)
219
- */
220
- deleteMultipleFiles(keys, storageConfigId) {
221
- return this.http.post(this.getUrl('upload/delete-multiple-file'), { keys, ...(storageConfigId && { storageConfigId }) });
222
- }
223
- /**
224
- * Get file download URL
225
- * GET /storage/upload/file/:filePath
226
- *
227
- * @param key - File key/path
228
- * @returns URL for direct file access
229
- */
230
- getFileUrl(key) {
231
- return this.getUrl(`upload/file/${key}`);
232
- }
233
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: UploadService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
234
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: UploadService, providedIn: 'root' });
235
- }
236
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: UploadService, decorators: [{
237
- type: Injectable,
238
- args: [{
239
- providedIn: 'root',
240
- }]
241
- }], ctorParameters: () => [] });
242
-
243
392
  /**
244
393
  * Storage Module Routes
245
- * All storage management routes: configs, files, folders
246
- *
247
- * All routes are protected by permission guards to prevent direct URL access.
394
+ * All routes protected by permission guards
248
395
  */
249
396
  const STORAGE_ROUTES = [
250
397
  {
@@ -254,63 +401,68 @@ const STORAGE_ROUTES = [
254
401
  // File Manager Routes
255
402
  {
256
403
  path: 'files',
404
+ canActivate: [permissionGuard(FILE_PERMISSIONS.READ)],
257
405
  children: [
258
406
  {
259
407
  path: '',
260
- loadComponent: () => import('./flusys-ng-storage-file-manager-list.component-CwnyNoYb.mjs').then((m) => m.FileManagerListComponent),
408
+ loadComponent: () => import('./flusys-ng-storage-file-manager-list.component-McM1zSlE.mjs').then((m) => m.FileManagerListComponent),
261
409
  },
262
410
  {
263
411
  path: 'new',
264
- loadComponent: () => import('./flusys-ng-storage-file-manager-form.component-DVZsFYrj.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),
265
414
  },
266
415
  {
267
416
  path: ':id',
268
- loadComponent: () => import('./flusys-ng-storage-file-manager-form.component-DVZsFYrj.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),
269
419
  },
270
420
  ],
271
421
  },
272
422
  // Folder Routes
273
423
  {
274
424
  path: 'folders',
425
+ canActivate: [permissionGuard(FOLDER_PERMISSIONS.READ)],
275
426
  children: [
276
427
  {
277
428
  path: '',
278
- loadComponent: () => import('./flusys-ng-storage-folder-list.component-iIys4NvL.mjs').then((m) => m.FolderListComponent),
429
+ loadComponent: () => import('./flusys-ng-storage-folder-list.component-C4xHI8SO.mjs').then((m) => m.FolderListComponent),
279
430
  },
280
431
  {
281
432
  path: 'new',
282
- loadComponent: () => import('./flusys-ng-storage-folder-form.component-DH-KCz21.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),
283
435
  },
284
436
  {
285
437
  path: ':id',
286
- loadComponent: () => import('./flusys-ng-storage-folder-form.component-DH-KCz21.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),
287
440
  },
288
441
  ],
289
442
  },
290
443
  // Storage Config Routes
291
444
  {
292
445
  path: 'configs',
446
+ canActivate: [permissionGuard(STORAGE_CONFIG_PERMISSIONS.READ)],
293
447
  children: [
294
448
  {
295
449
  path: '',
296
- loadComponent: () => import('./flusys-ng-storage-storage-config-list.component-Bfomqyr3.mjs').then((m) => m.StorageConfigListComponent),
450
+ loadComponent: () => import('./flusys-ng-storage-storage-config-list.component-iJez-rII.mjs').then((m) => m.StorageConfigListComponent),
297
451
  },
298
452
  {
299
453
  path: 'new',
300
- loadComponent: () => import('./flusys-ng-storage-storage-config-form.component-Bc95TVD0.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),
301
456
  },
302
457
  {
303
458
  path: ':id',
304
- loadComponent: () => import('./flusys-ng-storage-storage-config-form.component-Bc95TVD0.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),
305
461
  },
306
462
  ],
307
463
  },
308
- // Default redirect to files
309
- {
310
- path: '',
311
- redirectTo: 'files',
312
- pathMatch: 'full',
313
- },
464
+ // Default redirect
465
+ { path: '', redirectTo: 'files', pathMatch: 'full' },
314
466
  ],
315
467
  },
316
468
  ];
@@ -320,81 +472,93 @@ const STORAGE_ROUTES = [
320
472
  *
321
473
  * Main container for Storage module with tab navigation
322
474
  * Routes: Files | Folders | Configurations
475
+ * Tabs are filtered based on user permissions
323
476
  */
324
477
  class StorageContainerComponent {
325
- /** All available tabs */
326
- tabs = [
478
+ permissionValidator = inject(PermissionValidatorService);
479
+ /** All available tabs with required permissions */
480
+ allTabs = [
327
481
  {
328
482
  id: 'files',
329
483
  label: 'Files',
330
484
  icon: 'pi pi-file',
331
485
  route: 'files',
486
+ permission: FILE_PERMISSIONS.READ,
332
487
  },
333
488
  {
334
489
  id: 'folders',
335
490
  label: 'Folders',
336
491
  icon: 'pi pi-folder',
337
492
  route: 'folders',
493
+ permission: FOLDER_PERMISSIONS.READ,
338
494
  },
339
495
  {
340
496
  id: 'configs',
341
497
  label: 'Configurations',
342
498
  icon: 'pi pi-cog',
343
499
  route: 'configs',
500
+ permission: STORAGE_CONFIG_PERMISSIONS.READ,
344
501
  },
345
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" }] : []));
346
512
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: StorageContainerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
347
513
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: StorageContainerComponent, isStandalone: true, selector: "lib-storage-container", ngImport: i0, template: `
348
- <div class="storage-page p-4">
349
- <div class="storage-header mb-4">
350
- <h1 class="text-2xl font-bold m-0">Storage</h1>
351
- <p class="text-gray-500 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>
352
518
  </div>
353
519
 
354
- <div class="storage-tabs flex gap-1 mb-4 border-b border-gray-200">
355
- @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) {
356
522
  <a
357
523
  [routerLink]="tab.route"
358
- routerLinkActive="active"
359
- 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
+ >
360
527
  <i [class]="tab.icon"></i>
361
528
  <span>{{ tab.label }}</span>
362
529
  </a>
363
530
  }
364
531
  </div>
365
532
 
366
- <div class="storage-content">
367
- <router-outlet />
368
- </div>
533
+ <router-outlet />
369
534
  </div>
370
- `, 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 });
371
536
  }
372
537
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: StorageContainerComponent, decorators: [{
373
538
  type: Component,
374
- args: [{ selector: 'lib-storage-container', standalone: true, imports: [CommonModule, RouterOutlet, RouterLink, RouterLinkActive], template: `
375
- <div class="storage-page p-4">
376
- <div class="storage-header mb-4">
377
- <h1 class="text-2xl font-bold m-0">Storage</h1>
378
- <p class="text-gray-500 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>
379
544
  </div>
380
545
 
381
- <div class="storage-tabs flex gap-1 mb-4 border-b border-gray-200">
382
- @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) {
383
548
  <a
384
549
  [routerLink]="tab.route"
385
- routerLinkActive="active"
386
- 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
+ >
387
553
  <i [class]="tab.icon"></i>
388
554
  <span>{{ tab.label }}</span>
389
555
  </a>
390
556
  }
391
557
  </div>
392
558
 
393
- <div class="storage-content">
394
- <router-outlet />
395
- </div>
559
+ <router-outlet />
396
560
  </div>
397
- `, 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"] }]
398
562
  }] });
399
563
 
400
564
  var storageContainer_component = /*#__PURE__*/Object.freeze({
@@ -411,5 +575,5 @@ var storageContainer_component = /*#__PURE__*/Object.freeze({
411
575
  * Generated bundle index. Do not edit.
412
576
  */
413
577
 
414
- export { FileLocationEnum, FileManagerApiService, FolderApiService, ImageFormat, STORAGE_ROUTES, StorageConfigApiService, StorageContainerComponent, UploadService };
578
+ export { BaseStorageListPage, BaseStoragePage, FileLocationEnum, FileManagerApiService, FolderApiService, ImageFormat, ProfileUploadProviderAdapter, STORAGE_PROVIDERS, STORAGE_ROUTES, StorageConfigApiService, StorageContainerComponent, UploadService, formatFileSize, getFileIcon, getProviderIcon, getProviderLabel, getProviderSeverity, provideStorageProviders };
415
579
  //# sourceMappingURL=flusys-ng-storage.mjs.map