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
commit f91a11255e8a7f95dc63c087c1ad634a9cc19e56 Author: Marat Gubaidullin <[email protected]> AuthorDate: Fri Aug 18 14:33:01 2023 -0400 Fix #865 --- .../camel/karavan/api/InfrastructureResource.java | 10 +- .../apache/camel/karavan/api/StatusResource.java | 17 +- .../src/main/resources/services/devservices.yaml | 15 +- .../src/main/webui/src/api/KaravanApi.tsx | 6 +- .../src/main/webui/src/api/ProjectStore.ts | 67 ++++- .../src/main/webui/src/dashboard/DashboardPage.tsx | 66 +---- .../karavan-app/src/main/webui/src/index.css | 2 +- .../src/main/webui/src/main/DataPoller.tsx | 83 ------ .../karavan-app/src/main/webui/src/main/Main.tsx | 30 +-- .../src/main/webui/src/main/MainDataPoller.tsx | 62 +++++ .../src/main/webui/src/main/MainLogin.tsx | 102 ++++---- .../src/main/webui/src/main/PageNavigation.tsx | 16 +- .../main/webui/src/project/ProjectDataPoller.tsx | 61 +++++ .../src/main/webui/src/project/ProjectPage.tsx | 2 + .../webui/src/project/dashboard/DashboardTab.tsx | 44 +--- .../main/webui/src/project/files/FilesToolbar.tsx | 1 - .../main/webui/src/project/log/ProjectLogPanel.tsx | 2 - .../src/project/pipeline/ProjectPipelineTab.tsx | 4 +- .../webui/src/project/pipeline/ProjectStatus.tsx | 285 +++++++++------------ .../src/project/trace/RunnerInfoTraceModal.tsx | 4 +- .../src/project/trace/RunnerInfoTraceNode.tsx | 4 +- .../src/main/webui/src/project/trace/TraceTab.tsx | 36 +-- .../src/main/webui/src/projects/ProjectsPage.tsx | 38 +-- .../karavan/infinispan/InfinispanService.java | 7 + 24 files changed, 444 insertions(+), 520 deletions(-) diff --git a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/InfrastructureResource.java b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/InfrastructureResource.java index 478205b5..931aa2c5 100644 --- a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/InfrastructureResource.java +++ b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/InfrastructureResource.java @@ -156,9 +156,13 @@ public class InfrastructureResource { @Produces(MediaType.APPLICATION_JSON) @Path("/service") public List<ServiceStatus> getAllServiceStatuses() throws Exception { - return infinispanService.getServiceStatuses().stream() - .sorted(Comparator.comparing(ServiceStatus::getProjectId)) - .collect(Collectors.toList()); + if (infinispanService.isReady()) { + return infinispanService.getServiceStatuses().stream() + .sorted(Comparator.comparing(ServiceStatus::getProjectId)) + .collect(Collectors.toList()); + } else { + return List.of(); + } } @GET diff --git a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/StatusResource.java b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/StatusResource.java index 3a584f36..b4dcd21d 100644 --- a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/StatusResource.java +++ b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/StatusResource.java @@ -44,13 +44,12 @@ public class StatusResource { @GET @Produces(MediaType.APPLICATION_JSON) - @Path("/pipeline/{projectId}/{env}") - public Response getPipelineStatus(@PathParam("projectId") String projectId, @PathParam("env") String env) { - PipelineStatus status = infinispanService.getPipelineStatus(projectId, env); - if (status != null) { - return Response.ok(status).build(); + @Path("/pipeline/{env}") + public List<PipelineStatus> getPipelineStatuses(@PathParam("env") String env) { + if (infinispanService.isReady()) { + return infinispanService.getPipelineStatuses(env); } else { - return Response.noContent().build(); + return List.of(); } } @@ -81,6 +80,10 @@ public class StatusResource { @Produces(MediaType.APPLICATION_JSON) @Path("/camel/{env}") public List<CamelStatus> getCamelStatusByEnv(@PathParam("env") String env) { - return infinispanService.getCamelStatusesByEnv(env, CamelStatus.Name.context); + if (infinispanService.isReady()) { + return infinispanService.getCamelStatusesByEnv(env, CamelStatus.Name.context); + } else { + return List.of(); + } } } \ No newline at end of file diff --git a/karavan-web/karavan-app/src/main/resources/services/devservices.yaml b/karavan-web/karavan-app/src/main/resources/services/devservices.yaml index 1365fd9a..e921cd22 100644 --- a/karavan-web/karavan-app/src/main/resources/services/devservices.yaml +++ b/karavan-web/karavan-app/src/main/resources/services/devservices.yaml @@ -41,17 +41,20 @@ services: KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 - mariadb: - image: mariadb + postgres: + image: postgres:15.4 restart: always environment: - MARIADB_ROOT_PASSWORD: mariadb + - POSTGRES_USER=postgres + - POSTGRES_PASSWORD=postgres + ports: + - '5432:5432' - mariadb-adminer: - image: adminer + adminer: + image: adminer:4.8.1-standalone restart: always ports: - - 9080:8080 + - 8080:8080 greenmail: container_name: greenmail diff --git a/karavan-web/karavan-app/src/main/webui/src/api/KaravanApi.tsx b/karavan-web/karavan-app/src/main/webui/src/api/KaravanApi.tsx index 43919f12..cd2213f2 100644 --- a/karavan-web/karavan-app/src/main/webui/src/api/KaravanApi.tsx +++ b/karavan-web/karavan-app/src/main/webui/src/api/KaravanApi.tsx @@ -155,13 +155,13 @@ export class KaravanApi { }); } - static async getProjectPipelineStatus(projectId: string, env: string, after: (status?: PipelineStatus) => void) { - instance.get('/api/status/pipeline/' + projectId + "/" + env) + static async getPipelineStatuses(env: string, after: (status: PipelineStatus[]) => void) { + instance.get('/api/status/pipeline/' + env) .then(res => { if (res.status === 200) { after(res.data); } else if (res.status === 204) { - after(undefined); + after([]); } }).catch(err => { diff --git a/karavan-web/karavan-app/src/main/webui/src/api/ProjectStore.ts b/karavan-web/karavan-app/src/main/webui/src/api/ProjectStore.ts index 651c1071..ed7f1acc 100644 --- a/karavan-web/karavan-app/src/main/webui/src/api/ProjectStore.ts +++ b/karavan-web/karavan-app/src/main/webui/src/api/ProjectStore.ts @@ -16,16 +16,32 @@ */ import {create} from 'zustand' -import {AppConfig, DeploymentStatus, ContainerStatus, Project, ProjectFile, ServiceStatus, CamelStatus} from "./ProjectModels"; +import { + AppConfig, + DeploymentStatus, + ContainerStatus, + Project, + ProjectFile, + ServiceStatus, + CamelStatus, + PipelineStatus +} from "./ProjectModels"; import {ProjectEventBus} from "./ProjectEventBus"; import {unstable_batchedUpdates} from "react-dom"; +import {useState} from "react"; interface AppConfigState { + loading: boolean; + setLoading: (loading: boolean) => void; config: AppConfig; setConfig: (config: AppConfig) => void; } export const useAppConfigStore = create<AppConfigState>((set) => ({ + loading: false, + setLoading: (loading: boolean) => { + set({loading: loading}) + }, config: new AppConfig(), setConfig: (config: AppConfig) => { set({config: config}) @@ -56,12 +72,22 @@ export const useProjectsStore = create<ProjectsState>((set) => ({ })) interface ProjectState { - project: Project; isPushing: boolean, isRunning: boolean, - operation: "create" | "select" | "delete" | "none" | "copy"; + project: Project; setProject: (project: Project, operation: "create" | "select" | "delete"| "none" | "copy") => void; + operation: "create" | "select" | "delete" | "none" | "copy"; setOperation: (o: "create" | "select" | "delete"| "none" | "copy") => void; + memory: any, + setMemory: (memory: any) => void; + jvm: any, + setJvm: (jvm: any) => void; + context: any, + setContext: (context: any) => void; + trace: any, + setTrace: (trace: any) => void; + refreshTrace: boolean + setRefreshTrace: (refreshTrace: boolean) => void; } export const useProjectStore = create<ProjectState>((set) => ({ @@ -72,7 +98,12 @@ export const useProjectStore = create<ProjectState>((set) => ({ setProject: (project: Project, operation: "create" | "select" | "delete"| "none" | "copy") => { set((state: ProjectState) => ({ project: project, - operation: operation + operation: operation, + refreshTrace: false, + jvm: {}, + context: {}, + trace: {}, + memory: {}, })); }, setOperation: (o: "create" | "select" | "delete"| "none" | "copy") => { @@ -80,6 +111,26 @@ export const useProjectStore = create<ProjectState>((set) => ({ operation: o })); }, + memory: {}, + setMemory: (memory: boolean) => { + set({memory: memory}) + }, + jvm: {}, + setJvm: (jvm: boolean) => { + set({jvm: jvm}) + }, + context: {}, + setContext: (context: boolean) => { + set({context: context}) + }, + trace: {}, + setTrace: (trace: boolean) => { + set({trace: trace}) + }, + refreshTrace: false, + setRefreshTrace: (refreshTrace: boolean) => { + set({refreshTrace: refreshTrace}) + }, })) interface FilesState { @@ -170,6 +221,8 @@ interface StatusesState { setServices: (s: ServiceStatus[]) => void; setContainers: (c: ContainerStatus[]) => void; setCamels: (c: CamelStatus[]) => void; + pipelineStatuses: PipelineStatus[], + setPipelineStatuses: (pipelineStatus: PipelineStatus[]) => void; } export const useStatusesStore = create<StatusesState>((set) => ({ @@ -196,7 +249,11 @@ export const useStatusesStore = create<StatusesState>((set) => ({ set((state: StatusesState) => ({ camels: c, })); - } + }, + pipelineStatuses: [], + setPipelineStatuses: (pipelineStatuses: PipelineStatus[]) => { + set({pipelineStatuses: pipelineStatuses}) + }, })) interface LogState { diff --git a/karavan-web/karavan-app/src/main/webui/src/dashboard/DashboardPage.tsx b/karavan-web/karavan-app/src/main/webui/src/dashboard/DashboardPage.tsx index 9b54deba..c032a31b 100644 --- a/karavan-web/karavan-app/src/main/webui/src/dashboard/DashboardPage.tsx +++ b/karavan-web/karavan-app/src/main/webui/src/dashboard/DashboardPage.tsx @@ -1,4 +1,4 @@ -import React, {useEffect, useState} from 'react'; +import React, {useState} from 'react'; import { Badge, Bullseye, Button, EmptyState, EmptyStateIcon, EmptyStateVariant, @@ -26,11 +26,9 @@ import { Table } from '@patternfly/react-table/deprecated'; import {camelIcon, CamelUi} from "../designer/utils/CamelUi"; -import {KaravanApi} from "../api/KaravanApi"; import Icon from "../Logo"; import UpIcon from "@patternfly/react-icons/dist/esm/icons/check-circle-icon"; import DownIcon from "@patternfly/react-icons/dist/esm/icons/error-circle-o-icon"; -import RefreshIcon from "@patternfly/react-icons/dist/esm/icons/sync-alt-icon"; import SearchIcon from "@patternfly/react-icons/dist/esm/icons/search-icon"; import {MainToolbar} from "../designer/MainToolbar"; import {useAppConfigStore, useProjectsStore, useStatusesStore} from "../api/ProjectStore"; @@ -44,51 +42,8 @@ export const DashboardPage = () => { = useStatusesStore((state) => [state.deployments, state.services, state.containers, state.camels, state.setDeployments, state.setServices, state.setContainers, state.setCamels], shallow); const [filter, setFilter] = useState<string>(''); - const [loading, setLoading] = useState<boolean>(true); const [selectedEnv, setSelectedEnv] = useState<string[]>([config.environment]); - useEffect(() => { - const interval = setInterval(() => { - onGetProjects() - }, 1300); - return () => { - clearInterval(interval) - }; - }, []); - - function onGetProjects() { - KaravanApi.getConfiguration((config: any) => { - KaravanApi.getProjects((projects: Project[]) => { - setProjects(projects); - }); - KaravanApi.getAllDeploymentStatuses((statuses: DeploymentStatus[]) => { - setDeployments(statuses); - }); - KaravanApi.getAllServiceStatuses((statuses: ServiceStatus[]) => { - setServices(statuses); - }); - KaravanApi.getAllContainerStatuses((statuses: ContainerStatus[]) => { - setContainers(statuses); - }); - selectedEnv.forEach(env => { - KaravanApi.getAllCamelStatuses(env, (statuses: CamelStatus[]) => { - setCamels(statuses); - setLoading(false); - // setState((state) => { - // statuses.forEach(newStatus => { - // const index = state.camelStatuses.findIndex(s => s.projectId === newStatus.projectId && s.env === newStatus.env); - // if (index !== -1) { - // state.camelStatuses.splice(index, 1); - // } - // state.camelStatuses.push(newStatus); - // }) - // return state; - // }) - }); - }); - }); - } - function selectEnvironment(name: string, selected: boolean) { if (selected && !selectedEnv.includes(name)) { setSelectedEnv((state: string[]) => { @@ -105,9 +60,9 @@ export const DashboardPage = () => { function tools() { return (<Toolbar id="toolbar-group-types"> <ToolbarContent> - <ToolbarItem> - <Button variant="link" icon={<RefreshIcon/>} onClick={e => onGetProjects()}/> - </ToolbarItem> + {/*<ToolbarItem>*/} + {/* <Button variant="link" icon={<RefreshIcon/>} onClick={e => onGetProjects()}/>*/} + {/*</ToolbarItem>*/} <ToolbarItem> <ToggleGroup aria-label="Default with single selectable"> {config.environments.map(env => ( @@ -170,9 +125,9 @@ export const DashboardPage = () => { } function getCamelStatusByEnvironments(name: string): [string, CamelStatus | undefined] [] { - return getSelectedEnvironments().map(e => { + return selectedEnv.map(e => { const env: string = e as string; - const status = camels.find(d => d.projectId === name && d.env === env); + const status = camels?.find(d => d.projectId === name && d.env === env); return [env, status]; }); } @@ -222,12 +177,9 @@ export const DashboardPage = () => { <Tr> <Td colSpan={8}> <Bullseye> - {loading && <Spinner className="progress-stepper" diameter="80px" aria-label="Loading..."/>} - {!loading && - <EmptyState variant={EmptyStateVariant.sm}> - <EmptyStateHeader titleText="No results found" icon={<EmptyStateIcon icon={SearchIcon}/>} headingLevel="h2" /> - </EmptyState> - } + <EmptyState variant={EmptyStateVariant.sm}> + <EmptyStateHeader titleText="No results found" icon={<EmptyStateIcon icon={SearchIcon}/>} headingLevel="h2" /> + </EmptyState> </Bullseye> </Td> </Tr> diff --git a/karavan-web/karavan-app/src/main/webui/src/index.css b/karavan-web/karavan-app/src/main/webui/src/index.css index c013e3fd..d7ee69a5 100644 --- a/karavan-web/karavan-app/src/main/webui/src/index.css +++ b/karavan-web/karavan-app/src/main/webui/src/index.css @@ -28,7 +28,7 @@ .karavan .nav-buttons .logo { margin-top: 16px; - margin-bottom: 10px; + margin-bottom: 16px; width: 32px; height: 32px; } diff --git a/karavan-web/karavan-app/src/main/webui/src/main/DataPoller.tsx b/karavan-web/karavan-app/src/main/webui/src/main/DataPoller.tsx deleted file mode 100644 index 3b917172..00000000 --- a/karavan-web/karavan-app/src/main/webui/src/main/DataPoller.tsx +++ /dev/null @@ -1,83 +0,0 @@ -import React, {useEffect} from 'react'; - -import {KaravanApi} from "../api/KaravanApi"; -import {KameletApi} from "karavan-core/lib/api/KameletApi"; -import '../designer/karavan.css'; -import {ComponentApi} from "karavan-core/lib/api/ComponentApi"; -import {AppConfig, ContainerStatus} from "../api/ProjectModels"; -import {useAppConfigStore, useStatusesStore} from "../api/ProjectStore"; -import {InfrastructureAPI} from "../designer/utils/InfrastructureAPI"; -import {shallow} from "zustand/shallow"; - -export const DataPoller = () => { - - const [setConfig] = useAppConfigStore((state) => [state.setConfig], shallow) - const [setContainers] = useStatusesStore((state) => [state.setContainers], shallow); - - useEffect(() => { - console.log("DataPoller Start"); - const interval = setInterval(() => { - getStatuses(); - }, 1000); - return () => { - console.log("DataPoller End"); - clearInterval(interval); - }; - }, []); - - function getStatuses() { - if (KaravanApi.isAuthorized || KaravanApi.authType === 'public') { - KaravanApi.getAllContainerStatuses((statuses: ContainerStatus[]) => { - setContainers(statuses); - }); - } - } - - function getData() { - if (KaravanApi.isAuthorized || KaravanApi.authType === 'public') { - KaravanApi.getConfiguration((config: AppConfig) => { - setConfig(config); - InfrastructureAPI.infrastructure = config.infrastructure; - }); - updateKamelets(); - updateComponents(); - // updateSupportedComponents(); // not implemented yet - } - } - - async function updateKamelets(): Promise<void> { - await new Promise(resolve => { - KaravanApi.getKamelets(yamls => { - const kamelets: string[] = []; - yamls.split("\n---\n").map(c => c.trim()).forEach(z => kamelets.push(z)); - KameletApi.saveKamelets(kamelets, true); - }) - KaravanApi.getCustomKameletNames(names => { - KameletApi.saveCustomKameletNames(names); - }) - }); - } - - async function updateComponents(): Promise<void> { - await new Promise(resolve => { - KaravanApi.getComponents(code => { - const components: [] = JSON.parse(code); - const jsons: string[] = []; - components.forEach(c => jsons.push(JSON.stringify(c))); - ComponentApi.saveComponents(jsons, true); - }) - }); - } - - async function updateSupportedComponents(): Promise<void> { - await new Promise(resolve => { - KaravanApi.getSupportedComponents(jsons => { - ComponentApi.saveSupportedComponents(jsons); - }) - }); - } - - return ( - <React.Fragment></React.Fragment> - ) -} \ No newline at end of file diff --git a/karavan-web/karavan-app/src/main/webui/src/main/Main.tsx b/karavan-web/karavan-app/src/main/webui/src/main/Main.tsx index 3cf72154..7a3fa986 100644 --- a/karavan-web/karavan-app/src/main/webui/src/main/Main.tsx +++ b/karavan-web/karavan-app/src/main/webui/src/main/Main.tsx @@ -1,11 +1,9 @@ -import {Routes, Route, useNavigate, Navigate} from 'react-router-dom'; -import {useLocation} from 'react-router-dom'; -import React, {useEffect, useRef, useState} from "react"; +import {Routes, Route, Navigate} from 'react-router-dom'; +import React, {useEffect, useRef} from "react"; import {KaravanApi} from "../api/KaravanApi"; import {Bullseye, Flex, FlexItem, Page, Spinner} from "@patternfly/react-core"; import Icon from "../Logo"; import {MainLogin} from "./MainLogin"; -import {Notification} from "./Notification"; import {DashboardPage} from "../dashboard/DashboardPage"; import {ProjectsPage} from "../projects/ProjectsPage"; import {ProjectPage} from "../project/ProjectPage"; @@ -13,12 +11,14 @@ import {ServicesPage} from "../services/ServicesPage"; import {ContainersPage} from "../containers/ContainersPage"; import {KnowledgebasePage} from "../knowledgebase/KnowledgebasePage"; import {ProjectEventBus} from "../api/ProjectEventBus"; -import {Project, ToastMessage} from "../api/ProjectModels"; +import {ToastMessage} from "../api/ProjectModels"; import {SsoApi} from "../api/SsoApi"; -import {useAppConfigStore, useStatusesStore} from "../api/ProjectStore"; +import {useAppConfigStore} from "../api/ProjectStore"; import {shallow} from "zustand/shallow"; import {PageNavigation} from "./PageNavigation"; +import {Notification} from "./Notification"; import {useMainHook} from "./useMainHook"; +import {MainDataPoller} from "./MainDataPoller"; export const Main = () => { @@ -36,9 +36,6 @@ export const Main = () => { function effect() { console.log("Main Start"); - const interval = setInterval(() => { - getStatuses(); - }, 1000); KaravanApi.getAuthType((authType: string) => { console.log("authType", authType); if (authType === 'oidc') { @@ -52,19 +49,9 @@ export const Main = () => { }); return () => { console.log("Main End"); - clearInterval(interval); }; } - function onLogin(username: string, password: string) { - KaravanApi.auth(username, password, (res: any) => { - if (res?.status === 200) { - getData(); - } else { - toast("Error", "Incorrect username and/or password!", "danger"); - } - }); - } function toast(title: string, text: string, variant: 'success' | 'danger' | 'warning' | 'info' | 'custom') { ProjectEventBus.sendAlert(new ToastMessage(title, text, variant)) @@ -97,8 +84,9 @@ export const Main = () => { </Flex> } {!KaravanApi.isAuthorized && KaravanApi.authType === 'basic' && - <MainLogin config={config} onLogin={onLogin}/>} - {/*<Notification/>*/} + <MainLogin/>} + <Notification/> + <MainDataPoller/> </Page> ); }; diff --git a/karavan-web/karavan-app/src/main/webui/src/main/MainDataPoller.tsx b/karavan-web/karavan-app/src/main/webui/src/main/MainDataPoller.tsx new file mode 100644 index 00000000..02f9301c --- /dev/null +++ b/karavan-web/karavan-app/src/main/webui/src/main/MainDataPoller.tsx @@ -0,0 +1,62 @@ +import React, {useEffect, useState} from 'react'; + +import {KaravanApi} from "../api/KaravanApi"; +import '../designer/karavan.css'; +import { + AppConfig, + CamelStatus, + ContainerStatus, + DeploymentStatus, + PipelineStatus, + Project, + ServiceStatus +} from "../api/ProjectModels"; +import {useAppConfigStore, useProjectsStore, useStatusesStore} from "../api/ProjectStore"; +import {shallow} from "zustand/shallow"; + +export const MainDataPoller = () => { + + const [config, setLoading] = useAppConfigStore((state) => [state.config, state.setLoading], shallow) + const [projects, setProjects] = useProjectsStore((state) => [state.projects, state.setProjects], shallow) + const [deployments, services, containers, camels, setDeployments, setServices, setContainers, setCamels, setPipelineStatuses] + = useStatusesStore((s) => [s.deployments, s.services, s.containers, s.camels, + s.setDeployments, s.setServices, s.setContainers, s.setCamels, s.setPipelineStatuses], shallow); + + useEffect(() => { + console.log("MainDataPoller Start"); + const interval = setInterval(() => { + getData(); + }, 1300); + return () => { + console.log("MainDataPoller Stop"); + clearInterval(interval); + }; + }, []); + + function getData() { + setLoading(true); + KaravanApi.getConfiguration((config: AppConfig) => { + KaravanApi.getProjects((projects: Project[]) => { + setProjects(projects); + }); + KaravanApi.getAllDeploymentStatuses((statuses: DeploymentStatus[]) => { + setDeployments(statuses); + }); + KaravanApi.getAllServiceStatuses((statuses: ServiceStatus[]) => { + setServices(statuses); + }); + KaravanApi.getAllContainerStatuses((statuses: ContainerStatus[]) => { + setContainers(statuses); + }); + KaravanApi.getAllCamelStatuses(config.environment, (statuses: CamelStatus[]) => { + setCamels(statuses); + }); + KaravanApi.getPipelineStatuses(config.environment, (status: PipelineStatus[]) => { + setPipelineStatuses(status); + }); + setLoading(false); + }); + } + + return (<></>) +} \ No newline at end of file diff --git a/karavan-web/karavan-app/src/main/webui/src/main/MainLogin.tsx b/karavan-web/karavan-app/src/main/webui/src/main/MainLogin.tsx index 40426253..a493a963 100644 --- a/karavan-web/karavan-app/src/main/webui/src/main/MainLogin.tsx +++ b/karavan-web/karavan-app/src/main/webui/src/main/MainLogin.tsx @@ -1,61 +1,63 @@ -import React from 'react'; +import React, {useState} from 'react'; import { Bullseye, Card, CardBody, CardTitle, LoginForm, Text } from '@patternfly/react-core'; +import {KaravanApi} from "../api/KaravanApi"; +import {useAppConfigStore} from "../api/ProjectStore"; +import {shallow} from "zustand/shallow"; +import {ProjectEventBus} from "../api/ProjectEventBus"; +import {ToastMessage} from "../api/ProjectModels"; -interface Props { - config: any, - onLogin: (username: string, password: string) => void -} +export const MainLogin = () => { -interface State { - username: string, - password: string, - isValidUsername: boolean, - isValidPassword: boolean, - isRememberMeChecked: boolean, -} + const [config] = useAppConfigStore((state) => [state.config], shallow) + const [username, setUsername] = useState<string>(); + const [password, setPassword] = useState<string>(); + const [isValidUsername, setIsValidUsername] = useState<boolean>(true); + const [isValidPassword, setIsValidPassword] = useState<boolean>(true); + const [isRememberMeChecked, setIsRememberMeChecked] = useState<boolean>(false); -export class MainLogin extends React.Component<Props, State> { - public state: State = { - username: "", - password: "", - isValidUsername: true, - isValidPassword: true, - isRememberMeChecked: false, - } - - onLoginButtonClick = (event: any) => { + function onLoginButtonClick(event: any) { event.preventDefault(); - this.props.onLogin?.call(this, this.state.username, this.state.password); + if (username && password) { + onLogin(username, password); + } } - render() { - return ( - <Bullseye> - <Card isFlat isCompact> - <CardTitle> - <img alt="karavan-logo" src="karavan-logo-light.png" className="login-logo"/> - <Text component="h3" style={{width:"fit-content", marginLeft:"auto"}}>{this.props.config.version}</Text> - </CardTitle> - <CardBody> - <LoginForm - showHelperText={true} - usernameLabel="Username" - usernameValue={this.state.username} - onChangeUsername={(_event, value) => this.setState({username: value})} - isValidUsername={this.state.isValidUsername} - passwordLabel="Password" - passwordValue={this.state.password} - isShowPasswordEnabled - onChangePassword={(_event, value) => this.setState({password: value})} - isValidPassword={this.state.isValidPassword} - onLoginButtonClick={this.onLoginButtonClick} - loginButtonLabel="Log in" - /> - </CardBody> - </Card> - </Bullseye> - ); + function onLogin(username: string, password: string) { + KaravanApi.auth(username, password, (res: any) => { + if (res?.status === 200) { + } else { + ProjectEventBus.sendAlert(new ToastMessage("Error", "Incorrect username and/or password!", "danger")) + } + }); } + + return ( + <Bullseye> + <Card isFlat isCompact> + <CardTitle> + <img alt="karavan-logo" src="karavan-logo-light.png" className="login-logo"/> + <Text component="h3" + style={{width: "fit-content", marginLeft: "auto"}}>{config.version}</Text> + </CardTitle> + <CardBody> + <LoginForm + showHelperText={true} + usernameLabel="Username" + usernameValue={username} + onChangeUsername={(_event, value) => setUsername(value)} + isValidUsername={isValidUsername} + passwordLabel="Password" + passwordValue={password} + isShowPasswordEnabled + onChangePassword={(_event, value) => setPassword(value)} + isValidPassword={isValidPassword} + onLoginButtonClick={onLoginButtonClick} + loginButtonLabel="Log in" + /> + </CardBody> + </Card> + </Bullseye> + ); } \ No newline at end of file diff --git a/karavan-web/karavan-app/src/main/webui/src/main/PageNavigation.tsx b/karavan-web/karavan-app/src/main/webui/src/main/PageNavigation.tsx index 648add20..2c35c44b 100644 --- a/karavan-web/karavan-app/src/main/webui/src/main/PageNavigation.tsx +++ b/karavan-web/karavan-app/src/main/webui/src/main/PageNavigation.tsx @@ -4,7 +4,7 @@ import { Flex, FlexItem, Tooltip, - Divider, Popover, Badge + Divider, Popover, Badge, Spinner, Bullseye } from '@patternfly/react-core'; import {KaravanApi} from "../api/KaravanApi"; import '../designer/karavan.css'; @@ -33,7 +33,7 @@ class MenuItem { export const PageNavigation = () => { - const [config] = useAppConfigStore((state) => [state.config], shallow) + const [config, loading] = useAppConfigStore((state) => [state.config, state.loading], shallow) const [setFile] = useFileStore((state) => [state.setFile], shallow) const [setStatus, setPodName] = useDevModeStore((state) => [state.setStatus, state.setPodName], shallow) const [showUser, setShowUser] = useState<boolean>(false); @@ -58,10 +58,14 @@ export const PageNavigation = () => { return (<Flex className="nav-buttons" direction={{default: "column"}} style={{height: "100%"}} spaceItems={{default: "spaceItemsNone"}}> <FlexItem alignSelf={{default: "alignSelfCenter"}}> - <Tooltip className="logo-tooltip" content={"Apache Camel Karavan " + config.version} - position={"right"}> - {Icon()} - </Tooltip> + <Bullseye> + {loading && <Spinner style={{position: "absolute"}} diameter="40px" aria-label="Loading..."/>} + <Tooltip className="logo-tooltip" content={"Apache Camel Karavan " + config.version} + position={"right"}> + {Icon()} + </Tooltip> + </Bullseye> + </FlexItem> {getMenu().map(page => <FlexItem key={page.pageId} className={pageId === page.pageId ? "nav-button-selected" : ""}> diff --git a/karavan-web/karavan-app/src/main/webui/src/project/ProjectDataPoller.tsx b/karavan-web/karavan-app/src/main/webui/src/project/ProjectDataPoller.tsx new file mode 100644 index 00000000..2b86d831 --- /dev/null +++ b/karavan-web/karavan-app/src/main/webui/src/project/ProjectDataPoller.tsx @@ -0,0 +1,61 @@ +import React, {useEffect} from 'react'; + +import {KaravanApi} from "../api/KaravanApi"; +import '../designer/karavan.css'; +import {useAppConfigStore, useProjectStore} from "../api/ProjectStore"; +import {shallow} from "zustand/shallow"; +import {CamelStatus, DeploymentStatus, PipelineStatus} from "../api/ProjectModels"; + +export const ProjectDataPoller = () => { + + const [config] = useAppConfigStore((state) => [state.config], shallow) + const [project, setMemory, setJvm, setContext, refreshTrace, setTrace] = useProjectStore((s) => + [s.project, s.setMemory, s.setJvm, s.setContext, s.refreshTrace, s.setTrace], shallow); + + useEffect(() => { + console.log("ProjectDataPoller Start"); + const interval = setInterval(() => { + onRefreshStatus(); + }, 1000); + return () => { + console.log("ProjectDataPoller Stop"); + clearInterval(interval) + }; + }, [project, refreshTrace]); + + function onRefreshStatus() { + const projectId = project.projectId; + KaravanApi.getDevModeStatus(projectId, "memory", res => { + if (res.status === 200) { + setMemory(JSON.parse(res.data.status)); + } else { + setMemory({}); + } + }) + KaravanApi.getDevModeStatus(projectId, "jvm", res => { + if (res.status === 200) { + setJvm(JSON.parse(res.data.status)); + } else { + setJvm({}); + } + }) + KaravanApi.getDevModeStatus(projectId, "context", res => { + if (res.status === 200) { + setContext(JSON.parse(res.data.status)); + } else { + setContext({}); + } + }) + if (refreshTrace) { + KaravanApi.getDevModeStatus(projectId, "trace", res => { + if (res.status === 200) { + setTrace(JSON.parse(res.data.status)); + } else { + setTrace({}); + } + }) + } + } + + return (<></>) +} \ No newline at end of file diff --git a/karavan-web/karavan-app/src/main/webui/src/project/ProjectPage.tsx b/karavan-web/karavan-app/src/main/webui/src/project/ProjectPage.tsx index cb6bb96a..a771570c 100644 --- a/karavan-web/karavan-app/src/main/webui/src/project/ProjectPage.tsx +++ b/karavan-web/karavan-app/src/main/webui/src/project/ProjectPage.tsx @@ -14,6 +14,7 @@ import {FileEditor} from "./file/FileEditor"; import {shallow} from "zustand/shallow"; import {useParams} from "react-router-dom"; import {KaravanApi} from "../api/KaravanApi"; +import {ProjectDataPoller} from "./ProjectDataPoller"; export const ProjectPage = () => { @@ -53,6 +54,7 @@ export const ProjectPage = () => { {showFilePanel && <FileEditor/>} {!showFilePanel && <ProjectPanel/>} <ProjectLogPanel/> + <ProjectDataPoller/> </PageSection> ) } diff --git a/karavan-web/karavan-app/src/main/webui/src/project/dashboard/DashboardTab.tsx b/karavan-web/karavan-app/src/main/webui/src/project/dashboard/DashboardTab.tsx index beceb5c8..4a21f9ad 100644 --- a/karavan-web/karavan-app/src/main/webui/src/project/dashboard/DashboardTab.tsx +++ b/karavan-web/karavan-app/src/main/webui/src/project/dashboard/DashboardTab.tsx @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React, {useEffect, useState} from 'react'; +import React from 'react'; import { Card, CardBody, Flex, FlexItem, Divider, PageSection @@ -23,55 +23,19 @@ import '../../designer/karavan.css'; import {InfoContainer} from "./InfoContainer"; import {InfoContext} from "./InfoContext"; import {InfoMemory} from "./InfoMemory"; -import {KaravanApi} from "../../api/KaravanApi"; import {useProjectStore, useStatusesStore} from "../../api/ProjectStore"; import {shallow} from "zustand/shallow"; import {ContainerStatus} from "../../api/ProjectModels"; export const DashboardTab = () => { - const [project] = useProjectStore((state) => [state.project], shallow); + const [project, memory, jvm, context] = useProjectStore((state) => + [state.project, state.memory, state.jvm, state.context], shallow); const [containers] = useStatusesStore((state) => [state.containers], shallow); - const [memory, setMemory] = useState({}); - const [jvm, setJvm] = useState({}); - const [context, setContext] = useState({}); - - useEffect(() => { - const interval = setInterval(() => { - onRefreshStatus(); - }, 1000); - return () => { - clearInterval(interval) - }; - }, []); - - function onRefreshStatus() { - const projectId = project.projectId; - KaravanApi.getDevModeStatus(projectId, "memory", res => { - if (res.status === 200) { - setMemory(JSON.parse(res.data.status)); - } else { - setMemory({}); - } - }) - KaravanApi.getDevModeStatus(projectId, "jvm", res => { - if (res.status === 200) { - setJvm(JSON.parse(res.data.status)); - } else { - setJvm({}); - } - }) - KaravanApi.getDevModeStatus(projectId, "context", res => { - if (res.status === 200) { - setContext(JSON.parse(res.data.status)); - } else { - setContext({}); - } - }) - } const containerStatus = containers.filter(c => c.containerName === project.projectId).at(0); const showConsole = containerStatus?.state === 'running' + return ( <PageSection className="project-tab-panel" padding={{default: "padding"}}> <Card className="project-development"> diff --git a/karavan-web/karavan-app/src/main/webui/src/project/files/FilesToolbar.tsx b/karavan-web/karavan-app/src/main/webui/src/project/files/FilesToolbar.tsx index da56443a..a5ee8418 100644 --- a/karavan-web/karavan-app/src/main/webui/src/project/files/FilesToolbar.tsx +++ b/karavan-web/karavan-app/src/main/webui/src/project/files/FilesToolbar.tsx @@ -39,7 +39,6 @@ export const FileToolbar = () => { useEffect(() => { - console.log("ProjectToolbar useEffect", isPushing, project.lastCommitTimestamp); }, [project, file]); function push () { diff --git a/karavan-web/karavan-app/src/main/webui/src/project/log/ProjectLogPanel.tsx b/karavan-web/karavan-app/src/main/webui/src/project/log/ProjectLogPanel.tsx index ba5e4d7e..c7eec061 100644 --- a/karavan-web/karavan-app/src/main/webui/src/project/log/ProjectLogPanel.tsx +++ b/karavan-web/karavan-app/src/main/webui/src/project/log/ProjectLogPanel.tsx @@ -25,8 +25,6 @@ export const ProjectLogPanel = () => { const [currentPodName, setCurrentPodName] = useState<string | undefined>(undefined); useEffect(() => { - const containerStatus = containers.filter(c => c.containerName === podName).at(0); - console.log("ProjectLogPanel", showLog, type, podName, containerStatus); const controller = new AbortController(); if (showLog && type !== 'none' && podName !== undefined) { const f = KaravanApi.fetchData(type, podName, controller).then(value => { diff --git a/karavan-web/karavan-app/src/main/webui/src/project/pipeline/ProjectPipelineTab.tsx b/karavan-web/karavan-app/src/main/webui/src/project/pipeline/ProjectPipelineTab.tsx index e2209f89..83b8b990 100644 --- a/karavan-web/karavan-app/src/main/webui/src/project/pipeline/ProjectPipelineTab.tsx +++ b/karavan-web/karavan-app/src/main/webui/src/project/pipeline/ProjectPipelineTab.tsx @@ -14,8 +14,8 @@ export const ProjectPipelineTab = () => { <PageSection className="project-tab-panel" padding={{default: "padding"}}> <div className="project-operations"> {/*{["dev", "test", "prod"].map(env =>*/} - {["dev"].map(env => - <ProjectStatus key={env} project={project} config={config} env={env}/> + {config.environments.map(env => + <ProjectStatus env={env}/> )} </div> </PageSection> diff --git a/karavan-web/karavan-app/src/main/webui/src/project/pipeline/ProjectStatus.tsx b/karavan-web/karavan-app/src/main/webui/src/project/pipeline/ProjectStatus.tsx index ae022589..698621b8 100644 --- a/karavan-web/karavan-app/src/main/webui/src/project/pipeline/ProjectStatus.tsx +++ b/karavan-web/karavan-app/src/main/webui/src/project/pipeline/ProjectStatus.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, {useState} from 'react'; import { Button, DescriptionList, @@ -14,116 +14,76 @@ import UpIcon from "@patternfly/react-icons/dist/esm/icons/check-circle-icon"; import DownIcon from "@patternfly/react-icons/dist/esm/icons/error-circle-o-icon"; import ClockIcon from "@patternfly/react-icons/dist/esm/icons/clock-icon"; import DeleteIcon from "@patternfly/react-icons/dist/esm/icons/times-circle-icon"; -import {CamelStatus, DeploymentStatus, PipelineStatus, ContainerStatus, Project} from "../../api/ProjectModels"; -import {useLogStore} from "../../api/ProjectStore"; +import {CamelStatus, DeploymentStatus, ContainerStatus} from "../../api/ProjectModels"; +import {useLogStore, useProjectStore, useStatusesStore} from "../../api/ProjectStore"; +import {shallow} from "zustand/shallow"; interface Props { - project: Project, - config: any, env: string, } -interface State { - pipelineStatus?: PipelineStatus, - deploymentStatus?: DeploymentStatus, - podStatuses: ContainerStatus[], - camelStatus?: CamelStatus, - isPushing: boolean, - isBuilding: boolean, - isRolling: boolean, - showDeleteConfirmation: boolean, - deleteEntity?: 'pod' | 'deployment' | 'pipelinerun', - deleteEntityName?: string, - deleteEntityEnv?: string, -} - -export class ProjectStatus extends React.Component<Props, State> { - - public state: State = { - podStatuses: [], - isPushing: false, - isBuilding: false, - isRolling: false, - showDeleteConfirmation: false, - }; - interval: any; - - componentDidMount() { - this.interval = setInterval(() => this.onRefreshStatus(), 700); - } - - componentWillUnmount() { - clearInterval(this.interval); - } +export const ProjectStatus = (props: Props) => { - onRefreshStatus = () => { - const projectId = this.props.project.projectId; - const env = this.props.env; - if (this.props.project) { - KaravanApi.getProjectPipelineStatus(projectId, env, (status?: PipelineStatus) => { - this.setState({pipelineStatus: status}); - }); - KaravanApi.getProjectDeploymentStatus(projectId, env, (status?: DeploymentStatus) => { - this.setState({deploymentStatus: status}); - }); - KaravanApi.getProjectPodStatuses(projectId, env, (statuses: ContainerStatus[]) => { - this.setState({podStatuses: statuses}); - }); - KaravanApi.getProjectCamelStatus(projectId, env, (status: CamelStatus) => { - this.setState({camelStatus: status}); - }); - } - } + const [project] = useProjectStore((s) => [s.project], shallow); + const [containers, deployments, camels, pipelineStatuses] = + useStatusesStore((s) => [s.containers, s.deployments, s.camels, s.pipelineStatuses], shallow); + const [isPushing, setIsPushing] = useState<boolean>(false); + const [isBuilding, setIsBuilding] = useState<boolean>(false); + const [isRolling, setIsRolling] = useState<boolean>(false); + const [showDeleteConfirmation, setShowDeleteConfirmation] = useState<boolean>(false); + const [deleteEntityType, setDeleteEntityType] = useState<'pod' | 'deployment' | 'pipelinerun'>('pod'); + const [deleteEntityName, setDeleteEntityName] = useState<string>(); + const [deleteEntityEnv, setDeleteEntityEnv] = useState<string>(); - deleteEntity = (type: 'pod' | 'deployment' | 'pipelinerun', name: string, environment: string) => { + function deleteEntity (type: 'pod' | 'deployment' | 'pipelinerun', name: string, environment: string) { switch (type) { case "deployment": KaravanApi.deleteDeployment(environment, name, (res: any) => { // if (Array.isArray(res) && Array.from(res).length > 0) - // this.onRefresh(); + // onRefresh(); }); break; case "pod": KaravanApi.deleteContainer(environment, 'project', name, (res: any) => { // if (Array.isArray(res) && Array.from(res).length > 0) - // this.onRefresh(); + // onRefresh(); }); break; case "pipelinerun": KaravanApi.stopPipelineRun(environment, name, (res: any) => { // if (Array.isArray(res) && Array.from(res).length > 0) - // this.onRefresh(); + // onRefresh(); }); break; } } - build = () => { - this.setState({isBuilding: true}); - KaravanApi.pipelineRun(this.props.project, this.props.env, res => { + function build () { + setIsBuilding(true); + KaravanApi.pipelineRun(project, env, res => { if (res.status === 200 || res.status === 201) { - this.setState({isBuilding: false}); + setIsBuilding(false); } else { // Todo notification } }); } - rollout = () => { - this.setState({isRolling: true}); - KaravanApi.rolloutDeployment(this.props.project.projectId, this.props.env, res => { + function rollout () { + setIsRolling(true); + KaravanApi.rolloutDeployment(project.projectId, env, res => { console.log(res) if (res.status === 200 || res.status === 201) { - this.setState({isRolling: false}); + setIsRolling(false); } else { // Todo notification } }); } - buildButton = (env: string) => { - const {isBuilding, isPushing, pipelineStatus} = this.state; - const isRunning = pipelineStatus?.result === 'Running'; + function buildButton (env: string) { + const status = pipelineStatuses.filter(p => p.projectId === project.projectId).at(0); + const isRunning = status?.result === 'Running'; return (<Tooltip content="Start build pipeline" position={"left"}> <Button isLoading={isBuilding ? true : undefined} isDisabled={isBuilding || isRunning || isPushing} @@ -131,41 +91,41 @@ export class ProjectStatus extends React.Component<Props, State> { variant="secondary" className="project-button" icon={!isBuilding ? <BuildIcon/> : <div></div>} - onClick={e => this.build()}> + onClick={e => build()}> {isBuilding ? "..." : "Build"} </Button> </Tooltip>) } - rolloutButton = () => { - const isRolling = this.state.isRolling; + function rolloutButton () { return (<Tooltip content="Rollout deployment" position={"left"}> <Button isLoading={isRolling ? true : undefined} size="sm" variant="secondary" className="project-button" icon={!isRolling ? <RolloutIcon/> : <div></div>} - onClick={e => this.rollout()}> + onClick={e => rollout()}> {isRolling ? "..." : "Rollout"} </Button> </Tooltip>) } - deleteDeploymentButton = (env: string) => { + function deleteDeploymentButton (env: string) { return (<Tooltip content="Delete deployment" position={"left"}> <Button size="sm" variant="secondary" className="project-button" icon={<DeleteIcon/>} - onClick={e => this.setState({ - showDeleteConfirmation: true, - deleteEntity: "deployment", - deleteEntityEnv: env, - deleteEntityName: this.props.project?.projectId - })}> + onClick={e => { + setShowDeleteConfirmation(true); + setDeleteEntityType("deployment"); + setDeleteEntityEnv(env); + setDeleteEntityName(project?.projectId); + }}> {"Delete"} </Button> </Tooltip>) } - getReplicasPanel(env: string, deploymentStatus?: DeploymentStatus) { + function getReplicasPanel(env: string) { + const deploymentStatus = deployments.find(d => d.name === project?.projectId); const ok = (deploymentStatus && deploymentStatus?.readyReplicas > 0 && (deploymentStatus.unavailableReplicas === 0 || deploymentStatus.unavailableReplicas === undefined || deploymentStatus.unavailableReplicas === null) && deploymentStatus?.replicas === deploymentStatus?.readyReplicas) @@ -185,19 +145,20 @@ export class ProjectStatus extends React.Component<Props, State> { </LabelGroup>} {deploymentStatus === undefined && <Label icon={<DownIcon/>} color={"grey"}>No deployments</Label>} </FlexItem> - <FlexItem>{env === "dev" && this.deleteDeploymentButton(env)}</FlexItem> + <FlexItem>{env === "dev" && deleteDeploymentButton(env)}</FlexItem> </Flex> ) } - getPodsPanel(env: string, podStatuses: ContainerStatus[]) { + function getPodsPanel(env: string) { + const podStatuses = containers.filter(d => d.projectId === project?.projectId); return ( <Flex justifyContent={{default: "justifyContentSpaceBetween"}} alignItems={{default: "alignItemsFlexStart"}}> <FlexItem> {podStatuses.length === 0 && <Label icon={<DownIcon/>} color={"grey"}>No pods</Label>} <LabelGroup numLabels={2} isVertical> - {podStatuses.map(pod => { + {podStatuses.map((pod: ContainerStatus) => { const ready = pod.state === 'running'; return ( <Tooltip key={pod.containerName} content={pod.state}> @@ -213,12 +174,12 @@ export class ProjectStatus extends React.Component<Props, State> { {pod.containerName} </Button> <Tooltip content={"Delete Pod"}> - <Button icon={<DeleteIcon/>} variant="link" onClick={e => this.setState({ - showDeleteConfirmation: true, - deleteEntity: "pod", - deleteEntityEnv: env, - deleteEntityName: pod.containerName - })}></Button> + <Button icon={<DeleteIcon/>} variant="link" onClick={e => { + setShowDeleteConfirmation(true); + setDeleteEntityType("pod"); + setDeleteEntityEnv(env); + setDeleteEntityName(pod.containerName); + }}></Button> </Tooltip> </Label> </Tooltip> @@ -227,23 +188,23 @@ export class ProjectStatus extends React.Component<Props, State> { )} </LabelGroup> </FlexItem> - <FlexItem>{env === "dev" && this.rolloutButton()}</FlexItem> + <FlexItem>{env === "dev" && rolloutButton()}</FlexItem> </Flex> ) } - getStatusColor(status?: string) { + function getStatusColor(status?: string) { if (status === 'UP') return 'green'; if (status === 'DOWN') return 'red'; if (status === 'UNDEFINED') return 'grey'; } - getStatusIcon(status?: string) { + function getStatusIcon(status?: string) { return (status === 'UP' ? <UpIcon/> : <DownIcon/>) } - getHealthPanel(env: string) { - const status = this.state.camelStatus; + function getHealthPanel(env: string) { + const status = camels; // const routesStatus = status?.routesStatus; // const consumersStatus = status?.consumerStatus; // const contextStatus = status?.contextStatus; @@ -251,19 +212,19 @@ export class ProjectStatus extends React.Component<Props, State> { return ( <LabelGroup numLabels={4}> {/*{contextVersion &&*/} - {/* <Label icon={this.getStatusIcon(contextStatus)}*/} - {/* color={this.getStatusColor(contextStatus)}>{contextVersion}</Label>}*/} - {/*<Label icon={this.getStatusIcon(contextStatus)}*/} - {/* color={this.getStatusColor(contextStatus)}>Context</Label>*/} - {/*<Label icon={this.getStatusIcon(consumersStatus)}*/} - {/* color={this.getStatusColor(consumersStatus)}>Consumers</Label>*/} - {/*<Label icon={this.getStatusIcon(routesStatus)} color={this.getStatusColor(routesStatus)}>Routes</Label>*/} + {/* <Label icon={getStatusIcon(contextStatus)}*/} + {/* color={getStatusColor(contextStatus)}>{contextVersion}</Label>}*/} + {/*<Label icon={getStatusIcon(contextStatus)}*/} + {/* color={getStatusColor(contextStatus)}>Context</Label>*/} + {/*<Label icon={getStatusIcon(consumersStatus)}*/} + {/* color={getStatusColor(consumersStatus)}>Consumers</Label>*/} + {/*<Label icon={getStatusIcon(routesStatus)} color={getStatusColor(routesStatus)}>Routes</Label>*/} </LabelGroup> ) } - getPipelineState(env: string) { - const status = this.state.pipelineStatus; + function getPipelineState(env: string) { + const status = pipelineStatuses.filter(p => p.projectId === project.projectId).at(0); const pipeline = status?.pipelineName; const pipelineResult = status?.result; let lastPipelineRunTime = 0; @@ -292,14 +253,12 @@ export class ProjectStatus extends React.Component<Props, State> { </Button> : "No pipeline"} {isRunning && <Tooltip content={"Stop PipelineRun"}> - <Button icon={<DeleteIcon/>} variant="link" onClick={e => - this.setState({ - showDeleteConfirmation: true, - deleteEntity: "pipelinerun", - deleteEntityEnv: env, - deleteEntityName: pipeline - }) - }></Button> + <Button icon={<DeleteIcon/>} variant="link" onClick={e => { + setShowDeleteConfirmation(true); + setDeleteEntityType("pipelinerun"); + setDeleteEntityEnv(env); + setDeleteEntityName(pipeline); + }}></Button> </Tooltip>} </Label> {pipeline !== undefined && showTime === true && lastPipelineRunTime !== undefined && @@ -307,75 +266,71 @@ export class ProjectStatus extends React.Component<Props, State> { </LabelGroup> </Tooltip> </FlexItem> - <FlexItem>{env === "dev" && this.buildButton(env)}</FlexItem> + <FlexItem>{env === "dev" && buildButton(env)}</FlexItem> </Flex> ) } - getDeleteConfirmation() { - const {deleteEntity, deleteEntityEnv, deleteEntityName} = this.state; + function getDeleteConfirmation() { return (<Modal className="modal-delete" title="Confirmation" - isOpen={this.state.showDeleteConfirmation} - onClose={() => this.setState({showDeleteConfirmation: false})} + isOpen={showDeleteConfirmation} + onClose={() => setShowDeleteConfirmation(false)} actions={[ <Button key="confirm" variant="primary" onClick={e => { if (deleteEntityEnv && deleteEntityName && deleteEntity) { - this.deleteEntity(deleteEntity, deleteEntityName, deleteEntityEnv); - this.setState({showDeleteConfirmation: false}); + deleteEntity(deleteEntityType, deleteEntityName, deleteEntityEnv); + setShowDeleteConfirmation(false); } }}>Delete </Button>, <Button key="cancel" variant="link" - onClick={e => this.setState({showDeleteConfirmation: false})}>Cancel</Button> + onClick={e => setShowDeleteConfirmation(false)}>Cancel</Button> ]} - onEscapePress={e => this.setState({showDeleteConfirmation: false})}> + onEscapePress={e => setShowDeleteConfirmation(false)}> <div>{"Delete " + deleteEntity + " " + deleteEntityName + "?"}</div> </Modal>) } - render() { - const {deploymentStatus, podStatuses} = this.state; - const {env} = this.props; - return ( - <Card className="project-status"> - <CardBody> - <DescriptionList isHorizontal> - <DescriptionListGroup> - <DescriptionListTerm>Environment</DescriptionListTerm> - <DescriptionListDescription> - <Badge className="badge">{env}</Badge> - </DescriptionListDescription> - </DescriptionListGroup> - <DescriptionListGroup> - <DescriptionListTerm>Pipeline</DescriptionListTerm> - <DescriptionListDescription> - {this.getPipelineState(env)} - </DescriptionListDescription> - </DescriptionListGroup> - <DescriptionListGroup> - <DescriptionListTerm>Deployment</DescriptionListTerm> - <DescriptionListDescription> - {this.getReplicasPanel(env, deploymentStatus)} - </DescriptionListDescription> - </DescriptionListGroup> - <DescriptionListGroup> - <DescriptionListTerm>Pods</DescriptionListTerm> - <DescriptionListDescription> - {this.getPodsPanel(env, podStatuses)} - </DescriptionListDescription> - </DescriptionListGroup> - <DescriptionListGroup> - <DescriptionListTerm>Camel health</DescriptionListTerm> - <DescriptionListDescription> - {this.getHealthPanel(env)} - </DescriptionListDescription> - </DescriptionListGroup> - </DescriptionList> - </CardBody> - {this.state.showDeleteConfirmation && this.getDeleteConfirmation()} - </Card> - ) - } + const env = props.env; + return ( + <Card className="project-status"> + <CardBody> + <DescriptionList isHorizontal> + <DescriptionListGroup> + <DescriptionListTerm>Environment</DescriptionListTerm> + <DescriptionListDescription> + <Badge className="badge">{env}</Badge> + </DescriptionListDescription> + </DescriptionListGroup> + <DescriptionListGroup> + <DescriptionListTerm>Pipeline</DescriptionListTerm> + <DescriptionListDescription> + {getPipelineState(env)} + </DescriptionListDescription> + </DescriptionListGroup> + <DescriptionListGroup> + <DescriptionListTerm>Deployment</DescriptionListTerm> + <DescriptionListDescription> + {getReplicasPanel(env)} + </DescriptionListDescription> + </DescriptionListGroup> + <DescriptionListGroup> + <DescriptionListTerm>Pods</DescriptionListTerm> + <DescriptionListDescription> + {getPodsPanel(env)} + </DescriptionListDescription> + </DescriptionListGroup> + <DescriptionListGroup> + <DescriptionListTerm>Camel health</DescriptionListTerm> + <DescriptionListDescription> + {getHealthPanel(env)} + </DescriptionListDescription> + </DescriptionListGroup> + </DescriptionList> + </CardBody> + {showDeleteConfirmation && getDeleteConfirmation()} + </Card> + ) } diff --git a/karavan-web/karavan-app/src/main/webui/src/project/trace/RunnerInfoTraceModal.tsx b/karavan-web/karavan-app/src/main/webui/src/project/trace/RunnerInfoTraceModal.tsx index 6940f8a8..d734018d 100644 --- a/karavan-web/karavan-app/src/main/webui/src/project/trace/RunnerInfoTraceModal.tsx +++ b/karavan-web/karavan-app/src/main/webui/src/project/trace/RunnerInfoTraceModal.tsx @@ -58,8 +58,8 @@ export const RunnerInfoTraceModal = (props: Props) => { <DescriptionListTerm>Nodes</DescriptionListTerm> </DescriptionListGroup> </DescriptionList> - {props.nodes.map((node: any) => ( - <FlexItem> + {props.nodes.map((node: any, index: number) => ( + <FlexItem key={node.uid + "-" + index}> <Button variant={node.uid === activeNode.uid ? "secondary" : "link"} icon={node.nodeId === undefined ? <ArrowRightIcon/> : undefined} onClick={event => {setActiveNode(node)}}> diff --git a/karavan-web/karavan-app/src/main/webui/src/project/trace/RunnerInfoTraceNode.tsx b/karavan-web/karavan-app/src/main/webui/src/project/trace/RunnerInfoTraceNode.tsx index 90d71de6..5a470290 100644 --- a/karavan-web/karavan-app/src/main/webui/src/project/trace/RunnerInfoTraceNode.tsx +++ b/karavan-web/karavan-app/src/main/webui/src/project/trace/RunnerInfoTraceNode.tsx @@ -42,8 +42,8 @@ export const RunnerInfoTraceNode = (props: Props) => { <DescriptionListTerm>Headers</DescriptionListTerm> </DescriptionListGroup> <DataList aria-label="Compact data list example" isCompact> - {headers.map((header: any) => ( - <DataListItem key={header[0]} aria-labelledby="compact-item1"> + {headers.map((header: any, index: number) => ( + <DataListItem key={header[0] + "-" + index} aria-labelledby="compact-item1"> <DataListItemRow> <DataListItemCells dataListCells={[ diff --git a/karavan-web/karavan-app/src/main/webui/src/project/trace/TraceTab.tsx b/karavan-web/karavan-app/src/main/webui/src/project/trace/TraceTab.tsx index c62aa090..d4420f2f 100644 --- a/karavan-web/karavan-app/src/main/webui/src/project/trace/TraceTab.tsx +++ b/karavan-web/karavan-app/src/main/webui/src/project/trace/TraceTab.tsx @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React, {useEffect, useState} from 'react'; +import React, {useState} from 'react'; import { Bullseye, Button, @@ -39,40 +39,16 @@ import { Table } from '@patternfly/react-table/deprecated'; import SearchIcon from "@patternfly/react-icons/dist/esm/icons/search-icon"; -import {KaravanApi} from "../../api/KaravanApi"; import {useProjectStore} from "../../api/ProjectStore"; +import {shallow} from "zustand/shallow"; export const TraceTab = () => { - const {project} = useProjectStore(); - const [trace, setTrace] = useState<any>({}); + const [refreshTrace, setRefreshTrace, trace] = useProjectStore((state) => + [state.refreshTrace, state.setRefreshTrace, state.trace], shallow); const [nodes, setNodes] = useState([{}]); - const [isOpen, setIsOpen] = useState(false); - const [refreshTrace, setRefreshTrace] = useState(false); - - useEffect(() => { - const interval = setInterval(() => { - onRefreshStatus(); - }, 1000); - return () => { - clearInterval(interval) - }; - }, [refreshTrace]); - - - function onRefreshStatus() { - const projectId = project.projectId; - if (refreshTrace) { - KaravanApi.getDevModeStatus(projectId, "trace", res => { - if (res.status === 200) { - setTrace(JSON.parse(res.data.status)); - } else { - setTrace({}); - } - }) - } - } + const [isOpen, setIsOpen] = useState<boolean>(false); function closeModal() { setIsOpen(false); @@ -134,7 +110,7 @@ export const TraceTab = () => { <Td> <Button style={{padding: '0'}} variant={"link"} onClick={e => { - setTrace(trace); + // setTrace(trace); setNodes(getNodes(exchangeId)); setIsOpen(true); }}> diff --git a/karavan-web/karavan-app/src/main/webui/src/projects/ProjectsPage.tsx b/karavan-web/karavan-app/src/main/webui/src/projects/ProjectsPage.tsx index 64c5fd40..ef49c8aa 100644 --- a/karavan-web/karavan-app/src/main/webui/src/projects/ProjectsPage.tsx +++ b/karavan-web/karavan-app/src/main/webui/src/projects/ProjectsPage.tsx @@ -49,36 +49,10 @@ export const ProjectsPage = () => { const [projects] = useProjectsStore((state) => [state.projects], shallow) const [operation] = useProjectStore((state) => [state.operation], shallow) const [filter, setFilter] = useState<string>(''); - const [loading, setLoading] = useState<boolean>(false); - - useEffect(() => { - const interval = setInterval(() => { - if (projects.length === 0) setLoading(true); - if (!["create", "delete", "select", "copy"].includes(operation)) { - refresh(); - } - }, 1300); - return () => { - clearInterval(interval) - }; - }, [operation]); - - function refresh() { - ProjectService.refreshProjects(); - ProjectService.refreshAllDeploymentStatuses(); - ProjectService.refreshAllContainerStatuses(); - setLoading(false); - } function getTools() { return <Toolbar id="toolbar-group-types"> <ToolbarContent> - <ToolbarItem> - <Button variant="link" icon={<RefreshIcon/>} onClick={e => { - setLoading(true); - refresh(); - }}/> - </ToolbarItem> <ToolbarItem> <TextInput className="text-field" type="search" id="search" name="search" autoComplete="off" placeholder="Search by name" @@ -106,14 +80,10 @@ export const ProjectsPage = () => { <Tr> <Td colSpan={8}> <Bullseye> - {loading && - <Spinner className="progress-stepper" diameter="80px" aria-label="Loading..."/>} - {!loading && - <EmptyState variant={EmptyStateVariant.sm}> - <EmptyStateHeader titleText="No results found" - icon={<EmptyStateIcon icon={SearchIcon}/>} headingLevel="h2"/> - </EmptyState> - } + <EmptyState variant={EmptyStateVariant.sm}> + <EmptyStateHeader titleText="No results found" + icon={<EmptyStateIcon icon={SearchIcon}/>} headingLevel="h2"/> + </EmptyState> </Bullseye> </Td> </Tr> diff --git a/karavan-web/karavan-infinispan/src/main/java/org/apache/camel/karavan/infinispan/InfinispanService.java b/karavan-web/karavan-infinispan/src/main/java/org/apache/camel/karavan/infinispan/InfinispanService.java index 934f3042..e03e9c12 100644 --- a/karavan-web/karavan-infinispan/src/main/java/org/apache/camel/karavan/infinispan/InfinispanService.java +++ b/karavan-web/karavan-infinispan/src/main/java/org/apache/camel/karavan/infinispan/InfinispanService.java @@ -210,6 +210,13 @@ public class InfinispanService { return pipelineStatuses.get(GroupedKey.create(projectId, environment, projectId)); } + public List<PipelineStatus> getPipelineStatuses(String environment) { + QueryFactory queryFactory = Search.getQueryFactory((RemoteCache<?, ?>) pipelineStatuses); + return queryFactory.<PipelineStatus>create("FROM karavan.PipelineStatus WHERE env = :env") + .setParameter("env", environment) + .execute().list(); + } + public void savePipelineStatus(PipelineStatus status) { pipelineStatuses.put(GroupedKey.create(status.getProjectId(), status.getEnv(), status.getProjectId()), status); }
