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/24 19:57:46 UTC
[camel-karavan] 01/02: List of Docker containers #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 0050ccf796aa087fc6912947f2b896415052d2fa
Author: Marat Gubaidullin <ma...@gmail.com>
AuthorDate: Sun Jul 23 18:35:59 2023 -0400
List of Docker containers #817
---
.../camel/karavan/docker/DockerEventListener.java | 3 +-
.../apache/camel/karavan/docker/DockerService.java | 8 +-
.../karavan-app/src/main/webui/src/Main.tsx | 2 +
.../src/main/webui/src/api/ProjectModels.ts | 1 +
.../webui/src/containers/ContainerTableRow.tsx | 94 ++++++++++++
.../main/webui/src/containers/ContainersPage.tsx | 160 +++++++++++++++++++++
.../src/main/webui/src/dashboard/DashboardPage.tsx | 15 +-
.../main/webui/src/projects/ProjectsTableRow.tsx | 17 ++-
.../src/main/webui/src/services/ServicesPage.tsx | 2 +-
.../karavan/infinispan/model/ContainerStatus.java | 4 +-
10 files changed, 282 insertions(+), 24 deletions(-)
diff --git a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/DockerEventListener.java b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/DockerEventListener.java
index 8d4c9cc3..b055716d 100644
--- a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/DockerEventListener.java
+++ b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/DockerEventListener.java
@@ -96,13 +96,14 @@ public class DockerEventListener implements ResultCallback<Event> {
String created = Instant.ofEpochSecond(container.getCreated()).toString();
ContainerStatus ci = infinispanService.getContainerStatus(name, environment, name);
if (ci == null) {
- ci = ContainerStatus.createWithId(name, environment, container.getId(), ports, type, lc, created);
+ ci = ContainerStatus.createWithId(name, environment, container.getId(), container.getImage(), ports, type, lc, created);
} else {
ci.setContainerId(container.getId());
ci.setPorts(ports);
ci.setType(type);
ci.setLifeCycle(lc);
ci.setCreated(created);
+ ci.setImage(container.getImage());
}
infinispanService.saveContainerStatus(ci);
}
diff --git a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/DockerService.java b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/DockerService.java
index 9f1f4198..27d5a6b3 100644
--- a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/DockerService.java
+++ b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/DockerService.java
@@ -145,13 +145,7 @@ public class DockerService {
public void collectContainersStatuses() {
getDockerClient().listContainersCmd().exec().forEach(container -> {
- String name = container.getNames()[0].replace("/", "");
- if (!Objects.equals(name, INFINISPAN_CONTAINER_NAME)) {
- dockerEventListener.onCreateContainer(container, new Event(container.getStatus(), container.getId(), container.getImage(), container.getCreated()));
- }
- if (Objects.equals(container.getLabels().get(LABEL_TYPE), ContainerStatus.CType.devmode.name())) {
- dockerEventListener.onCreateContainer(container, new Event(container.getStatus(), container.getId(), container.getImage(), container.getCreated()));
- }
+ dockerEventListener.onCreateContainer(container, new Event(container.getStatus(), container.getId(), container.getImage(), container.getCreated()));
});
}
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 9e36ffa3..8adfe79d 100644
--- a/karavan-web/karavan-app/src/main/webui/src/Main.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/Main.tsx
@@ -24,6 +24,7 @@ import ServicesIcon from "@patternfly/react-icons/dist/js/icons/services-icon";
import ComponentsIcon from "@patternfly/react-icons/dist/js/icons/module-icon";
import {MainLogin} from "./MainLogin";
import {DashboardPage} from "./dashboard/DashboardPage";
+import {ContainersPage} from "./containers/ContainersPage";
import {ProjectEventBus} from "./api/ProjectEventBus";
import {AppConfig, ContainerStatus, Project, ToastMessage} from "./api/ProjectModels";
import {ProjectPage} from "./project/ProjectPage";
@@ -216,6 +217,7 @@ export const Main = () => {
{pageId === 'projects' && <ProjectsPage key={request}/>}
{pageId === 'project' && <ProjectPage key="projects"/>}
{pageId === 'services' && <ServicesPage key="services"/>}
+ {pageId === 'containers' && <ContainersPage key="containers"/>}
{pageId === 'knowledgebase' && <KnowledgebasePage dark={false}/>}
</FlexItem>
</Flex>
diff --git a/karavan-web/karavan-app/src/main/webui/src/api/ProjectModels.ts b/karavan-web/karavan-app/src/main/webui/src/api/ProjectModels.ts
index 73be3256..fe0fb19e 100644
--- a/karavan-web/karavan-app/src/main/webui/src/api/ProjectModels.ts
+++ b/karavan-web/karavan-app/src/main/webui/src/api/ProjectModels.ts
@@ -69,6 +69,7 @@ export class ServiceStatus {
export class ContainerStatus {
containerName: string = '';
+ containerId: string = '';
lifeCycle: string = '';
deployment: string = '';
projectId: string = '';
diff --git a/karavan-web/karavan-app/src/main/webui/src/containers/ContainerTableRow.tsx b/karavan-web/karavan-app/src/main/webui/src/containers/ContainerTableRow.tsx
new file mode 100644
index 00000000..29241c44
--- /dev/null
+++ b/karavan-web/karavan-app/src/main/webui/src/containers/ContainerTableRow.tsx
@@ -0,0 +1,94 @@
+import React, {useState} from 'react';
+import {
+ Button,
+ Tooltip,
+ Flex, FlexItem, Label, Badge
+} from '@patternfly/react-core';
+import '../designer/karavan.css';
+import {ExpandableRowContent, Tbody, Td, Tr} from "@patternfly/react-table";
+import StopIcon from "@patternfly/react-icons/dist/js/icons/stop-icon";
+import PlayIcon from "@patternfly/react-icons/dist/esm/icons/play-icon";
+import {ContainerStatus} from "../api/ProjectModels";
+
+interface Props {
+ index: number
+ container: ContainerStatus
+}
+
+export const ContainerTableRow = (props: Props) => {
+
+ const [isExpanded, setIsExpanded] = useState<boolean>(false);
+ const [running, setRunning] = useState<boolean>(false);
+
+ const container = props.container;
+ const env = container.env;
+ const ports = container.ports;
+ const icon = running ? <StopIcon/> : <PlayIcon/>;
+ const tooltip = running ? "Stop container" : "Start container";
+ const color = container.lifeCycle === 'ready' ? "green" : "grey";
+ return (
+ <Tbody isExpanded={isExpanded}>
+ <Tr key={container.containerName}>
+ <Td expand={
+ container.containerName
+ ? {
+ rowIndex: props.index,
+ isExpanded: isExpanded,
+ onToggle: () => setIsExpanded(!isExpanded),
+ expandId: 'composable-expandable-example'
+ }
+ : undefined}
+ modifier={"fitContent"}>
+ </Td>
+ <Td style={{verticalAlign: "middle"}} modifier={"fitContent"}>
+ <Badge className="badge">{container.type}</Badge>
+ </Td>
+ <Td>
+ <Label color={color}>{container.containerName}</Label>
+ </Td>
+ <Td>{container.image}</Td>
+ <Td>
+ <Label color={color}>{container.cpuInfo}</Label>
+ </Td>
+ <Td>
+ <Label color={color}>{container.memoryInfo}</Label>
+ </Td>
+ {/*<Td>{container.environment}</Td>*/}
+ <Td className="project-action-buttons">
+ <Flex direction={{default: "row"}} justifyContent={{default: "justifyContentFlexEnd"}}
+ spaceItems={{default: 'spaceItemsNone'}}>
+ <FlexItem>
+ <Tooltip content={tooltip} position={"bottom"}>
+ <Button variant={"plain"} icon={icon} onClick={e => {
+ // setProject(project, "delete");
+ }}></Button>
+ </Tooltip>
+ </FlexItem>
+ </Flex>
+ </Td>
+ </Tr>
+ {<Tr isExpanded={isExpanded}>
+ <Td></Td>
+ <Td colSpan={2}>Container ID</Td>
+ <Td colSpan={2}>
+ <ExpandableRowContent>
+ <Flex direction={{default: "column"}} cellPadding={"0px"}>
+ {container.containerId}
+ </Flex>
+ </ExpandableRowContent>
+ </Td>
+ </Tr>}
+ {ports !== undefined && ports.length > 0 && <Tr isExpanded={isExpanded}>
+ <Td></Td>
+ <Td colSpan={2}>Ports</Td>
+ <Td colSpan={2}>
+ <ExpandableRowContent>
+ <Flex direction={{default: "row"}} cellPadding={"0px"}>
+ {ports.map(port => <FlexItem>{port}</FlexItem>)}
+ </Flex>
+ </ExpandableRowContent>
+ </Td>
+ </Tr>}
+ </Tbody>
+ )
+}
\ No newline at end of file
diff --git a/karavan-web/karavan-app/src/main/webui/src/containers/ContainersPage.tsx b/karavan-web/karavan-app/src/main/webui/src/containers/ContainersPage.tsx
new file mode 100644
index 00000000..34d44ff7
--- /dev/null
+++ b/karavan-web/karavan-app/src/main/webui/src/containers/ContainersPage.tsx
@@ -0,0 +1,160 @@
+import React, {useEffect, useState} from 'react';
+import {
+ Badge, Bullseye,
+ Button, EmptyState, EmptyStateIcon, EmptyStateVariant,
+ Flex,
+ FlexItem, HelperText, HelperTextItem, Label, LabelGroup,
+ PageSection, Spinner,
+ Text,
+ TextContent,
+ TextInput, Title, ToggleGroup, ToggleGroupItem,
+ Toolbar,
+ ToolbarContent,
+ ToolbarItem, Tooltip
+} from '@patternfly/react-core';
+import '../designer/karavan.css';
+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";
+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";
+import {shallow} from "zustand/shallow";
+import {Service} from "../api/ServiceModels";
+import {ServicesTableRow} from "../services/ServicesTableRow";
+import {ContainerTableRow} from "./ContainerTableRow";
+
+export const ContainersPage = () => {
+
+ const [config] = useAppConfigStore((state) => [state.config], shallow)
+ const [containers, setContainers] = useStatusesStore((state) => [state.containers, state.setContainers], shallow);
+ const [filter, setFilter] = useState<string>('');
+ const [loading, setLoading] = useState<boolean>(true);
+ const [selectedEnv, setSelectedEnv] = useState<string[]>([config.environment]);
+
+ useEffect(() => {
+ const interval = setInterval(() => {
+ onGetProjects()
+ }, 2000);
+ return () => {
+ clearInterval(interval)
+ };
+ }, []);
+
+ function onGetProjects() {
+ KaravanApi.getConfiguration((config: any) => {
+ KaravanApi.getAllContainerStatuses((statuses: ContainerStatus[]) => {
+ setContainers(statuses);
+ setLoading(false);
+ });
+ });
+ }
+
+ function selectEnvironment(name: string, selected: boolean) {
+ if (selected && !selectedEnv.includes(name)) {
+ setSelectedEnv((state: string[]) => {
+ state.push(name);
+ return state;
+ })
+ } else if (!selected && selectedEnv.includes(name)) {
+ setSelectedEnv((state: string[]) => {
+ return state.filter(e => e !== name)
+ })
+ }
+ }
+
+ 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 by name"
+ value={filter}
+ onChange={e => setFilter(e)}/>
+ </ToolbarItem>
+ </ToolbarContent>
+ </Toolbar>);
+ }
+
+ function title() {
+ return (<TextContent>
+ <Text component="h2">Containers</Text>
+ </TextContent>);
+ }
+
+ function getSelectedEnvironments(): string [] {
+ return config.environments.filter(e => selectedEnv.includes(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 getEmptyState() {
+ return (
+ <Tr>
+ <Td colSpan={8}>
+ <Bullseye>
+ {loading && <Spinner className="progress-stepper" isSVG diameter="80px" aria-label="Loading..."/>}
+ {!loading &&
+ <EmptyState variant={EmptyStateVariant.small}>
+ <EmptyStateIcon icon={SearchIcon}/>
+ <Title headingLevel="h2" size="lg">
+ No results found
+ </Title>
+ </EmptyState>
+ }
+ </Bullseye>
+ </Td>
+ </Tr>
+ )
+ }
+
+ const conts = containers.filter(d => d.containerName.toLowerCase().includes(filter));
+ 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">
+ <TableComposable aria-label="Projects" variant={TableVariant.compact}>
+ <Thead>
+ <Tr>
+ <Th />
+ <Th key='type'>Type</Th>
+ <Th key='name'>Name</Th>
+ <Th key='image'>Image</Th>
+ <Th key='cpuInfo'>CPU</Th>
+ <Th key='memoryInfo'>Memory</Th>
+ <Th key='action'></Th>
+ </Tr>
+ </Thead>
+ {conts?.map((container: ContainerStatus, index: number) => (
+ <ContainerTableRow key={container.containerName} index={index} container={container}/>
+ ))}
+ {conts?.length === 0 && getEmptyState()}
+ </TableComposable>
+ </PageSection>
+ </PageSection>
+ )
+
+}
\ No newline at end of file
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 4f2cc9ca..b338b3bc 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
@@ -63,6 +63,7 @@ export const DashboardPage = () => {
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);
@@ -75,7 +76,6 @@ export const DashboardPage = () => {
// })
});
});
- setLoading(false);
});
}
@@ -107,7 +107,7 @@ export const DashboardPage = () => {
</ToolbarItem>
<ToolbarItem>
<TextInput className="text-field" type="search" id="search" name="search"
- autoComplete="off" placeholder="Search deployment by name"
+ autoComplete="off" placeholder="Search by name"
value={filter}
onChange={e => setFilter(e)}/>
</ToolbarItem>
@@ -322,7 +322,7 @@ export const DashboardPage = () => {
{conts.map(container => (
<Tr key={container.containerName}>
<Td style={{verticalAlign: "middle"}} modifier={"fitContent"}>
- <Label variant={"outline"}>{container.type}</Label>
+ <Badge className="badge">{container.type}</Badge>
</Td>
<Td style={{verticalAlign: "middle"}}>
<Label color={container.lifeCycle === 'ready' ? "green" : 'grey'}>
@@ -337,8 +337,9 @@ export const DashboardPage = () => {
<Td>
<Flex direction={{default: "column"}}>
{container.ports.map(port => (
- <FlexItem className="badge-flex-item" key={port}><Badge className="badge"
- isRead={true}>{port}</Badge></FlexItem>
+ <FlexItem className="badge-flex-item" key={port}>
+ <Badge className="badge" isRead={true}>{port}</Badge>
+ </FlexItem>
))}
</Flex>
</Td>
@@ -346,9 +347,9 @@ export const DashboardPage = () => {
<Flex direction={{default: "column"}}>
{getContainerByEnvironments(container.containerName).map(value => (
<FlexItem className="badge-flex-item" key={value[0]}>
- <Badge className={"badge"}>
+ <Label color={"green"}>
{value[1] ? value[1]?.env : ""}
- </Badge>
+ </Label>
</FlexItem>
))}
</Flex>
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 43b38dbf..d2a7323a 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
@@ -3,7 +3,7 @@ import {
Button,
Badge,
Tooltip,
- Flex, FlexItem
+ Flex, FlexItem, Label
} from '@patternfly/react-core';
import '../designer/karavan.css';
import { Td, Tr} from "@patternfly/react-table";
@@ -17,6 +17,8 @@ import {
} from "../api/ProjectStore";
import {ProjectEventBus} from "../api/ProjectEventBus";
import {shallow} from "zustand/shallow";
+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";
interface Props {
project: Project
@@ -66,17 +68,20 @@ export const ProjectsTableRow = (props: Props) => {
<Td>{project.description}</Td>
<Td isActionCell>
<Tooltip content={project.lastCommit} position={"bottom"}>
- <Badge>{project.lastCommit?.substr(0, 7)}</Badge>
+ <Badge className="badge">{project.lastCommit?.substr(0, 7)}</Badge>
</Tooltip>
</Td>
<Td noPadding style={{width: "180px"}}>
{!isBuildIn &&
<Flex direction={{default: "row"}}>
- {getStatusByEnvironments(project.projectId).map(value => (
- <FlexItem className="badge-flex-item" key={value[0]}>
- <Badge className="badge" isRead={!value[1]}>{value[0]}</Badge>
+ {getStatusByEnvironments(project.projectId).map(value => {
+ const active = value[1];
+ const color = active ? "green" : "grey"
+ const style = active ? {fontWeight: "bold"} : {}
+ return <FlexItem className="badge-flex-item" key={value[0]}>
+ <Label style={style} color={color} >{value[0]}</Label>
</FlexItem>
- ))}
+ })}
</Flex>
}
</Td>
diff --git a/karavan-web/karavan-app/src/main/webui/src/services/ServicesPage.tsx b/karavan-web/karavan-app/src/main/webui/src/services/ServicesPage.tsx
index 940723a0..54a101be 100644
--- a/karavan-web/karavan-app/src/main/webui/src/services/ServicesPage.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/services/ServicesPage.tsx
@@ -67,7 +67,7 @@ export const ServicesPage = () => {
function title() {
return <TextContent>
- <Text component="h2">Services</Text>
+ <Text component="h2">Dev Services</Text>
</TextContent>
}
diff --git a/karavan-web/karavan-infinispan/src/main/java/org/apache/camel/karavan/infinispan/model/ContainerStatus.java b/karavan-web/karavan-infinispan/src/main/java/org/apache/camel/karavan/infinispan/model/ContainerStatus.java
index 66d983d8..de1fea4e 100644
--- a/karavan-web/karavan-infinispan/src/main/java/org/apache/camel/karavan/infinispan/model/ContainerStatus.java
+++ b/karavan-web/karavan-infinispan/src/main/java/org/apache/camel/karavan/infinispan/model/ContainerStatus.java
@@ -92,8 +92,8 @@ public class ContainerStatus {
return new ContainerStatus(projectId, projectId, null, null, null, env, CType.devmode, null, null, null, Lifecycle.init, false, false);
}
- public static ContainerStatus createWithId(String name, String env, String containerId, List<Integer> ports, CType type, Lifecycle lifeCycle, String created) {
- return new ContainerStatus(name, name, containerId, null, ports, env, type,
+ public static ContainerStatus createWithId(String name, String env, String containerId, String image, List<Integer> ports, CType type, Lifecycle lifeCycle, String created) {
+ return new ContainerStatus(name, name, containerId, image, ports, env, type,
null, null, created, lifeCycle, false, false);
}