Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
…e-browser into gh-pages
  • Loading branch information
nicholasdgoodman committed Jan 21, 2025
2 parents 896121d + e7805ed commit 91d32c1
Show file tree
Hide file tree
Showing 18 changed files with 680 additions and 120 deletions.
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,13 @@ Open a browser and navigate to http://localhost:4173/stable/ or http://localhost

_Commit the changes and push back to the repo_
```
git add -A
git commit -a -m "Update latest with new feature ..."
git push origin gh-pages
```

A note to maintainers: the build process will insert a timestamp into the `index.html` which can be observed for troubleshooting by inspecting the page content. For example, the HTML of the website should begin as follows:
```
<!doctype html>
<html lang="en" data-build-time="2024-12-19T22:03:42.043Z">
```
2 changes: 1 addition & 1 deletion index.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<!doctype html>
<html lang="en">
<html lang="en" data-build-time="%BUILD_TIME%">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="./favicon.ico" />
Expand Down
5 changes: 5 additions & 0 deletions src/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ iframe {
flex: 1;
}

svg {
min-width: fit-content;
min-height: fit-content;
}

#app {
border: 1px solid var(--surface-border);
background-color: var(--surface-ground);
Expand Down
10 changes: 5 additions & 5 deletions src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ import 'primeicons/primeicons.css';
import './App.css';

export default function App() {
const [selectedQueue, setSelectedQueue] = useState({});
const [selectedSource, setSelectedSource] = useState({});
const [selectedMessage, setSelectedMessage] = useState({});

const { brokers, brokerEditor } = useBrokerConfig();

const handleQueueSelected = (queue) => {
setSelectedQueue(queue);
const handleSourceSelected = (queue) => {
setSelectedSource(queue);
setSelectedMessage({});
};

Expand All @@ -35,11 +35,11 @@ export default function App() {
<>
<RootLayout>
<RootLayout.LeftPanel>
<TreeView brokers={brokers} brokerEditor={brokerEditor} onQueueSelected={handleQueueSelected} />
<TreeView brokers={brokers} brokerEditor={brokerEditor} onSourceSelected={handleSourceSelected} />
</RootLayout.LeftPanel>
<RootLayout.CenterPanel>
<MessageList
queueDefinition={selectedQueue}
sourceDefinition={selectedSource}
selectedMessage={selectedMessage}
onMessageSelect={handleMessageSelect}
/>
Expand Down
5 changes: 3 additions & 2 deletions src/components/BrokerConfigDialog/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,10 @@ export default function BrokerConfigDialog( { config, brokerEditor, onHide }) {
}

const Header = () => (
(!visible) ? null :
(values.id) ?
<>Edit Broker</> :
<>Add New Broker</>
<>Edit Broker</> :
<>Add New Broker</>
);

const Footer = () => (
Expand Down
188 changes: 138 additions & 50 deletions src/components/BrokerQueueTreeView/index.jsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,24 @@
import { useState } from 'react';
import { useSempApi } from '../../providers/SempClientProvider';

import { PrimeIcons } from 'primereact/api';
import { Button } from 'primereact/button';
import { Tree } from 'primereact/tree';
import { Toolbar } from 'primereact/toolbar';

import ContentPanel from '../ContentPanel';
import BrokerConfigDialog from '../BrokerConfigDialog';
import ReplayTopicDialog from '../ReplayTopicDialog';

import { TopicIcon, LvqIcon, QueueIcon } from '../../icons';

import classes from './styles.module.css';

export default function TreeView({ brokers, brokerEditor, onQueueSelected }) {
const [ brokerForConfig, setBrokerForConfig ] = useState(null);
const [ isLoading, setIsLoading ] = useState(false);
export default function TreeView({ brokers, brokerEditor, onSourceSelected }) {
const [brokerForConfig, setBrokerForConfig] = useState(null);
const [brokerAndReplayTopic, setBrokerAndReplayTopic] = useState(null);
const [isLoading, setIsLoading] = useState(false);

const [queuesListMap, setQueuesListMap] = useState({});
const [topicsListMap, setTopicsListMap] = useState({});

const [ queuesListMap, setQueuesListMap ] = useState({});

const sempApi = useSempApi();

const getBrokerIcon = (testResult) => (
Expand All @@ -32,81 +35,166 @@ export default function TreeView({ brokers, brokerEditor, onQueueSelected }) {
const getQueueIcon = (queue) => {
const isLvq = queue.maxMsgSpoolUsage === 0;
const isEmpty = queue.msgSpoolUsage === 0;
const isFull = (queue.msgSpoolUsage/queue.maxMsgSpoolUsage) > queue.eventMsgSpoolUsageThreshold.setPercent;
const isFull = (queue.msgSpoolUsage / queue.maxMsgSpoolUsage) > queue.eventMsgSpoolUsageThreshold.setPercent;

const iconType = isLvq ? 'pi-caret-right' : 'pi-forward';
const iconColor = isEmpty ? '' : (!isLvq && isFull) ? 'text-red-500' : 'text-primary';
return `pi ${iconType} ${iconColor}`;
return isLvq ?
<LvqIcon size="16" className={iconColor} /> :
<QueueIcon size="16" className={iconColor} />;
};

const nodes = brokers.map(config => ({
const nodes = [...brokers.map(config => ({
id: config.id,
key: config.id,
label: config.displayName,
data: {
type: 'broker',
toolIcon: 'pi pi-ellipsis-h',
onToolClick: () => setBrokerForConfig(config),
config
},
icon: getBrokerIcon(config.testResult),
leaf: false,
children: queuesListMap[config.id] || []
}));
children: [
{
id: `${config.id}/queues`,
key: `${config.id}/queues`,
label: 'Queues',
icon: <QueueIcon size="16" />,
data: {
type: 'queues',
toolIcon: '',
config
},
leaf: false,
children: queuesListMap[config.id] || []
},
...(config.testResult?.replay ? [{
id: `${config.id}/topics`,
key: `${config.id}/topics`,
label: 'Replay Log',
icon: 'pi pi-backward',
data: {
type: 'topics',
toolIcon: 'pi pi-plus',
onToolClick: () => { setBrokerAndReplayTopic({ broker: config, replayTopic: null }) },
config
},
leaf: false,
children: topicsListMap[config.id] || []
}] : [])
]
})),
];

const buildQueueNodeList = (config, queues) => {
return queues
.filter((queue) => !queue.queueName.startsWith('#'))
.map((queue, n) => ({
id: `${config.id}/queue/${n}`,
key: `queue/${n}`,
label: queue.queueName,
data: {
type: 'queue',
toolIcon: '',
config,
sourceName: queue.queueName
},
icon: getQueueIcon(queue)
}));
};

const handleExpand = async (event) => {
setIsLoading(true);
const { config } = event.node.data;
const buildTopicNodeList = (config) => {
const { replayTopics = [] } = config;
return replayTopics
.map((replayTopic, n) => ({
id: `${config.id}/topic/${n}`,
key: `topic/${n}`,
label: replayTopic.subscriptionName,
icon: <TopicIcon />,
data: {
type: 'topic',
toolIcon: 'pi pi-ellipsis-h',
onToolClick: () => setBrokerAndReplayTopic({ broker: config, replayTopic }),
config,
sourceName: replayTopic.subscriptionName,
topics: replayTopic.topics
}
}));
}

const { result } = await brokerEditor.test(config);
Object.assign(config, { testResult: result }); //HACK: this updates the during each expansion
const handleExpand = async (event) => {
console.log('handleExpand', event.node);
const { node } = event;
const { type, config } = node.data;

if (type === 'broker') {
setIsLoading(true);
const { result } = await brokerEditor.test(config);
Object.assign(config, { testResult: result }); //HACK: this updates the during each expansion
setIsLoading(false);
return;
}

let queueNodeList = [];
if(result.connected) {
if (type === 'queues' && config.testResult.connected) {
setIsLoading(true);
const { data: queues } = await sempApi.getClient(config).getMsgVpnQueues(config.vpn, { count: 100 });
queueNodeList = queues
.filter((queue) => !queue.queueName.startsWith('#'))
.map((queue, n) => ({
id: `${config.id}-${n}`,
key: n.toString(),
label: queue.queueName,
data: {
type: 'queue',
config: Object.assign({}, config, { queueName: queue.queueName })
},
icon: getQueueIcon(queue)
}));
const queueNodeList = buildQueueNodeList(config, queues);
setQueuesListMap(prev => ({ ...prev, [config.id]: queueNodeList }));
setIsLoading(false);
}

setQueuesListMap(prev => ({...prev, [config.id]: queueNodeList}));
setIsLoading(false);
if (type === 'topics' && config.testResult.connected) {
const topicNodeList = buildTopicNodeList(config);
setTopicsListMap(prev => ({ ...prev, [config.id]: topicNodeList }));
}
};

const handleSelect = (event) => {
if(event.node.data.type === 'queue') {
onQueueSelected?.(event.node.data.config);
console.log('handleSelect', event.node.data);
if (event.node.data.type === 'queue' || event.node.data.type === 'topic') {
onSourceSelected?.(event.node.data);
}
};

const handleAddBrokerClick = () => {
setBrokerForConfig({});
};

const handleDoubleClick = (event) => {
if(event.node.data.type === 'broker') {
setBrokerForConfig(event.node.data.config);
}
};

const handleConfigHide = (data) => {
setBrokerForConfig(null);
};

const handleTopicDialogHide = (data) => {
const { broker } = brokerAndReplayTopic;
const topicNodeList = buildTopicNodeList(broker);
setTopicsListMap(prev => ({ ...prev, [broker.id]: topicNodeList }));
setBrokerAndReplayTopic(null);
};

const nodeTemplate = (node, options) => {
const handleClick = (evt) => {
evt.stopPropagation();
node.data.onToolClick && node.data.onToolClick();
};
return (
<div className={`${options.className} ${classes.treeNodeLabel}`}>
<div style={{ flex: '1' }}>{node.label}</div>
<i className={`${node.data.toolIcon} ${classes.toolIcon}`} onClick={handleClick} />
</div>
);
};

return (
<div className={classes.container}>
<Toolbar className={classes.toolbar} start={() => <Button size="small" icon={PrimeIcons.PLUS} onClick={handleAddBrokerClick} />} />
<Tree value={nodes} className={classes.tree} onExpand={handleExpand} onSelect={handleSelect} onNodeDoubleClick={handleDoubleClick} selectionMode="single" loading={isLoading}
pt={{ container: { className: classes.treeContainer }, label: { className: classes.treeNodeLabel } }}
/>
<BrokerConfigDialog config={brokerForConfig} brokerEditor={brokerEditor} onHide={handleConfigHide} />
</div>
<ContentPanel title="Broker Definitions" toolbar={<i className={`pi pi-plus ${classes.toolIcon}`} onClick={handleAddBrokerClick}></i>}>
<div className={classes.container}>
<Tree value={nodes} className={classes.tree} nodeTemplate={nodeTemplate} selectionMode="single" loading={isLoading}
onExpand={handleExpand} onSelect={handleSelect}
pt={{ container: { className: classes.treeContainer }, label: { className: classes.treeNodeLabel } }}
/>
<BrokerConfigDialog config={brokerForConfig} brokerEditor={brokerEditor} onHide={handleConfigHide} />
<ReplayTopicDialog config={brokerAndReplayTopic} brokerEditor={brokerEditor} onHide={handleTopicDialogHide} />
</div>
</ContentPanel>
);
}
28 changes: 28 additions & 0 deletions src/components/BrokerQueueTreeView/styles.module.css
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
.container {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
}
Expand All @@ -20,8 +21,35 @@

.treeContainer {
overflow-x: hidden;
margin: 1em 0;
}

.treeNodeLabel {
white-space: nowrap;
display: flex;
width: 100%;
align-items: center;
}

i.toolIcon {
color: rgba(255, 255, 255, 0.6);
cursor: pointer;
border-radius: 50%;
}

.treeNodeLabel > i.toolIcon {
display: none;
}

.treeNodeLabel:hover > i.toolIcon {
display: inline-block;
}

.treeNodeLabel:hover > i.toolIcon {
display: inline-block;
}

i.toolIcon:hover {
background-color: rgba(255, 255, 255, 0.06);
color: rgba(255, 255, 255, 0.87);
}
30 changes: 30 additions & 0 deletions src/components/ContentPanel/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Panel } from 'primereact/panel';

import classes from './styles.module.css';

export default function ContentPanel({ title, toolbar, footer, children }) {

const headerTemplate = (options) => {
return (
<div className={options.className}>
<div>
<strong>{title}</strong>
</div>
<div>
{toolbar}
</div>
</div>
);
};

return (
<Panel headerTemplate={headerTemplate}
pt={{
root: { className: classes.panelRoot },
header: { className: classes.panelHeader },
toggleableContent: { className: classes.panelOuterContent },
content: { className: classes.panelInnerContent }}}>
{children}
</Panel>
);
}
Loading

0 comments on commit 91d32c1

Please sign in to comment.