Browse Source

enhance: display downloading progress in upgrader

0xJacky 2 years ago
parent
commit
710325447a
7 changed files with 146 additions and 58 deletions
  1. 1 1
      frontend/src/version.json
  2. 56 32
      frontend/src/views/system/Upgrade.vue
  3. 1 1
      frontend/version.json
  4. 1 0
      go.mod
  5. 2 0
      go.sum
  6. 12 4
      server/api/upgrade.go
  7. 73 20
      server/service/upgrade.go

+ 1 - 1
frontend/src/version.json

@@ -1 +1 @@
-{"version":"1.7.9","build_id":88,"total_build":158}
+{"version":"1.7.9","build_id":92,"total_build":162}

+ 56 - 32
frontend/src/views/system/Upgrade.vue

@@ -7,6 +7,7 @@ import dayjs from 'dayjs'
 import {marked} from 'marked'
 import Template from '@/views/template/Template.vue'
 import websocket from '@/lib/websocket'
+import {message} from 'ant-design-vue'
 
 const {$gettext} = useGettext()
 
@@ -20,15 +21,23 @@ const progressStrokeColor = {
 }
 
 const modalVisible = ref(false)
-const progressPercent = ref(0)
+const progressPercent: any = ref(0)
 const progressStatus = ref('active')
 const modalClosable = ref(false)
+const get_release_error = ref(false)
 
-function get_release_list() {
+const progressPercentComputed = computed(() => {
+    return parseFloat(progressPercent.value.toFixed(1))
+})
+
+function get_latest_release() {
     loading.value = true
     upgrade.get_latest_release().then(r => {
         data.value = r
         last_check.value = dayjs().format('YYYY-MM-DD HH:mm:ss')
+    }).catch(e => {
+        get_release_error.value = e?.message
+        message.error(e?.message ?? $gettext('Server error'))
     }).finally(() => {
         setTimeout(() => {
             loading.value = false
@@ -36,7 +45,7 @@ function get_release_list() {
     })
 }
 
-get_release_list()
+get_latest_release()
 
 const is_latest_ver = computed(() => {
     return data.value.name === `v${version.version}`
@@ -64,14 +73,20 @@ async function perform_upgrade() {
 
     const ws = websocket('/api/upgrade/perform', false)
 
-    ws.onmessage = m => {
-        const r = JSON.parse(m.data)
-        log(r.message)
+    let last = 0
 
+    ws.onmessage = async m => {
+        const r = JSON.parse(m.data)
+        if (r.message) log(r.message)
+        console.log(r.status)
         switch (r.status) {
             case 'info':
                 progressPercent.value += 10
                 break
+            case 'progress':
+                progressPercent.value += (r.progress - last) / 2
+                last = r.progress
+                break
             case 'error':
                 progressStatus.value = 'exception'
                 modalClosable.value = true
@@ -109,7 +124,7 @@ async function perform_upgrade() {
             :footer="null" :closable="modalClosable" force-render>
             <a-progress
                 :stroke-color="progressStrokeColor"
-                :percent="progressPercent"
+                :percent="progressPercentComputed"
                 :status="progressStatus"
             />
 
@@ -118,32 +133,41 @@ async function perform_upgrade() {
         <div class="upgrade-container">
             <p>{{ $gettext('You can check Nginx UI upgrade at this page.') }}</p>
             <h3>{{ $gettext('Current Version') }}: v{{ version.version }}</h3>
-            <p>{{ $gettext('OS') }}: {{ data.os }}</p>
-            <p>{{ $gettext('Arch') }}: {{ data.arch }}</p>
-            <p>{{ $gettext('Executable Path') }}: {{ data.ex_path }}</p>
-            <p>{{ $gettext('Last checked at') }}: {{ last_check }}
-                <a-button type="link" @click="get_release_list" :loading="loading">
-                    {{ $gettext('Check again') }}
-                </a-button>
-            </p>
-            <a-alert type="success" v-if="is_latest_ver"
-                     :message="$gettext('You are using the latest version')"
-                     banner
-            />
-            <a-alert type="info" v-else
-                     :message="$gettext('New version released')"
-                     banner
-            />
-            <div class="control-btn">
-                <a-space>
-                    <a-button type="primary" @click="perform_upgrade"
-                              ghost v-if="is_latest_ver">{{ $gettext('Reinstall') }}
-                    </a-button>
-                    <a-button type="primary" @click="perform_upgrade"
-                              ghost v-else>{{ $gettext('Upgrade') }}
+            <template v-if="get_release_error">
+                <a-alert type="error"
+                         :title="$gettext('Get release information error')"
+                         :message="get_release_error"
+                         banner
+                />
+            </template>
+            <template v-else>
+                <p>{{ $gettext('OS') }}: {{ data.os }}</p>
+                <p>{{ $gettext('Arch') }}: {{ data.arch }}</p>
+                <p>{{ $gettext('Executable Path') }}: {{ data.ex_path }}</p>
+                <p>{{ $gettext('Last checked at') }}: {{ last_check }}
+                    <a-button type="link" @click="get_latest_release" :loading="loading">
+                        {{ $gettext('Check again') }}
                     </a-button>
-                </a-space>
-            </div>
+                </p>
+                <a-alert type="success" v-if="is_latest_ver"
+                         :message="$gettext('You are using the latest version')"
+                         banner
+                />
+                <a-alert type="info" v-else
+                         :message="$gettext('New version released')"
+                         banner
+                />
+                <div class="control-btn">
+                    <a-space>
+                        <a-button type="primary" @click="perform_upgrade"
+                                  ghost v-if="is_latest_ver">{{ $gettext('Reinstall') }}
+                        </a-button>
+                        <a-button type="primary" @click="perform_upgrade"
+                                  ghost v-else>{{ $gettext('Upgrade') }}
+                        </a-button>
+                    </a-space>
+                </div>
+            </template>
             <template v-if="data.body">
                 <h2>{{ $gettext('Release Note') }}</h2>
                 <div v-html="marked.parse(data.body)"></div>

+ 1 - 1
frontend/version.json

@@ -1 +1 @@
-{"version":"1.7.9","build_id":88,"total_build":158}
+{"version":"1.7.9","build_id":92,"total_build":162}

+ 1 - 0
go.mod

@@ -36,6 +36,7 @@ require (
 	github.com/BurntSushi/toml v1.2.1 // indirect
 	github.com/StackExchange/wmi v1.2.1 // indirect
 	github.com/bytedance/sonic v1.8.7 // indirect
+	github.com/cavaliergopher/grab/v3 v3.0.1 // indirect
 	github.com/cenkalti/backoff/v4 v4.2.0 // indirect
 	github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
 	github.com/gin-contrib/sse v0.1.0 // indirect

+ 2 - 0
go.sum

@@ -6,6 +6,8 @@ github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9
 github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
 github.com/bytedance/sonic v1.8.7 h1:d3sry5vGgVq/OpgozRUNP6xBsSo0mtNdwliApw+SAMQ=
 github.com/bytedance/sonic v1.8.7/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
+github.com/cavaliergopher/grab/v3 v3.0.1 h1:4z7TkBfmPjmLAAmkkAZNX/6QJ1nNFdv3SdIHXju0Fr4=
+github.com/cavaliergopher/grab/v3 v3.0.1/go.mod h1:1U/KNnD+Ft6JJiYoYBAimKH2XrYptb8Kl3DFGmsjpq4=
 github.com/cenkalti/backoff/v4 v4.2.0 h1:HN5dHm3WBOgndBH6E8V0q2jIYIR3s9yglV8k/+MN3u4=
 github.com/cenkalti/backoff/v4 v4.2.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
 github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=

+ 12 - 4
server/api/upgrade.go

@@ -7,7 +7,6 @@ import (
 	"log"
 	"net/http"
 	"os"
-	"path/filepath"
 )
 
 func GetRelease(c *gin.Context) {
@@ -67,7 +66,16 @@ func PerformCoreUpgrade(c *gin.Context) {
 		"status":  "info",
 		"message": "Downloading latest release",
 	})
-	tarName, err := u.DownloadLatestRelease()
+	progressChan := make(chan float64)
+	go func() {
+		for progress := range progressChan {
+			_ = ws.WriteJSON(gin.H{
+				"status":   "progress",
+				"progress": progress,
+			})
+		}
+	}()
+	tarName, err := u.DownloadLatestRelease(progressChan)
 	if err != nil {
 		_ = ws.WriteJSON(gin.H{
 			"status":  "error",
@@ -86,7 +94,7 @@ func PerformCoreUpgrade(c *gin.Context) {
 	})
 	_ = os.Remove(u.Release.ExPath)
 	// bye, overseer will restart nginx-ui
-	err = u.PerformCoreUpgrade(filepath.Dir(u.Release.ExPath), tarName)
+	err = u.PerformCoreUpgrade(u.Release.ExPath, tarName)
 	if err != nil {
 		_ = ws.WriteJSON(gin.H{
 			"status":  "error",
@@ -96,7 +104,7 @@ func PerformCoreUpgrade(c *gin.Context) {
 			"status":  "error",
 			"message": err.Error(),
 		})
-		log.Println("[Error] PerformCoreUpgrade PerformCoreUpgrade", err)
+		log.Println("[Error] PerformCoreUpgrade", err)
 		return
 	}
 }

+ 73 - 20
server/service/upgrade.go

@@ -9,11 +9,13 @@ import (
 	"github.com/0xJacky/Nginx-UI/server/settings"
 	"github.com/pkg/errors"
 	"io"
+	"log"
 	"net/http"
 	"net/url"
 	"os"
 	"path/filepath"
 	"runtime"
+	"strconv"
 	"time"
 )
 
@@ -72,6 +74,12 @@ func GetRelease() (data TRelease, err error) {
 		err = errors.Wrap(err, "service.GetReleaseList io.ReadAll err")
 		return
 	}
+	defer resp.Body.Close()
+	if resp.StatusCode != 200 {
+		err = errors.New(string(body))
+		log.Println(string(body))
+		return
+	}
 	err = json.Unmarshal(body, &data)
 	if err != nil {
 		err = errors.Wrap(err, "service.GetReleaseList json.Unmarshal err")
@@ -118,7 +126,56 @@ func NewUpgrader() (u *Upgrader, err error) {
 	return
 }
 
-func (u *Upgrader) DownloadLatestRelease() (tarName string, err error) {
+type ProgressWriter struct {
+	io.Writer
+	totalSize    int64
+	currentSize  int64
+	progressChan chan<- float64
+}
+
+func (pw *ProgressWriter) Write(p []byte) (int, error) {
+	n, err := pw.Writer.Write(p)
+	pw.currentSize += int64(n)
+	progress := float64(pw.currentSize) / float64(pw.totalSize) * 100
+	pw.progressChan <- progress
+	return n, err
+}
+
+func downloadRelease(url string, dir string, progressChan chan float64) (tarName string, err error) {
+	client := &http.Client{}
+	req, err := http.NewRequest("GET", url, nil)
+	if err != nil {
+		return
+	}
+	resp, err := client.Do(req)
+	if err != nil {
+		return
+	}
+	defer resp.Body.Close()
+
+	totalSize, err := strconv.ParseInt(resp.Header.Get("Content-Length"), 10, 64)
+	if err != nil {
+		return
+	}
+
+	file, err := os.CreateTemp(dir, "nginx-ui-temp-*.tar.gz")
+	if err != nil {
+		err = errors.Wrap(err, "service.DownloadLatestRelease CreateTemp error")
+		return
+	}
+	defer file.Close()
+
+	progressWriter := &ProgressWriter{Writer: file, totalSize: totalSize, progressChan: progressChan}
+	multiWriter := io.MultiWriter(progressWriter)
+
+	_, err = io.Copy(multiWriter, resp.Body)
+	close(progressChan)
+
+	tarName = file.Name()
+	return
+}
+
+func (u *Upgrader) DownloadLatestRelease(progressChan chan float64) (tarName string, err error) {
 	bytes, err := _github.DistFS.ReadFile("build/build_info.json")
 	if err != nil {
 		err = errors.Wrap(err, "service.DownloadLatestRelease Read build_info.json error")
@@ -156,12 +213,7 @@ func (u *Upgrader) DownloadLatestRelease() (tarName string, err error) {
 	}
 
 	dir := filepath.Dir(u.Release.ExPath)
-	file, err := os.CreateTemp(dir, "nginx-ui-temp-*.tar.gz")
-	if err != nil {
-		err = errors.Wrap(err, "service.DownloadLatestRelease CreateTemp error")
-		return
-	}
-	defer file.Close()
+
 	if settings.ServerSettings.GithubProxy != "" {
 		downloadUrl, err = url.JoinPath(settings.ServerSettings.GithubProxy, downloadUrl)
 		if err != nil {
@@ -169,30 +221,31 @@ func (u *Upgrader) DownloadLatestRelease() (tarName string, err error) {
 			return
 		}
 	}
-	client := &http.Client{}
-	resp, err := client.Get(downloadUrl)
+	tarName, err = downloadRelease(downloadUrl, dir, progressChan)
 	if err != nil {
-		err = errors.Wrap(err, "service.DownloadLatestRelease client.Get() error")
+		err = errors.Wrap(err, "service.DownloadLatestRelease downloadFile error")
 		return
 	}
-	defer resp.Body.Close()
-	_, err = io.Copy(file, resp.Body)
-	if err != nil {
-		err = errors.Wrap(err, "service.DownloadLatestRelease io.Copy error")
-		return
-	}
-	tarName = file.Name()
 	return
 }
 
-func (u *Upgrader) PerformCoreUpgrade(dir, tarPath string) (err error) {
+func (u *Upgrader) PerformCoreUpgrade(exPath string, tarPath string) (err error) {
+	dir := filepath.Dir(exPath)
 	err = helper.UnTar(dir, tarPath)
 	if err != nil {
 		err = errors.Wrap(err, "PerformCoreUpgrade unTar error")
 		return
 	}
+	err = os.Rename(filepath.Join(dir, "nginx-ui"), exPath)
+	if err != nil {
+		err = errors.Wrap(err, "PerformCoreUpgrade rename error")
+		return
+	}
 
-	_ = os.Remove(tarPath)
-
+	err = os.Remove(tarPath)
+	if err != nil {
+		err = errors.Wrap(err, "PerformCoreUpgrade remove tar error")
+		return
+	}
 	return
 }