diff --git a/client/src/api/docker.jsx b/client/src/api/docker.jsx index 7fbfbe8..e0418c9 100644 --- a/client/src/api/docker.jsx +++ b/client/src/api/docker.jsx @@ -268,6 +268,54 @@ function pullImage(imageName, onProgress, ifMissing) { }); } +function updateContainerImage(containerName, onProgress) { + const requestOptions = { + method: 'GET', + headers: { + 'Content-Type': 'application/json' + }, + }; + + const containerNameEncoded = encodeURIComponent(containerName); + + return fetch(`/cosmos/api/servapps/${containerNameEncoded}/manage/update`, 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(); + } + }); + }); +} + function autoUpdate(id, toggle) { return wrap(fetch('/cosmos/api/servapps/' + id + '/auto-update/'+toggle, { method: 'GET', @@ -299,4 +347,5 @@ export { createService, pullImage, autoUpdate, + updateContainerImage, }; \ No newline at end of file diff --git a/client/src/components/logsInModal.jsx b/client/src/components/logsInModal.jsx index f4bfda3..03565a3 100644 --- a/client/src/components/logsInModal.jsx +++ b/client/src/components/logsInModal.jsx @@ -53,7 +53,7 @@ const LogsInModal = ({title, request, OnSuccess, OnError, closeAnytime, initialL if(preRef.current) preRef.current.scrollTop = preRef.current.scrollHeight; - + if (newlog.includes('[OPERATION SUCCEEDED]')) { setDone(true); setOpenModal(false); diff --git a/client/src/pages/servapps/actionBar.jsx b/client/src/pages/servapps/actionBar.jsx index 14e01a9..7ce3bc7 100644 --- a/client/src/pages/servapps/actionBar.jsx +++ b/client/src/pages/servapps/actionBar.jsx @@ -21,9 +21,9 @@ const GetActions = ({ const doTo = (action) => { setIsUpdating(true); - if(action === 'pull') { + if(action === 'update') { setPullRequest(() => ((cb) => { - API.docker.pullImage(image, cb, true) + API.docker.updateContainerImage(Id, cb, true) })); return; } @@ -42,7 +42,7 @@ const GetActions = ({ { t: 'Update Available', if: ['update_available'], - e: {doTo('pull')}} size={isMiniMobile ? 'medium' : 'large'}> + e: {doTo('update')}} size={isMiniMobile ? 'medium' : 'large'}> }, @@ -113,12 +113,11 @@ const GetActions = ({ request={pullRequest} title="Updating ServeApp..." OnSuccess={() => { - doTo('update') + refreshServeApps(); }} /> {!isUpdating && actions.filter((action) => { - updateAvailable = true; return action.if.includes(state) || (updateAvailable && action.if.includes('update_available')); }).map((action) => { return {action.e} diff --git a/package.json b/package.json index 0116444..0f0c4cd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cosmos-server", - "version": "0.5.0-unstable11", + "version": "0.5.0-unstable12", "description": "", "main": "test-server.js", "bugs": { diff --git a/src/docker/api_managecont.go b/src/docker/api_managecont.go index 51438ea..447adfb 100644 --- a/src/docker/api_managecont.go +++ b/src/docker/api_managecont.go @@ -3,8 +3,9 @@ package docker import ( "net/http" "encoding/json" - "io" "os" + "bufio" + "fmt" "github.com/azukaar/cosmos-server/src/utils" @@ -69,14 +70,47 @@ func ManageContainerRoute(w http.ResponseWriter, req *http.Request) { case "recreate": _, err = EditContainer(container.ID, container) case "update": - pull, errPull := DockerClient.ImagePull(DockerContext, imagename, doctype.ImagePullOptions{}) + out, errPull := DockerClient.ImagePull(DockerContext, imagename, doctype.ImagePullOptions{}) if errPull != nil { utils.Error("Docker Pull", errPull) utils.HTTPError(w, "Cannot pull new image", http.StatusBadRequest, "DS004") return } - io.Copy(os.Stdout, pull) + defer out.Close() + + // Enable streaming of response by setting appropriate headers + w.Header().Set("X-Content-Type-Options", "nosniff") + w.Header().Set("Transfer-Encoding", "chunked") + + flusher, ok := w.(http.Flusher) + + if !ok { + utils.Error("Container Update - Cannot stream response", nil) + utils.HTTPError(w, "Cannot stream response", http.StatusInternalServerError, "DS004") + return + } + + // wait for image pull to finish + scanner := bufio.NewScanner(out) + for scanner.Scan() { + utils.Log(scanner.Text()) + fmt.Fprintf(w, scanner.Text() + "\n") + flusher.Flush() + } + + utils.Log("Container Update - Image pulled " + imagename) + _, err = EditContainer(container.ID, container) + + if err != nil { + utils.Error("Container Update - EditContainer", err) + utils.HTTPError(w, "[OPERATION FAILED] Cannot recreate container", http.StatusBadRequest, "DS004") + return + } + + fmt.Fprintf(w, "[OPERATION SUCCEEDED]") + flusher.Flush() + return default: utils.HTTPError(w, "Invalid action", http.StatusBadRequest, "DS003") return