parent
b186fb3f64
commit
e74a72f482
|
|
@ -4,7 +4,7 @@ from typing import Any, Literal, override
|
|||
from textual.validation import ValidationResult, Validator
|
||||
|
||||
from archinstall.lib.translationhandler import tr
|
||||
from archinstall.tui.ui.components import InputScreen, LoadingScreen, NotifyScreen, OptionListScreen, SelectListScreen, TableSelectionScreen
|
||||
from archinstall.tui.ui.components import InputInfo, InputScreen, LoadingScreen, NotifyScreen, OptionListScreen, SelectListScreen, TableSelectionScreen
|
||||
from archinstall.tui.ui.menu_item import MenuItemGroup
|
||||
from archinstall.tui.ui.result import Result, ResultType
|
||||
|
||||
|
|
@ -138,6 +138,7 @@ class Input:
|
|||
allow_skip: bool = True,
|
||||
allow_reset: bool = False,
|
||||
validator_callback: Callable[[str], str | None] | None = None,
|
||||
info_callback: Callable[[str], InputInfo | None] | None = None,
|
||||
):
|
||||
self._header = header
|
||||
self._placeholder = placeholder
|
||||
|
|
@ -146,6 +147,7 @@ class Input:
|
|||
self._allow_skip = allow_skip
|
||||
self._allow_reset = allow_reset
|
||||
self._validator_callback = validator_callback
|
||||
self._info_callback = info_callback
|
||||
|
||||
async def show(self) -> Result[str]:
|
||||
validator = GenericValidator(self._validator_callback) if self._validator_callback else None
|
||||
|
|
@ -158,6 +160,7 @@ class Input:
|
|||
allow_skip=self._allow_skip,
|
||||
allow_reset=self._allow_reset,
|
||||
validator=validator,
|
||||
info_callback=self._info_callback,
|
||||
).run()
|
||||
|
||||
if result.type_ == ResultType.Reset:
|
||||
|
|
|
|||
|
|
@ -3,9 +3,9 @@ import time
|
|||
from pathlib import Path
|
||||
|
||||
from archinstall.lib.menu.helpers import Confirmation, Input
|
||||
from archinstall.lib.models.users import Password
|
||||
from archinstall.lib.models.users import Password, PasswordStrength
|
||||
from archinstall.lib.translationhandler import tr
|
||||
from archinstall.tui.ui.components import tui
|
||||
from archinstall.tui.ui.components import InputInfo, InputInfoType, tui
|
||||
from archinstall.tui.ui.result import ResultType
|
||||
|
||||
|
||||
|
|
@ -15,12 +15,25 @@ async def get_password(
|
|||
preset: str | None = None,
|
||||
skip_confirmation: bool = False,
|
||||
) -> Password | None:
|
||||
def password_hint(value: str) -> InputInfo | None:
|
||||
if not value:
|
||||
return None
|
||||
strength = PasswordStrength.strength(value)
|
||||
if strength in (PasswordStrength.VERY_WEAK, PasswordStrength.WEAK):
|
||||
return InputInfo(message=tr('Password strength: Weak'), info_type=InputInfoType.MsgError)
|
||||
elif strength == PasswordStrength.MODERATE:
|
||||
return InputInfo(message=tr('Password strength: Moderate'), info_type=InputInfoType.MsgWarning)
|
||||
elif strength == PasswordStrength.STRONG:
|
||||
return InputInfo(message=tr('Password strength: Strong'), info_type=InputInfoType.MsgInfo)
|
||||
return None
|
||||
|
||||
while True:
|
||||
result = await Input(
|
||||
header=header,
|
||||
allow_skip=allow_skip,
|
||||
default_value=preset,
|
||||
password=True,
|
||||
info_callback=password_hint,
|
||||
).show()
|
||||
|
||||
if result.type_ == ResultType.Skip:
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
import sys
|
||||
from abc import ABC, abstractmethod
|
||||
from collections.abc import Awaitable, Callable
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum, auto
|
||||
from typing import Any, ClassVar, Literal, TypeVar, cast, override
|
||||
|
||||
from textual import work
|
||||
|
|
@ -707,6 +709,18 @@ class NotifyScreen(ConfirmationScreen[ValueT]):
|
|||
super().__init__(group, header)
|
||||
|
||||
|
||||
class InputInfoType(Enum):
|
||||
MsgInfo = auto()
|
||||
MsgWarning = auto()
|
||||
MsgError = auto()
|
||||
|
||||
|
||||
@dataclass
|
||||
class InputInfo:
|
||||
message: str
|
||||
info_type: InputInfoType
|
||||
|
||||
|
||||
class InputScreen(BaseScreen[str]):
|
||||
CSS = """
|
||||
InputScreen {
|
||||
|
|
@ -728,6 +742,22 @@ class InputScreen(BaseScreen[str]):
|
|||
color: red;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#input-info {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.input-hint-msg-error {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.input-hint-msg-warning {
|
||||
color: yellow;
|
||||
}
|
||||
|
||||
.input-hint-msg-info {
|
||||
color: green;
|
||||
}
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
|
|
@ -739,6 +769,7 @@ class InputScreen(BaseScreen[str]):
|
|||
allow_reset: bool = False,
|
||||
allow_skip: bool = False,
|
||||
validator: Validator | None = None,
|
||||
info_callback: Callable[[str], InputInfo | None] | None = None,
|
||||
):
|
||||
super().__init__(allow_skip, allow_reset)
|
||||
self._header = header or ''
|
||||
|
|
@ -748,6 +779,7 @@ class InputScreen(BaseScreen[str]):
|
|||
self._allow_reset = allow_reset
|
||||
self._allow_skip = allow_skip
|
||||
self._validator = validator
|
||||
self._info_callback = info_callback
|
||||
|
||||
async def run(self) -> Result[str]:
|
||||
assert TApp.app
|
||||
|
|
@ -768,6 +800,7 @@ class InputScreen(BaseScreen[str]):
|
|||
validate_on=['submitted'],
|
||||
)
|
||||
yield Label('', classes='input-failure', id='input-failure')
|
||||
yield Label('', id='input-info')
|
||||
|
||||
yield Footer()
|
||||
|
||||
|
|
@ -784,6 +817,24 @@ class InputScreen(BaseScreen[str]):
|
|||
else:
|
||||
_ = self.dismiss(Result(ResultType.Selection, _data=event.value))
|
||||
|
||||
def on_input_changed(self, event: Input.Changed) -> None:
|
||||
info_label = self.query_one('#input-info', Label)
|
||||
if self._info_callback:
|
||||
result = self._info_callback(event.value)
|
||||
if result:
|
||||
css_class = ''
|
||||
if result.info_type == InputInfoType.MsgError:
|
||||
css_class = 'input-hint-msg-error'
|
||||
elif result.info_type == InputInfoType.MsgWarning:
|
||||
css_class = 'input-hint-msg-warning'
|
||||
elif result.info_type == InputInfoType.MsgInfo:
|
||||
css_class = 'input-hint-msg-info'
|
||||
info_label.update(result.message)
|
||||
info_label.set_classes(css_class)
|
||||
else:
|
||||
info_label.update('')
|
||||
info_label.set_classes('')
|
||||
|
||||
|
||||
class _DataTable(DataTable[ValueT]):
|
||||
BINDINGS: ClassVar = [
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
import pytest
|
||||
|
||||
from archinstall.lib.models.users import PasswordStrength
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'password, expected',
|
||||
[
|
||||
('abc', PasswordStrength.VERY_WEAK),
|
||||
('Abcdef1!', PasswordStrength.WEAK),
|
||||
('Abcdef1234!', PasswordStrength.MODERATE),
|
||||
('Abcdef12345!@', PasswordStrength.STRONG),
|
||||
('', PasswordStrength.VERY_WEAK),
|
||||
('123456789', PasswordStrength.VERY_WEAK),
|
||||
('abcdefghijklmnopqr', PasswordStrength.STRONG),
|
||||
],
|
||||
)
|
||||
def test_password_strength(password: str, expected: PasswordStrength) -> None:
|
||||
assert PasswordStrength.strength(password) == expected
|
||||
Loading…
Reference in New Issue