Confirm delete, edit modals, some cleanup.

remotes/origin/refactor/jwt-ls
Artem Golub 3 years ago
parent 7bc6892865
commit b3f3fd6a39

@ -1,5 +1,7 @@
# Container ToolKit # Container ToolKit
![Alt text](/screenshots/ui.png?raw=true "UI")
## Local setup and development ## Local setup and development
On a Mac/Linux/Windows you need Docker, Docker Compose installed. Optionally GCC to run make commands for convenience, or just run the commands from the Makefile by hand. On a Mac/Linux/Windows you need Docker, Docker Compose installed. Optionally GCC to run make commands for convenience, or just run the commands from the Makefile by hand.

Binary file not shown.

After

Width:  |  Height:  |  Size: 314 KiB

BIN
services/.DS_Store vendored

Binary file not shown.

@ -0,0 +1,22 @@
import { TrashIcon, PencilIcon } from "@heroicons/react/solid";
export const Popover = ({
onEditClick,
onDeleteClick
}: {
onEditClick: Function
onDeleteClick: Function
}) => {
return (
<div className="relative flex flex-col items-center">
<div className="flex absolute -bottom-2 flex flex-col items-center p-2">
<span className="relative z-10 p-2.5 text-xs leading-none text-white whitespace-no-wrap bg-gray-700 shadow-lg rounded-md">
<div className="flex space-x-2.5">
<TrashIcon onClick={() => onDeleteClick()} className="w-3 h-3 text-red-400"></TrashIcon>
<PencilIcon onClick={() => onEditClick()} className="w-3 h-3"></PencilIcon>
</div>
</span>
<div className="w-3 h-3 -mt-2.5 rotate-45 bg-gray-600"></div>
</div>
</div>
);
};

@ -14,10 +14,11 @@ import {
position position
} from "../../reducers"; } from "../../reducers";
import Remove from "../Remove"; import Remove from "../Remove";
import eventBus from "../../events/eventBus";
import { Popover } from "./Popover";
import ModalConfirmDelete from "../Modal/Service/ConfirmDelete";
import ModalServiceCreate from "../Modal/Service/Create"; import ModalServiceCreate from "../Modal/Service/Create";
import ModalServiceEdit from "../Modal/Service/Edit"; import ModalServiceEdit from "../Modal/Service/Edit";
import ModalCreate from "../Modal/Node/Create";
import ModalEdit from "../Modal/Node/Edit";
import { useClickOutside } from "../../utils/clickOutside"; import { useClickOutside } from "../../utils/clickOutside";
import { IClientNodeItem, IGraphData } from "../../types"; import { IClientNodeItem, IGraphData } from "../../types";
import { nodeLibraries } from "../../utils/data/libraries"; import { nodeLibraries } from "../../utils/data/libraries";
@ -47,17 +48,21 @@ export const Canvas: FC<ICanvasProps> = (props) => {
const [instanceConnections, setInstanceConnections] = useState(state.connections); const [instanceConnections, setInstanceConnections] = useState(state.connections);
const [copyText, setCopyText] = useState("Copy"); const [copyText, setCopyText] = useState("Copy");
const [selectedNode, setSelectedNode] = useState<IClientNodeItem | null>(null); const [selectedNode, setSelectedNode] = useState<IClientNodeItem | null>(null);
const [showModalCreateStep, setShowModalCreateStep] = useState(false); const [showModalCreateService, setShowModalCreateService] = useState(false);
const [showModalEditStep, setShowModalEditStep] = useState(false); const [showModalEditService, setShowModalEditService] = useState(false);
const [showModalCreate, setShowModalCreate] = useState(false); const [showModalConfirmDeleteService, setShowModalConfirmDeleteService] = useState(false);
const [showModalEdit, setShowModalEdit] = useState(false); const [showVolumesModal, setShowVolumesModal] = useState(false);
const [showNetworksModal, setShowNetworksModal] = useState(false);
const [nodeDragging, setNodeDragging] = useState<string | null>();
const [nodeHovering, setNodeHovering] = useState<string | null>();
const [dragging, setDragging] = useState(false); const [dragging, setDragging] = useState(false);
const [_scale, _setScale] = useState(1); const [_scale, _setScale] = useState(1);
const [_left, _setLeft] = useState(0); const [_left, _setLeft] = useState(0);
const [_top, _setTop] = useState(0); const [_top, _setTop] = useState(0);
const [_initX, _setInitX] = useState(0); const [_initX, _setInitX] = useState(0);
const [_initY, _setInitY] = useState(0); const [_initY, _setInitY] = useState(0);
const [containerCallbackRef, setZoom, setStyle, removeEndpoint] = useJsPlumb( const [containerCallbackRef, setZoom, setStyle, removeEndpoint] = useJsPlumb(
instanceNodes, instanceNodes,
instanceConnections, instanceConnections,
@ -76,8 +81,7 @@ export const Canvas: FC<ICanvasProps> = (props) => {
stateRef.current = state.nodes; stateRef.current = state.nodes;
useClickOutside(drop, () => { useClickOutside(drop, () => {
setShowModalCreateStep(false); setShowModalCreateService(false);
setShowModalCreate(false);
}); });
useEffect(() => { useEffect(() => {
@ -256,38 +260,49 @@ export const Canvas: FC<ICanvasProps> = (props) => {
_setScale(state.canvasPosition.scale); _setScale(state.canvasPosition.scale);
}, [state.canvasPosition]); }, [state.canvasPosition]);
useEffect(() => {
eventBus.on("EVENT_DRAG_START", (data: any) => {
setNodeDragging(data.detail.message.id);
});
eventBus.on("EVENT_DRAG_STOP", (data: any) => {
setNodeDragging(null);
});
return () => {
eventBus.remove("EVENT_DRAG_START", () => {});
eventBus.remove("EVENT_DRAG_STOP", () => { });
};
}, []);
return ( return (
<> <>
{showModalCreateStep {showModalCreateService
? <ModalServiceCreate ? <ModalServiceCreate
onHide={() => setShowModalCreateStep(false)} onHide={() => setShowModalCreateService(false)}
onAddEndpoint={(values: any) => onAddEndpoint(values)} onAddEndpoint={(values: any) => onAddEndpoint(values)}
/> />
: null : null
} }
{showModalEditStep {showModalEditService
? <ModalServiceEdit ? <ModalServiceEdit
node={selectedNode} node={selectedNode}
onHide={() => setShowModalEditStep(false)} onHide={() => setShowModalEditService(false)}
onUpdateEndpoint={(values: any) => onUpdateEndpoint(values)} onUpdateEndpoint={(values: any) => onUpdateEndpoint(values)}
/> />
: null : null
} }
{showModalCreate {showModalConfirmDeleteService
? <ModalCreate ? <ModalConfirmDelete
onHide={() => setShowModalCreate(false)} onHide={() => setShowModalConfirmDeleteService(false)}
onAddEndpoint={(values: any) => onAddEndpoint(values)} onConfirm={() => {
/> setShowModalEditService(false);
: null if (selectedNode) {
} onRemoveEndpoint(selectedNode.key);
}
{showModalEdit }}
? <ModalEdit
node={selectedNode}
onHide={() => setShowModalEdit(false)}
onUpdateEndpoint={(nodeItem: IClientNodeItem) => onUpdateEndpoint(nodeItem)}
/> />
: null : null
} }
@ -300,14 +315,14 @@ export const Canvas: FC<ICanvasProps> = (props) => {
<div className="flex space-x-2 p-2"> <div className="flex space-x-2 p-2">
<button className="hidden btn-util" type="button" onClick={zoomOut} disabled={scale <= 0.5}>-</button> <button className="hidden btn-util" type="button" onClick={zoomOut} disabled={scale <= 0.5}>-</button>
<button className="hidden btn-util" type="button" onClick={zoomIn} disabled={scale >= 1}>+</button> <button className="hidden btn-util" type="button" onClick={zoomIn} disabled={scale >= 1}>+</button>
<button className="flex space-x-1 btn-util" type="button" onClick={() => setShowModalCreateStep(true)}> <button className="flex space-x-1 btn-util" type="button" onClick={() => setShowModalCreateService(true)}>
<PlusIcon className="w-3"/> <PlusIcon className="w-3"/>
<span>Service</span> <span>Service</span>
</button> </button>
<button className="btn-util" type="button" onClick={() => setShowModalCreate(true)}> <button className="btn-util" type="button" onClick={() => setShowVolumesModal(true)}>
Volumes Volumes
</button> </button>
<button className="btn-util" type="button" onClick={() => setShowModalCreate(true)}> <button className="btn-util" type="button" onClick={() => setShowNetworksModal(true)}>
Networks Networks
</button> </button>
</div> </div>
@ -335,10 +350,28 @@ export const Canvas: FC<ICanvasProps> = (props) => {
{values(instanceNodes).map((x) => ( {values(instanceNodes).map((x) => (
<div <div
key={x.key} key={x.key}
className={"node-item cursor-pointer shadow flex flex-col"} className={"node-item cursor-pointer shadow flex flex-col group"}
id={x.key} id={x.key}
style={{ top: x.position.top, left: x.position.left }} style={{ top: x.position.top, left: x.position.left }}
onMouseEnter={() => setNodeHovering(x.key)}
onMouseLeave={() => {
if (nodeHovering === x.key) {
setNodeHovering(null);
}
}}
> >
{((nodeHovering === x.key) && (nodeDragging !== x.key)) &&
<Popover
onEditClick={() => {
setSelectedNode(x);
setShowModalEditService(true);
}}
onDeleteClick={() => {
setSelectedNode(x);
setShowModalConfirmDeleteService(true);
}}
></Popover>
}
<div className="node-label w-full py-2 px-4"> <div className="node-label w-full py-2 px-4">
<div className="text-sm font-semibold"> <div className="text-sm font-semibold">
{x.configuration.prettyName} {x.configuration.prettyName}

@ -0,0 +1,71 @@
import { XIcon, ExclamationIcon } from "@heroicons/react/outline";
interface IModalConfirmDeleteProps {
onConfirm: any;
onHide: any;
}
const ModalConfirmDelete = (props: IModalConfirmDeleteProps) => {
const { onConfirm, onHide } = props;
return (
<div className="fixed z-50 inset-0 overflow-y-auto">
<div className="justify-center items-center flex overflow-x-hidden overflow-y-auto fixed inset-0 outline-none focus:outline-none">
<div onClick={onHide} className="opacity-25 fixed inset-0 z-40 bg-black"></div>
<div className="relative w-auto my-6 mx-auto max-w-5xl z-50">
<div className="border-0 rounded-lg shadow-lg relative flex flex-col w-full bg-white outline-none focus:outline-none">
<div className="flex items-center justify-between px-4 py-3 border-b border-solid border-blueGray-200 rounded-t">
<h3 className="text-sm font-semibold">Confirm delete</h3>
<button
className="p-1 ml-auto text-black float-right outline-none focus:outline-none"
onClick={onHide}
>
<span className="block outline-none focus:outline-none">
<XIcon className="w-4" />
</span>
</button>
</div>
<div className="relative px-4 py-3 flex-auto">
<div className="sm:flex sm:items-start">
<div className="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-red-100 sm:mx-0 sm:h-10 sm:w-10">
<ExclamationIcon className="h-6 w-6 text-red-600" aria-hidden="true" />
</div>
<div className="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<div className="mt-2">
<p className="text-sm dark:text-gray-100 text-gray-500">
Careful! This action cannot be undone.
</p>
</div>
</div>
</div>
</div>
<div className="flex items-center justify-end px-4 py-3 border-t border-solid border-blueGray-200 rounded-b">
<button
type="button"
className="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-3 py-1 bg-white text-sm font-medium text-gray-700 hover:bg-gray-50 sm:mt-0 sm:w-auto sm:text-sm"
onClick={onHide}
>
Cancel
</button>
<button
type="button"
className="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-3 py-1 bg-red-600 text-sm font-medium text-white hover:bg-red-700 sm:ml-3 sm:w-auto sm:text-sm"
onClick={() => {
onHide()
onConfirm()
}}
>
Delete
</button>
</div>
</div>
</div>
</div>
</div>
)
}
export default ModalConfirmDelete;

@ -27,78 +27,76 @@ const ModalServiceCreate = (props: IModalServiceProps) => {
}); });
return ( return (
<> <div className="fixed z-50 inset-0 overflow-y-auto">
<div className="fixed z-50 inset-0 overflow-y-auto"> <div className="justify-center items-center flex overflow-x-hidden overflow-y-auto fixed inset-0 outline-none focus:outline-none">
<div className="justify-center items-center flex overflow-x-hidden overflow-y-auto fixed inset-0 outline-none focus:outline-none"> <div onClick={onHide} className="opacity-25 fixed inset-0 z-40 bg-black"></div>
<div onClick={onHide} className="opacity-25 fixed inset-0 z-40 bg-black"></div> <div className="relative w-auto my-6 mx-auto max-w-5xl z-50">
<div className="relative w-auto my-6 mx-auto max-w-5xl z-50"> <div className="border-0 rounded-lg shadow-lg relative flex flex-col w-full bg-white outline-none focus:outline-none">
<div className="border-0 rounded-lg shadow-lg relative flex flex-col w-full bg-white outline-none focus:outline-none"> <div className="flex items-center justify-between px-4 py-3 border-b border-solid border-blueGray-200 rounded-t">
<div className="flex items-center justify-between px-4 py-3 border-b border-solid border-blueGray-200 rounded-t"> <h3 className="text-sm font-semibold">Add service</h3>
<h3 className="text-sm font-semibold">Add service</h3> <button
<button className="p-1 ml-auto text-black float-right outline-none focus:outline-none"
className="p-1 ml-auto text-black float-right outline-none focus:outline-none" onClick={onHide}
onClick={onHide} >
> <span className="block outline-none focus:outline-none">
<span className="block outline-none focus:outline-none"> <XIcon className="w-4" />
<XIcon className="w-4" /> </span>
</span> </button>
</button> </div>
</div>
<div className="relative px-4 py-3 flex-auto"> <div className="relative px-4 py-3 flex-auto">
<div className="grid grid-cols-3 gap-4"> <div className="grid grid-cols-3 gap-4">
<div className="col-span-3"> <div className="col-span-3">
<label htmlFor="prettyName" className="block text-xs font-medium text-gray-700">Name</label> <label htmlFor="prettyName" className="block text-xs font-medium text-gray-700">Name</label>
<div className="mt-1"> <div className="mt-1">
<input <input
id="prettyName" id="prettyName"
name="configuration.prettyName" name="configuration.prettyName"
type="text" type="text"
autoComplete="none" autoComplete="none"
className="input-util" className="input-util"
onChange={formik.handleChange} onChange={formik.handleChange}
value={formik.values.configuration.prettyName} value={formik.values.configuration.prettyName}
/> />
</div>
</div> </div>
</div> </div>
</div>
<div className="mt-2"> <div className="mt-2">
<div className="col-span-3"> <div className="col-span-3">
<label htmlFor="template" className="block text-xs font-medium text-gray-700">Template</label> <label htmlFor="template" className="block text-xs font-medium text-gray-700">Template</label>
<div className="mt-1"> <div className="mt-1">
<input <input
id="template" id="template"
name="configuration.template" name="configuration.template"
type="text" type="text"
autoComplete="none" autoComplete="none"
className="input-util" className="input-util"
onChange={formik.handleChange} onChange={formik.handleChange}
value={formik.values.configuration.template} value={formik.values.configuration.template}
/> />
</div>
</div> </div>
</div> </div>
</div> </div>
</div>
<div className="flex items-center justify-end px-4 py-3 border-t border-solid border-blueGray-200 rounded-b"> <div className="flex items-center justify-end px-4 py-3 border-t border-solid border-blueGray-200 rounded-b">
<button <button
className="btn-util" className="btn-util"
type="button" type="button"
onClick={() => { onClick={() => {
formik.values.configuration.name = formatName(formik.values.configuration.prettyName); formik.values.configuration.name = formatName(formik.values.configuration.prettyName);
onAddEndpoint(formik.values); onAddEndpoint(formik.values);
formik.resetForm(); formik.resetForm();
}} }}
> >
Add Add
</button> </button>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</> </div>
); );
} }

@ -13,8 +13,10 @@ import {
import { import {
BrowserJsPlumbInstance, BrowserJsPlumbInstance,
newInstance, newInstance,
EVENT_DRAG_START,
EVENT_DRAG_STOP, EVENT_DRAG_STOP,
EVENT_CONNECTION_DBL_CLICK EVENT_CONNECTION_DBL_CLICK,
DragStartPayload
} from "@jsplumb/browser-ui"; } from "@jsplumb/browser-ui";
import { import {
defaultOptions, defaultOptions,
@ -23,6 +25,7 @@ import {
sourceEndpoint, sourceEndpoint,
targetEndpoint targetEndpoint
} from "../utils/options"; } from "../utils/options";
import eventBus from "../events/eventBus";
import { getConnections } from "../utils"; import { getConnections } from "../utils";
import { IClientNodeItem } from "../types"; import { IClientNodeItem } from "../types";
import { Dictionary, isEqual } from "lodash"; import { Dictionary, isEqual } from "lodash";
@ -90,11 +93,13 @@ export const useJsPlumb = (
if (nodeConnections) { if (nodeConnections) {
Object.values(nodeConnections).forEach((conn) => { Object.values(nodeConnections).forEach((conn) => {
instance.destroyConnector(conn);
instance.deleteConnection(conn); instance.deleteConnection(conn);
}); });
}; };
instance.removeAllEndpoints(document.getElementById(node.key) as Element); instance.removeAllEndpoints(document.getElementById(node.key) as Element);
instance.repaintEverything();
}, [instance]); }, [instance]);
const getAnchors = (port: string[], anchorIds: AnchorId[]): IAnchor[] => { const getAnchors = (port: string[], anchorIds: AnchorId[]): IAnchor[] => {
@ -234,7 +239,7 @@ export const useJsPlumb = (
'connections': getConnections(instance.getConnections({}, true) as Connection[]) 'connections': getConnections(instance.getConnections({}, true) as Connection[])
}); });
} }
}, [instance, addEndpoints, onGraphUpdate]); }, [instance, addEndpoints, stateRef.current]);
useEffect(() => { useEffect(() => {
if (!instance) return; if (!instance) return;
@ -263,6 +268,14 @@ export const useJsPlumb = (
container: containerRef.current container: containerRef.current
}); });
jsPlumbInstance.bind(EVENT_DRAG_START, function (params: DragStartPayload) {
eventBus.dispatch("EVENT_DRAG_START", { message: { "id": params.el.id } });
});
jsPlumbInstance.bind(EVENT_DRAG_STOP, function (params: DragStartPayload) {
eventBus.dispatch("EVENT_DRAG_STOP", { message: {"id": params.el.id} });
});
jsPlumbInstance.bind(INTERCEPT_BEFORE_DROP, function (params: BeforeDropParams) { jsPlumbInstance.bind(INTERCEPT_BEFORE_DROP, function (params: BeforeDropParams) {
return onbeforeDropIntercept(jsPlumbInstance, params); return onbeforeDropIntercept(jsPlumbInstance, params);
}); });
@ -318,7 +331,6 @@ export const useJsPlumb = (
return () => { return () => {
reset(); reset();
}; };
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []); }, []);
return [containerCallbackRef, setZoom, setStyle, removeEndpoint]; return [containerCallbackRef, setZoom, setStyle, removeEndpoint];

@ -0,0 +1,13 @@
const eventBus = {
on(event: string, callback: { (data: any): void; (data: any): void; (arg: any): any; }) {
document.addEventListener(event, (e) => callback(e));
},
dispatch(event: string, data: { message: { id: string; } | { id: string; }; }) {
document.dispatchEvent(new CustomEvent(event, { detail: data }));
},
remove(event: string, callback: { (): void; (this: Document, ev: any): any; }) {
document.removeEventListener(event, callback);
},
};
export default eventBus;
Loading…
Cancel
Save