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:
parent
089c46db4a
commit
65a5a335aa
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ def get_password(prompt: str = '') -> Optional[str]:
|
|||
continue
|
||||
|
||||
return passwd
|
||||
|
||||
return None
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue