add user? new page? new web

This commit is contained in:
2026-03-23 21:28:10 +05:00
parent 04bfff3d3f
commit 9cd6d83428
26 changed files with 917 additions and 65 deletions

BIN
web/assets/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 851 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

3
web/assets/icons/README Normal file
View File

@@ -0,0 +1,3 @@
# Положи сюда своё изображение с именем loading-animation.png
# Это заменит мигающий логотип Flet при загрузке страницы.
# Рекомендуемый размер: PNG, минимум 200x200 px.

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

View File

@@ -9,10 +9,31 @@ class clolors:
class TextField_input(ft.TextField):
def __init__(self):
class CastomTextField_input(ft.TextField):
def __init__(self, on_change, on_submit, label="Ваше имя", hint_text="Введите текст...", prefix_icon=None, suffix_icon=None):
super().__init__(
label=label,
hint_text=hint_text,
filled=True, # заливка фона
bgcolor=clolors.ferst, # цвет заливки
border_color=clolors.laer2, # цвет границы
border_radius=12, # скругление углов
label_style=ft.TextStyle(
color=clolors.laer3,
weight=ft.FontWeight.BOLD,
),
hint_style=ft.TextStyle(
color=clolors.laer1,
italic=True,
),
text_style=ft.TextStyle(
color=clolors.laer3,
size=16,
),
prefix_icon=prefix_icon,
suffix_icon=suffix_icon,
on_change=on_change,
on_submit=on_submit,
)
class CastomButton(ft.Button):

View File

@@ -1,7 +1,49 @@
import os
import httpx
import flet as ft
class user():
def __init__(self, page: ft.Page):
pass
API_URL = os.getenv("API_URL", "http://api:8000")
_TOKEN_KEY = "auth_token"
#page.client_storage.set("token", "your_jwt_token_here")
class AuthService:
"""Сервис авторизации: логин, логаут, проверка токена."""
def __init__(self, page: ft.Page):
self._page = page
async def login(self, username: str, password: str) -> str | None:
"""
Отправляет запрос на POST /auth/login.
Возвращает None при успехе, строку с ошибкой при неудаче.
"""
try:
async with httpx.AsyncClient(base_url=API_URL, timeout=10) as client:
resp = await client.post(
"/auth/login",
data={"username": username, "password": password},
)
if resp.status_code == 200:
token = resp.json()["access_token"]
self._page.session.set(_TOKEN_KEY, token)
return None
elif resp.status_code == 401:
return "Неверный логин или пароль"
else:
return f"Ошибка сервера: {resp.status_code}"
except httpx.ConnectError:
return "Не удалось подключиться к серверу"
except Exception as e:
return f"Ошибка: {e}"
async def logout(self):
"""Удаляет токен — пользователь разлогинен."""
self._page.session.remove(_TOKEN_KEY)
async def get_token(self) -> str | None:
"""Возвращает сохранённый токен или None если не авторизован."""
return self._page.session.get(_TOKEN_KEY)
async def is_authenticated(self) -> bool:
token = await self.get_token()
return token is not None and token != ""

View File

@@ -1,19 +1,36 @@
import flet as ft
from view import ViewsHendler
import designer as ds
from info import AuthService
async def main(page: ft.Page):
page.title = "COPP"
page.bgcolor = ds.clolors.background
auth = AuthService(page)
_PUBLIC_ROUTES = {"/login"}
def route_change(e: ft.RouteChangeEvent):
print("Route changed to:", page.route)
page.run_task(_guarded_route_change, page.route)
async def _guarded_route_change(route: str):
if route not in _PUBLIC_ROUTES and not await auth.is_authenticated():
await page.push_route("/login")
return
page.views.clear()
view = ViewsHendler(page).get(page.route)
if view: # проверяем, что view получен
view = ViewsHendler(page).get(route)
if view:
page.views.append(view)
else:
await page.push_route("/main")
page.update()
page.on_route_change = route_change
await page.push_route("/main")
ft.run(main, port=80345, assets_dir="assets")
# export_asgi_app=True — Flet отдаёт ASGI-объект вместо запуска встроенного сервера.
# Uvicorn из CMD в Dockerfile подхватывает этот объект через «main:app».
app = ft.run(main, export_asgi_app=True, assets_dir="assets")

View File

@@ -1,9 +1,10 @@
import flet as ft
from views import main_view
from views import main_view, login_view
def ViewsHendler(page: ft.Page):
return {
"/main": main_view.MainView(),
"/login": login_view.LoginView(page),
"/about": ft.View(route="/about", controls=[ft.Text("О нас",color="#008000", size=20)]),
"/contact": ft.View(route="/contact", controls=[ft.Text("Контакты",color="#FF0000", size=20)])
}

96
web/views/login_view.py Normal file
View File

@@ -0,0 +1,96 @@
import flet as ft
import designer as ds
from info import AuthService
class LoginView(ft.View):
def __init__(self, page: ft.Page):
self._auth = AuthService(page)
self._username = ft.TextField(
label="Логин",
hint_text="Введите логин...",
filled=True,
bgcolor=ds.clolors.ferst,
border_color=ds.clolors.laer2,
border_radius=12,
prefix_icon=ft.Icons.PERSON,
label_style=ft.TextStyle(color=ds.clolors.laer3, weight=ft.FontWeight.BOLD),
text_style=ft.TextStyle(color=ds.clolors.laer3, size=16),
)
self._password = ft.TextField(
label="Пароль",
hint_text="Введите пароль...",
filled=True,
bgcolor=ds.clolors.ferst,
border_color=ds.clolors.laer2,
border_radius=12,
prefix_icon=ft.Icons.LOCK,
password=True,
can_reveal_password=True,
label_style=ft.TextStyle(color=ds.clolors.laer3, weight=ft.FontWeight.BOLD),
text_style=ft.TextStyle(color=ds.clolors.laer3, size=16),
on_submit=lambda e: page.run_task(self._do_login),
)
self._error = ft.Text("", color=ft.Colors.RED_400, size=14, visible=False)
self._btn = ds.CastomButton(
text="Войти",
on_click=lambda e: page.run_task(self._do_login),
)
super().__init__(
route="/login",
bgcolor=ds.clolors.background,
horizontal_alignment=ft.CrossAxisAlignment.CENTER,
vertical_alignment=ft.MainAxisAlignment.CENTER,
)
self.controls = [
ft.Text("COPP", color=ds.clolors.ferst, size=30, weight=ft.FontWeight.BOLD),
ft.Container(height=8),
ft.Text("Вход в систему", color=ds.clolors.ferst, size=16),
ft.Container(height=24),
ft.Container(
width=360,
padding=ft.padding.all(24),
bgcolor=ds.clolors.laer3,
border_radius=16,
content=ft.Column(
spacing=16,
controls=[
self._username,
self._password,
self._error,
self._btn,
],
),
),
]
async def _do_login(self):
username = self._username.value.strip()
password = self._password.value
if not username or not password:
self._show_error("Заполните логин и пароль")
return
self._btn.disabled = True
self._error.visible = False
self.update()
error = await self._auth.login(username, password)
if error:
self._btn.disabled = False
self._show_error(error)
else:
# Успешный вход — переходим на главную
await self.page.push_route("/main")
def _show_error(self, message: str):
self._error.value = message
self._error.visible = True
self.update()