This is an automated email from the ASF dual-hosted git repository.

marat pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel-karavan.git


The following commit(s) were added to refs/heads/main by this push:
     new 56b156a5 Fix #1411
56b156a5 is described below

commit 56b156a57a55b060ee4f8e727bb87f279ed2bb0d
Author: Marat Gubaidullin <[email protected]>
AuthorDate: Fri Sep 13 18:46:37 2024 -0400

    Fix #1411
---
 .../org/apache/camel/karavan/KaravanCache.java     |  14 +-
 .../apache/camel/karavan/api/ProjectResource.java  |   1 +
 .../camel/karavan/listener/CommitListener.java     |   5 +-
 .../apache/camel/karavan/service/GitService.java   |  18 ++-
 .../camel/karavan/service/ProjectService.java      |  12 +-
 karavan-app/src/main/webui/src/api/LogWatchApi.tsx |   6 +-
 .../src/main/webui/src/api/NotificationApi.tsx     |  11 +-
 .../src/main/webui/src/api/NotificationService.ts  |   2 +
 .../src/main/webui/src/api/ProjectService.ts       |   6 +-
 karavan-app/src/main/webui/src/api/ProjectStore.ts |  28 +++-
 .../src/main/webui/src/project/ProjectPanel.tsx    |   3 +-
 .../src/main/webui/src/project/files/FilesTab.tsx  | 143 +++++++++++++--------
 .../main/webui/src/project/files/FilesToolbar.tsx  |  10 +-
 13 files changed, 169 insertions(+), 90 deletions(-)

diff --git 
a/karavan-app/src/main/java/org/apache/camel/karavan/KaravanCache.java 
b/karavan-app/src/main/java/org/apache/camel/karavan/KaravanCache.java
index 5d1ead6d..4a355245 100644
--- a/karavan-app/src/main/java/org/apache/camel/karavan/KaravanCache.java
+++ b/karavan-app/src/main/java/org/apache/camel/karavan/KaravanCache.java
@@ -137,16 +137,14 @@ public class KaravanCache {
         }
     }
 
-    public void syncFilesCommited(String projectId) {
+    public void syncFilesCommited(String projectId, List<String> fileNames) {
         List<String> currentFileNames = new ArrayList<>();
-        getCopyProjectFilesCommited().stream()
-                .filter(pf -> Objects.equals(pf.getProjectId(), projectId))
+        getProjectFilesCommited(projectId).stream().filter(file -> 
fileNames.contains(file.getName()))
                 .forEach(pf -> currentFileNames.add(pf.getName()));
 
         currentFileNames.forEach(name -> deleteProjectFileCommited(projectId, 
name));
-        files.entrySet().stream()
-                .filter(es -> Objects.equals(es.getValue().getProjectId(), 
projectId))
-                .forEach(es -> filesCommited.put(es.getKey(), 
es.getValue().copy()));
+        getProjectFiles(projectId).stream().filter(file -> 
fileNames.contains(file.getName()))
+                .forEach(f -> saveProjectFileCommited(f.copy()));
     }
 
     public void saveProjectFiles(Map<String, ProjectFile> filesToSave, boolean 
startup) {
@@ -179,6 +177,10 @@ public class KaravanCache {
         filesCommited.remove(GroupedKey.create(projectId, DEV, filename));
     }
 
+    public void saveProjectFileCommited(ProjectFile file) {
+        filesCommited.put(GroupedKey.create(file.getProjectId(), DEV, 
file.getName()), file);
+    }
+
     public void deleteProject(String projectId, boolean startup) {
         var key = new GroupedKey(projectId, DEV, projectId);
         projects.remove(key.getCacheKey());
diff --git 
a/karavan-app/src/main/java/org/apache/camel/karavan/api/ProjectResource.java 
b/karavan-app/src/main/java/org/apache/camel/karavan/api/ProjectResource.java
index a6160864..dfba7c04 100644
--- 
a/karavan-app/src/main/java/org/apache/camel/karavan/api/ProjectResource.java
+++ 
b/karavan-app/src/main/java/org/apache/camel/karavan/api/ProjectResource.java
@@ -107,6 +107,7 @@ public class ProjectResource extends AbstractApiResource {
         var identity = getIdentity(securityContext);
         // delete from cache
         karavanCache.getProjectFiles(projectId).forEach(file -> 
karavanCache.deleteProjectFile(projectId, file.getName(), false));
+        karavanCache.getProjectFilesCommited(projectId).forEach(file -> 
karavanCache.deleteProjectFileCommited(projectId, file.getName()));
         karavanCache.deleteProject(projectId, false);
         // delete from git
         gitService.deleteProject(projectId, identity.get("name"), 
identity.get("email"));
diff --git 
a/karavan-app/src/main/java/org/apache/camel/karavan/listener/CommitListener.java
 
b/karavan-app/src/main/java/org/apache/camel/karavan/listener/CommitListener.java
index 3392ec2a..78130767 100644
--- 
a/karavan-app/src/main/java/org/apache/camel/karavan/listener/CommitListener.java
+++ 
b/karavan-app/src/main/java/org/apache/camel/karavan/listener/CommitListener.java
@@ -26,6 +26,8 @@ import org.apache.camel.karavan.model.Project;
 import org.apache.camel.karavan.service.ProjectService;
 import org.jboss.logging.Logger;
 
+import java.util.List;
+
 import static org.apache.camel.karavan.KaravanEvents.*;
 
 @Default
@@ -49,8 +51,9 @@ public class CommitListener {
         String eventId = event.getString("eventId");
         String authorName = event.getString("authorName");
         String authorEmail = event.getString("authorEmail");
+        List<String> fileNames = event.containsKey("fileNames") ? 
List.of(event.getString("fileNames").split(",")) : List.of();
         try {
-            Project p = projectService.commitAndPushProject(projectId, 
message, authorName, authorEmail);
+            Project p = projectService.commitAndPushProject(projectId, 
message, authorName, authorEmail, fileNames);
             if (userId != null) {
                 eventBus.publish(COMMIT_HAPPENED, JsonObject.of("userId", 
userId, "eventId", eventId, "project", JsonObject.mapFrom(p)));
             }
diff --git 
a/karavan-app/src/main/java/org/apache/camel/karavan/service/GitService.java 
b/karavan-app/src/main/java/org/apache/camel/karavan/service/GitService.java
index 7575cc89..d1e81300 100644
--- a/karavan-app/src/main/java/org/apache/camel/karavan/service/GitService.java
+++ b/karavan-app/src/main/java/org/apache/camel/karavan/service/GitService.java
@@ -110,7 +110,7 @@ public class GitService {
         return new GitConfig(repository, username.orElse(null), 
password.orElse(null), branch, privateKeyPath.orElse(null));
     }
 
-    public RevCommit commitAndPushProject(Project project, List<ProjectFile> 
files, String message, String authorName, String authorEmail) throws 
GitAPIException, IOException, URISyntaxException {
+    public RevCommit commitAndPushProject(Project project, List<ProjectFile> 
files, String message, String authorName, String authorEmail, List<String> 
fileNames) throws GitAPIException, IOException, URISyntaxException {
         LOGGER.info("Commit and push project " + project.getProjectId());
         GitConfig gitConfig = getGitConfig();
         String uuid = UUID.randomUUID().toString();
@@ -128,7 +128,7 @@ public class GitService {
 //        }
         writeProjectToFolder(folder, project, files);
         addDeletedFilesToIndex(git, folder, project, files);
-        return commitAddedAndPush(git, gitConfig.getBranch(), message, 
authorName, authorEmail);
+        return commitAddedAndPush(git, gitConfig.getBranch(), message, 
authorName, authorEmail, fileNames, project.getProjectId());
     }
 
     public List<GitRepo> readProjectsToImport() {
@@ -157,10 +157,10 @@ public class GitService {
                     String name = entry.getKey();
                     String body = entry.getValue();
                     Tuple2<String, Integer> fileCommit = lastCommit(git, 
project + File.separator + name);
-                    files.add(new GitRepoFile(name, 
Integer.valueOf(fileCommit.getItem2()).longValue() * 1000, body));
+                    files.add(new GitRepoFile(name, 
fileCommit.getItem2().longValue() * 1000, body));
                 }
                 Tuple2<String, Integer> commit = lastCommit(git, project);
-                GitRepo repo = new GitRepo(project, commit.getItem1(), 
Integer.valueOf(commit.getItem2()).longValue() * 1000, files);
+                GitRepo repo = new GitRepo(project, commit.getItem1(), 
commit.getItem2().longValue() * 1000, files);
                 result.add(repo);
             }
             return result;
@@ -269,9 +269,13 @@ public class GitService {
         });
     }
 
-    public RevCommit commitAddedAndPush(Git git, String branch, String 
message, String authorName, String authorEmail) throws GitAPIException {
+    public RevCommit commitAddedAndPush(Git git, String branch, String 
message, String authorName, String authorEmail, List<String> fileNames, String 
projectId) throws GitAPIException {
         LOGGER.info("Commit and push changes to the branch " + branch);
-        LOGGER.info("Git add: " + git.add().addFilepattern(".").call());
+        AddCommand add = git.add();
+        for (String fileName : fileNames) {
+            add = add.addFilepattern(projectId + File.separator + fileName);
+        }
+        LOGGER.info("Git add: " + add.call());
         RevCommit commit = git.commit().setMessage(message).setAuthor(new 
PersonIdent(authorName, authorEmail)).call();
         LOGGER.info("Git commit: " + commit);
         if (!ephemeral) {
@@ -310,7 +314,7 @@ public class GitService {
         try {
             Git git = getGit(true, folder);
             addDeletedFolderToIndex(git, projectId);
-            commitAddedAndPush(git, gitConfig.getBranch(), commitMessage, 
authorName, authorEmail);
+            commitAddedAndPush(git, gitConfig.getBranch(), commitMessage, 
authorName, authorEmail, List.of("."), projectId);
             LOGGER.info("Delete Temp folder " + folder);
             vertx.fileSystem().deleteRecursiveBlocking(folder, true);
             LOGGER.infof("Project %s deleted from Git", projectId);
diff --git 
a/karavan-app/src/main/java/org/apache/camel/karavan/service/ProjectService.java
 
b/karavan-app/src/main/java/org/apache/camel/karavan/service/ProjectService.java
index 1c2f6995..b3fa8e0d 100644
--- 
a/karavan-app/src/main/java/org/apache/camel/karavan/service/ProjectService.java
+++ 
b/karavan-app/src/main/java/org/apache/camel/karavan/service/ProjectService.java
@@ -71,17 +71,17 @@ public class ProjectService {
     EventBus eventBus;
 
 
-    public Project commitAndPushProject(String projectId, String message) 
throws Exception {
-        return commitAndPushProject(projectId, message, DEFAULT_AUTHOR_NAME, 
DEFAULT_AUTHOR_EMAIL);
+    public Project commitAndPushProject(String projectId, String message, 
List<String> fileNames) throws Exception {
+        return commitAndPushProject(projectId, message, DEFAULT_AUTHOR_NAME, 
DEFAULT_AUTHOR_EMAIL, fileNames);
     }
 
-    public Project commitAndPushProject(String projectId, String message, 
String authorName, String authorEmail) throws Exception {
+    public Project commitAndPushProject(String projectId, String message, 
String authorName, String authorEmail, List<String> fileNames) throws Exception 
{
         if (Objects.equals(environment, DEV)) {
             LOGGER.info("Commit project: " + projectId);
             Project p = karavanCache.getProject(projectId);
             List<ProjectFile> files = karavanCache.getProjectFiles(projectId);
-            RevCommit commit = gitService.commitAndPushProject(p, files, 
message, authorName, authorEmail);
-            karavanCache.syncFilesCommited(projectId);
+            RevCommit commit = gitService.commitAndPushProject(p, files, 
message, authorName, authorEmail, fileNames);
+            karavanCache.syncFilesCommited(projectId, fileNames);
             String commitId = commit.getId().getName();
             Long lastUpdate = commit.getCommitTime() * 1000L;
             p.setLastCommit(commitId);
@@ -163,6 +163,7 @@ public class ProjectService {
                 ProjectFile file = new ProjectFile(repoFile.getName(), 
repoFile.getBody(), repo.getName(), repoFile.getLastCommitTimestamp());
                 karavanCache.saveProjectFile(file, true, false);
             });
+            karavanCache.syncFilesCommited(project.getProjectId(), 
karavanCache.getProjectFiles(project.getProjectId()).stream().map(ProjectFile::getName).collect(Collectors.toList()));
         } catch (Exception e) {
             LOGGER.error("Error during project import", e);
         }
@@ -215,6 +216,7 @@ public class ProjectService {
         String imageName = data.getString("imageName");
         boolean commit = data.getBoolean("commit");
         data.put("projectId", projectId);
+        data.put("fileNames", PROJECT_COMPOSE_FILENAME);
         codeService.updateDockerComposeImage(projectId, imageName);
         if (commit) {
             eventBus.publish(CMD_PUSH_PROJECT, data);
diff --git a/karavan-app/src/main/webui/src/api/LogWatchApi.tsx 
b/karavan-app/src/main/webui/src/api/LogWatchApi.tsx
index 83e5640d..e4bab51c 100644
--- a/karavan-app/src/main/webui/src/api/LogWatchApi.tsx
+++ b/karavan-app/src/main/webui/src/api/LogWatchApi.tsx
@@ -51,7 +51,11 @@ export class LogWatchApi {
                         }
                     },
                     onmessage(event) {
-                        ProjectEventBus.sendLog('add', event.data);
+                        if (event.event !== 'ping') {
+                            ProjectEventBus.sendLog('add', event.data);
+                        } else {
+                            console.log('Logger SSE Ping', event);
+                        }
                     },
                     onclose() {
                         console.log("Connection closed by the server");
diff --git a/karavan-app/src/main/webui/src/api/NotificationApi.tsx 
b/karavan-app/src/main/webui/src/api/NotificationApi.tsx
index f33729fb..1ccc339e 100644
--- a/karavan-app/src/main/webui/src/api/NotificationApi.tsx
+++ b/karavan-app/src/main/webui/src/api/NotificationApi.tsx
@@ -31,15 +31,15 @@ export class NotificationApi {
     }
 
     static onSystemMessage (ev: EventSourceMessage) {
+        console.log('onSystemMessage', ev)
         const ke = NotificationApi.getKaravanEvent(ev, 'system');
         NotificationEventBus.sendEvent(ke);
-        console.log('onSystemMessage', ev)
     }
 
     static onUserMessage (ev: EventSourceMessage) {
+        console.log('onUserMessage', ev)
         const ke = NotificationApi.getKaravanEvent(ev, 'user');
         NotificationEventBus.sendEvent(ke);
-        console.log('onUserMessage', ev)
     }
 
     static async notification(controller: AbortController) {
@@ -58,7 +58,6 @@ export class NotificationApi {
             if (ready) {
                 NotificationApi.fetch('/ui/notification/system/' + 
KaravanApi.getUserId(), controller, headers,
                     ev => NotificationApi.onSystemMessage(ev));
-                console.log("KaravanApi.getUserId()", KaravanApi.getUserId())
                 NotificationApi.fetch('/ui/notification/user/' + 
KaravanApi.getUserId(), controller, headers,
                     ev => NotificationApi.onUserMessage(ev));
             }
@@ -84,7 +83,11 @@ export class NotificationApi {
                 }
             },
             onmessage(event) {
-                onmessage(event);
+                if (event.event !== 'ping') {
+                    onmessage(event);
+                } else {
+                    console.log('Notification SSE Ping', event);
+                }
             },
             onclose() {
                 console.log("Connection closed by the server");
diff --git a/karavan-app/src/main/webui/src/api/NotificationService.ts 
b/karavan-app/src/main/webui/src/api/NotificationService.ts
index ccb31750..a8c821e1 100644
--- a/karavan-app/src/main/webui/src/api/NotificationService.ts
+++ b/karavan-app/src/main/webui/src/api/NotificationService.ts
@@ -61,6 +61,8 @@ const sub = NotificationEventBus.onEvent()?.subscribe((event: 
KaravanEvent) => {
     } else if (event.event === 'error') {
         const error = event.data?.error;
         EventBus.sendAlert('Error', error, "danger");
+    } else if (event.event === 'ping') {
+        // do nothing
     } else {
         const message = event.data?.message ?  event.data?.message : 
JSON.stringify(event.data);
         EventBus.sendAlert('Success', message);
diff --git a/karavan-app/src/main/webui/src/api/ProjectService.ts 
b/karavan-app/src/main/webui/src/api/ProjectService.ts
index 924d5913..575f0aa7 100644
--- a/karavan-app/src/main/webui/src/api/ProjectService.ts
+++ b/karavan-app/src/main/webui/src/api/ProjectService.ts
@@ -98,12 +98,14 @@ export class ProjectService {
         });
     }
 
-    public static pushProject(project: Project, commitMessage: string) {
+    public static pushProject(project: Project, commitMessage: string, 
selectedFileNames: string[]) {
         const params = {
             'projectId': project.projectId,
             'message': commitMessage,
-            'userId': KaravanApi.getUserId()
+            'userId': KaravanApi.getUserId(),
+            'fileNames': selectedFileNames.join(","),
         };
+        console.log(params);
         KaravanApi.push(params, res => {
             if (res.status === 200 || res.status === 201) {
                 // ProjectService.refreshProject(project.projectId);
diff --git a/karavan-app/src/main/webui/src/api/ProjectStore.ts 
b/karavan-app/src/main/webui/src/api/ProjectStore.ts
index 87b6bd68..9b2da8c0 100644
--- a/karavan-app/src/main/webui/src/api/ProjectStore.ts
+++ b/karavan-app/src/main/webui/src/api/ProjectStore.ts
@@ -28,6 +28,7 @@ import {ProjectEventBus} from "./ProjectEventBus";
 import {unstable_batchedUpdates} from "react-dom";
 import {createWithEqualityFn} from "zustand/traditional";
 import {shallow} from "zustand/shallow";
+import {useState} from "react";
 
 interface AppConfigState {
     isAuthorized: boolean;
@@ -197,11 +198,16 @@ interface FilesState {
     diff: any;
     setFiles: (files: ProjectFile[]) => void;
     upsertFile: (file: ProjectFile) => void;
+    selectedFileNames: string[];
+    setSelectedFileNames: (selectedFileNames: string[]) => void;
+    selectFile: (filename: string) => void;
+    unselectFile: (filename: string) => void;
 }
 
 export const useFilesStore = createWithEqualityFn<FilesState>((set) => ({
     files: [],
     diff: {},
+    selectedFileNames: [],
     setFiles: (files: ProjectFile[]) => {
         set((state: FilesState) => ({
             files: files
@@ -213,6 +219,26 @@ export const useFilesStore = 
createWithEqualityFn<FilesState>((set) => ({
                 ? [...state.files, file]
                 : [...state.files.filter(f => f.name !== file.name), file]
         }));
+    },
+    setSelectedFileNames: (selectedFileNames: string[]) => {
+        set((state: FilesState) => ({
+            selectedFileNames: selectedFileNames
+        }));
+    },
+    selectFile: (filename: string) => {
+        set((state: FilesState) => {
+            const names = [...state.selectedFileNames];
+            if (!state.selectedFileNames.includes(filename)) {
+                names.push(filename);
+            }
+            return ({selectedFileNames: names})
+        });
+    },
+    unselectFile: (filename: string) => {
+        set((state: FilesState) => {
+            const names = [...state.selectedFileNames.filter(f => f !== 
filename)];
+            return ({selectedFileNames: names})
+        });
     }
 }), shallow)
 
@@ -237,8 +263,6 @@ export const useFileStore = 
createWithEqualityFn<FileState>((set) => ({
     },
 }), shallow)
 
-
-
 interface WizardState {
     showWizard: boolean;
     setShowWizard: (showWizard: boolean) => void;
diff --git a/karavan-app/src/main/webui/src/project/ProjectPanel.tsx 
b/karavan-app/src/main/webui/src/project/ProjectPanel.tsx
index 3aeb34ca..d33d4d41 100644
--- a/karavan-app/src/main/webui/src/project/ProjectPanel.tsx
+++ b/karavan-app/src/main/webui/src/project/ProjectPanel.tsx
@@ -43,7 +43,7 @@ export function ProjectPanel() {
     const [config] = useAppConfigStore((state) => [state.config], shallow)
     const [project, tab, setTab] = useProjectStore((s) => [s.project, 
s.tabIndex, s.setTabIndex], shallow);
     const [setFile] = useFileStore((s) => [s.setFile], shallow);
-    const [files, setFiles] = useFilesStore((s) => [s.files, s.setFiles], 
shallow);
+    const [files, setFiles, setSelectedFileNames] = useFilesStore((s) => 
[s.files, s.setFiles, s.setSelectedFileNames], shallow);
     const [setShowWizard] = useWizardStore((s) => [s.setShowWizard], shallow)
     const isDev = config.environment === 'dev';
 
@@ -54,6 +54,7 @@ export function ProjectPanel() {
     function onRefresh() {
         if (project.projectId) {
             setFiles([]);
+            setSelectedFileNames([]);
             ProjectService.refreshProjectData(project.projectId);
             setTab(project.type !== ProjectType.normal ? 'files' : tab);
         }
diff --git a/karavan-app/src/main/webui/src/project/files/FilesTab.tsx 
b/karavan-app/src/main/webui/src/project/files/FilesTab.tsx
index 05eced7f..aa20cc89 100644
--- a/karavan-app/src/main/webui/src/project/files/FilesTab.tsx
+++ b/karavan-app/src/main/webui/src/project/files/FilesTab.tsx
@@ -14,7 +14,6 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 import React, {useEffect, useState} from 'react';
 import {
     Badge,
@@ -54,23 +53,25 @@ import {CreateIntegrationModal} from 
"./CreateIntegrationModal";
 import {DiffFileModal} from "./DiffFileModal";
 import {ProjectService} from "../../api/ProjectService";
 
-export function FilesTab () {
+export function FilesTab() {
 
     const [config] = useAppConfigStore((s) => [s.config], shallow);
-    const [files, diff] = useFilesStore((s) => [s.files, s.diff], shallow);
+    const [files, diff, selectedFileNames, selectFile, unselectFile, 
setSelectedFileNames]
+        = useFilesStore((s) => [s.files, s.diff, s.selectedFileNames, 
s.selectFile, s.unselectFile, s.setSelectedFileNames], shallow);
     const [project] = useProjectStore((s) => [s.project], shallow);
     const [setFile] = useFileStore((s) => [s.setFile], shallow);
     const [id, setId] = useState<string>('');
 
     const filenames = files.map(f => f.name);
-    const deletedFilenames: string[] =  Object.getOwnPropertyNames(diff)
-        .map(name => diff[name] === 'DELETED' ? name: '')
+    const deletedFilenames: string[] = Object.getOwnPropertyNames(diff)
+        .map(name => diff[name] === 'DELETED' ? name : '')
         .filter(name => name !== '' && !filenames.includes(name));
-    const deletedFiles: ProjectFile[] =  deletedFilenames.map(d => new 
ProjectFile(d, project.projectId, '', 0))
-    const allFiles =  files.concat(deletedFiles);
+    const deletedFiles: ProjectFile[] = deletedFilenames.map(d => new 
ProjectFile(d, project.projectId, '', 0))
+    const allFiles = files.concat(deletedFiles);
 
     useEffect(() => {
         onRefresh();
+        setSelectedFileNames([]);
     }, []);
 
     function onRefresh() {
@@ -83,7 +84,7 @@ export function FilesTab () {
         return diff && diff[filename] !== undefined;
     }
 
-    function download (file: ProjectFile) {
+    function download(file: ProjectFile) {
         if (file) {
             const type = file.name.endsWith("yaml") ? 
"application/yaml;charset=utf-8" : undefined;
             const f = new File([file.code], file.name, {type: type});
@@ -126,6 +127,14 @@ export function FilesTab () {
         return false;
     }
 
+    function selectAllFiles(isSelecting: boolean) {
+        if (isSelecting) {
+            allFiles.forEach(file => selectFile(file.name))
+        } else {
+            allFiles.forEach(file => unselectFile(file.name))
+        }
+    }
+
     return (
         <PageSection className="project-tab-panel" padding={{default: 
"padding"}}>
             <Panel>
@@ -133,11 +142,17 @@ export function FilesTab () {
                     <FileToolbar/>
                 </PanelHeader>
             </Panel>
-            <DiffFileModal id={id}/>
-            <div style={{height:"100%", overflow:"auto"}}>
+            <div style={{height: "100%", overflow: "auto"}}>
                 <Table aria-label="Files" variant={"compact"} 
className={"table"}>
                     <Thead>
                         <Tr>
+                            <Th
+                                select={{
+                                    onSelect: (_event, isSelecting) => 
selectAllFiles(isSelecting),
+                                    isSelected: selectedFileNames.length === 
allFiles.length
+                                }}
+                                aria-label="Row select"
+                            />
                             <Th key='type' width={20}>Type</Th>
                             <Th key='filename' width={40}>Filename</Th>
                             <Th key='status' width={30}>Status</Th>
@@ -145,54 +160,69 @@ export function FilesTab () {
                         </Tr>
                     </Thead>
                     <Tbody>
-                        {allFiles.map(file => {
+                        {allFiles.map((file, rowIndex) => {
                             const type = getProjectFileTypeTitle(file)
                             const diffType = diff[file.name];
                             const isForOtherEnv = 
forOtherEnvironment(file.name);
-                            return <Tr key={file.name}>
-                                <Td>
-                                    <Badge 
isRead={isForOtherEnv}>{type}</Badge>
-                                </Td>
-                                <Td>
-                                    <Button style={{padding: '6px'}} 
variant={isForOtherEnv ? 'plain' : 'link'}
-                                            onClick={e => {
-                                                setFile('select', file, 
undefined);
-                                            }}>
-                                        {file.name}
-                                    </Button>
-                                </Td>
-                                <Td>
-                                    {needCommit(file.name) &&
-                                        <Tooltip content="Show diff" 
position={"right"}>
-                                            <Label color="grey">
-                                                <Button size="sm" 
variant="link" className='labeled-button'
-                                                        icon={<DiffIcon/>}
-                                                        onClick={e => {
-                                                            setFile('diff', 
file, undefined);
-                                                            
setId(Math.random().toString());
-                                                        }}>
-                                                    {diffType}
-                                                </Button>
-                                            </Label>
+                            return (
+                                <Tr key={file.name}>
+                                    <Td
+                                        select={{
+                                            rowIndex,
+                                            onSelect: (_event, isSelecting) => 
{
+                                                if (isSelecting) {
+                                                    selectFile(file.name);
+                                                } else {
+                                                    unselectFile(file.name);
+                                                }
+                                            },
+                                            isSelected: 
selectedFileNames.includes(file.name),
+                                        }}
+                                    />
+                                    <Td>
+                                        <Badge 
isRead={isForOtherEnv}>{type}</Badge>
+                                    </Td>
+                                    <Td>
+                                        <Button style={{padding: '6px'}} 
variant={isForOtherEnv ? 'plain' : 'link'}
+                                                onClick={e => {
+                                                    setFile('select', file, 
undefined);
+                                                }}>
+                                            {file.name}
+                                        </Button>
+                                    </Td>
+                                    <Td>
+                                        {needCommit(file.name) &&
+                                            <Tooltip content="Show diff" 
position={"right"}>
+                                                <Label color="grey">
+                                                    <Button size="sm" 
variant="link" className='labeled-button'
+                                                            icon={<DiffIcon/>}
+                                                            onClick={e => {
+                                                                
setFile('diff', file, undefined);
+                                                                
setId(Math.random().toString());
+                                                            }}>
+                                                        {diffType}
+                                                    </Button>
+                                                </Label>
+                                            </Tooltip>
+                                        }
+                                        {!needCommit(file.name) &&
+                                            <Label color="green" 
icon={<CheckIcon/>}/>
+                                        }
+                                    </Td>
+                                    <Td modifier={"fitContent"}>
+                                        <Button className="dev-action-button" 
variant={"plain"}
+                                                
isDisabled={!canDeleteFiles(file.name)}
+                                                onClick={e =>
+                                                    setFile('delete', file)
+                                                }>
+                                            <DeleteIcon/>
+                                        </Button>
+                                        <Tooltip content="Download source" 
position={"bottom-end"}>
+                                            <Button 
className="dev-action-button" size="sm" variant="plain" icon={<DownloadIcon/>} 
onClick={e => download(file)}/>
                                         </Tooltip>
-                                    }
-                                    {!needCommit(file.name) &&
-                                        <Label color="green" 
icon={<CheckIcon/>}/>
-                                    }
-                                </Td>
-                                <Td modifier={"fitContent"}>
-                                    <Button className="dev-action-button" 
variant={"plain"}
-                                            
isDisabled={!canDeleteFiles(file.name)}
-                                            onClick={e =>
-                                                setFile('delete', file)
-                                    }>
-                                        <DeleteIcon/>
-                                    </Button>
-                                    <Tooltip content="Download source" 
position={"bottom-end"}>
-                                        <Button className="dev-action-button"  
size="sm" variant="plain" icon={<DownloadIcon/>} onClick={e => download(file)}/>
-                                    </Tooltip>
-                                </Td>
-                            </Tr>
+                                    </Td>
+                                </Tr>
+                            )
                         })}
                         {diff && Object.keys(diff).filter(f => diff[f] === 
'DELETE').map(fileName => {
                             const type = 
getProjectFileTypeByNameTitle(fileName)
@@ -208,7 +238,7 @@ export function FilesTab () {
                                 <Td colSpan={8}>
                                     <Bullseye>
                                         <EmptyState 
variant={EmptyStateVariant.sm}>
-                                            <EmptyStateHeader titleText="No 
results found" icon={<EmptyStateIcon icon={SearchIcon}/>} headingLevel="h2" />
+                                            <EmptyStateHeader titleText="No 
results found" icon={<EmptyStateIcon icon={SearchIcon}/>} headingLevel="h2"/>
                                         </EmptyState>
                                     </Bullseye>
                                 </Td>
@@ -218,9 +248,10 @@ export function FilesTab () {
                 </Table>
             </div>
             <UploadFileModal/>
+            <DeleteFileModal/>
+            <DiffFileModal id={id}/>
             {!isKameletsProject() && <CreateFileModal/>}
             {isKameletsProject() && <CreateIntegrationModal/>}
-            <DeleteFileModal />
         </PageSection>
     )
 }
diff --git a/karavan-app/src/main/webui/src/project/files/FilesToolbar.tsx 
b/karavan-app/src/main/webui/src/project/files/FilesToolbar.tsx
index eecb38df..1721f7f7 100644
--- a/karavan-app/src/main/webui/src/project/files/FilesToolbar.tsx
+++ b/karavan-app/src/main/webui/src/project/files/FilesToolbar.tsx
@@ -42,13 +42,13 @@ import {getShortCommit, isEmpty} from 
"../../util/StringUtils";
 
 export function FileToolbar () {
 
+    const [config] = useAppConfigStore((s) => [s.config], shallow);
+    const [project, isPushing, isPulling] = useProjectStore((s) => [s.project, 
s.isPushing, s.isPulling], shallow )
+    const [diff, selectedFileNames] = useFilesStore((s) => [s.diff, 
s.selectedFileNames], shallow);
+    const [file, setFile] = useFileStore((s) => [s.file, s.setFile], shallow )
     const [commitMessageIsOpen, setCommitMessageIsOpen] = useState(false);
     const [pullIsOpen, setPullIsOpen] = useState(false);
     const [commitMessage, setCommitMessage] = useState('');
-    const [project, isPushing, isPulling] = useProjectStore((s) => [s.project, 
s.isPushing, s.isPulling], shallow )
-    const [diff] = useFilesStore((s) => [s.diff], shallow);
-    const [file, setFile] = useFileStore((s) => [s.file, s.setFile], shallow )
-    const [config] = useAppConfigStore((s) => [s.config], shallow);
     const isDev = config.environment === 'dev';
 
     useEffect(() => {
@@ -57,7 +57,7 @@ export function FileToolbar () {
     function push () {
         setCommitMessageIsOpen(false);
         useProjectStore.setState({isPushing: true});
-        ProjectService.pushProject(project, commitMessage);
+        ProjectService.pushProject(project, commitMessage, selectedFileNames);
     }
 
     function pull () {

Reply via email to