Skip to content

Commit

Permalink
UI Improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
mgubaidullin committed Jan 30, 2025
1 parent a091b28 commit 33da37e
Show file tree
Hide file tree
Showing 14 changed files with 115 additions and 101 deletions.
4 changes: 3 additions & 1 deletion karavan-app/src/main/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ karavan.keycloak.backend.secret=karavan
# Git repository Configuration
karavan.git.repository=http://gitea:3000/karavan/karavan.git
karavan.git.username=karavan
karavan.git.password=karavan
karavan.git.password=karavankaravan
karavan.git.branch=main

karavan.private-key-path=
Expand Down Expand Up @@ -99,6 +99,8 @@ quarkus.kubernetes-client.devservices.enabled=false
%public.quarkus.swagger-ui.always-include=true

quarkus.quinoa.ci=false
quarkus.quinoa.package-manager-install=true
quarkus.quinoa.package-manager-install.node-version=22.9.0
quarkus.quinoa.dev-server.port=3003
quarkus.quinoa.dev-server.check-timeout=60000
quarkus.quinoa.ignored-path-prefixes=/ui,/public
7 changes: 5 additions & 2 deletions karavan-app/src/main/webui/src/api/ProjectModels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,12 @@ export enum ProjectType {
templates ='templates',
kamelets ='kamelets',
configuration ='configuration',
services ='services',
normal ='normal',
}

export const BUILD_IN_PROJECTS: string[] = [ProjectType.kamelets.toString(), ProjectType.templates.toString(), ProjectType.configuration.toString()];
export const BUILD_IN_PROJECTS: string[] = [ProjectType.kamelets.toString(), ProjectType.templates.toString(), ProjectType.configuration.toString(), ProjectType.services.toString()];
export const RESERVED_WORDS: string[] = [...BUILD_IN_PROJECTS, 'karavan'];

export class Project {
projectId: string = '';
Expand Down Expand Up @@ -68,6 +70,7 @@ export class DeploymentStatus {
replicas: number = 0;
readyReplicas: number = 0;
unavailableReplicas: number = 0;
type: 'devmode' | 'devservice' | 'project' | 'internal' | 'build' | 'unknown' = 'unknown';
}

export class ServiceStatus {
Expand Down Expand Up @@ -105,7 +108,7 @@ export class ContainerStatus {
ports: ContainerPort [] = [];
commands: string [] = [];
inTransit: boolean = false;
camelRuntime: string = ''
labels: any

public constructor(init?: Partial<ContainerStatus>) {
Object.assign(this, init);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ interface Props {
dark: boolean,
}

export const ConfigurationPage = (props: Props) => {
export const SystemPage = (props: Props) => {

const [tab, setTab] = useState<string | number>("statuses");

Expand Down Expand Up @@ -62,7 +62,7 @@ export const ConfigurationPage = (props: Props) => {

function title() {
return (<TextContent>
<Text component="h2">Configuration</Text>
<Text component="h2">System</Text>
</TextContent>);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,15 @@ import {
DescriptionListTerm,
Label,
} from '@patternfly/react-core';
import '../../designer/karavan.css';
import DownIcon from "@patternfly/react-icons/dist/esm/icons/error-circle-o-icon";
import UpIcon from "@patternfly/react-icons/dist/esm/icons/check-circle-icon";
import {ContainerStatus} from "../../api/ProjectModels";

import {ContainerStatus} from "../api/ProjectModels";

interface Props {
containerStatus: ContainerStatus,
}

export function InfoContainer (props: Props) {
export function InfoTabContainer (props: Props) {

function getPodInfoLabel(info: React.ReactNode) {
return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,17 @@ import {
Label, LabelGroup,
Tooltip
} from '@patternfly/react-core';
import '../../designer/karavan.css';
import DownIcon from "@patternfly/react-icons/dist/esm/icons/error-circle-o-icon";
import UpIcon from "@patternfly/react-icons/dist/esm/icons/check-circle-icon";
import {ContainerStatus} from "../../api/ProjectModels";
import {useProjectStore} from "../../api/ProjectStore";
import {shallow} from "zustand/shallow";

import {useProjectStore} from "../api/ProjectStore";
import {ContainerStatus} from "../api/ProjectModels";

interface Props {
containerStatus: ContainerStatus
}

export function InfoContext (props: Props) {
export function InfoTabContext (props: Props) {
const [camelStatuses] = useProjectStore((state) => [state.camelStatuses], shallow);

const camelStatus = camelStatuses.filter(s => s.containerName === props.containerStatus.containerName).at(0);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,17 @@ import {
Label, LabelGroup,
Tooltip
} from '@patternfly/react-core';
import '../../designer/karavan.css';
import DownIcon from "@patternfly/react-icons/dist/esm/icons/error-circle-o-icon";
import UpIcon from "@patternfly/react-icons/dist/esm/icons/check-circle-icon";
import {ContainerStatus} from "../../api/ProjectModels";
import {useProjectStore} from "../../api/ProjectStore";
import {shallow} from "zustand/shallow";

import {useProjectStore} from "../api/ProjectStore";
import { ContainerStatus } from '../api/ProjectModels';

interface Props {
containerStatus: ContainerStatus
}

export function InfoMemory (props: Props) {
export function InfoTabMemory (props: Props) {

const [camelStatuses] = useProjectStore((state) => [state.camelStatuses], shallow);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,60 +14,61 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React, {useEffect} from 'react';
import React from 'react';
import {
Card,
CardBody,
Flex,
FlexItem,
Divider,
PageSection,
EmptyState,
EmptyStateVariant,
EmptyStateHeader,
EmptyStateIcon,
Bullseye
} from '@patternfly/react-core';
import '../../designer/karavan.css';
import {InfoContainer} from "./InfoContainer";
import {InfoContext} from "./InfoContext";
import {InfoMemory} from "./InfoMemory";
import {useProjectStore, useStatusesStore} from "../../api/ProjectStore";
import {InfoTabContainer} from "./InfoTabContainer";
import {InfoTabContext} from "./InfoTabContext";
import {InfoTabMemory} from "./InfoTabMemory";
import {shallow} from "zustand/shallow";
import SearchIcon from "@patternfly/react-icons/dist/esm/icons/search-icon";
import {useStatusesStore} from "../api/ProjectStore";

export function DashboardTab() {
interface Props {
currentPodName: string
header?: React.ReactNode
}

const [project] = useProjectStore((state) => [state.project], shallow);
const [containers] = useStatusesStore((state) => [state.containers], shallow);
export function InformationLog(props: Props): JSX.Element {

const camelContainers = containers
.filter(c => c.projectId === project.projectId && ['devmode', 'project'].includes(c.type));
const [containers] = useStatusesStore((state) => [state.containers], shallow);
const camelContainers = containers.filter(cs => cs.containerName === props.currentPodName);

return (
<PageSection className="project-tab-panel" padding={{default: "padding"}}>
<div style={{display: "flex", flexDirection: "column", position: "relative", height: "100%"}}>
{props.header}
{camelContainers.map((containerStatus, index) =>
<Card className="dashboard-card" key={containerStatus.containerId}>
<Card key={containerStatus.containerId} isFlat isFullHeight>
<CardBody>
<Flex direction={{default: "row"}}
justifyContent={{default: "justifyContentSpaceBetween"}}>
<FlexItem flex={{default: "flex_1"}}>
<InfoContainer containerStatus={containerStatus}/>
<InfoTabContainer containerStatus={containerStatus}/>
</FlexItem>
<Divider orientation={{default: "vertical"}}/>
<FlexItem flex={{default: "flex_1"}}>
<InfoMemory containerStatus={containerStatus}/>
<InfoTabMemory containerStatus={containerStatus}/>
</FlexItem>
<Divider orientation={{default: "vertical"}}/>
<FlexItem flex={{default: "flex_1"}}>
<InfoContext containerStatus={containerStatus}/>
<InfoTabContext containerStatus={containerStatus}/>
</FlexItem>
</Flex>
</CardBody>
</Card>
)}
{camelContainers.length === 0 &&
<Card className="project-development">
<Card>
<CardBody>
<Bullseye>
<EmptyState variant={EmptyStateVariant.sm}>
Expand All @@ -78,6 +79,6 @@ export function DashboardTab() {
</CardBody>
</Card>
}
</PageSection>
</div>
)
}
113 changes: 65 additions & 48 deletions karavan-app/src/main/webui/src/log/ProjectLogPanel.tsx
Original file line number Diff line number Diff line change
@@ -1,46 +1,44 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import React, {useEffect, useState} from 'react';
import {Button, Checkbox, Label, PageSection, Tooltip, TooltipPosition} from '@patternfly/react-core';
import {
Button,
Checkbox,
Label,
PageSection, ToggleGroup, ToggleGroupItem,
Tooltip,
TooltipPosition
} from '@patternfly/react-core';
import './ProjectLog.css';
import CloseIcon from '@patternfly/react-icons/dist/esm/icons/times-icon';
import ExpandIcon from '@patternfly/react-icons/dist/esm/icons/expand-icon';
import CollapseIcon from '@patternfly/react-icons/dist/esm/icons/compress-icon';
import CleanIcon from '@patternfly/react-icons/dist/esm/icons/trash-alt-icon';
import {useLogStore} from "../api/ProjectStore";
import {useAppConfigStore, useLogStore, useProjectStore, useStatusesStore} from "../api/ProjectStore";
import {shallow} from "zustand/shallow";
import {ProjectEventBus} from "../api/ProjectEventBus";
import {ProjectLog} from "./ProjectLog";
import {LogWatchApi} from "../api/LogWatchApi";
import {InformationLog} from "./InformationLog";
import {ProjectService} from "../api/ProjectService";

const INITIAL_LOG_HEIGHT = "50%";

export function ProjectLogPanel () {
export function ProjectLogPanel() {

const [config] = useAppConfigStore((state) => [state.config], shallow);
const [project] = useProjectStore((s) => [s.project], shallow);
const [containers] = useStatusesStore((state) => [state.containers], shallow);
const [showLog, type, setShowLog, podName] = useLogStore(
(state) => [state.showLog, state.type, state.setShowLog, state.podName], shallow)

const [height, setHeight] = useState(INITIAL_LOG_HEIGHT);
const [isTextWrapped, setIsTextWrapped] = useState(true);
const [autoScroll, setAutoScroll] = useState(true);
const [controller, setController] = React.useState(new AbortController());
const [currentPodName, setCurrentPodName] = useState<string | undefined>(undefined);
const [tab, setTab] = useState<'log' | 'events' | 'main-configuration' | 'info'>('log');
const [controller, setController] = React.useState(new AbortController());

useEffect(() => {
setTab('log');
controller.abort()
const c = new AbortController();
setController(c);
Expand All @@ -61,39 +59,58 @@ export function ProjectLogPanel () {
}
}, [podName]);

useEffect(() => {
const interval = setInterval(() => refreshData(), 1300)
return () => clearInterval(interval);
}, [tab]);

function refreshData(){
if (tab === 'info') {
ProjectService.refreshCamelStatus(project.projectId, config.environment);
}
}

function getButtons() {
return (<div className="buttons">
<Label className="log-name">{podName !== undefined ? (type + ": " + podName) : ''}</Label>
<Tooltip content={"Clean log"} position={TooltipPosition.bottom}>
<Button variant="plain" onClick={() => ProjectEventBus.sendLog('set', '')} icon={<CleanIcon/>}/>
</Tooltip>
<Checkbox label="Wrap text" aria-label="wrap text checkbox" isChecked={isTextWrapped}
id="wrap-text-checkbox"
onChange={(_, checked) => setIsTextWrapped(checked)}/>
<Checkbox label="Autoscroll" aria-label="autoscroll checkbox" isChecked={autoScroll}
id="autoscroll-checkbox"
onChange={(_, checked) => setAutoScroll(checked)}/>
{/*<Tooltip content={"Scroll to bottom"} position={TooltipPosition.bottom}>*/}
{/* <Button variant="plain" onClick={() => } icon={<ScrollIcon/>}/>*/}
{/*</Tooltip>*/}
<Tooltip content={height === "100%" ? "Collapse" : "Expand"} position={TooltipPosition.bottom}>
const isKubernetes = config.infrastructure === 'kubernetes';
const title = isKubernetes ? ('Pod (' + type + "): " + podName) : (type + ": " + podName);
const containerType = containers.filter(c => c.containerName === podName).at(0)?.type;
const showMain= containerType !== undefined && ['devmode', 'project'].includes(containerType);
return (
<div className="buttons">
<Label className="log-name">{podName !== undefined ? title : ''}</Label>
<ToggleGroup isCompact style={{marginRight:'auto'}}>
<ToggleGroupItem key={0} isSelected={tab === 'log'} text='Log' onChange={_ => setTab('log')}/>
<ToggleGroupItem key={4} isSelected={tab === 'info'} text='Information' onChange={_ => setTab('info')}/>
</ToggleGroup>
<Tooltip content={"Clean log"} position={TooltipPosition.bottom}>
<Button variant="plain" onClick={() => ProjectEventBus.sendLog('set', '')} icon={<CleanIcon/>}/>
</Tooltip>
<Checkbox label="Wrap text" aria-label="wrap text checkbox" isChecked={isTextWrapped}
id="wrap-text-checkbox"
onChange={(_, checked) => setIsTextWrapped(checked)}/>
<Checkbox label="Autoscroll" aria-label="autoscroll checkbox" isChecked={autoScroll}
id="autoscroll-checkbox"
onChange={(_, checked) => setAutoScroll(checked)}/>
<Tooltip content={height === "100%" ? "Collapse" : "Expand"} position={TooltipPosition.bottom}>
<Button variant="plain" onClick={() => {
const h = height === "100%" ? INITIAL_LOG_HEIGHT : "100%";
setHeight(h);
ProjectEventBus.sendLog('add', ' ')
}} icon={height === "100%" ? <CollapseIcon/> : <ExpandIcon/>}/>
</Tooltip>
<Button variant="plain" onClick={() => {
const h = height === "100%" ? INITIAL_LOG_HEIGHT : "100%";
setHeight(h);
ProjectEventBus.sendLog('add', ' ')
}} icon={height === "100%" ? <CollapseIcon/> : <ExpandIcon/>}/>
</Tooltip>
<Button variant="plain" onClick={() => {
setShowLog(false, 'none');
setHeight(INITIAL_LOG_HEIGHT);
ProjectEventBus.sendLog('set', '')
}} icon={<CloseIcon/>}/>
</div>);
setShowLog(false, 'none');
setHeight(INITIAL_LOG_HEIGHT);
ProjectEventBus.sendLog('set', '')
}} icon={<CloseIcon/>}/>
</div>
);
}

return (showLog ?
<PageSection className="project-log" padding={{default: "noPadding"}} style={{height: height}}>
<ProjectLog autoScroll={autoScroll} isTextWrapped={isTextWrapped} header={getButtons()}/>
{tab === 'log' && <ProjectLog autoScroll={autoScroll} isTextWrapped={isTextWrapped} header={getButtons()}/>}
{tab === 'info' && currentPodName && <InformationLog currentPodName={currentPodName} header={getButtons()}/>}
</PageSection>
: <></>);
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import { ProjectService } from "../api/ProjectService";
interface Props {
dark: boolean,
}
export const KnowledgebaseWrapper = (props: Props) => {
export const HelpWrapper = (props: Props) => {

const [blockList, setBlockList] = useState<ProjectFile[]>();

Expand Down
Loading

0 comments on commit 33da37e

Please sign in to comment.