diff --git a/api/src/Sozsoft.Platform.Application/FileManagement/FileManagementAppService.cs b/api/src/Sozsoft.Platform.Application/FileManagement/FileManagementAppService.cs index 156aaa5..fd78ae6 100644 --- a/api/src/Sozsoft.Platform.Application/FileManagement/FileManagementAppService.cs +++ b/api/src/Sozsoft.Platform.Application/FileManagement/FileManagementAppService.cs @@ -25,17 +25,16 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe private readonly BlobManager _blobContainer; private readonly IConfiguration _configuration; - private const string FileMetadataSuffix = ".metadata.json"; private const string FolderMarkerSuffix = ".folder"; private const string IndexFileName = "index.json"; // Protected system folders that cannot be deleted, renamed, or moved private static readonly HashSet ProtectedFolders = new(StringComparer.OrdinalIgnoreCase) { - BlobContainerNames.Avatar, - BlobContainerNames.Import, - BlobContainerNames.Note, - BlobContainerNames.Intranet + // BlobContainerNames.Avatar, + // BlobContainerNames.Import, + // BlobContainerNames.Note, + // BlobContainerNames.Intranet }; public FileManagementAppService( @@ -119,16 +118,12 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe var rootFolder = pathParts[0]; var isProtected = ProtectedFolders.Contains(rootFolder); - Logger.LogInformation($"IsProtectedFolder - Path: '{path}', RootFolder: '{rootFolder}', IsProtected: {isProtected}"); - Logger.LogInformation($"Protected folders: {string.Join(", ", ProtectedFolders)}"); - return isProtected; } private void ValidateNotProtectedFolder(string id, string operation) { var decodedPath = DecodeIdAsPath(id); - Logger.LogInformation($"ValidateNotProtectedFolder - ID: {id}, DecodedPath: {decodedPath}, Operation: {operation}"); if (IsProtectedFolder(decodedPath)) { @@ -136,8 +131,6 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe Logger.LogWarning($"Blocked {operation} operation on protected folder: {folderName}"); throw new UserFriendlyException($"Cannot {operation} system folder '{folderName}'. This folder is protected."); } - - Logger.LogInformation($"Folder {decodedPath} is not protected, allowing {operation}"); } private async Task> GetFolderIndexAsync(string? parentId, string tenantId) @@ -1124,7 +1117,6 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe // Decode the folderId to get the actual path var decodedPath = DecodeIdAsPath(folderId); - Logger.LogInformation($"GetFolderPath - FolderId: {folderId}, DecodedPath: {decodedPath}"); // Split path into parts and build breadcrumb var pathParts = decodedPath.Split('/', StringSplitOptions.RemoveEmptyEntries); @@ -1136,8 +1128,6 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe var pathUpToCurrent = string.Join("/", pathParts.Take(i + 1)); currentEncodedPath = EncodePathAsId(pathUpToCurrent); - Logger.LogInformation($"PathItem {i}: Name='{pathParts[i]}', Id='{currentEncodedPath}', PathUpToCurrent='{pathUpToCurrent}'"); - pathItems.Add(new PathItemDto { Id = currentEncodedPath, @@ -1145,7 +1135,6 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe }); } - Logger.LogInformation($"Returning {pathItems.Count} breadcrumb items"); return new FolderPathDto { Path = pathItems }; } diff --git a/api/src/Sozsoft.Platform.DbMigrator/Seeds/MenusData.json b/api/src/Sozsoft.Platform.DbMigrator/Seeds/MenusData.json index 7df0e64..ccb29dd 100644 --- a/api/src/Sozsoft.Platform.DbMigrator/Seeds/MenusData.json +++ b/api/src/Sozsoft.Platform.DbMigrator/Seeds/MenusData.json @@ -822,16 +822,6 @@ "RequiredPermissionName": "App.Menus.Manager", "IsDisabled": false }, - { - "ParentCode": "App.Saas", - "Code": "App.Files", - "DisplayName": "App.Files", - "Order": 14, - "Url": "/admin/files", - "Icon": "FcFolder", - "RequiredPermissionName": "App.Files", - "IsDisabled": false - }, { "ParentCode": "App.Saas", "Code": "App.DeveloperKit", @@ -1084,11 +1074,21 @@ "RequiredPermissionName": "App.Reports.ReportTemplates", "IsDisabled": false }, + { + "ParentCode": "App.Administration", + "Code": "App.Files", + "DisplayName": "App.Files", + "Order": 5, + "Url": "/admin/files", + "Icon": "FcFolder", + "RequiredPermissionName": "App.Files", + "IsDisabled": false + }, { "ParentCode": "App.Administration", "Code": "App.Forum", "DisplayName": "App.Forum", - "Order": 5, + "Order": 6, "Url": "/admin/forum", "Icon": "FcLink", "RequiredPermissionName": "App.ForumManagement.Publish", diff --git a/api/src/Sozsoft.Platform.DbMigrator/Seeds/PermissionsData.json b/api/src/Sozsoft.Platform.DbMigrator/Seeds/PermissionsData.json index 78f5503..2b4494b 100644 --- a/api/src/Sozsoft.Platform.DbMigrator/Seeds/PermissionsData.json +++ b/api/src/Sozsoft.Platform.DbMigrator/Seeds/PermissionsData.json @@ -2233,51 +2233,6 @@ "MultiTenancySide": 2, "MenuGroup": "Erp|Kurs" }, - { - "GroupName": "App.Saas", - "Name": "App.Files", - "ParentName": null, - "DisplayName": "App.Files", - "IsEnabled": true, - "MultiTenancySide": 2, - "MenuGroup": "Erp|Kurs" - }, - { - "GroupName": "App.Saas", - "Name": "App.Files.Create", - "ParentName": "App.Files", - "DisplayName": "Create", - "IsEnabled": true, - "MultiTenancySide": 2, - "MenuGroup": "Erp|Kurs" - }, - { - "GroupName": "App.Saas", - "Name": "App.Files.Update", - "ParentName": "App.Files", - "DisplayName": "Update", - "IsEnabled": true, - "MultiTenancySide": 2, - "MenuGroup": "Erp|Kurs" - }, - { - "GroupName": "App.Saas", - "Name": "App.Files.Delete", - "ParentName": "App.Files", - "DisplayName": "Delete", - "IsEnabled": true, - "MultiTenancySide": 2, - "MenuGroup": "Erp|Kurs" - }, - { - "GroupName": "App.Saas", - "Name": "App.Files.Widget", - "ParentName": "App.Files", - "DisplayName": "Widget", - "IsEnabled": true, - "MultiTenancySide": 2, - "MenuGroup": "Erp|Kurs" - }, { "GroupName": "App.Saas", "Name": "App.ForumManagement", @@ -3484,6 +3439,42 @@ "MultiTenancySide": 3, "MenuGroup": "Erp|Kurs" }, + { + "GroupName": "App.Administration", + "Name": "App.Files", + "ParentName": null, + "DisplayName": "App.Files", + "IsEnabled": true, + "MultiTenancySide": 3, + "MenuGroup": "Erp|Kurs" + }, + { + "GroupName": "App.Administration", + "Name": "App.Files.Create", + "ParentName": "App.Files", + "DisplayName": "Create", + "IsEnabled": true, + "MultiTenancySide": 3, + "MenuGroup": "Erp|Kurs" + }, + { + "GroupName": "App.Administration", + "Name": "App.Files.Update", + "ParentName": "App.Files", + "DisplayName": "Update", + "IsEnabled": true, + "MultiTenancySide": 3, + "MenuGroup": "Erp|Kurs" + }, + { + "GroupName": "App.Administration", + "Name": "App.Files.Delete", + "ParentName": "App.Files", + "DisplayName": "Delete", + "IsEnabled": true, + "MultiTenancySide": 3, + "MenuGroup": "Erp|Kurs" + }, { "GroupName": "App.Administration", "Name": "App.ForumManagement.Publish", diff --git a/ui/src/components/template/AiAssistant.tsx b/ui/src/components/template/AiAssistant.tsx index 18958cd..9651e14 100644 --- a/ui/src/components/template/AiAssistant.tsx +++ b/ui/src/components/template/AiAssistant.tsx @@ -2,7 +2,7 @@ import Tooltip from '@/components/ui/Tooltip' import { ROUTES_ENUM } from '@/routes/route.constant' import { useLocalization } from '@/utils/hooks/useLocalization' import { usePermission } from '@/utils/hooks/usePermission' -import { FcAssistant, FcHeadset } from 'react-icons/fc' +import { FcHeadset } from 'react-icons/fc' import { useNavigate } from 'react-router-dom' const AiAssistant = () => { diff --git a/ui/src/proxy/ai/models.ts b/ui/src/proxy/ai/models.ts index c121a03..8c04a3b 100644 --- a/ui/src/proxy/ai/models.ts +++ b/ui/src/proxy/ai/models.ts @@ -21,4 +21,6 @@ export type MessageContent = string | BaseContent export interface Message { role: 'user' | 'assistant' content: MessageContent + /** ISO string */ + createdAt?: string } \ No newline at end of file diff --git a/ui/src/views/admin/files/FileManager.tsx b/ui/src/views/admin/files/FileManager.tsx index 0ed9395..d7602b9 100644 --- a/ui/src/views/admin/files/FileManager.tsx +++ b/ui/src/views/admin/files/FileManager.tsx @@ -1,6 +1,7 @@ import { useState, useEffect, useCallback, useRef } from 'react' import { Helmet } from 'react-helmet' import { Button, Input, Select, toast, Notification, Spinner } from '@/components/ui' +import { useStoreState } from '@/store' import { FaFolder, FaCloudUploadAlt, @@ -43,6 +44,10 @@ import { APP_NAME } from '@/constants/app.constant' const FileManager = () => { const { translate } = useLocalization() + const authTenantId = useStoreState((state) => state.auth.tenant?.tenantId) + const authTenantName = useStoreState((state) => state.auth.tenant?.tenantName) + const isHostContext = !authTenantId + // State const [loading, setLoading] = useState(true) const [items, setItems] = useState([]) @@ -71,7 +76,7 @@ const FileManager = () => { const [tenants, setTenants] = useState([]) const [tenantsLoading, setTenantsLoading] = useState(false) const [selectedTenant, setSelectedTenant] = useState<{ id: string; name: string } | undefined>( - undefined, + authTenantId ? { id: authTenantId, name: authTenantName || '' } : undefined, ) // Tracks mid-flight tenant change so the fetch effect doesn't fire with a stale folderId const pendingTenantChange = useRef(false) @@ -96,8 +101,20 @@ const FileManager = () => { }, []) useEffect(() => { - fetchTenants() - }, [fetchTenants]) + if (isHostContext) { + fetchTenants() + } + }, [fetchTenants, isHostContext]) + + // If user is in a tenant context, lock selection to that tenant. + useEffect(() => { + if (!authTenantId) return + + setSelectedTenant((prev) => { + if (prev?.id === authTenantId) return prev + return { id: authTenantId, name: authTenantName || prev?.name || '' } + }) + }, [authTenantId, authTenantName]) // Reset navigation when tenant changes useEffect(() => { @@ -124,20 +141,6 @@ const FileManager = () => { } }) - // console.log('Fetched items:', protectedItems) - // console.log( - // 'Protected folders check:', - // protectedItems.filter((item) => item.isReadOnly), - // ) - // console.log( - // 'Folders with childCount:', - // protectedItems.filter((item) => item.type === 'folder').map(item => ({ - // name: item.name, - // childCount: item.childCount, - // hasChildCount: 'childCount' in item, - // type: typeof item.childCount - // })) - // ) setItems(protectedItems) } catch (error) { console.error('Failed to fetch items:', error) @@ -742,29 +745,38 @@ const FileManager = () => { {/* Tenant Selector Row */}
- ({ value: t.id ?? '', label: t.name ?? '' })), + ]} + value={{ + value: selectedTenant ? selectedTenant.id : '', + label: selectedTenant ? selectedTenant.name : 'Host', + }} + onChange={(option) => { + if (option && 'value' in option) { + const val = option.value as string + if (!val) { + setSelectedTenant(undefined) + } else { + const found = tenants.find((t) => t.id === val) + if (found) setSelectedTenant({ id: found.id!, name: found.name ?? '' }) + } } - } - }} - /> + }} + /> + ) : ( +
+ {authTenantName || selectedTenant?.name || ''} +
+ )}
{/* File Operations */} diff --git a/ui/src/views/ai/Assistant.tsx b/ui/src/views/ai/Assistant.tsx index 5f54399..5682e35 100644 --- a/ui/src/views/ai/Assistant.tsx +++ b/ui/src/views/ai/Assistant.tsx @@ -9,6 +9,7 @@ import { AiDto } from '@/proxy/ai/models' import { Container } from '@/components/shared' import { Helmet } from 'react-helmet' import { APP_NAME } from '@/constants/app.constant' +import dayjs from 'dayjs' // Types type ChatType = 'chat' | 'query' | 'analyze' @@ -27,6 +28,7 @@ type MessageContent = string | BaseContent interface Message { role: 'user' | 'assistant' content: MessageContent + createdAt?: string } const isContentObject = (content: MessageContent): content is BaseContent => @@ -102,13 +104,17 @@ const Assistant = () => { if (!input.trim()) return const userMessage = input.trim() + const userMessageCreatedAt = new Date().toISOString() setInput('') setLoading(true) // 1️⃣ Soruyu store'a ekle - addAiPost({ role: 'user', content: userMessage }) + addAiPost({ role: 'user', content: userMessage, createdAt: userMessageCreatedAt }) - setMessages((prev) => [...prev, { role: 'user', content: userMessage }]) + setMessages((prev) => [ + ...prev, + { role: 'user', content: userMessage, createdAt: userMessageCreatedAt }, + ]) try { const selectedBotItem = bot.find((item) => item.id === selectedBot) @@ -140,17 +146,24 @@ const Assistant = () => { const formattedAnswer = typeof mapped.answer === 'string' ? mapped.answer : JSON.stringify(mapped.answer) - addAiPost({ role: 'assistant', content: mapped }) // mapped bir BaseContent + const assistantMessageCreatedAt = new Date().toISOString() - setMessages((prev) => [...prev, { role: 'assistant', content: mapped }]) + addAiPost({ role: 'assistant', content: mapped, createdAt: assistantMessageCreatedAt }) // mapped bir BaseContent + + setMessages((prev) => [ + ...prev, + { role: 'assistant', content: mapped, createdAt: assistantMessageCreatedAt }, + ]) } catch { const errorMessage = 'Üzgünüm, bir hata oluştu. Lütfen tekrar deneyin.' - addAiPost({ role: 'assistant', content: errorMessage }) + const assistantMessageCreatedAt = new Date().toISOString() + addAiPost({ role: 'assistant', content: errorMessage, createdAt: assistantMessageCreatedAt }) setMessages((prev) => [ ...prev, { role: 'assistant', content: errorMessage, + createdAt: assistantMessageCreatedAt, }, ]) } @@ -312,7 +325,16 @@ const Assistant = () => {
- {renderMessageContent(msg)} +
+ {renderMessageContent(msg)} + {msg.createdAt && ( +
+ {dayjs(msg.createdAt).format('DD.MM.YYYY HH:mm')} +
+ )} +
))}