Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 18 additions & 2 deletions src/assets/lang/en_US.json
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,6 @@
"Checking for system package updates...": "Checking for system package updates...",
"Applying system package updates...": "Applying system package updates...",
"Finishing system package update...": "Finishing system package update...",
"Are you sure you want to update all available system packages?": "Are you sure you want to update all available system packages?",
"The package list may change before the update starts.": "The package list may change before the update starts.",
"Retry": "Retry",
"Let more friends know": "Let more friends know",
Expand Down Expand Up @@ -928,5 +927,22 @@
"ReCasaOS update": "ReCasaOS update",
"Share on {network}": "Share on {network}",
"The update could not be started.": "The update could not be started.",
"The progress of the update could not be loaded.": "The progress of the update could not be loaded."
"The progress of the update could not be loaded.": "The progress of the update could not be loaded.",
"Docker": "Docker",
"Docker has updates. This update leaves it alone.": "Docker has updates. This update leaves it alone.",
"Updating Docker restarts it: every container stops until it is back. Apps set to restart start again by themselves, the others stay stopped. Do it when that suits you.": "Updating Docker restarts it: every container stops until it is back. Apps set to restart start again by themselves, the others stay stopped. Do it when that suits you.",
"In a terminal on this machine:": "In a terminal on this machine:",
"ReCasaOS does not know how Docker was installed here, so it shows no command.": "ReCasaOS does not know how Docker was installed here, so it shows no command.",
"Docker is up to date.": "Docker is up to date.",
"Docker is not installed from a package here.": "Docker is not installed from a package here.",
"Installed from Docker's own repository.": "Installed from Docker's own repository.",
"Installed from your distribution's docker.io package.": "Installed from your distribution's docker.io package.",
"Installed as a snap.": "Installed as a snap.",
"Installed from a source ReCasaOS does not recognise.": "Installed from a source ReCasaOS does not recognise.",
"Are you sure you want to update the system packages listed? Docker is not part of this update.": "Are you sure you want to update the system packages listed? Docker is not part of this update.",
"No other system package updates are available.": "No other system package updates are available.",
"Command to update Docker": "Command to update Docker",
"ReCasaOS cannot see updates of a snap. Update it with the command below.": "ReCasaOS cannot see updates of a snap. Update it with the command below.",
"This machine's package sources offer no update for it, and ReCasaOS cannot tell more.": "This machine's package sources offer no update for it, and ReCasaOS cannot tell more.",
"Are you sure you want to update the system packages listed?": "Are you sure you want to update the system packages listed?"
}
20 changes: 18 additions & 2 deletions src/assets/lang/fr_FR.json
Original file line number Diff line number Diff line change
Expand Up @@ -439,7 +439,6 @@
"Checking for system package updates...": "Recherche de mises à jour des paquets système...",
"Applying system package updates...": "Application des mises à jour des paquets système...",
"Finishing system package update...": "Finalisation de la mise à jour des paquets système...",
"Are you sure you want to update all available system packages?": "Voulez-vous vraiment mettre à jour tous les paquets système disponibles ?",
"The package list may change before the update starts.": "La liste des paquets peut changer avant le début de la mise à jour.",
"Retry": "Réessayer",
"Merged Storage": "Stockage fusionné",
Expand Down Expand Up @@ -928,5 +927,22 @@
"ReCasaOS update": "Mise à jour de ReCasaOS",
"Share on {network}": "Partager sur {network}",
"The update could not be started.": "La mise à jour n’a pas pu démarrer.",
"The progress of the update could not be loaded.": "L’avancement de la mise à jour n’a pas pu être chargé."
"The progress of the update could not be loaded.": "L’avancement de la mise à jour n’a pas pu être chargé.",
"Docker": "Docker",
"Docker has updates. This update leaves it alone.": "Des mises à jour de Docker sont disponibles. Cette mise à jour ne les installe pas.",
"Updating Docker restarts it: every container stops until it is back. Apps set to restart start again by themselves, the others stay stopped. Do it when that suits you.": "La mise à jour de Docker le redémarre : tous les conteneurs s'arrêtent jusqu'à son retour. Les apps configurées pour redémarrer repartent d'elles-mêmes, les autres restent arrêtées. Faites-le quand cela vous arrange.",
"In a terminal on this machine:": "Dans un terminal sur cette machine :",
"ReCasaOS does not know how Docker was installed here, so it shows no command.": "ReCasaOS ne sait pas comment Docker a été installé ici, il n'affiche donc pas de commande.",
"Docker is up to date.": "Docker est à jour.",
"Docker is not installed from a package here.": "Docker n'a pas été installé à partir d'un paquet sur cette machine.",
"Installed from Docker's own repository.": "Installé depuis le dépôt officiel de Docker.",
"Installed from your distribution's docker.io package.": "Installé depuis le paquet docker.io de votre distribution.",
"Installed as a snap.": "Installé via snap.",
"Installed from a source ReCasaOS does not recognise.": "Installé depuis une source que ReCasaOS ne reconnaît pas.",
"Are you sure you want to update the system packages listed? Docker is not part of this update.": "Voulez-vous vraiment mettre à jour les paquets système listés ? Docker ne fait pas partie de cette mise à jour.",
"No other system package updates are available.": "Aucune autre mise à jour de paquets système n'est disponible.",
"Command to update Docker": "Commande pour mettre Docker à jour",
"ReCasaOS cannot see updates of a snap. Update it with the command below.": "ReCasaOS ne voit pas les mises à jour d'un snap. Mettez-le à jour avec la commande ci-dessous.",
"This machine's package sources offer no update for it, and ReCasaOS cannot tell more.": "Les sources de paquets de cette machine n'offrent pas de mise à jour pour lui, et ReCasaOS ne peut pas en dire plus.",
"Are you sure you want to update the system packages listed?": "Voulez-vous vraiment mettre à jour les paquets système listés ?"
}
146 changes: 143 additions & 3 deletions src/components/settings/SystemPackageUpdateModal.spec.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// @vitest-environment happy-dom
import { h } from 'vue'
import { flushPromises, mount } from '@vue/test-utils'
import { afterEach, describe, expect, it } from 'vitest'
import { afterEach, describe, expect, it, vi } from 'vitest'
import SystemPackageUpdateModal from './SystemPackageUpdateModal.vue'

const answer = data => () => Promise.resolve({ data: { success: 200, data } })
Expand All @@ -17,10 +17,10 @@ const withSlot = {
let wrapper
afterEach(() => wrapper?.unmount())

async function open(sys) {
async function open(sys, mocks = {}) {
wrapper = mount(SystemPackageUpdateModal, {
global: {
mocks: { $t: key => key, $api: { sys: { getSystemPackageUpdateStatus: answer(idle), ...sys } } },
mocks: { $t: key => key, $api: { sys: { getSystemPackageUpdateStatus: answer(idle), ...sys } }, ...mocks },
stubs: { 'b-button': withSlot, 'b-message': withSlot, 'b-icon': true },
},
})
Expand Down Expand Up @@ -57,3 +57,143 @@ describe('system packages', () => {
expect(w.find('pre').text()).toBe('Reading package lists...')
})
})

describe('docker on a line of its own', () => {
const docker = {
installed: true,
origin: 'docker-repository',
version: '29.8.1',
restarts_docker: true,
updates: [{ name: 'docker-ce', current_version: '5:29.8.1-1', candidate_version: '5:29.8.2-1' }],
manual_command: 'sudo apt-get update && sudo apt-get install --only-upgrade docker-ce docker-ce-cli containerd.io',
}
const check = extra => answer({
supported: true,
manager: 'apt',
updates: [{ name: 'libc6', current_version: '2.35-1', candidate_version: '2.35-2' }],
count: 1,
...extra,
})

it('shows its version and where it came from, what it has, the warning and the command to type', async () => {
const w = await open({ getSystemPackages: check({ docker }) })

const line = w.find('.docker-line')
expect(line.exists()).toBe(true)
expect(line.text()).toContain('Docker')
expect(line.text()).toContain('29.8.1')
expect(line.text()).toContain('Installed from Docker\'s own repository.')
expect(line.text()).toContain('Docker has updates. This update leaves it alone.')
expect(line.text()).toContain('docker-ce')
expect(line.text()).toContain('every container stops until it is back')
expect(line.find('pre.docker-command').text()).toBe(docker.manual_command)
expect(w.find('.package-list').text()).toContain('libc6')
})

it('says it is up to date, with no command, when apt has nothing on offer for a source it can see', async () => {
for (const origin of ['docker-repository', 'distribution']) {
const w = await open({ getSystemPackages: check({ docker: { ...docker, origin, updates: [] } }) })

expect(w.find('.docker-line').text()).toContain('Docker is up to date.')
expect(w.find('.docker-line pre').exists()).toBe(false)
w.unmount()
}
})

it('does not say "up to date" of a snap, which apt cannot see, and gives the command', async () => {
const w = await open({ getSystemPackages: check({ docker: { installed: true, origin: 'snap', updates: [], manual_command: 'sudo snap refresh docker' } }) })

const line = w.find('.docker-line').text()
expect(line).toContain('Installed as a snap.')
expect(line).toContain('ReCasaOS cannot see updates of a snap. Update it with the command below.')
expect(line).not.toContain('Docker is up to date.')
expect(w.find('pre.docker-command').text()).toBe('sudo snap refresh docker')
})

it('does not say "up to date" of a Docker whose source it does not know, and shows no command', async () => {
const w = await open({ getSystemPackages: check({ docker: { installed: true, origin: 'unknown', version: '24.0.5', updates: [], manual_command: '' } }) })

const line = w.find('.docker-line').text()
expect(line).toContain('Installed from a source ReCasaOS does not recognise.')
expect(line).toContain('offer no update for it')
expect(line).not.toContain('Docker is up to date.')
expect(w.find('.docker-line pre').exists()).toBe(false)
})

it('says it has no command when it has updates for a source it does not know', async () => {
const w = await open({ getSystemPackages: check({ docker: { ...docker, origin: 'unknown', manual_command: '' } }) })

expect(w.find('.docker-line').text()).toContain('ReCasaOS does not know how Docker was installed here, so it shows no command.')
expect(w.find('.docker-line pre').exists()).toBe(false)
})

it('warns that Docker restarts only when what is pending restarts it', async () => {
const plugin = { ...docker, restarts_docker: false, updates: [{ name: 'docker-compose-plugin', current_version: '2.40.0', candidate_version: '2.40.1' }] }
const w = await open({ getSystemPackages: check({ docker: plugin }) })

expect(w.find('.docker-line').text()).toContain('Docker has updates.')
expect(w.find('.docker-line').text()).not.toContain('every container stops until it is back')
})

it('copes with a Docker object whose updates are null', async () => {
const w = await open({ getSystemPackages: check({ docker: { ...docker, updates: null } }) })

expect(w.find('.docker-line').text()).toContain('Docker is up to date.')
})

it('has no Docker line when the host reports none', async () => {
const w = await open({ getSystemPackages: check({}) })

expect(w.find('.docker-line').exists()).toBe(false)
})

it('shows Docker even when nothing else is to update, next to the "no updates" sentence', async () => {
const w = await open({ getSystemPackages: check({ updates: [], count: 0, docker }) })

expect(w.text()).toContain('No other system package updates are available.')
expect(w.find('.docker-line').text()).toContain('docker-ce')
expect(w.text()).not.toContain('Update packages')
})

it('says plainly that nothing is available when Docker has nothing either', async () => {
const w = await open({ getSystemPackages: check({ updates: [], count: 0, docker: { ...docker, updates: [] } }) })

expect(w.text()).toContain('No system package updates are available.')
expect(w.text()).not.toContain('No other')
})

it('asks for confirmation of the listed packages and says Docker is not part of it', async () => {
const confirm = vi.fn()
const w = await open({ getSystemPackages: check({ docker }) }, { $buefy: { dialog: { confirm } } })

w.vm.confirmUpdate()

expect(confirm).toHaveBeenCalledTimes(1)
expect(confirm.mock.calls[0][0].message).toContain('Docker is not part of this update.')
expect(confirm.mock.calls[0][0].message).not.toContain('all available')
})

it('does not say Docker is not part of the update when the core did not say so', async () => {
// an older core lists Docker's packages with the rest and installs them: the sentence would be false
const confirm = vi.fn()
const w = await open({ getSystemPackages: check({}) }, { $buefy: { dialog: { confirm } } })

w.vm.confirmUpdate()

expect(confirm.mock.calls[0][0].message).not.toContain('Docker')
})

it('shows the core\'s reason once when the update is refused, and checks the list again', async () => {
const reason = 'another update or package operation is running on this box: casaos-update is running'
const refusal = Object.assign(new Error('Request failed with status code 409'), { response: { status: 409, data: { message: reason, data: { ...idle, error: reason } } } })
const getSystemPackages = vi.fn(check({ docker }))
const w = await open({ getSystemPackages, startSystemPackageUpdate: () => Promise.reject(refusal) })
expect(getSystemPackages).toHaveBeenCalledTimes(1)

await w.vm.startUpdate()
await flushPromises()

expect(w.text().split(reason).length - 1).toBe(1)
expect(getSystemPackages).toHaveBeenCalledTimes(2)
})
})
87 changes: 84 additions & 3 deletions src/components/settings/SystemPackageUpdateModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
</b-message>

<div v-if="info.count === 0 && !isRunning && status.state === 'idle' && !error" class="package-empty">
{{ $t('No system package updates are available.') }}
{{ $t(dockerHasUpdates ? 'No other system package updates are available.' : 'No system package updates are available.') }}
</div>

<div v-else-if="info.count > 0" class="package-summary">
Expand All @@ -56,6 +56,32 @@
</div>
</div>

<div v-if="info.docker" class="docker-line mt-4">
<h4 class="docker-title">
{{ $t('Docker') }}
<span v-if="info.docker.version" class="ml-1 _has-text-gray">{{ info.docker.version }}</span>
</h4>
<p class="is-size-7 _has-text-gray">{{ dockerOrigin }}</p>
<template v-if="dockerHasUpdates">
<p class="mt-2 is-size-7">{{ $t('Docker has updates. This update leaves it alone.') }}</p>
<ul class="docker-updates is-size-7 _has-text-gray">
<li v-for="item in info.docker.updates" :key="item.name">
{{ item.name }} {{ item.current_version || '—' }} → {{ item.candidate_version }}
</li>
</ul>
<p v-if="info.docker.restarts_docker" class="mt-2 is-size-7">
{{ $t('Updating Docker restarts it: every container stops until it is back. Apps set to restart start again by themselves, the others stay stopped. Do it when that suits you.') }}
</p>
</template>
<p v-else-if="dockerCanSeeUpdates" class="is-size-7">{{ $t('Docker is up to date.') }}</p>
<p v-else class="is-size-7">{{ dockerUnseen }}</p>
<template v-if="info.docker.manual_command && (dockerHasUpdates || !dockerCanSeeUpdates)">
<p class="mt-2 is-size-7">{{ $t('In a terminal on this machine:') }}</p>
<pre class="docker-command" tabindex="0" :aria-label="$t('Command to update Docker')">{{ info.docker.manual_command }}</pre>
</template>
<p v-else-if="dockerHasUpdates" class="mt-2 is-size-7">{{ $t('ReCasaOS does not know how Docker was installed here, so it shows no command.') }}</p>
</div>

<div v-if="isRunning" class="mt-4">
<p class="has-text-info-on-scheme">
{{ $t(isReconciliationPending ? 'Finishing system package update...' : 'Applying system package updates...') }}
Expand All @@ -67,7 +93,7 @@
<div v-else-if="status.state === 'failed'" class="mt-4 has-text-danger-on-scheme">
{{ $t('System package update failed.') }}
</div>
<p v-if="status.error && !isReconciliationPending" class="mt-1 is-size-7 _has-text-gray">
<p v-if="status.error && !isReconciliationPending && status.error !== error?.detail" class="mt-1 is-size-7 _has-text-gray">
{{ status.error }}
</p>

Expand Down Expand Up @@ -138,6 +164,32 @@ export default {
hasUpdates() {
return Array.isArray(this.info.updates) && this.info.updates.length > 0
},
dockerHasUpdates() {
return Array.isArray(this.info.docker?.updates) && this.info.docker.updates.length > 0
},
// apt tells of an update for Docker's own repository and for the distribution's package;
// of a snap, or of a Docker whose source it does not know, it tells nothing, and "up to
// date" would be a claim nobody checked
dockerCanSeeUpdates() {
return ['docker-repository', 'distribution'].includes(this.info.docker?.origin)
},
dockerOrigin() {
const docker = this.info.docker
if (!docker?.installed) {
return this.$t('Docker is not installed from a package here.')
}
const words = {
'docker-repository': 'Installed from Docker\'s own repository.',
'distribution': 'Installed from your distribution\'s docker.io package.',
'snap': 'Installed as a snap.',
}
return this.$t(words[docker.origin] || 'Installed from a source ReCasaOS does not recognise.')
},
dockerUnseen() {
return this.$t(this.info.docker?.origin === 'snap'
? 'ReCasaOS cannot see updates of a snap. Update it with the command below.'
: 'This machine\'s package sources offer no update for it, and ReCasaOS cannot tell more.')
},
},
mounted() {
this.loadInitialState()
Expand Down Expand Up @@ -182,7 +234,7 @@ export default {
confirmUpdate() {
this.$buefy.dialog.confirm({
title: this.$t('Update system packages'),
message: `${this.$t('Are you sure you want to update all available system packages?')}<br><br>${this.$t('The package list may change before the update starts.')}`,
message: `${this.$t(this.info.docker ? 'Are you sure you want to update the system packages listed? Docker is not part of this update.' : 'Are you sure you want to update the system packages listed?')}<br><br>${this.$t('The package list may change before the update starts.')}`,
type: 'is-warning',
hasIcon: true,
confirmText: this.$t('Update packages'),
Expand All @@ -205,6 +257,10 @@ export default {
}
}
this.fail('Could not start the system package update.', error)
// refused (409) for a reason other than "already running": what was on screen may no longer be so
if (error?.response?.status === 409 && this.status.state !== 'running' && !this.isReconciliationPending) {
this.checkPackages(true)
}
} finally {
this.isStarting = false
}
Expand Down Expand Up @@ -268,6 +324,31 @@ export default {
}
}

.docker-line {
padding-top: 1rem;
border-top: 1px solid rgba(128, 128, 128, 0.25);
}

.docker-title {
font-weight: 600;
}

.docker-updates {
margin: 0.25rem 0 0 1rem;
list-style: disc;
}

.docker-command {
margin: 0.25rem 0 0;
padding: 0.5rem 0.75rem;
border-radius: 0.5rem;
background: rgba(128, 128, 128, 0.12);
white-space: pre-wrap;
overflow-wrap: anywhere;
user-select: all;
font-size: 0.75rem;
}

.package-log {
max-height: 14rem;
overflow: auto;
Expand Down
Loading
Loading