Upgrade.vue 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327
  1. <script setup lang="ts">
  2. import type { Ref } from 'vue'
  3. import type { ReleaseInfo } from '@/api/upgrade'
  4. import dayjs from 'dayjs'
  5. import { marked } from 'marked'
  6. import upgrade from '@/api/upgrade'
  7. import websocket from '@/lib/websocket'
  8. import version from '@/version.json'
  9. const route = useRoute()
  10. const data = ref<ReleaseInfo>({} as ReleaseInfo)
  11. const lastCheck = ref('')
  12. const loading = ref(false)
  13. const channel = ref('stable')
  14. const progressStrokeColor = {
  15. from: '#108ee9',
  16. to: '#87d068',
  17. }
  18. const modalVisible = ref(false)
  19. const progressPercent = ref(0)
  20. const progressStatus = ref('active') as Ref<'normal' | 'active' | 'success' | 'exception'>
  21. const modalClosable = ref(false)
  22. const getReleaseError = ref(false)
  23. const progressPercentComputed = computed(() => {
  24. return Number.parseFloat(progressPercent.value.toFixed(1))
  25. })
  26. function getLatestRelease() {
  27. loading.value = true
  28. data.value.body = ''
  29. upgrade.get_latest_release(channel.value).then(r => {
  30. data.value = r
  31. lastCheck.value = dayjs().format('YYYY-MM-DD HH:mm:ss')
  32. }).catch(e => {
  33. getReleaseError.value = e?.message
  34. }).finally(() => {
  35. loading.value = false
  36. })
  37. }
  38. getLatestRelease()
  39. watch(channel, getLatestRelease)
  40. const isLatestVer = computed(() => {
  41. return data.value.name === `v${version.version}`
  42. })
  43. const logContainer = useTemplateRef('logContainer')
  44. function log(msg: string) {
  45. const para = document.createElement('p')
  46. para.appendChild(document.createTextNode($gettext(msg)))
  47. logContainer.value!.appendChild(para)
  48. logContainer.value!.scroll({ top: 320, left: 0, behavior: 'smooth' })
  49. }
  50. const dryRun = computed(() => {
  51. return !!route.query.dry_run
  52. })
  53. const testCommitAndRestart = computed(() => {
  54. return !!route.query.test_commit_and_restart
  55. })
  56. async function performUpgrade() {
  57. progressStatus.value = 'active'
  58. modalClosable.value = false
  59. modalVisible.value = true
  60. progressPercent.value = 0
  61. logContainer.value!.innerHTML = ''
  62. log($gettext('Upgrading Nginx UI, please wait...'))
  63. const ws = websocket('/api/upgrade/perform', false)
  64. let last = 0
  65. ws.onopen = () => {
  66. ws.send(JSON.stringify({
  67. dry_run: dryRun.value,
  68. channel: channel.value,
  69. test_commit_and_restart: testCommitAndRestart.value,
  70. }))
  71. }
  72. let isFailed = false
  73. ws.onmessage = async m => {
  74. const r = JSON.parse(m.data)
  75. if (r.message)
  76. log(r.message)
  77. switch (r.status) {
  78. case 'info':
  79. progressPercent.value += 10
  80. break
  81. case 'progress':
  82. progressPercent.value += (r.progress - last) / 2
  83. last = r.progress
  84. break
  85. case 'error':
  86. progressStatus.value = 'exception'
  87. modalClosable.value = true
  88. isFailed = true
  89. break
  90. default:
  91. modalClosable.value = true
  92. break
  93. }
  94. }
  95. ws.onerror = () => {
  96. isFailed = true
  97. progressStatus.value = 'exception'
  98. modalClosable.value = true
  99. }
  100. ws.onclose = async () => {
  101. if (isFailed)
  102. return
  103. const t = setInterval(() => {
  104. const interval = data.value.in_docker ? 10000 : 1000
  105. upgrade.current_version().then(() => {
  106. clearInterval(t)
  107. setTimeout(() => {
  108. progressStatus.value = 'success'
  109. progressPercent.value = 100
  110. modalClosable.value = true
  111. log('Upgraded successfully')
  112. setTimeout(() => {
  113. location.reload()
  114. }, 1000)
  115. }, interval)
  116. })
  117. }, 5000)
  118. }
  119. }
  120. const performUpgradeBtnText = computed(() => {
  121. if (channel.value === 'dev')
  122. return $gettext('Install')
  123. else if (isLatestVer.value)
  124. return $gettext('Reinstall')
  125. else
  126. return $gettext('Upgrade')
  127. })
  128. </script>
  129. <template>
  130. <ACard :title="$gettext('Upgrade')">
  131. <AModal
  132. v-model:open="modalVisible"
  133. :title="$gettext('Core Upgrade')"
  134. :mask-closable="false"
  135. :footer="null"
  136. :closable="modalClosable"
  137. force-render
  138. >
  139. <AProgress
  140. :stroke-color="progressStrokeColor"
  141. :percent="progressPercentComputed"
  142. :status="progressStatus"
  143. />
  144. <div
  145. ref="logContainer"
  146. class="core-upgrade-log-container"
  147. />
  148. </AModal>
  149. <div class="upgrade-container">
  150. <p>{{ $gettext('You can check Nginx UI upgrade at this page.') }}</p>
  151. <h3>
  152. {{ $gettext('Current Version') }}: v{{ version.version }}
  153. <span v-if="data?.cur_version?.short_hash" class="short-hash">({{ data?.cur_version?.short_hash }})</span>
  154. </h3>
  155. <template v-if="getReleaseError">
  156. <AAlert
  157. type="error"
  158. :title="$gettext('Get release information error')"
  159. :message="getReleaseError"
  160. banner
  161. />
  162. </template>
  163. <template v-else>
  164. <p>{{ $gettext('OS') }}: {{ data.os }}</p>
  165. <p>{{ $gettext('Arch') }}: {{ data.arch }}</p>
  166. <p>{{ $gettext('Executable Path') }}: {{ data.ex_path }}</p>
  167. <p>
  168. {{ $gettext('Last checked at') }}: {{ lastCheck }}
  169. <AButton
  170. type="link"
  171. :loading="loading"
  172. @click="getLatestRelease"
  173. >
  174. {{ $gettext('Check again') }}
  175. </AButton>
  176. </p>
  177. <AFormItem :label="$gettext('Channel')">
  178. <ASelect v-model:value="channel">
  179. <ASelectOption key="stable">
  180. {{ $gettext('Stable') }}
  181. </ASelectOption>
  182. <ASelectOption key="prerelease">
  183. {{ $gettext('Pre-release') }}
  184. </ASelectOption>
  185. <ASelectOption key="dev">
  186. {{ $gettext('Dev') }}
  187. </ASelectOption>
  188. </ASelect>
  189. </AFormItem>
  190. <template v-if="!loading">
  191. <AAlert
  192. v-if="isLatestVer && channel !== 'dev'"
  193. type="success"
  194. :message="$gettext('You are using the latest version')"
  195. banner
  196. />
  197. <AAlert
  198. v-else
  199. type="info"
  200. :message="$gettext('New version released')"
  201. banner
  202. />
  203. <template v-if="dryRun">
  204. <br>
  205. <AAlert
  206. type="info"
  207. :message="$gettext('Dry run mode enabled')"
  208. banner
  209. />
  210. </template>
  211. <div class="control-btn">
  212. <ASpace>
  213. <AButton
  214. type="primary"
  215. ghost
  216. @click="performUpgrade"
  217. >
  218. {{ performUpgradeBtnText }}
  219. </AButton>
  220. </ASpace>
  221. </div>
  222. </template>
  223. </template>
  224. <template v-if="data.body">
  225. <h2 class="latest-version">
  226. {{ data.name }}
  227. <ATag
  228. v-if="channel === 'stable'"
  229. color="green"
  230. >
  231. {{ $gettext('Stable') }}
  232. </ATag>
  233. <ATag
  234. v-if="channel === 'prerelease'"
  235. color="blue"
  236. >
  237. {{ $gettext('Pre-release') }}
  238. </ATag>
  239. </h2>
  240. <h3>{{ $gettext('Release Note') }}</h3>
  241. <div v-dompurify-html="marked.parse(data.body)" />
  242. <a
  243. v-if="data.html_url"
  244. :href="data.html_url"
  245. target="_blank"
  246. >
  247. {{ $gettext('View on GitHub') }}
  248. </a>
  249. </template>
  250. </div>
  251. </ACard>
  252. </template>
  253. <style lang="less">
  254. .dark {
  255. .core-upgrade-log-container {
  256. background-color: rgba(0, 0, 0, 0.84);
  257. }
  258. }
  259. .core-upgrade-log-container {
  260. height: 320px;
  261. overflow: scroll;
  262. background-color: #f3f3f3;
  263. border-radius: 4px;
  264. margin-top: 15px;
  265. padding: 10px;
  266. p {
  267. font-size: 12px;
  268. line-height: 1.3;
  269. }
  270. }
  271. </style>
  272. <style lang="less" scoped>
  273. .upgrade-container {
  274. width: 100%;
  275. max-width: 600px;
  276. margin: 0 auto;
  277. padding: 0 10px;
  278. }
  279. .control-btn {
  280. margin: 15px 0;
  281. }
  282. .latest-version {
  283. display: flex;
  284. align-items: center;
  285. span.ant-tag {
  286. margin-left: 10px;
  287. }
  288. }
  289. </style>