mirror of https://github.com/ctk-hq/ctk
feat: support k8s in ui, some component separation
parent
1f27a0541b
commit
5b826f6a27
@ -0,0 +1,156 @@
|
|||||||
|
import { useEffect, useMemo, useRef, useState } from "react";
|
||||||
|
import YAML from "yaml";
|
||||||
|
import { debounce } from "lodash";
|
||||||
|
import { generatePayload } from "../../utils/generators";
|
||||||
|
import { checkHttpStatus } from "../../services/helpers";
|
||||||
|
import { generateHttp } from "../../services/generate";
|
||||||
|
import { toaster } from "../../utils";
|
||||||
|
import eventBus from "../../events/eventBus";
|
||||||
|
import ManifestSelect from "./ManifestSelect";
|
||||||
|
import CodeEditor from "../CodeEditor";
|
||||||
|
import useWindowDimensions from "../../hooks/useWindowDimensions";
|
||||||
|
|
||||||
|
const CodeBox = () => {
|
||||||
|
const versionRef = useRef<string>();
|
||||||
|
const manifestRef = useRef<number>();
|
||||||
|
const [language, setLanguage] = useState("yaml");
|
||||||
|
const [version, setVersion] = useState("3");
|
||||||
|
const [copyText, setCopyText] = useState("Copy");
|
||||||
|
const [generatedCode, setGeneratedCode] = useState<string>("");
|
||||||
|
const [formattedCode, setFormattedCode] = useState<string>("");
|
||||||
|
const [manifest, setManifest] = useState(0);
|
||||||
|
const { height } = useWindowDimensions();
|
||||||
|
|
||||||
|
versionRef.current = version;
|
||||||
|
manifestRef.current = manifest;
|
||||||
|
|
||||||
|
const getCode = (payload: any, manifest: number) => {
|
||||||
|
generateHttp(JSON.stringify(payload), manifest)
|
||||||
|
.then(checkHttpStatus)
|
||||||
|
.then((data) => {
|
||||||
|
if (data["code"]) {
|
||||||
|
setGeneratedCode(data["code"]);
|
||||||
|
} else {
|
||||||
|
setGeneratedCode("");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data["error"].length) {
|
||||||
|
setGeneratedCode("");
|
||||||
|
toaster(`error ${data["error"]}`, "error");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => undefined)
|
||||||
|
.finally(() => undefined);
|
||||||
|
};
|
||||||
|
|
||||||
|
const debouncedOnGraphUpdate = useMemo(
|
||||||
|
() =>
|
||||||
|
debounce((payload, manifest) => {
|
||||||
|
getCode(payload, manifest);
|
||||||
|
}, 600),
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
const versionChange = (e: any) => {
|
||||||
|
setVersion(e.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const copy = () => {
|
||||||
|
navigator.clipboard.writeText(formattedCode);
|
||||||
|
setCopyText("Copied");
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
setCopyText("Copy");
|
||||||
|
}, 300);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (language === "json") {
|
||||||
|
setFormattedCode(
|
||||||
|
JSON.stringify(YAML.parseAllDocuments(generatedCode), null, 2)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (language === "yaml") {
|
||||||
|
setFormattedCode(generatedCode);
|
||||||
|
}
|
||||||
|
}, [language, generatedCode]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
eventBus.dispatch("GENERATE", {
|
||||||
|
message: {
|
||||||
|
id: ""
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, [version, manifest]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
eventBus.on("FETCH_CODE", (data) => {
|
||||||
|
const graphData = data.detail.message;
|
||||||
|
graphData.version = versionRef.current;
|
||||||
|
debouncedOnGraphUpdate(generatePayload(graphData), manifestRef.current);
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
eventBus.remove("FETCH_CODE", () => undefined);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
className={`absolute top-0 left-0 right-0 z-10 flex justify-end p-1 space-x-2 group-hover:visible invisible`}
|
||||||
|
>
|
||||||
|
<select
|
||||||
|
id="version"
|
||||||
|
onChange={versionChange}
|
||||||
|
value={version}
|
||||||
|
className="input-util w-min pr-8"
|
||||||
|
>
|
||||||
|
<option value="1">v 1</option>
|
||||||
|
<option value="2">v 2</option>
|
||||||
|
<option value="3">v 3</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<button
|
||||||
|
className={`btn-util ${
|
||||||
|
language === "json" ? `btn-util-selected` : ``
|
||||||
|
}`}
|
||||||
|
onClick={() => setLanguage("json")}
|
||||||
|
>
|
||||||
|
json
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className={`btn-util ${
|
||||||
|
language === "yaml" ? `btn-util-selected` : ``
|
||||||
|
}`}
|
||||||
|
onClick={() => setLanguage("yaml")}
|
||||||
|
>
|
||||||
|
yaml
|
||||||
|
</button>
|
||||||
|
<button className="btn-util" type="button" onClick={copy}>
|
||||||
|
{copyText}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className={`absolute top-10 left-0 right-0 z-10 flex justify-end p-1 space-x-2 group-hover:visible invisible`}
|
||||||
|
>
|
||||||
|
<ManifestSelect setManifest={setManifest} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<CodeEditor
|
||||||
|
data={formattedCode}
|
||||||
|
language={language}
|
||||||
|
onChange={() => {
|
||||||
|
return;
|
||||||
|
}}
|
||||||
|
disabled={true}
|
||||||
|
lineWrapping={false}
|
||||||
|
height={height - 64}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CodeBox;
|
||||||
@ -0,0 +1,106 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { CallbackFunction, IProject } from "../../types";
|
||||||
|
import Spinner from "../global/Spinner";
|
||||||
|
import VisibilitySwitch from "../global/VisibilitySwitch";
|
||||||
|
|
||||||
|
interface IManifestSelectProps {
|
||||||
|
onSave: CallbackFunction;
|
||||||
|
isLoading: boolean;
|
||||||
|
projectData: IProject;
|
||||||
|
isAuthenticated: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ManifestSelect = (props: IManifestSelectProps) => {
|
||||||
|
const { onSave, isLoading, projectData, isAuthenticated } = props;
|
||||||
|
const [visibility, setVisibility] = useState(false);
|
||||||
|
const [projectName, setProjectName] = useState("Untitled");
|
||||||
|
|
||||||
|
const handleNameChange = (e: any) => {
|
||||||
|
setProjectName(e.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSave = () => {
|
||||||
|
const data: any = {
|
||||||
|
name: projectName,
|
||||||
|
visibility: +visibility
|
||||||
|
};
|
||||||
|
|
||||||
|
onSave(data);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!projectData) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setProjectName(projectData.name);
|
||||||
|
setVisibility(Boolean(projectData.visibility));
|
||||||
|
}, [projectData]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="px-4 py-3 border-b border-gray-200">
|
||||||
|
<form
|
||||||
|
className="flex flex-col space-y-2 md:space-y-0 md:flex-row md:justify-between items-center"
|
||||||
|
autoComplete="off"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
className={`
|
||||||
|
bg-gray-100
|
||||||
|
appearance-none
|
||||||
|
w-full
|
||||||
|
md:w-1/2
|
||||||
|
lg:w-1/3
|
||||||
|
block
|
||||||
|
text-gray-700
|
||||||
|
border
|
||||||
|
border-gray-100
|
||||||
|
dark:bg-gray-900
|
||||||
|
dark:text-white
|
||||||
|
dark:border-gray-900
|
||||||
|
rounded
|
||||||
|
py-2
|
||||||
|
px-3
|
||||||
|
leading-tight
|
||||||
|
focus:outline-none
|
||||||
|
focus:border-indigo-400
|
||||||
|
focus:ring-0
|
||||||
|
`}
|
||||||
|
type="text"
|
||||||
|
placeholder="Project name"
|
||||||
|
autoComplete="off"
|
||||||
|
id="name"
|
||||||
|
name="name"
|
||||||
|
onChange={handleNameChange}
|
||||||
|
value={projectName}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="flex flex-col space-y-2 w-full justify-end mb-4 md:flex-row md:space-y-0 md:space-x-2 md:mb-0">
|
||||||
|
{isAuthenticated && (
|
||||||
|
<VisibilitySwitch
|
||||||
|
isVisible={visibility}
|
||||||
|
onToggle={() => {
|
||||||
|
setVisibility(!visibility);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={() => handleSave()}
|
||||||
|
type="button"
|
||||||
|
className="btn-util text-white bg-green-600 hover:bg-green-700 sm:w-auto"
|
||||||
|
>
|
||||||
|
<div className="flex justify-center items-center space-x-2 mx-auto">
|
||||||
|
{isLoading && <Spinner className="w-4 h-4 text-green-300" />}
|
||||||
|
{isLoading && <Spinner className="w-4 h-4 text-green-300" />}
|
||||||
|
<span>Save</span>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ManifestSelect;
|
||||||
@ -0,0 +1,51 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import DcLogo from "../global/dc-logo";
|
||||||
|
import K8sLogo from "../global/k8s-logo";
|
||||||
|
|
||||||
|
const styles = {
|
||||||
|
default: {
|
||||||
|
filter: "grayscale(100%)",
|
||||||
|
opacity: "90%"
|
||||||
|
},
|
||||||
|
selected: {
|
||||||
|
filter: "grayscale(0)",
|
||||||
|
opacity: "100%"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
interface IManifestSelectProps {
|
||||||
|
setManifest: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ManifestSelect = (props: IManifestSelectProps) => {
|
||||||
|
const { setManifest } = props;
|
||||||
|
const [selected, setSelected] = useState(0);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<button
|
||||||
|
style={selected === 1 ? styles.selected : styles.default}
|
||||||
|
type="button"
|
||||||
|
onClick={() => {
|
||||||
|
setManifest(1);
|
||||||
|
setSelected(1);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<K8sLogo />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
style={selected === 0 ? styles.selected : styles.default}
|
||||||
|
type="button"
|
||||||
|
onClick={() => {
|
||||||
|
setManifest(0);
|
||||||
|
setSelected(0);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DcLogo />
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ManifestSelect;
|
||||||
Loading…
Reference in New Issue