@flusys/ng-storage 1.1.0-beta → 1.1.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 +144 -5
  2. package/fesm2022/{flusys-ng-storage-file-manager-form.component-CxAYC6nj.mjs → flusys-ng-storage-file-manager-form.component-CXVB9G-f.mjs} +120 -351
  3. package/fesm2022/flusys-ng-storage-file-manager-form.component-CXVB9G-f.mjs.map +1 -0
  4. package/fesm2022/flusys-ng-storage-file-manager-list.component-0O_b4EjN.mjs +304 -0
  5. package/fesm2022/flusys-ng-storage-file-manager-list.component-0O_b4EjN.mjs.map +1 -0
  6. package/fesm2022/flusys-ng-storage-folder-form.component-BTQGEqNu.mjs +155 -0
  7. package/fesm2022/flusys-ng-storage-folder-form.component-BTQGEqNu.mjs.map +1 -0
  8. package/fesm2022/flusys-ng-storage-folder-list.component-B8GoIh6Z.mjs +194 -0
  9. package/fesm2022/flusys-ng-storage-folder-list.component-B8GoIh6Z.mjs.map +1 -0
  10. package/fesm2022/{flusys-ng-storage-storage-config-form.component-SEYIBife.mjs → flusys-ng-storage-storage-config-form.component-CorNWrmF.mjs} +219 -302
  11. package/fesm2022/flusys-ng-storage-storage-config-form.component-CorNWrmF.mjs.map +1 -0
  12. package/fesm2022/flusys-ng-storage-storage-config-list.component-YXzz3Tvo.mjs +187 -0
  13. package/fesm2022/flusys-ng-storage-storage-config-list.component-YXzz3Tvo.mjs.map +1 -0
  14. package/fesm2022/flusys-ng-storage.mjs +267 -226
  15. package/fesm2022/flusys-ng-storage.mjs.map +1 -1
  16. package/package.json +9 -9
  17. package/types/flusys-ng-storage.d.ts +101 -151
  18. package/fesm2022/flusys-ng-storage-file-manager-form.component-CxAYC6nj.mjs.map +0 -1
  19. package/fesm2022/flusys-ng-storage-file-manager-list.component-BevfDZO0.mjs +0 -475
  20. package/fesm2022/flusys-ng-storage-file-manager-list.component-BevfDZO0.mjs.map +0 -1
  21. package/fesm2022/flusys-ng-storage-folder-form.component-BhurG3dl.mjs +0 -253
  22. package/fesm2022/flusys-ng-storage-folder-form.component-BhurG3dl.mjs.map +0 -1
  23. package/fesm2022/flusys-ng-storage-folder-list.component-Clrjcw0B.mjs +0 -267
  24. package/fesm2022/flusys-ng-storage-folder-list.component-Clrjcw0B.mjs.map +0 -1
  25. package/fesm2022/flusys-ng-storage-storage-config-form.component-SEYIBife.mjs.map +0 -1
  26. package/fesm2022/flusys-ng-storage-storage-config-list.component-CofVBysj.mjs +0 -328
  27. package/fesm2022/flusys-ng-storage-storage-config-list.component-CofVBysj.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.5", ngImport: i0, type: BaseStoragePage, deps: [], target: i0.ɵɵFactoryTarget.Directive });
139
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.1.5", type: BaseStoragePage, isStandalone: true, ngImport: i0 });
140
+ }
141
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", 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.5", ngImport: i0, type: BaseStorageListPage, deps: null, target: i0.ɵɵFactoryTarget.Directive });
154
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.1.5", type: BaseStorageListPage, isStandalone: true, usesInheritance: true, ngImport: i0 });
155
+ }
156
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", 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('file-manager', inject(HttpClient), 'storage');
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
  }
88
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: FileManagerApiService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
89
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: FileManagerApiService, providedIn: 'root' });
171
+ async createFileAsync(dto) {
172
+ return firstValueFrom(this.createFile(dto));
173
+ }
174
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: FileManagerApiService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
175
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: FileManagerApiService, providedIn: 'root' });
90
176
  }
91
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: FileManagerApiService, decorators: [{
177
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", 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 }) });
264
+ buildDeleteBody(data, storageConfigId) {
265
+ const configId = this.resolveConfigId(storageConfigId);
266
+ return { ...data, ...(configId && { storageConfigId: configId }) };
188
267
  }
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
- }));
237
- }
238
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: UploadService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
239
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: UploadService, providedIn: 'root' });
268
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: UploadService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
269
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: UploadService, providedIn: 'root' });
240
270
  }
241
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: UploadService, decorators: [{
271
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", 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
  /**
@@ -285,10 +313,10 @@ class ProfileUploadProviderAdapter {
285
313
  : undefined,
286
314
  })));
287
315
  }
288
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: ProfileUploadProviderAdapter, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
289
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: ProfileUploadProviderAdapter });
316
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: ProfileUploadProviderAdapter, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
317
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: ProfileUploadProviderAdapter });
290
318
  }
291
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: ProfileUploadProviderAdapter, decorators: [{
319
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: ProfileUploadProviderAdapter, decorators: [{
292
320
  type: Injectable
293
321
  }] });
294
322
 
@@ -326,13 +354,12 @@ function provideStorageProviders() {
326
354
  */
327
355
  class FolderApiService extends ApiResourceService {
328
356
  constructor() {
329
- const http = inject(HttpClient);
330
- super('storage/folder', http);
357
+ super('folder', inject(HttpClient), 'storage');
331
358
  }
332
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: FolderApiService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
333
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: FolderApiService, providedIn: 'root' });
359
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: FolderApiService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
360
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: FolderApiService, providedIn: 'root' });
334
361
  }
335
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: FolderApiService, decorators: [{
362
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: FolderApiService, decorators: [{
336
363
  type: Injectable,
337
364
  args: [{
338
365
  providedIn: 'root',
@@ -348,13 +375,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
348
375
  */
349
376
  class StorageConfigApiService extends ApiResourceService {
350
377
  constructor() {
351
- const http = inject(HttpClient);
352
- super('storage/storage-config', http);
378
+ super('storage-config', inject(HttpClient), 'storage');
353
379
  }
354
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: StorageConfigApiService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
355
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: StorageConfigApiService, providedIn: 'root' });
380
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: StorageConfigApiService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
381
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: StorageConfigApiService, providedIn: 'root' });
356
382
  }
357
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: StorageConfigApiService, decorators: [{
383
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: StorageConfigApiService, decorators: [{
358
384
  type: Injectable,
359
385
  args: [{
360
386
  providedIn: 'root',
@@ -363,9 +389,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
363
389
 
364
390
  /**
365
391
  * 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.
392
+ * All routes protected by permission guards
369
393
  */
370
394
  const STORAGE_ROUTES = [
371
395
  {
@@ -375,63 +399,68 @@ const STORAGE_ROUTES = [
375
399
  // File Manager Routes
376
400
  {
377
401
  path: 'files',
402
+ canActivate: [permissionGuard(FILE_PERMISSIONS.READ)],
378
403
  children: [
379
404
  {
380
405
  path: '',
381
- loadComponent: () => import('./flusys-ng-storage-file-manager-list.component-BevfDZO0.mjs').then((m) => m.FileManagerListComponent),
406
+ loadComponent: () => import('./flusys-ng-storage-file-manager-list.component-0O_b4EjN.mjs').then((m) => m.FileManagerListComponent),
382
407
  },
383
408
  {
384
409
  path: 'new',
385
- loadComponent: () => import('./flusys-ng-storage-file-manager-form.component-CxAYC6nj.mjs').then((m) => m.FileManagerFormComponent),
410
+ canActivate: [permissionGuard(FILE_PERMISSIONS.CREATE)],
411
+ loadComponent: () => import('./flusys-ng-storage-file-manager-form.component-CXVB9G-f.mjs').then((m) => m.FileManagerFormComponent),
386
412
  },
387
413
  {
388
414
  path: ':id',
389
- loadComponent: () => import('./flusys-ng-storage-file-manager-form.component-CxAYC6nj.mjs').then((m) => m.FileManagerFormComponent),
415
+ canActivate: [permissionGuard(FILE_PERMISSIONS.UPDATE)],
416
+ loadComponent: () => import('./flusys-ng-storage-file-manager-form.component-CXVB9G-f.mjs').then((m) => m.FileManagerFormComponent),
390
417
  },
391
418
  ],
392
419
  },
393
420
  // Folder Routes
394
421
  {
395
422
  path: 'folders',
423
+ canActivate: [permissionGuard(FOLDER_PERMISSIONS.READ)],
396
424
  children: [
397
425
  {
398
426
  path: '',
399
- loadComponent: () => import('./flusys-ng-storage-folder-list.component-Clrjcw0B.mjs').then((m) => m.FolderListComponent),
427
+ loadComponent: () => import('./flusys-ng-storage-folder-list.component-B8GoIh6Z.mjs').then((m) => m.FolderListComponent),
400
428
  },
401
429
  {
402
430
  path: 'new',
403
- loadComponent: () => import('./flusys-ng-storage-folder-form.component-BhurG3dl.mjs').then((m) => m.FolderFormComponent),
431
+ canActivate: [permissionGuard(FOLDER_PERMISSIONS.CREATE)],
432
+ loadComponent: () => import('./flusys-ng-storage-folder-form.component-BTQGEqNu.mjs').then((m) => m.FolderFormComponent),
404
433
  },
405
434
  {
406
435
  path: ':id',
407
- loadComponent: () => import('./flusys-ng-storage-folder-form.component-BhurG3dl.mjs').then((m) => m.FolderFormComponent),
436
+ canActivate: [permissionGuard(FOLDER_PERMISSIONS.UPDATE)],
437
+ loadComponent: () => import('./flusys-ng-storage-folder-form.component-BTQGEqNu.mjs').then((m) => m.FolderFormComponent),
408
438
  },
409
439
  ],
410
440
  },
411
441
  // Storage Config Routes
412
442
  {
413
443
  path: 'configs',
444
+ canActivate: [permissionGuard(STORAGE_CONFIG_PERMISSIONS.READ)],
414
445
  children: [
415
446
  {
416
447
  path: '',
417
- loadComponent: () => import('./flusys-ng-storage-storage-config-list.component-CofVBysj.mjs').then((m) => m.StorageConfigListComponent),
448
+ loadComponent: () => import('./flusys-ng-storage-storage-config-list.component-YXzz3Tvo.mjs').then((m) => m.StorageConfigListComponent),
418
449
  },
419
450
  {
420
451
  path: 'new',
421
- loadComponent: () => import('./flusys-ng-storage-storage-config-form.component-SEYIBife.mjs').then((m) => m.StorageConfigFormComponent),
452
+ canActivate: [permissionGuard(STORAGE_CONFIG_PERMISSIONS.CREATE)],
453
+ loadComponent: () => import('./flusys-ng-storage-storage-config-form.component-CorNWrmF.mjs').then((m) => m.StorageConfigFormComponent),
422
454
  },
423
455
  {
424
456
  path: ':id',
425
- loadComponent: () => import('./flusys-ng-storage-storage-config-form.component-SEYIBife.mjs').then((m) => m.StorageConfigFormComponent),
457
+ canActivate: [permissionGuard(STORAGE_CONFIG_PERMISSIONS.UPDATE)],
458
+ loadComponent: () => import('./flusys-ng-storage-storage-config-form.component-CorNWrmF.mjs').then((m) => m.StorageConfigFormComponent),
426
459
  },
427
460
  ],
428
461
  },
429
- // Default redirect to files
430
- {
431
- path: '',
432
- redirectTo: 'files',
433
- pathMatch: 'full',
434
- },
462
+ // Default redirect
463
+ { path: '', redirectTo: 'files', pathMatch: 'full' },
435
464
  ],
436
465
  },
437
466
  ];
@@ -441,81 +470,93 @@ const STORAGE_ROUTES = [
441
470
  *
442
471
  * Main container for Storage module with tab navigation
443
472
  * Routes: Files | Folders | Configurations
473
+ * Tabs are filtered based on user permissions
444
474
  */
445
475
  class StorageContainerComponent {
446
- /** All available tabs */
447
- tabs = [
476
+ permissionValidator = inject(PermissionValidatorService);
477
+ /** All available tabs with required permissions */
478
+ allTabs = [
448
479
  {
449
480
  id: 'files',
450
481
  label: 'Files',
451
482
  icon: 'pi pi-file',
452
483
  route: 'files',
484
+ permission: FILE_PERMISSIONS.READ,
453
485
  },
454
486
  {
455
487
  id: 'folders',
456
488
  label: 'Folders',
457
489
  icon: 'pi pi-folder',
458
490
  route: 'folders',
491
+ permission: FOLDER_PERMISSIONS.READ,
459
492
  },
460
493
  {
461
494
  id: 'configs',
462
495
  label: 'Configurations',
463
496
  icon: 'pi pi-cog',
464
497
  route: 'configs',
498
+ permission: STORAGE_CONFIG_PERMISSIONS.READ,
465
499
  },
466
500
  ];
467
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: StorageContainerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
468
- 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-gray-500 mt-1">Manage files, folders, and storage configurations</p>
501
+ /** Visible tabs based on user permissions */
502
+ visibleTabs = computed(() => this.allTabs.filter((tab) => this.permissionValidator.hasPermission(tab.permission)), ...(ngDevMode ? [{ debugName: "visibleTabs" }] : []));
503
+ /** Dynamic subtitle based on visible tabs */
504
+ subtitle = computed(() => {
505
+ const tabs = this.visibleTabs();
506
+ if (tabs.length === 0)
507
+ return 'No permissions available';
508
+ return `Manage ${tabs.map((t) => t.label.toLowerCase()).join(', ')}`;
509
+ }, ...(ngDevMode ? [{ debugName: "subtitle" }] : []));
510
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: StorageContainerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
511
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.5", type: StorageContainerComponent, isStandalone: true, selector: "lib-storage-container", ngImport: i0, template: `
512
+ <div class="p-2 sm:p-4">
513
+ <div class="mb-4">
514
+ <h1 class="text-xl sm:text-2xl font-bold m-0">Storage</h1>
515
+ <p class="text-muted-color mt-1 text-sm sm:text-base">{{ subtitle() }}</p>
473
516
  </div>
474
517
 
475
- <div class="storage-tabs flex gap-1 mb-4 border-b border-gray-200">
476
- @for (tab of tabs; track tab.id) {
518
+ <div class="scrollbar-hide flex gap-1 mb-4 border-b border-surface overflow-x-auto flex-nowrap">
519
+ @for (tab of visibleTabs(); track tab.id) {
477
520
  <a
478
521
  [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">
522
+ routerLinkActive="tab-active"
523
+ 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"
524
+ >
481
525
  <i [class]="tab.icon"></i>
482
526
  <span>{{ tab.label }}</span>
483
527
  </a>
484
528
  }
485
529
  </div>
486
530
 
487
- <div class="storage-content">
488
- <router-outlet />
489
- </div>
531
+ <router-outlet />
490
532
  </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"] }] });
533
+ `, 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
534
  }
493
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: StorageContainerComponent, decorators: [{
535
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: StorageContainerComponent, decorators: [{
494
536
  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-gray-500 mt-1">Manage files, folders, and storage configurations</p>
537
+ args: [{ selector: 'lib-storage-container', changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, RouterOutlet, RouterLink, RouterLinkActive], template: `
538
+ <div class="p-2 sm:p-4">
539
+ <div class="mb-4">
540
+ <h1 class="text-xl sm:text-2xl font-bold m-0">Storage</h1>
541
+ <p class="text-muted-color mt-1 text-sm sm:text-base">{{ subtitle() }}</p>
500
542
  </div>
501
543
 
502
- <div class="storage-tabs flex gap-1 mb-4 border-b border-gray-200">
503
- @for (tab of tabs; track tab.id) {
544
+ <div class="scrollbar-hide flex gap-1 mb-4 border-b border-surface overflow-x-auto flex-nowrap">
545
+ @for (tab of visibleTabs(); track tab.id) {
504
546
  <a
505
547
  [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">
548
+ routerLinkActive="tab-active"
549
+ 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"
550
+ >
508
551
  <i [class]="tab.icon"></i>
509
552
  <span>{{ tab.label }}</span>
510
553
  </a>
511
554
  }
512
555
  </div>
513
556
 
514
- <div class="storage-content">
515
- <router-outlet />
516
- </div>
557
+ <router-outlet />
517
558
  </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"] }]
559
+ `, styles: [".scrollbar-hide{-ms-overflow-style:none;scrollbar-width:none}.scrollbar-hide::-webkit-scrollbar{display:none}\n"] }]
519
560
  }] });
520
561
 
521
562
  var storageContainer_component = /*#__PURE__*/Object.freeze({
@@ -532,5 +573,5 @@ var storageContainer_component = /*#__PURE__*/Object.freeze({
532
573
  * Generated bundle index. Do not edit.
533
574
  */
534
575
 
535
- export { FileLocationEnum, FileManagerApiService, FolderApiService, ImageFormat, ProfileUploadProviderAdapter, STORAGE_ROUTES, StorageConfigApiService, StorageContainerComponent, UploadService, provideStorageProviders };
576
+ export { BaseStorageListPage, BaseStoragePage, FileLocationEnum, FileManagerApiService, FolderApiService, ImageFormat, ProfileUploadProviderAdapter, STORAGE_PROVIDERS, STORAGE_ROUTES, StorageConfigApiService, StorageContainerComponent, UploadService, formatFileSize, getFileIcon, getProviderIcon, getProviderLabel, getProviderSeverity, provideStorageProviders };
536
577
  //# sourceMappingURL=flusys-ng-storage.mjs.map