Filament Panel Builder. Конструктор панелей Filament. Начало работы. Документация с примерами.
Конструктор панелей Filament (Panel Builder). Введение с рабочими примерами.

Начало работы с Panel Builder Filament (Конструктором панелей)



Обзор

Панели (Panels) — это контейнер верхнего уровня в Filament, позволяющий создавать многофункциональные административные панели, включающие страницы, ресурсы, формы, таблицы, уведомления, мероприятия, инфолисты и виджеты. Все панели включают в себя панель по умолчанию, которая может содержать виджеты со статистикой, графиками, таблицами и т.д.

Необходимые условия

Прежде чем приступить к использованию Filament, необходимо ознакомиться с работой Laravel. Filament опирается на многие основные концепции Laravel, в частности, миграции баз данных и Eloquent ORM. Если вы впервые знакомитесь с Laravel или нуждаетесь в дополнительном обучении, настоятельно рекомендуем пройти курс Laravel Bootcamp, в котором рассматриваются основы создания приложений на базе Laravel.

Демо-проект

В данном руководстве рассматривается создание простой системы управления пациентами для ветеринарной клиники с использованием Filament. Она будет поддерживать добавление новых пациентов (кошек, собак или кроликов), закрепление их за владельцем и регистрацию полученных ими процедур. Система будет иметь рабочую панель со статистикой по типам пациентов и диаграмму, показывающую количество проведенных процедур за последний год.

Настройка базы данных и моделей

В данном проекте необходимы три модели и миграции: Owner, Patient и Treatment. Для их создания воспользуйтесь следующими командами artisan:

php artisan make:model Owner -m
php artisan make:model Patient -m
php artisan make:model Treatment -m

Создание миграций

Для миграции баз данных используйте следующие базовые схемы:

// create_owners_table
Schema::create('owners', function (Blueprint $table) {
    $table->id();
    $table->string('email');
    $table->string('name');
    $table->string('phone');
    $table->timestamps();
});
 
// create_patients_table
Schema::create('patients', function (Blueprint $table) {
    $table->id();
    $table->date('date_of_birth');
    $table->string('name');
    $table->foreignId('owner_id')->constrained('owners')->cascadeOnDelete();
    $table->string('type');
    $table->timestamps();
});
 
// create_treatments_table
Schema::create('treatments', function (Blueprint $table) {
    $table->id();
    $table->string('description');
    $table->text('notes')->nullable();
    $table->foreignId('patient_id')->constrained('patients')->cascadeOnDelete();
    $table->unsignedInteger('price')->nullable();
    $table->timestamps();
});

Запустите миграцию с помощью команды php artisan migrate.

Отвязка всех моделей

Для краткости в этом руководстве отключаем защиту Laravel от массового присвоения. Filament сохраняет в моделях только корректные данные, поэтому для моделей можно безопасно снять защиту. Чтобы снять защиту сразу со всех моделей Laravel, добавьте Model::unguard() в метод boot() файла app/Providers/AppServiceProvider.php:

use Illuminate\Database\Eloquent\Model;
 
public function boot(): void
{
    Model::unguard();
}

Настройка отношений между моделями

Установим связи между моделями. В нашей системе владельцы домашних животных могут иметь несколько домашних животных (пациентов), а пациенты могут иметь множество видов лечения:

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
 
class Owner extends Model
{
    public function patients(): HasMany
    {
        return $this->hasMany(Patient::class);
    }
}
 
class Patient extends Model
{
    public function owner(): BelongsTo
    {
        return $this->belongsTo(Owner::class);
    }
 
    public function treatments(): HasMany
    {
        return $this->hasMany(Treatment::class);
    }
}
 
class Treatment extends Model
{
    public function patient(): BelongsTo
    {
        return $this->belongsTo(Patient::class);
    }
}

Внедрение ресурсов

В Filament ресурсы — это статические классы, используемые для построения CRUD-интерфейсов для ваших моделей Eloquent. Они описывают, как администраторы могут взаимодействовать с данными из вашей панели с помощью таблиц и форм.

Поскольку пациенты (домашние животные) являются основной сущностью в данной системе, начнем с создания ресурса patient, который позволит нам создавать страницы для создания, просмотра, обновления и удаления пациентов.

С помощью следующей команды artisan создайте новый ресурс Filament для модели Patient:

php artisan make:filament-resource Patient

В результате в каталоге app/Filament/Resources будет создано несколько файлов:

.
+-- PatientResource.php
+-- PatientResource
|   +-- Pages
|   |   +-- CreatePatient.php
|   |   +-- EditPatient.php
|   |   +-- ListPatients.php

Зайдите в браузере на страницу /admin/patients и обратите внимание на новую ссылку Patients в боковой панели. При клике по ссылке отобразится пустая таблица. Добавим форму для создания новых пациентов.

Настройка формы ресурсов.

Если открыть файл PatientResource.php, то в нем есть метод form() с пустым массивом schema([...]). Добавив в эту схему поля формы, мы построим форму, которая может быть использована для создания и редактирования новых пациентов.

"Name" — текстовое поле.

Filament содержит большой набор полей форм. Начнем с простого поля ввода текста:

use Filament\Forms;
use Filament\Forms\Form;
 
public static function form(Form $form): Form
{
    return $form
        ->schema([
            Forms\Components\TextInput::make('name'),
        ]);
}

Зайдите в раздел /admin/patients/create (или нажмите кнопку New Patient) и обратите внимание, что в форму добавлено поле для ввода клички пациента.

Поскольку это поле является обязательным для заполнения в базе данных и имеет максимальную длину 255 символов, добавим два правила валидации для поля клички пациента:

use Filament\Forms;
 
Forms\Components\TextInput::make('name')
    ->required()
    ->maxLength(255)

Попытаемся отправить форму для создания нового пациента без его клички и увидим, что на экране появится сообщение о том, что поле клички пациента является обязательным.

"Type" — поле выбора.

Добавим второе поле для типа пациента: выбор между кошкой, собакой или кроликом. Поскольку имеется фиксированный набор вариантов выбора, хорошо подходит поле select:

use Filament\Forms;
use Filament\Forms\Form;
 
public static function form(Form $form): Form
{
    return $form
        ->schema([
            Forms\Components\TextInput::make('name')
                ->required()
                ->maxLength(255),
            Forms\Components\Select::make('type')
                ->options([
                    'cat' => 'Кошка',
                    'dog' => 'Собака',
                    'rabbit' => 'Кролик',
                ]),
        ]);
}

Метод options() поля Select принимает массив вариантов, из которых пользователь может выбирать. Ключи массива должны соответствовать базе данных, а значения используются в качестве меток формы. В этот массив можно добавить любое количество животных.

Поскольку это поле также является обязательным в базе данных, добавим правило валидации required():

use Filament\Forms;
 
Forms\Components\Select::make('type')
    ->options([
        'cat' => 'Кошка',
        'dog' => 'Собака',
        'rabbit' => 'Кролик',
    ])
    ->required()

"Date of birth" — поле выбора даты.

Добавим поле выбора даты для столбца date_of_birth вместе с валидацией (дата рождения обязательна, и дата должна быть не позднее текущего дня).

use Filament\Forms;
use Filament\Forms\Form;
 
public static function form(Form $form): Form
{
    return $form
        ->schema([
            Forms\Components\TextInput::make('name')
                ->required()
                ->maxLength(255),
            Forms\Components\Select::make('type')
                ->options([
                    'cat' => 'Кошка',
                    'dog' => 'Собака',
                    'rabbit' => 'Кролик',
                ])
                ->required(),
            Forms\Components\DatePicker::make('date_of_birth')
                ->required()
                ->maxDate(now()),
        ]);
}

"Owner" — поле выбора

При создании нового пациента необходимо также добавить владельца. Поскольку мы добавили отношение BelongsTo в модель Patient (связав ее со связанной моделью Owner), мы можем использовать метод relationship() из поля select для загрузки списка владельцев для выбора:

use Filament\Forms;
use Filament\Forms\Form;
 
public static function form(Form $form): Form
{
    return $form
        ->schema([
            Forms\Components\TextInput::make('name')
                ->required()
                ->maxLength(255),
            Forms\Components\Select::make('type')
                ->options([
                    'cat' => 'Кошка',
                    'dog' => 'Собака',
                    'rabbit' => 'Кролик',
                ])
                ->required(),
            Forms\Components\DatePicker::make('date_of_birth')
                ->required()
                ->maxDate(now()),
            Forms\Components\Select::make('owner_id')
                ->relationship('owner', 'name')
                ->required(),
        ]);
}

Первым аргументом метода relationship() является имя функции, определяющей отношение в модели (используется Filament для загрузки параметров выбора), — в данном случае owner. Вторым аргументом является имя столбца из связанной таблицы — в данном случае name.

Также сделаем поле owner обязательным, searchable() и preload() первые 50 владельцев в список для поиска (на случай, если список будет длинным):

use Filament\Forms;
 
Forms\Components\Select::make('owner_id')
    ->relationship('owner', 'name')
    ->searchable()
    ->preload()
    ->required()

Создание новых владельцев, не покидая страницы

В настоящее время в нашей базе данных нет ни одного владельца. Вместо того чтобы создавать отдельный ресурс Filament для владельцев, давайте предоставим пользователям более простой способ добавления владельцев через модальную форму (доступную в виде кнопки + рядом с кнопкой выбора). Используйте метод createOptionForm() для встраивания модальной формы с полями TextInput для имени владельца, адреса электронной почты и номера телефона:

use Filament\Forms;
 
Forms\Components\Select::make('owner_id')
    ->relationship('owner', 'name')
    ->searchable()
    ->preload()
    ->createOptionForm([
        Forms\Components\TextInput::make('name')
            ->required()
            ->maxLength(255),
        Forms\Components\TextInput::make('email')
            ->label('Электронная почта')
            ->email()
            ->required()
            ->maxLength(255),
        Forms\Components\TextInput::make('phone')
            ->label('Номер телефона')
            ->tel()
            ->required(),
    ])
    ->required()

В этом примере было использовано несколько новых методов TextInput:

  • Функция label() переопределяет автоматически сгенерированную метку для каждого поля. В данном случае мы хотим, чтобы меткой email была Электронная почта, а меткой phoneНомер телефона.
  • Функция email() гарантирует, что в поле будут введены только корректные адреса электронной почты. Она также изменяет раскладку клавиатуры на мобильных устройствах.
  • Функция tel() обеспечивает ввод в поле только корректных телефонных номеров. Также изменяется раскладка клавиатуры на мобильных устройствах.

Теперь форма должна работать! Попробуйте создать нового пациента и его владельца. После создания вы будете перенаправлены на страницу редактирования, где сможете обновить информацию о пациенте.

Настройка таблицы пациентов

Снова зайдите на страницу /admin/patients. Если вы создали пациента, то в таблице должна быть одна пустая строка с кнопкой редактирования. Давайте добавим в таблицу несколько столбцов, чтобы можно было просматривать реальные данные пациента.

Откройте файл PatientResource.php. Вы должны увидеть метод table() с пустым массивом columns([...]). Этот массив можно использовать для добавления колонок в таблицу patients.

Добавление столбцов с текстом

Filament предоставляет большой выбор колонок для таблиц. Используем простые текстовые колонки для всех полей таблицы patients:

use Filament\Tables;
use Filament\Tables\Table;
 
public static function table(Table $table): Table
{
    return $table
        ->columns([
            Tables\Columns\TextColumn::make('name'),
            Tables\Columns\TextColumn::make('type'),
            Tables\Columns\TextColumn::make('date_of_birth'),
            Tables\Columns\TextColumn::make('owner.name'),
        ]);
}

Filament использует точечную нотацию для ускоренной загрузки связанных данных. Мы использовали owner.name в нашей таблице для отображения списка имен владельцев вместо менее информативных идентификационных номеров. Можно также добавить столбцы для адреса электронной почты и номера телефона владельца.

Создание столбцов с возможностью поиска

Возможность поиска пациентов непосредственно в таблице была бы полезна по мере развития ветеринарной практики. Сделать столбцы доступными для поиска можно, привязав к ним метод searchable(). Давайте сделаем доступными для поиска кличку пациента и имя владельца.

use Filament\Tables;
use Filament\Tables\Table;
 
public static function table(Table $table): Table
{
    return $table
        ->columns([
            Tables\Columns\TextColumn::make('name')
                ->searchable(),
            Tables\Columns\TextColumn::make('type'),
            Tables\Columns\TextColumn::make('date_of_birth'),
            Tables\Columns\TextColumn::make('owner.name')
                ->searchable(),
        ]);
}

Перезагрузите страницу и обратите внимание на новое поле ввода поиска в таблице, которое фильтрует записи таблицы по критериям поиска.

Создание сортировки столбцов

Чтобы сделать таблицу patients сортируемой по возрасту, добавьте метод sortable() к столбцу date_of_birth:

use Filament\Tables;
use Filament\Tables\Table;
 
public static function table(Table $table): Table
{
    return $table
        ->columns([
            Tables\Columns\TextColumn::make('name')
                ->searchable(),
            Tables\Columns\TextColumn::make('type'),
            Tables\Columns\TextColumn::make('date_of_birth')
                ->sortable(),
            Tables\Columns\TextColumn::make('owner.name')
                ->searchable(),
        ]);
}

Это добавит в заголовок столбца кнопку с пиктограммой сортировки. При клике на нее таблица будет отсортирована по дате рождения.

Фильтр таблицы по типу пациента

Хотя поле type можно сделать доступным для поиска, гораздо удобнее сделать его доступным для фильтрации.

Таблицы Filament могут содержать фильтры, которые представляют собой компоненты, уменьшающие количество записей в таблице путем добавления области видимости к запросу Eloquent. Фильтры могут содержать даже компоненты пользовательских форм, что делает их мощным инструментом для построения интерфейсов.

В состав Filament входит готовый фильтр SelectFilter, который можно добавить к функции filters() таблицы:

use Filament\Tables;
use Filament\Tables\Table;
 
public static function table(Table $table): Table
{
    return $table
        ->columns([
            // ...
        ])
        ->filters([
            Tables\Filters\SelectFilter::make('type')
                ->options([
                    'cat' => 'Кошка',
                    'dog' => 'Собака',
                    'rabbit' => 'Кролик',
                ]),
        ]);
}

Перезагрузите страницу, и в правом верхнем углу (рядом с формой поиска) вы увидите значок нового фильтра. Фильтр открывает меню выбора со списком типов пациентов. Попробуйте отфильтровать пациентов по типу.

Внедрение менеджера отношений

В настоящее время в нашей системе пациенты могут быть связаны со своими владельцами. Но как быть, если нам нужен третий уровень? Пациенты приходят в ветеринарную клинику для лечения, и система должна иметь возможность регистрировать эти процедуры и связывать их с пациентом.

Один из вариантов — это создать новый ресурс TreatmentResource с полем выбора, чтобы связать лечение с пациентом. Однако управление лечением отдельно от остальной информации о пациенте является обременительным для пользователя. Для решения этой проблемы в Filament используются "менеджеры связей" (relation managers).

Менеджеры связей — это таблицы, которые отображают связанные записи для существующего ресурса на экране редактирования родительского ресурса. Например, в нашем проекте можно просматривать и управлять лечением пациента непосредственно под его формой редактирования.

Также можно использовать "действия" (actions) Filament для открытия модальной формы для создания, редактирования и удаления процедур непосредственно из таблицы пациента.

С помощью команды make:filament-relation-manager artisan можно быстро создать менеджер связей, соединяющий ресурс пациента со связанными с ним процедурами:

php artisan make:filament-relation-manager PatientResource treatments description
  • PatientResource — это имя класса ресурсов для модели владельца. Поскольку лечение принадлежит пациентам, то лечение должно отображаться на странице Edit Patient.
  • treatments — это имя отношения в модели Patient, которую мы создали ранее.
  • description — столбец для отображения из таблицы лечения.

В результате будет создан файл PatientResource/RelationManagers/TreatmentsRelationManager.php. Новый менеджер связей необходимо зарегистрировать в методе getRelations() ресурса PatientResource:

use App\Filament\Resources\PatientResource\RelationManagers;
 
public static function getRelations(): array
{
    return [
        RelationManagers\TreatmentsRelationManager::class,
    ];
}

Файл TreatmentsRelationManager.php содержит класс, который содержит форму и таблицу, заполненные с помощью параметров команды make:filament-relation-manager artisan. Поля и колонки менеджера связей можно настраивать аналогично тому, как это делается в ресурсе:

use Filament\Forms;
use Filament\Forms\Form;
use Filament\Tables;
use Filament\Tables\Table;
 
public function form(Form $form): Form
{
    return $form
        ->schema([
            Forms\Components\TextInput::make('description')
                ->required()
                ->maxLength(255),
        ]);
}
 
public function table(Table $table): Table
{
    return $table
        ->columns([
            Tables\Columns\TextColumn::make('description'),
        ]);
}

Перейдите на страницу редактирования одного из пациентов. Теперь у вас должна быть возможность создавать, редактировать, удалять и выводить список процедур для этого пациента.

Настройка формы лечения

По умолчанию текстовые поля занимают только половину ширины формы. Поскольку поле description может содержать много информации, добавьте метод columnSpan('full'), чтобы поле занимало всю ширину модальной формы:

use Filament\Forms;
 
Forms\Components\TextInput::make('description')
    ->required()
    ->maxLength(255)
    ->columnSpan('full')

Добавим поле notes, которое может быть использовано для добавления более подробной информации о лечении. Для этого мы можем использовать поле textarea с методом columnSpan('full'):

use Filament\Forms;
use Filament\Forms\Form;
 
public function form(Form $form): Form
{
    return $form
        ->schema([
            Forms\Components\TextInput::make('description')
                ->required()
                ->maxLength(255)
                ->columnSpan('full'),
            Forms\Components\Textarea::make('notes')
                ->maxLength(65535)
                ->columnSpan('full'),
        ]);
}

Настройка поля price

Добавим поле price для лечения. Мы можем использовать текстовый ввод с некоторыми настройками, чтобы сделать его пригодным для ввода валюты. Он должен быть numeric(), что добавляет валидацию и изменяет раскладку клавиатуры на мобильных устройствах. Добавьте желаемый префикс валюты с помощью метода prefix(), например, prefix('€') добавит перед вводимым значением, не влияя на сохраненное выходное значение:

use Filament\Forms;
use Filament\Forms\Form;
 
public function form(Form $form): Form
{
    return $form
        ->schema([
            Forms\Components\TextInput::make('description')
                ->required()
                ->maxLength(255)
                ->columnSpan('full'),
            Forms\Components\Textarea::make('notes')
                ->maxLength(65535)
                ->columnSpan('full'),
            Forms\Components\TextInput::make('price')
                ->numeric()
                ->prefix('€')
                ->maxValue(42949672.95),
        ]);
}

Приведение цены к целому числу

Filament хранит значения валют в виде целых чисел (а не плавающих), чтобы избежать проблем с округлением и точностью, что является общепринятым подходом в сообществе Laravel. Однако для этого необходимо создать в Laravel приведение, которое преобразует число float в integer при получении и обратно в integer при сохранении в базе данных. Для создания этого используется следующая команда artisan:

php artisan make:cast MoneyCast

Внутри нового файла app/Casts/MoneyCast.php обновите методы get() и set():

public function get($model, string $key, $value, array $attributes): float
{
    // Преобразование integer, хранящегося в базе данных, во float.
    return round(floatval($value) / 100, precision: 2);
}
 
public function set($model, string $key, $value, array $attributes): float
{
    // Преобразование числа float в integer для хранения.
    return round(floatval($value) * 100);
}

Теперь добавьте MoneyCast к атрибуту price в модели Treatment:

use App\Casts\MoneyCast;
use Illuminate\Database\Eloquent\Model;
 
class Treatment extends Model
{
    protected $casts = [
        'price' => MoneyCast::class,
    ];
 
    // ...
}

Настройка таблицы лечения

При предыдущей генерации менеджера отношений колонка с текстом описания была добавлена автоматически. Добавим также столбец sortable() для price с префиксом валюты. С помощью метода Filament money() отформатируем колонку price в денежном виде - в данном случае в EUR (€):

use Filament\Tables;
use Filament\Tables\Table;
 
public function table(Table $table): Table
{
    return $table
        ->columns([
            Tables\Columns\TextColumn::make('description'),
            Tables\Columns\TextColumn::make('price')
                ->money('EUR')
                ->sortable(),
        ]);
}

Добавим также столбец, указывающий на то, когда было назначено лечение, используя стандартную временную метку created_at. Для отображения даты-времени в удобочитаемом формате воспользуемся методом dateTime():

use Filament\Tables\Table;
 
public function table(Table $table): Table
{
    return $table
        ->columns([
            Tables\Columns\TextColumn::make('description'),
            Tables\Columns\TextColumn::make('price')
                ->money('EUR')
                ->sortable(),
            Tables\Columns\TextColumn::make('created_at')
                ->dateTime(),
        ]);
}

В метод dateTime() можно передать любую допустимую в PHP строку форматирования даты (например, dateTime('m-d-Y h:i A')).

Внедрение виджетов

Виджеты Filament — это компоненты, отображающие информацию на панели инструментов, в частности статистику. Виджеты обычно добавляются в Панель инструментов по умолчанию, но их можно добавить на любую страницу, включая страницы для всех пользователей. Filament включает в себя такие встроенные виджеты, как виджет статистики, позволяющий отображать важную статистику в виде простого обзора; виджет диаграмм, позволяющий отображать интерактивный график; и виджет таблиц, позволяющий легко встраивать конструктор таблиц.

Давайте добавим виджет статистики на нашу стандартную страницу рабочей панели, которая включает статистику для каждого типа пациентов и диаграмму для визуализации назначенного лечения в течение определенного времени.

Создание виджета статистики.

Создать виджет статистики для отображения типов пациентов можно с помощью следующей команды artisan:

php artisan make:filament-widget PatientTypeOverview --stats-overview

При появлении запроса не указывайте ресурс и выберите "admin" в качестве местоположения.

В результате будет создан новый файл app/Filament/Widgets/PatientTypeOverview.php. Откройте его и верните экземпляры Stat из метода getStats():

namespace App\Filament\Widgets;
 
use App\Models\Patient;
use Filament\Widgets\StatsOverviewWidget as BaseWidget;
use Filament\Widgets\StatsOverviewWidget\Stat;
 
class PatientTypeOverview extends BaseWidget
{
    protected function getStats(): array
    {
        return [
            Stat::make('Cats', Patient::query()->where('type', 'cat')->count()),
            Stat::make('Dogs', Patient::query()->where('type', 'dog')->count()),
            Stat::make('Rabbits', Patient::query()->where('type', 'rabbit')->count()),
        ];
    }
}

Откройте свою рабочую панель, и на ней должен появиться новый виджет. Каждая статистика должна показывать общее количество пациентов для указанного типа.

Создание виджета графиков.

Добавим на приборную панель график для визуализации количества назначенных процедур с течением времени. Для создания нового виджета диаграммы воспользуйтесь следующей командой artisan:

php artisan make:filament-widget TreatmentsChart --chart

При появлении запроса не указывайте ресурс, выберите "admin" для местоположения и выберите "линейный график" ("line chart") в качестве типа графика.

Откройте файл app/Filament/Widgets/TreatmentsChart.php и установите для диаграммы заголовок $heading в значение "Treatments".

Метод getData() возвращает массив наборов данных и меток. Каждый набор данных представляет собой маркированный массив точек для построения графика, а каждая метка является строкой. Эта структура идентична библиотеке Chart.js, которую Filament использует для построения графиков.

Для наполнения графиков данными из модели Eloquent Filament рекомендует установить пакет flowframe/laravel-trend:

composer require flowframe/laravel-trend

Обновите getData() для отображения количества процедур в месяц за последний год:

use App\Models\Treatment;
use Flowframe\Trend\Trend;
use Flowframe\Trend\TrendValue;
 
protected function getData(): array
{
    $data = Trend::model(Treatment::class)
        ->between(
            start: now()->subYear(),
            end: now(),
        )
        ->perMonth()
        ->count();
 
    return [
        'datasets' => [
            [
                'label' => 'Treatments',
                'data' => $data->map(fn (TrendValue $value) => $value->aggregate),
            ],
        ],
        'labels' => $data->map(fn (TrendValue $value) => $value->date),
    ];
}

Теперь посмотрите на свой новый виджет графика на приборной панели!

На странице приборной панели можно настроить сетку и количество отображаемых виджетов.

Дальнейшие действия с конструктором панелей

Поздравляем! Теперь, когда вы знаете, как создать базовое приложение Filament, вот некоторые рекомендации по дальнейшему обучению:

Перевод с английского официальной документации Filament 3:
https://filamentphp.com/docs/3.x/panels/getting-started

Заберите ссылку на статью к себе, чтобы потом легко её найти!
Раз уж досюда дочитали, то может может есть желание рассказать об этом месте своим друзьям, знакомым и просто мимо проходящим?
Не надо себя сдерживать! ;)

Старт! Горячий старт на просторы интернета
Старт! Горячий старт на просторы интернета
Старт! Меню