This commit is contained in:
Will McGugan
2023-08-20 18:59:17 +01:00
parent 672da759c3
commit b29684a092
4 changed files with 91 additions and 33 deletions

View File

@@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "textual_web" name = "textual_web"
version = "0.1.0" version = "0.1.0pre0"
description = "Serve Textual apps" description = "Serve Textual apps"
authors = ["Will McGugan <will@textualize.io>"] authors = ["Will McGugan <will@textualize.io>"]
license = "MIT" license = "MIT"

View File

@@ -1,9 +1,12 @@
import json
from pathlib import Path
import re import re
import unicodedata import unicodedata
import httpx import httpx
from rich.console import RenderableType from rich.console import Console, RenderableType
from rich.panel import Panel
import xdg
from textual import on from textual import on
from textual import work from textual import work
@@ -13,8 +16,11 @@ from textual.containers import Vertical, Container
from textual.renderables.bar import Bar from textual.renderables.bar import Bar
from textual.reactive import reactive from textual.reactive import reactive
from textual.widget import Widget from textual.widget import Widget
from textual.widgets import Label, Input, Button, LoadingIndicator from textual.widgets import Label, Input, Button, LoadingIndicator, RichLog
from textual.screen import Screen from textual.screen import ModalScreen, Screen
from ..environment import Environment
class Form(Container): class Form(Container):
@@ -28,7 +34,7 @@ class Form(Container):
layout: grid; layout: grid;
grid-size: 2; grid-size: 2;
grid-columns: auto 50; grid-columns: auto 50;
grid_rows: auto; grid-rows: auto;
grid-gutter: 1; grid-gutter: 1;
} }
@@ -81,9 +87,6 @@ class Form(Container):
Form Input { Form Input {
border: tall transparent; border: tall transparent;
} }
""" """
@@ -237,7 +240,7 @@ class SignupScreen(Screen):
try: try:
async with httpx.AsyncClient() as client: async with httpx.AsyncClient() as client:
response = await client.post( response = await client.post(
"http://127.0.0.1:8080/api/signup/", data=data f"{self.app.environment.api_url}signup/", data=data
) )
result = response.json() result = response.json()
@@ -250,25 +253,32 @@ class SignupScreen(Screen):
finally: finally:
self.disabled = False self.disabled = False
self.notify("There are errors in the form. Please try again.") try:
self.query_one(Form).add_class("-show-errors") result = response.json()
except Exception:
self.notify(
"Server returned an invalid response. Please try again later.",
severity="error",
)
return
for error_label in self.query(ErrorLabel): for error_label in self.query(ErrorLabel):
error_label.update("") error_label.update("")
error_label.remove_class("-show-error") error_label.remove_class("-show-error")
for error in result: result_type = result["type"]
if error["loc"]:
error_label = self.query_one( if result_type == "success":
f"#{error['loc'][0]} ErrorLabel", ErrorLabel self.dismiss(result)
) elif result_type == "fail":
error_label.add_class("-show-error") for field, errors in result.get("errors", {}).items():
error_label.update(error["ctx"].get("error", error["msg"])) if field == "_":
for error in errors:
self.notify(error, severity="error")
else: else:
self.notify( error_label = self.query_one(f"#{field} ErrorLabel", ErrorLabel)
error["ctx"].get("error", error["msg"]), severity="error", timeout=5 error_label.add_class("-show-error")
) error_label.update("\n".join(errors))
self.app.log(result)
@on(Input.Changed, "#password Input") @on(Input.Changed, "#password Input")
def input_changed(self, event: Input.Changed): def input_changed(self, event: Input.Changed):
@@ -295,10 +305,53 @@ class SignupScreen(Screen):
class SignUpApp(App): class SignUpApp(App):
CSS_PATH = "signup.tcss" CSS_PATH = "signup.tcss"
def __init__(self, environment: Environment) -> None:
self.environment = environment
super().__init__()
def on_ready(self) -> None: def on_ready(self) -> None:
self.push_screen(SignupScreen()) self.push_screen(SignupScreen(), callback=self.exit)
@classmethod
def signup(cls, environment: Environment) -> None:
console = Console()
app = SignUpApp(environment)
result = app.run()
if __name__ == "__main__": if result is None:
app = SignUpApp() return
app.run()
console.print(
Panel.fit("[bold]You have signed up to textual-web!", border_style="green")
)
home_path = xdg.xdg_config_home()
config_path = home_path / "textual-web"
config_path.mkdir(parents=True, exist_ok=True)
auth_path = config_path / "auth.json"
auth = {
"email": result["user"]["email"],
"auth": result["auth_token"]["key"],
}
auth_path.write_text(json.dumps(auth))
console.print(f" • Wrote auth to {str(auth_path)!r}")
api_key = result["api_key"]["key"]
console.print(f" • Your API key is {api_key!r}")
ganglion_path = Path("./ganglion.toml")
CONFIG = f"""\
[account]
api_key = "{api_key}"
"""
if ganglion_path.exists():
console.print(
f" • [red]Not writing to existing {str(ganglion_path)!r}, please update manually."
)
else:
ganglion_path.write_text(CONFIG)
console.print(f" • [green]Wrote {str(ganglion_path)!r}")
console.print()
console.print("Run 'textual-web --config ganglion.toml' to get started.")

View File

@@ -70,11 +70,9 @@ def print_disclaimer() -> None:
@click.option( @click.option(
"-t", "--terminal", is_flag=True, help="Publish a remote terminal on a random URL" "-t", "--terminal", is_flag=True, help="Publish a remote terminal on a random URL"
) )
@click.option("-s", "--signup", is_flag=True, help="Create a textual-web account")
def app( def app(
config: str | None, config: str | None, environment: str, terminal: bool, api_key: str, signup: bool
environment: str,
terminal: bool,
api_key: str,
) -> None: ) -> None:
"""Main entry point for the CLI. """Main entry point for the CLI.
@@ -84,12 +82,19 @@ def app(
terminal: Enable a terminal. terminal: Enable a terminal.
api_key: API key. api_key: API key.
""" """
error_console = Console(stderr=True) error_console = Console(stderr=True)
from .config import load_config, default_config from .config import load_config, default_config
from .environment import get_environment from .environment import get_environment
_environment = get_environment(environment) _environment = get_environment(environment)
if signup:
from .apps.signup import SignUpApp
SignUpApp.signup(_environment)
return
VERSION = version("textual-web") VERSION = version("textual-web")
print_disclaimer() print_disclaimer()

View File

@@ -19,7 +19,7 @@ ENVIRONMENTS = {
# ), # ),
"local": Environment( "local": Environment(
name="local", name="local",
api_url="ws://127.0.0.1:8080/api", api_url="ws://127.0.0.1:8080/api/",
url="ws://127.0.0.1:8080/app-service/", url="ws://127.0.0.1:8080/app-service/",
), ),
"dev": Environment( "dev": Environment(