Skip to content

Commit

Permalink
feat(web-ui): add fields in InputDescription with `InputFieldDesc…
Browse files Browse the repository at this point in the history
…ription` data structures to describe the fields of an input demand in detail
  • Loading branch information
sassanh committed Oct 12, 2024
1 parent 60979b1 commit 3b8543d
Show file tree
Hide file tree
Showing 9 changed files with 204 additions and 26 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
- refactor(core): avoid truncating or coloring logs in log files
- feat(web-ui): add web-ui service
- feat(web-ui): process input demands, dispatched on the bus
- feat(web-ui): add `fields` in `InputDescription` with `InputFieldDescription` data structures to describe the fields of an input demand in detail
- fix(users): avoid setting user as sudoer when it performs a password reset

## Version 1.0.0
Expand Down
9 changes: 6 additions & 3 deletions ubo_app/services/010-voice/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from ubo_app.store.core import RegisterSettingAppAction, SettingsCategory
from ubo_app.store.main import store
from ubo_app.store.services.audio import AudioPlayAudioAction, AudioPlaybackDoneEvent
from ubo_app.store.services.notifications import NotificationExtraInformation
from ubo_app.store.services.voice import (
VoiceEngine,
VoiceReadTextAction,
Expand Down Expand Up @@ -74,10 +75,12 @@ async def act() -> None:
try:
access_key = (
await ubo_input(
'.*',
prompt='Convert the Picovoice access key to a QR code and '
'scan it.',
title='Picovoice Access Key',
extra_information=NotificationExtraInformation(
text='Convert the Picovoice access key to a QR code and ',
),
prompt='Enter Picovoice Access Key',
fields=[],
)
)[0]
secrets.write_secret(key=PICOVOICE_ACCESS_KEY, value=access_key)
Expand Down
50 changes: 41 additions & 9 deletions ubo_app/services/030-wifi/pages/create_wireless_connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from ubo_app.logging import logger
from ubo_app.store.core import CloseApplicationEvent
from ubo_app.store.main import store
from ubo_app.store.operations import InputFieldDescription, InputFieldType
from ubo_app.store.services.notifications import (
Chime,
Notification,
Expand Down Expand Up @@ -55,36 +56,67 @@ def __init__(

async def create_wireless_connection(self: CreateWirelessConnectionPage) -> None:
try:
_, match = await ubo_input(
BARCODE_PATTERN,
_, data = await ubo_input(

Check warning on line 59 in ubo_app/services/030-wifi/pages/create_wireless_connection.py

View check run for this annotation

Codecov / codecov/patch

ubo_app/services/030-wifi/pages/create_wireless_connection.py#L59

Added line #L59 was not covered by tests
prompt='Enter WiFi connection',
extra_information=NotificationExtraInformation(
text='Go to your phone settings, choose QR code and hold it in '
'front of the camera to scan it.',
picovoice_text='Go to your phone settings, choose {QR|K Y UW AA R} '
'code and hold it in front of the camera to scan it.',
),
pattern=BARCODE_PATTERN,
fields=[
InputFieldDescription(
name='SSID',
label='SSID',
type=InputFieldType.TEXT,
description='The name of the WiFi network',
required=True,
),
InputFieldDescription(
name='Password',
label='Password',
type=InputFieldType.PASSWORD,
description='The password of the WiFi network',
required=False,
),
InputFieldDescription(
name='Type',
label='Type',
type=InputFieldType.SELECT,
description='The type of the WiFi network',
default='WPA2',
options=['WEP', 'WPA', 'WPA2', 'nopass'],
required=False,
),
InputFieldDescription(
name='Hidden',
label='Hidden',
type=InputFieldType.CHECKBOX,
description='Is the WiFi network hidden?',
default='false',
required=False,
),
],
)
except asyncio.CancelledError:
store.dispatch(CloseApplicationEvent(application=self))
return

if not match:
if not data:

Check warning on line 106 in ubo_app/services/030-wifi/pages/create_wireless_connection.py

View check run for this annotation

Codecov / codecov/patch

ubo_app/services/030-wifi/pages/create_wireless_connection.py#L106

Added line #L106 was not covered by tests
store.dispatch(CloseApplicationEvent(application=self))
return
ssid = match.get('SSID') or match.get('SSID_')
ssid = data.get('SSID') or data.get('SSID_')

Check warning on line 109 in ubo_app/services/030-wifi/pages/create_wireless_connection.py

View check run for this annotation

Codecov / codecov/patch

ubo_app/services/030-wifi/pages/create_wireless_connection.py#L109

Added line #L109 was not covered by tests
if ssid is None:
store.dispatch(CloseApplicationEvent(application=self))
return

password = match.get('Password') or match.get('Password_')
type = match.get('Type') or match.get('Type_')
password = data.get('Password') or data.get('Password_')
type = data.get('Type') or data.get('Type_')

Check warning on line 115 in ubo_app/services/030-wifi/pages/create_wireless_connection.py

View check run for this annotation

Codecov / codecov/patch

ubo_app/services/030-wifi/pages/create_wireless_connection.py#L114-L115

Added lines #L114 - L115 were not covered by tests
if type:
type = type.upper()
type = cast(WiFiType, type)
hidden = (
str_to_bool(match.get('Hidden') or match.get('Hidden_') or 'false') == 1
)
hidden = str_to_bool(data.get('Hidden') or data.get('Hidden_') or 'false') == 1

Check warning on line 119 in ubo_app/services/030-wifi/pages/create_wireless_connection.py

View check run for this annotation

Codecov / codecov/patch

ubo_app/services/030-wifi/pages/create_wireless_connection.py#L119

Added line #L119 was not covered by tests

if not password:
logger.warning('Password is required')
Expand Down
5 changes: 4 additions & 1 deletion ubo_app/services/040-camera/reducer.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,10 @@ def reducer(
InputProvideAction(
id=state.current.id,
value=code,
data=match.groupdict(),
data={
key.rstrip('_'): value
for key, value in match.groupdict().items()
},
),
],
events=[
Expand Down
4 changes: 2 additions & 2 deletions ubo_app/services/080-docker/images_.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,6 @@ class ImageEntry(Immutable):
registry='docker.io',
environment_vairables={
'NGROK_AUTHTOKEN': lambda: ubo_input(
r'^[a-zA-Z0-9]{20,30}_[a-zA-Z0-9]{20,30}$',
resolver=lambda code, _: code,
prompt='Enter the Ngrok Auth Token',
extra_information=NotificationExtraInformation(
Expand All @@ -135,10 +134,10 @@ class ImageEntry(Immutable):
3. Convert it to {QR|K Y UW AA R} code
4. Scan QR code to input the token""",
),
pattern=rf'^[a-zA-Z0-9]{20,30}_[a-zA-Z0-9]{20,30}$',
),
},
command=lambda: ubo_input(
'',
resolver=lambda code, _: code,
prompt='Enter the command, for example: `http 80` or `tcp 22`',
extra_information=NotificationExtraInformation(
Expand All @@ -148,6 +147,7 @@ class ImageEntry(Immutable):
This is the command you would enter when running {ngrok|EH N G EH R AA K}.
Refer to {ngrok|EH N G EH R AA K} documentation for further information""",
),
fields=[],
),
),
*(
Expand Down
30 changes: 26 additions & 4 deletions ubo_app/services/080-docker/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
SettingsCategory,
)
from ubo_app.store.main import store
from ubo_app.store.operations import InputFieldDescription, InputFieldType
from ubo_app.store.services.docker import (
DockerImageRegisterAppEvent,
DockerRemoveUsernameAction,
Expand Down Expand Up @@ -210,10 +211,7 @@ async def act() -> None:
try:
credentials = (
await ubo_input(
r'^(?P<Service>[^|]*)\|(?P<Username>[^|]*)\|(?P<Password>[^|]*)$|'
r'(?P<Username_>^[^|]*)|(?P<Password_>[^|]*)$',
prompt='Format: [i]SERVICE|USERNAME|PASSWORD[/i]',
title='Enter Docker Credentials',
prompt='Enter Docker Credentials',
extra_information=NotificationExtraInformation(
text="""To generate your QR code for login, format your \
details by separating your service, username, and password with the pipe symbol. For \
Expand All @@ -232,6 +230,30 @@ async def act() -> None:
omit the service name, "docker {.|D AA T} io" will automatically be used as the \
default.""",
),
pattern=r'^(?P<Service>[^|]*)\|(?P<Username>[^|]*)\|(?P<Password>[^|]*)$|'
r'(?P<Username_>^[^|]*)|(?P<Password_>[^|]*)$',
fields=[
InputFieldDescription(
name='Service',
label='Service',
type=InputFieldType.TEXT,
description='The service name',
default='docker.io',
required=False,
),
InputFieldDescription(
name='Username',
label='Username',
type=InputFieldType.TEXT,
required=True,
),
InputFieldDescription(
name='Password',
label='Password',
type=InputFieldType.PASSWORD,
required=True,
),
],
)
)[1]
if not credentials:
Expand Down
74 changes: 69 additions & 5 deletions ubo_app/services/090-web-ui/templates/index.jinja2
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,38 @@
<head>
<meta charset="UTF-8" />
<title>ubo - web-ui</title>
<style>
body {
font-family: sans-serif;
margin: 0;
padding: 0;
}
form {
margin: 1em auto;
padding: 1em;
width: 50%;
border: 1px solid #ccc;
}
input, select {
font-size: 1em;
}
input[type="text"], input[type="password"], select {
margin-top: .5em;
width: 100%;
}
small {
display: block;
margin-top: .5em;
font-size: .75em;
color: #666;
}
button[type="submit"] {
margin-top: 1em;
border: none;
padding: .75em;
font-size: 1em;
}
</style>
</head>

<body>
Expand All @@ -11,11 +43,43 @@
<input name="id" type="hidden" value="{{ input.id }}" />
<p>
<label>
{{ input.title }}:
{% if re.compile(input.pattern).groupindex.keys() | length > 0 %}
{{ input.prompt }}:
{% if input.fields %}
{% for field in input.fields %}
<p>
<label>
{{ field.label }}:
{% if field.type == 'select' %}
<select name="{{ field.name }}">
{% for option in field.options %}
<option
value="{{ option }}"
{{ ' selected="selected"' if option == field.default else '' }}
>
{{ option }}
</option>
{% endfor %}
</select>
{% else %}
<input
type="{{ field.type }}"
name="{{ field.name }}"
{{ 'value="' ~ field.default ~ '"' if field.default else '' }}
{{ 'title="' ~ field.title ~ '"' if field.title else '' }}
{{ 'pattern="' ~ field.pattern ~ '"' if field.pattern else '' }}
{{ 'required' if field.required else '' }}
/>
{% endif %}
{% if field.description %}
<small>{{ field.description }}</small>
{% endif %}
</label>
</p>
{% endfor %}
{% elif input.pattern and re.compile(input.pattern).groupindex.keys() | length > 0 %}
{%
for group_name in
re.compile(input.pattern).groupindex.keys() | map('replace', '_', '')|list|unique
re.compile(input.pattern).groupindex.keys() | map('regex_replace', '_+$', '') | list | unique
%}
<p>
<label>
Expand All @@ -29,8 +93,8 @@
{% endif %}
</label>
</p>
<input type="submit" value="provide" name='action' />
<input type="submit" value="cancel" name='action' />
<button type="submit" name="action" value="provide">Provide</button>
<button type="submit" name="action" value="cancel">Cancel</button>
{% if not loop.last %}
<hr />
{% endif %}
Expand Down
31 changes: 31 additions & 0 deletions ubo_app/store/operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from __future__ import annotations

from enum import StrEnum
from typing import TYPE_CHECKING

from immutable import Immutable
Expand All @@ -19,6 +20,35 @@ class SnapshotEvent(BaseEvent):
"""Event for taking a snapshot of the store."""


class InputFieldType(StrEnum):
"""Enumeration of input field types."""

LONG = 'long'
TEXT = 'text'
PASSWORD = 'password' # noqa: S105
NUMBER = 'number'
CHECKBOX = 'checkbox'
COLOR = 'color'
SELECT = 'select'
FILE = 'file'
DATE = 'date'
TIME = 'time'


class InputFieldDescription(Immutable):
"""Description of an input field in an input demand."""

name: str
label: str
type: InputFieldType
description: str | None = None
title: str | None = None
pattern: str | None = None
default: str | None = None
options: list[str] | None = None
required: bool = False


class InputDescription(Immutable):
"""Description of an input demand."""

Expand All @@ -27,6 +57,7 @@ class InputDescription(Immutable):
extra_information: NotificationExtraInformation | None = None
id: str
pattern: str | None
fields: list[InputFieldDescription] | None = None


class InputAction(BaseAction):
Expand Down
Loading

0 comments on commit 3b8543d

Please sign in to comment.