This is an automated email from the ASF dual-hosted git repository. mgubaidullin pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/camel-karavan.git
commit a784ba5c940d70787eb679412efa027cce97aa3d Author: Marat Gubaidullin <[email protected]> AuthorDate: Mon Aug 24 15:35:46 2026 -0400 Karavan Webapp UI Services --- .../main/webui/src/services/NotificationService.ts | 73 +++++ .../src/main/webui/src/services/ProjectService.ts | 310 +++++++++++++++++++++ .../src/main/webui/src/services/SystemService.ts | 37 +++ 3 files changed, 420 insertions(+) diff --git a/karavan-app/src/main/webui/src/services/NotificationService.ts b/karavan-app/src/main/webui/src/services/NotificationService.ts new file mode 100644 index 00000000..27265bf5 --- /dev/null +++ b/karavan-app/src/main/webui/src/services/NotificationService.ts @@ -0,0 +1,73 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {Subject} from "rxjs"; +import {unstable_batchedUpdates} from "react-dom"; +import {useProjectStore} from "@stores/ProjectStore"; +import {ProjectService} from "./ProjectService"; +import {EventBus} from "@designer/utils/EventBus"; + +export class KaravanEvent { + id: string = ''; + type: 'system' | 'user' = 'system'; + event: string = ''; + className: string = ''; + data: any = {}; + + public constructor(init?: Partial<KaravanEvent>) { + Object.assign(this, init); + } +} + +const karavanEvents = new Subject<KaravanEvent>(); + +export const NotificationEventBus = { + sendEvent: (event: KaravanEvent) => karavanEvents.next(event), + onEvent: () => karavanEvents.asObservable(), +} + +const sub = NotificationEventBus.onEvent()?.subscribe((event: KaravanEvent) => { + if (event.event === 'commit' && event.className === "ProjectFolder") { + const projectId = event.data?.projectId; + const statuses = event.data?.statuses; + const messages = event.data?.messages; + const variant = statuses?.filter((s: any) => s !== "OK")?.length > 0 ? 'danger' : 'success'; + const title = variant === 'success' ? 'Commited' : 'Error'; + EventBus.sendAlert(title, messages, variant); + if (useProjectStore.getState().project?.projectId === projectId) { + unstable_batchedUpdates(() => { + useProjectStore.setState({isPushing: false}); + ProjectService.refreshProject(projectId); + ProjectService.refreshProjectData(projectId); + }); + } + } else if (event.event === 'imagesLoaded') { + const projectId = event.data?.projectId; + unstable_batchedUpdates(() => { + ProjectService.refreshImages(projectId); + }); + } 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/services/ProjectService.ts b/karavan-app/src/main/webui/src/services/ProjectService.ts new file mode 100644 index 00000000..e732d047 --- /dev/null +++ b/karavan-app/src/main/webui/src/services/ProjectService.ts @@ -0,0 +1,310 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {KaravanApi} from '@api/KaravanApi'; +import {CamelStatus, ContainerImage, ContainerStatus, Project, ProjectFile, ProjectType} from '@models/ProjectModels'; +import {TemplateApi} from '@core/api/TemplateApi'; +import {InfrastructureAPI} from '@designer/utils/InfrastructureAPI'; +import {unstable_batchedUpdates} from 'react-dom' +import {useDevModeStore, useFilesStore, useFileStore, useProjectsStore, useProjectStore, useStatusesStore} from '@stores/ProjectStore'; +import {LogsEventBus} from '@bus/LogsEventBus'; +import {EventBus} from "@designer/utils/EventBus"; +import {KameletApi} from "@core/api/KameletApi"; +import {getCurrentUser} from "@api/auth/AuthApi"; +import {AxiosResponse} from "axios"; +import {JSON_SCHEMA_EXTENSION, KARAVAN_DOT_EXTENSION, LANDSCAPE_FILE_NAME_JSON, OPENAPI_FILE_NAME_JSON} from "@core/contants"; +import {useLogStore} from "@stores/LogStore"; +import {useContainerStatusesStore} from "@stores/ContainerStatusesStore"; +import {ComponentApi} from "@core/api/ComponentApi"; + +const COMPONENTS_BLACK_LIST = 'component-blocklist.txt'; + +export class ProjectService { + + public static startDevModeContainer(projectId: string, verbose: boolean, compile: boolean = false, showLog: boolean = true) { + useDevModeStore.setState({status: 'wip'}) + KaravanApi.startDevModeContainer(projectId, verbose, compile, res => { + useDevModeStore.setState({status: 'none'}) + if (res.status === 200 || res.status === 201) { + LogsEventBus.sendLog('set', ''); + useLogStore.setState({podName: res.data}) + useProjectStore.setState({tabIndex: 'log'}) + } else { + const resData = (res as any)?.response?.data; + const error = resData?.message ? resData?.message : res.statusText; + EventBus.sendAlert('Error Starting DevMode container', error, 'warning') + } + }); + } + + public static reloadDevModeCode(project: Project) { + useDevModeStore.setState({status: 'wip'}) + KaravanApi.reloadDevModeCode(project.projectId, res => { + useDevModeStore.setState({status: 'none'}) + if (res.status === 200 || res.status === 201) { + // setIsReloadingPod(false); + } else { + EventBus.sendAlert('Error Reloading DevMode container', res.statusText, 'warning') + } + }); + } + + public static stopDevModeContainer(projectId: string) { + useDevModeStore.setState({status: 'wip'}) + KaravanApi.manageContainer(projectId, 'devmode', projectId, 'stop', 'never', res => { + useDevModeStore.setState({status: 'none'}) + if (res.status === 200) { + } else { + EventBus.sendAlert('Error stopping DevMode container', res.statusText, 'warning') + } + }); + } + + public static pauseDevModeContainer(project: Project) { + useDevModeStore.setState({status: 'wip'}) + KaravanApi.manageContainer(project.projectId, 'devmode', project.projectId, 'pause', 'never', res => { + useDevModeStore.setState({status: 'none'}) + if (res.status === 200) { + } else { + EventBus.sendAlert('Error stopping DevMode container', res.statusText, 'warning') + } + }); + } + + public static deleteDevModeContainer(projectId: string) { + useDevModeStore.setState({status: 'wip'}) + LogsEventBus.sendLog('set', ''); + KaravanApi.deleteDevModeContainer(projectId, false, res => { + useDevModeStore.setState({status: 'none'}) + if (res.status === 202) { + } else { + EventBus.sendAlert('Error delete runner', res.statusText, 'warning') + } + }); + } + + public static pushProject(project: Project, commitMessage: string, selectedFileNames: string[]) { + const params = { + 'projectId': project.projectId, + 'message': commitMessage, + 'userId': getCurrentUser()?.username, + 'fileNames': selectedFileNames.join(","), + }; + KaravanApi.push(params, res => { + if (res.status === 200 || res.status === 201) { + // ProjectService.refreshProject(project.projectId); + // ProjectService.refreshProjectData(project.projectId); + } else { + EventBus.sendAlert("Error pushing", (res as any)?.response?.data, 'danger') + } + }); + } + + public static pullProject(projectId: string) { + useProjectStore.setState({isPulling: true}) + KaravanApi.pull(projectId, res => { + if (res.status === 200 || res.status === 201) { + useProjectStore.setState({isPulling: false}) + ProjectService.refreshProject(projectId); + ProjectService.refreshProjectData(projectId); + } else { + EventBus.sendAlert("Error pulling", (res as any)?.response?.data, 'danger') + } + useProjectStore.setState({isPulling: false}) + }); + } + + public static pullAllProjects() { + useProjectStore.setState({isPulling: true}) + KaravanApi.pull(undefined, res => { + if (res.status === 200 || res.status === 201) { + useProjectStore.setState({isPulling: false}) + ProjectService.refreshProjects(); + } else { + EventBus.sendAlert("Error pulling", (res as any)?.response?.data, 'danger') + } + useProjectStore.setState({isPulling: false}) + }); + } + + static afterKameletsLoad(yamls: string, saveCamelKamelets: (kameletYamls: string[], clean?: boolean) => void): void { + try { + const kamelets: string[] = []; + yamls.split(/\n?---\n?/).map(c => c.trim()).forEach(z => kamelets.push(z)); + saveCamelKamelets(kamelets, true); + } catch (e: any) { + console.error(e); + EventBus.sendAlert("Error updating kamelets", e?.message, 'danger'); + } + } + + public static loadCustomKamelets() { + KaravanApi.getCustomKamelets(yaml => ProjectService.afterKameletsLoad(yaml, KameletApi.saveCustomKamelets)); + } + + public static loadBlockedComponentAndKamelets() { + KaravanApi.getFiles(ProjectType.configuration, (files: ProjectFile[]) => { + const c = files.find(f => f.name === COMPONENTS_BLACK_LIST); + const listC = c?.code?.split('\n') || []; + ComponentApi.saveBlockedComponentNames(listC); + }); + } + + public static updateFile(file: ProjectFile, active: boolean, updateFilesStore: boolean = true, after?: (res: AxiosResponse<any>) => void) { + KaravanApi.putProjectFile(file, res => { + if (res.status === 200) { + const newFile = res.data; + if (updateFilesStore) { + useFilesStore.getState().upsertFile(newFile); + } + if (active) { + useFileStore.setState({file: newFile}); + } + after?.(res) + } else { + // console.log(res) //TODO show notification + } + }) + } + + public static renameFile(projectId: string, oldName: string, newName: string, after: (res: boolean) => void) { + KaravanApi.renameProjectFile(projectId, oldName, newName, (result: boolean, err?: Error | undefined) => { + if (result) { + after(result); + } else { + EventBus.sendAlert("Error", err?.message ?? "Error copying file!", "warning"); + } + }) + } + + public static refreshProject(projectId: string) { + KaravanApi.getProject(projectId, (project: Project) => { + useProjectStore.setState({project: project}); + unstable_batchedUpdates(() => { + useProjectsStore.getState().upsertProject(project); + }) + }); + } + + public static refreshProjects() { + KaravanApi.getProjects((projects: Project[]) => { + useProjectsStore.setState({projects: projects}); + }); + } + + public static refreshAllContainerStatuses() { + KaravanApi.getAllContainerStatuses((statuses: ContainerStatus[]) => { + useContainerStatusesStore.setState({containers: statuses}); + }); + } + + public static refreshAllCamelContextStatuses() { + KaravanApi.getAllCamelStatuses("context",(statuses: CamelStatus[]) => { + useStatusesStore.setState({camelContexts: statuses}); + }); + } + + public static refreshAllCamelRouteStatuses() { + KaravanApi.getAllCamelStatuses("route",(statuses: CamelStatus[]) => { + useStatusesStore.setState({routes: statuses}); + }); + } + + public static refreshAllCamelProcessorStatuses() { + KaravanApi.getAllCamelStatuses("processor",(statuses: CamelStatus[]) => { + useStatusesStore.setState({processors: statuses}); + }); + } + public static refreshAllCamelConsumerStatuses() { + KaravanApi.getAllCamelStatuses("consumer",(statuses: CamelStatus[]) => { + useStatusesStore.setState({consumers: statuses}); + }); + } + + public static refreshCamelTraces(projectId: string) { + KaravanApi.getProjectCamelTraces(projectId, res => { + if (res.status === 200) { + useProjectStore.setState({camelTraces: res.data}) + } else { + useProjectStore.setState({camelTraces: []}) + } + }) + } + + public static refreshImages(projectId: string) { + KaravanApi.getImages(projectId, (res: ContainerImage[]) => { + useProjectStore.setState({images: res}); + }); + } + + public static deleteProject(project: Project, deleteContainers?: boolean) { + KaravanApi.deleteProject(project, deleteContainers === true, res => { + if (res.status === 204) { + EventBus.sendAlert('Success', 'Project deleted', 'success'); + ProjectService.refreshProjects(); + } else { + EventBus.sendAlert('Warning', 'Error when deleting project:' + res.statusText, 'warning'); + } + }); + } + + public static deleteFile(file: ProjectFile) { + KaravanApi.deleteProjectFile(file, res => { + if (res.status === 204) { + ProjectService.refreshProjectData(file.projectId); + } else { + } + }); + } + + public static refreshProjectFiles(projectId: string) { + KaravanApi.getFiles(projectId, (files: ProjectFile[]) => { + const kameletsYamls = files.filter(f => f.name.endsWith('.kamelet.yaml') && f.code?.length > 0).map(f => f.code).join("\n---\n") + if (kameletsYamls && kameletsYamls.length > 0) { + ProjectService.afterKameletsLoad(kameletsYamls, KameletApi.saveProjectKamelets); + } + useFilesStore.setState({files: files}); + }); + + useFilesStore.getState().fetchCommitedFiles(projectId); + + KaravanApi.getFilesDiff(projectId, (diff: any) => { + useFilesStore.setState({diff: diff}); + }); + } + + public static refreshProjectData(projectId: string) { + KaravanApi.getProject(projectId, (project: Project) => { + // ProjectEventBus.selectProject(project); + KaravanApi.getFiles(ProjectType.templates, (files: ProjectFile[]) => { + files.filter(f => f.name.endsWith('java')) + .forEach(f => { + const name = f.name.replace(".java", ''); + TemplateApi.saveTemplate(name, f.code); + }) + }); + }); + KaravanApi.getCustomKamelets(yaml => ProjectService.afterKameletsLoad(yaml, KameletApi.saveCustomKamelets)); + ProjectService.refreshProjectFiles(projectId); + ProjectService.loadBlockedComponentAndKamelets(); + KaravanApi.getConfigMaps((any: []) => InfrastructureAPI.setConfigMaps(any)); + KaravanApi.getSecrets((any: []) => InfrastructureAPI.setSecrets(any)); + KaravanApi.getServices((any: []) => InfrastructureAPI.setServices(any)); + KaravanApi.getImages(projectId, (images: []) => useProjectStore.setState({images: images})); + useFilesStore.getState().fetchCommitedFiles(projectId); + } +} \ No newline at end of file diff --git a/karavan-app/src/main/webui/src/services/SystemService.ts b/karavan-app/src/main/webui/src/services/SystemService.ts new file mode 100644 index 00000000..76e27114 --- /dev/null +++ b/karavan-app/src/main/webui/src/services/SystemService.ts @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import {useSystemStore} from "@stores/SystemStore"; +import {SystemApi} from "@api/SystemApi"; +import {KubernetesConfigMap, KubernetesSecret} from "@models/SystemModels"; + +export class SystemService { + + public static refresh() { + SystemApi.getSecrets((secrets: KubernetesSecret[]) => { + useSystemStore.setState({secrets: [...secrets].sort((a, b) => a.name.localeCompare(b.name))}); + }); + SystemApi.getConfigMaps((configmaps: KubernetesConfigMap[]) => { + useSystemStore.setState({configmaps: [...configmaps].sort((a, b) => a.name.localeCompare(b.name))}); + }); + SystemApi.getEnvVars((envVars: string[]) => { + useSystemStore.setState({envVars: [...envVars].sort()}); + }); + SystemApi.getAppProps((appProps: string[]) => { + useSystemStore.setState({appProps: [...appProps].sort()}); + }); + } +} \ No newline at end of file
