parent
b186fb3f64
commit
e74a72f482
|
|
@ -4,7 +4,7 @@ from typing import Any, Literal, override
|
||||||
from textual.validation import ValidationResult, Validator
|
from textual.validation import ValidationResult, Validator
|
||||||
|
|
||||||
from archinstall.lib.translationhandler import tr
|
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.menu_item import MenuItemGroup
|
||||||
from archinstall.tui.ui.result import Result, ResultType
|
from archinstall.tui.ui.result import Result, ResultType
|
||||||
|
|
||||||
|
|
@ -138,6 +138,7 @@ class Input:
|
||||||
allow_skip: bool = True,
|
allow_skip: bool = True,
|
||||||
allow_reset: bool = False,
|
allow_reset: bool = False,
|
||||||
validator_callback: Callable[[str], str | None] | None = None,
|
validator_callback: Callable[[str], str | None] | None = None,
|
||||||
|
info_callback: Callable[[str], InputInfo | None] | None = None,
|
||||||
):
|
):
|
||||||
self._header = header
|
self._header = header
|
||||||
self._placeholder = placeholder
|
self._placeholder = placeholder
|
||||||
|
|
@ -146,6 +147,7 @@ class Input:
|
||||||
self._allow_skip = allow_skip
|
self._allow_skip = allow_skip
|
||||||
self._allow_reset = allow_reset
|
self._allow_reset = allow_reset
|
||||||
self._validator_callback = validator_callback
|
self._validator_callback = validator_callback
|
||||||
|
self._info_callback = info_callback
|
||||||
|
|
||||||
async def show(self) -> Result[str]:
|
async def show(self) -> Result[str]:
|
||||||
validator = GenericValidator(self._validator_callback) if self._validator_callback else None
|
validator = GenericValidator(self._validator_callback) if self._validator_callback else None
|
||||||
|
|
@ -158,6 +160,7 @@ class Input:
|
||||||
allow_skip=self._allow_skip,
|
allow_skip=self._allow_skip,
|
||||||
allow_reset=self._allow_reset,
|
allow_reset=self._allow_reset,
|
||||||
validator=validator,
|
validator=validator,
|
||||||
|
info_callback=self._info_callback,
|
||||||
).run()
|
).run()
|
||||||
|
|
||||||
if result.type_ == ResultType.Reset:
|
if result.type_ == ResultType.Reset:
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,9 @@ import time
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from archinstall.lib.menu.helpers import Confirmation, Input
|
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.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
|
from archinstall.tui.ui.result import ResultType
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -15,12 +15,25 @@ async def get_password(
|
||||||
preset: str | None = None,
|
preset: str | None = None,
|
||||||
skip_confirmation: bool = False,
|
skip_confirmation: bool = False,
|
||||||
) -> Password | None:
|
) -> 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:
|
while True:
|
||||||
result = await Input(
|
result = await Input(
|
||||||
header=header,
|
header=header,
|
||||||
allow_skip=allow_skip,
|
allow_skip=allow_skip,
|
||||||
default_value=preset,
|
default_value=preset,
|
||||||
password=True,
|
password=True,
|
||||||
|
info_callback=password_hint,
|
||||||
).show()
|
).show()
|
||||||
|
|
||||||
if result.type_ == ResultType.Skip:
|
if result.type_ == ResultType.Skip:
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
import sys
|
import sys
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from collections.abc import Awaitable, Callable
|
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 typing import Any, ClassVar, Literal, TypeVar, cast, override
|
||||||
|
|
||||||
from textual import work
|
from textual import work
|
||||||
|
|
@ -707,6 +709,18 @@ class NotifyScreen(ConfirmationScreen[ValueT]):
|
||||||
super().__init__(group, header)
|
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]):
|
class InputScreen(BaseScreen[str]):
|
||||||
CSS = """
|
CSS = """
|
||||||
InputScreen {
|
InputScreen {
|
||||||
|
|
@ -728,6 +742,22 @@ class InputScreen(BaseScreen[str]):
|
||||||
color: red;
|
color: red;
|
||||||
text-align: center;
|
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__(
|
def __init__(
|
||||||
|
|
@ -739,6 +769,7 @@ class InputScreen(BaseScreen[str]):
|
||||||
allow_reset: bool = False,
|
allow_reset: bool = False,
|
||||||
allow_skip: bool = False,
|
allow_skip: bool = False,
|
||||||
validator: Validator | None = None,
|
validator: Validator | None = None,
|
||||||
|
info_callback: Callable[[str], InputInfo | None] | None = None,
|
||||||
):
|
):
|
||||||
super().__init__(allow_skip, allow_reset)
|
super().__init__(allow_skip, allow_reset)
|
||||||
self._header = header or ''
|
self._header = header or ''
|
||||||
|
|
@ -748,6 +779,7 @@ class InputScreen(BaseScreen[str]):
|
||||||
self._allow_reset = allow_reset
|
self._allow_reset = allow_reset
|
||||||
self._allow_skip = allow_skip
|
self._allow_skip = allow_skip
|
||||||
self._validator = validator
|
self._validator = validator
|
||||||
|
self._info_callback = info_callback
|
||||||
|
|
||||||
async def run(self) -> Result[str]:
|
async def run(self) -> Result[str]:
|
||||||
assert TApp.app
|
assert TApp.app
|
||||||
|
|
@ -768,6 +800,7 @@ class InputScreen(BaseScreen[str]):
|
||||||
validate_on=['submitted'],
|
validate_on=['submitted'],
|
||||||
)
|
)
|
||||||
yield Label('', classes='input-failure', id='input-failure')
|
yield Label('', classes='input-failure', id='input-failure')
|
||||||
|
yield Label('', id='input-info')
|
||||||
|
|
||||||
yield Footer()
|
yield Footer()
|
||||||
|
|
||||||
|
|
@ -784,6 +817,24 @@ class InputScreen(BaseScreen[str]):
|
||||||
else:
|
else:
|
||||||
_ = self.dismiss(Result(ResultType.Selection, _data=event.value))
|
_ = 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]):
|
class _DataTable(DataTable[ValueT]):
|
||||||
BINDINGS: ClassVar = [
|
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