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
# run: mypy --strict --module archinstall || exit 0
- 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

@ -34,11 +34,29 @@ class BlockDevice:
def __repr__(self, *args :str, **kwargs :str) -> str:
return self._str_repr
@cached_property
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})"
@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]:
for partition in self.partitions:
yield self.partitions[partition]
@ -79,7 +97,7 @@ class BlockDevice:
for device in output['blockdevices']:
return device['pttype']
@property
@cached_property
def device_or_backfile(self) -> str:
"""
Returns the actual device-endpoint of the BlockDevice.
@ -162,7 +180,7 @@ class BlockDevice:
from .filesystem import GPT
return GPT
@property
@cached_property
def uuid(self) -> str:
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')
@property
@cached_property
def _safe_size(self) -> float:
from .helpers import convert_size_to_gb
@ -184,7 +202,7 @@ class BlockDevice:
for device in output['blockdevices']:
return convert_size_to_gb(device['size'])
@property
@cached_property
def size(self) -> float:
from .helpers import convert_size_to_gb
@ -193,28 +211,28 @@ class BlockDevice:
for device in output['blockdevices']:
return convert_size_to_gb(device['size'])
@property
@cached_property
def bus_type(self) -> str:
output = json.loads(SysCommand(f"lsblk --json -o+ROTA,TRAN {self.path}").decode('UTF-8'))
for device in output['blockdevices']:
return device['tran']
@property
@cached_property
def spinning(self) -> bool:
output = json.loads(SysCommand(f"lsblk --json -o+ROTA,TRAN {self.path}").decode('UTF-8'))
for device in output['blockdevices']:
return device['rota'] is True
@property
@cached_property
def _safe_free_space(self) -> Tuple[str, ...]:
try:
return '+'.join(part[2] for part in self.free_space)
except SysCallError:
return '?'
@property
@cached_property
def free_space(self) -> Tuple[str, ...]:
# 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,
@ -228,7 +246,7 @@ class BlockDevice:
except SysCallError as error:
log(f"Could not get free space on {self.path}: {error}", level=logging.DEBUG)
@property
@cached_property
def largest_free_space(self) -> List[str]:
info = []
for space_info in self.free_space:
@ -240,7 +258,7 @@ class BlockDevice:
info = space_info
return info
@property
@cached_property
def first_free_sector(self) -> str:
if info := self.largest_free_space:
start = info[0]
@ -248,7 +266,7 @@ class BlockDevice:
start = '512MB'
return start
@property
@cached_property
def first_end_sector(self) -> str:
if info := self.largest_free_space:
end = info[1]

View File

@ -40,7 +40,7 @@ def get_fido2_devices() -> typing.Dict[str, typing.Dict[str, str]]:
}
return devices
def fido2_enroll(hsm_device_path :pathlib.Path, partition :Partition, password :str) -> bool:
worker = SysCommandWorker(f"systemd-cryptenroll --fido2-device={hsm_device_path} {partition.real_device}", peak_output=True)
pw_inputted = False

View File

@ -1,6 +1,6 @@
from __future__ import annotations
from typing import Any, List, Optional, Union
from typing import Any, List, Optional, Union, Dict, TYPE_CHECKING
import archinstall
@ -33,10 +33,15 @@ from ..user_interaction import select_encrypted_partitions
from ..user_interaction import select_harddrives
from ..user_interaction import select_profile
from ..user_interaction import select_additional_repositories
from ..user_interaction.partitioning_conf import current_partition_layout
if TYPE_CHECKING:
_: Any
class GlobalMenu(GeneralMenu):
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):
# archinstall.Language will not use preset values
@ -69,7 +74,10 @@ class GlobalMenu(GeneralMenu):
self._menu_options['harddrives'] = \
Selector(
_('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'] = \
Selector(
_('Disk layout'),
@ -78,6 +86,8 @@ class GlobalMenu(GeneralMenu):
storage['arguments'].get('harddrives', []),
storage['arguments'].get('advanced', False)
),
preview_func=self._prev_disk_layouts,
display_func=lambda x: self._display_disk_layout(x),
dependencies=['harddrives'])
self._menu_options['!encryption-password'] = \
Selector(
@ -131,7 +141,8 @@ class GlobalMenu(GeneralMenu):
Selector(
_('Profile'),
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'] = \
Selector(
_('Audio'),
@ -189,7 +200,7 @@ class GlobalMenu(GeneralMenu):
def _update_install_text(self, name :str = None, result :Any = None):
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):
self._update_install_text(name, result)
@ -225,6 +236,35 @@ class GlobalMenu(GeneralMenu):
else:
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]:
if missing := self._missing_configs():
text = str(_('Missing configurations:\n'))
@ -247,17 +287,17 @@ class GlobalMenu(GeneralMenu):
if not check('harddrives'):
missing += ['Hard drives']
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']
return missing
def _set_root_password(self):
def _set_root_password(self) -> Optional[str]:
prompt = str(_('Enter root password (leave blank to disable root): '))
password = get_password(prompt=prompt)
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): '))):
return passwd
else:
@ -271,7 +311,7 @@ class GlobalMenu(GeneralMenu):
return ntp
def _select_harddrives(self, old_harddrives : list) -> list:
def _select_harddrives(self, old_harddrives : list) -> List:
harddrives = select_harddrives(old_harddrives)
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
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'] = {}
return harddrives
@ -340,11 +380,11 @@ class GlobalMenu(GeneralMenu):
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: ')))
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: ')))
return users
@ -356,7 +396,7 @@ class GlobalMenu(GeneralMenu):
else:
return list(superusers.keys()) if superusers else ''
def _users_resynch(self):
def _users_resynch(self) -> bool:
self.synch('!superusers')
self.synch('!users')
return False

View File

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

View File

@ -15,7 +15,7 @@ if TYPE_CHECKING:
_: 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
"""
@ -487,6 +487,8 @@ class GeneralMenu:
match choice.type_:
case MenuSelectionType.Esc: return preset
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
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?'))
selection = Menu(

View File

@ -64,7 +64,7 @@ class ManualNetworkConfig(ListManager):
elif self.action == self._action_delete:
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()
available = set(all_ifaces) - set(existing_ifaces)
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')
# Implemented new check for correct gateway IP address
gateway = None
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()
try:
if len(gateway) == 0:
gateway = None
else:
ipaddress.ip_address(gateway)
if len(gateway_input) > 0:
ipaddress.ip_address(gateway_input)
break
except ValueError:
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
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
pad_left = int(spaces / 2)
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'
title = str(_('Current partition layout'))
return f'\n\n{title}:\n\n{current_layout}'
if with_title:
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]:
@ -173,7 +176,7 @@ def manage_new_and_existing_partitions(block_device: 'BlockDevice') -> Dict[str,
# show current partition layout:
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]
@ -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])
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:
title = _('{}\n\nSelect by index which partitions to delete').format(current_layout)
@ -375,7 +378,7 @@ def select_encrypted_partitions(
# show current partition layout:
if len(partitions):
title += _current_partition_layout(partitions) + '\n'
title += current_partition_layout(partitions) + '\n'
choice = Menu(title, partition_indexes, multi=multiple).run()
@ -386,4 +389,4 @@ def select_encrypted_partitions(
for partition_index in choice.value:
yield int(partition_index)
else:
yield (partition_index)
yield (partition_index)

View File

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