Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(web-ui): process input demands dispatched on the bus #180

Merged
merged 1 commit into from
Oct 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions .github/workflows/integration_delivery.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ name: CI/CD

on:
push:
branches:
- main
tags:
- 'v*'
pull_request:
workflow_dispatch:

Expand Down Expand Up @@ -373,17 +377,19 @@ jobs:
name: Publish
if: >-
github.event_name == 'push' && github.ref == 'refs/heads/main' ||
github.event_name == 'pull_request' && github.head_ref == 'main'
github.event_name == 'pull_request' && github.event.pull_request.base.ref
== 'main'
needs:
- type-check
- lint
- test
- build
- images
runs-on: ubuntu-latest
environment:
name: pypi
url: https://pypi.org/p/${{ needs.build.outputs.name }}
url:
https://pypi.org/project/${{ needs.build.outputs.name }}/${{
needs.build.outputs.version }}
permissions:
id-token: write
steps:
Expand Down
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
- fix(users): avoid setting user as sudoer when it performs a password reset
- feat(web-ui): process input demands, dispatched on the bus

## Version 1.0.0

Expand Down
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,11 @@ This is the fastest, easiest, and recommended way to get started with Ubo App.

If you want to install the image on an existing operating system, then read on. Otherwise, skip this section.

---
______________________________________________________________________

⚠️ **Executing scripts directly from the internet with root privileges poses a significant security risk. It's generally a good practice to ensure you understand the script's content before running it. You can check the content of this particular script [here](https://raw.githubusercontent.com/ubopod/ubo-app/main/ubo_app/system/install.sh) before running it.**

---
______________________________________________________________________

To install ubo, run this command in a terminal shell:

Expand Down Expand Up @@ -177,9 +177,11 @@ uv run poe test

#### QR code

In development environment, the camera is probably not working as it is relying, on `picamera2`, so it may become challenging to test the flows relying on QR code input.
In development environment, the camera is probably not working, as it is relying on `picamera2`, so it may become challenging to test the flows relying on QR code input.

To address this, the `qrcode_input` method, in not-RPi environments, will try to get its input from `/tmp/qrcode_input.txt`. So, whenever you encounter a QR code input, you can write the content of the QR code in that file and the application will read it from there and continue the flow.
To address this, the camera module, in not-RPi environments, will try reading from `/tmp/qrcode_input.txt` and `/tmp/qrcode_input.png` too. So, whenever you encounter a QR code input, you can write the content of the QR code in the text file path or put the qrcode image itself in the image file path and the application will read it from there and continue the flow.

Alternatively you may be able to provide the input in the web-ui (needs refresh at the moment) or provide it by `InputProvideAction` in grpc channel.

## 🔒 License

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ dependencies = [
"platformdirs >=4.2.0",
"dill >=0.3.8",
"simpleaudio >=1.0.4",
"python-redux >=0.17.1",
"python-redux >=0.17.2",
"python-debouncer >=0.1.5",
"python-strtobool >=1.0.0",
"python-fake >=0.1.3",
Expand Down
68 changes: 49 additions & 19 deletions scripts/test_on_device.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ set -o nounset

# Signal handler
function cleanup() {
run_on_pod "killall -9 pytest"
run_on_pod killall -9 pytest
}
trap cleanup ERR
trap cleanup SIGINT
Expand All @@ -17,16 +17,18 @@ run=${run:-"False"}
results=${results:-"False"}

function run_on_pod() {
echo $1
if [ $# -lt 1 ]; then
echo "Usage: run_on_pod_out_of_env <command>"
echo "Usage: run_on_pod <command>"
return 1
fi
if [ $# -eq 1 ]; then
ssh ubo-development-pod "sudo XDG_RUNTIME_DIR=/run/user/\$(id -u ubo) -u ubo bash -c 'cd; source /etc/profile && source \$HOME/.profile && ($1)'"
return 0
fi
return 1

# Use SSH to execute commands read from stdin
ssh ubo-development-pod "sudo XDG_RUNTIME_DIR=/run/user/\$(id -u ubo) -u ubo bash -s" <<EOF
cd
source /etc/profile
source "\$HOME/.profile"
$*
EOF
}

function run_on_pod_as_root() {
Expand All @@ -43,19 +45,47 @@ function run_on_pod_as_root() {

if [ "$copy" == "True" ]; then
# Since rsync is not called with -r, it treats ./scripts as an empty directory and its content are ignored, it could be any other random directory inside "./". It is needed solely to create the root directory with ubo:ubo ownership.
(echo './scripts'; git ls-files --others --exclude-standard --cached) | rsync --rsync-path="sudo rsync" --delete --info=progress2 -ae ssh --files-from=- --ignore-missing-args ./ ubo-development-pod:/home/ubo/test-runner/ --chown ubo:ubo
(echo ./scripts; echo ./ubo_app/_version.py; git ls-files --others --exclude-standard --cached) | rsync --rsync-path="sudo rsync" --delete --info=progress2 -ae ssh --files-from=- --ignore-missing-args ./ ubo-development-pod:/home/ubo/test-runner/ --chown ubo:ubo
fi

if [ "$run" == "True" ] || [ "$deps" == "True" ]; then
run_on_pod "$(if [ "$deps" == "True" ]; then echo "(uv --version ||
curl -LsSf https://astral.sh/uv/install.sh | sh) &&"; fi)
$(if [ "$run" == "True" ]; then echo "killall -9 pytest || true && systemctl --user stop ubo-app || true &&"; fi)
cd ~/test-runner &&
uv venv --system-site-packages &&
uv python pin python3.11 &&
$(if [ "$deps" == "True" ]; then echo "SETUPTOOLS_SCM_PRETEND_VERSION=$(uvx hatch version) uv sync --frozen &&"; fi)
$(if [ "$run" == "True" ]; then echo "uv run poe test --verbosity=2 --capture=no --make-screenshots -n1 $* || true &&"; fi)
true"
if [ "$run" == "True" ] || [ "$deps" == "True" ] || [ "$copy" == "True" ]; then
# Initialize an array to build the command
cmd_list=()

# Conditional commands based on the flags
if [ "$deps" == "True" ]; then
cmd_list+=("(uv --version || curl -LsSf https://astral.sh/uv/install.sh | sh) &&")
fi

if [ "$copy" == "True" ]; then
cmd_list+=('perl -pi -e "s|source = \"vcs\"|path = \"ubo_app/_version.py\"\\npattern = \"version = '\''\(?P<version>[^'\'']+\)'\''\"|" ~/test-runner/pyproject.toml && cd ~/test-runner && uv python pin python3.11 && uv venv --system-site-packages && true')
fi

if [ "$run" == "True" ]; then
cmd_list+=("killall -9 pytest || true && systemctl --user stop ubo-app || true &&")
fi

# Common commands
cmd_list+=("cd ~/test-runner &&")
cmd_list+=("uv venv --system-site-packages &&")
cmd_list+=("uv python pin python3.11 &&")

if [ "$deps" == "True" ]; then
cmd_list+=('SETUPTOOLS_SCM_PRETEND_VERSION=$(uvx hatch version) uv sync --frozen &&')
fi

if [ "$run" == "True" ]; then
cmd_list+=("uv run poe test --verbosity=2 --capture=no --make-screenshots -n1 $* || true &&")
fi

# Add a final true to ensure the command exits successfully
cmd_list+=("true")

# Combine the commands into a single string
cmd="${cmd_list[*]}"

# Execute the command on the pod
run_on_pod $cmd
fi

if [ "$run" == "True" ] || [ "$results" == True ]; then
Expand Down
6 changes: 3 additions & 3 deletions setup_scm_schemes.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from setuptools_scm.version import get_local_node_and_date
from setuptools_scm.version import get_local_node_and_date # pyright: ignore
import re
from datetime import datetime
from datetime import datetime, timezone


def local_scheme(version):
Expand All @@ -11,4 +11,4 @@ def local_scheme(version):
)
original_local_version = get_local_node_and_date(version)
numeric_version = original_local_version.replace('+', '').replace('.d', '')
return datetime.utcnow().strftime('%y%m%d') + numeric_version
return datetime.now(timezone.utc).strftime('%y%m%d') + numeric_version
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
// window-rpi-001
37ce32a12a80915276ecbf3176fdf9162f152f74a9f67e34e1df640f48e3f741
92b0e1d02d380eb831bcafb57b27c2f2afa023a52a95af44765c17754ca3de38
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
// window-rpi-002
37ce32a12a80915276ecbf3176fdf9162f152f74a9f67e34e1df640f48e3f741
92b0e1d02d380eb831bcafb57b27c2f2afa023a52a95af44765c17754ca3de38
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
{
"_id": "e3e70682c2094cac629f6fbed82c07cd",
"_type": "RootState",
"input": null,
"main": {
"_type": "MainState",
"depth": 1,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
{
"_id": "e3e70682c2094cac629f6fbed82c07cd",
"_type": "RootState",
"input": null,
"main": {
"_type": "MainState",
"depth": 1,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@
"usernames": {}
}
},
"input": null,
"ip": {
"_type": "IpState",
"interfaces": [
Expand Down Expand Up @@ -1106,7 +1107,10 @@
"is_pending": false,
"status": null
},
"web_ui": null,
"web_ui": {
"_type": "WebUIState",
"active_inputs": []
},
"wifi": {
"_type": "WiFiState",
"connections": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@
"usernames": {}
}
},
"input": null,
"ip": {
"_type": "IpState",
"interfaces": [
Expand Down Expand Up @@ -1176,7 +1177,10 @@
"is_pending": false,
"status": null
},
"web_ui": null,
"web_ui": {
"_type": "WebUIState",
"active_inputs": []
},
"wifi": {
"_type": "WiFiState",
"connections": [],
Expand Down
2 changes: 1 addition & 1 deletion ubo_app/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
GRPC_LISTEN_HOST = os.environ.get('UBO_GRPC_LISTEN_HOST', '127.0.0.1')
GRPC_LISTEN_PORT = int(os.environ.get('UBO_GRPC_LISTEN_PORT', '50051'))

WEB_UI_LISTEN_HOST = os.environ.get('UBO_WEB_UI_LISTEN_HOST', '127.0.0.1')
WEB_UI_LISTEN_HOST = os.environ.get('UBO_WEB_UI_LISTEN_HOST', '0.0.0.0') # noqa: S104
WEB_UI_LISTEN_PORT = int(os.environ.get('UBO_WEB_UI_LISTEN_PORT', '21215'))
WEB_UI_DEBUG_MODE = str_to_bool(os.environ.get('UBO_WEB_UI_DEBUG_MODE', 'False')) == 1

Expand Down
22 changes: 15 additions & 7 deletions ubo_app/load_services.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ def __init__(
def register_reducer(self: UboServiceThread, reducer: ReducerType) -> None:
from ubo_app.store.main import root_reducer_id, store

logger.info(
logger.debug(
'Registering ubo service reducer',
extra={
'service_id': self.service_id,
Expand All @@ -205,7 +205,7 @@ def register(
setup: SetupFunction,
) -> None:
if service_id in DISABLED_SERVICES:
logger.info(
logger.debug(
'Skipping disabled ubo service',
extra={
'service_id': service_id,
Expand All @@ -216,7 +216,7 @@ def register(
return

if WHITE_LIST and service_id not in WHITE_LIST:
logger.info(
logger.debug(
'Service is not in services white list',
extra={
'service_id': service_id,
Expand All @@ -230,7 +230,7 @@ def register(
self.service_id = service_id
self.setup = setup

logger.info(
logger.debug(
'Ubo service registered!',
extra={
'service_id': self.service_id,
Expand Down Expand Up @@ -306,6 +306,15 @@ def run(self: UboServiceThread) -> None:

self.loop.run_forever()

logger.info(
'Ubo service thread stopped',
extra={
'thread_native_id': self.native_id,
'service_label': self.label,
'service_id': self.service_id,
},
)

def __repr__(self: UboServiceThread) -> str:
return (
f'<UboServiceThread id='
Expand All @@ -332,8 +341,8 @@ def task_wrapper(stack: str) -> None:
async def shutdown(self: UboServiceThread) -> None:
from ubo_app.logging import logger

logger.info(
'Shutting down service thread',
logger.debug(
'Stopping service thread',
extra={
'thread_native_id': self.native_id,
'service_label': self.label,
Expand Down Expand Up @@ -368,7 +377,6 @@ async def shutdown(self: UboServiceThread) -> None:
)
await asyncio.sleep(0.1)

logger.info('Stopping event loop', extra={'thread_': self})
self.loop.stop()

def stop(self: UboServiceThread) -> None:
Expand Down
12 changes: 6 additions & 6 deletions ubo_app/rpc/generate_proto.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,9 @@ def get_proto(
current_package: str | None,
) -> str:
_ = name, current_package
if self.type == 'UboAction': # Assuming it is kivy color
if self.type == 'UboAction':
return 'Action'
if self.type == 'UboEvent': # Assuming it is kivy color
if self.type == 'UboEvent':
return 'Event'
if self.type == 'Color': # Assuming it is kivy color
return 'string'
Expand Down Expand Up @@ -122,7 +122,7 @@ def package(self: Self) -> str | None:
return global_enums[self.type]
if self.type in global_types:
return global_types[self.type]
if self.type == 'Color':
if self.type in ('Color', 'UboAction', 'UboEvent'):
return None
msg = f'Unknown type "{self.type}"'
raise TypeError(msg)
Expand Down Expand Up @@ -250,7 +250,7 @@ def get_embedded_definitions(
if len(self.types) > 0:
definitions += f' oneof {betterproto.casing.snake_case(name)} {{\n'
index = 1
for item in self.types:
for item in sorted(self.types, key=lambda x: x.local_name):
try:
definitions += f""" {
item.get_embedded_proto(f"{name}_{index}")} {
Expand Down Expand Up @@ -599,7 +599,7 @@ def _generate_operations_proto(output_directory: Path) -> None:
if actions:
proto += 'message Action {\n'
proto += ' oneof action {\n'
for i, (action, _) in enumerate(actions, 1):
for i, (action, _) in enumerate(sorted(actions), 1):
# proto += f""" {package_name}.v1.{action} {
proto += f""" {action} {
betterproto.casing.snake_case(action)} = {i};\n"""
Expand All @@ -608,7 +608,7 @@ def _generate_operations_proto(output_directory: Path) -> None:
if events:
proto += 'message Event {\n'
proto += ' oneof event {\n'
for i, (event, _) in enumerate(events, 1):
for i, (event, _) in enumerate(sorted(events), 1):
# proto += f""" {package_name}.v1.{event} {
proto += f""" {event} {
betterproto.casing.snake_case(event)} = {i};\n"""
Expand Down
Loading
Loading