123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327 |
- <script setup lang="ts">
- import type { Ref } from 'vue'
- import type { ReleaseInfo } from '@/api/upgrade'
- import dayjs from 'dayjs'
- import { marked } from 'marked'
- import upgrade from '@/api/upgrade'
- import websocket from '@/lib/websocket'
- import version from '@/version.json'
- const route = useRoute()
- const data = ref<ReleaseInfo>({} as ReleaseInfo)
- const lastCheck = ref('')
- const loading = ref(false)
- const channel = ref('stable')
- const progressStrokeColor = {
- from: '#108ee9',
- to: '#87d068',
- }
- const modalVisible = ref(false)
- const progressPercent = ref(0)
- const progressStatus = ref('active') as Ref<'normal' | 'active' | 'success' | 'exception'>
- const modalClosable = ref(false)
- const getReleaseError = ref(false)
- const progressPercentComputed = computed(() => {
- return Number.parseFloat(progressPercent.value.toFixed(1))
- })
- function getLatestRelease() {
- loading.value = true
- data.value.body = ''
- upgrade.get_latest_release(channel.value).then(r => {
- data.value = r
- lastCheck.value = dayjs().format('YYYY-MM-DD HH:mm:ss')
- }).catch(e => {
- getReleaseError.value = e?.message
- }).finally(() => {
- loading.value = false
- })
- }
- getLatestRelease()
- watch(channel, getLatestRelease)
- const isLatestVer = computed(() => {
- return data.value.name === `v${version.version}`
- })
- const logContainer = useTemplateRef('logContainer')
- function log(msg: string) {
- const para = document.createElement('p')
- para.appendChild(document.createTextNode($gettext(msg)))
- logContainer.value!.appendChild(para)
- logContainer.value!.scroll({ top: 320, left: 0, behavior: 'smooth' })
- }
- const dryRun = computed(() => {
- return !!route.query.dry_run
- })
- const testCommitAndRestart = computed(() => {
- return !!route.query.test_commit_and_restart
- })
- async function performUpgrade() {
- progressStatus.value = 'active'
- modalClosable.value = false
- modalVisible.value = true
- progressPercent.value = 0
- logContainer.value!.innerHTML = ''
- log($gettext('Upgrading Nginx UI, please wait...'))
- const ws = websocket('/api/upgrade/perform', false)
- let last = 0
- ws.onopen = () => {
- ws.send(JSON.stringify({
- dry_run: dryRun.value,
- channel: channel.value,
- test_commit_and_restart: testCommitAndRestart.value,
- }))
- }
- let isFailed = false
- ws.onmessage = async m => {
- const r = JSON.parse(m.data)
- if (r.message)
- log(r.message)
- 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
- isFailed = true
- break
- default:
- modalClosable.value = true
- break
- }
- }
- ws.onerror = () => {
- isFailed = true
- progressStatus.value = 'exception'
- modalClosable.value = true
- }
- ws.onclose = async () => {
- if (isFailed)
- return
- const t = setInterval(() => {
- const interval = data.value.in_docker ? 10000 : 1000
- upgrade.current_version().then(() => {
- clearInterval(t)
- setTimeout(() => {
- progressStatus.value = 'success'
- progressPercent.value = 100
- modalClosable.value = true
- log('Upgraded successfully')
- setTimeout(() => {
- location.reload()
- }, 1000)
- }, interval)
- })
- }, 5000)
- }
- }
- const performUpgradeBtnText = computed(() => {
- if (channel.value === 'dev')
- return $gettext('Install')
- else if (isLatestVer.value)
- return $gettext('Reinstall')
- else
- return $gettext('Upgrade')
- })
- </script>
- <template>
- <ACard :title="$gettext('Upgrade')">
- <AModal
- v-model:open="modalVisible"
- :title="$gettext('Core Upgrade')"
- :mask-closable="false"
- :footer="null"
- :closable="modalClosable"
- force-render
- >
- <AProgress
- :stroke-color="progressStrokeColor"
- :percent="progressPercentComputed"
- :status="progressStatus"
- />
- <div
- ref="logContainer"
- class="core-upgrade-log-container"
- />
- </AModal>
- <div class="upgrade-container">
- <p>{{ $gettext('You can check Nginx UI upgrade at this page.') }}</p>
- <h3>
- {{ $gettext('Current Version') }}: v{{ version.version }}
- <span v-if="data?.cur_version?.short_hash" class="short-hash">({{ data?.cur_version?.short_hash }})</span>
- </h3>
- <template v-if="getReleaseError">
- <AAlert
- type="error"
- :title="$gettext('Get release information error')"
- :message="getReleaseError"
- 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') }}: {{ lastCheck }}
- <AButton
- type="link"
- :loading="loading"
- @click="getLatestRelease"
- >
- {{ $gettext('Check again') }}
- </AButton>
- </p>
- <AFormItem :label="$gettext('Channel')">
- <ASelect v-model:value="channel">
- <ASelectOption key="stable">
- {{ $gettext('Stable') }}
- </ASelectOption>
- <ASelectOption key="prerelease">
- {{ $gettext('Pre-release') }}
- </ASelectOption>
- <ASelectOption key="dev">
- {{ $gettext('Dev') }}
- </ASelectOption>
- </ASelect>
- </AFormItem>
- <template v-if="!loading">
- <AAlert
- v-if="isLatestVer && channel !== 'dev'"
- type="success"
- :message="$gettext('You are using the latest version')"
- banner
- />
- <AAlert
- v-else
- type="info"
- :message="$gettext('New version released')"
- banner
- />
- <template v-if="dryRun">
- <br>
- <AAlert
- type="info"
- :message="$gettext('Dry run mode enabled')"
- banner
- />
- </template>
- <div class="control-btn">
- <ASpace>
- <AButton
- type="primary"
- ghost
- @click="performUpgrade"
- >
- {{ performUpgradeBtnText }}
- </AButton>
- </ASpace>
- </div>
- </template>
- </template>
- <template v-if="data.body">
- <h2 class="latest-version">
- {{ data.name }}
- <ATag
- v-if="channel === 'stable'"
- color="green"
- >
- {{ $gettext('Stable') }}
- </ATag>
- <ATag
- v-if="channel === 'prerelease'"
- color="blue"
- >
- {{ $gettext('Pre-release') }}
- </ATag>
- </h2>
- <h3>{{ $gettext('Release Note') }}</h3>
- <div v-dompurify-html="marked.parse(data.body)" />
- <a
- v-if="data.html_url"
- :href="data.html_url"
- target="_blank"
- >
- {{ $gettext('View on GitHub') }}
- </a>
- </template>
- </div>
- </ACard>
- </template>
- <style lang="less">
- .dark {
- .core-upgrade-log-container {
- background-color: rgba(0, 0, 0, 0.84);
- }
- }
- .core-upgrade-log-container {
- height: 320px;
- overflow: scroll;
- background-color: #f3f3f3;
- border-radius: 4px;
- margin-top: 15px;
- padding: 10px;
- p {
- font-size: 12px;
- line-height: 1.3;
- }
- }
- </style>
- <style lang="less" scoped>
- .upgrade-container {
- width: 100%;
- max-width: 600px;
- margin: 0 auto;
- padding: 0 10px;
- }
- .control-btn {
- margin: 15px 0;
- }
- .latest-version {
- display: flex;
- align-items: center;
- span.ant-tag {
- margin-left: 10px;
- }
- }
- </style>
|