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

- 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:
mrleo1nidandClaude Opus 5.5 committed 2026-10-20 06:33:46 +03:00
1 parent f075863f15
commit ab092def54
44 files changed
+4070 -203

No files matched your search

+66
View File
@@ -0,0 +1,66 @@
{
"users": {
"title": "Users",
"subtitle": "Create users and moderators, manage roles, reset passwords, block accounts",
"create": "Create user",
"search": "Name or username",
"all": "All",
"col_user": "User",
"col_role": "Role",
"col_status": "Status",
"col_last_login": "Last seen",
"status_active": "Active",
"status_blocked": "Blocked",
"status_must_change": "Must change password",
"never": "Never signed in",
"empty": "Nobody found",
"more": "Show more",
"actions": "Actions for {{username}}",
"reset_password": "Reset password",
"block": "Block",
"unblock": "Unblock",
"make_role": "Make {{role}}",
"delete": "Delete",
"delete_confirm": "Delete {{username}}? Their history and likes will be lost.",
"block_confirm": "Block {{username}}? All their sessions will end.",
"reset_confirm": "Reset {{username}}'s password? All their sessions will end.",
"confirm": "Confirm",
"you": "you"
},
"create": {
"title": "New user",
"username": "Username",
"username_hint": "Latin letters, digits, \".\", \"_\", \"-\"",
"display_name": "Name",
"role": "Role",
"password_note": "The server will generate a password and show it once.",
"submit": "Create"
},
"temp_password": {
"title": "Temporary password",
"text": "Give this password to {{username}}. It is shown only now; they will be asked to change it on sign-in.",
"done": "Done"
},
"audit": {
"title": "Audit log",
"subtitle": "Actions of administrators and moderators",
"col_when": "When",
"col_who": "Who",
"col_action": "Action",
"col_details": "Details",
"system": "System",
"empty": "No entries yet",
"more": "Show more",
"actions": {
"user.bootstrap_create": "Initial administrator created",
"user.bootstrap_credentials": "Administrator credentials set",
"user.create": "User created",
"user.rename": "Name changed",
"user.role_change": "Role changed",
"user.block": "User blocked",
"user.unblock": "User unblocked",
"user.password_reset": "Password reset",
"user.delete": "User deleted"
}
}
}
+24
View File
@@ -0,0 +1,24 @@
{
"login": {
"title": "Sign in",
"subtitle": "Your music from your own server",
"username": "Username",
"password": "Password",
"submit": "Sign in",
"bootstrap_hint": "First start: sign in as the administrator. The password is set in BOOTSTRAP_ADMIN_PASSWORD or printed in the api service log."
},
"setup": {
"admin_badge": "First administrator sign-in",
"admin_title": "Change the username and password",
"admin_text": "The server stays locked until you do: the initial administrator credentials are visible in logs and settings.",
"password_badge": "Password reset",
"password_title": "Set a new password",
"password_text": "You signed in with a temporary password. Choose your own — the temporary one will stop working.",
"new_username": "New username",
"new_password": "New password",
"repeat_password": "Repeat the password",
"min_length": "At least {{min}} characters",
"submit": "Save and continue",
"logout": "Sign out"
}
}
+62
View File
@@ -17,5 +17,67 @@
"label": "Language",
"ru": "Русский",
"en": "English"
},
"nav": {
"main": "Navigation",
"home": "Home",
"admin": "Admin",
"users": "Users",
"audit": "Audit log"
},
"profile": {
"menu": "Profile menu",
"settings": "Settings",
"logout": "Sign out"
},
"roles": {
"admin": "Administrator",
"moderator": "Moderator",
"user": "User"
},
"home": {
"greeting": "Hi, {{name}}!",
"wave": "My Wave",
"wave_soon": "An endless stream tuned to your taste will appear once the server has a library."
},
"player": {
"empty": "Nothing is playing"
},
"settings": {
"title": "Settings",
"account": "Account",
"display_name": "Display name",
"save": "Save",
"saved": "Saved",
"appearance": "Appearance",
"theme": "Theme",
"theme_dark": "Dark",
"theme_light": "Light",
"theme_system": "System",
"password": "Password",
"current_password": "Current password",
"new_password": "New password",
"repeat_password": "Repeat the password",
"change_password": "Change password",
"password_changed": "Password changed. Other devices have been signed out.",
"sessions": "Devices",
"session_current": "This device",
"session_last_used": "Active: {{date}}",
"session_end": "Sign out",
"logout_all": "Sign out everywhere"
},
"form": {
"show_password": "Show password",
"hide_password": "Hide password",
"passwords_mismatch": "Passwords don't match",
"required": "Fill in this field",
"cancel": "Cancel",
"close": "Close",
"copy": "Copy",
"copied": "Copied"
},
"loading": "Loading…",
"route_error": {
"reload": "Reload the page"
}
}
+2 -1
View File
@@ -3,7 +3,8 @@
"internal": "Something went wrong on the server. Please try again",
"bad_request": "Invalid request",
"not_found": "Not found",
"method_not_allowed": "This action is not supported"
"method_not_allowed": "This action is not supported",
"network": "Cannot reach the server. Check your network"
},
"auth": {
"invalid_credentials": "Wrong username or password",
+66
View File
@@ -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": "Пользователь удалён"
}
}
}
+24
View File
@@ -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": "Выйти"
}
}
+62
View File
@@ -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": "Обновить страницу"
}
}
+2 -1
View File
@@ -3,7 +3,8 @@
"internal": "Что-то пошло не так на сервере. Попробуйте ещё раз",
"bad_request": "Некорректный запрос",
"not_found": "Не найдено",
"method_not_allowed": "Действие не поддерживается"
"method_not_allowed": "Действие не поддерживается",
"network": "Нет связи с сервером. Проверьте сеть"
},
"auth": {
"invalid_credentials": "Неверный логин или пароль",