crane-cloud/frontend

View on GitHub
src/pages/MiraPage/index.jsx

Summary

Maintainability
A
55 mins
Test Coverage
F
46%
import React, { useState, useRef } from "react";
//import axios from "axios";
import Dropzone from "../../components/DropZone";
import styles from "./MiraPage.module.css";
import BlackInputText from "../../components/BlackInputText";
import Select from "../../components/Select";
import PrimaryButton from "../../components/PrimaryButton";
import Tooltip from "../../components/Tooltip";
import { MIRA_API_URL } from "../../config";
import Spinner from "../../components/Spinner";
import { retrieveFrameworkChoices } from "../../helpers/frameworkChoices";
import { retrieveRegistryChoices } from "../../helpers/registryChoices";
import { ReactComponent as BackButton } from "../../assets/images/arrow-left.svg";


const MiraPge = ({ projectID }) => {
  const frameworks = retrieveFrameworkChoices();
  const registries = retrieveRegistryChoices();
  const logsSectionRef = useRef(null);

  const [files, setFiles] = useState([]);
  const [framework, setFramework] = useState("");
  const [registry, setRegistry] = useState("");
  const [image, setImage] = useState({
    name: "",
    version: "",
  });
  const [loading, setLoader] = useState(false);
  const [error, setError] = useState("");
  const [logs, setLogs] = useState([]);

  const [successfulDeployment, setSuccessfulDeployment] = useState();  

  const getPathName = (path) => {
    let filePath = path.replaceAll("/", "|").replace("|", "");
    filePath = filePath.substring(filePath.indexOf("|")).replace("|", "");
    return filePath;
  };
  const handleChange = ({ target }) => {
    const { name, value } = target;
    if (error) {
      setError("");
    }
    setImage({
      ...image,
      [name]: value,
    });
  };
  const handleDropdownChange = (selected) => {
    setFramework(selected.value);
  };

  const handleRegistryDropdownChange = (selected) => {
    setRegistry(selected.value);
  };

  const handleSubmit = async () => {
    setLoader(true);
    const token = localStorage.getItem("token");
    const formData = new FormData();
    const { name, version } = image;
    for (const file of files) {
      const { path } = file;
      formData.append("files", file, getPathName(path));
    }
    formData.append("name", name);
    formData.append("tag", version);
    formData.append("framework", framework);
    formData.append("token", token);
    formData.append("project", projectID);
    formData.append("registry", registry);

    // axios
    //   .post(`${MIRA_API_URL}/containerize`, formData)
    //   .then(res => {
    //     setLoader(false);
    //     console.log(res)
    //    // window.location.href = `/projects/${projectID}/Apps`;
    //   })
    //   .catch(error => {
    //     console.log(error)
    //     setLoader(false);
    //     setError("failed to deploy");
    //   });
    if (logsSectionRef.current) {
      logsSectionRef.current.scrollIntoView({ behavior: 'smooth' });
    }
    //since it is stream data it will always return 200

    fetch(`${MIRA_API_URL}/containerize`, {
      method: 'POST',
      body: formData
    }).then((response) => {
    const reader = response.body.getReader();
    const decoder = new TextDecoder();
    const readStream = () => {
      reader.read().then(({ done, value }) => {
        if (done) {
          setLoader(false);
          return;
        }
        const str = decoder.decode(value);
        
        const regex = /{"timestamp":"(.*?)","message":"(.*?)","success":(true|false)}(?={|$)|{"timestamp":"(.*?)","message":"(.*?)"}(?={|$)/g;
        let match;
        while ((match = regex.exec(str)) !== null) {
          const timestamp = match[1] || match[4]; 
          const message = match[2] || match[5]; 
           if(match[3] !== undefined){
            setSuccessfulDeployment(match[3])
           }
          setLogs((prevLogs) => [...prevLogs, {timestamp: timestamp, message: message}])
        }
        readStream();
      }).catch((error) => {
       const errorLog =  { timestamp: new Date().toISOString(), message: "Error reading log stream" }
       setLogs((prevLogs) => [...prevLogs, errorLog])
      });
    };
    // Start consuming the stream
    readStream();
  })
  .catch((error) => {                                                 
    console.error('Fetch error:', error);
  });
  };

  return (
    <div className={styles.CreateFormHolder}>
      <div className={styles.FormInputs}>
        <div className={styles.FormHeading}>Fields marked * are required</div>
        <div className={styles.FrameworkSelect}>
          <Select
            placeholder="Choose a framework"
            options={frameworks}
            onChange={handleDropdownChange}
          />
        </div>
        {framework === "Django" && (
          <div>
            If deploying a Django app also see these additional pre-deployment
            <a href="https://docs.google.com/document/d/1-zqaLC4x4yZflRS-LMhycVbhpCEvyId0smaqAwC5TBE/edit?usp=sharing">
              instructions
            </a>{" "}
          </div>
        )}
        {framework === "Laravel-custom" && (
          <div>
            Please make sure your project has a custom dockerfile added in the
            root of your Laravel app<br></br>
            <a href="https://medium.com/cranecloud/dockerizing-a-laravel-application-36b5ccd23691">
              Take an example
            </a>{" "}
            <br></br> Be sure to use your current version of laravel in your
            dockerfile{" "}
          </div>
        )}
        <div className={styles.RegistrySelect}>
          <Select
            placeholder="Select a registry"
            options={registries}
            onChange={handleRegistryDropdownChange}
          />
        </div>
        <div className={styles.FormFieldWithTooltip}>
          <BlackInputText
            required
            name="name"
            placeholder="Image Name"
            onChange={handleChange}
            value={image.name}
          />
          <div className={styles.FormInputTooltipContainer}>
            <Tooltip
              showIcon
              message="This is the image repository for your image"
              position="left"
            />
          </div>
        </div>
        <div className={styles.FormFieldWithTooltip}>
          <BlackInputText
            placeholder="Version"
            name="version"
            onChange={handleChange}
            value={image.version}
          />
          <div className={styles.FormInputTooltipContainer}>
            <Tooltip
              showIcon
              message="This is preferably a tag for your image"
              position="left"
            />
          </div>
        </div>

        <div className={styles.HeadingWithTooltip}>
          <h4>Upload Zip file</h4>
          <Tooltip
            showIcon
            message="This is the zipped folder containing your source code"
          />
        </div>
        <div className={styles.DropSection}>
          <div className={styles.Dropzone}>
            <Dropzone handleDrop={(files) => setFiles(files)} />
          </div>
        </div>
        {error && <div className="LoginErrorDiv">{error}</div>}
        <div className={styles.ButtonSection}>
          <PrimaryButton className="AuthBtn FullWidth" onClick={handleSubmit}>
            {loading ? <Spinner /> : "Deploy"}
          </PrimaryButton>
        </div>
      </div>
      
       
      <h2>Mira Logs</h2>
      <div className={`${styles.MiraLogsFrameContainer}`}>
        <div className={"LogsBodySection Dark"} ref={logsSectionRef}>
          <div  className={`LogsAvailable ${styles.logsDiv}`}>
            _/
            {logs.map((log, index) => (
              <div key={index} className={`LogsAvailable`}>
                <div className={styles.logItem}>
                  <span className={styles.logItemTitle}>
                    {new Date(log.timestamp).toLocaleTimeString()}
                  </span>
                  <p>{log.message}</p>
                  {/* {log} */}
                </div>
              </div>
            ))}
          </div>
        </div>
      </div>
       
      
      {successfulDeployment==="false" && (
        <div className={styles.successfulDeployment}>
          <div className={styles.ErrorDiv}>Failed to deploy application</div>
        </div>
      )}
      {successfulDeployment==="true" && (
        <div className={styles.successfulDeployment}>
          <div className="LoginFeedBackDiv"> Deployed successfully</div>
          <div className={styles.ButtonSection}>
            <PrimaryButton className="AuthBtn FullWidth" onClick={()=> {window.location.href = `/projects/${projectID}/Apps`}}>
              <BackButton/> View applications
            </PrimaryButton>
          </div>
        </div>
      )}
    </div>
  );
};

export default MiraPge;