Поддержка
Начало работы

Установка API

Подключение серверной части Frame через Composer

Установка серверной части — последовательность: подключить приватные репозитории, поставить пакет, опубликовать файлы Frame, прописать окружение и запустить миграции.

Подключите репозитории Frame

Добавьте репозитории Frame в composer.json:

composer.json
{
  "repositories": {
    "frame": {
      "type": "composer",
      "url": "https://git.qubeek.io/api/v4/group/55/-/packages/composer/packages.json"
    },
    "qubeekio": {
      "type": "composer",
      "url": "https://git.qubeek.io/api/v4/group/138/-/packages/composer/packages.json"
    }
  }
}

Добавьте зависимость:

composer.json
{
  "require": {
    "frame/frame": "^0.2.0"
  }
}
Используйте "frame/frame": "*" если хотите всегда получать последнюю версию.

Настройте авторизацию Composer

Репозитории приватные — Composer отдельно нужен токен. Создайте auth.json рядом с composer.json:

auth.json
{
  "gitlab-domains": ["git.qubeek.io"],
  "gitlab-token": {
    "git.qubeek.io": "<ваш-токен>"
  }
}
Добавьте auth.json в .gitignore — файл содержит секрет. Токен выдаётся службой поддержки.

Проверьте платформенные требования

Frame (через phpoffice/phpspreadsheet) требует PHP-расширение ext-gd. Убедитесь, что оно установлено:

php -m | grep -i gd

Если расширение отсутствует, добавьте в Dockerfile/окружение, либо разово установите с игнорированием проверки:

composer update --ignore-platform-req=ext-gd
frame/frame также фиксирует giggsey/libphonenumber-for-php ^8.13. Если в вашем проекте уже установлена 9-я ветка — потребуется временное понижение или координация с апстримом.

Пропишите переменные окружения

Добавьте в .env:

FRAME_DOMAIN_NAME
string
Адрес панели администратора, например panel.example.com.
SANCTUM_STATEFUL_DOMAINS
string
Домены, для которых Sanctum поддерживает сессионную аутентификацию. Укажите основной домен и домен панели через запятую, например example.com,panel.example.com.
SESSION_DOMAIN
string
Для работы сессии между поддоменами (например основной сайт на example.com, панель на panel.example.com) значение должно начинаться с точки: .example.com.
FRAME_TOTP_ENABLED
bool
TOTP-авторизация (приложение-аутентификатор).
FRAME_WEBAUTHN_ENABLED
bool
WebAuthn-авторизация (ключи доступа, биометрия).
FRAME_OTP_ENABLED
bool
OTP-авторизация (код на email).
FRAME_SESSION_LOCK_ENABLED
bool
Автоматическая блокировка сессии по таймауту. По умолчанию включена.
FRAME_SESSION_LOCK_TIMEOUT
int
Таймаут блокировки сессии в секундах. По умолчанию 900 (15 минут).

Установите зависимости

composer update --with-all-dependencies

Флаг -W (--with-all-dependencies) нужен, потому что установка frame/frame может затрагивать уже заблокированные корневые пакеты (Laravel, Sanctum, nesbot/carbon и др.).

Опубликуйте файлы Frame

php artisan frame:install

php artisan frame:publish

Зарегистрируйте сервис-провайдер

frame:install создаёт app/Providers/FrameServiceProvider.php, но не прописывает его в bootstrap/providers.php. Добавьте вручную:

bootstrap/providers.php
use App\Providers\FrameServiceProvider;

return [
  AppServiceProvider::class,
  FrameServiceProvider::class,
  // ...
];

Без этого биндинги (DefinesPermissions, дашборды, глобальная конфигурация Frame) не будут применяться.

Настройте путь панели

По умолчанию опубликованный config/frame.php использует web.path = '/frame'. Если панель живёт на отдельном домене (FRAME_DOMAIN_NAME), откройте её на корне:

config/frame.php
'web' => [
  'path' => '/',
  'middleware' => [],
],

Запустите миграции

php artisan migrate

Миграции Frame (таблицы frame_*) подгружаются автоматически из вендора.

Настройте поисковый движок (опционально)

По умолчанию глобальный поиск работает через TNTSearch — файловый индекс в storage/frame/frame_global_index. Индекс автоматически создаётся и обновляется, но на больших объёмах заметно проигрывает специализированным движкам по качеству выдачи и скорости.

Для продакшена рекомендуется Meilisearch. Подключение:

  1. Добавьте сервис в docker-compose.yml:
docker-compose.yml
services:
meilisearch:
  image: getmeili/meilisearch:v1.14
  ports:
    - '7700:7700'
  environment:
    - MEILI_MASTER_KEY=${MEILISEARCH_KEY:-masterKey}
    - MEILI_NO_ANALYTICS=true
    - MEILI_ENV=development
  volumes:
    - 'meilisearch-storage:/meili_data'

volumes:
meilisearch-storage:
  1. Установите PHP-клиент:
composer require meilisearch/meilisearch-php:^1.15
  1. Пропишите в .env:
.env
MEILISEARCH_HOST=http://meilisearch:7700
MEILISEARCH_KEY=masterKey
  1. Переключите драйвер в config/frame.php:
config/frame.php
use Frame\Laravel\Search\MeilisearchDriver;

'global_search' => [
  'driver' => MeilisearchDriver::class,
  // ...
],
  1. Создайте индекс и перенесите данные:
php artisan frame:create-meilisearch-index
php artisan frame:import-resources

Если до этого использовался TNTSearch — удалите папку storage/frame/frame_global_index и добавьте /storage/frame в .gitignore, чтобы артефакты индекса не попадали в репозиторий.

Альтернативные поисковые движки — Algolia (frame:create-algolia-index) и Typesense (frame:create-typesense-index). Драйверы называются AlgoliaDriver/TypesenseDriver в Frame\Laravel\Search\*.

Проверьте entry.blade.php

После публикации откройте resources/views/vendor/frame/entry.blade.php. Критично наличие директивы @frameAssets — она регистрируется в FrameServiceProvider и подключает Vite-теги фронтенд-бандла (dev-сервер с HMR или продакшен-сборку из каталога frame). Без неё фронтенд не подключится, а страница отрисуется как пустой <div id="app"> без ошибок в консоли. Если директива отсутствует, замените содержимое файла:

resources/views/vendor/frame/entry.blade.php
<!doctype html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
  <meta charset="UTF-8">
  @if($favicon)
      <link rel="icon" href="{{ $favicon }}">
  @endif
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta name="author" content="Qubeek.io">
  <meta name="robots" content="noindex, nofollow, noarchive, nosnippet, noimageindex">
  <meta name="googlebot" content="noindex, nofollow">
  <meta name="format-detection" content="telephone=no">

  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta http-equiv="X-Content-Type-Options" content="nosniff">
  <meta http-equiv="X-XSS-Protection" content="1; mode=block">

  @if(app()->environment('production'))
      <meta http-equiv="X-Frame-Options" content="DENY">
  @endif

  <title>{{ $app_name }}</title>

  <script type="text/javascript">
      window.Frame = {
          config: @json($__data)
      }
  </script>

  @frameAssets
</head>
<body>
<div id="app"></div>
</body>
</html>

Настройте nginx (опционально)

Если панель доступна на отдельном домене, настройте проксирование:

nginx.conf
server {
  listen 80;
  server_name panel.you-domain.com;

  charset utf-8;
  client_max_body_size 1024M;

  location / {
      proxy_pass http://api:80;
      proxy_http_version 1.1;
      proxy_set_header Host $host;
      proxy_cache_bypass $http_upgrade;
  }

  location /api-storage {
      alias /var/www/html/api-storage;
      try_files $uri $uri/ =404;
  }
}

Что дальше