feat(web): sign-in, first-run setup, app shell and user admin (M1)
ci / contract / contract (сгенерированный код совпадает с openapi.yaml) (push) Successful in 50s
ci / ts / ts (packages + web) (push) Successful in 43s
ci / docker / docker (образы собираются — их сервер собирает сам при обновлении) (push) Successful in 27s
ci / mobile / mobile (Metro-бандл Android) (push) Successful in 1m22s
ci / contract / contract (сгенерированный код совпадает с openapi.yaml) (push) Successful in 50s
ci / ts / ts (packages + web) (push) Successful in 43s
ci / docker / docker (образы собираются — их сервер собирает сам при обновлении) (push) Successful in 27s
ci / mobile / mobile (Metro-бандл Android) (push) Successful in 1m22s
- core: AuthSession keeps the access token in memory, refreshes it once for all parallel requests (single-flight) and retries them; refresh races between tabs are retried, reuse signs out; ApiError carries problem+json codes and field errors - web: TanStack Router with guards — guests go to /login, the bootstrap admin to /setup/admin, temporary passwords to /setup/password, admin pages by role - login, setup screens, Classic shell (top bar, role-based sidebar, empty player), settings: name, theme, language, password, devices - users admin: search, role filter, create with a one-time temporary password, reset, block, role change, delete as the policy allows; audit log for admins - ru/en strings for auth, admin and error codes; language and theme are saved in the profile Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
f075863f15
commit
ab092def54
44 files changed
+4070
-203
No files matched your search
@@ -0,0 +1,66 @@
|
||||
{
|
||||
"users": {
|
||||
"title": "Пользователи",
|
||||
"subtitle": "Создание пользователей и модераторов, роли, сброс пароля, блокировка",
|
||||
"create": "Создать пользователя",
|
||||
"search": "Имя или логин",
|
||||
"all": "Все",
|
||||
"col_user": "Пользователь",
|
||||
"col_role": "Роль",
|
||||
"col_status": "Статус",
|
||||
"col_last_login": "Был в сети",
|
||||
"status_active": "Активен",
|
||||
"status_blocked": "Заблокирован",
|
||||
"status_must_change": "Ждёт смены пароля",
|
||||
"never": "Ещё не входил",
|
||||
"empty": "Никого не нашли",
|
||||
"more": "Показать ещё",
|
||||
"actions": "Действия с {{username}}",
|
||||
"reset_password": "Сбросить пароль",
|
||||
"block": "Заблокировать",
|
||||
"unblock": "Разблокировать",
|
||||
"make_role": "Сделать: {{role}}",
|
||||
"delete": "Удалить",
|
||||
"delete_confirm": "Удалить {{username}}? Его история и лайки пропадут.",
|
||||
"block_confirm": "Заблокировать {{username}}? Все его сессии завершатся.",
|
||||
"reset_confirm": "Сбросить пароль {{username}}? Все его сессии завершатся.",
|
||||
"confirm": "Подтвердить",
|
||||
"you": "вы"
|
||||
},
|
||||
"create": {
|
||||
"title": "Новый пользователь",
|
||||
"username": "Логин",
|
||||
"username_hint": "Латинские буквы, цифры, «.», «_», «-»",
|
||||
"display_name": "Имя",
|
||||
"role": "Роль",
|
||||
"password_note": "Пароль сгенерирует сервер и покажет один раз.",
|
||||
"submit": "Создать"
|
||||
},
|
||||
"temp_password": {
|
||||
"title": "Временный пароль",
|
||||
"text": "Передайте пароль пользователю {{username}}. Он виден только сейчас; при входе его попросят сменить.",
|
||||
"done": "Готово"
|
||||
},
|
||||
"audit": {
|
||||
"title": "Журнал",
|
||||
"subtitle": "Действия администраторов и модераторов",
|
||||
"col_when": "Когда",
|
||||
"col_who": "Кто",
|
||||
"col_action": "Действие",
|
||||
"col_details": "Подробности",
|
||||
"system": "Система",
|
||||
"empty": "Записей пока нет",
|
||||
"more": "Показать ещё",
|
||||
"actions": {
|
||||
"user.bootstrap_create": "Создан стартовый администратор",
|
||||
"user.bootstrap_credentials": "Заданы учётные данные администратора",
|
||||
"user.create": "Создан пользователь",
|
||||
"user.rename": "Изменено имя",
|
||||
"user.role_change": "Изменена роль",
|
||||
"user.block": "Пользователь заблокирован",
|
||||
"user.unblock": "Пользователь разблокирован",
|
||||
"user.password_reset": "Сброшен пароль",
|
||||
"user.delete": "Пользователь удалён"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"login": {
|
||||
"title": "Вход",
|
||||
"subtitle": "Ваша музыка с вашего сервера",
|
||||
"username": "Логин",
|
||||
"password": "Пароль",
|
||||
"submit": "Войти",
|
||||
"bootstrap_hint": "Первый запуск: войдите как администратор. Пароль задан в BOOTSTRAP_ADMIN_PASSWORD или напечатан в логе сервиса api."
|
||||
},
|
||||
"setup": {
|
||||
"admin_badge": "Первый вход администратора",
|
||||
"admin_title": "Смените логин и пароль",
|
||||
"admin_text": "Без этого шага доступ к серверу закрыт: стартовые данные администратора видны в логах и настройках.",
|
||||
"password_badge": "Пароль сброшен",
|
||||
"password_title": "Задайте новый пароль",
|
||||
"password_text": "Вы вошли по временному паролю. Придумайте свой — временный больше не подойдёт.",
|
||||
"new_username": "Новый логин",
|
||||
"new_password": "Новый пароль",
|
||||
"repeat_password": "Повторите пароль",
|
||||
"min_length": "Не короче {{min}} символов",
|
||||
"submit": "Сохранить и продолжить",
|
||||
"logout": "Выйти"
|
||||
}
|
||||
}
|
||||
@@ -17,5 +17,67 @@
|
||||
"label": "Язык",
|
||||
"ru": "Русский",
|
||||
"en": "English"
|
||||
},
|
||||
"nav": {
|
||||
"main": "Навигация",
|
||||
"home": "Главная",
|
||||
"admin": "Админка",
|
||||
"users": "Пользователи",
|
||||
"audit": "Журнал"
|
||||
},
|
||||
"profile": {
|
||||
"menu": "Меню профиля",
|
||||
"settings": "Настройки",
|
||||
"logout": "Выйти"
|
||||
},
|
||||
"roles": {
|
||||
"admin": "Администратор",
|
||||
"moderator": "Модератор",
|
||||
"user": "Пользователь"
|
||||
},
|
||||
"home": {
|
||||
"greeting": "Привет, {{name}}!",
|
||||
"wave": "Моя волна",
|
||||
"wave_soon": "Бесконечная лента под ваш вкус появится, когда сервер подключит библиотеку."
|
||||
},
|
||||
"player": {
|
||||
"empty": "Ничего не играет"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Настройки",
|
||||
"account": "Аккаунт",
|
||||
"display_name": "Отображаемое имя",
|
||||
"save": "Сохранить",
|
||||
"saved": "Сохранено",
|
||||
"appearance": "Внешний вид",
|
||||
"theme": "Тема",
|
||||
"theme_dark": "Тёмная",
|
||||
"theme_light": "Светлая",
|
||||
"theme_system": "Как в системе",
|
||||
"password": "Пароль",
|
||||
"current_password": "Текущий пароль",
|
||||
"new_password": "Новый пароль",
|
||||
"repeat_password": "Повторите пароль",
|
||||
"change_password": "Сменить пароль",
|
||||
"password_changed": "Пароль изменён. Другие устройства вышли из аккаунта.",
|
||||
"sessions": "Устройства",
|
||||
"session_current": "Это устройство",
|
||||
"session_last_used": "Активность: {{date}}",
|
||||
"session_end": "Завершить",
|
||||
"logout_all": "Выйти на всех устройствах"
|
||||
},
|
||||
"form": {
|
||||
"show_password": "Показать пароль",
|
||||
"hide_password": "Скрыть пароль",
|
||||
"passwords_mismatch": "Пароли не совпадают",
|
||||
"required": "Заполните поле",
|
||||
"cancel": "Отмена",
|
||||
"close": "Закрыть",
|
||||
"copy": "Копировать",
|
||||
"copied": "Скопировано"
|
||||
},
|
||||
"loading": "Загрузка…",
|
||||
"route_error": {
|
||||
"reload": "Обновить страницу"
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,8 @@
|
||||
"internal": "Что-то пошло не так на сервере. Попробуйте ещё раз",
|
||||
"bad_request": "Некорректный запрос",
|
||||
"not_found": "Не найдено",
|
||||
"method_not_allowed": "Действие не поддерживается"
|
||||
"method_not_allowed": "Действие не поддерживается",
|
||||
"network": "Нет связи с сервером. Проверьте сеть"
|
||||
},
|
||||
"auth": {
|
||||
"invalid_credentials": "Неверный логин или пароль",
|
||||
|
||||
Reference in new issue
Block a user