+
{filteredConfigurationFiles.map((file) => (
void
onCreate: (name: string, rootPath: string) => void
- initialPath?: string
+ initialPath: string
}
const CONFIG_DIR = 'src/main/configurations'
@@ -26,12 +26,12 @@ export default function NewConfigurationModal({
useEffect(() => {
if (!isOpen || !isLocal) {
- if (isOpen) setLocation(initialPath ?? '')
+ if (isOpen) setLocation(initialPath)
return
}
filesystemService
- .resolveNearestAccessiblePath(initialPath ?? '')
+ .resolveNearestAccessiblePath(initialPath)
.then(setLocation)
.catch(() => setLocation(''))
}, [isOpen, isLocal, initialPath])
diff --git a/src/main/frontend/app/routes/projectlanding/project-landing.tsx b/src/main/frontend/app/routes/projectlanding/project-landing.tsx
index ca3bfb16..70ca29a1 100644
--- a/src/main/frontend/app/routes/projectlanding/project-landing.tsx
+++ b/src/main/frontend/app/routes/projectlanding/project-landing.tsx
@@ -6,6 +6,7 @@ import { fetchInstanceConfigurations, type FFConfiguration } from '~/services/fr
import { useProjectStore } from '~/stores/project-store'
import { ApiError } from '~/utils/api'
import { logApiError } from '~/utils/logger'
+import {getParentPath, normalizePath} from '~/utils/path-utils'
import ConfigurationRow from './configuration-row'
import Search from '~/components/search/search'
@@ -187,7 +188,7 @@ export default function ProjectLanding() {
const projects = recentProjects ?? []
const filteredProjects = projects.filter((project) => project.name.toLowerCase().includes(searchTerm.toLowerCase()))
- const lastRecentRootPath = projects[0]?.rootPath
+ const lastRecentRootPath = normalizePath(projects[0]?.rootPath ?? '')
if (isLoading || isOpeningProject) return
@@ -231,14 +232,14 @@ export default function ProjectLanding() {
onClose={() => setIsModalOpen(false)}
onCreate={onCreateProject}
isLocal={isLocalEnvironment}
- initialPath={lastRecentRootPath}
+ initialPath={getParentPath(lastRecentRootPath)}
/>
setIsCloneModalOpen(false)}
onClone={onCloneProject}
- initialPath={lastRecentRootPath}
+ initialPath={getParentPath(lastRecentRootPath)}
/>
{!isLocalEnvironment && (
setIsOpenPickerOpen(false)}
rootLabel={rootLocationName}
- initialPath={lastRecentRootPath}
+ initialPath={getParentPath(lastRecentRootPath)}
/>
)
@@ -351,13 +352,13 @@ const ProjectList = ({
)
-const Toolbar = ({ onSearchChange }: { onSearchChange: (val: string) => void }) => (
+const Toolbar = ({ onSearchChange }: { onSearchChange: (value: string) => void }) => (
- onSearchChange(e.target.value)} />
+ onSearchChange(event.target.value)} />
)
diff --git a/src/main/frontend/app/utils/path-utils.ts b/src/main/frontend/app/utils/path-utils.ts
index 209341b3..af12ef87 100644
--- a/src/main/frontend/app/utils/path-utils.ts
+++ b/src/main/frontend/app/utils/path-utils.ts
@@ -3,7 +3,17 @@
* Returns null if the marker is not found.
*/
export function toRelativePath(absolutePath: string, marker: string): string | null {
- const normalized = absolutePath.replaceAll('\\', '/')
- const idx = normalized.indexOf(marker)
- return idx === -1 ? null : normalized.slice(idx + marker.length)
+ const normalizedPath = normalizePath(absolutePath)
+ const normalizedMarker = normalizePath(marker)
+ const idx = normalizedPath.indexOf(marker)
+ return idx === -1 ? null : normalizedMarker.slice(idx + marker.length)
+}
+
+export function normalizePath(path: string) {
+ return path.replaceAll('\\', '/')
+}
+
+export function getParentPath(path: string): string {
+ if (!path) return path // Return empty string if path is empty, small optimization to avoid regex processing
+ return path.replace(/\/?[^/]*$/, '')
}
diff --git a/src/main/java/org/frankframework/flow/project/ConfigurationProjectService.java b/src/main/java/org/frankframework/flow/project/ConfigurationProjectService.java
index 599cad77..6369cda3 100644
--- a/src/main/java/org/frankframework/flow/project/ConfigurationProjectService.java
+++ b/src/main/java/org/frankframework/flow/project/ConfigurationProjectService.java
@@ -34,8 +34,6 @@
@Log4j2
@Service
public class ConfigurationProjectService {
- private static final String CONFIGURATIONS_DIR = "src/main/configurations";
-
private final FileSystemStorage fileSystemStorage;
private final RecentProjectsService recentProjectsService;
@@ -70,7 +68,7 @@ private List
getProjectsFromRecentList() {
ConfigurationProject configurationProject = loadProjectCached(recent.rootPath());
foundProjects.add(configurationProject);
} catch (Exception _) {
- log.debug("Recent project no longer valid: {}", recent.rootPath());
+ log.debug("Recent project is no longer valid: {}", recent.rootPath());
}
}
return foundProjects;
@@ -101,13 +99,16 @@ public ConfigurationProject getProject(String name) throws ApiException {
return getProjects().stream()
.filter(project -> project.getName().equals(name))
.findFirst()
- .orElseThrow(() -> new ApiException("Project not found: " + name, HttpStatus.NOT_FOUND));
+ .orElseThrow(() -> new ApiException("Project \"" + name +"\" not found", HttpStatus.NOT_FOUND));
}
public ConfigurationProject createProjectOnDisk(ConfigurationProjectCreateDTO projectCreate) throws IOException {
Path rootPath = Path.of(projectCreate.rootPath());
- String resolvedRootPath = rootPath.endsWith(CONFIGURATIONS_DIR) ? projectCreate.name() : CONFIGURATIONS_DIR + "/" + projectCreate.name();
- Path projectCreationPath = rootPath.resolve(resolvedRootPath);
+ Path projectCreationPath = rootPath.resolve(projectCreate.name());
+
+ if (Files.exists(projectCreationPath)) {
+ throw new ApiException("Project already exists at " + projectCreationPath, HttpStatus.NOT_FOUND);
+ }
Path projectPath = fileSystemStorage.createProjectDirectory(projectCreationPath.toString());
ClassPathResource resource = new ClassPathResource("templates/default-configuration.xml");
@@ -125,9 +126,9 @@ public ConfigurationProject createProjectOnDisk(ConfigurationProjectCreateDTO pr
public ConfigurationProject openProjectFromDisk(String path) throws IOException, ApiException {
Path absolutePath = fileSystemStorage.toAbsolutePath(path);
if (!Files.exists(absolutePath) || !Files.isDirectory(absolutePath)) {
- throw new ApiException("Project not found at: " + path, HttpStatus.NOT_FOUND);
- } else if (!absolutePath.endsWith(CONFIGURATIONS_DIR + "/" + absolutePath.getFileName())) {
- throw new ApiException("Provided path doesn't seem to be a singular configuration", HttpStatus.BAD_REQUEST);
+ throw new ApiException("Project not found at \"" + path + "\"", HttpStatus.NOT_FOUND);
+ } else if (!absolutePath.resolve("Configuration.xml").toFile().exists()) {
+ throw new ApiException("Project doesn't seem to be a valid configuration or Configuration.xml might be missing", HttpStatus.BAD_REQUEST);
}
return loadProjectAndCache(path);
}
@@ -136,7 +137,7 @@ public ConfigurationProject cloneAndOpenProject(String repoUrl, String localPath
Path targetDir = fileSystemStorage.toAbsolutePath(localPath);
if (Files.exists(targetDir)) {
- throw new IllegalArgumentException("Project already exists at: " + localPath);
+ throw new IllegalArgumentException("Project already exists at \"" + localPath + "\"");
}
try {
@@ -154,9 +155,9 @@ public ConfigurationProject cloneAndOpenProject(String repoUrl, String localPath
} catch (GitAPIException exception) {
String msg = exception.getMessage() != null ? exception.getMessage().toLowerCase() : "";
if (msg.contains("auth") || msg.contains("not permitted") || msg.contains("403") || msg.contains("401")) {
- throw new IllegalArgumentException("Clone failed — authentication error. Please provide a valid Personal Access Token (PAT)", exception);
+ throw new IllegalArgumentException("Cloning authentication error. Please provide a valid Personal Access Token (PAT)", exception);
}
- throw new IllegalArgumentException("Clone failed: " + exception.getMessage(), exception);
+ throw new IllegalArgumentException("Cloning failed: " + exception.getMessage(), exception);
}
ConfigurationProject configurationProject = loadProjectAndCache(targetDir.toString());
diff --git a/src/test/java/org/frankframework/flow/project/ConfigurationProjectServiceTest.java b/src/test/java/org/frankframework/flow/project/ConfigurationProjectServiceTest.java
index ccda6197..094f9dcb 100644
--- a/src/test/java/org/frankframework/flow/project/ConfigurationProjectServiceTest.java
+++ b/src/test/java/org/frankframework/flow/project/ConfigurationProjectServiceTest.java
@@ -408,9 +408,9 @@ void testOpenProjectFromDisk() throws Exception {
String projectName = "manual_project";
Path projectDir = tempDir.resolve(projectName);
- Files.createDirectories(projectDir.resolve("src/main/configurations"));
+ Files.createDirectories(projectDir);
Files.writeString(
- projectDir.resolve("src/main/configurations/TestConfig.xml"),
+ projectDir.resolve("Configuration.xml"),
"",
StandardCharsets.UTF_8
);
@@ -449,26 +449,6 @@ void testOpenProjectFromDiskThrowsWhenPathIsAFile() throws Exception {
assertThrows(ApiException.class, () -> configurationProjectService.openProjectFromDisk(file.toString()));
}
- @Test
- void testOpenProjectFromDiskLoadsEmptyProject_whenNoConfigurationsDir() throws Exception {
- when(fileSystemStorage.toAbsolutePath(anyString())).thenAnswer(invocation -> {
- String pathStr = invocation.getArgument(0);
- Path path = Path.of(pathStr);
- return path.isAbsolute() ? path : tempDir.resolve(pathStr);
- });
-
- Path projDir = tempDir.resolve("empty_proj");
- Files.createDirectory(projDir);
-
- ConfigurationProject configurationProject = configurationProjectService.openProjectFromDisk(projDir.toString());
-
- assertNotNull(configurationProject);
- assertEquals("empty_proj", configurationProject.getName());
-
- ConfigurationProjectDTO dto = configurationProjectService.toDto(configurationProject);
- assertTrue(dto.filepaths().isEmpty(), "No configurations dir means empty config list");
- }
-
@Test
void testGetProjectsFromWorkspaceScan() throws Exception {
when(fileSystemStorage.isLocalEnvironment()).thenReturn(false);
From 80d8a4460a2c1558dc59f0f22bfcfe2667fbc222 Mon Sep 17 00:00:00 2001
From: Vivy <4380412+Matthbo@users.noreply.github.com>
Date: Thu, 28 May 2026 18:20:43 +0200
Subject: [PATCH 5/5] Linting
---
.../app/routes/configurations/configuration-overview.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/main/frontend/app/routes/configurations/configuration-overview.tsx b/src/main/frontend/app/routes/configurations/configuration-overview.tsx
index 4086f76e..a16831ed 100644
--- a/src/main/frontend/app/routes/configurations/configuration-overview.tsx
+++ b/src/main/frontend/app/routes/configurations/configuration-overview.tsx
@@ -11,7 +11,7 @@ import type { FileTreeNode } from '~/types/filesystem.types'
import { fetchProjectTree } from '~/services/file-tree-service'
import Button from '~/components/inputs/button'
import Search from '~/components/search/search'
-import {normalizePath, toRelativePath} from '~/utils/path-utils'
+import { normalizePath, toRelativePath } from '~/utils/path-utils'
interface ConfigurationFile {
path: string