You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by ma...@apache.org on 2023/07/23 18:52:16 UTC
[camel-karavan] 02/03: Dashboard supports containers in Docker #817
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 4dd412f91ac73346b079121b7029c548c781a896
Author: Marat Gubaidullin <ma...@gmail.com>
AuthorDate: Sun Jul 23 14:51:25 2023 -0400
Dashboard supports containers in Docker #817
---
.../camel/karavan/api/InfrastructureResource.java | 13 +
.../src/main/resources/application.properties | 2 +-
.../karavan-app/src/main/webui/src/Main.tsx | 27 +-
.../src/main/webui/src/api/KaravanApi.tsx | 11 +
.../src/main/webui/src/api/ProjectService.ts | 17 +-
.../src/main/webui/src/api/ProjectStore.ts | 56 ++-
.../src/main/webui/src/dashboard/DashboardPage.tsx | 484 +++++++++++----------
.../src/main/webui/src/projects/ProjectsPage.tsx | 20 +-
.../main/webui/src/projects/ProjectsTableRow.tsx | 15 +-
.../karavan/infinispan/InfinispanService.java | 4 +
10 files changed, 382 insertions(+), 267 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 2a7ec6c2..443c9e69 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
@@ -141,6 +141,19 @@ public class InfrastructureResource {
.collect(Collectors.toList());
}
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("/container")
+ public List<ContainerStatus> getAllContainerStatuses() throws Exception {
+ if (infinispanService.isReady()) {
+ return infinispanService.getContainerStatuses().stream()
+ .sorted(Comparator.comparing(ContainerStatus::getProjectId))
+ .collect(Collectors.toList());
+ } else {
+ return List.of();
+ }
+ }
+
@GET
@Produces(MediaType.APPLICATION_JSON)
@Path("/pod/{env}")
diff --git a/karavan-web/karavan-app/src/main/resources/application.properties b/karavan-web/karavan-app/src/main/resources/application.properties
index 2cee473c..37a78899 100644
--- a/karavan-web/karavan-app/src/main/resources/application.properties
+++ b/karavan-web/karavan-app/src/main/resources/application.properties
@@ -1,6 +1,6 @@
karavan.version=3.21.1-SNAPSHOT
karavan.environment=dev
-karavan.environments=dev,test,prod
+karavan.environments=dev
karavan.default-runtime=quarkus
karavan.runtimes=quarkus,spring-boot
karavan.camel.status.interval=off
diff --git a/karavan-web/karavan-app/src/main/webui/src/Main.tsx b/karavan-web/karavan-app/src/main/webui/src/Main.tsx
index 11bf54f0..9e36ffa3 100644
--- a/karavan-web/karavan-app/src/main/webui/src/Main.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/Main.tsx
@@ -50,11 +50,7 @@ export const Main = () => {
const [config, setConfig] = useAppConfigStore((state) => [state.config, state.setConfig], shallow)
const [pageId, setPageId] = useState<string>('projects');
- const [openapi, setOpenapi] = useState<string>('');
- const [filename, setFileName] = useState<string>('');
- const [key, setKey] = useState<string>('');
const [request, setRequest] = useState<string>(uuidv4());
- const [isModalOpen, setIsModelOpen] = useState<boolean>(false);
const [showUser, setShowUser] = useState<boolean>(false);
useEffect(() => {
@@ -71,8 +67,6 @@ export const Main = () => {
getData();
});
});
- } else {
- setKey(Math.random().toString())
}
if (KaravanApi.isAuthorized || KaravanApi.authType === 'public') {
getData();
@@ -138,15 +132,22 @@ export const Main = () => {
});
}
- function pageNav() {
+ function getMenu() : MenuItem[] {
const pages: MenuItem[] = [
new MenuItem("dashboard", "Dashboard", <DashboardIcon/>),
new MenuItem("projects", "Projects", <ProjectsIcon/>),
- new MenuItem("services", "Services", <ServicesIcon/>),
- new MenuItem("containers", "Containers", <ContainersIcon/>),
- new MenuItem("knowledgebase", "Knowledgebase", <KnowledgebaseIcon/>),
- // new MenuItem("components", "Components", <ComponentsIcon/>)
]
+ if (config.infrastructure === 'docker') {
+ pages.push(
+ new MenuItem("services", "Services", <ServicesIcon/>),
+ new MenuItem("containers", "Containers", <ContainersIcon/>)
+ )
+ }
+ pages.push(new MenuItem("knowledgebase", "Knowledgebase", <KnowledgebaseIcon/>));
+ return pages;
+ }
+
+ function pageNav() {
return (<Flex className="nav-buttons" direction={{default: "column"}} style={{height: "100%"}}
spaceItems={{default: "spaceItemsNone"}}>
<FlexItem alignSelf={{default: "alignSelfCenter"}}>
@@ -155,7 +156,7 @@ export const Main = () => {
{Icon()}
</Tooltip>
</FlexItem>
- {pages.map(page =>
+ {getMenu().map(page =>
<FlexItem key={page.pageId} className={pageId === page.pageId ? "nav-button-selected" : ""}>
<Tooltip content={page.tooltip} position={"right"}>
<Button id={page.pageId} icon={page.icon} variant={"plain"}
@@ -211,7 +212,7 @@ export const Main = () => {
{pageNav()}
</FlexItem>
<FlexItem flex={{default: "flex_2"}} style={{height: "100%"}}>
- {pageId === 'dashboard' && <DashboardPage key={request} toast={toast} config={config}/>}
+ {pageId === 'dashboard' && <DashboardPage key='dashboard'/>}
{pageId === 'projects' && <ProjectsPage key={request}/>}
{pageId === 'project' && <ProjectPage key="projects"/>}
{pageId === 'services' && <ServicesPage key="services"/>}
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 6515e83a..7a964268 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
@@ -401,6 +401,17 @@ export class KaravanApi {
});
}
+ static async getAllContainerStatuses(after: (statuses: ContainerStatus[]) => void) {
+ instance.get('/api/infrastructure/container')
+ .then(res => {
+ if (res.status === 200) {
+ after(res.data);
+ }
+ }).catch(err => {
+ console.log(err);
+ });
+ }
+
static async getAllDeploymentStatuses(after: (statuses: DeploymentStatus[]) => void) {
instance.get('/api/infrastructure/deployment')
.then(res => {
diff --git a/karavan-web/karavan-app/src/main/webui/src/api/ProjectService.ts b/karavan-web/karavan-app/src/main/webui/src/api/ProjectService.ts
index 4e6621cc..4fbd9da7 100644
--- a/karavan-web/karavan-app/src/main/webui/src/api/ProjectService.ts
+++ b/karavan-web/karavan-app/src/main/webui/src/api/ProjectService.ts
@@ -4,9 +4,8 @@ import {TemplateApi} from "karavan-core/lib/api/TemplateApi";
import {InfrastructureAPI} from "../designer/utils/InfrastructureAPI";
import {unstable_batchedUpdates} from 'react-dom'
import {
- useAppConfigStore,
- useDeploymentStatusesStore,
useFilesStore,
+ useStatusesStore,
useFileStore, useLogStore,
useProjectsStore,
useProjectStore, useDevModeStore
@@ -123,9 +122,21 @@ export class ProjectService {
});
}
+ public static refreshAllContainerStatuses() {
+ KaravanApi.getAllContainerStatuses( (statuses: ContainerStatus[]) => {
+ useStatusesStore.setState({containers: statuses});
+ });
+ }
+
+ public static refreshAllDeploymentStatuses() {
+ KaravanApi.getAllDeploymentStatuses( (statuses: DeploymentStatus[]) => {
+ useStatusesStore.setState({deployments: statuses});
+ });
+ }
+
public static refreshDeploymentStatuses(environment: string) {
KaravanApi.getDeploymentStatuses(environment, (statuses: DeploymentStatus[]) => {
- useDeploymentStatusesStore.setState({statuses: statuses});
+ useStatusesStore.setState({deployments: statuses});
});
}
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 b4930ead..418d1848 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,10 +16,9 @@
*/
import {create} from 'zustand'
-import {AppConfig, DeploymentStatus, ContainerStatus, Project, ProjectFile, ToastMessage} from "./ProjectModels";
+import {AppConfig, DeploymentStatus, ContainerStatus, Project, ProjectFile, ServiceStatus, CamelStatus} from "./ProjectModels";
import {ProjectEventBus} from "./ProjectEventBus";
import {unstable_batchedUpdates} from "react-dom";
-import {bottom} from "@patternfly/react-core/helpers/Popper/thirdparty/popper-core";
interface AppConfigState {
config: AppConfig;
@@ -136,21 +135,6 @@ export const useFileStore = create<FileState>((set) => ({
},
}))
-interface DeploymentStatusesState {
- statuses: DeploymentStatus[];
- setDeploymentStatuses: (statuses: DeploymentStatus[]) => void;
-}
-
-export const useDeploymentStatusesStore = create<DeploymentStatusesState>((set) => ({
- statuses: [],
- setDeploymentStatuses: (statuses: DeploymentStatus[]) => {
- set((state: DeploymentStatusesState) => ({
- statuses: statuses
- }));
- },
-}))
-
-
interface DevModeState {
podName?: string,
status: "none" | "starting" | "deleting"| "reloading" | "running",
@@ -167,6 +151,44 @@ export const useDevModeStore = create<DevModeState>((set) => ({
},
}))
+interface StatusesState {
+ deployments: DeploymentStatus[];
+ services: ServiceStatus[];
+ containers: ContainerStatus[];
+ camels: CamelStatus[];
+ setDeployments: (d: DeploymentStatus[]) => void;
+ setServices: (s: ServiceStatus[]) => void;
+ setContainers: (c: ContainerStatus[]) => void;
+ setCamels: (c: CamelStatus[]) => void;
+}
+
+export const useStatusesStore = create<StatusesState>((set) => ({
+ deployments: [],
+ services: [],
+ containers: [],
+ camels: [],
+ setDeployments: (d: DeploymentStatus[]) => {
+ set((state: StatusesState) => ({
+ deployments: d,
+ }));
+ },
+ setServices: (s: ServiceStatus[]) => {
+ set((state: StatusesState) => ({
+ services: s,
+ }));
+ },
+ setContainers: (c: ContainerStatus[]) => {
+ set((state: StatusesState) => ({
+ containers: c,
+ }));
+ },
+ setCamels: (c: CamelStatus[]) => {
+ set((state: StatusesState) => ({
+ camels: c,
+ }));
+ }
+}))
+
interface LogState {
podName?: string,
isRunning: boolean,
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 285508d3..4f2cc9ca 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 from 'react';
+import React, {useEffect, useState} from 'react';
import {
Badge, Bullseye,
Button, EmptyState, EmptyStateIcon, EmptyStateVariant,
@@ -13,7 +13,7 @@ import {
ToolbarItem, Tooltip
} from '@patternfly/react-core';
import '../designer/karavan.css';
-import {CamelStatus, DeploymentStatus, Project, ServiceStatus} from "../api/ProjectModels";
+import {CamelStatus, ContainerStatus, DeploymentStatus, Project, ServiceStatus} from "../api/ProjectModels";
import {TableComposable, TableVariant, Tbody, Td, Th, Thead, Tr} from "@patternfly/react-table";
import {camelIcon, CamelUi} from "../designer/utils/CamelUi";
import {KaravanApi} from "../api/KaravanApi";
@@ -23,177 +23,158 @@ 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";
+import {shallow} from "zustand/shallow";
-interface Props {
- config: any,
- toast: (title: string, text: string, variant: 'success' | 'danger' | 'warning' | 'info' | 'default') => void
-}
+export const DashboardPage = () => {
-interface State {
- projects: Project[],
- deploymentStatuses: DeploymentStatus[],
- serviceStatuses: ServiceStatus[],
- camelStatuses: CamelStatus[],
- isCreateModalOpen: boolean,
- isDeleteModalOpen: boolean,
- isCopy: boolean,
- loading: boolean,
- projectToCopy?: Project,
- projectToDelete?: Project,
- filter: string,
- name: string,
- description: string,
- projectId: string,
- selectedEnv: string[]
-}
+ const [config] = useAppConfigStore((state) => [state.config], shallow)
+ const [projects, setProjects] = useProjectsStore((state) => [state.projects, state.setProjects], shallow)
+ const [deployments, services, containers, camels, setDeployments, setServices, setContainers, setCamels]
+ = 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]);
-export class DashboardPage extends React.Component<Props, State> {
+ useEffect(() => {
+ const interval = setInterval(() => {
+ onGetProjects()
+ }, 1300);
+ return () => {
+ clearInterval(interval)
+ };
+ }, []);
- public state: State = {
- projects: [],
- deploymentStatuses: [],
- serviceStatuses: [],
- camelStatuses: [],
- isCreateModalOpen: false,
- isDeleteModalOpen: false,
- isCopy: false,
- loading: true,
- filter: '',
- name: '',
- description: '',
- projectId: '',
- selectedEnv: this.getEnvironments()
- };
- interval: any;
-
- componentDidMount() {
- this.interval = setInterval(() => this.onGetProjects(), 1300);
- }
-
- componentWillUnmount() {
- clearInterval(this.interval);
- }
-
- onGetProjects = () => {
+ function onGetProjects() {
KaravanApi.getConfiguration((config: any) => {
KaravanApi.getProjects((projects: Project[]) => {
- this.setState({projects: projects, loading: false})
+ setProjects(projects);
});
KaravanApi.getAllDeploymentStatuses((statuses: DeploymentStatus[]) => {
- this.setState({deploymentStatuses: statuses});
+ setDeployments(statuses);
});
KaravanApi.getAllServiceStatuses((statuses: ServiceStatus[]) => {
- this.setState({serviceStatuses: statuses});
+ setServices(statuses);
});
- this.getSelectedEnvironments().forEach(env => {
- KaravanApi.getAllCamelStatuses(env,(statuses: CamelStatus[]) => {
- this.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;
- })
+ KaravanApi.getAllContainerStatuses((statuses: ContainerStatus[]) => {
+ setContainers(statuses);
+ });
+ selectedEnv.forEach(env => {
+ KaravanApi.getAllCamelStatuses(env, (statuses: CamelStatus[]) => {
+ setCamels(statuses);
+ // 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;
+ // })
});
- })
+ });
+ setLoading(false);
});
}
- selectEnvironment(name: string, selected: boolean) {
- if (selected && !this.state.selectedEnv.includes(name)) {
- this.setState((state) => {
- state.selectedEnv.push(name);
+ function selectEnvironment(name: string, selected: boolean) {
+ if (selected && !selectedEnv.includes(name)) {
+ setSelectedEnv((state: string[]) => {
+ state.push(name);
return state;
})
- } else if (!selected && this.state.selectedEnv.includes(name)) {
- this.setState((prevState) => ({
- selectedEnv: prevState.selectedEnv.filter(e => e !== name)
- }));
+ } else if (!selected && selectedEnv.includes(name)) {
+ setSelectedEnv((state: string[]) => {
+ return state.filter(e => e !== name)
+ })
}
}
- tools = () => (<Toolbar id="toolbar-group-types">
- <ToolbarContent>
- <ToolbarItem>
- <Button variant="link" icon={<RefreshIcon/>} onClick={e => this.onGetProjects()}/>
- </ToolbarItem>
- <ToolbarItem>
- <ToggleGroup aria-label="Default with single selectable">
- {this.getEnvironments().map(env => (
- <ToggleGroupItem key={env} text={env} buttonId={env} isSelected={this.state.selectedEnv.includes(env)} onChange={selected => this.selectEnvironment(env, selected)}/>
- ))}
- </ToggleGroup>
- </ToolbarItem>
- <ToolbarItem>
- <TextInput className="text-field" type="search" id="search" name="search"
- autoComplete="off" placeholder="Search deployment by name"
- value={this.state.filter}
- onChange={e => this.setState({filter: e})}/>
- </ToolbarItem>
- </ToolbarContent>
- </Toolbar>);
-
- title = () => (<TextContent>
- <Text component="h2">Dashboard</Text>
- </TextContent>);
+ function tools() {
+ return (<Toolbar id="toolbar-group-types">
+ <ToolbarContent>
+ <ToolbarItem>
+ <Button variant="link" icon={<RefreshIcon/>} onClick={e => onGetProjects()}/>
+ </ToolbarItem>
+ <ToolbarItem>
+ <ToggleGroup aria-label="Default with single selectable">
+ {config.environments.map(env => (
+ <ToggleGroupItem key={env} text={env} buttonId={env} isSelected={selectedEnv.includes(env)} onChange={selected => selectEnvironment(env, selected)}/>
+ ))}
+ </ToggleGroup>
+ </ToolbarItem>
+ <ToolbarItem>
+ <TextInput className="text-field" type="search" id="search" name="search"
+ autoComplete="off" placeholder="Search deployment by name"
+ value={filter}
+ onChange={e => setFilter(e)}/>
+ </ToolbarItem>
+ </ToolbarContent>
+ </Toolbar>);
+ }
- getEnvironments(): string [] {
- return this.props.config.environments && Array.isArray(this.props.config.environments) ? Array.from(this.props.config.environments) : [];
+ function title() {
+ return (<TextContent>
+ <Text component="h2">Dashboard</Text>
+ </TextContent>);
}
- getSelectedEnvironments(): string [] {
- return this.getEnvironments().filter(e => this.state.selectedEnv.includes(e));
+ function getSelectedEnvironments(): string [] {
+ return config.environments.filter(e => selectedEnv.includes(e));
}
- getDeploymentEnvironments(name: string): [string, boolean] [] {
- const deps = this.state.deploymentStatuses;
- return this.getSelectedEnvironments().map(e => {
+ function getDeploymentEnvironments(name: string): [string, boolean] [] {
+ return selectedEnv.map(e => {
const env: string = e as string;
- const dep = deps.find(d => d.name === name && d.env === env);
+ const dep = deployments.find(d => d.name === name && d.env === env);
const deployed: boolean = dep !== undefined && dep.replicas > 0 && dep.replicas === dep.readyReplicas;
return [env, deployed];
});
}
- getDeploymentByEnvironments(name: string): [string, DeploymentStatus | undefined] [] {
- const deps = this.state.deploymentStatuses;
- return this.getSelectedEnvironments().map(e => {
+ function getDeploymentByEnvironments(name: string): [string, DeploymentStatus | undefined] [] {
+ return selectedEnv.map(e => {
const env: string = e as string;
- const dep = deps.find(d => d.name === name && d.env === env);
+ const dep = deployments.find(d => d.name === name && d.env === env);
return [env, dep];
});
}
- getServiceByEnvironments(name: string): [string, ServiceStatus | undefined] [] {
- const services = this.state.serviceStatuses;
- return this.getSelectedEnvironments().map(e => {
+ function getServiceByEnvironments(name: string): [string, ServiceStatus | undefined] [] {
+ return selectedEnv.map(e => {
const env: string = e as string;
const service = services.find(d => d.name === name && d.env === env);
return [env, service];
});
}
- getCamelStatusByEnvironments(name: string): [string, CamelStatus | undefined] [] {
- const camelStatuses = this.state.camelStatuses;
- return this.getSelectedEnvironments().map(e => {
+ function getContainerByEnvironments(name: string): [string, ContainerStatus | undefined] [] {
+ return selectedEnv.map(e => {
+ const env: string = e as string;
+ const container = containers.find(d => d.containerName === name && d.env === env);
+ return [env, container];
+ });
+ }
+
+ function getCamelStatusByEnvironments(name: string): [string, CamelStatus | undefined] [] {
+ return getSelectedEnvironments().map(e => {
const env: string = e as string;
- const status = camelStatuses.find(d => d.projectId === name && d.env === env);
+ const status = camels.find(d => d.projectId === name && d.env === env);
return [env, status];
});
}
- getProject(name: string): Project | undefined {
- return this.state.projects.filter(p => p.projectId === name)?.at(0);
+ function getProject(name: string): Project | undefined {
+ return projects.filter(p => p.projectId === name)?.at(0);
}
- isKaravan(name: string): boolean {
- return this.state.projects.findIndex(p => p.projectId === name) > -1;
+ function isKaravan(name: string): boolean {
+ return projects.findIndex(p => p.projectId === name) > -1;
}
- getReplicasPanel(deploymentStatus?: DeploymentStatus) {
+ function getReplicasPanel(deploymentStatus?: DeploymentStatus) {
if (deploymentStatus) {
const readyReplicas = deploymentStatus.readyReplicas ? deploymentStatus.readyReplicas : 0;
const ok = (deploymentStatus && readyReplicas > 0
@@ -225,8 +206,7 @@ export class DashboardPage extends React.Component<Props, State> {
}
}
- getEmptyState() {
- const {loading} = this.state;
+ function getEmptyState() {
return (
<Tr>
<Td colSpan={8}>
@@ -246,103 +226,167 @@ export class DashboardPage extends React.Component<Props, State> {
)
}
- render() {
- const deployments = Array.from(new Set(this.state.deploymentStatuses.filter(d => d.name.toLowerCase().includes(this.state.filter)).map(d => d.name)));
+ function getKubernetesTable() {
+ const deps = Array.from(new Set(deployments.filter(d => d.name.toLowerCase().includes(filter)).map(d => d.name)));
return (
- <PageSection className="kamelet-section dashboard-page" padding={{default: 'noPadding'}}>
- <PageSection className="tools-section" padding={{default: 'noPadding'}}>
- <MainToolbar title={this.title()} tools={this.tools()}/>
- </PageSection>
- <PageSection isFilled className="kamelets-page">
- <TableComposable aria-label="Projects" variant={TableVariant.compact}>
- <Thead>
- <Tr>
- <Th key='type'>Type</Th>
- <Th key='name'>Deployment</Th>
- <Th key='description'>Project/Description</Th>
- <Th key='environment'>Environment</Th>
- <Th key='namespace'>Namespace</Th>
- <Th key='replicas'>Replicas</Th>
- <Th key='services'>Services</Th>
- <Th key='camel'>Camel Health</Th>
- {/*<Th key='action'></Th>*/}
- </Tr>
- </Thead>
- <Tbody>
- {deployments.map(deployment => (
- <Tr key={deployment}>
- <Td style={{verticalAlign: "middle"}}>
- {this.isKaravan(deployment) ? Icon("icon") : CamelUi.getIconFromSource(camelIcon)}
- </Td>
- <Td style={{verticalAlign: "middle"}}>
- <Button style={{padding: '6px'}} variant={"link"}>{deployment}</Button>
- </Td>
- <Td style={{verticalAlign: "middle"}}>
- <HelperText>
- <HelperTextItem>{this.getProject(deployment)?.name || ""}</HelperTextItem>
- <HelperTextItem>{this.getProject(deployment)?.description || "Camel project"}</HelperTextItem>
- </HelperText>
- </Td>
- <Td>
- <Flex direction={{default: "column"}}>
- {this.getDeploymentEnvironments(deployment).map(value => (
- <FlexItem className="badge-flex-item" key={value[0]}><Badge className="badge"
- isRead={!value[1]}>{value[0]}</Badge></FlexItem>
- ))}
- </Flex>
- </Td>
- <Td>
- <Flex direction={{default: "column"}}>
- {this.getDeploymentByEnvironments(deployment).map(value => (
- <FlexItem className="badge-flex-item" key={value[0]}>
- <Label variant={"outline"}>
- {value[1]?.namespace || "???"}
- </Label>
- </FlexItem>
- ))}
- </Flex>
- </Td>
- <Td>
- <Flex direction={{default: "column"}}>
- {this.getDeploymentByEnvironments(deployment).map(value => (
- <FlexItem className="badge-flex-item" key={value[0]}>{this.getReplicasPanel(value[1])}</FlexItem>
- ))}
- </Flex>
- </Td>
- <Td>
- <Flex direction={{default: "column"}}>
- {this.getServiceByEnvironments(deployment).map(value => (
- <FlexItem className="badge-flex-item" key={value[0]}>
- <Label variant={"outline"}>
- {value[1] ? (value[1]?.port + " -> " + value[1]?.targetPort) : "???"}
- </Label>
- </FlexItem>
- ))}
- </Flex>
- </Td>
- <Td modifier={"fitContent"}>
- <Flex direction={{default: "column"}}>
- {this.getCamelStatusByEnvironments(deployment).map(value => {
- // const color = value[1] ? (value[1].consumerStatus === "UP" ? "green" : "red") : "grey";
- // let icon = undefined;
- // if (value[1]?.consumerStatus === "UP") icon = <UpIcon/>
- // if (value[1]?.consumerStatus === "DOWN") icon = <DownIcon/>
- // const text = value[1] && value[1]?.contextVersion ? value[1]?.contextVersion : "???";
- return <FlexItem key={value[0]}>
- {/*<LabelGroup numLabels={4} className="camel-label-group">*/}
- {/* <Label color={color} className="table-label" icon={icon}>{text}</Label>*/}
- {/*</LabelGroup>*/}
- </FlexItem>
- })}
- </Flex>
- </Td>
- </Tr>
- ))}
- {deployments.length === 0 && this.getEmptyState()}
- </Tbody>
- </TableComposable>
- </PageSection>
- </PageSection>
+ <TableComposable aria-label="Projects" variant={TableVariant.compact}>
+ <Thead>
+ <Tr>
+ <Th key='type'>Type</Th>
+ <Th key='name'>Deployment</Th>
+ <Th key='description'>Project/Description</Th>
+ <Th key='environment'>Environment</Th>
+ <Th key='namespace'>Namespace</Th>
+ <Th key='replicas'>Replicas</Th>
+ <Th key='services'>Services</Th>
+ <Th key='camel'>Camel Health</Th>
+ {/*<Th key='action'></Th>*/}
+ </Tr>
+ </Thead>
+ <Tbody>
+ {deps.map(deployment => (
+ <Tr key={deployment}>
+ <Td style={{verticalAlign: "middle"}}>
+ {isKaravan(deployment) ? Icon("icon") : CamelUi.getIconFromSource(camelIcon)}
+ </Td>
+ <Td style={{verticalAlign: "middle"}}>
+ <Button style={{padding: '6px'}} variant={"link"}>{deployment}</Button>
+ </Td>
+ <Td style={{verticalAlign: "middle"}}>
+ <HelperText>
+ <HelperTextItem>{getProject(deployment)?.name || ""}</HelperTextItem>
+ <HelperTextItem>{getProject(deployment)?.description || "Camel project"}</HelperTextItem>
+ </HelperText>
+ </Td>
+ <Td>
+ <Flex direction={{default: "column"}}>
+ {getDeploymentEnvironments(deployment).map(value => (
+ <FlexItem className="badge-flex-item" key={value[0]}><Badge className="badge"
+ isRead={!value[1]}>{value[0]}</Badge></FlexItem>
+ ))}
+ </Flex>
+ </Td>
+ <Td>
+ <Flex direction={{default: "column"}}>
+ {getServiceByEnvironments(deployment).map(value => (
+ <FlexItem className="badge-flex-item" key={value[0]}>
+ <Label variant={"outline"}>
+ {value[1] ? (value[1]?.port + " -> " + value[1]?.targetPort) : "???"}
+ </Label>
+ </FlexItem>
+ ))}
+ </Flex>
+ </Td>
+ <Td modifier={"fitContent"}>
+ <Flex direction={{default: "column"}}>
+ {getCamelStatusByEnvironments(deployment).map(value => {
+ // const color = value[1] ? (value[1].consumerStatus === "UP" ? "green" : "red") : "grey";
+ // let icon = undefined;
+ // if (value[1]?.consumerStatus === "UP") icon = <UpIcon/>
+ // if (value[1]?.consumerStatus === "DOWN") icon = <DownIcon/>
+ // const text = value[1] && value[1]?.contextVersion ? value[1]?.contextVersion : "???";
+ return <FlexItem key={value[0]}>
+ {/*<LabelGroup numLabels={4} className="camel-label-group">*/}
+ {/* <Label color={color} className="table-label" icon={icon}>{text}</Label>*/}
+ {/*</LabelGroup>*/}
+ </FlexItem>
+ })}
+ </Flex>
+ </Td>
+ </Tr>
+ ))}
+ {deps.length === 0 && getEmptyState()}
+ </Tbody>
+ </TableComposable>
+ )
+ }
+
+ function getDockerTable() {
+ const conts = containers
+ .filter(c => ['devmode', 'project'].includes(c.type))
+ .filter(d => d.containerName.toLowerCase().includes(filter));
+ return (
+ <TableComposable aria-label="Projects" variant={TableVariant.compact}>
+ <Thead>
+ <Tr>
+ <Th key='type'>Type</Th>
+ <Th key='container'>Container</Th>
+ <Th key='description'>Project Description</Th>
+ <Th key='ports'>Ports</Th>
+ <Th key='environment'>Environment</Th>
+ <Th key='camel'>Camel Health</Th>
+ {/*<Th key='action'></Th>*/}
+ </Tr>
+ </Thead>
+ <Tbody>
+ {conts.map(container => (
+ <Tr key={container.containerName}>
+ <Td style={{verticalAlign: "middle"}} modifier={"fitContent"}>
+ <Label variant={"outline"}>{container.type}</Label>
+ </Td>
+ <Td style={{verticalAlign: "middle"}}>
+ <Label color={container.lifeCycle === 'ready' ? "green" : 'grey'}>
+ {container.containerName}
+ </Label>
+ </Td>
+ <Td style={{verticalAlign: "middle"}}>
+ <HelperText>
+ <HelperTextItem>{getProject(container.containerName)?.description || "Camel project"}</HelperTextItem>
+ </HelperText>
+ </Td>
+ <Td>
+ <Flex direction={{default: "column"}}>
+ {container.ports.map(port => (
+ <FlexItem className="badge-flex-item" key={port}><Badge className="badge"
+ isRead={true}>{port}</Badge></FlexItem>
+ ))}
+ </Flex>
+ </Td>
+ <Td>
+ <Flex direction={{default: "column"}}>
+ {getContainerByEnvironments(container.containerName).map(value => (
+ <FlexItem className="badge-flex-item" key={value[0]}>
+ <Badge className={"badge"}>
+ {value[1] ? value[1]?.env : ""}
+ </Badge>
+ </FlexItem>
+ ))}
+ </Flex>
+ </Td>
+ <Td modifier={"fitContent"}>
+ <Flex direction={{default: "column"}}>
+ {getCamelStatusByEnvironments(container.containerName).map(value => {
+ // const color = value[1] ? (value[1].consumerStatus === "UP" ? "green" : "red") : "grey";
+ // let icon = undefined;
+ // if (value[1]?.consumerStatus === "UP") icon = <UpIcon/>
+ // if (value[1]?.consumerStatus === "DOWN") icon = <DownIcon/>
+ // const text = value[1] && value[1]?.contextVersion ? value[1]?.contextVersion : "???";
+ return <FlexItem key={value[0]}>
+ {/*<LabelGroup numLabels={4} className="camel-label-group">*/}
+ {/* <Label color={color} className="table-label" icon={icon}>{text}</Label>*/}
+ {/*</LabelGroup>*/}
+ </FlexItem>
+ })}
+ </Flex>
+ </Td>
+ </Tr>
+ ))}
+ {conts.length === 0 && getEmptyState()}
+ </Tbody>
+ </TableComposable>
)
}
+
+ const isKubernetes = config.infrastructure === 'kubernetes';
+ return (
+ <PageSection className="kamelet-section dashboard-page" padding={{default: 'noPadding'}}>
+ <PageSection className="tools-section" padding={{default: 'noPadding'}}>
+ <MainToolbar title={title()} tools={tools()}/>
+ </PageSection>
+ <PageSection isFilled className="kamelets-page">
+ {isKubernetes ? getKubernetesTable() : getDockerTable()}
+ </PageSection>
+ </PageSection>
+ )
+
}
\ No newline at end of file
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 4f74ab8b..735f99d0 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
@@ -22,23 +22,28 @@ import SearchIcon from '@patternfly/react-icons/dist/esm/icons/search-icon';
import {ProjectsTableRow} from "./ProjectsTableRow";
import {DeleteProjectModal} from "./DeleteProjectModal";
import {CreateProjectModal} from "./CreateProjectModal";
-import {useProjectsStore, useProjectStore} from "../api/ProjectStore";
+import {useAppConfigStore, useProjectsStore, useProjectStore} from "../api/ProjectStore";
import {ProjectService} from "../api/ProjectService";
import {MainToolbar} from "../designer/MainToolbar";
import {Project, ProjectType} from "../api/ProjectModels";
+import {shallow} from "zustand/esm/shallow";
export const ProjectsPage = () => {
- const {projects, setProjects} = useProjectsStore();
- const {operation} = useProjectStore();
+ 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)) ProjectService.refreshProjects();
+ if (!["create", "delete", "select", "copy"].includes(operation)) {
+ ProjectService.refreshProjects();
+ ProjectService.refreshAllDeploymentStatuses();
+ ProjectService.refreshAllContainerStatuses();
+ }
}, 1300);
return () => {
clearInterval(interval)
@@ -49,8 +54,11 @@ export const ProjectsPage = () => {
return <Toolbar id="toolbar-group-types">
<ToolbarContent>
<ToolbarItem>
- <Button variant="link" icon={<RefreshIcon/>} onClick={e =>
- ProjectService.refreshProjects()}/>
+ <Button variant="link" icon={<RefreshIcon/>} onClick={e => {
+ ProjectService.refreshProjects();
+ ProjectService.refreshAllDeploymentStatuses();
+ ProjectService.refreshAllContainerStatuses();
+ }}/>
</ToolbarItem>
<ToolbarItem>
<TextInput className="text-field" type="search" id="search" name="search"
diff --git a/karavan-web/karavan-app/src/main/webui/src/projects/ProjectsTableRow.tsx b/karavan-web/karavan-app/src/main/webui/src/projects/ProjectsTableRow.tsx
index a416035f..43b38dbf 100644
--- a/karavan-web/karavan-app/src/main/webui/src/projects/ProjectsTableRow.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/projects/ProjectsTableRow.tsx
@@ -12,9 +12,8 @@ import CopyIcon from "@patternfly/react-icons/dist/esm/icons/copy-icon";
import {DeploymentStatus, Project} from '../api/ProjectModels';
import {
useAppConfigStore,
- useDeploymentStatusesStore,
useLogStore,
- useProjectStore,
+ useProjectStore, useStatusesStore,
} from "../api/ProjectStore";
import {ProjectEventBus} from "../api/ProjectEventBus";
import {shallow} from "zustand/shallow";
@@ -25,7 +24,7 @@ interface Props {
export const ProjectsTableRow = (props: Props) => {
- const {statuses} = useDeploymentStatusesStore();
+ const [deployments, containers] = useStatusesStore((state) => [state.deployments, state.containers], shallow)
const {config} = useAppConfigStore();
const [setProject] = useProjectStore((state) => [state.setProject, state.setOperation], shallow);
const [setShowLog] = useLogStore((state) => [state.setShowLog], shallow);
@@ -34,11 +33,13 @@ export const ProjectsTableRow = (props: Props) => {
return config.environments && Array.isArray(config.environments) ? Array.from(config.environments) : [];
}
- function getDeploymentByEnvironments(name: string): [string, DeploymentStatus | undefined] [] {
+ function getStatusByEnvironments(name: string): [string, any] [] {
return getEnvironments().map(e => {
const env: string = e as string;
- const dep = statuses.find(d => d.name === name && d.env === env);
- return [env, dep];
+ const status = config.infrastructure === 'kubernetes'
+ ? deployments.find(d => d.name === name && d.env === env)
+ : containers.find(d => d.containerName === name && d.env === env);
+ return [env, status];
});
}
@@ -71,7 +72,7 @@ export const ProjectsTableRow = (props: Props) => {
<Td noPadding style={{width: "180px"}}>
{!isBuildIn &&
<Flex direction={{default: "row"}}>
- {getDeploymentByEnvironments(project.projectId).map(value => (
+ {getStatusByEnvironments(project.projectId).map(value => (
<FlexItem className="badge-flex-item" key={value[0]}>
<Badge className="badge" isRead={!value[1]}>{value[0]}</Badge>
</FlexItem>
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 58732c4b..efd1ee2c 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
@@ -250,6 +250,10 @@ public class InfinispanService {
return new ArrayList<>(serviceStatuses.values());
}
+ public List<ContainerStatus> getContainerStatuses() {
+ return new ArrayList<>(containerStatuses.values());
+ }
+
public List<ContainerStatus> getContainerStatuses(String projectId, String env) {
QueryFactory queryFactory = Search.getQueryFactory(containerStatuses);
return queryFactory.<ContainerStatus>create("FROM karavan.ContainerStatus WHERE projectId = :projectId AND env = :env")