@@ -18,7 +18,7 @@ import { Popover } from "@patternfly/react-core/dist/esm/components/Popover";
1818import { MinusIcon , OutlinedQuestionCircleIcon } from '@patternfly/react-icons' ;
1919import * as dockerNames from 'docker-names' ;
2020
21- import { ErrorNotification } from './Notification.jsx' ;
21+ import { ErrorNotification , WarningNotification } from './Notification.jsx' ;
2222import * as utils from './util.js' ;
2323import * as client from './client.js' ;
2424import rest from './rest.js' ;
@@ -54,10 +54,10 @@ const units = {
5454
5555// healthchecks.go HealthCheckOnFailureAction
5656const HealthCheckOnFailureActionOrder = [
57- { value : 0 , label : _ ( "No action" ) } ,
58- { value : 3 , label : _ ( "Restart" ) } ,
59- { value : 4 , label : _ ( "Stop" ) } ,
60- { value : 2 , label : _ ( "Force stop" ) } ,
57+ { value : 0 , label : _ ( "No action" ) , apiName : "none" } ,
58+ { value : 3 , label : _ ( "Restart" ) , apiName : "restart" } ,
59+ { value : 4 , label : _ ( "Stop" ) , apiName : "stop" } ,
60+ { value : 2 , label : _ ( "Force stop" ) , apiName : "kill" } ,
6161] ;
6262
6363const handleEnvValue = ( key , value , idx , onChange , additem , itemCount , companionField ) => {
@@ -175,6 +175,9 @@ export class ImageRunModal extends React.Component {
175175 componentDidMount ( ) {
176176 this . _isMounted = true ;
177177 this . onSearchTriggered ( this . state . searchText ) ;
178+
179+ if ( this . props . prefill )
180+ this . prefillModal ( ) ;
178181 }
179182
180183 componentWillUnmount ( ) {
@@ -635,6 +638,72 @@ export class ImageRunModal extends React.Component {
635638 return owner === systemOwner ;
636639 } ;
637640
641+ prefillModal ( ) {
642+ const container = this . props . container ;
643+ const containerDetail = this . props . containerDetail ;
644+ const image = this . props . localImages . find ( img => img . Id === container . ImageID ) ;
645+
646+ if ( containerDetail . Config . CreateCommand ) {
647+ this . setState ( {
648+ dialogWarning : _ ( "This container was not created by cockpit" ) ,
649+ dialogWarningDetail : _ ( "Some options may not be copied to the new container." ) ,
650+ } ) ;
651+ }
652+
653+ const env = containerDetail . Config . Env . filter ( variable => {
654+ if ( image . Env . includes ( variable ) ) {
655+ return false ;
656+ }
657+
658+ return ! variable . match ( / ( ( H O M E | T E R M | H O S T N A M E ) = .* ) | c o n t a i n e r = p o d m a n / ) ;
659+ } ) . map ( ( variable , index ) => {
660+ const split = variable . split ( '=' ) ;
661+ return { key : index , envKey : split [ 0 ] , envValue : split [ 1 ] } ;
662+ } ) ;
663+
664+ const publish = container . Ports
665+ ? container . Ports . map ( ( port , index ) => {
666+ return { key : index , IP : port . hostIP || port . host_ip , containerPort : port . containerPort || port . container_port , hostPort : port . hostPort || port . host_port , protocol : port . protocol } ;
667+ } )
668+ : [ ] ;
669+
670+ const volumes = containerDetail . Mounts . map ( ( mount , index ) => {
671+ // podman does not expose SELinux labels
672+ return { key : index , containerPath : mount . Destination , hostPath : mount . Source , mode : ( mount . RW ? 'rw' : 'ro' ) , selinux : '' } ;
673+ } ) ;
674+
675+ // check if memory and cpu limitations or healthcheck are used
676+ const memoryConfigure = containerDetail . HostConfig . Memory > 0 ;
677+ const cpuSharesConfigure = containerDetail . HostConfig . CpuShares > 0 ;
678+ const healthcheck = ! ! containerDetail . Config . Healthcheck ;
679+ const healthCheckOnFailureAction = HealthCheckOnFailureActionOrder . find ( item => item . apiName === containerDetail . Config . HealthcheckOnFailureAction ) . value ;
680+
681+ this . setState ( {
682+ command : container . Command ? container . Command . join ( ' ' ) : "" ,
683+ containerName : container . Names [ 0 ] + "_copy" ,
684+ env,
685+ hasTTY : containerDetail . Config . Tty ,
686+ publish,
687+ // memory in MB
688+ memory : memoryConfigure ? ( containerDetail . HostConfig . Memory / 1000000 ) : 512 ,
689+ cpuShares : cpuSharesConfigure ? containerDetail . HostConfig . CpuShares : 1024 ,
690+ memoryConfigure,
691+ cpuSharesConfigure,
692+ volumes,
693+ owner : container . isSystem ? 'system' : this . props . user ,
694+ // unless-stopped: Identical to always
695+ restartPolicy : containerDetail . HostConfig . RestartPolicy . Name === 'unless-stopped' ? 'always' : containerDetail . HostConfig . RestartPolicy . Name ,
696+ selectedImage : image ,
697+ healthcheck_command : healthcheck ? containerDetail . Config . Healthcheck . Test . join ( ' ' ) : "" ,
698+ // convert to seconds
699+ healthcheck_interval : healthcheck ? ( containerDetail . Config . Healthcheck . Interval / 1000000000 ) : 30 ,
700+ healthcheck_timeout : healthcheck ? ( containerDetail . Config . Healthcheck . Timeout / 1000000000 ) : 30 ,
701+ healthcheck_start_period : healthcheck ? ( containerDetail . Config . Healthcheck . StartPeriod / 1000000000 ) : 0 ,
702+ healthcheck_retries : healthcheck ? containerDetail . Config . Healthcheck . Retries : 3 ,
703+ healthcheck_action : healthcheck ? healthCheckOnFailureAction : 0 ,
704+ } ) ;
705+ }
706+
638707 render ( ) {
639708 const Dialogs = this . context ;
640709 const { image } = this . props ;
@@ -688,6 +757,7 @@ export class ImageRunModal extends React.Component {
688757
689758 const defaultBody = (
690759 < Form >
760+ { this . state . dialogWarning && < WarningNotification warningMessage = { this . state . dialogWarning } warningDetail = { this . state . dialogWarningDetail } /> }
691761 { this . state . dialogError && < ErrorNotification errorMessage = { this . state . dialogError } errorDetail = { this . state . dialogErrorDetail } /> }
692762 < FormGroup fieldId = 'run-image-dialog-name' label = { _ ( "Name" ) } className = "ct-m-horizontal" >
693763 < TextInput id = 'run-image-dialog-name'
@@ -938,6 +1008,7 @@ export class ImageRunModal extends React.Component {
9381008 actionLabel = { _ ( "Add port mapping" ) }
9391009 onChange = { value => this . onValueChanged ( 'publish' , value ) }
9401010 default = { { IP : null , containerPort : null , hostPort : null , protocol : 'tcp' } }
1011+ prefill = { this . state . publish }
9411012 itemcomponent = { < PublishPort /> } />
9421013
9431014 < DynamicListForm id = 'run-image-dialog-volume'
@@ -948,6 +1019,7 @@ export class ImageRunModal extends React.Component {
9481019 onChange = { value => this . onValueChanged ( 'volumes' , value ) }
9491020 default = { { containerPath : null , hostPath : null , mode : 'rw' } }
9501021 options = { { selinuxAvailable : this . props . selinuxAvailable } }
1022+ prefill = { this . state . volumes }
9511023 itemcomponent = { < Volume /> } />
9521024
9531025 < DynamicListForm id = 'run-image-dialog-env'
@@ -958,6 +1030,7 @@ export class ImageRunModal extends React.Component {
9581030 onChange = { value => this . onValueChanged ( 'env' , value ) }
9591031 default = { { envKey : null , envValue : null } }
9601032 helperText = { _ ( "Paste one or more lines of key=value pairs into any field for bulk import" ) }
1033+ prefill = { this . state . env }
9611034 itemcomponent = { < EnvVar /> } />
9621035 </ Tab >
9631036 < Tab eventKey = { 2 } title = { < TabTitleText > { _ ( "Health check" ) } </ TabTitleText > } id = "create-image-dialog-tab-healthcheck" className = "pf-c-form pf-m-horizontal" >
@@ -1089,6 +1162,31 @@ export class ImageRunModal extends React.Component {
10891162 </ Tabs >
10901163 </ Form >
10911164 ) ;
1165+
1166+ const cardFooter = ( ) => {
1167+ let createRunText = _ ( "Create and run" ) ;
1168+ let createText = _ ( "Create" ) ;
1169+
1170+ if ( this . props . prefill ) {
1171+ createRunText = _ ( "Clone and run" ) ;
1172+ createText = _ ( "Clone" ) ;
1173+ }
1174+
1175+ return (
1176+ < >
1177+ < Button variant = 'primary' id = "create-image-create-run-btn" onClick = { ( ) => this . onCreateClicked ( true ) } isDisabled = { ( ! image && selectedImage === "" ) } >
1178+ { createRunText }
1179+ </ Button >
1180+ < Button variant = 'secondary' id = "create-image-create-btn" onClick = { ( ) => this . onCreateClicked ( false ) } isDisabled = { ( ! image && selectedImage === "" ) } >
1181+ { createText }
1182+ </ Button >
1183+ < Button variant = 'link' className = 'btn-cancel' onClick = { Dialogs . close } >
1184+ { _ ( "Cancel" ) }
1185+ </ Button >
1186+ </ >
1187+ ) ;
1188+ } ;
1189+
10921190 return (
10931191 < Modal isOpen
10941192 position = "top" variant = "medium"
@@ -1102,17 +1200,7 @@ export class ImageRunModal extends React.Component {
11021200 }
11031201 } }
11041202 title = { this . props . pod ? cockpit . format ( _ ( "Create container in $0" ) , this . props . pod . Name ) : _ ( "Create container" ) }
1105- footer = { < >
1106- < Button variant = 'primary' id = "create-image-create-run-btn" onClick = { ( ) => this . onCreateClicked ( true ) } isDisabled = { ! image && selectedImage === "" } >
1107- { _ ( "Create and run" ) }
1108- </ Button >
1109- < Button variant = 'secondary' id = "create-image-create-btn" onClick = { ( ) => this . onCreateClicked ( false ) } isDisabled = { ! image && selectedImage === "" } >
1110- { _ ( "Create" ) }
1111- </ Button >
1112- < Button variant = 'link' className = 'btn-cancel' onClick = { Dialogs . close } >
1113- { _ ( "Cancel" ) }
1114- </ Button >
1115- </ > }
1203+ footer = { cardFooter ( ) }
11161204 >
11171205 { defaultBody }
11181206 </ Modal >
0 commit comments