All files / src/components/panels DockerContainerPicker.tsx

85.71% Statements 12/14
100% Branches 17/17
71.42% Functions 5/7
85.71% Lines 12/14

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161                                                                                                          92x                   22x 22x 22x 22x                                                                                                 5x 5x 5x               24x                                     24x 24x 3x                            
import { type JSX } from "react";
import type { DockerContainer } from "../../hooks/types.js";
import styles from "./EnvironmentEditPanel.module.scss";
 
/** Props for the DockerContainerPicker component. */
export interface DockerContainerPickerProps {
  /** Whether to create a new container or attach to an existing one. */
  dockerMode: "create" | "attach";
  /** Called when the docker mode changes. */
  onDockerModeChange: (mode: "create" | "attach") => void;
  /** Docker image for new container creation. */
  image: string;
  /** Called when the image value changes. */
  onImageChange: (image: string) => void;
  /** Repository to clone into the new container. */
  repo: string;
  /** Called when the repo value changes. */
  onRepoChange: (repo: string) => void;
  /** Currently selected container name for attach mode. */
  attachContainer: string;
  /** Called when the attach container selection changes. */
  onAttachContainerChange: (container: string) => void;
  /** Current environment name (used for auto-fill). */
  envName: string;
  /** Called when the environment name should be auto-filled from the container. */
  onEnvNameChange: (name: string) => void;
  /** Running Docker containers available to attach to. */
  dockerContainers: DockerContainer[];
  /** Non-fatal error from listing Docker containers (e.g. docker CLI unavailable). */
  dockerContainersError: string;
  /** Callback to list running Docker containers. */
  onListDockerContainers: () => void;
}
 
/**
 * Docker source picker — toggle between creating a new container and attaching
 * to an existing one. Shows container discovery or a manual-entry fallback.
 */
export function DockerContainerPicker({
  dockerMode,
  onDockerModeChange,
  image,
  onImageChange,
  repo,
  onRepoChange,
  attachContainer,
  onAttachContainerChange,
  envName,
  onEnvNameChange,
  dockerContainers,
  dockerContainersError,
  onListDockerContainers,
}: DockerContainerPickerProps): JSX.Element {
  return (
    <>
      <div className={styles.section}>
        <label className={styles.label} htmlFor="env-docker-mode">
          Source
        </label>
        <select
          id="env-docker-mode"
          value={dockerMode}
          onChange={(e) => {
            const next = e.target.value as "create" | "attach";
            onDockerModeChange(next);
            if (next === "attach") {
              onListDockerContainers();
            }
          }}
          className={styles.adapterSelect}
          data-testid="env-docker-mode"
        >
          <option value="create">Create new container</option>
          <option value="attach">Attach to existing container</option>
        </select>
      </div>
 
      {dockerMode === "create" ? (
        <>
          <div className={styles.section}>
            <label className={styles.label} htmlFor="env-create-image">
              Image
            </label>
            <input
              id="env-create-image"
              type="text"
              value={image}
              onChange={(e) => onImageChange(e.target.value)}
              placeholder="Image (optional)..."
              className={styles.fieldInput}
              data-testid="env-create-image"
            />
          </div>
          <div className={styles.section}>
            <label className={styles.label} htmlFor="env-create-repo">
              Repo
            </label>
            <input
              id="env-create-repo"
              type="text"
              value={repo}
              onChange={(e) => onRepoChange(e.target.value)}
              placeholder="Repo (optional)..."
              className={styles.fieldInput}
              data-testid="env-create-repo"
            />
          </div>
        </>
      ) : (
        <div className={styles.section}>
          <label className={styles.label}>Container</label>
          {!dockerContainersError && dockerContainers.length > 0 && (
            <select
              value={attachContainer}
              onChange={(e) => {
                onAttachContainerChange(e.target.value);
                if (e.target.value && !envName.trim()) {
                  onEnvNameChange(e.target.value);
                }
              }}
              className={styles.adapterSelect}
              data-testid="env-docker-container-select"
            >
              <option value="">Select a container...</option>
              {dockerContainers.map((c) => (
                <option key={c.id} value={c.name}>
                  {c.name} ({c.image}) {c.status}
                </option>
              ))}
            </select>
          )}
          {/* Manual entry fallback: shown on listing error OR when no running
              containers were found, so the user is never stuck with an empty picker. */}
          {(dockerContainersError || dockerContainers.length === 0) && (
            <>
              {dockerContainersError ? (
                <span className={styles.errorHint}>{dockerContainersError}</span>
              ) : (
                <span className={styles.creatingHint}>No running containers found.</span>
              )}
              <input
                type="text"
                value={attachContainer}
                onChange={(e) => {
                  onAttachContainerChange(e.target.value);
                  if (e.target.value && !envName.trim()) {
                    onEnvNameChange(e.target.value);
                  }
                }}
                placeholder="Enter container name/ID..."
                className={styles.fieldInput}
                data-testid="env-docker-container-manual"
              />
            </>
          )}
        </div>
      )}
    </>
  );
}