From ce5ca8c682de777a0652a08c6892ba24317a3d24 Mon Sep 17 00:00:00 2001 From: Softer Date: Fri, 24 Apr 2026 18:26:06 +0300 Subject: [PATCH 1/2] Show install summary when configuration is valid Previously the Install menu preview was empty when everything was valid, leaving the user with no "ready" signal. Now show "Ready to install" plus a two-column summary of the current configuration. - New _install_summary() composes an aligned key/value table with rows for disks+FS+LUKS, bootloader, kernel, profile, greeter, package count, network, locale and timezone. Column width adapts to the longest translated label so translations keep the alignment. - Rows whose underlying config is not set are skipped rather than rendered as empty. - base.pot / uk base.po: add new msgids for summary labels. --- archinstall/lib/global_menu.py | 69 +++++++++++++++++++++- archinstall/locales/base.pot | 15 +++++ archinstall/locales/uk/LC_MESSAGES/base.po | 15 +++++ 3 files changed, 98 insertions(+), 1 deletion(-) diff --git a/archinstall/lib/global_menu.py b/archinstall/lib/global_menu.py index 3d94f31e2b..a36440910e 100644 --- a/archinstall/lib/global_menu.py +++ b/archinstall/lib/global_menu.py @@ -498,6 +498,70 @@ def _validate_bootloader(self) -> str | None: return None + def _install_summary(self) -> str: + """ + Render a concise two-column summary of the current install configuration. + + The left column holds section labels, the right column holds values. + Column width adapts to the longest translated label so translations + do not break the alignment. Rows whose underlying config is not set + are skipped. + + Returns an empty string if nothing meaningful to show. + """ + rows: list[tuple[str, str]] = [] + + disk_config: DiskLayoutConfiguration | None = self._item_group.find_by_key('disk_config').value + if disk_config and disk_config.device_modifications: + disk_parts: list[str] = [] + for mod in disk_config.device_modifications: + path = str(mod.device_path) + root_part = mod.get_root_partition() + flags: list[str] = [] + if root_part and root_part.fs_type: + flags.append(root_part.fs_type.value) + if disk_config.disk_encryption: + flags.append(tr('LUKS')) + disk_parts.append(f'{path} ({" + ".join(flags)})' if flags else path) + rows.append((tr('Disks'), ', '.join(disk_parts))) + + bl_config: BootloaderConfiguration | None = self._item_group.find_by_key('bootloader_config').value + if bl_config and bl_config.bootloader != Bootloader.NO_BOOTLOADER: + rows.append((tr('Bootloader'), bl_config.bootloader.value)) + + kernels: list[str] | None = self._item_group.find_by_key('kernels').value + if kernels: + rows.append((tr('Kernel'), ', '.join(kernels))) + + profile_config: ProfileConfiguration | None = self._item_group.find_by_key('profile_config').value + if profile_config and profile_config.profile: + names = profile_config.profile.current_selection_names() + rows.append((tr('Profile'), ', '.join(names) if names else profile_config.profile.name)) + if profile_config.greeter: + rows.append((tr('Greeter'), profile_config.greeter.value)) + + packages: list[str] | None = self._item_group.find_by_key('packages').value + if packages: + rows.append((tr('Packages'), str(len(packages)))) + + net_config = self._item_group.find_by_key('network_config').value + if isinstance(net_config, NetworkConfiguration): + rows.append((tr('Network'), net_config.type.display_msg())) + + locale_config: LocaleConfiguration | None = self._item_group.find_by_key('locale_config').value + if locale_config: + rows.append((tr('Locale'), locale_config.sys_lang)) + + tz = self._item_group.find_by_key('timezone').value + if tz: + rows.append((tr('Timezone'), tz)) + + if not rows: + return '' + + label_width = max(len(label) for label, _ in rows) + 2 + return '\n'.join(f'{label:<{label_width}}{value}' for label, value in rows) + def _prev_install_invalid_config(self, item: MenuItem) -> str | None: if missing := self._missing_configs(): text = tr('Missing configurations:\n') @@ -508,7 +572,10 @@ def _prev_install_invalid_config(self, item: MenuItem) -> str | None: if error := self._validate_bootloader(): return tr(f'Invalid configuration: {error}') - return None + summary = self._install_summary() + if summary: + return f'{tr("Ready to install")}\n\n{summary}' + return tr('Ready to install') def _prev_profile(self, item: MenuItem) -> str | None: profile_config: ProfileConfiguration | None = item.value diff --git a/archinstall/locales/base.pot b/archinstall/locales/base.pot index f707ec7a18..54fea2a7fd 100644 --- a/archinstall/locales/base.pot +++ b/archinstall/locales/base.pot @@ -1245,6 +1245,21 @@ msgstr "" msgid "Invalid configuration: {error}" msgstr "" +msgid "Ready to install" +msgstr "" + +msgid "Disks" +msgstr "" + +msgid "Packages" +msgstr "" + +msgid "Network" +msgstr "" + +msgid "Locale" +msgstr "" + msgid "Type" msgstr "" diff --git a/archinstall/locales/uk/LC_MESSAGES/base.po b/archinstall/locales/uk/LC_MESSAGES/base.po index a178ac2d47..583756ed3d 100644 --- a/archinstall/locales/uk/LC_MESSAGES/base.po +++ b/archinstall/locales/uk/LC_MESSAGES/base.po @@ -1219,6 +1219,21 @@ msgstr "Модель" msgid "Invalid configuration: {error}" msgstr "Неправильна конфігурація: {error}" +msgid "Ready to install" +msgstr "Готово до встановлення" + +msgid "Disks" +msgstr "Диски" + +msgid "Packages" +msgstr "Пакети" + +msgid "Network" +msgstr "Мережа" + +msgid "Locale" +msgstr "Локаль" + msgid "Type" msgstr "Тип" From bd6fc8673225da56eb5fe07d489f46fb6dd90879 Mon Sep 17 00:00:00 2001 From: Softer Date: Sun, 26 Apr 2026 17:20:20 +0300 Subject: [PATCH 2/2] Move install summary into ConfigurationOutput The summary helper that renders the install preview's two-column configuration overview lived in GlobalMenu. Move it to ConfigurationOutput.as_summary() so it sits alongside the JSON output methods that share the same role. The preview now syncs menu state to ArchConfig and delegates rendering. --- archinstall/lib/configuration.py | 66 ++++++++++++++++++++++++++++++ archinstall/lib/global_menu.py | 69 ++------------------------------ 2 files changed, 69 insertions(+), 66 deletions(-) diff --git a/archinstall/lib/configuration.py b/archinstall/lib/configuration.py index 39d39511a8..0f22190e0d 100644 --- a/archinstall/lib/configuration.py +++ b/archinstall/lib/configuration.py @@ -10,6 +10,8 @@ from archinstall.lib.crypt import encrypt from archinstall.lib.menu.helpers import Confirmation, Selection from archinstall.lib.menu.util import get_password, prompt_dir +from archinstall.lib.models.bootloader import Bootloader +from archinstall.lib.models.network import NetworkConfiguration from archinstall.lib.output import debug, logger, warn from archinstall.lib.translationhandler import tr from archinstall.tui.ui.menu_item import MenuItem, MenuItemGroup @@ -58,6 +60,70 @@ def write_debug(self) -> None: debug(' -- Chosen configuration --') debug(self.user_config_to_json()) + def as_summary(self) -> str: + """ + Render a concise two-column summary of the current configuration. + + The left column holds section labels, the right column holds values. + Column width adapts to the longest translated label so translations + do not break the alignment. Rows whose underlying config is not set + are skipped. + + Returns an empty string if nothing meaningful to show. + """ + rows: list[tuple[str, str]] = [] + + disk_config = self._config.disk_config + if disk_config and disk_config.device_modifications: + disk_parts: list[str] = [] + for mod in disk_config.device_modifications: + path = str(mod.device_path) + root_part = mod.get_root_partition() + flags: list[str] = [] + if root_part and root_part.fs_type: + flags.append(root_part.fs_type.value) + if disk_config.disk_encryption: + flags.append(tr('LUKS')) + disk_parts.append(f'{path} ({" + ".join(flags)})' if flags else path) + rows.append((tr('Disks'), ', '.join(disk_parts))) + + bl_config = self._config.bootloader_config + if bl_config and bl_config.bootloader != Bootloader.NO_BOOTLOADER: + rows.append((tr('Bootloader'), bl_config.bootloader.value)) + + kernels = self._config.kernels + if kernels: + rows.append((tr('Kernel'), ', '.join(kernels))) + + profile_config = self._config.profile_config + if profile_config and profile_config.profile: + names = profile_config.profile.current_selection_names() + rows.append((tr('Profile'), ', '.join(names) if names else profile_config.profile.name)) + if profile_config.greeter: + rows.append((tr('Greeter'), profile_config.greeter.value)) + + packages = self._config.packages + if packages: + rows.append((tr('Packages'), str(len(packages)))) + + net_config = self._config.network_config + if isinstance(net_config, NetworkConfiguration): + rows.append((tr('Network'), net_config.type.display_msg())) + + locale_config = self._config.locale_config + if locale_config: + rows.append((tr('Locale'), locale_config.sys_lang)) + + tz = self._config.timezone + if tz: + rows.append((tr('Timezone'), tz)) + + if not rows: + return '' + + label_width = max(len(label) for label, _ in rows) + 2 + return '\n'.join(f'{label:<{label_width}}{value}' for label, value in rows) + async def confirm_config(self) -> bool: header = f'{tr("The specified configuration will be applied")}. ' header += tr('Would you like to continue?') + '\n' diff --git a/archinstall/lib/global_menu.py b/archinstall/lib/global_menu.py index a36440910e..d22a5ebedb 100644 --- a/archinstall/lib/global_menu.py +++ b/archinstall/lib/global_menu.py @@ -6,7 +6,7 @@ from archinstall.lib.authentication.authentication_menu import AuthenticationMenu from archinstall.lib.bootloader.bootloader_menu import BootloaderMenu from archinstall.lib.bootloader.utils import validate_bootloader_layout -from archinstall.lib.configuration import save_config +from archinstall.lib.configuration import ConfigurationOutput, save_config from archinstall.lib.disk.disk_menu import DiskLayoutConfigurationMenu from archinstall.lib.general.general_menu import select_hostname, select_ntp, select_timezone from archinstall.lib.general.system_menu import select_kernel, select_swap @@ -498,70 +498,6 @@ def _validate_bootloader(self) -> str | None: return None - def _install_summary(self) -> str: - """ - Render a concise two-column summary of the current install configuration. - - The left column holds section labels, the right column holds values. - Column width adapts to the longest translated label so translations - do not break the alignment. Rows whose underlying config is not set - are skipped. - - Returns an empty string if nothing meaningful to show. - """ - rows: list[tuple[str, str]] = [] - - disk_config: DiskLayoutConfiguration | None = self._item_group.find_by_key('disk_config').value - if disk_config and disk_config.device_modifications: - disk_parts: list[str] = [] - for mod in disk_config.device_modifications: - path = str(mod.device_path) - root_part = mod.get_root_partition() - flags: list[str] = [] - if root_part and root_part.fs_type: - flags.append(root_part.fs_type.value) - if disk_config.disk_encryption: - flags.append(tr('LUKS')) - disk_parts.append(f'{path} ({" + ".join(flags)})' if flags else path) - rows.append((tr('Disks'), ', '.join(disk_parts))) - - bl_config: BootloaderConfiguration | None = self._item_group.find_by_key('bootloader_config').value - if bl_config and bl_config.bootloader != Bootloader.NO_BOOTLOADER: - rows.append((tr('Bootloader'), bl_config.bootloader.value)) - - kernels: list[str] | None = self._item_group.find_by_key('kernels').value - if kernels: - rows.append((tr('Kernel'), ', '.join(kernels))) - - profile_config: ProfileConfiguration | None = self._item_group.find_by_key('profile_config').value - if profile_config and profile_config.profile: - names = profile_config.profile.current_selection_names() - rows.append((tr('Profile'), ', '.join(names) if names else profile_config.profile.name)) - if profile_config.greeter: - rows.append((tr('Greeter'), profile_config.greeter.value)) - - packages: list[str] | None = self._item_group.find_by_key('packages').value - if packages: - rows.append((tr('Packages'), str(len(packages)))) - - net_config = self._item_group.find_by_key('network_config').value - if isinstance(net_config, NetworkConfiguration): - rows.append((tr('Network'), net_config.type.display_msg())) - - locale_config: LocaleConfiguration | None = self._item_group.find_by_key('locale_config').value - if locale_config: - rows.append((tr('Locale'), locale_config.sys_lang)) - - tz = self._item_group.find_by_key('timezone').value - if tz: - rows.append((tr('Timezone'), tz)) - - if not rows: - return '' - - label_width = max(len(label) for label, _ in rows) + 2 - return '\n'.join(f'{label:<{label_width}}{value}' for label, value in rows) - def _prev_install_invalid_config(self, item: MenuItem) -> str | None: if missing := self._missing_configs(): text = tr('Missing configurations:\n') @@ -572,7 +508,8 @@ def _prev_install_invalid_config(self, item: MenuItem) -> str | None: if error := self._validate_bootloader(): return tr(f'Invalid configuration: {error}') - summary = self._install_summary() + self.sync_all_to_config() + summary = ConfigurationOutput(self._arch_config).as_summary() if summary: return f'{tr("Ready to install")}\n\n{summary}' return tr('Ready to install')