mirror of
https://github.com/nextcloud/server.git
synced 2026-06-08 16:26:59 -04:00
refactor(core): migrate web updater to Vue
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
This commit is contained in:
parent
2a385fd5b8
commit
f7dad729e4
10 changed files with 452 additions and 277 deletions
|
|
@ -29,6 +29,7 @@ module.exports = {
|
|||
public: path.join(__dirname, 'core/src', 'public.ts'),
|
||||
public_share_auth: path.join(__dirname, 'core/src', 'public-share-auth.ts'),
|
||||
'twofactor-request-token': path.join(__dirname, 'core/src', 'twofactor-request-token.ts'),
|
||||
update: path.join(__dirname, 'core/src', 'update.ts'),
|
||||
},
|
||||
dashboard: {
|
||||
main: path.join(__dirname, 'apps/dashboard/src', 'main.js'),
|
||||
|
|
|
|||
|
|
@ -106,7 +106,6 @@ if (Util::needUpgrade()) {
|
|||
});
|
||||
$updater->listen('\OC\Updater', 'failure', function ($message) use ($eventSource, $config): void {
|
||||
$eventSource->send('failure', $message);
|
||||
$eventSource->close();
|
||||
$config->setSystemValue('maintenance', false);
|
||||
});
|
||||
$updater->listen('\OC\Updater', 'setDebugLogLevel', function ($logLevel, $logLevelName) use ($eventSource, $l): void {
|
||||
|
|
|
|||
|
|
@ -1,160 +0,0 @@
|
|||
/**
|
||||
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-FileCopyrightText: 2014 ownCloud Inc.
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
(function() {
|
||||
OC.Update = {
|
||||
_started: false,
|
||||
options: {},
|
||||
|
||||
/**
|
||||
* Start the update process.
|
||||
*
|
||||
* @param $el progress list element
|
||||
* @param options
|
||||
*/
|
||||
start: function($el, options) {
|
||||
if (this._started) {
|
||||
return
|
||||
}
|
||||
|
||||
this.options = options
|
||||
let hasWarnings = false
|
||||
|
||||
this.$el = $el
|
||||
|
||||
this._started = true
|
||||
|
||||
const self = this
|
||||
|
||||
$(window).on('beforeunload.inprogress', function() {
|
||||
return t('core', 'The update is in progress, leaving this page might interrupt the process in some environments.')
|
||||
})
|
||||
|
||||
$('#update-progress-title').html(t(
|
||||
'core',
|
||||
'Update to {version}',
|
||||
{
|
||||
version: options.version,
|
||||
},
|
||||
))
|
||||
|
||||
const updateEventSource = new OC.EventSource(OC.getRootPath() + '/core/ajax/update.php')
|
||||
updateEventSource.listen('success', function(message) {
|
||||
self.setMessage(message)
|
||||
})
|
||||
updateEventSource.listen('notice', function(message) {
|
||||
self.setPermanentMessage(message)
|
||||
hasWarnings = true
|
||||
})
|
||||
updateEventSource.listen('error', function(message) {
|
||||
$('#update-progress-message').hide()
|
||||
$('#update-progress-icon')
|
||||
.addClass('icon-error-white')
|
||||
.removeClass('icon-loading-dark')
|
||||
message = message || t('core', 'An error occurred.')
|
||||
$(window).off('beforeunload.inprogress')
|
||||
self.setErrorMessage(message)
|
||||
message = t('core', 'Please reload the page.')
|
||||
$('<p>').append('<a href=".">' + message + '</a>').appendTo($el)
|
||||
updateEventSource.close()
|
||||
})
|
||||
updateEventSource.listen('failure', function(message) {
|
||||
$(window).off('beforeunload.inprogress')
|
||||
$('#update-progress-message').hide()
|
||||
$('#update-progress-icon')
|
||||
.addClass('icon-error-white')
|
||||
.removeClass('icon-loading-dark')
|
||||
|
||||
self.setErrorMessage(message)
|
||||
const updateUnsuccessful = $('<p>')
|
||||
if (message === 'Exception: Updates between multiple major versions and downgrades are unsupported.') {
|
||||
updateUnsuccessful.append(t('core', 'The update was unsuccessful. For more information <a href="{url}">check our forum post</a> covering this issue.', { url: 'https://help.nextcloud.com/t/updates-between-multiple-major-versions-are-unsupported/7094' }))
|
||||
} else if (OC.Update.options.productName === 'Nextcloud') {
|
||||
updateUnsuccessful.append(t('core', 'The update was unsuccessful. '
|
||||
+ 'Please report this issue to the '
|
||||
+ '<a href="https://github.com/nextcloud/server/issues" target="_blank">Nextcloud community</a>.'))
|
||||
}
|
||||
updateUnsuccessful.appendTo($el)
|
||||
})
|
||||
updateEventSource.listen('done', function() {
|
||||
$(window).off('beforeunload.inprogress')
|
||||
|
||||
$('#update-progress-message').hide()
|
||||
|
||||
$('#update-progress-icon')
|
||||
.addClass('icon-checkmark-white')
|
||||
.removeClass('icon-loading-dark')
|
||||
|
||||
if (hasWarnings) {
|
||||
$el.find('.update-show-detailed').before($('<input type="button" class="primary" value="' + t('core', 'Continue to {productName}', OC.Update.options) + '">').on('click', function() {
|
||||
window.location.reload()
|
||||
}))
|
||||
} else {
|
||||
$el.find('.update-show-detailed').before($('<p id="redirect-countdown"></p>'))
|
||||
|
||||
for (let i = 0; i <= 4; i++) {
|
||||
self.updateCountdown(i, 4)
|
||||
}
|
||||
|
||||
setTimeout(function() {
|
||||
window.location = window.location.href
|
||||
window.location.reload()
|
||||
}, 3000)
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
updateCountdown: function(i, total) {
|
||||
setTimeout(function() {
|
||||
$('#redirect-countdown').text(n('core', 'The update was successful. Redirecting you to {productName} in %n second.', 'The update was successful. Redirecting you to {productName} in %n seconds.', i, OC.Update.options))
|
||||
}, (total - i) * 1000)
|
||||
},
|
||||
|
||||
setMessage: function(message) {
|
||||
$('#update-progress-message').html(message)
|
||||
$('#update-progress-detailed')
|
||||
.append('<p>' + message + '</p>')
|
||||
},
|
||||
|
||||
setPermanentMessage: function(message) {
|
||||
$('#update-progress-message').html(message)
|
||||
$('#update-progress-message-warnings')
|
||||
.show()
|
||||
.append($('<ul>').append(message))
|
||||
$('#update-progress-detailed')
|
||||
.append('<p>' + message + '</p>')
|
||||
},
|
||||
|
||||
setErrorMessage: function(message) {
|
||||
$('#update-progress-message-error')
|
||||
.show()
|
||||
.html(message)
|
||||
$('#update-progress-detailed')
|
||||
.append('<p>' + message + '</p>')
|
||||
},
|
||||
}
|
||||
})()
|
||||
|
||||
window.addEventListener('DOMContentLoaded', function() {
|
||||
$('.updateButton').on('click', function() {
|
||||
const $updateEl = $('.update')
|
||||
const $progressEl = $('.update-progress')
|
||||
$progressEl.removeClass('hidden')
|
||||
$('.updateOverview').addClass('hidden')
|
||||
$('#update-progress-message-error').hide()
|
||||
$('#update-progress-message-warnings').hide()
|
||||
OC.Update.start($progressEl, {
|
||||
productName: $updateEl.attr('data-productname'),
|
||||
version: $updateEl.attr('data-version'),
|
||||
})
|
||||
return false
|
||||
})
|
||||
|
||||
$('.update-show-detailed').on('click', function() {
|
||||
$('#update-progress-detailed').toggleClass('hidden')
|
||||
return false
|
||||
})
|
||||
})
|
||||
20
core/src/update.ts
Normal file
20
core/src/update.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
/*!
|
||||
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { getCSPNonce } from '@nextcloud/auth'
|
||||
import { loadState } from '@nextcloud/initial-state'
|
||||
import Vue, { defineAsyncComponent } from 'vue'
|
||||
|
||||
__webpack_nonce__ = getCSPNonce()
|
||||
|
||||
const UpdaterAdmin = defineAsyncComponent(() => import('./views/UpdaterAdmin.vue'))
|
||||
const UpdaterAdminCli = defineAsyncComponent(() => import('./views/UpdaterAdminCli.vue'))
|
||||
|
||||
const view = loadState('core', 'updaterView')
|
||||
const app = new Vue({
|
||||
name: 'NextcloudUpdater',
|
||||
render: (h) => view === 'adminCli' ? h(UpdaterAdminCli) : h(UpdaterAdmin),
|
||||
})
|
||||
app.$mount('#core-updater')
|
||||
317
core/src/views/UpdaterAdmin.vue
Normal file
317
core/src/views/UpdaterAdmin.vue
Normal file
|
|
@ -0,0 +1,317 @@
|
|||
<!--
|
||||
- SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
|
||||
- SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
-->
|
||||
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
mdiAlertCircleOutline,
|
||||
mdiCheckCircleOutline,
|
||||
mdiChevronDown,
|
||||
mdiChevronUp,
|
||||
mdiCloseCircleOutline,
|
||||
mdiInformationOutline,
|
||||
} from '@mdi/js'
|
||||
import { loadState } from '@nextcloud/initial-state'
|
||||
import { t } from '@nextcloud/l10n'
|
||||
import { generateFilePath } from '@nextcloud/router'
|
||||
import { NcButton, NcIconSvgWrapper, NcLoadingIcon } from '@nextcloud/vue'
|
||||
import { computed, onMounted, onUnmounted, ref } from 'vue'
|
||||
import NcGuestContent from '@nextcloud/vue/components/NcGuestContent'
|
||||
import NcNoteCard from '@nextcloud/vue/components/NcNoteCard'
|
||||
import OCEventSource from '../OC/eventsource.js'
|
||||
|
||||
const updateInfo = loadState<{
|
||||
appsToUpgrade: { id: string, name: string, version: string, oldVersion: string }[]
|
||||
incompatibleAppsList: { id: string, name: string }[]
|
||||
isAppsOnlyUpgrade: boolean
|
||||
oldTheme: string | null
|
||||
productName: string
|
||||
version: string
|
||||
}>('core', 'updateInfo')
|
||||
|
||||
const isShowingDetails = ref(false)
|
||||
const isUpdateRunning = ref(false)
|
||||
const isUpdateDone = ref(false)
|
||||
|
||||
const messages = ref<{ message: string, type: string }[]>([])
|
||||
const wasSuccessfull = computed(() => messages.value.every((msg) => msg.type === 'success' || msg.type === 'notice'))
|
||||
const hasErrors = computed(() => messages.value.some((msg) => msg.type === 'error' || msg.type === 'failure'))
|
||||
const resultIcon = computed(() => wasSuccessfull.value ? mdiCheckCircleOutline : (hasErrors.value ? mdiCloseCircleOutline : mdiAlertCircleOutline))
|
||||
|
||||
const statusMessage = computed(() => {
|
||||
if (isUpdateDone.value) {
|
||||
if (!wasSuccessfull.value) {
|
||||
return t('core', 'The update completed with warnings. Please check the details for more information.')
|
||||
} else {
|
||||
return t('core', 'The update completed successfully.')
|
||||
}
|
||||
}
|
||||
return messages.value.at(-1)?.message || t('core', 'Preparing update…')
|
||||
})
|
||||
|
||||
const redirectCountdown = ref(6)
|
||||
const redirectMessage = computed(() => {
|
||||
if (!isUpdateDone.value || !wasSuccessfull.value) {
|
||||
return ''
|
||||
}
|
||||
|
||||
return t('core', 'You will be redirected to {productName} in {count} seconds.', { productName: updateInfo.productName, count: redirectCountdown.value })
|
||||
})
|
||||
|
||||
onMounted(() => window.addEventListener('beforeunload', onUnload))
|
||||
onUnmounted(() => window.removeEventListener('beforeunload', onUnload))
|
||||
|
||||
/**
|
||||
* Get the status icon for a given severity
|
||||
*
|
||||
* @param type - The severity
|
||||
*/
|
||||
function getSeverityIcon(type: string) {
|
||||
switch (type) {
|
||||
case 'success':
|
||||
return mdiCheckCircleOutline
|
||||
case 'notice':
|
||||
return mdiInformationOutline
|
||||
case 'warning':
|
||||
return mdiAlertCircleOutline
|
||||
case 'error':
|
||||
case 'failure':
|
||||
return mdiCloseCircleOutline
|
||||
default:
|
||||
return mdiInformationOutline
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the update process
|
||||
*/
|
||||
async function onStartUpdate() {
|
||||
if (isUpdateRunning.value || isUpdateDone.value) {
|
||||
return
|
||||
}
|
||||
|
||||
isUpdateRunning.value = true
|
||||
const eventSource = new OCEventSource(generateFilePath('core', '', 'ajax/update.php'))
|
||||
eventSource.listen('success', (message) => {
|
||||
messages.value.push({ message, type: 'success' })
|
||||
})
|
||||
eventSource.listen('notice', (message) => {
|
||||
messages.value.push({ message, type: 'notice' })
|
||||
})
|
||||
eventSource.listen('error', (message) => {
|
||||
messages.value.push({ message, type: 'error' })
|
||||
isUpdateRunning.value = false
|
||||
isUpdateDone.value = true
|
||||
eventSource.close()
|
||||
})
|
||||
eventSource.listen('failure', (message) => {
|
||||
messages.value.push({ message, type: 'failure' })
|
||||
})
|
||||
eventSource.listen('done', () => {
|
||||
isUpdateRunning.value = false
|
||||
isUpdateDone.value = true
|
||||
eventSource.close()
|
||||
updateCountdown()
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the countdown for the redirect
|
||||
*/
|
||||
function updateCountdown() {
|
||||
if (hasErrors.value || !wasSuccessfull.value) {
|
||||
return
|
||||
}
|
||||
|
||||
if (--redirectCountdown.value > 0) {
|
||||
window.setTimeout(updateCountdown, 1000)
|
||||
} else {
|
||||
reloadPage()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the beforeunload event to warn the user if an update is running.
|
||||
*
|
||||
* @param event - The beforeunload event object.
|
||||
*/
|
||||
function onUnload(event: BeforeUnloadEvent) {
|
||||
if (isUpdateRunning.value) {
|
||||
event.preventDefault()
|
||||
event.returnValue = t('core', 'The update is in progress, leaving this page might interrupt the process in some environments.')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reload the page
|
||||
*/
|
||||
function reloadPage() {
|
||||
window.location.reload()
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NcGuestContent>
|
||||
<h2>
|
||||
{{ updateInfo.isAppsOnlyUpgrade
|
||||
? t('core', 'App update required')
|
||||
: t('core', '{productName} will be updated to version {version}', { productName: updateInfo.productName, version: updateInfo.version }) }}
|
||||
</h2>
|
||||
|
||||
<NcNoteCard v-if="!!updateInfo.oldTheme" type="info">
|
||||
{{ t('core', 'The theme {oldTheme} has been disabled.', { oldTheme: updateInfo.oldTheme }) }}
|
||||
</NcNoteCard>
|
||||
|
||||
<NcNoteCard v-if="updateInfo.incompatibleAppsList.length" type="warning">
|
||||
{{ t('core', 'These incompatible apps will be disabled:') }}
|
||||
<ul :aria-label="t('core', 'Incompatible apps')" :class="$style.updater__appsList">
|
||||
<li v-for="app of updateInfo.incompatibleAppsList" :key="'app-disable-' + app.id">
|
||||
{{ app.name }} ({{ app.id }})
|
||||
</li>
|
||||
</ul>
|
||||
</NcNoteCard>
|
||||
|
||||
<NcNoteCard v-if="updateInfo.incompatibleAppsList.length" type="info">
|
||||
{{ t('core', 'These apps will be updated:') }}
|
||||
<ul :aria-label="t('core', 'Apps to update')" :class="$style.updater__appsList">
|
||||
<li v-for="app of updateInfo.appsToUpgrade" :key="'app-update-' + app.id">
|
||||
{{ t('core', '{app} from {oldVersion} to {version}', { app: `${app.name} (${app.id})`, oldVersion: app.oldVersion, version: app.version }) }}
|
||||
</li>
|
||||
</ul>
|
||||
</NcNoteCard>
|
||||
|
||||
<p>
|
||||
<strong>{{ t('core', 'Please make sure that the database, the config folder and the data folder have been backed up before proceeding.') }}</strong>
|
||||
<br>
|
||||
{{ t('core', 'To avoid timeouts with larger installations, you can instead run the following command from your installation directory:') }}
|
||||
<pre>./occ upgrade</pre>
|
||||
</p>
|
||||
|
||||
<NcButton
|
||||
v-if="!isUpdateRunning && !isUpdateDone"
|
||||
:class="$style.updater__updateButton"
|
||||
variant="primary"
|
||||
@click="onStartUpdate">
|
||||
{{ t('core', 'Start update') }}
|
||||
</NcButton>
|
||||
<NcButton
|
||||
v-else
|
||||
:class="$style.updater__updateButton"
|
||||
:disabled="isUpdateRunning"
|
||||
variant="primary"
|
||||
@click="reloadPage">
|
||||
{{ t('core', 'Continue to {productName}', { productName: updateInfo.productName }) }}
|
||||
</NcButton>
|
||||
|
||||
<div v-if="isUpdateRunning || isUpdateDone">
|
||||
<h2>{{ t('core', 'Update to {version}', { version: updateInfo.version }) }}</h2>
|
||||
|
||||
<NcLoadingIcon v-if="isUpdateRunning" />
|
||||
<NcIconSvgWrapper
|
||||
v-else
|
||||
:path="resultIcon"
|
||||
:class="{
|
||||
[$style.updater__messageIcon_success]: wasSuccessfull,
|
||||
[$style.updater__messageIcon_error]: hasErrors && !wasSuccessfull,
|
||||
[$style.updater__messageIcon_warning]: !hasErrors && !wasSuccessfull,
|
||||
}" />
|
||||
<div aria-live="polite">
|
||||
<em>{{ statusMessage }}</em><br>
|
||||
<span v-if="redirectMessage">{{ redirectMessage }}</span>
|
||||
</div>
|
||||
|
||||
<NcButton
|
||||
aria-controlls="core-update-details"
|
||||
:aria-expanded="isShowingDetails"
|
||||
variant="tertiary"
|
||||
@click="isShowingDetails = !isShowingDetails">
|
||||
<template #icon>
|
||||
<NcIconSvgWrapper
|
||||
:path="isShowingDetails ? mdiChevronUp : mdiChevronDown" />
|
||||
</template>
|
||||
{{ isShowingDetails ? t('core', 'Hide details') : t('core', 'Show details') }}
|
||||
</NcButton>
|
||||
<Transition
|
||||
:enter-active-class="$style.updater__transition_active"
|
||||
:leave-active-class="$style.updater__transition_active"
|
||||
:leave-to-class="$style.updater__transition_collapsed"
|
||||
:enter-class="$style.updater__transition_collapsed">
|
||||
<ul
|
||||
v-show="isShowingDetails"
|
||||
id="core-update-details"
|
||||
:aria-label="t('core', 'Update details')"
|
||||
:class="$style.updater__messageList">
|
||||
<li
|
||||
v-for="{ message, type } of messages"
|
||||
:key="message"
|
||||
:class="$style.updater__message">
|
||||
<NcIconSvgWrapper
|
||||
:class="{
|
||||
[$style.updater__messageIcon_error]: type === 'error' || type === 'failure',
|
||||
[$style.updater__messageIcon_info]: type === 'notice',
|
||||
[$style.updater__messageIcon_success]: type === 'success',
|
||||
[$style.updater__messageIcon_warning]: type === 'warning',
|
||||
}"
|
||||
:path="getSeverityIcon(type)" />
|
||||
<span :class="$style.updater__messageText">{{ message }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</Transition>
|
||||
</div>
|
||||
</NcGuestContent>
|
||||
</template>
|
||||
|
||||
<style module>
|
||||
.updater__appsList {
|
||||
list-style-type: disc;
|
||||
margin-inline-start: var(--default-clickable-area);
|
||||
}
|
||||
|
||||
.updater__updateButton {
|
||||
margin-inline: auto;
|
||||
margin-block: 1rem;
|
||||
}
|
||||
|
||||
.updater__messageList {
|
||||
max-height: 50vh;
|
||||
overflow: visible scroll;
|
||||
padding-inline-start: var(--default-grid-baseline);
|
||||
}
|
||||
|
||||
.updater__message {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: start;
|
||||
gap: var(--default-grid-baseline);
|
||||
}
|
||||
|
||||
.updater__messageText {
|
||||
text-align: start;
|
||||
}
|
||||
|
||||
.updater__messageIcon_success {
|
||||
color: var(--color-element-success);
|
||||
}
|
||||
|
||||
.updater__messageIcon_info {
|
||||
color: var(--color-element-info);
|
||||
}
|
||||
|
||||
.updater__messageIcon_error {
|
||||
color: var(--color-element-error);
|
||||
}
|
||||
|
||||
.updater__messageIcon_warning {
|
||||
color: var(--color-element-warning);
|
||||
}
|
||||
|
||||
.updater__transition_active {
|
||||
transition: all var(--animation-slow);
|
||||
}
|
||||
|
||||
.updater__transition_collapsed {
|
||||
opacity: 0;
|
||||
max-height: 0px;
|
||||
}
|
||||
</style>
|
||||
53
core/src/views/UpdaterAdminCli.vue
Normal file
53
core/src/views/UpdaterAdminCli.vue
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
<!--
|
||||
- SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
|
||||
- SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
-->
|
||||
|
||||
<script setup lang="ts">
|
||||
import { mdiInformationOutline } from '@mdi/js'
|
||||
import { loadState } from '@nextcloud/initial-state'
|
||||
import { t } from '@nextcloud/l10n'
|
||||
import NcButton from '@nextcloud/vue/components/NcButton'
|
||||
import NcGuestContent from '@nextcloud/vue/components/NcGuestContent'
|
||||
import NcIconSvgWrapper from '@nextcloud/vue/components/NcIconSvgWrapper'
|
||||
import NcNoteCard from '@nextcloud/vue/components/NcNoteCard'
|
||||
|
||||
const updateInfo = loadState<{
|
||||
tooBig: boolean
|
||||
cliUpgradeLink: string
|
||||
}>('core', 'updateInfo')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NcGuestContent>
|
||||
<h2>{{ t('core', 'Update needed') }}</h2>
|
||||
<p>
|
||||
{{ updateInfo.tooBig
|
||||
? t('core', 'Please use the command line updater because you have a big instance with more than 50 accounts.')
|
||||
: t('core', 'Please use the command line updater because updating via browser is disabled in your config.php.') }}
|
||||
|
||||
<NcButton :class="$style.updaterAdminCli__button" :href="updateInfo.cliUpgradeLink">
|
||||
<template #icon>
|
||||
<NcIconSvgWrapper :path="mdiInformationOutline" />
|
||||
</template>
|
||||
{{ t('core', 'Documentation') }}
|
||||
</NcButton>
|
||||
</p>
|
||||
|
||||
<NcNoteCard type="warning">
|
||||
{{ t('core', 'I know that if I continue doing the update via web UI has the risk, that the request runs into a timeout and could cause data loss, but I have a backup and know how to restore my instance in case of a failure.') }}
|
||||
<NcButton
|
||||
href="?IKnowThatThisIsABigInstanceAndTheUpdateRequestCouldRunIntoATimeoutAndHowToRestoreABackup=IAmSuperSureToDoThis"
|
||||
variant="tertiary">
|
||||
{{ t('core', 'Upgrade via web on my own risk') }}
|
||||
</NcButton>
|
||||
</NcNoteCard>
|
||||
</NcGuestContent>
|
||||
</template>
|
||||
|
||||
<style module>
|
||||
.updaterAdminCli__button {
|
||||
margin-block: 1rem;
|
||||
margin-inline: auto;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,60 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-FileCopyrightText: 2013-2015 ownCloud, Inc.
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
?>
|
||||
<div class="guest-box update" data-productname="<?php p($_['productName']) ?>" data-version="<?php p($_['version']) ?>">
|
||||
<div class="updateOverview">
|
||||
<?php if ($_['isAppsOnlyUpgrade']) { ?>
|
||||
<h2 class="title"><?php p($l->t('App update required')); ?></h2>
|
||||
<?php } else { ?>
|
||||
<h2 class="title"><?php p($l->t('%1$s will be updated to version %2$s',
|
||||
[$_['productName'], $_['version']])); ?></h2>
|
||||
<?php } ?>
|
||||
<?php if (!empty($_['appsToUpgrade'])) { ?>
|
||||
<div class="text-left">
|
||||
<span><?php p($l->t('The following apps will be updated:')); ?></span>
|
||||
<ul class="content appList">
|
||||
<?php foreach ($_['appsToUpgrade'] as $appInfo) { ?>
|
||||
<li><?php p($appInfo['name']) ?> (<?php p($appInfo['id']) ?>)</li>
|
||||
<?php } ?>
|
||||
</ul>
|
||||
</div>
|
||||
<?php } ?>
|
||||
<?php if (!empty($_['incompatibleAppsList'])) { ?>
|
||||
<div class="text-left">
|
||||
<span><?php p($l->t('These incompatible apps will be disabled:')) ?></span>
|
||||
<ul class="content appList">
|
||||
<?php foreach ($_['incompatibleAppsList'] as $appInfo) { ?>
|
||||
<li><?php p($appInfo['name']) ?> (<?php p($appInfo['id']) ?>)</li>
|
||||
<?php } ?>
|
||||
</ul>
|
||||
</div>
|
||||
<?php } ?>
|
||||
<?php if (!empty($_['oldTheme'])) { ?>
|
||||
<div class="infogroup">
|
||||
<?php p($l->t('The theme %s has been disabled.', [$_['oldTheme']])) ?>
|
||||
</div>
|
||||
<?php } ?>
|
||||
<div class="text-left margin-top bold">
|
||||
<?php p($l->t('Please make sure that the database, the config folder and the data folder have been backed up before proceeding.')) ?>
|
||||
</div>
|
||||
<input class="updateButton primary margin-top" type="button" value="<?php p($l->t('Start update')) ?>">
|
||||
<div class="notecard warning">
|
||||
<?php p($l->t('To avoid timeouts with larger installations, you can instead run the following command from your installation directory:')) ?>
|
||||
<pre>./occ upgrade</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="update-progress hidden">
|
||||
<h2 id="update-progress-title"></h2>
|
||||
<div id="update-progress-icon" class="icon-loading-dark"></div>
|
||||
<p id="update-progress-message-error" class="hidden"></p>
|
||||
<ul id="update-progress-message-warnings" class="hidden"></ul>
|
||||
<p id="update-progress-message"></p>
|
||||
<a class="update-show-detailed"><?php p($l->t('Detailed logs')); ?> <span class="icon-caret-white"></span></a>
|
||||
<div id="update-progress-detailed" class="hidden"></div>
|
||||
</div>
|
||||
</div>
|
||||
8
core/templates/update.php
Normal file
8
core/templates/update.php
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
<?php
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
?>
|
||||
|
||||
<div id="core-updater"></div>
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2017-2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
?>
|
||||
<div class="guest-box update" data-productname="<?php p($_['productName']) ?>" data-version="<?php p($_['version']) ?>">
|
||||
<div class="updateOverview">
|
||||
<h2 class="title"><?php p($l->t('Update needed')) ?></h2>
|
||||
<div class="text-left">
|
||||
<?php if ($_['tooBig']) {
|
||||
p($l->t('Please use the command line updater because you have a big instance with more than 50 accounts.'));
|
||||
} else {
|
||||
p($l->t('Please use the command line updater because updating via browser is disabled in your config.php.'));
|
||||
} ?><br><br>
|
||||
<?php if (is_string($_['cliUpgradeLink']) && $_['cliUpgradeLink'] !== '') {
|
||||
$cliUpgradeLink = $_['cliUpgradeLink'];
|
||||
} else {
|
||||
$cliUpgradeLink = link_to_docs('admin-cli-upgrade');
|
||||
}
|
||||
print_unescaped($l->t('For help, see the <a target="_blank" rel="noreferrer noopener" href="%s">documentation</a>.', [$cliUpgradeLink])); ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if ($_['tooBig']) { ?>
|
||||
<div class="notecard warning">
|
||||
<p><?php p($l->t('I know that if I continue doing the update via web UI has the risk, that the request runs into a timeout and could cause data loss, but I have a backup and know how to restore my instance in case of a failure.')); ?></p>
|
||||
<a class="button error margin-top" href="?IKnowThatThisIsABigInstanceAndTheUpdateRequestCouldRunIntoATimeoutAndHowToRestoreABackup=IAmSuperSureToDoThis"><?php p($l->t('Upgrade via web on my own risk')); ?></a>
|
||||
</div>
|
||||
<?php } ?>
|
||||
</div>
|
||||
77
lib/base.php
77
lib/base.php
|
|
@ -17,7 +17,9 @@ use OCP\EventDispatcher\IEventDispatcher;
|
|||
use OCP\Files\Events\BeforeFileSystemSetupEvent;
|
||||
use OCP\Group\Events\GroupDeletedEvent;
|
||||
use OCP\Group\Events\UserRemovedEvent;
|
||||
use OCP\IAppConfig;
|
||||
use OCP\IConfig;
|
||||
use OCP\IInitialStateService;
|
||||
use OCP\ILogger;
|
||||
use OCP\IRequest;
|
||||
use OCP\IURLGenerator;
|
||||
|
|
@ -290,47 +292,49 @@ class OC {
|
|||
$ignoreTooBigWarning = isset($_GET['IKnowThatThisIsABigInstanceAndTheUpdateRequestCouldRunIntoATimeoutAndHowToRestoreABackup'])
|
||||
&& $_GET['IKnowThatThisIsABigInstanceAndTheUpdateRequestCouldRunIntoATimeoutAndHowToRestoreABackup'] === 'IAmSuperSureToDoThis';
|
||||
|
||||
Util::addTranslations('core');
|
||||
Util::addScript('core', 'common');
|
||||
Util::addScript('core', 'main');
|
||||
Util::addScript('core', 'update');
|
||||
|
||||
$initialState = Server::get(IInitialStateService::class);
|
||||
$serverVersion = \OCP\Server::get(\OCP\ServerVersion::class);
|
||||
if ($disableWebUpdater || ($tooBig && !$ignoreTooBigWarning)) {
|
||||
// send http status 503
|
||||
http_response_code(503);
|
||||
header('Retry-After: 120');
|
||||
|
||||
$serverVersion = \OCP\Server::get(\OCP\ServerVersion::class);
|
||||
$urlGenerator = Server::get(IURLGenerator::class);
|
||||
$initialState->provideInitialState('core', 'updaterView', 'adminCli');
|
||||
$initialState->provideInitialState('core', 'updateInfo', [
|
||||
'cliUpgradeLink' => $cliUpgradeLink ?: $urlGenerator->linkToDocs('admin-cli-upgrade'),
|
||||
'productName' => self::getProductName(),
|
||||
'version' => $serverVersion->getVersionString(),
|
||||
'tooBig' => $tooBig,
|
||||
]);
|
||||
|
||||
// render error page
|
||||
$template = Server::get(ITemplateManager::class)->getTemplate('', 'update.use-cli', 'guest');
|
||||
$template->assign('productName', 'nextcloud'); // for now
|
||||
$template->assign('version', $serverVersion->getVersionString());
|
||||
$template->assign('tooBig', $tooBig);
|
||||
$template->assign('cliUpgradeLink', $cliUpgradeLink);
|
||||
|
||||
$template->printPage();
|
||||
Server::get(ITemplateManager::class)
|
||||
->getTemplate('', 'update', 'guest')
|
||||
->printPage();
|
||||
die();
|
||||
}
|
||||
|
||||
// check whether this is a core update or apps update
|
||||
$installedVersion = $systemConfig->getValue('version', '0.0.0');
|
||||
$currentVersion = implode('.', \OCP\Util::getVersion());
|
||||
$currentVersion = implode('.', $serverVersion->getVersion());
|
||||
|
||||
// if not a core upgrade, then it's apps upgrade
|
||||
$isAppsOnlyUpgrade = version_compare($currentVersion, $installedVersion, '=');
|
||||
|
||||
$oldTheme = $systemConfig->getValue('theme');
|
||||
$systemConfig->setValue('theme', '');
|
||||
\OCP\Util::addScript('core', 'common');
|
||||
\OCP\Util::addScript('core', 'main');
|
||||
\OCP\Util::addTranslations('core');
|
||||
\OCP\Util::addScript('core', 'update');
|
||||
|
||||
/** @var \OC\App\AppManager $appManager */
|
||||
$appManager = Server::get(\OCP\App\IAppManager::class);
|
||||
|
||||
$tmpl = Server::get(ITemplateManager::class)->getTemplate('', 'update.admin', 'guest');
|
||||
$tmpl->assign('version', \OCP\Server::get(\OCP\ServerVersion::class)->getVersionString());
|
||||
$tmpl->assign('isAppsOnlyUpgrade', $isAppsOnlyUpgrade);
|
||||
|
||||
// get third party apps
|
||||
$ocVersion = \OCP\Util::getVersion();
|
||||
$ocVersion = $serverVersion->getVersion();
|
||||
$ocVersion = implode('.', $ocVersion);
|
||||
$incompatibleApps = $appManager->getIncompatibleApps($ocVersion);
|
||||
$incompatibleOverwrites = $systemConfig->getValue('app_install_overwrite', []);
|
||||
|
|
@ -351,16 +355,41 @@ class OC {
|
|||
throw new \OCP\HintException('Application ' . implode(', ', $incompatibleShippedApps) . ' is not present or has a non-compatible version with this server. Please check the apps directory.', $hint);
|
||||
}
|
||||
|
||||
$tmpl->assign('appsToUpgrade', $appManager->getAppsNeedingUpgrade($ocVersion));
|
||||
$tmpl->assign('incompatibleAppsList', $incompatibleDisabledApps);
|
||||
$appConfig = Server::get(IAppConfig::class);
|
||||
$appsToUpgrade = array_map(function ($app) use (&$appConfig) {
|
||||
return [
|
||||
'id' => $app['id'],
|
||||
'name' => $app['name'],
|
||||
'version' => $app['version'],
|
||||
'oldVersion' => $appConfig->getValueString($app['id'], 'installed_version'),
|
||||
];
|
||||
}, $appManager->getAppsNeedingUpgrade($ocVersion));
|
||||
|
||||
$params = [
|
||||
'appsToUpgrade' => $appsToUpgrade,
|
||||
'incompatibleAppsList' => $incompatibleDisabledApps,
|
||||
'isAppsOnlyUpgrade' => $isAppsOnlyUpgrade,
|
||||
'oldTheme' => $oldTheme,
|
||||
'productName' => self::getProductName(),
|
||||
'version' => $serverVersion->getVersionString(),
|
||||
];
|
||||
|
||||
$initialState->provideInitialState('core', 'updaterView', 'admin');
|
||||
$initialState->provideInitialState('core', 'updateInfo', $params);
|
||||
Server::get(ITemplateManager::class)
|
||||
->getTemplate('', 'update', 'guest')
|
||||
->printPage();
|
||||
}
|
||||
|
||||
private static function getProductName(): string {
|
||||
$productName = 'Nextcloud';
|
||||
try {
|
||||
$defaults = new \OC_Defaults();
|
||||
$tmpl->assign('productName', $defaults->getName());
|
||||
$productName = $defaults->getName();
|
||||
} catch (Throwable $error) {
|
||||
$tmpl->assign('productName', 'Nextcloud');
|
||||
// ignore
|
||||
}
|
||||
$tmpl->assign('oldTheme', $oldTheme);
|
||||
$tmpl->printPage();
|
||||
return $productName;
|
||||
}
|
||||
|
||||
public static function initSession(): void {
|
||||
|
|
|
|||
Loading…
Reference in a new issue