diff --git a/client/src/api/docker.jsx b/client/src/api/docker.jsx index 6661e1b..73adb85 100644 --- a/client/src/api/docker.jsx +++ b/client/src/api/docker.jsx @@ -220,6 +220,54 @@ function createService(serviceData, onProgress) { }); } +function pullImage(imageName, onProgress, ifMissing) { + const requestOptions = { + method: 'GET', + headers: { + 'Content-Type': 'application/json' + }, + }; + + const imageNameEncoded = encodeURIComponent(imageName); + + return fetch(`/cosmos/api/images/${ifMissing ? 'pull-if-missing' : 'pull'}?imageName=${imageNameEncoded}`, requestOptions) + .then(response => { + if (!response.ok) { + throw new Error(response.statusText); + } + + // The response body is a ReadableStream. This code reads the stream and passes chunks to the callback. + const reader = response.body.getReader(); + + // Read the stream and pass chunks to the callback as they arrive + return new ReadableStream({ + start(controller) { + function read() { + return reader.read().then(({ done, value }) => { + if (done) { + controller.close(); + return; + } + // Decode the UTF-8 text + let text = new TextDecoder().decode(value); + // Split by lines in case there are multiple lines in one chunk + let lines = text.split('\n'); + for (let line of lines) { + if (line) { + // Call the progress callback + onProgress(line); + } + } + controller.enqueue(value); + return read(); + }); + } + return read(); + } + }); + }); +} + export { list, get, @@ -240,4 +288,5 @@ export { attachTerminal, createTerminal, createService, + pullImage, }; \ No newline at end of file diff --git a/client/src/components/logsInModal.jsx b/client/src/components/logsInModal.jsx new file mode 100644 index 0000000..f4bfda3 --- /dev/null +++ b/client/src/components/logsInModal.jsx @@ -0,0 +1,88 @@ +// material-ui +import * as React from 'react'; +import { Alert, Button, Stack, Typography } from '@mui/material'; +import { WarningOutlined, PlusCircleOutlined, CopyOutlined, ExclamationCircleOutlined , SyncOutlined, UserOutlined, KeyOutlined, ArrowUpOutlined, FileZipOutlined } from '@ant-design/icons'; +import Dialog from '@mui/material/Dialog'; +import DialogActions from '@mui/material/DialogActions'; +import DialogContent from '@mui/material/DialogContent'; +import DialogContentText from '@mui/material/DialogContentText'; +import DialogTitle from '@mui/material/DialogTitle'; +import { useEffect, useState } from 'react'; +import { smartDockerLogConcat, tryParseProgressLog } from '../utils/docker'; + +const preStyle = { + backgroundColor: '#000', + color: '#fff', + padding: '10px', + borderRadius: '5px', + overflow: 'auto', + maxHeight: '500px', + maxWidth: '100%', + width: '100%', + margin: '0', + position: 'relative', + fontSize: '12px', + fontFamily: 'monospace', + whiteSpace: 'pre-wrap', + wordWrap: 'break-word', + wordBreak: 'break-all', + lineHeight: '1.5', + boxShadow: '0 0 10px rgba(0,0,0,0.5)', + border: '1px solid rgba(255,255,255,0.1)', + boxSizing: 'border-box', + marginBottom: '10px', + marginTop: '10px', + marginLeft: '0', + marginRight: '0', +} + +const LogsInModal = ({title, request, OnSuccess, OnError, closeAnytime, initialLogs = [], }) => { + const [openModal, setOpenModal] = useState(false); + const [logs, setLogs] = useState(initialLogs); + const [done, setDone] = useState(closeAnytime); + const preRef = React.useRef(null); + + useEffect(() => { + setLogs(initialLogs); + setDone(closeAnytime); + + if(request === null) return; + + request((newlog) => { + setLogs((old) => smartDockerLogConcat(old, newlog)); + + if(preRef.current) + preRef.current.scrollTop = preRef.current.scrollHeight; + + if (newlog.includes('[OPERATION SUCCEEDED]')) { + setDone(true); + setOpenModal(false); + OnSuccess && OnSuccess(); + } else if (newlog.includes('[OPERATION FAILED]')) { + setDone(true); + OnError && OnError(newlog); + } else { + setOpenModal(true); + } + }); + }, [request]); + + return <> + + >; +}; + +export default LogsInModal; diff --git a/client/src/pages/servapps/containers/setup.jsx b/client/src/pages/servapps/containers/setup.jsx index a795961..336d2d7 100644 --- a/client/src/pages/servapps/containers/setup.jsx +++ b/client/src/pages/servapps/containers/setup.jsx @@ -7,6 +7,7 @@ import { CosmosCheckbox, CosmosFormDivider, CosmosInputText, CosmosSelect } import { DeleteOutlined, PlusCircleOutlined } from '@ant-design/icons'; import * as API from '../../../api'; import { LoadingButton } from '@mui/lab'; +import LogsInModal from '../../../components/logsInModal'; const containerInfoFrom = (values) => { const labels = {}; @@ -33,9 +34,11 @@ const DockerContainerSetup = ({config, containerInfo, OnChange, refresh, newCont ['on-failure', 'Restart On Failure'], ['unless-stopped', 'Restart Unless Stopped'], ]; + const [pullRequest, setPullRequest] = React.useState(null); const theme = useTheme(); const isMobile = useMediaQuery(theme.breakpoints.down('sm')); const padding = isMobile ? '6px 4px' : '12px 10px'; + const [latestImage, setLatestImage] = React.useState(containerInfo.Config.Image); return (