Merge pull request #77 from nuxxapp/fix/random-fixes

Fix random issues
pull/79/head
Samuel Rowe 3 years ago committed by GitHub
commit 39533a64c6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -2,11 +2,6 @@
:heart: Thanks for taking the time and for your help improving this project!
## Getting Help ##
If you have a question about nuxx or have encountered problems using it,
start by asking a question on [slack][slack]
## Submitting a Pull Request ##
Do you have an improvement?
@ -23,9 +18,4 @@ Do you have an improvement?
Squash or rebase commits so that all changes from a branch are
committed to master as a single commit. All pull requests are squashed when
merged, but rebasing prior to merge gives you better control over the commit
message.
<!----variables---->
[slack]: https://join.slack.com/t/nuxxapp/shared_invite/zt-fkgoyz5h-CYo5tqAT0CwRZMpuOJYAJA
message.

@ -1,3 +1,4 @@
import json
from rest_framework import generics, status
from rest_framework.response import Response
@ -11,7 +12,7 @@ class GenerateGenericAPIView(generics.GenericAPIView):
return Response({}, status=status.HTTP_404_NOT_FOUND)
def post(self, request, format=None):
request_data = request.data
request_data = json.loads(request.data)
version = request_data['data'].get('version', '3')
services = request_data['data'].get('services', None)
volumes = request_data['data'].get('volumes', None)

@ -71,6 +71,7 @@ def generate(services, volumes, networks, version="3", return_format='yaml'):
s = io.StringIO()
ret_yaml = YAML()
ret_yaml.indent(mapping=2, sequence=4, offset=2)
ret_yaml.preserve_quotes = True
ret_yaml.explicit_start = True
specified_version = get_version(version)
base_version = int(specified_version)

@ -78,38 +78,43 @@ export const Canvas: FC<ICanvasProps> = (props) => {
}
};
const onCanvasMouseUpLeave = (e: any) => {
if (dragging) {
const left = _left + e.pageX - _initX;
const top = _top + e.pageY - _initY;
_setLeft(left);
_setTop(top);
setDragging(false);
onCanvasUpdate({
left: left,
top: top
});
}
};
const onCanvasMouseMove = (e: any) => {
if (!dragging) {
return;
}
const styles = {
left: _left + e.pageX - _initX + "px",
top: _top + e.pageY - _initY + "px"
};
if (e.pageX && e.pageY) {
const styles = {
left: _left + e.pageX - _initX + "px",
top: _top + e.pageY - _initY + "px"
};
setStyle(styles);
}
};
setStyle(styles);
const onCanvasMouseUpLeave = (e: any) => {
if (dragging) {
if (e.pageX && e.pageY) {
const left = _left + e.pageX - _initX;
const top = _top + e.pageY - _initY;
_setLeft(left);
_setTop(top);
setDragging(false);
onCanvasUpdate({
left: left,
top: top
});
}
}
};
const onCanvasMouseDown = (e: any) => {
_setInitX(e.pageX);
_setInitY(e.pageY);
setDragging(true);
if (e.pageX && e.pageY) {
_setInitX(e.pageX);
_setInitY(e.pageY);
setDragging(true);
}
};
useEffect(() => {
@ -166,7 +171,7 @@ export const Canvas: FC<ICanvasProps> = (props) => {
<div
id={CANVAS_ID}
ref={containerCallbackRef}
className="canvas h-full w-full"
className="canvas"
style={{
transformOrigin: "0px 0px 0px",
transform: `translate(${translateWidth}px, ${translateHeight}px) scale(${_scale})`

@ -79,31 +79,32 @@ const NetworkCreate = (props: INetworkCreateProps) => {
>
{(formik) => (
<>
<div className="hidden sm:block">
<div className="border-b border-gray-200 px-8">
<nav className="-mb-px flex space-x-8" aria-label="Tabs">
{tabs.map((tab) => (
<a
key={tab.name}
href={tab.href}
className={classNames(
tab.name === openTab
? "border-indigo-500 text-indigo-600"
: "border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300",
"whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm",
tab.hidden ? "hidden" : ""
)}
aria-current={tab.current ? "page" : undefined}
onClick={(e) => {
e.preventDefault();
setOpenTab(tab.name);
}}
>
{tab.name}
</a>
))}
</nav>
</div>
<div className="border-b border-gray-200 px-4 md:px-8">
<nav
className="-mb-px flex space-x-4 md:space-x-8"
aria-label="Tabs"
>
{tabs.map((tab) => (
<a
key={tab.name}
href={tab.href}
className={classNames(
tab.name === openTab
? "border-indigo-500 text-indigo-600"
: "border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300",
"whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm",
tab.hidden ? "hidden" : ""
)}
aria-current={tab.current ? "page" : undefined}
onClick={(e) => {
e.preventDefault();
setOpenTab(tab.name);
}}
>
{tab.name}
</a>
))}
</nav>
</div>
<div className="relative px-4 py-3 flex-auto">

@ -82,31 +82,32 @@ const NetworkEdit = (props: INetworkEditProps) => {
>
{(formik) => (
<>
<div className="hidden sm:block">
<div className="border-b border-gray-200 px-8">
<nav className="-mb-px flex space-x-8" aria-label="Tabs">
{tabs.map((tab) => (
<a
key={tab.name}
href={tab.href}
className={classNames(
tab.name === openTab
? "border-indigo-500 text-indigo-600"
: "border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300",
"whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm",
tab.hidden ? "hidden" : ""
)}
aria-current={tab.current ? "page" : undefined}
onClick={(e) => {
e.preventDefault();
setOpenTab(tab.name);
}}
>
{tab.name}
</a>
))}
</nav>
</div>
<div className="border-b border-gray-200 px-4 md:px-8">
<nav
className="-mb-px flex space-x-4 md:space-x-8"
aria-label="Tabs"
>
{tabs.map((tab) => (
<a
key={tab.name}
href={tab.href}
className={classNames(
tab.name === openTab
? "border-indigo-500 text-indigo-600"
: "border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300",
"whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm",
tab.hidden ? "hidden" : ""
)}
aria-current={tab.current ? "page" : undefined}
onClick={(e) => {
e.preventDefault();
setOpenTab(tab.name);
}}
>
{tab.name}
</a>
))}
</nav>
</div>
<div className="relative px-4 py-3 flex-auto">

@ -7,8 +7,8 @@ import Volumes from "./Volumes";
import Labels from "./Labels";
import { CallbackFunction } from "../../../types";
import {
getInitialValues,
getFinalValues,
getInitialValues,
validationSchema
} from "./form-utils";
@ -48,9 +48,9 @@ const ModalServiceCreate = (props: IModalServiceProps) => {
const { onHide, onAddEndpoint } = props;
const [openTab, setOpenTab] = useState("General");
const handleCreate = (values: any, formik: any) => {
// TODO: This modal should not be aware of endpoints. Seperation of concerns.
onAddEndpoint(getFinalValues(values));
formik.resetForm();
onHide();
};
const initialValues = useMemo(() => getInitialValues(), []);
@ -83,38 +83,37 @@ const ModalServiceCreate = (props: IModalServiceProps) => {
<Formik
initialValues={initialValues}
enableReinitialize={true}
onSubmit={(values, formik) => {
handleCreate(values, formik);
}}
onSubmit={handleCreate}
validationSchema={validationSchema}
>
{(formik) => (
<>
<div className="hidden sm:block">
<div className="border-b border-gray-200 px-8">
<nav className="-mb-px flex space-x-8" aria-label="Tabs">
{tabs.map((tab) => (
<a
key={tab.name}
href={tab.href}
className={classNames(
tab.name === openTab
? "border-indigo-500 text-indigo-600"
: "border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300",
"whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm",
tab.hidden ? "hidden" : ""
)}
aria-current={tab.current ? "page" : undefined}
onClick={(e) => {
e.preventDefault();
setOpenTab(tab.name);
}}
>
{tab.name}
</a>
))}
</nav>
</div>
<div className="border-b border-gray-200 px-4 md:px-8">
<nav
className="-mb-px flex space-x-4 md:space-x-8"
aria-label="Tabs"
>
{tabs.map((tab) => (
<a
key={tab.name}
href={tab.href}
className={classNames(
tab.name === openTab
? "border-indigo-500 text-indigo-600"
: "border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300",
"whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm",
tab.hidden ? "hidden" : ""
)}
aria-current={tab.current ? "page" : undefined}
onClick={(e) => {
e.preventDefault();
setOpenTab(tab.name);
}}
>
{tab.name}
</a>
))}
</nav>
</div>
<div className="relative px-4 py-3 flex-auto">

@ -100,34 +100,32 @@ const ModalServiceEdit = (props: IModalServiceProps) => {
>
{(formik) => (
<>
<div className="hidden sm:block">
<div className="border-b border-gray-200 px-8">
<nav
className="-mb-px flex space-x-8"
aria-label="Tabs"
>
{tabs.map((tab) => (
<a
key={tab.name}
href={tab.href}
className={classNames(
tab.name === openTab
? "border-indigo-500 text-indigo-600"
: "border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300",
"whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm",
tab.hidden ? "hidden" : ""
)}
aria-current={tab.current ? "page" : undefined}
onClick={(e) => {
e.preventDefault();
setOpenTab(tab.name);
}}
>
{tab.name}
</a>
))}
</nav>
</div>
<div className="border-b border-gray-200 px-4 md:px-8">
<nav
className="-mb-px flex space-x-4 md:space-x-8"
aria-label="Tabs"
>
{tabs.map((tab) => (
<a
key={tab.name}
href={tab.href}
className={classNames(
tab.name === openTab
? "border-indigo-500 text-indigo-600"
: "border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300",
"whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm",
tab.hidden ? "hidden" : ""
)}
aria-current={tab.current ? "page" : undefined}
onClick={(e) => {
e.preventDefault();
setOpenTab(tab.name);
}}
>
{tab.name}
</a>
))}
</nav>
</div>
<div className="relative px-4 py-3 flex-auto">

@ -74,7 +74,7 @@ const Environment = () => {
{
name: `environmentVariables[${index}].value`,
placeholder: "Value",
required: true,
required: false,
type: "text"
}
]}

@ -15,6 +15,9 @@ const Fields = styled("div")`
const ImageNameGroup = styled("div")`
display: flex;
flex-direction: row;
@media (max-width: 640px) {
flex-direction: column;
}
column-gap: ${({ theme }) => theme.spacing(1)};
width: 100%;
`;
@ -31,6 +34,7 @@ const GroupTitle = styled("h5")`
font-weight: 500;
width: 100%;
text-align: left;
margin-bottom: 0.25em;
`;
const Records = styled("div")`
@ -80,12 +84,7 @@ const General = () => {
<Fields>
<TextField label="Service name" name="serviceName" required={true} />
<ImageNameGroup>
<TextField
label="Image name"
name="imageName"
required={true}
style={{ minWidth: 400 }}
/>
<TextField label="Image name" name="imageName" required={true} />
<TextField label="Image tag" name="imageTag" />
</ImageNameGroup>
<TextField

@ -70,7 +70,7 @@ const Labels = () => {
{
name: `labels[${index}].value`,
placeholder: "Value",
required: true,
required: false,
type: "text"
}
]}

@ -60,8 +60,7 @@ export const validationSchema = yup.object({
),
environmentVariables: yup.array(
yup.object({
key: yup.string().required("Key is required"),
value: yup.string().required("Value is required")
key: yup.string().required("Key is required")
})
),
volumes: yup.array(
@ -73,8 +72,7 @@ export const validationSchema = yup.object({
),
labels: yup.array(
yup.object({
key: yup.string().required("Key is required"),
value: yup.string().required("Value is required")
key: yup.string().required("Key is required")
})
)
});
@ -110,10 +108,10 @@ export const getInitialValues = (node?: IServiceNodeItem): IEditServiceForm => {
serviceName: node_name,
containerName: container_name,
environmentVariables: environment0.map((variable) => {
const [key, value] = variable.split(":");
const [key, value] = variable.split("=");
return {
key,
value
value: value ? value : ""
};
}),
volumes: volumes0.map((volume) => {
@ -140,7 +138,7 @@ export const getInitialValues = (node?: IServiceNodeItem): IEditServiceForm => {
return { hostPort, containerPort, protocol } as any;
}),
labels: labels0.map((label) => {
const [key, value] = label.split(":");
const [key, value] = label.split("=");
return {
key,
value
@ -155,7 +153,7 @@ export const getFinalValues = (
): IServiceNodeItem => {
const { environmentVariables, ports, volumes, labels } = values;
return lodash.merge(
return lodash.mergeWith(
lodash.cloneDeep(previous) || {
key: "service",
type: "SERVICE",
@ -168,25 +166,38 @@ export const getFinalValues = (
node_name: values.serviceName
},
serviceConfig: {
image: `${values.imageName}:${values.imageTag}`,
image: `${values.imageName}${
values.imageTag ? `:${values.imageTag}` : ""
}`,
container_name: values.containerName,
environment: environmentVariables.map(
(variable) => `${variable.key}:${variable.value}`
),
volumes: volumes.map(
(volume) =>
volume.name +
(volume.containerPath ? `:${volume.containerPath}` : "") +
(volume.accessMode ? `:${volume.accessMode}` : "")
(variable) =>
`${variable.key}${variable.value ? `=${variable.value}` : ""}`
),
volumes: volumes.length
? volumes.map(
(volume) =>
volume.name +
(volume.containerPath ? `:${volume.containerPath}` : "") +
(volume.accessMode ? `:${volume.accessMode}` : "")
)
: [],
ports: ports.map(
(port) =>
port.hostPort +
(port.containerPort ? `:${port.containerPort}` : "") +
(port.protocol ? `/${port.protocol}` : "")
),
labels: labels.map((label) => `${label.key}:${label.value}`)
labels: labels.map(
(label) => `${label.key}${label.value ? `=${label.value}` : ""}`
)
}
},
(obj, src) => {
if (!lodash.isNil(src)) {
return src;
}
return obj;
}
) as any;
};

@ -26,6 +26,7 @@ const CreateVolumeModal = (props: ICreateVolumeModalProps) => {
const handleCreate = useCallback((values: any, formik: any) => {
onAddEndpoint(getFinalValues(values));
formik.resetForm();
onHide();
}, []);
const initialValues = useMemo(() => getInitialValues(), []);
@ -59,31 +60,32 @@ const CreateVolumeModal = (props: ICreateVolumeModalProps) => {
>
{(formik) => (
<>
<div className="hidden sm:block">
<div className="border-b border-gray-200 px-8">
<nav className="-mb-px flex space-x-8" aria-label="Tabs">
{tabs.map((tab) => (
<a
key={tab.name}
href={tab.href}
className={classNames(
tab.name === openTab
? "border-indigo-500 text-indigo-600"
: "border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300",
"whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm",
tab.hidden ? "hidden" : ""
)}
aria-current={tab.current ? "page" : undefined}
onClick={(e) => {
e.preventDefault();
setOpenTab(tab.name);
}}
>
{tab.name}
</a>
))}
</nav>
</div>
<div className="border-b border-gray-200 px-4 md:px-8">
<nav
className="-mb-px flex space-x-4 md:space-x-8"
aria-label="Tabs"
>
{tabs.map((tab) => (
<a
key={tab.name}
href={tab.href}
className={classNames(
tab.name === openTab
? "border-indigo-500 text-indigo-600"
: "border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300",
"whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm",
tab.hidden ? "hidden" : ""
)}
aria-current={tab.current ? "page" : undefined}
onClick={(e) => {
e.preventDefault();
setOpenTab(tab.name);
}}
>
{tab.name}
</a>
))}
</nav>
</div>
<div className="relative px-4 py-3 flex-auto">

@ -71,34 +71,32 @@ const EditVolumeModal = (props: IEditVolumeModal) => {
>
{(formik) => (
<>
<div className="hidden sm:block">
<div className="border-b border-gray-200 px-8">
<nav
className="-mb-px flex space-x-8"
aria-label="Tabs"
>
{tabs.map((tab) => (
<a
key={tab.name}
href={tab.href}
className={classNames(
tab.name === openTab
? "border-indigo-500 text-indigo-600"
: "border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300",
"whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm",
tab.hidden ? "hidden" : ""
)}
aria-current={tab.current ? "page" : undefined}
onClick={(e) => {
e.preventDefault();
setOpenTab(tab.name);
}}
>
{tab.name}
</a>
))}
</nav>
</div>
<div className="border-b border-gray-200 px-4 md:px-8">
<nav
className="-mb-px flex space-x-4 md:space-x-8"
aria-label="Tabs"
>
{tabs.map((tab) => (
<a
key={tab.name}
href={tab.href}
className={classNames(
tab.name === openTab
? "border-indigo-500 text-indigo-600"
: "border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300",
"whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm",
tab.hidden ? "hidden" : ""
)}
aria-current={tab.current ? "page" : undefined}
onClick={(e) => {
e.preventDefault();
setOpenTab(tab.name);
}}
>
{tab.name}
</a>
))}
</nav>
</div>
<div className="relative px-4 py-3 flex-auto">

@ -70,7 +70,7 @@ const Labels = () => {
{
name: `labels[${index}].value`,
placeholder: "Value",
required: true,
required: false,
type: "text"
}
]}

@ -14,8 +14,7 @@ export const validationSchema = yup.object({
.required("Volume name is required"),
labels: yup.array(
yup.object({
key: yup.string().required("Key is required"),
value: yup.string().required("Value is required")
key: yup.string().required("Key is required")
})
)
});
@ -44,7 +43,7 @@ export const getInitialValues = (node?: IVolumeNodeItem): IEditVolumeForm => {
entryName: node_name,
volumeName: name,
labels: labels0.map((label) => {
const [key, value] = label.split(":");
const [key, value] = label.split("=");
return {
key,
value
@ -59,7 +58,7 @@ export const getFinalValues = (
): IVolumeNodeItem => {
const { labels } = values;
return lodash.merge(
return lodash.mergeWith(
lodash.cloneDeep(previous) || {
key: "volume",
type: "VOLUME",
@ -73,8 +72,16 @@ export const getFinalValues = (
},
volumeConfig: {
name: values.volumeName,
labels: labels.map((label) => `${label.key}:${label.value}`)
labels: labels.map(
(label) => `${label.key}${label.value ? `=${label.value}` : ""}`
)
}
},
(obj, src) => {
if (!lodash.isNil(src)) {
return src;
}
return obj;
}
) as any;
};

@ -182,7 +182,7 @@ export default function Project() {
debounce((graphData) => {
graphData.networks = stateNetworksRef.current;
const flatData = generatePayload(graphData);
generateHttp(flatData)
generateHttp(JSON.stringify(flatData))
.then(checkHttpStatus)
.then((data) => {
if (data["code"].length) {
@ -231,8 +231,19 @@ export default function Project() {
values,
ensure(sections.find((l) => l.type === values.type))
);
clientNodeItem.position = { left: 60, top: 30 };
clientNodeItem.position = {
left: 60 - canvasPosition.left,
top: 30 - canvasPosition.top
};
setNodes({ ...nodes, [clientNodeItem.key]: clientNodeItem });
if (clientNodeItem.type === "VOLUME") {
setVolumeToEdit(clientNodeItem as unknown as IVolumeNodeItem);
}
if (clientNodeItem.type === "SERVICE") {
setServiceToEdit(clientNodeItem as unknown as IServiceNodeItem);
}
};
const onCreateNetwork = (values: any) => {
@ -446,7 +457,7 @@ export default function Project() {
</form>
</div>
<div className="flex flex-grow relative flex-col md:flex-row">
<div className="flex flex-grow relative">
<div
className="w-full overflow-hidden md:w-2/3 z-40"
style={{ height: height - 64 }}
@ -515,7 +526,8 @@ export default function Project() {
/>
</div>
</div>
<div className="relative group code-column w-full md:w-1/3">
<div className="group code-column w-1/2 md:w-1/3 absolute top-0 right-0 sm:relative z-40 md:z-30">
<div
className={`absolute top-0 left-0 right-0 z-10 flex justify-end p-1 space-x-2 group-hover:visible invisible`}
>
@ -555,8 +567,28 @@ export default function Project() {
</div>
</>
);
} else {
return <>Something went wrong</>;
}
if (error) {
return (
<div
className="text-center"
style={{
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
minHeight: "calc(100vh - 120px)"
}}
>
<h3 className="mt-2 text-sm font-medium text-gray-900">
Something went wrong...
</h3>
<p className="mt-1 text-sm text-gray-500">
Either this project does not exist, or the link is wrong.
</p>
</div>
);
}
}

@ -1,5 +1,5 @@
import { useState } from "react";
import { Link } from "react-router-dom";
import { Link, useNavigate } from "react-router-dom";
import { PencilIcon, TrashIcon } from "@heroicons/react/outline";
import { truncateStr } from "../../utils";
import { IProject } from "../../types";
@ -15,6 +15,7 @@ const PreviewBlock = (props: IPreviewBlockProps) => {
const [isHovering, setIsHovering] = useState(false);
const [showDeleteConfirmModal, setShowDeleteConfirmModal] = useState(false);
const mutation = useDeleteProject(project.uuid);
const navigate = useNavigate();
const handleMouseOver = () => {
setIsHovering(true);
@ -24,7 +25,12 @@ const PreviewBlock = (props: IPreviewBlockProps) => {
setIsHovering(false);
};
const onDelete = () => {
const handleClick = (e: any) => {
navigate(`/projects/${project.uuid}`);
};
const onDelete = (e: any) => {
e.stopPropagation();
setShowDeleteConfirmModal(true);
};
@ -37,8 +43,10 @@ const PreviewBlock = (props: IPreviewBlockProps) => {
<div
onMouseOver={handleMouseOver}
onMouseLeave={handleMouseLeave}
onClick={handleClick}
key={project.id}
className={`
cursor-pointer
relative
rounded-lg
dark:bg-gray-900
@ -49,7 +57,7 @@ const PreviewBlock = (props: IPreviewBlockProps) => {
flex
items-center
space-x-3
hover:border-gray-400
hover:bg-gray-200
`}
>
<div className="flex-1 min-w-0">{truncateStr(project.name, 25)}</div>
@ -57,18 +65,11 @@ const PreviewBlock = (props: IPreviewBlockProps) => {
{isHovering && (
<div className="flex space-x-1 absolute top-2 right-2">
<button
onClick={() => onDelete()}
onClick={onDelete}
className="flex justify-center items-center p-2 hover:bg-gray-100 shadow bg-white rounded-md"
>
<TrashIcon className="w-3 h-3 text-red-500 hover:text-red-600" />
</button>
<Link
to={`/projects/${project.uuid}`}
className="flex justify-center items-center p-2 hover:bg-gray-100 shadow bg-white rounded-md"
>
<PencilIcon className="w-3 h-3 text-gray-500 hover:text-gray-600" />
</Link>
</div>
)}
</div>

@ -5,6 +5,7 @@ import { IProject } from "../../types";
import Spinner from "../../components/global/Spinner";
import PreviewBlock from "./PreviewBlock";
import { useProjects } from "../../hooks/useProjects";
import { PlusIcon } from "@heroicons/react/outline";
const Projects = () => {
const [limit] = useState(PROJECTS_FETCH_LIMIT);
@ -22,22 +23,24 @@ const Projects = () => {
Projects
</h1>
<Link
className="btn-util text-white bg-blue-600 hover:bg-blue-700 sm:w-auto"
to="/projects/new"
>
<span>Create new project</span>
</Link>
{data && data.results.length > 0 && (
<Link
className="btn-util text-white bg-blue-600 hover:bg-blue-700 sm:w-auto"
to="/projects/new"
>
<span>Create new project</span>
</Link>
)}
</div>
<div className="px-4 sm:px-6 md:px-8">
{isFetching && (
{(isFetching || isLoading) && (
<div className="flex justify-center items-center mx-auto mt-10">
<Spinner className="w-6 h-6 text-blue-600" />
</div>
)}
{!isFetching && (
{!isFetching && !isLoading && (
<>
<div className="py-4">
{error && (
@ -70,22 +73,23 @@ const Projects = () => {
minHeight: "calc(100vh - 120px)"
}}
>
<img
style={{
width: 400,
height: "auto"
}}
src="https://res.cloudinary.com/hypertool/image/upload/v1657816359/hypertool-assets/empty-projects_fdcxtk.svg"
/>
<p className="mt-4 text-md text-gray-500 dark:text-gray-400">
We tried our best, but could not find any projects.
<h3 className="mt-2 text-sm font-medium text-gray-900">
No projects
</h3>
<p className="mt-1 text-sm text-gray-500">
Get started by creating a new project.
</p>
<Link
className="btn-util text-white bg-blue-600 hover:bg-blue-700 sm:w-auto mt-3"
to="/projects/new"
>
<span>Create new project</span>
</Link>
<div className="mt-6">
<Link to="/projects/new" className="btn-util">
<span className="flex space-x-1 items-center">
<PlusIcon
className="h-3 w-3"
aria-hidden="true"
/>
<span>New Project</span>
</span>
</Link>
</div>
</div>
)}
</div>

@ -28,6 +28,9 @@ const Root = styled("div")`
justify-content: flex-start;
align-items: flex-start;
column-gap: ${({ theme }) => theme.spacing(2)};
@media (max-width: 768px) {
column-gap: ${({ theme }) => theme.spacing(1)};
}
`;
const RemoveButton = styled(IconButton)``;

@ -12,6 +12,7 @@ export interface ITextFieldProps {
const Root = styled("div")`
display: flex;
flex-direction: column;
width: 100%;
`;
const TextField: FunctionComponent<ITextFieldProps> = (
@ -25,10 +26,7 @@ const TextField: FunctionComponent<ITextFieldProps> = (
return (
<Root>
{label && (
<label
htmlFor={name}
className="block text-xs font-medium text-gray-700"
>
<label htmlFor={name} className="lbl-util">
{label + (required ? "*" : "")}
</label>
)}
@ -37,8 +35,8 @@ const TextField: FunctionComponent<ITextFieldProps> = (
id={name}
name={name}
type="text"
autoComplete="none"
className="input-util mt-1"
autoComplete="off"
className="input-util"
required={required}
onBlur={formik.handleBlur}
onChange={formik.handleChange}

@ -16,7 +16,7 @@ const Search = (props: ISearchProps) => {
action=""
>
<input
autoComplete="false"
autoComplete="off"
name="hidden"
type="text"
className="hidden"

@ -29,15 +29,15 @@ export default function SideBar(props: ISideBarProps) {
return (
<>
<div className="md:flex md:w-16 md:flex-col md:fixed md:inset-y-0">
<div className="flex flex-col flex-grow pt-5 bg-blue-700 overflow-y-auto">
<div className="flex items-center flex-shrink-0 mx-auto">
<div className="flex justify-between flex-col sm:flex-row md:flex-col md:flex-grow md:pt-5 bg-blue-700 overflow-y-auto">
<div className="flex items-center flex-shrink-0 mx-auto p-2 ">
<Link to={isAuthenticated ? "/" : "projects/new"}>
<Logo />
</Link>
</div>
<div className="mt-5 flex-1 flex flex-col">
<nav className="flex-1 px-2 pb-4 space-y-1">
<div className="md:mt-5 flex-1 flex flex-col items-center sm:flex-row md:flex-col justify-end">
<nav className="md:flex-1 space-y-1">
{navigation.map((item) => (
<a
key={item.name}
@ -46,23 +46,23 @@ export default function SideBar(props: ISideBarProps) {
item.current
? "bg-blue-800 text-white"
: "text-blue-100 hover:bg-blue-600",
"group flex items-center justify-center px-2 py-2 text-sm font-medium rounded-md"
"group flex items-center justify-center p-2 text-sm font-medium rounded-md"
)}
>
<item.icon
className="mr-3 md:mr-0 flex-shrink-0 h-5 w-5"
className="mr-3 sm:mr-0 flex-shrink-0 h-5 w-5"
aria-hidden="true"
/>
<span className="md:hidden">{item.name}</span>
<span className="sm:hidden">{item.name}</span>
</a>
))}
</nav>
</div>
<UserMenu
username={userName}
current={pathname.includes("profile")}
/>
<UserMenu
username={userName}
current={pathname.includes("profile")}
/>
</div>
</div>
</div>
</>

@ -19,18 +19,15 @@ export default function UserMenu(props: IUserMenuProps) {
${
current ? "bg-blue-800 text-white" : "text-blue-100 hover:bg-blue-600"
},
flex border-t border-blue-800 p-4 w-full hover:cursor-pointer hover:bg-blue-600
flex md:border-t md:border-blue-800 p-4 md:w-full hover:cursor-pointer hover:bg-blue-600
`}
>
<div className="flex items-center mx-auto">
<UserCircleIcon className="inline-block h-8 w-8 rounded-full text-white" />
<div className="ml-3 md:ml-0">
<p className="text-base font-medium text-white md:hidden">
<div className="ml-3 sm:ml-0">
<p className="text-sm font-medium text-white sm:hidden">
{username ? <>{username}</> : <>Log in</>}
</p>
<p className="text-sm font-medium text-indigo-200 group-hover:text-white md:hidden">
View profile
</p>
</div>
</div>
</div>

@ -8,7 +8,7 @@ const Logo = () => {
xmlns="http://www.w3.org/2000/svg"
xmlnsXlink="http://www.w3.org/1999/xlink"
>
<title>Nuxx</title>
<title>CTK</title>
<g
id="Page-1"
stroke="none"
@ -17,9 +17,9 @@ const Logo = () => {
fillRule="evenodd"
>
<g id="Logo" transform="translate(-179.000000, -225.000000)">
<g id="nuxx-logo-copy" transform="translate(167.000000, 200.000000)">
<g id="ctk-logo-copy" transform="translate(167.000000, 200.000000)">
<g
id="nuxx-logo"
id="ctk-logo"
transform="translate(98.500000, 95.000000) rotate(-60.000000) translate(-98.500000, -95.000000) translate(33.000000, 20.000000)"
>
<polygon

@ -1,3 +1,3 @@
export const API_SERVER_URL = process.env.REACT_APP_API_SERVER;
export const PROJECTS_FETCH_LIMIT = 300;
export const LOCAL_STORAGE = "NuxxLocalStorage";
export const LOCAL_STORAGE = "CtkLocalStorage";

@ -16,6 +16,7 @@ body {
.canvas {
position: relative;
height: 100%;
width: 100%;
}
.jsplumb-box {
@ -112,6 +113,9 @@ path,
.btn-util {
@apply inline-flex items-center px-2.5 py-1.5 border border-transparent text-xs font-medium rounded text-indigo-700 bg-indigo-100 hover:bg-indigo-200 focus:outline-none focus:ring-1 focus:ring-offset-1 focus:ring-indigo-500;
}
.lbl-util {
@apply block text-xs font-medium text-gray-700 mb-1
}
.btn-util-red {
@apply inline-flex items-center px-2.5 py-1.5 border border-transparent text-xs font-medium rounded text-red-700 bg-red-100 hover:bg-red-200 focus:outline-none focus:ring-1 focus:ring-offset-1 focus:ring-red-500;
}

@ -1,7 +1,6 @@
import { IGeneratePayload } from "../types";
import { API_SERVER_URL } from "../constants";
export const generateHttp = (data: IGeneratePayload) => {
export const generateHttp = (data: string) => {
return fetch(`${API_SERVER_URL}/generate/`, {
method: "POST",
headers: {

@ -12,6 +12,11 @@ export const generatePayload = (data: any): IGeneratePayload => {
}
};
Object.keys(networks).forEach((key) => {
base.data.networks[networks[key].canvasConfig.node_name] =
networks[key].networkConfig;
});
Object.keys(nodes).forEach((key) => {
if (nodes[key].type === "SERVICE") {
base.data.services[nodes[key].canvasConfig.node_name] =
@ -24,10 +29,5 @@ export const generatePayload = (data: any): IGeneratePayload => {
}
});
Object.keys(networks).forEach((key) => {
base.data.networks[networks[key].canvasConfig.node_name] =
networks[key].networkConfig;
});
return base;
};

Loading…
Cancel
Save