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 20f173dc8e2a0f25e53567f8ea2484877d9ca0e3
Author: Marat Gubaidullin <[email protected]>
AuthorDate: Fri Feb 27 16:25:43 2026 -0500

    Front-end Shared UI for 4.18.0
---
 .../main/webui/src/karavan/shared/monaco-setup.ts  | 33 ++++----
 .../src/karavan/shared/ui/ContainerButton.tsx      |  4 +-
 .../webui/src/karavan/shared/ui/MainToolbar.css    | 25 ++++++
 .../webui/src/karavan/shared/ui/MainToolbar.tsx    | 13 ++-
 .../src/karavan/shared/ui/ModalConfirmation.tsx    | 29 +++++--
 .../webui/src/karavan/shared/ui/PlatformLogos.css  | 37 +++++++++
 .../webui/src/karavan/shared/ui/PlatformLogos.tsx  | 95 ++++++++++++++++++++++
 .../webui/src/karavan/shared/ui/RightPanel.css     |  6 +-
 .../webui/src/karavan/shared/ui/RightPanel.tsx     |  2 +-
 .../src/karavan/shared/ui/SideBarFormWrapper.tsx   | 84 +++++++++++++++++++
 10 files changed, 300 insertions(+), 28 deletions(-)

diff --git a/karavan-app/src/main/webui/src/karavan/shared/monaco-setup.ts 
b/karavan-app/src/main/webui/src/karavan/shared/monaco-setup.ts
index 0b2c167c..397a93f3 100644
--- a/karavan-app/src/main/webui/src/karavan/shared/monaco-setup.ts
+++ b/karavan-app/src/main/webui/src/karavan/shared/monaco-setup.ts
@@ -1,18 +1,23 @@
-// Core API only (no full bundle)
-import 'monaco-editor/esm/vs/editor/editor.api';
+import {loader} from '@monaco-editor/react';
+import * as monaco from 'monaco-editor';
+import {type IRange, Position, Range} from 'monaco-editor';
+import editorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker';
+import jsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker';
+import YamlWorker from 'monaco-yaml/yaml.worker.js?worker';
 
-// Language services
-import 'monaco-editor/esm/vs/language/json/monaco.contribution';
+self.MonacoEnvironment = {
+    getWorker(_, label) {
+        if (label === 'json') {
+            return new jsonWorker();
+        }
+        if (label === 'yaml' ) {
+            return new YamlWorker()
+        }
+        return new editorWorker();
+    },
+};
 
-// Basic languages you need
-import 'monaco-editor/esm/vs/basic-languages/yaml/yaml.contribution';
-import 'monaco-editor/esm/vs/basic-languages/java/java.contribution';
-import 'monaco-editor/esm/vs/basic-languages/sql/sql.contribution';
-import 'monaco-editor/esm/vs/basic-languages/ini/ini.contribution';
-import 'monaco-editor/esm/vs/basic-languages/markdown/markdown.contribution';
-import 'monaco-editor/esm/vs/basic-languages/xml/xml.contribution';
+loader.config({ monaco });
 
-import {IRange, Position, Range} from "monaco-editor/esm/vs/editor/editor.api";
-// (Optional) If you want YAML schema validation instead of just syntax:
-export {Range, Position};
+export { Range, Position };
 export type { IRange };
diff --git 
a/karavan-app/src/main/webui/src/karavan/shared/ui/ContainerButton.tsx 
b/karavan-app/src/main/webui/src/karavan/shared/ui/ContainerButton.tsx
index 072fcf29..608540c8 100644
--- a/karavan-app/src/main/webui/src/karavan/shared/ui/ContainerButton.tsx
+++ b/karavan-app/src/main/webui/src/karavan/shared/ui/ContainerButton.tsx
@@ -23,7 +23,7 @@ import {useSelectedContainerStore} from 
"@stores/ProjectStore";
 import {ContainerStatus, ContainerType} from '@models/ProjectModels';
 import DevIcon from "@patternfly/react-icons/dist/esm/icons/dev-icon";
 import './ContainerButton.css'
-import {BuildIcon, LockIcon, PackageIcon, ServiceIcon, UnknownIcon} from 
'@patternfly/react-icons';
+import {BuildIcon, LockIcon, PackageIcon, UnknownIcon} from 
'@patternfly/react-icons';
 
 interface Props {
     container: ContainerStatus,
@@ -41,7 +41,6 @@ export function ContainerButton(props: Props) {
     const typeIconColor = isRunning ? 
'var(--pf-t--global--color--status--success--100)' : 
'var(--pf-v6-c-button--m-control--Color)';
     const iconMap: Record<ContainerType, ReactElement> = {
         devmode: <DevIcon color={typeIconColor}/>,
-        devservice: <ServiceIcon color={typeIconColor}/>,
         packaged: <PackageIcon color={typeIconColor}/>,
         internal: <LockIcon color={typeIconColor}/>,
         build: <BuildIcon color={typeIconColor}/>,
@@ -49,7 +48,6 @@ export function ContainerButton(props: Props) {
     };
     const typeMap: Record<ContainerType, string> = {
         devmode: 'container',
-        devservice: 'devservice',
         packaged: 'container',
         internal: 'internal',
         build: 'build',
diff --git a/karavan-app/src/main/webui/src/karavan/shared/ui/MainToolbar.css 
b/karavan-app/src/main/webui/src/karavan/shared/ui/MainToolbar.css
index a0906587..fff0a110 100644
--- a/karavan-app/src/main/webui/src/karavan/shared/ui/MainToolbar.css
+++ b/karavan-app/src/main/webui/src/karavan/shared/ui/MainToolbar.css
@@ -23,6 +23,31 @@
     flex-direction: row;
     justify-content: flex-start;
     align-items: center;
+
+    .logo {
+        width: 32px;
+        height: 32px;
+    }
+
+    .pf-v6-c-nav {
+        .pf-v6-c-nav__item {
+            .pf-m-current {
+                position: relative;
+                background: rgba(236, 122, 8, 0.25);
+                &::before {
+                    content: "";
+                    position: absolute;
+                    inset: 0;
+                    border-radius: inherit;
+                    border: 1px solid rgba(236, 122, 8, 0.66);
+                    pointer-events: none;
+                }
+            }
+        }
+        .pf-v6-c-nav__link:hover {
+            background: color-mix(in srgb, var(--pf-t--color--blue--60) 50%, 
transparent);
+        }
+    }
 }
 
 .karavan .main-toolbar h2 {
diff --git a/karavan-app/src/main/webui/src/karavan/shared/ui/MainToolbar.tsx 
b/karavan-app/src/main/webui/src/karavan/shared/ui/MainToolbar.tsx
index 29105e3b..0479e77c 100644
--- a/karavan-app/src/main/webui/src/karavan/shared/ui/MainToolbar.tsx
+++ b/karavan-app/src/main/webui/src/karavan/shared/ui/MainToolbar.tsx
@@ -16,27 +16,32 @@
  */
 
 import React from 'react';
-import './MainToolbar.css';
+import '@shared/ui/MainToolbar.css';
+import {useAppConfigStore} from "@stores/ProjectStore";
+import {shallow} from "zustand/shallow";
+import {PlatformVersionWithName} from "@shared/ui/PlatformLogos";
 
 interface Props {
     title: React.ReactNode;
     toolsStart?: React.ReactNode;
-    tools: React.ReactNode;
+    tools?: React.ReactNode;
 }
 
 export function MainToolbar(props: Props) {
 
     const { title, toolsStart, tools } = props;
+    const [config] = useAppConfigStore((s) => [s.config], shallow)
+    const titleSvg = config?.advanced.titleSvg ? config?.advanced.titleSvg : 
undefined;
 
     return (
         <div className="main-toolbar dark-toolbar">
-            {title}
+            {/*{title}*/}
             {toolsStart &&
                 <div style={{flex: 2}}>
                     {toolsStart}
                 </div>
             }
-            {tools}
+            {tools || PlatformVersionWithName(titleSvg)}
         </div>
     )
 }
diff --git 
a/karavan-app/src/main/webui/src/karavan/shared/ui/ModalConfirmation.tsx 
b/karavan-app/src/main/webui/src/karavan/shared/ui/ModalConfirmation.tsx
index 3f35df58..c873965b 100644
--- a/karavan-app/src/main/webui/src/karavan/shared/ui/ModalConfirmation.tsx
+++ b/karavan-app/src/main/webui/src/karavan/shared/ui/ModalConfirmation.tsx
@@ -6,7 +6,7 @@
  * (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
+ * 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,
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-import React from 'react';
+import React, {useEffect, useRef} from 'react';
 import {Button, Modal, ModalBody, ModalFooter, ModalHeader} from 
'@patternfly/react-core';
 
 export interface ModalConfirmationProps {
@@ -34,9 +34,23 @@ export interface ModalConfirmationProps {
 export function ModalConfirmation(props: ModalConfirmationProps) {
 
     const {title, isOpen, message, onConfirm, onCancel, btnConfirm, btnCancel, 
btnConfirmVariant, btnCancelVariant, variant} = props;
+
+    // 1. Create a ref for the button
+    const confirmBtnRef = useRef<HTMLButtonElement>(null);
+
+    // 2. Focus the button when the modal opens
+    useEffect(() => {
+        if (isOpen) {
+            // A small timeout ensures the modal DOM is ready before focusing
+            const timer = setTimeout(() => {
+                confirmBtnRef.current?.focus();
+            }, 100);
+            return () => clearTimeout(timer);
+        }
+    }, [isOpen]);
+
     return (
         <Modal
-            // className="modal-confirm"
             variant={variant ?? "small"}
             isOpen={isOpen}
             onClose={() => onCancel()}
@@ -46,7 +60,12 @@ export function ModalConfirmation(props: 
ModalConfirmationProps) {
                 {message}
             </ModalBody>
             <ModalFooter>
-                <Button key="confirm" variant={btnConfirmVariant || 'primary'} 
onClick={event => onConfirm()}>
+                <Button
+                    ref={confirmBtnRef} // 3. Attach the ref here
+                    key="confirm"
+                    variant={btnConfirmVariant || 'primary'}
+                    onClick={event => onConfirm()}
+                >
                     {btnConfirm || 'Confirm'}
                 </Button>
                 <Button key="cancel" variant={btnCancelVariant || 'secondary'} 
onClick={e => onCancel()}>
@@ -55,4 +74,4 @@ export function ModalConfirmation(props: 
ModalConfirmationProps) {
             </ModalFooter>
         </Modal>
     )
-}
+}
\ No newline at end of file
diff --git a/karavan-app/src/main/webui/src/karavan/shared/ui/PlatformLogos.css 
b/karavan-app/src/main/webui/src/karavan/shared/ui/PlatformLogos.css
new file mode 100644
index 00000000..9c38a052
--- /dev/null
+++ b/karavan-app/src/main/webui/src/karavan/shared/ui/PlatformLogos.css
@@ -0,0 +1,37 @@
+.platform-versions {
+    display: flex;
+    flex-direction: column;
+    gap: 10px;
+    align-items: center;
+
+    .pf-v6-c-label {
+        padding: 4px;
+    }
+}
+
+.platform-versions {
+
+}
+
+.platform-versions-item {
+    display: flex;
+    flex-direction: row;
+    gap: 3px;
+    align-items: center;
+
+    .platform-version {
+        font-size: var(--pf-t--global--font--size--body--sm);
+        line-height: var(--pf-t--global--font--size--body--sm);
+        color: var(--pf-t--color--gray--30);
+    }
+
+    .platform-small-logo {
+        width: 12px;
+        height: 12px;
+    }
+
+    .icon {
+        width: 12px;
+        height: 12px;
+    }
+}
diff --git a/karavan-app/src/main/webui/src/karavan/shared/ui/PlatformLogos.tsx 
b/karavan-app/src/main/webui/src/karavan/shared/ui/PlatformLogos.tsx
new file mode 100644
index 00000000..b6805abe
--- /dev/null
+++ b/karavan-app/src/main/webui/src/karavan/shared/ui/PlatformLogos.tsx
@@ -0,0 +1,95 @@
+import React from "react";
+import {Badge, Label} from "@patternfly/react-core";
+import PlatformLogo from "@app/navigation/PlatformLogo";
+import './PlatformLogos.css'
+
+export const KARAVAN_PLATFORM_VERSION = "4.18.0";
+
+interface PlatformVersionProps {
+    environment: string
+    short?: boolean
+}
+
+export function PlatformVersion(props: PlatformVersionProps) {
+    const {environment, short} = props;
+    const full = short !== true;
+    const badgeClassName = environment === 'dev' ? 'environment-dev'
+        : (environment === 'prod' ? 'environment-prod' : 
'var(environment-default)');
+
+    return (
+        <div className="platform-versions">
+            <Label variant='outline' color='blue'>
+                <div className='platform-versions-item'>
+                    {full && PlatformLogo("platform-small-logo")}
+                    <p 
className='platform-version'>{KARAVAN_PLATFORM_VERSION}</p>
+                    <Badge className={badgeClassName}>{environment || 
''}</Badge>
+                </div>
+            </Label>
+        </div>
+    )
+}
+
+export function PlatformVersions() {
+    return (
+        <div style={{display: 'flex', flexDirection: 'row', justifyContent: 
'center', alignItems: "center", gap: 6, paddingBottom: 6}}>
+            <div className='platform-versions-item'>
+                {PlatformLogo("platform-small-logo")}
+                <p style={{fontSize: '12px', color: 
'var(--pf-t--color--gray--30)'}}>{KARAVAN_PLATFORM_VERSION}</p>
+            </div>
+        </div>
+    )
+}
+
+export function PlatformVersionWithName() {
+    const brand = (
+        <div style={{display: 'flex', flexDirection: 'row', alignItems: 
'center', gap: 8}}>
+            <div>
+                {PlatformName(20)}
+            </div>
+        </div>
+    )
+
+    return (<div style={{
+        padding: '0',
+        height: '100%',
+        display: 'flex',
+        flexDirection: 'column',
+        justifyContent: 'center',
+        alignItems: 'end',
+    }}>
+        {brand}
+    </div>)
+}
+
+export function PlatformNameForToolbar() {
+    return (<div style={{
+        height: '100%',
+        display: 'flex',
+        flexDirection: 'column',
+        justifyContent: 'center',
+        alignItems: 'center',
+        gap: '3px',
+    }}>
+        {PlatformName()}
+    </div>)
+}
+
+export const PlatformName = (height: number | string = 20, width: number | 
string = 292.797) => (
+    <svg
+        xmlns="http://www.w3.org/2000/svg";
+        width={width}
+        height={height}
+        preserveAspectRatio="xMidYMid"
+        viewBox="0 0 292 20"
+    >
+        <path
+            d="M44.653 
161.954V26.52H0V3.287h114.694V26.52H70.04v135.434Zm68.34 0L174.647 
3.287h27.54l63.24 158.667h-28.673l-14.62-37.853h-67.207l-14.054 
37.853zm48.507-59.727h53.834l-27.427-69.133Zm126.934 
59.727V3.287h25.386v135.32h69.134v23.347zm123.307 
0V3.287h25.386v158.667Zm118.433 3.4q-16.433 
0-32.186-4.647-15.64-4.76-26.407-13.6l12.467-19.38q5.666 4.874 13.146 8.387 
7.594 3.513 16.094 5.44 8.5 1.813 17 1.813 15.526 0 25.046-5.893 9.634-5.893 
9.634-17.227 0-8.5-6.574-14.393-6.46-5 [...]
+            aria-label="KARAVAN"
+            style={{
+                fill: "#06c",
+                strokeWidth: 5.3487,
+            }}
+            transform="scale(.12095)"
+        />
+    </svg>
+)
diff --git a/karavan-app/src/main/webui/src/karavan/shared/ui/RightPanel.css 
b/karavan-app/src/main/webui/src/karavan/shared/ui/RightPanel.css
index e97f422c..c870c98b 100644
--- a/karavan-app/src/main/webui/src/karavan/shared/ui/RightPanel.css
+++ b/karavan-app/src/main/webui/src/karavan/shared/ui/RightPanel.css
@@ -5,7 +5,7 @@
     flex-direction: column;
     justify-content: stretch;
     overflow: hidden;
-    background-color: var(--pf-t--color--gray--95);
+    background: var(--pf-t--color--gray--95);
 }
 .karavan .right-panel .right-panel-top {
     height: 64px; /* or whatever fixed height you want */
@@ -21,6 +21,10 @@
     border-top-left-radius: var(--pf-t--global--border--radius--300);
 }
 
+.pf-v6-theme-dark .karavan .right-panel .right-panel-wrapper {
+    background: var(--pf-t--color--gray--90);
+}
+
 .karavan .right-panel .right-panel-wrapper .right-panel-card {
     height: 100%;
     display: flex;
diff --git a/karavan-app/src/main/webui/src/karavan/shared/ui/RightPanel.tsx 
b/karavan-app/src/main/webui/src/karavan/shared/ui/RightPanel.tsx
index d6805fc8..5b8c4157 100644
--- a/karavan-app/src/main/webui/src/karavan/shared/ui/RightPanel.tsx
+++ b/karavan-app/src/main/webui/src/karavan/shared/ui/RightPanel.tsx
@@ -6,7 +6,7 @@ import {ErrorBoundaryWrapper} from 
"@shared/ui/ErrorBoundaryWrapper";
 interface Props {
     title: React.ReactNode;
     toolsStart?: React.ReactNode;
-    tools: React.ReactNode;
+    tools?: React.ReactNode;
     mainPanel: React.ReactNode;
 }
 
diff --git 
a/karavan-app/src/main/webui/src/karavan/shared/ui/SideBarFormWrapper.tsx 
b/karavan-app/src/main/webui/src/karavan/shared/ui/SideBarFormWrapper.tsx
new file mode 100644
index 00000000..257610a3
--- /dev/null
+++ b/karavan-app/src/main/webui/src/karavan/shared/ui/SideBarFormWrapper.tsx
@@ -0,0 +1,84 @@
+import React from 'react';
+import {Button, Form} from "@patternfly/react-core";
+import {UseFormReturn} from "react-hook-form";
+
+interface AsyncFormWrapperProps {
+    children: React.ReactNode;
+    formContext: UseFormReturn<any>; // Accepts any form context
+    selectedId: string | null;
+    onSave?: (data: any) => void;
+    onCancel?: () => void;
+    onDelete?: () => void;
+    showDelete?: boolean;
+    isSubmitDisabled?: boolean;
+    footer?: React.ReactNode;
+}
+
+export function SideBarFormWrapper({
+                                       children,
+                                       formContext,
+                                       selectedId,
+                                       onSave,
+                                       onCancel,
+                                       onDelete,
+                                       showDelete = false,
+                                       isSubmitDisabled = false,
+                                       footer
+                                   }: AsyncFormWrapperProps) {
+
+    const {formState: {errors}, handleSubmit, getValues} = formContext;
+
+    function onKeyDown(event: React.KeyboardEvent<HTMLFormElement>): void {
+        event.stopPropagation();
+        if (event.key === 'Enter') {
+            event.preventDefault();
+            handleSubmit(onSave)();
+        }
+    }
+
+    // Common Footer Logic
+    function getFooter() {
+        const isDisabled = Object.getOwnPropertyNames(errors).length > 0 || 
isSubmitDisabled;
+        return (
+            <div style={{display: "flex", flexDirection: "row", gap: 8, 
justifyContent: "space-between", marginTop: 16}}>
+                <div style={{flexGrow: 1}}>
+                    {showDelete &&
+                        <Button
+                            key="delete"
+                            variant={"danger"}
+                            isDanger
+                            isDisabled={isDisabled}
+                            onClick={onDelete}
+                        >
+                            Delete
+                        </Button>
+                    }
+                </div>
+                <Button
+                    key="confirm"
+                    variant={"primary"}
+                    isDisabled={isDisabled}
+                    onClick={handleSubmit(onSave)}
+                >
+                    Save
+                </Button>
+                <Button
+                    key="cancel"
+                    variant="link" // Standardized to "link" or "secondary" as 
you prefer
+                    onClick={onCancel}
+                >
+                    Cancel
+                </Button>
+            </div>
+        )
+    }
+
+    return (
+        <Form isHorizontal={true} autoComplete="off" noValidate 
onKeyDown={onKeyDown}>
+            <div style={{display: "flex", flexDirection: "column", gap: 16}}>
+                {children}
+            </div>
+            {footer || getFooter()}
+        </Form>
+    );
+}
\ No newline at end of file

Reply via email to