Enhance view (#1210)

* Add preview for menu entries

* Fix mypy

* Update

* Update

* Fix mypy

Co-authored-by: Daniel Girtler <girtler.daniel@gmail.com>
This commit is contained in:
Daniel Girtler 2022-05-18 21:59:49 +10:00 committed by GitHub
parent 089c46db4a
commit 65a5a335aa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 110 additions and 47 deletions

View File

@ -15,4 +15,4 @@ jobs:
# one day this will be enabled # one day this will be enabled
# run: mypy --strict --module archinstall || exit 0 # run: mypy --strict --module archinstall || exit 0
- name: run mypy - name: run mypy
run: mypy --follow-imports=skip archinstall/lib/menu/selection_menu.py archinstall/lib/models/network_configuration.py archinstall/lib/menu/list_manager.py archinstall/lib/user_interaction/network_conf.py run: mypy --follow-imports=silent archinstall/lib/menu/selection_menu.py archinstall/lib/menu/global_menu.py archinstall/lib/models/network_configuration.py archinstall/lib/menu/list_manager.py archinstall/lib/user_interaction/network_conf.py

View File

@ -39,6 +39,24 @@ class BlockDevice:
def _str_repr(self) -> str: def _str_repr(self) -> str:
return f"BlockDevice({self.device_or_backfile}, size={self._safe_size}GB, free_space={self._safe_free_space}, bus_type={self.bus_type})" return f"BlockDevice({self.device_or_backfile}, size={self._safe_size}GB, free_space={self._safe_free_space}, bus_type={self.bus_type})"
@cached_property
def display_info(self) -> str:
columns = {
str(_('Device')): self.device_or_backfile,
str(_('Size')): f'{self._safe_size}GB',
str(_('Free space')): f'{self._safe_free_space}',
str(_('Bus-type')): f'{self.bus_type}'
}
padding = max([len(k) for k in columns.keys()])
pretty = ''
for k, v in columns.items():
k = k.ljust(padding, ' ')
pretty += f'{k} = {v}\n'
return pretty.rstrip()
def __iter__(self) -> Iterator[Partition]: def __iter__(self) -> Iterator[Partition]:
for partition in self.partitions: for partition in self.partitions:
yield self.partitions[partition] yield self.partitions[partition]
@ -79,7 +97,7 @@ class BlockDevice:
for device in output['blockdevices']: for device in output['blockdevices']:
return device['pttype'] return device['pttype']
@property @cached_property
def device_or_backfile(self) -> str: def device_or_backfile(self) -> str:
""" """
Returns the actual device-endpoint of the BlockDevice. Returns the actual device-endpoint of the BlockDevice.
@ -162,7 +180,7 @@ class BlockDevice:
from .filesystem import GPT from .filesystem import GPT
return GPT return GPT
@property @cached_property
def uuid(self) -> str: def uuid(self) -> str:
log('BlockDevice().uuid is untested!', level=logging.WARNING, fg='yellow') log('BlockDevice().uuid is untested!', level=logging.WARNING, fg='yellow')
""" """
@ -172,7 +190,7 @@ class BlockDevice:
""" """
return SysCommand(f'blkid -s PTUUID -o value {self.path}').decode('UTF-8') return SysCommand(f'blkid -s PTUUID -o value {self.path}').decode('UTF-8')
@property @cached_property
def _safe_size(self) -> float: def _safe_size(self) -> float:
from .helpers import convert_size_to_gb from .helpers import convert_size_to_gb
@ -184,7 +202,7 @@ class BlockDevice:
for device in output['blockdevices']: for device in output['blockdevices']:
return convert_size_to_gb(device['size']) return convert_size_to_gb(device['size'])
@property @cached_property
def size(self) -> float: def size(self) -> float:
from .helpers import convert_size_to_gb from .helpers import convert_size_to_gb
@ -193,28 +211,28 @@ class BlockDevice:
for device in output['blockdevices']: for device in output['blockdevices']:
return convert_size_to_gb(device['size']) return convert_size_to_gb(device['size'])
@property @cached_property
def bus_type(self) -> str: def bus_type(self) -> str:
output = json.loads(SysCommand(f"lsblk --json -o+ROTA,TRAN {self.path}").decode('UTF-8')) output = json.loads(SysCommand(f"lsblk --json -o+ROTA,TRAN {self.path}").decode('UTF-8'))
for device in output['blockdevices']: for device in output['blockdevices']:
return device['tran'] return device['tran']
@property @cached_property
def spinning(self) -> bool: def spinning(self) -> bool:
output = json.loads(SysCommand(f"lsblk --json -o+ROTA,TRAN {self.path}").decode('UTF-8')) output = json.loads(SysCommand(f"lsblk --json -o+ROTA,TRAN {self.path}").decode('UTF-8'))
for device in output['blockdevices']: for device in output['blockdevices']:
return device['rota'] is True return device['rota'] is True
@property @cached_property
def _safe_free_space(self) -> Tuple[str, ...]: def _safe_free_space(self) -> Tuple[str, ...]:
try: try:
return '+'.join(part[2] for part in self.free_space) return '+'.join(part[2] for part in self.free_space)
except SysCallError: except SysCallError:
return '?' return '?'
@property @cached_property
def free_space(self) -> Tuple[str, ...]: def free_space(self) -> Tuple[str, ...]:
# NOTE: parted -s will default to `cancel` on prompt, skipping any partition # NOTE: parted -s will default to `cancel` on prompt, skipping any partition
# that is "outside" the disk. in /dev/sr0 this is usually the case with Archiso, # that is "outside" the disk. in /dev/sr0 this is usually the case with Archiso,
@ -228,7 +246,7 @@ class BlockDevice:
except SysCallError as error: except SysCallError as error:
log(f"Could not get free space on {self.path}: {error}", level=logging.DEBUG) log(f"Could not get free space on {self.path}: {error}", level=logging.DEBUG)
@property @cached_property
def largest_free_space(self) -> List[str]: def largest_free_space(self) -> List[str]:
info = [] info = []
for space_info in self.free_space: for space_info in self.free_space:
@ -240,7 +258,7 @@ class BlockDevice:
info = space_info info = space_info
return info return info
@property @cached_property
def first_free_sector(self) -> str: def first_free_sector(self) -> str:
if info := self.largest_free_space: if info := self.largest_free_space:
start = info[0] start = info[0]
@ -248,7 +266,7 @@ class BlockDevice:
start = '512MB' start = '512MB'
return start return start
@property @cached_property
def first_end_sector(self) -> str: def first_end_sector(self) -> str:
if info := self.largest_free_space: if info := self.largest_free_space:
end = info[1] end = info[1]

View File

@ -1,6 +1,6 @@
from __future__ import annotations from __future__ import annotations
from typing import Any, List, Optional, Union from typing import Any, List, Optional, Union, Dict, TYPE_CHECKING
import archinstall import archinstall
@ -33,10 +33,15 @@ from ..user_interaction import select_encrypted_partitions
from ..user_interaction import select_harddrives from ..user_interaction import select_harddrives
from ..user_interaction import select_profile from ..user_interaction import select_profile
from ..user_interaction import select_additional_repositories from ..user_interaction import select_additional_repositories
from ..user_interaction.partitioning_conf import current_partition_layout
if TYPE_CHECKING:
_: Any
class GlobalMenu(GeneralMenu): class GlobalMenu(GeneralMenu):
def __init__(self,data_store): def __init__(self,data_store):
super().__init__(data_store=data_store, auto_cursor=True) super().__init__(data_store=data_store, auto_cursor=True, preview_size=0.3)
def _setup_selection_menu_options(self): def _setup_selection_menu_options(self):
# archinstall.Language will not use preset values # archinstall.Language will not use preset values
@ -69,7 +74,10 @@ class GlobalMenu(GeneralMenu):
self._menu_options['harddrives'] = \ self._menu_options['harddrives'] = \
Selector( Selector(
_('Drive(s)'), _('Drive(s)'),
lambda preset: self._select_harddrives(preset)) lambda preset: self._select_harddrives(preset),
display_func=lambda x: f'{len(x)} ' + str(_('Drive(s)')) if x is not None and len(x) > 0 else '',
preview_func=self._prev_harddrives,
)
self._menu_options['disk_layouts'] = \ self._menu_options['disk_layouts'] = \
Selector( Selector(
_('Disk layout'), _('Disk layout'),
@ -78,6 +86,8 @@ class GlobalMenu(GeneralMenu):
storage['arguments'].get('harddrives', []), storage['arguments'].get('harddrives', []),
storage['arguments'].get('advanced', False) storage['arguments'].get('advanced', False)
), ),
preview_func=self._prev_disk_layouts,
display_func=lambda x: self._display_disk_layout(x),
dependencies=['harddrives']) dependencies=['harddrives'])
self._menu_options['!encryption-password'] = \ self._menu_options['!encryption-password'] = \
Selector( Selector(
@ -131,7 +141,8 @@ class GlobalMenu(GeneralMenu):
Selector( Selector(
_('Profile'), _('Profile'),
lambda preset: self._select_profile(preset), lambda preset: self._select_profile(preset),
display_func=lambda x: x if x else 'None') display_func=lambda x: x if x else 'None'
)
self._menu_options['audio'] = \ self._menu_options['audio'] = \
Selector( Selector(
_('Audio'), _('Audio'),
@ -189,7 +200,7 @@ class GlobalMenu(GeneralMenu):
def _update_install_text(self, name :str = None, result :Any = None): def _update_install_text(self, name :str = None, result :Any = None):
text = self._install_text() text = self._install_text()
self._menu_options.get('install').update_description(text) self._menu_options['install'].update_description(text)
def post_callback(self,name :str = None ,result :Any = None): def post_callback(self,name :str = None ,result :Any = None):
self._update_install_text(name, result) self._update_install_text(name, result)
@ -225,6 +236,35 @@ class GlobalMenu(GeneralMenu):
else: else:
return str(cur_value) return str(cur_value)
def _prev_harddrives(self) -> Optional[str]:
selector = self._menu_options['harddrives']
if selector.has_selection():
drives = selector.current_selection
return '\n\n'.join([d.display_info for d in drives])
return None
def _prev_disk_layouts(self) -> Optional[str]:
selector = self._menu_options['disk_layouts']
if selector.has_selection():
layouts: Dict[str, Dict[str, Any]] = selector.current_selection
output = ''
for device, layout in layouts.items():
output += f'{_("Device")}: {device}\n\n'
output += current_partition_layout(layout['partitions'], with_title=False)
output += '\n\n'
return output.rstrip()
return None
def _display_disk_layout(self, current_value: Optional[Dict[str, Any]]) -> str:
if current_value:
total_partitions = [entry['partitions'] for entry in current_value.values()]
total_nr = sum([len(p) for p in total_partitions])
return f'{total_nr} {_("Partitions")}'
return ''
def _prev_install_missing_config(self) -> Optional[str]: def _prev_install_missing_config(self) -> Optional[str]:
if missing := self._missing_configs(): if missing := self._missing_configs():
text = str(_('Missing configurations:\n')) text = str(_('Missing configurations:\n'))
@ -247,17 +287,17 @@ class GlobalMenu(GeneralMenu):
if not check('harddrives'): if not check('harddrives'):
missing += ['Hard drives'] missing += ['Hard drives']
if check('harddrives'): if check('harddrives'):
if not self._menu_options.get('harddrives').is_empty() and not check('disk_layouts'): if not self._menu_options['harddrives'].is_empty() and not check('disk_layouts'):
missing += ['Disk layout'] missing += ['Disk layout']
return missing return missing
def _set_root_password(self): def _set_root_password(self) -> Optional[str]:
prompt = str(_('Enter root password (leave blank to disable root): ')) prompt = str(_('Enter root password (leave blank to disable root): '))
password = get_password(prompt=prompt) password = get_password(prompt=prompt)
return password return password
def _select_encrypted_password(self): def _select_encrypted_password(self) -> Optional[str]:
if passwd := get_password(prompt=str(_('Enter disk encryption password (leave blank for no encryption): '))): if passwd := get_password(prompt=str(_('Enter disk encryption password (leave blank for no encryption): '))):
return passwd return passwd
else: else:
@ -271,7 +311,7 @@ class GlobalMenu(GeneralMenu):
return ntp return ntp
def _select_harddrives(self, old_harddrives : list) -> list: def _select_harddrives(self, old_harddrives : list) -> List:
harddrives = select_harddrives(old_harddrives) harddrives = select_harddrives(old_harddrives)
if len(harddrives) == 0: if len(harddrives) == 0:
@ -288,7 +328,7 @@ class GlobalMenu(GeneralMenu):
# in case the harddrives got changed we have to reset the disk layout as well # in case the harddrives got changed we have to reset the disk layout as well
if old_harddrives != harddrives: if old_harddrives != harddrives:
self._menu_options.get('disk_layouts').set_current_selection(None) self._menu_options['disk_layouts'].set_current_selection(None)
storage['arguments']['disk_layouts'] = {} storage['arguments']['disk_layouts'] = {}
return harddrives return harddrives
@ -340,11 +380,11 @@ class GlobalMenu(GeneralMenu):
return ret return ret
def _create_superuser_account(self): def _create_superuser_account(self) -> Optional[Dict[str, Dict[str, str]]]:
superusers = ask_for_superuser_account(str(_('Manage superuser accounts: '))) superusers = ask_for_superuser_account(str(_('Manage superuser accounts: ')))
return superusers if superusers else None return superusers if superusers else None
def _create_user_account(self): def _create_user_account(self) -> Dict[str, Dict[str, str | None]]:
users = ask_for_additional_users(str(_('Manage ordinary user accounts: '))) users = ask_for_additional_users(str(_('Manage ordinary user accounts: ')))
return users return users
@ -356,7 +396,7 @@ class GlobalMenu(GeneralMenu):
else: else:
return list(superusers.keys()) if superusers else '' return list(superusers.keys()) if superusers else ''
def _users_resynch(self): def _users_resynch(self) -> bool:
self.synch('!superusers') self.synch('!superusers')
self.synch('!users') self.synch('!users')
return False return False

View File

@ -89,7 +89,7 @@ from .text_input import TextInput
from .menu import Menu, MenuSelectionType from .menu import Menu, MenuSelectionType
from os import system from os import system
from copy import copy from copy import copy
from typing import Union, Any, TYPE_CHECKING, Dict from typing import Union, Any, TYPE_CHECKING, Dict, Optional
if TYPE_CHECKING: if TYPE_CHECKING:
_: Any _: Any
@ -147,7 +147,7 @@ class ListManager:
self.base_data = base_list self.base_data = base_list
self._data = copy(base_list) # as refs, changes are immediate self._data = copy(base_list) # as refs, changes are immediate
# default values for the null case # default values for the null case
self.target = None self.target: Optional[Any] = None
self.action = self._null_action self.action = self._null_action
if len(self._data) == 0 and self._null_action: if len(self._data) == 0 and self._null_action:

View File

@ -15,7 +15,7 @@ if TYPE_CHECKING:
_: Any _: Any
def select_archinstall_language(preset_value: str) -> Optional[str]: def select_archinstall_language(preset_value: str) -> Optional[Any]:
""" """
copied from user_interaction/general_conf.py as a temporary measure copied from user_interaction/general_conf.py as a temporary measure
""" """
@ -487,6 +487,8 @@ class GeneralMenu:
match choice.type_: match choice.type_:
case MenuSelectionType.Esc: return preset case MenuSelectionType.Esc: return preset
case MenuSelectionType.Selection: case MenuSelectionType.Selection:
return pathlib.Path(list(fido_devices.keys())[int(choice.value.split('|',1)[0])]) selection: Any = choice.value
index = int(selection.split('|',1)[0])
return pathlib.Path(list(fido_devices.keys())[index])
return None return None

View File

@ -142,7 +142,6 @@ def select_profile(preset) -> Optional[Profile]:
options[option] = profile options[option] = profile
title = _('This is a list of pre-programmed profiles, they might make it easier to install things like desktop environments') title = _('This is a list of pre-programmed profiles, they might make it easier to install things like desktop environments')
warning = str(_('Are you sure you want to reset this setting?')) warning = str(_('Are you sure you want to reset this setting?'))
selection = Menu( selection = Menu(

View File

@ -64,7 +64,7 @@ class ManualNetworkConfig(ListManager):
elif self.action == self._action_delete: elif self.action == self._action_delete:
del data[iface_name] del data[iface_name]
def _select_iface(self, existing_ifaces: List[str]) -> Optional[str]: def _select_iface(self, existing_ifaces: List[str]) -> Optional[Any]:
all_ifaces = list_interfaces().values() all_ifaces = list_interfaces().values()
available = set(all_ifaces) - set(existing_ifaces) available = set(all_ifaces) - set(existing_ifaces)
choice = Menu(str(_('Select interface to add')), list(available), skip=True).run() choice = Menu(str(_('Select interface to add')), list(available), skip=True).run()
@ -94,14 +94,14 @@ class ManualNetworkConfig(ListManager):
log("You need to enter a valid IP in IP-config mode.", level=logging.WARNING, fg='red') log("You need to enter a valid IP in IP-config mode.", level=logging.WARNING, fg='red')
# Implemented new check for correct gateway IP address # Implemented new check for correct gateway IP address
gateway = None
while 1: while 1:
gateway = TextInput(_('Enter your gateway (router) IP address or leave blank for none: '), gateway_input = TextInput(_('Enter your gateway (router) IP address or leave blank for none: '),
edit_iface.gateway).run().strip() edit_iface.gateway).run().strip()
try: try:
if len(gateway) == 0: if len(gateway_input) > 0:
gateway = None ipaddress.ip_address(gateway_input)
else:
ipaddress.ip_address(gateway)
break break
except ValueError: except ValueError:
log("You need to enter a valid gateway (router) IP address.", level=logging.WARNING, fg='red') log("You need to enter a valid gateway (router) IP address.", level=logging.WARNING, fg='red')

View File

@ -20,9 +20,9 @@ def partition_overlap(partitions: list, start: str, end: str) -> bool:
return False return False
def _current_partition_layout(partitions: List[Partition], with_idx: bool = False) -> str: def current_partition_layout(partitions: List[Dict[str, Any]], with_idx: bool = False, with_title: bool = True) -> str:
def do_padding(name, max_len): def do_padding(name: str, max_len: int):
spaces = abs(len(str(name)) - max_len) + 2 spaces = abs(len(str(name)) - max_len) + 2
pad_left = int(spaces / 2) pad_left = int(spaces / 2)
pad_right = spaces - pad_left pad_right = spaces - pad_left
@ -62,8 +62,11 @@ def _current_partition_layout(partitions: List[Partition], with_idx: bool = Fals
current_layout += f'{row[:-1]}\n' current_layout += f'{row[:-1]}\n'
title = str(_('Current partition layout')) if with_title:
return f'\n\n{title}:\n\n{current_layout}' title = str(_('Current partition layout'))
return f'\n\n{title}:\n\n{current_layout}'
return current_layout
def _get_partitions(partitions :List[Partition], filter_ :Callable = None) -> List[str]: def _get_partitions(partitions :List[Partition], filter_ :Callable = None) -> List[str]:
@ -173,7 +176,7 @@ def manage_new_and_existing_partitions(block_device: 'BlockDevice') -> Dict[str,
# show current partition layout: # show current partition layout:
if len(block_device_struct["partitions"]): if len(block_device_struct["partitions"]):
title += _current_partition_layout(block_device_struct['partitions']) + '\n' title += current_partition_layout(block_device_struct['partitions']) + '\n'
modes += [save_and_exit, cancel] modes += [save_and_exit, cancel]
@ -246,7 +249,7 @@ def manage_new_and_existing_partitions(block_device: 'BlockDevice') -> Dict[str,
block_device_struct.update(suggest_single_disk_layout(block_device)[block_device.path]) block_device_struct.update(suggest_single_disk_layout(block_device)[block_device.path])
else: else:
current_layout = _current_partition_layout(block_device_struct['partitions'], with_idx=True) current_layout = current_partition_layout(block_device_struct['partitions'], with_idx=True)
if task == delete_partition: if task == delete_partition:
title = _('{}\n\nSelect by index which partitions to delete').format(current_layout) title = _('{}\n\nSelect by index which partitions to delete').format(current_layout)
@ -375,7 +378,7 @@ def select_encrypted_partitions(
# show current partition layout: # show current partition layout:
if len(partitions): if len(partitions):
title += _current_partition_layout(partitions) + '\n' title += current_partition_layout(partitions) + '\n'
choice = Menu(title, partition_indexes, multi=multiple).run() choice = Menu(title, partition_indexes, multi=multiple).run()

View File

@ -52,6 +52,7 @@ def get_password(prompt: str = '') -> Optional[str]:
continue continue
return passwd return passwd
return None return None