diff --git a/src/assets/lang/en_US.json b/src/assets/lang/en_US.json index cdf3b5d8..41063ced 100644 --- a/src/assets/lang/en_US.json +++ b/src/assets/lang/en_US.json @@ -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", @@ -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?" } diff --git a/src/assets/lang/fr_FR.json b/src/assets/lang/fr_FR.json index 7bf63680..301e6f79 100644 --- a/src/assets/lang/fr_FR.json +++ b/src/assets/lang/fr_FR.json @@ -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é", @@ -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 ?" } diff --git a/src/components/settings/SystemPackageUpdateModal.spec.js b/src/components/settings/SystemPackageUpdateModal.spec.js index 4d5f317e..e25db217 100644 --- a/src/components/settings/SystemPackageUpdateModal.spec.js +++ b/src/components/settings/SystemPackageUpdateModal.spec.js @@ -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 } }) @@ -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 }, }, }) @@ -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) + }) +}) diff --git a/src/components/settings/SystemPackageUpdateModal.vue b/src/components/settings/SystemPackageUpdateModal.vue index 84954f65..48b95520 100644 --- a/src/components/settings/SystemPackageUpdateModal.vue +++ b/src/components/settings/SystemPackageUpdateModal.vue @@ -29,7 +29,7 @@
{{ dockerOrigin }}
+ +{{ $t('Docker has updates. This update leaves it alone.') }}
++ {{ $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.') }} +
+ +{{ $t('Docker is up to date.') }}
+{{ dockerUnseen }}
+ +{{ $t('In a terminal on this machine:') }}
+{{ info.docker.manual_command }}
+
+ {{ $t('ReCasaOS does not know how Docker was installed here, so it shows no command.') }}
+{{ $t(isReconciliationPending ? 'Finishing system package update...' : 'Applying system package updates...') }} @@ -67,7 +93,7 @@
+
{{ status.error }}
@@ -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() @@ -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?')}