You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

36 KiB

у меня есть AirFlow класстер и github репозиторий для хранения DAGs. Я хочу написать Laravel Jetstream Livewire App для учета и загрузки DAGs в репозиторий чтобы AirFlow мог получить к ним доступ. Никто из пользователей не имеет доступ к Github репозиторию. Доступ осуществляется только через Laravel App в рамках предоставленных прав пользователя. Также я хотел бы видеть статусы выполнения и очередь пользовательского загруженного DAG в Laravel App. Обобщи следующие предложения и напиши финальный пошаговый план для реализации моей задачи:

1. Настройка Laravel Jetstream с Livewire

  • Установка Laravel:

    composer create-project laravel/laravel airflow-dag-manager
    
  • Установка Jetstream с Livewire:

    composer require laravel/jetstream
    php artisan jetstream:install livewire
    npm install
    npm run dev
    php artisan migrate
    

2. Реализация аутентификации и авторизации пользователей

  • Аутентификация:

    Jetstream предоставляет готовые механизмы регистрации, входа и восстановления пароля.

  • Авторизация:

    • Определите роли и разрешения пользователей.

    • Используйте Policies или Gates Laravel для контроля доступа.

    • Рекомендуется использовать пакет Spatie Laravel-Permission:

      composer require spatie/laravel-permission
      
    • Настройте роли (например, admin, user) и разрешения (upload dag, view status).


3. Интеграция с GitHub API для загрузки DAG-файлов

  • Аутентификация GitHub API:

    • Создайте Personal Access Token с необходимыми правами (repo для приватных репозиториев).

    • Сохраните токен в файле .env:

      GITHUB_TOKEN=ваш_токен
      
  • Использование GitHub API:

    • Используйте пакет knplabs/github-api или Guzzle для взаимодействия с API.

      composer require knplabs/github-api
      
    • Пример загрузки файла в репозиторий:

      use Github\Client;
      
      $client = new Client();
      $client->authenticate(env('GITHUB_TOKEN'), null, Client::AUTH_ACCESS_TOKEN);
      
      $username = аш_логин';
      $repository = 'имя_репозитория';
      $branch = 'main';
      $path = 'dags/' . $dagFilename;
      $content = file_get_contents($uploadedDagFile);
      
      // Получение текущего SHA коммита
      $reference = $client->api('gitData')->references()->show($username, $repository, 'heads/' . $branch);
      $commitSha = $reference['object']['sha'];
      
      // Создание нового блоба
      $blob = $client->api('gitData')->blobs()->create($username, $repository, [
          'content'  => base64_encode($content),
          'encoding' => 'base64',
      ]);
      
      // Создание нового дерева
      $tree = $client->api('gitData')->trees()->create($username, $repository, [
          'base_tree' => $commitSha,
          'tree' => [
              [
                  'path' => $path,
                  'mode' => '100644',
                  'type' => 'blob',
                  'sha' => $blob['sha'],
              ],
          ],
      ]);
      
      // Создание нового коммита
      $commit = $client->api('gitData')->commits()->create($username, $repository, [
          'message' => 'Добавлен DAG ' . $dagFilename,
          'tree' => $tree['sha'],
          'parents' => [$commitSha],
      ]);
      
      // Обновление ссылки
      $client->api('gitData')->references()->update($username, $repository, 'heads/' . $branch, [
          'sha' => $commit['sha'],
      ]);
      
  • Отслеживание загруженных DAG:

  • Создайте таблицу dags в базе данных с полями id, user_id, filename, created_at и т.д.

5. Получение статусов выполнения и очереди из Airflow

  • API Airflow:

  • Включите REST API в airflow.cfg:

    [api]
    auth_backend = airflow.api.auth.backend.basic_auth
    
  • Аутентификация:

    • Используйте Basic Auth или другой поддерживаемый метод.
  • Получение статусов DAG:

    use Illuminate\Support\Facades\Http;
    
    $response = Http::withBasicAuth('airflow_username', 'airflow_password')
        ->get('http://airflow-server:8080/api/v1/dags/{dag_id}/dagRuns');
    
    $dagRuns = $response->json();
    
  1. Компонент для загрузки DAG-файлов 1.1. Создание компонента Создайте Livewire-компонент UploadDagComponent:
php artisan make:livewire UploadDagComponent

1.2. Код компонента app/Http/Livewire/UploadDagComponent.php

<?php

namespace App\Http\Livewire;

use Livewire\Component;
use Livewire\WithFileUploads;
use Illuminate\Support\Facades\Auth;
use Github\Client;
use Illuminate\Support\Facades\Storage;

class UploadDagComponent extends Component
{
    use WithFileUploads;

    public $dagFile;

    protected $rules = [
        'dagFile' => 'required|file|mimes:py|max:1024', // Максимальный размер 1MB
    ];

    public function uploadDag()
    {
        $this->validate();

        // Сохранение файла временно
        $path = $this->dagFile->store('temp_dags');

        $filename = $this->dagFile->getClientOriginalName();
        $content = Storage::get($path);

        // Интеграция с GitHub API
        $client = new Client();
        $client->authenticate(env('GITHUB_TOKEN'), null, Client::AUTH_ACCESS_TOKEN);

        $username = env('GITHUB_USERNAME');
        $repository = env('GITHUB_REPOSITORY');
        $branch = 'main';
        $dagPath = 'dags/' . $filename;

        // Получение SHA последнего коммита
        $reference = $client->api('gitData')->references()->show($username, $repository, 'heads/' . $branch);
        $commitSha = $reference['object']['sha'];

        // Создание нового блоба
        $blob = $client->api('gitData')->blobs()->create($username, $repository, [
            'content'  => base64_encode($content),
            'encoding' => 'base64',
        ]);

        // Создание нового дерева
        $tree = $client->api('gitData')->trees()->create($username, $repository, [
            'base_tree' => $commitSha,
            'tree' => [
                [
                    'path' => $dagPath,
                    'mode' => '100644',
                    'type' => 'blob',
                    'sha' => $blob['sha'],
                ],
            ],
        ]);

        // Создание нового коммита
        $commit = $client->api('gitData')->commits()->create($username, $repository, [
            'message' => 'Добавлен DAG ' . $filename,
            'tree' => $tree['sha'],
            'parents' => [$commitSha],
        ]);

        // Обновление ссылки
        $client->api('gitData')->references()->update($username, $repository, 'heads/' . $branch, [
            'sha' => $commit['sha'],
        ]);

        // Удаление временного файла
        Storage::delete($path);

        // Сохранение информации о DAG в базе данных
        Auth::user()->dags()->create([
            'filename' => $filename,
            'commit_sha' => $commit['sha'],
        ]);

        session()->flash('message', 'DAG успешно загружен и отправлен в репозиторий GitHub.');

        // Обновление компонента списка DAG
        $this->emit('dagUploaded');
    }

    public function render()
    {
        return view('livewire.upload-dag-component');
    }
}

1.3. Шаблон компонента resources/views/livewire/upload-dag-component.blade.php

<div>
    @if (session()->has('message'))
        <div class="alert alert-success">
            {{ session('message') }}
        </div>
    @endif

    <form wire:submit.prevent="uploadDag" enctype="multipart/form-data">
        <div class="form-group">
            <label for="dagFile">Выберите DAG-файл (.py):</label>
            <input type="file" wire:model="dagFile" id="dagFile" class="form-control">
            @error('dagFile') <span class="text-danger">{{ $message }}</span> @enderror
        </div>

        <button type="submit" class="btn btn-primary mt-2">Загрузить DAG</button>
    </form>
</div>

1.4. Обновление модели пользователя Добавьте связь между пользователем и DAG в модели User.

app/Models/User.php

public function dags()
{
    return $this->hasMany(Dag::class);
}
  1. Компонент для отображения списка DAG-файлов 2.1. Создание компонента Создайте Livewire-компонент DagListComponent:
php artisan make:livewire DagListComponent

2.2. Код компонента app/Http/Livewire/DagListComponent.php

<?php

namespace App\Http\Livewire;

use Livewire\Component;
use Illuminate\Support\Facades\Auth;

class DagListComponent extends Component
{
    protected $listeners = ['dagUploaded' => 'render'];

    public function render()
    {
        $dags = Auth::user()->dags()->latest()->get();

        return view('livewire.dag-list-component', [
            'dags' => $dags,
        ]);
    }
}

2.3. Шаблон компонента resources/views/livewire/dag-list-component.blade.php

<div>
    <h3>Мои DAG-файлы</h3>

    <table class="table">
        <thead>
            <tr>
                <th>Имя файла</th>
                <th>Дата загрузки</th>
            </tr>
        </thead>
        <tbody>
            @foreach($dags as $dag)
                <tr>
                    <td>{{ $dag->filename }}</td>
                    <td>{{ $dag->created_at->format('d.m.Y H:i') }}</td>
                </tr>
            @endforeach
        </tbody>
    </table>
</div>
  1. Компонент для отображения статусов DAG из Airflow 3.1. Создание компонента Создайте Livewire-компонент DagStatusComponent:
php artisan make:livewire DagStatusComponent

3.2. Код компонента app/Http/Livewire/DagStatusComponent.php

<?php

namespace App\Http\Livewire;

use Livewire\Component;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Http;

class DagStatusComponent extends Component
{
    public $dagRuns = [];

    public function mount()
    {
        $this->fetchDagStatuses();
    }

    public function fetchDagStatuses()
    {
        $airflowUrl = env('AIRFLOW_API_URL');
        $username = env('AIRFLOW_USERNAME');
        $password = env('AIRFLOW_PASSWORD');

        $userDags = Auth::user()->dags()->pluck('filename')->toArray();

        $dagIds = array_map(function($filename) {
            return pathinfo($filename, PATHINFO_FILENAME);
        }, $userDags);

        $dagRuns = [];

        foreach ($dagIds as $dagId) {
            $response = Http::withBasicAuth($username, $password)
                ->get("$airflowUrl/api/v1/dags/$dagId/dagRuns");

            if ($response->ok()) {
                $dagRuns[$dagId] = $response->json()['dag_runs'];
            } else {
                $dagRuns[$dagId] = [];
            }
        }

        $this->dagRuns = $dagRuns;
    }

    public function render()
    {
        return view('livewire.dag-status-component');
    }
}

3.3. Шаблон компонента resources/views/livewire/dag-status-component.blade.php

<div wire:poll.60000ms="fetchDagStatuses">
    <h3>Статусы выполнения DAG</h3>

    @foreach($dagRuns as $dagId => $runs)
        <h4>DAG ID: {{ $dagId }}</h4>
        <table class="table">
            <thead>
                <tr>
                    <th>Run ID</th>
                    <th>Старт</th>
                    <th>Завершение</th>
                    <th>Статус</th>
                </tr>
            </thead>
            <tbody>
                @foreach($runs as $run)
                    <tr>
                        <td>{{ $run['dag_run_id'] }}</td>
                        <td>{{ $run['start_date'] }}</td>
                        <td>{{ $run['end_date'] }}</td>
                        <td>{{ $run['state'] }}</td>
                    </tr>
                @endforeach
            </tbody>
        </table>
    @endforeach
</div>
  1. Маршруты и отображение компонентов 4.1. Обновление маршрутов routes/web.php
Route::middleware(['auth:sanctum', 'verified'])->group(function () {
    Route::get('/dashboard', function () {
        return view('dashboard');
    })->name('dashboard');
});

4.2. Обновление шаблона панели управления resources/views/dashboard.blade.php

@extends('layouts.app')

@section('content')
<div class="container">
    <h2>Панель управления</h2>

    @livewire('upload-dag-component')

    @livewire('dag-list-component')

    @livewire('dag-status-component')
</div>
@endsection
  1. Модель DAG 5.1. Создание модели и миграции Создайте модель Dag с миграцией:
php artisan make:model Dag -m

5.2. Обновление миграции database/migrations/xxxx_xx_xx_create_dags_table.php

public function up()
{
    Schema::create('dags', function (Blueprint $table) {
        $table->id();
        $table->unsignedBigInteger('user_id');
        $table->string('filename');
        $table->string('commit_sha');
        $table->timestamps();

        $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
    });
}

5.3. Обновление модели DAG app/Models/Dag.php

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Dag extends Model
{
    use HasFactory;

    protected $fillable = [
        'user_id',
        'filename',
        'commit_sha',
    ];

    public function user()
    {
        return $this->belongsTo(User::class);
    }
}

5.4. Миграция базы данных

php artisan migrate
  1. Настройка переменных окружения Добавьте следующие переменные в файл .env:
GITHUB_TOKEN=ваш_токен
GITHUB_USERNAME=ваш_логин
GITHUB_REPOSITORY=имя_репозитория
AIRFLOW_API_URL=http://airflow-server:8080
AIRFLOW_USERNAME=airflow_username
AIRFLOW_PASSWORD=airflow_password
  1. Добавление авторизации (опционально) Для контроля доступа к компонентам и функциям используйте встроенные возможности Laravel или пакет Spatie Laravel-Permission.

8.3. Уведомления Реализуйте систему уведомлений при изменении статусов DAG (например, через Laravel Notifications).

  1. Компонент UploadDagComponent Описание:

Этот компонент отвечает за загрузку DAG-файлов пользователями и их сохранение в GitHub репозитории через GitHub API.

Основные функции:

Форма для загрузки файлов. Валидация загружаемых файлов. Обработка загрузки и отправка файла в GitHub репозиторий. Сохранение информации о DAG в базе данных. Реализация:

Создание компонента:

php artisan make:livewire UploadDagComponent

Свойства компонента:

$file для хранения загружаемого файла. $dagName имя DAG (если требуется). Методы компонента:

rules() для определения правил валидации:

protected $rules = [
    'file' => 'required|file|mimes:py|max:1024', // Максимум 1MB
];

uploadDag() основной метод для обработки загрузки:

Выполнить валидацию файла. Прочитать содержимое файла. Использовать GitHub API для загрузки файла в репозиторий (как описано в вашем основном коде). Сохранить информацию о DAG в базе данных, связав его с текущим пользователем (auth()->user()->id). Шаблон компонента (Blade):

Форма с полем для выбора файла:

<form wire:submit.prevent="uploadDag">
    <input type="file" wire:model="file">
    @error('file') <span class="error">{{ $message }}</span> @enderror
    <button type="submit">Загрузить DAG</button>
</form>

Отображение сообщений об успехе или ошибках.

  1. Компонент DagListComponent Описание:

Этот компонент отображает список DAG-файлов, загруженных текущим пользователем.

Основные функции:

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

Создание компонента:

bash Копировать код php artisan make:livewire DagListComponent Свойства компонента:

$dags коллекция DAG-файлов пользователя. Методы компонента:

mount() или render() для загрузки списка DAG:

public function render()
{
    $this->dags = Dag::where('user_id', auth()->id())->get();
    return view('livewire.dag-list-component');
}

deleteDag($dagId) метод для удаления DAG (если необходимо).

Шаблон компонента (Blade):

Таблица или список для отображения DAG:

<table>
    <thead>
        <tr>
            <th>Имя файла</th>
            <th>Дата загрузки</th>
            <th>Действия</th>
        </tr>
    </thead>
    <tbody>
        @foreach($dags as $dag)
            <tr>
                <td>{{ $dag->filename }}</td>
                <td>{{ $dag->created_at }}</td>
                <td>
                    <button wire:click="deleteDag({{ $dag->id }})">Удалить</button>
                </td>
            </tr>
        @endforeach
    </tbody>
</table>
  1. Компонент DagStatusComponent Описание:

Отображает текущий статус выполнения DAG, получая данные из Airflow API.

Основные функции:

Получение статусов DAG из Airflow API. Обновление статусов в реальном времени. Отображение статусов в удобном формате. Реализация:

Создание компонента:

bash Копировать код php artisan make:livewire DagStatusComponent Свойства компонента:

$dagStatuses массив статусов DAG. Методы компонента:

mount() для инициализации данных.

getDagStatuses() метод для обращения к Airflow API:

public function getDagStatuses()
{
    $dags = Dag::where('user_id', auth()->id())->get();
    foreach ($dags as $dag) {
        $response = Http::withBasicAuth('airflow_username', 'airflow_password')
            ->get("http://airflow-server:8080/api/v1/dags/{$dag->dag_id}/dagRuns");
        if ($response->successful()) {
            $this->dagStatuses[$dag->dag_id] = $response->json();
        }
    }
}

Вы можете использовать wire:poll для автоматического обновления статусов:

<div wire:poll.5000ms="getDagStatuses">
    <!-- отображение статусов -->
</div>

Шаблон компонента (Blade):

Отображение статусов DAG:

<ul>
    @foreach($dagStatuses as $dagId => $status)
        <li>DAG {{ $dagId }}: {{ $status['state'] }}</li>
    @endforeach
</ul>
  1. Компонент DagQueueComponent Описание:

Отображает очередь задач DAG для текущего пользователя, используя данные из Airflow API.

Основные функции:

Получение информации о запланированных и выполняемых задачах. Обновление данных в реальном времени. Реализация:

Создание компонента:

bash Копировать код php artisan make:livewire DagQueueComponent Свойства компонента:

$dagQueue массив задач в очереди. Методы компонента:

getDagQueue() обращение к Airflow API для получения очереди задач:

public function getDagQueue()
{
    $response = Http::withBasicAuth('airflow_username', 'airflow_password')
        ->get('http://airflow-server:8080/api/v1/queues');
    if ($response->successful()) {
        $this->dagQueue = $response->json();
    }
}

Используйте wire:poll для обновления данных:

<div wire:poll.5000ms="getDagQueue">
    <!-- отображение очереди -->
</div>

Шаблон компонента (Blade):

Отображение очереди задач:

<ul>
    @foreach($dagQueue as $task)
        @if(in_array($task['dag_id'], $userDagIds))
            <li>Задача {{ $task['task_id'] }} в DAG {{ $task['dag_id'] }}</li>
        @endif
    @endforeach
</ul>

Здесь $userDagIds массив dag_id текущего пользователя.

Общие рекомендации для компонентов Валидация и безопасность:

Всегда проверяйте права доступа, чтобы пользователи могли взаимодействовать только со своими данными. Используйте методы authorize() или проверки внутри методов компонентов. Обработка ошибок:

Обрабатывайте возможные исключения при работе с внешними API. Предоставляйте пользователям понятные сообщения об ошибках. Пользовательский интерфейс:

Делайте интерфейс интуитивно понятным и отзывчивым. Используйте стандартные компоненты UI или фреймворки CSS, такие как Tailwind CSS (который идет с Jetstream). Интеграция компонентов в приложение Маршруты и контроллеры:

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

Route::middleware(['auth:sanctum', 'verified'])->get('/dashboard', function () {
    return view('dashboard');
})->name('dashboard');

Шаблоны Blade:

Вставьте компоненты в соответствующие шаблоны.

<!-- resources/views/dashboard.blade.php -->
@extends('layouts.app')

@section('content')
    <div class="container mx-auto">
        <livewire:upload-dag-component />
        <livewire:dag-list-component />
        <livewire:dag-status-component />
        <livewire:dag-queue-component />
    </div>
@endsection

Дополнительные настройки Конфигурация Airflow API:

Убедитесь, что Airflow API доступен и настроен для приема запросов от вашего приложения.

Добавьте необходимые переменные в .env файл для хранения URL и данных аутентификации:

AIRFLOW_API_URL=http://airflow-server:8080/api/v1
AIRFLOW_API_USER=airflow_username
AIRFLOW_API_PASSWORD=airflow_password

Конфигурация GitHub API:

Обновите конфигурацию GitHub API, если требуется, и храните токен в .env файле:

GITHUB_TOKEN=ваш_токен
GITHUB_USERNAME=ваш_логин
GITHUB_REPOSITORY=имя_репозитория
GITHUB_BRANCH=main

Модели и миграции:

Создайте модель Dag и соответствующую миграцию:

bash Копировать код php artisan make:model Dag -m Поля таблицы dags могут включать:

id user_id dag_id или filename created_at updated_at Не забудьте установить связь между моделями User и Dag.

// В модели User
public function dags()
{
    return $this->hasMany(Dag::class);
}

// В модели Dag
public function user()
{
    return $this->belongsTo(User::class);
}

Заключение Следуя этим инструкциям, вы сможете создать полноценное приложение на Laravel Jetstream Livewire, которое позволит пользователям:

Загружать DAG-файлы без прямого доступа к GitHub репозиторию. Просматривать список своих DAG-файлов. Отслеживать статусы выполнения и очередь задач своих DAG в Airflow. Не забудьте протестировать каждую часть приложения и убедиться, что все компоненты работают корректно вместе.

Извиняюсь за недочет. Сейчас я опишу более конкретные шаги разработки приложения с финальным исходным кодом.

Полный пошаговый план разработки приложения с исходным кодом:


Шаг 1: Установка Laravel и Jetstream с Livewire

  1. Создание нового проекта Laravel:

    composer create-project laravel/laravel airflow-dag-manager
    
  2. Установка Jetstream с Livewire:

    composer require laravel/jetstream
    php artisan jetstream:install livewire
    npm install
    npm run dev
    php artisan migrate
    
  3. Настройка аутентификации: Jetstream автоматически добавит базовые функции аутентификации.


Шаг 2: Настройка ролей и прав доступа

  1. Установка пакета Spatie Laravel-Permission для управления ролями и правами доступа:

    composer require spatie/laravel-permission
    
  2. Публикация конфигурации и выполнение миграций:

    php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider"
    php artisan migrate
    
  3. Настройка ролей и прав: Добавьте роли, например, admin и user, а также права, такие как upload dag, view status. Пример настройки ролей можно добавить в seeder:

    use Spatie\Permission\Models\Role;
    use Spatie\Permission\Models\Permission;
    
    // В seeder:
    Role::create(['name' => 'admin']);
    Role::create(['name' => 'user']);
    
    Permission::create(['name' => 'upload dag']);
    Permission::create(['name' => 'view status']);
    

Шаг 3: Интеграция с GitHub API

  1. Создание GitHub Personal Access Token: Перейдите в GitHub, создайте новый токен с правами repo для доступа к репозиторию. Сохраните его в .env:

    GITHUB_TOKEN=ваш_токен
    GITHUB_USERNAME=ваш_логин
    GITHUB_REPOSITORY=имя_репозитория
    GITHUB_BRANCH=main
    
  2. Установка библиотеки для работы с GitHub API:

    composer require knplabs/github-api
    
  3. Создание метода для загрузки DAG в репозиторий:

    Создайте компонент UploadDagComponent для загрузки DAG:

    php artisan make:livewire UploadDagComponent
    

    Код компонента UploadDagComponent:

    <?php
    
    namespace App\Http\Livewire;
    
    use Livewire\Component;
    use Livewire\WithFileUploads;
    use Github\Client;
    use Illuminate\Support\Facades\Storage;
    
    class UploadDagComponent extends Component
    {
        use WithFileUploads;
    
        public $dagFile;
    
        protected $rules = [
            'dagFile' => 'required|file|mimes:py|max:1024',
        ];
    
        public function uploadDag()
        {
            $this->validate();
    
            // Сохранение временного файла
            $path = $this->dagFile->store('temp_dags');
    
            // Получение содержимого файла
            $filename = $this->dagFile->getClientOriginalName();
            $content = Storage::get($path);
    
            // Интеграция с GitHub API
            $client = new Client();
            $client->authenticate(env('GITHUB_TOKEN'), null, Client::AUTH_ACCESS_TOKEN);
    
            $username = env('GITHUB_USERNAME');
            $repository = env('GITHUB_REPOSITORY');
            $branch = env('GITHUB_BRANCH');
            $dagPath = 'dags/' . $filename;
    
            // Получение SHA последнего коммита
            $reference = $client->api('gitData')->references()->show($username, $repository, 'heads/' . $branch);
            $commitSha = $reference['object']['sha'];
    
            // Создание нового блоба
            $blob = $client->api('gitData')->blobs()->create($username, $repository, [
                'content'  => base64_encode($content),
                'encoding' => 'base64',
            ]);
    
            // Создание нового дерева
            $tree = $client->api('gitData')->trees()->create($username, $repository, [
                'base_tree' => $commitSha,
                'tree' => [
                    [
                        'path' => $dagPath,
                        'mode' => '100644',
                        'type' => 'blob',
                        'sha' => $blob['sha'],
                    ],
                ],
            ]);
    
            // Создание нового коммита
            $commit = $client->api('gitData')->commits()->create($username, $repository, [
                'message' => 'Добавлен DAG ' . $filename,
                'tree' => $tree['sha'],
                'parents' => [$commitSha],
            ]);
    
            // Обновление ссылки
            $client->api('gitData')->references()->update($username, $repository, 'heads/' . $branch, [
                'sha' => $commit['sha'],
            ]);
    
            // Удаление временного файла
            Storage::delete($path);
    
            // Сообщение об успехе
            session()->flash('message', 'DAG успешно загружен и отправлен в репозиторий GitHub.');
        }
    
        public function render()
        {
            return view('livewire.upload-dag-component');
        }
    }
    
  4. Шаблон компонента (Blade):

    <div>
        @if (session()->has('message'))
            <div class="alert alert-success">
                {{ session('message') }}
            </div>
        @endif
    
        <form wire:submit.prevent="uploadDag">
            <input type="file" wire:model="dagFile">
            @error('dagFile') <span class="text-danger">{{ $message }}</span> @enderror
            <button type="submit" class="btn btn-primary mt-2">Загрузить DAG</button>
        </form>
    </div>
    

Шаг 4: Синхронизация DAG с Airflow

  1. Настройка Airflow для получения DAG: Вы можете настроить Airflow для автоматического выполнения git pull либо использовать GitHub Actions для деплоя DAG на сервер Airflow.

Шаг 5: Получение статусов выполнения DAG через Airflow API

  1. Включение Airflow API: В airflow.cfg добавьте или активируйте следующие строки:

    [api]
    auth_backend = airflow.api.auth.backend.basic_auth
    
  2. Запросы к Airflow API для получения статусов DAG:

    Создайте компонент DagStatusComponent:

    php artisan make:livewire DagStatusComponent
    

    Код компонента DagStatusComponent:

    <?php
    
    namespace App\Http\Livewire;
    
    use Livewire\Component;
    use Illuminate\Support\Facades\Http;
    
    class DagStatusComponent extends Component
    {
        public $dagRuns = [];
    
        public function mount()
        {
            $this->fetchDagStatuses();
        }
    
        public function fetchDagStatuses()
        {
            $response = Http::withBasicAuth(env('AIRFLOW_USERNAME'), env('AIRFLOW_PASSWORD'))
                ->get(env('AIRFLOW_API_URL') . '/api/v1/dags/{dag_id}/dagRuns');
    
            if ($response->ok()) {
                $this->dagRuns = $response->json();
            }
        }
    
        public function render()
        {
            return view('livewire.dag-status-component');
        }
    }
    
  3. Шаблон компонента (Blade):

    <div wire:poll.60000ms="fetchDagStatuses">
        <h3>Статусы выполнения DAG</h3>
    
        <table class="table">
            <thead>
                <tr>
                    <th>DAG Run ID</th>
                    <th>Старт</th>
                    <th>Завершение</th>
                    <th>Статус</th>
                </tr>
            </thead>
            <tbody>
                @foreach($dagRuns as $run)
                    <tr>
                        <td>{{ $run['dag_run_id'] }}</td>
                        <td>{{ $run['start_date'] }}</td>
                        <td>{{ $run['end_date'] }}</td>
                        <td>{{ $run['state'] }}</td>
                    </tr>
                @endforeach
            </tbody>
        </table>
    </div>