diff --git a/.github/workflows/build-frontend.yml b/.github/workflows/build-frontend.yml new file mode 100644 index 0000000..c0a1df3 --- /dev/null +++ b/.github/workflows/build-frontend.yml @@ -0,0 +1,60 @@ +name: Build Frontend (from frontend/) and Push Build Branch + +on: + push: + branches: [ "main" ] + workflow_dispatch: + +env: + NODE_VERSION: '22.12.0' + FRONTEND_DIR: 'frontend' + BUILD_DIR: 'dist' + PUBLISH_BRANCH: 'build' # change to gh-pages if you prefer + +jobs: + build_and_push: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Node.js ${{ env.NODE_VERSION }} (with npm cache pointing to frontend lockfile) + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + cache: 'npm' + cache-dependency-path: '${{ env.FRONTEND_DIR }}/package-lock.json' + + - name: Show node/npm versions + run: | + node -v + npm -v + + - name: Install dependencies (frontend) + working-directory: ${{ env.FRONTEND_DIR }} + run: | + # Use npm ci if lockfile exists, else fallback to npm install + if [ -f package-lock.json ]; then + npm ci + else + npm install + fi + + - name: Build (frontend) + working-directory: ${{ env.FRONTEND_DIR }} + run: npm run build + + - name: Verify build output + run: | + echo "Listing build output:" + ls -la ${{ env.FRONTEND_DIR }}/${{ env.BUILD_DIR }} + + - name: Publish dist to branch '${{ env.PUBLISH_BRANCH }}' + uses: peaceiris/actions-gh-pages@v4 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_branch: ${{ env.PUBLISH_BRANCH }} + publish_dir: ${{ env.FRONTEND_DIR }}/${{ env.BUILD_DIR }} + force_orphan: true diff --git a/frontend/src/i18n.js b/frontend/src/i18n.js index 4157176..01a163d 100644 --- a/frontend/src/i18n.js +++ b/frontend/src/i18n.js @@ -3,6 +3,7 @@ import { initReactI18next } from 'react-i18next'; import enTranslation from './lang/en.json'; import faTranslation from './lang/fa.json'; +import ruTranslation from './lang/ru.json'; const resources = { en: { @@ -11,6 +12,9 @@ const resources = { fa: { translation: faTranslation, }, + ru: { + translation: ruTranslation, + }, }; i18n @@ -25,4 +29,5 @@ i18n }, }); -export default i18n; \ No newline at end of file + +export default i18n; diff --git a/frontend/src/lang/ru.json b/frontend/src/lang/ru.json new file mode 100644 index 0000000..bb08fd7 --- /dev/null +++ b/frontend/src/lang/ru.json @@ -0,0 +1,72 @@ +{ + "loginTitle": "Вход в панель администратора", + "username": "Имя пользователя", + "password": "Пароль", + "loginButton": "Войти", + "loginError": "Неверное имя пользователя или пароль.", + "dashboard": "Панель управления", + "userManagement": "Управление пользователями", + "nodeManagement": "Управление узлами", + "logout": "Выйти", + "nodes": "Узлы", + "users": "Пользователи", + "server": "Сервер", + "settings": "Настройки", + "saveSettingsButton": "Сохранить настройки", + "totalUsers": "Всего пользователей", + "activeAndInactive": "Активные и неактивные", + "averageCPU": "Средняя загрузка CPU", + "currentUsage": "Текущее использование", + "memory": "Память", + "disk": "Диск", + "uptime": "Время работы", + "sinceLastBoot": "С момента последней загрузки", + "addNewUser": "Добавить нового пользователя", + "addNewNode": "Добавить новый узел", + "activeUsers": "Активные пользователи", + "inactiveUsers": "Неактивные пользователи", + "nodesTotal": "Всего узлов", + "nodesActive": "Активные узлы", + "nodesInactive": "Неактивные узлы", + "th_username": "Имя пользователя", + "th_expiryDate": "Дата истечения", + "th_status": "Статус", + "th_owner": "Владелец", + "th_actions": "Действия", + "th_nodeName": "Узел", + "modal_editNodeTitle": "Редактировать узел", + "updateNodeButton": "Обновить узел", + "th_address": "Адрес", + "th_protocol": "Протокол", + "status_active": "Активен", + "status_inactive": "Неактивен", + "downloadButton": "Скачать", + "deleteButton": "Удалить", + "editButton": "Редактировать", + "downloadConfig": "Получить конфиг", + "checkStatus": "Проверить статус", + "downloadConfigFromNode": "Скачать конфиг с", + "enterUsername": "Введите имя пользователя для генерации конфига", + "selectNodeForDownload": "Выберите источник для", + "downloadSource": "Источник скачивания", + "mainPanel": "Главная панель (по умолчанию)", + "modal_createUserTitle": "Создать нового пользователя", + "modal_editUserTitle": "Редактировать пользователя", + "modal_expiryDate": "Дата истечения", + "modal_createNodeTitle": "Создать новый узел", + "cancelButton": "Отмена", + "createUserButton": "Создать пользователя", + "updateUserButton": "Обновить пользователя", + "createNodeButton": "Создать узел", + "nodeName": "Имя узла", + "nodeHostname": "Хостнейм", + "nodePort": "Порт", + "tunnelAddress": "Адрес туннеля", + "ovpnPort": "Порт OVPN", + "key": "Ключ", + "setNewSetting": "Установить новую настройку", + "nodeCreatedAt": "Создан", + "noNodesFound": "Узлы не найдены.", + "optional": "Необязательно", + "deleteNodeConfirm": "Вы уверены, что хотите удалить узел" +} diff --git a/frontend/src/pages/DashboardLayout.jsx b/frontend/src/pages/DashboardLayout.jsx index 6e620bf..93a45bd 100644 --- a/frontend/src/pages/DashboardLayout.jsx +++ b/frontend/src/pages/DashboardLayout.jsx @@ -15,10 +15,13 @@ const DashboardLayout = () => { }; const changeLanguage = () => { - const newLang = i18n.language === 'en' ? 'fa' : 'en'; - i18n.changeLanguage(newLang); - document.documentElement.dir = newLang === 'fa' ? 'rtl' : 'ltr'; - }; + const langs = ['en', 'fa', 'ru']; + const current = i18n.language; + const next = langs[(langs.indexOf(current) + 1) % langs.length]; + + i18n.changeLanguage(next); + document.documentElement.dir = next === 'fa' ? 'rtl' : 'ltr'; +}; return (