Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
1d6401c
feat(software-center): add automatic updates confirmation modal
stephdl Jul 27, 2026
c972ee4
feat(software-center): reword strings for applications and automatic …
stephdl Jul 27, 2026
f7350e1
feat(software-center): restructure the updates tab
stephdl Jul 27, 2026
834af3c
feat(applications): expose per-application automatic updates opt-out
stephdl Jul 27, 2026
7a04004
feat(software-center): expose automatic updates opt-out on app instances
stephdl Jul 27, 2026
5eadf29
fix(shell): avoid redundant navigation from the side menu
stephdl Jul 27, 2026
1a561a2
fix(ui): align overflow menu icon with flex wrapper
stephdl Aug 6, 2026
1efb4c9
fixup! feat(software-center): add automatic updates confirmation modal
stephdl Aug 25, 2026
c8e29ff
fixup! feat(software-center): restructure the updates tab
stephdl Aug 25, 2026
cd44182
fixup! feat(applications): expose per-application automatic updates o…
stephdl Aug 25, 2026
031d4b9
fixup! feat(software-center): expose automatic updates opt-out on app…
stephdl Aug 25, 2026
666a43b
Revert "fix(ui): align overflow menu icon with flex wrapper"
stephdl Aug 25, 2026
204edc3
feat(applications): bring the automatic updates controls to Applications
stephdl Aug 27, 2026
6874ca8
style(applications): align the toolbar button with Software center
stephdl Aug 27, 2026
ed6177b
Revert "style(applications): align the toolbar button with Software c…
stephdl Aug 27, 2026
32e220b
fix(applications): show the logo shipped with the app
stephdl Aug 27, 2026
d5a1460
fix(app-drawer): refresh it after an uninstall, a clone or a move
stephdl Aug 27, 2026
d299298
fixup! fix(applications): show the logo shipped with the app
stephdl Aug 27, 2026
a4de55a
Revert "fixup! fix(applications): show the logo shipped with the app"
stephdl Aug 27, 2026
3750656
Revert "fix(applications): show the logo shipped with the app"
stephdl Aug 27, 2026
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
76 changes: 48 additions & 28 deletions core/ui/public/i18n/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,7 @@
"list-modules": "List applications",
"list-installed-modules": "List installed applications",
"list-updates": "List updates",
"set-automatic-updates": "Set automatic updates",
"get-cluster-status": "Get cluster status",
"get-defaults": "Get defaults",
"create-cluster": "Create cluster",
Expand Down Expand Up @@ -1013,10 +1014,8 @@
},
"software_center": {
"title": "Software center",
"title_tooltip": "Discover, install and update applications in your cluster. To manage installed applications, go to {applications} page",
"go_to_applications_center": "Go to applications center",
"search_placeholder": "Search applications",
"software_updates": "Updates",
"software_updates": "Updates available",
"you_have_updates": "You have {numUpdates} application to update | You have {numUpdates} applications to update",
"testing_warning_title": "Testing repositories enabled",
"testing_warning_description": "{repos} software repository includes non-stable versions of applications | The following software repositories include non-stable versions of applications: {repos}",
Expand All @@ -1036,7 +1035,7 @@
"update_all_apps": "Update all applications",
"apps_update_will_start_in_a_moment": "Applications update will start in a moment...",
"core_update_will_start_in_a_moment": "Core update will start in a moment...",
"instances_update_will_start_in_a_moment": "Instances update will start in a moment...",
"instances_update_will_start_in_a_moment": "Applications update will start in a moment...",
"add_to_favorites": "Add to favorites",
"remove_from_favorites": "Remove from favorites",
"details": "Details",
Expand All @@ -1060,26 +1059,26 @@
"other_files_remain_on_default_volume": "Other application files remain on the default volume",
"installing_on_node": "Installing on {node}",
"instance_installed_on_node": "{module_id} installed on {node}",
"app_instances": "{app} instances",
"app_instances_no_name": "Application instances",
"app_instances": "{app} applications",
"app_instances_no_name": "Applications",
"app_uninstallation": "Uninstall {app}",
"instance_uninstallation": "Uninstall {instance}",
"about_to_uninstall_instance": "{instance} will be uninstalled from the cluster",
"uninstall_instance": "Uninstall instance",
"uninstall_instance": "Uninstall application",
"uninstalling": "Uninstalling",
"no_instance_installed": "No instance installed",
"instance": "Instance",
"instance_label": "Application instance label",
"no_instance_installed": "No applications installed",
"instance": "Application",
"instance_label": "Application label",
"instance_label_tooltip": "You can give the application a memorable label to help recall its name.",
"edit_instance_label": "Edit instance label",
"edit_instance_label": "Edit application label",
"app_managed_in": "This application is managed in",
"reload_software_repositories": "Reload repositories",
"clone": "Clone",
"move": "Move",
"clone_app": "Clone application",
"move_app": "Move application",
"clone_app_description": "Clone {instanceLabel} instance and data to any node. Current instance will be left untouched.",
"move_app_description": "Move {instanceLabel} instance and data to a different node.",
"clone_app_description": "Clone {instanceLabel} application and data to any node. Current application will be left untouched.",
"move_app_description": "Move {instanceLabel} application and data to a different node.",
"select_destination_node": "Select destination node",
"current_node": "Current node",
"cloning_app_to_node": "Cloning {instance} to {node}",
Expand All @@ -1102,26 +1101,47 @@
"core_app_update_available": "Core update available",
"core_app_update_available_description": "Some core applications need to be updated",
"version": "Version",
"id": "Instances",
"id": "Applications",
"node_id": "Node",
"core_update": "Core update",
"app_updates": "Application updates",
"core_update_description": "Core updates help keep the cluster secure, stable, and up to date.",
"core_updates_available_description": "Core updates are available.",
"core_and_app_updates_available": "Core updates and {numUpdates} application update are available. | Core updates and {numUpdates} application updates are available.",
"no_updates_available": "No updates are currently available.",
"review_and_update": "Review and update",
"core_apps": "Core applications",
"update_all_instances": "Update all instances",
"update_app_instances": "Update {app} instances",
"updating_n_instances_c": "Updating {num} instance | Updating {num} instances",
"num_instances_updated": "{numInstances} instances updated",
"n_instances_will_be_updated_c": "Update of {num} instance will start in a moment... | Update of {num} instances will start in a moment...",
"applications_update": "Applications update",
"applications_update_description": "Application updates are installed automatically. Individual applications can be excluded from automatic updates.",
"automatic_updates_disabled_title": "Automatic updates disabled",
"automatic_updates_disabled_description": "Regular updates help protect applications, core components, and operating system packages from security and stability issues.",
"enable_automatic_updates": "Enable automatic updates",
"enable_automatic_updates_description1": "Automatic updates will be enabled for applications, core components, and operating system packages in this cluster.",
"enable_automatic_updates_description2": "Applications previously excluded from automatic updates will remain so.",
"disable_automatic_updates": "Disable automatic updates",
"disable_automatic_updates_description1": "Automatic updates will be disabled for all applications, core components, and operating system packages in this cluster.",
"disable_automatic_updates_description2": "Disabling automatic updates may expose the cluster to security issues and reduced stability. Ensure updates are reviewed and installed manually on a regular basis.",
"enable_instance_automatic_updates_description1": "{instance} will be included in automatic application updates.",
"enable_instance_automatic_updates_description2": "Updates will be installed automatically as soon as they are available.",
"disable_instance_automatic_updates_description1": "{instance} will be excluded from automatic application updates.",
"disable_instance_automatic_updates_description2": "You can still update it manually.",
"go_to_updates": "Go to Updates",
"go_to_core_applications": "Go to Core applications",
"go_to_software_repositories": "Go to Software repositories",
"updates_disabled": "Updates disabled",
"update_all_instances": "Update all applications",
"update_app_instances": "Update {app} applications",
"updating_n_instances_c": "Updating {num} application | Updating {num} applications",
"num_instances_updated": "{numInstances} applications updated",
"n_instances_will_be_updated_c": "Update of {num} application will start in a moment... | Update of {num} applications will start in a moment...",
"version_version_available": "Version {version} available",
"updates_disabled_ns7_migration_title": "Updates are suspended",
"updates_disabled_ns7_migration_description": "There is an ongoing NS7 migration. Updates will resume once the migration is complete.",
"app_restart": "Restart the instance {app}",
"app_restart": "Restart the application {app}",
"restart_app": "Restarting the application {name} will interrupt its operation and may cause downtime. Proceed only if maintenance is needed and users are aware.",
"restart_instance": "Restart instance",
"restart_instance": "Restart application",
"restart_module_warning": "Proceed only if maintenance is needed and users are aware.",
"restarting":" Restarting...",
"restart_instance_name": "Restart instance {instance}",
"restart_instance_name": "Restart application {instance}",
"restart_application": "Restart application",
"app_categories": {
"core": "Core",
Expand Down Expand Up @@ -1152,20 +1172,20 @@
"level_5_description": "Application developed, certified and supported by Nethesis",
"update_to_stable_version": "Update to stable version",
"update_to_testing_version": "Update to testing version",
"num_instances_installed": "{num} instance installed | {num} instances installed",
"reason_max_per_node_limit": "Limit of {param} instance reached | Limit of {param} instances reached",
"num_instances_installed": "{num} application installed | {num} applications installed",
"reason_max_per_node_limit": "Limit of {param} application reached | Limit of {param} applications reached",
"reason_min_core_requirement": "Core version is lower than {param}",
"search_core_apps": "Search core applications",
"no_node_eligible_for_app_installation": "No node is eligible for application installation",
"no_node_eligible_for_instance_cloning": "No node is eligible for instance cloning",
"no_node_eligible_for_instance_migration": "No node is eligible for instance migration",
"no_node_eligible_for_instance_cloning": "No node is eligible for application cloning",
"no_node_eligible_for_instance_migration": "No node is eligible for application migration",
"node_offline": "Node is offline",
"no_version_reason": {
"no_app_version": "No stable release of {app_name} is available in the '{app_repository}' repository",
"core_version_too_low": "Update core to version {core_min} or higher to install this application",
"rootfull_certification_level_too_low": "Applications with administrative privileges must have a minimum certification level of {certification_min}/5 to be installed"
},
"migrating_app_title": "This application instance is undergoing migration",
"migrating_app_title": "This application is undergoing migration",
"migrating_app_description": "You'll be able to use {name} again once the migration from NethServer 7 is complete.",
"additional_storage_available": "Additional volumes available",
"volume_usage": "{available} free of {total} ({percentage}% used)",
Expand Down
5 changes: 4 additions & 1 deletion core/ui/src/components/shell/SideMenuContent.vue
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,10 @@ export default {
return this.$route.path.includes(path);
},
goTo(path) {
this.$router.push(path);
// avoid NavigationDuplicated when clicking the already active menu item
if (this.$route.fullPath !== path) {
this.$router.push(path);
}
this.setMobileSideMenuShownInStore(false);
},
},
Expand Down
187 changes: 187 additions & 0 deletions core/ui/src/components/software-center/AutomaticUpdatesModal.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
<!--
Copyright (C) 2026 Nethesis S.r.l.
SPDX-License-Identifier: GPL-3.0-or-later
-->
<template>
<NsModal
size="default"
:visible="visible"
:isLoading="loading"
:primaryButtonDisabled="loading"
autoHideOff
v-on:modal-hide-request="onModalHideRequest"
@secondary-click="onModalHideRequest"
@primary-click="applyAutomaticUpdates"
>
<template slot="title">
{{ title }}
</template>
<template slot="content">
<p class="mg-bottom-md">
{{ description1 }}
</p>
<p>
{{ description2 }}
</p>
<div v-if="error.setAutomaticUpdates" class="mg-top-md">
<NsInlineNotification
kind="error"
:title="$t('action.set-automatic-updates')"
:description="error.setAutomaticUpdates"
:showCloseButton="false"
/>
</div>
</template>
<template slot="secondary-button">{{ $t("common.cancel") }}</template>
<template slot="primary-button">{{
pendingEnable ? $t("common.enable") : $t("common.disable")
}}</template>
</NsModal>
</template>
<script>
import to from "await-to-js";
import { UtilService, TaskService } from "@nethserver/ns8-ui-lib";

export default {
name: "AutomaticUpdatesModal",
mixins: [UtilService, TaskService],
props: {
visible: Boolean,
enable: Boolean,
// when set, automatic updates are toggled for this instance only
instance: {
type: Object,
default: null,
},
},
data() {
return {
loading: false,
// frozen at open: the caller flips its own state while the modal closes
pendingEnable: false,
error: {
setAutomaticUpdates: "",
},
};
},
computed: {
title() {
return this.pendingEnable
? this.$t("software_center.enable_automatic_updates")
: this.$t("software_center.disable_automatic_updates");
},
instanceName() {
if (!this.instance) {
return "";
}
return this.instance.ui_name || this.instance.id;
},
description1() {
if (this.instance) {
return this.pendingEnable
? this.$t(
"software_center.enable_instance_automatic_updates_description1",
{ instance: this.instanceName }
)
: this.$t(
"software_center.disable_instance_automatic_updates_description1",
{ instance: this.instanceName }
);
}
return this.pendingEnable
? this.$t("software_center.enable_automatic_updates_description1")
: this.$t("software_center.disable_automatic_updates_description1");
},
description2() {
if (this.instance) {
return this.pendingEnable
? this.$t(
"software_center.enable_instance_automatic_updates_description2"
)
: this.$t(
"software_center.disable_instance_automatic_updates_description2"
);
}
return this.pendingEnable
? this.$t("software_center.enable_automatic_updates_description2")
: this.$t("software_center.disable_automatic_updates_description2");
},
},
watch: {
visible(newVal) {
if (newVal) {
this.error.setAutomaticUpdates = "";
this.pendingEnable = this.enable;
}
},
},
methods: {
async applyAutomaticUpdates() {
this.error.setAutomaticUpdates = "";
this.loading = true;
const data = this.instance
? { instances: { [this.instance.id]: this.pendingEnable } }
: { apply_updates_is_active: this.pendingEnable };
const taskAction = "set-automatic-updates";
const eventId = this.getUuid();

this.$root.$once(
`${taskAction}-completed-${eventId}`,
this.setAutomaticUpdatesCompleted
);
this.$root.$once(
`${taskAction}-aborted-${eventId}`,
this.setAutomaticUpdatesAborted
);
this.$root.$once(
`${taskAction}-validation-failed-${eventId}`,
this.setAutomaticUpdatesValidationFailed
);

const res = await to(
this.createClusterTask({
action: taskAction,
data,
extra: {
title: this.title,
eventId,
},
})
);
const err = res[0];
if (err) {
console.error(`error creating task ${taskAction}`, err);
this.error.setAutomaticUpdates = this.getErrorMessage(err);
this.loading = false;
}
},
setAutomaticUpdatesAborted(taskResult, taskContext) {
console.error(`${taskContext.action} aborted`, taskResult);
this.error.setAutomaticUpdates = this.$t("error.generic_error");
this.loading = false;
},
setAutomaticUpdatesValidationFailed(validationErrors) {
console.error(
"set-automatic-updates validation failed",
validationErrors
);
this.error.setAutomaticUpdates = this.$t("error.generic_error");
this.loading = false;
},
setAutomaticUpdatesCompleted() {
this.loading = false;
// close before the caller flips its state
this.$emit("hide");
this.$emit("completed", this.pendingEnable);
},
onModalHideRequest() {
// stay open while the task runs, so a failure stays visible
if (this.loading) {
return;
}
this.error.setAutomaticUpdates = "";
this.$emit("hide");
},
},
};
</script>
Original file line number Diff line number Diff line change
Expand Up @@ -674,6 +674,9 @@ export default {

// reload instances and highlight cloned/moved instance
this.$emit("cloneOrMoveCompleted", newModuleId);

// a clone adds an app, a move changes its node: the drawer lists both
this.$root.$emit("reloadAppDrawer");
},
onSelectNode(selectedNode) {
this.selectedNode = selectedNode;
Expand Down
Loading
Loading