2026-02-27 04:29:51 +00:00
import { useState } from "react" ;
import { useDocker } from "../../hooks/useDocker" ;
2026-02-27 15:22:49 +00:00
import { useSettings } from "../../hooks/useSettings" ;
import type { ImageSource } from "../../lib/types" ;
2026-03-12 09:35:04 -07:00
import Tooltip from "../ui/Tooltip" ;
2026-02-27 15:22:49 +00:00
const REGISTRY_IMAGE = "repo.anhonesthost.net/cybercovellc/triple-c/triple-c-sandbox:latest" ;
const IMAGE_SOURCE_OPTIONS : { value : ImageSource ; label : string ; description : string } [ ] = [
{ value : "registry" , label : "Registry" , description : "Pull from container registry" } ,
{ value : "local_build" , label : "Local Build" , description : "Build from embedded Dockerfile" } ,
{ value : "custom" , label : "Custom" , description : "Specify a custom image" } ,
] ;
2026-02-27 04:29:51 +00:00
export default function DockerSettings() {
2026-02-27 15:22:49 +00:00
const { dockerAvailable , imageExists , checkDocker , checkImage , buildImage , pullImage } =
2026-02-27 04:29:51 +00:00
useDocker ( ) ;
2026-02-27 15:22:49 +00:00
const { appSettings , saveSettings } = useSettings ( ) ;
const [ working , setWorking ] = useState ( false ) ;
const [ log , setLog ] = useState < string [ ] > ( [ ] ) ;
2026-02-27 04:29:51 +00:00
const [ error , setError ] = useState < string | null > ( null ) ;
2026-02-27 15:22:49 +00:00
const [ customInput , setCustomInput ] = useState ( appSettings ? . custom_image_name ? ? "" ) ;
const imageSource = appSettings ? . image_source ? ? "registry" ;
const resolvedImageName = ( ( ) = > {
switch ( imageSource ) {
case "registry" : return REGISTRY_IMAGE ;
case "local_build" : return "triple-c:latest" ;
case "custom" : return customInput || REGISTRY_IMAGE ;
}
} ) ( ) ;
const handleSourceChange = async ( source : ImageSource ) = > {
if ( ! appSettings ) return ;
await saveSettings ( { . . . appSettings , image_source : source } ) ;
2026-02-28 20:42:40 +00:00
await checkImage ( ) ;
2026-02-27 15:22:49 +00:00
} ;
const handleCustomChange = async ( value : string ) = > {
setCustomInput ( value ) ;
if ( ! appSettings ) return ;
await saveSettings ( { . . . appSettings , custom_image_name : value || null } ) ;
} ;
const handlePull = async ( ) = > {
setWorking ( true ) ;
setLog ( [ ] ) ;
setError ( null ) ;
try {
await pullImage ( resolvedImageName , ( msg ) = > {
setLog ( ( prev ) = > [ . . . prev , msg ] ) ;
} ) ;
await checkImage ( ) ;
} catch ( e ) {
setError ( String ( e ) ) ;
} finally {
setWorking ( false ) ;
}
} ;
2026-02-27 04:29:51 +00:00
const handleBuild = async ( ) = > {
2026-02-27 15:22:49 +00:00
setWorking ( true ) ;
setLog ( [ ] ) ;
2026-02-27 04:29:51 +00:00
setError ( null ) ;
try {
await buildImage ( ( msg ) = > {
2026-02-27 15:22:49 +00:00
setLog ( ( prev ) = > [ . . . prev , msg ] ) ;
2026-02-27 04:29:51 +00:00
} ) ;
2026-02-27 15:22:49 +00:00
await checkImage ( ) ;
2026-02-27 04:29:51 +00:00
} catch ( e ) {
setError ( String ( e ) ) ;
} finally {
2026-02-27 15:22:49 +00:00
setWorking ( false ) ;
2026-02-27 04:29:51 +00:00
}
} ;
return (
< div >
< label className = "block text-sm font-medium mb-2" > Docker < / label >
2026-02-27 15:22:49 +00:00
< div className = "space-y-3 text-sm" >
2026-02-27 04:29:51 +00:00
< div className = "flex items-center justify-between" >
< span className = "text-[var(--text-secondary)]" > Docker Status < / span >
< span className = { dockerAvailable ? "text-[var(--success)]" : "text-[var(--error)]" } >
{ dockerAvailable === null ? "Checking..." : dockerAvailable ? "Connected" : "Not Available" }
< / span >
< / div >
2026-02-27 15:22:49 +00:00
{ /* Image Source Selector */ }
< div >
2026-03-12 09:35:04 -07:00
< span className = "text-[var(--text-secondary)] text-xs block mb-1.5" > Image Source < Tooltip text = "Registry pulls the pre-built image. Local Build compiles from the bundled Dockerfile. Custom lets you specify any image." / > < / span >
2026-02-27 15:22:49 +00:00
< div className = "flex gap-1" >
{ IMAGE_SOURCE_OPTIONS . map ( ( opt ) = > (
< button
key = { opt . value }
onClick = { ( ) = > handleSourceChange ( opt . value ) }
className = { ` flex-1 px-2 py-1.5 text-xs rounded border transition-colors ${
imageSource === opt . value
? "bg-[var(--accent)] text-white border-[var(--accent)]"
: "bg-[var(--bg-tertiary)] border-[var(--border-color)] hover:bg-[var(--border-color)]"
} ` }
title = { opt . description }
>
{ opt . label }
< / button >
) ) }
< / div >
< / div >
{ /* Custom image input */ }
{ imageSource === "custom" && (
< div >
2026-03-12 09:35:04 -07:00
< span className = "text-[var(--text-secondary)] text-xs block mb-1" > Custom Image < Tooltip text = "Full image name including registry and tag (e.g. myregistry.com/image:tag)." / > < / span >
2026-02-27 15:22:49 +00:00
< input
type = "text"
value = { customInput }
onChange = { ( e ) = > handleCustomChange ( e . target . value ) }
placeholder = "e.g., myregistry.com/image:tag"
className = "w-full px-2 py-1.5 text-xs bg-[var(--bg-primary)] border border-[var(--border-color)] rounded focus:outline-none focus:border-[var(--accent)]"
/ >
< / div >
) }
{ /* Resolved image display */ }
2026-03-12 09:26:58 -07:00
< div >
2026-02-27 04:29:51 +00:00
< span className = "text-[var(--text-secondary)]" > Image < / span >
2026-03-12 09:26:58 -07:00
< span className = "block text-xs text-[var(--text-secondary)] font-mono mt-0.5 truncate" title = { resolvedImageName } >
2026-02-27 15:22:49 +00:00
{ resolvedImageName }
< / span >
< / div >
< div className = "flex items-center justify-between" >
< span className = "text-[var(--text-secondary)]" > Status < / span >
2026-02-27 04:29:51 +00:00
< span className = { imageExists ? "text-[var(--success)]" : "text-[var(--text-secondary)]" } >
2026-02-27 15:22:49 +00:00
{ imageExists === null ? "Checking..." : imageExists ? "Ready" : "Not Found" }
2026-02-27 04:29:51 +00:00
< / span >
< / div >
2026-02-27 15:22:49 +00:00
{ /* Action buttons */ }
2026-02-27 04:29:51 +00:00
< div className = "flex gap-2" >
< button
onClick = { async ( ) = > { await checkDocker ( ) ; await checkImage ( ) ; } }
className = "px-3 py-1.5 text-xs bg-[var(--bg-tertiary)] border border-[var(--border-color)] rounded hover:bg-[var(--border-color)] transition-colors"
>
2026-02-27 15:22:49 +00:00
Refresh
2026-02-27 04:29:51 +00:00
< / button >
2026-02-27 15:22:49 +00:00
{ imageSource === "local_build" ? (
< button
onClick = { handleBuild }
disabled = { working || ! dockerAvailable }
className = "px-3 py-1.5 text-xs bg-[var(--accent)] text-white rounded hover:bg-[var(--accent-hover)] disabled:opacity-50 transition-colors"
>
{ working ? "Building..." : imageExists ? "Rebuild Image" : "Build Image" }
< / button >
) : (
< button
onClick = { handlePull }
disabled = { working || ! dockerAvailable }
className = "px-3 py-1.5 text-xs bg-[var(--accent)] text-white rounded hover:bg-[var(--accent-hover)] disabled:opacity-50 transition-colors"
>
{ working ? "Pulling..." : imageExists ? "Re-pull Image" : "Pull Image" }
< / button >
) }
2026-02-27 04:29:51 +00:00
< / div >
2026-02-27 15:22:49 +00:00
{ /* Log output */ }
{ log . length > 0 && (
2026-02-27 04:29:51 +00:00
< div className = "max-h-40 overflow-y-auto bg-[var(--bg-primary)] border border-[var(--border-color)] rounded p-2 text-xs font-mono text-[var(--text-secondary)]" >
2026-02-27 15:22:49 +00:00
{ log . map ( ( line , i ) = > (
2026-02-27 04:29:51 +00:00
< div key = { i } > { line } < / div >
) ) }
< / div >
) }
{ error && < div className = "text-xs text-[var(--error)]" > { error } < / div > }
< / div >
< / div >
) ;
}